def push_cib(self, custom_cib=None, wait=False): """ Push previously loaded instance of CIB or a custom CIB etree custom_cib -- push a custom CIB instead of a loaded instance (allows to push an externally provided CIB and replace the one in the cluster completely) mixed wait -- how many seconds to wait for pacemaker to process new CIB or False for not waiting at all """ if custom_cib is not None: if self.__loaded_cib_diff_source is not None: raise AssertionError( "CIB has been loaded, cannot push custom CIB") return self.__push_cib_full(custom_cib, wait) if self.__loaded_cib_diff_source is None: raise AssertionError("CIB has not been loaded") # Push by diff works with crm_feature_set > 3.0.8, see # https://bugzilla.redhat.com/show_bug.cgi?id=1488044 for details. We # only check the version if a CIB has been loaded, otherwise the push # fails anyway. By my testing it seems that only the source CIB's # version matters. if (self.__loaded_cib_diff_source_feature_set < MIN_FEATURE_SET_VERSION_FOR_DIFF): current_set = str( self.__loaded_cib_diff_source_feature_set.normalize()) self.report_processor.report( ReportItem.warning( reports.messages.CibPushForcedFullDueToCrmFeatureSet( str(MIN_FEATURE_SET_VERSION_FOR_DIFF.normalize()), current_set, ))) return self.__push_cib_full(self.__loaded_cib_to_modify, wait=wait) return self.__push_cib_diff(wait=wait)
def _log_response_failure(self, response): msg = "Unable to connect to {node} ({reason})" self._logger.debug( msg.format( node=response.request.host_label, reason=response.error_msg ) ) self._reporter.report( ReportItem.debug( reports.messages.NodeCommunicationNotConnected( response.request.host_label, response.error_msg, ) ) ) if is_proxy_set(os.environ): self._logger.warning("Proxy is set") self._reporter.report( ReportItem.warning( reports.messages.NodeCommunicationProxyIsSet( response.request.host_label, response.request.dest.addr, ) ) )
def _make_unique_intervals( report_processor: ReportProcessor, operation_list: Iterable[ResourceOperationFilteredIn], ) -> List[ResourceOperationFilteredOut]: """ Return operation list similar to operation_list where intervals for the same operation are unique report_processor -- tool for warning/info/error reporting operation_list -- contains operation definitions """ get_unique_interval = get_interval_uniquer() adapted_operation_list = [] for operation in operation_list: adapted = dict(operation) if "interval" in adapted: adapted["interval"] = get_unique_interval( operation["name"], operation["interval"] ) if adapted["interval"] != operation["interval"]: report_processor.report( ReportItem.warning( reports.messages.ResourceOperationIntervalAdapted( operation["name"], operation["interval"], adapted["interval"], ) ) ) adapted_operation_list.append(adapted) return adapted_operation_list
def find_valid_resource_id(report_processor: ReportProcessor, cib, in_clone_allowed, _id): parent_tags = resource.clone.ALL_TAGS + [resource.bundle.TAG] resource_element = find_element_by_tag_and_id( sorted(parent_tags + [resource.primitive.TAG, resource.group.TAG]), cib, _id, ) if resource_element.tag in parent_tags: return resource_element.attrib["id"] clone = find_parent(resource_element, parent_tags) if clone is None: return resource_element.attrib["id"] report_msg = reports.messages.ResourceForConstraintIsMultiinstance( resource_element.attrib["id"], "clone" if clone.tag == "master" else clone.tag, clone.attrib["id"], ) if in_clone_allowed: if report_processor.report(ReportItem.warning(report_msg)).has_errors: raise LibraryError() return resource_element.attrib["id"] raise LibraryError( ReportItem.error( report_msg, force_code=reports.codes.FORCE, ))
def _validate_generic_container_options(container_options, force_options=False): validators = [ validate.NamesIn( GENERIC_CONTAINER_OPTIONS, option_type="container", **validate.set_warning(report_codes.FORCE_OPTIONS, force_options), ), validate.IsRequiredAll(["image"], option_type="container"), validate.ValueNotEmpty("image", "image name"), validate.ValueNonnegativeInteger("masters"), validate.ValueNonnegativeInteger("promoted-max"), validate.MutuallyExclusive( ["masters", "promoted-max"], option_type="container", ), validate.ValuePositiveInteger("replicas"), validate.ValuePositiveInteger("replicas-per-host"), ] deprecation_reports = [] if "masters" in container_options: deprecation_reports.append( ReportItem.warning( reports.messages.DeprecatedOption( "masters", ["promoted-max"], "container", ))) return (validate.ValidatorAll(validators).validate(container_options) + deprecation_reports)
def make_unique_intervals(report_processor: ReportProcessor, operation_list): """ Return operation list similar to operation_list where intervals for the same operation are unique report_processor is tool for warning/info/error reporting list operation_list contains dictionaries with attributes of operation """ get_unique_interval = get_interval_uniquer() adapted_operation_list = [] for operation in operation_list: adapted = operation.copy() if "interval" in adapted: adapted["interval"] = get_unique_interval( operation["name"], operation["interval"] ) if adapted["interval"] != operation["interval"]: report_processor.report( ReportItem.warning( reports.messages.ResourceOperationIntervalAdapted( operation["name"], operation["interval"], adapted["interval"], ) ) ) adapted_operation_list.append(adapted) return adapted_operation_list
def get_authfile_name_and_data(booth_conf_facade): """ Get booth auth filename, content and reports based on booth config facade pcs.lib.booth.config_facade.ConfigFacade booth_conf_facade -- booth config """ authfile_name = None authfile_data = None report_list = [] authfile_path = booth_conf_facade.get_authfile() if authfile_path: authfile_dir, authfile_name = os.path.split(authfile_path) if (authfile_dir == settings.booth_config_dir) and authfile_name: authfile_data = FileInstance.for_booth_key( authfile_name).read_raw() else: authfile_name = None report_list.append( ReportItem.warning( reports.messages.BoothUnsupportedFileLocation( authfile_path, settings.booth_config_dir, file_type_codes.BOOTH_KEY, ))) return authfile_name, authfile_data, report_list
def log_retry(self, response, previous_dest): old_port = _get_port(previous_dest.port) new_port = _get_port(response.request.dest.port) msg = ( "Unable to connect to '{label}' via address '{old_addr}' and port " "'{old_port}'. Retrying request '{req}' via address '{new_addr}' " "and port '{new_port}'").format( label=response.request.host_label, old_addr=previous_dest.addr, old_port=old_port, new_addr=response.request.dest.addr, new_port=new_port, req=response.request.url, ) self._logger.warning(msg) self._reporter.report( ReportItem.warning( reports.messages.NodeCommunicationRetrying( response.request.host_label, previous_dest.addr, old_port, response.request.dest.addr, new_port, response.request.url, )))
def _process_response(self, response): report_item = response_to_report_item( response, severity=reports.ReportItemSeverity.WARNING) node = response.request.target.label if report_item is not None: self._has_failure = True self._report(report_item) return self._get_next_list() if response.data.strip() == "Cannot initialize CMAP service": # corosync is not running on the node, this is OK return self._get_next_list() try: quorum_status = corosync_live.QuorumStatus.from_string( response.data) if not quorum_status.is_quorate: return self._get_next_list() self._quorum_status = quorum_status except corosync_live.QuorumStatusParsingException as e: self._has_failure = True self._report( ReportItem.warning( reports.messages.CorosyncQuorumGetStatusError( e.reason, node=node, ))) return self._get_next_list() return []
def find_valid_resource_id(report_processor: ReportProcessor, cib, in_clone_allowed, _id): parent_tags = resource.clone.ALL_TAGS + [resource.bundle.TAG] resource_element = find_element_by_tag_and_id( sorted(parent_tags + [resource.primitive.TAG, resource.group.TAG]), cib, _id, ) if resource_element.tag in parent_tags: return resource_element.attrib["id"] clone = find_parent(resource_element, parent_tags) if clone is None: return resource_element.attrib["id"] report_msg = reports.messages.ResourceForConstraintIsMultiinstance( resource_element.attrib["id"], "clone" if clone.tag == "master" else clone.tag, clone.attrib["id"], ) if in_clone_allowed: if report_processor.report(ReportItem.warning(report_msg)).has_errors: raise LibraryError() return resource_element.attrib["id"] raise LibraryError( ReportItem.error( report_msg, # repair to clone is workaround for web ui, so we put only # information about one forceable possibility force_code=reports.codes.FORCE_CONSTRAINT_MULTIINSTANCE_RESOURCE, ))
def _defaults_create( env: LibraryEnvironment, cib_section_name: str, validator_options: Mapping[str, Any], nvpairs: Mapping[str, str], nvset_options: Mapping[str, str], nvset_rule: Optional[str] = None, force_flags: Optional[Container] = None, ) -> None: if force_flags is None: force_flags = set() force = (reports.codes.FORCE in force_flags) or (reports.codes.FORCE_OPTIONS in force_flags) required_cib_version = None nice_to_have_cib_version = None if nvset_rule: # Parse the rule to see if we need to upgrade CIB schema. All errors # would be properly reported by a validator called bellow, so we can # safely ignore them here. try: rule_tree = parse_rule(nvset_rule) if has_rsc_or_op_expression(rule_tree): required_cib_version = Version(3, 4, 0) if has_node_attr_expr_with_type_integer(rule_tree): nice_to_have_cib_version = Version(3, 5, 0) except RuleParseError: pass cib = env.get_cib( minimal_version=required_cib_version, nice_to_have_version=nice_to_have_cib_version, ) id_provider = IdProvider(cib) validator = nvpair_multi.ValidateNvsetAppendNew( id_provider, nvpairs, nvset_options, nvset_rule=nvset_rule, **validator_options, ) if env.report_processor.report_list( validator.validate(force_options=force)).has_errors: raise LibraryError() nvpair_multi.nvset_append_new( sections.get(cib, cib_section_name), id_provider, get_pacemaker_version_by_which_cib_was_validated(cib), nvpair_multi.NVSET_META, nvpairs, nvset_options, nvset_rule=validator.get_parsed_rule(), ) env.report_processor.report( ReportItem.warning(reports.messages.DefaultsCanBeOverriden())) env.push_cib()
def log_no_more_addresses(self, response): msg = "No more addresses for node {label} to run '{req}'".format( label=response.request.host_label, req=response.request.url, ) self._logger.warning(msg) self._reporter.report( ReportItem.warning( reports.messages.NodeCommunicationNoMoreAddresses( response.request.host_label, response.request.url, )))
def _process_response(self, response): report = response_to_report_item(response) if report is None: self._online_target_list.append(response.request.target) return if not response.was_connected: report = (ReportItem.warning( reports.messages.OmittingNode(response.request.target.label)) if self._ignore_offline_targets else response_to_report_item( response, forceable=report_codes.SKIP_OFFLINE_NODES)) self._report(report)
def _get_rule_evaluator( cib: _Element, runner: CommandRunner, report_processor: reports.ReportProcessor, evaluate_expired: bool, ) -> RuleInEffectEval: if evaluate_expired: if has_rule_in_effect_status_tool(): return RuleInEffectEvalOneByOne(cib, runner) report_processor.report( ReportItem.warning( reports.messages.RuleInEffectStatusDetectionNotSupported())) return RuleInEffectEvalDummy()
def _process_response(self, response): report_item = response_to_report_item( response, severity=ReportItemSeverity.WARNING) node_label = response.request.target.label if report_item is not None: self._report_list([ report_item, # reason is in previous report item, warning is there # implicit ReportItem.warning( reports.messages.UnableToGetSbdStatus(node_label, "")), ]) return try: self._status_list.append({ "node": node_label, "status": json.loads(response.data)["sbd"] }) self._successful_target_list.append(node_label) except (ValueError, KeyError) as e: self._report( ReportItem.warning( reports.messages.UnableToGetSbdStatus(node_label, str(e))))
def _process_response(self, response): report_item = response_to_report_item( response, severity=ReportItemSeverity.WARNING) node = response.request.target.label if report_item is not None: self.__has_failures = True self._report(report_item) return self._get_next_list() try: output = json.loads(response.data) if output["code"] == "reloaded": self.__was_successful = True self._report( ReportItem.info( reports.messages.CorosyncConfigReloaded(node))) return [] if output["code"] == "not_running": self._report( ReportItem.warning( reports.messages.CorosyncConfigReloadNotPossible( node))) else: self.__has_failures = True self._report( ReportItem.warning( reports.messages.CorosyncConfigReloadError( output["message"], node=node, ))) except (ValueError, LookupError): self.__has_failures = True self._report( ReportItem.warning( reports.messages.InvalidResponseFormat(node))) return self._get_next_list()
def _process_response(self, response): report_item = response_to_report_item( response, severity=ReportItemSeverity.WARNING) if report_item is not None: self._report(report_item) return self._get_next_list() node = response.request.target.label try: output = json.loads(response.data) if output["status"] == "success": self._was_successful = True self._cluster_status = output["data"] return [] if output["status_msg"]: self._report( ReportItem.error( reports.messages.NodeCommunicationCommandUnsuccessful( node, response.request.action, output["status_msg"], ))) # TODO Node name should be added to each received report item and # those modified report itemss should be reported. That, however, # requires reports overhaul which would add possibility to add a # node name to any report item. Also, infos and warnings should not # be ignored. if output["report_list"]: for report_data in output["report_list"]: if (report_data["severity"] == ReportItemSeverity.ERROR and report_data["report_text"]): # pylint: disable=line-too-long self._report( ReportItem.error( reports.messages. NodeCommunicationCommandUnsuccessful( node, response.request.action, report_data["report_text"], ))) except (ValueError, LookupError, TypeError): self._report( ReportItem.warning( reports.messages.InvalidResponseFormat(node))) return self._get_next_list()
def _process_response(self, response): report_item = response_to_report_item( response, severity=ReportItemSeverity.WARNING) node_label = response.request.target.label if report_item is not None: if not response.was_connected: self._report(report_item) self._report( ReportItem.warning( reports.messages.UnableToGetSbdConfig(node_label, ""))) return self._config_list.append({ "node": node_label, "config": environment_file_to_dict(response.data), }) self._successful_target_list.append(node_label)
def _defaults_create( env: LibraryEnvironment, cib_section_name: str, validator_options: Mapping[str, Any], nvpairs: Mapping[str, str], nvset_options: Mapping[str, str], nvset_rule: Optional[str] = None, force_flags: Optional[Container] = None, ) -> None: if force_flags is None: force_flags = set() force = (reports.codes.FORCE in force_flags) or (reports.codes.FORCE_OPTIONS in force_flags) required_cib_version = None if nvset_rule: required_cib_version = Version(3, 4, 0) cib = env.get_cib(required_cib_version) id_provider = IdProvider(cib) validator = nvpair_multi.ValidateNvsetAppendNew( id_provider, nvpairs, nvset_options, nvset_rule=nvset_rule, **validator_options, ) if env.report_processor.report_list( validator.validate(force_options=force)).has_errors: raise LibraryError() nvpair_multi.nvset_append_new( sections.get(cib, cib_section_name), id_provider, nvpair_multi.NVSET_META, nvpairs, nvset_options, nvset_rule=validator.get_parsed_rule(), ) env.report_processor.report( ReportItem.warning(reports.messages.DefaultsCanBeOverriden())) env.push_cib()
def disable_sbd(lib_env, ignore_offline_nodes=False): """ Disable SBD on all nodes in cluster. lib_env -- LibraryEnvironment ignore_offline_nodes -- if True, omit offline nodes """ node_list, get_nodes_report_list = get_existing_nodes_names( lib_env.get_corosync_conf() ) if not node_list: get_nodes_report_list.append( ReportItem.error(reports.messages.CorosyncConfigNoNodesDefined()) ) if lib_env.report_processor.report_list(get_nodes_report_list).has_errors: raise LibraryError() com_cmd = GetOnlineTargets( lib_env.report_processor, ignore_offline_targets=ignore_offline_nodes, ) com_cmd.set_targets( lib_env.get_node_target_factory().get_target_list( node_list, skip_non_existing=ignore_offline_nodes, ) ) online_nodes = run_and_raise(lib_env.get_node_communicator(), com_cmd) com_cmd = SetStonithWatchdogTimeoutToZero(lib_env.report_processor) com_cmd.set_targets(online_nodes) run_and_raise(lib_env.get_node_communicator(), com_cmd) com_cmd = DisableSbdService(lib_env.report_processor) com_cmd.set_targets(online_nodes) run_and_raise(lib_env.get_node_communicator(), com_cmd) lib_env.report_processor.report( ReportItem.warning( reports.messages.ClusterRestartRequiredToApplyChanges() ) )
def update_device( lib_env: LibraryEnvironment, model_options, generic_options, heuristics_options, force_options=False, skip_offline_nodes=False, ): """ Change quorum device settings, distribute and reload configs if live dict model_options -- model specific options dict generic_options -- generic quorum device options dict heuristics_options -- heuristics options bool force_options -- continue even if options are not valid bool skip_offline_nodes -- continue even if not all nodes are accessible """ cfg = lib_env.get_corosync_conf() if not cfg.has_quorum_device(): raise LibraryError( ReportItem.error(reports.messages.QdeviceNotDefined()) ) if lib_env.report_processor.report_list( corosync_conf_validators.update_quorum_device( cfg.get_quorum_device_model(), model_options, generic_options, heuristics_options, [node.nodeid for node in cfg.get_nodes()], force_options=force_options, ) ).has_errors: raise LibraryError() cfg.update_quorum_device(model_options, generic_options, heuristics_options) if cfg.is_quorum_device_heuristics_enabled_with_no_exec(): lib_env.report_processor.report( ReportItem.warning( reports.messages.CorosyncQuorumHeuristicsEnabledWithNoExec() ) ) lib_env.push_corosync_conf(cfg, skip_offline_nodes)
def _set_any_defaults(section_name, env: LibraryEnvironment, options): """ string section_name -- determine the section of defaults env -- provides access to outside environment dict options -- are desired options with its values; when value is empty the option have to be removed """ # Do not ever remove the nvset element, even if it is empty. There may be # ACLs set in pacemaker which allow "write" for nvpairs (adding, changing # and removing) but not nvsets. In such a case, removing the nvset would # cause the whole change to be rejected by pacemaker with a "permission # denied" message. # https://bugzilla.redhat.com/show_bug.cgi?id=1642514 env.report_processor.report( ReportItem.warning(reports.messages.DefaultsCanBeOverriden())) if not options: return cib = env.get_cib() # Do not create new defaults element if we are only removing values from it. only_removing = True for value in options.values(): if value != "": only_removing = False break if only_removing and not sections.exists(cib, section_name): return defaults_section = sections.get(cib, section_name) arrange_first_meta_attributes( defaults_section, options, IdProvider(cib), new_id="{0}-options".format(section_name), ) env.push_cib()
def _defaults_config(env: LibraryEnvironment, cib_section_name: str, evaluate_expired: bool) -> List[CibNvsetDto]: runner = env.cmd_runner() cib = env.get_cib() if evaluate_expired: if has_rule_in_effect_status_tool(): in_effect_eval: RuleInEffectEval = RuleInEffectEvalOneByOne( cib, runner) else: in_effect_eval = RuleInEffectEvalDummy() env.report_processor.report( ReportItem.warning(reports.messages. RuleInEffectStatusDetectionNotSupported())) else: in_effect_eval = RuleInEffectEvalDummy() return [ nvpair_multi.nvset_element_to_dto(nvset_el, in_effect_eval) for nvset_el in nvpair_multi.find_nvsets( sections.get(cib, cib_section_name)) ]
def uniquify_operations_intervals( operation_list: Iterable[CibResourceOperationDto], ) -> Tuple[reports.ReportItemList, List[CibResourceOperationDto]]: """ Return list of operation where intervals for the same operation are unique operation_list -- operations the new operation list will be based on """ get_unique_interval = _get_interval_uniquer() report_list = [] new_operations = [] for operation in operation_list: new_interval = get_unique_interval(operation.name, operation.interval) if new_interval != operation.interval: report_list.append( ReportItem.warning( reports.messages.ResourceOperationIntervalAdapted( operation.name, operation.interval, new_interval, ))) operation = dt_replace(operation, interval=new_interval) new_operations.append(operation) return report_list, new_operations
def add_device( lib_env: LibraryEnvironment, model, model_options, generic_options, heuristics_options, force_model=False, force_options=False, skip_offline_nodes=False, ): # pylint: disable=too-many-locals """ Add a quorum device to a cluster, distribute and reload configs if live string model -- quorum device model dict model_options -- model specific options dict generic_options -- generic quorum device options dict heuristics_options -- heuristics options bool force_model -- continue even if the model is not valid bool force_options -- continue even if options are not valid bool skip_offline_nodes -- continue even if not all nodes are accessible """ cfg = lib_env.get_corosync_conf() if cfg.has_quorum_device(): raise LibraryError( ReportItem.error(reports.messages.QdeviceAlreadyDefined())) report_processor = lib_env.report_processor report_processor.report_list( corosync_conf_validators.add_quorum_device( model, model_options, generic_options, heuristics_options, [node.nodeid for node in cfg.get_nodes()], force_model=force_model, force_options=force_options, )) if lib_env.is_corosync_conf_live: cluster_nodes_names, report_list = get_existing_nodes_names( cfg, # Pcs is unable to communicate with nodes missing names. It cannot # send new corosync.conf to them. That might break the cluster. # Hence we error out. error_on_missing_name=True, ) report_processor.report_list(report_list) if report_processor.has_errors: raise LibraryError() cfg.add_quorum_device( model, model_options, generic_options, heuristics_options, ) if cfg.is_quorum_device_heuristics_enabled_with_no_exec(): lib_env.report_processor.report( ReportItem.warning( reports.messages.CorosyncQuorumHeuristicsEnabledWithNoExec())) # First setup certificates for qdevice, then send corosync.conf to nodes. # If anything fails, nodes will not have corosync.conf with qdevice in it, # so there is no effect on the cluster. if lib_env.is_corosync_conf_live: target_factory = lib_env.get_node_target_factory() target_list = target_factory.get_target_list( cluster_nodes_names, skip_non_existing=skip_offline_nodes, ) # Do model specific configuration. # If the model is not known to pcs and was forced, do not configure # anything else than corosync.conf, as we do not know what to do # anyway. if model == "net": qdevice_net.set_up_client_certificates( lib_env.cmd_runner(), lib_env.report_processor, lib_env.communicator_factory, # We are sure the "host" key is there, it has been validated # above. target_factory.get_target_from_hostname(model_options["host"]), cfg.get_cluster_name(), target_list, skip_offline_nodes, ) lib_env.report_processor.report( ReportItem.info( reports.messages.ServiceActionStarted( reports.const.SERVICE_ACTION_ENABLE, "corosync-qdevice"))) com_cmd = qdevice_com.Enable(lib_env.report_processor, skip_offline_nodes) com_cmd.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd) # everything set up, it's safe to tell the nodes to use qdevice lib_env.push_corosync_conf(cfg, skip_offline_nodes) # Now, when corosync.conf has been reloaded, we can start qdevice service. if lib_env.is_corosync_conf_live: lib_env.report_processor.report( ReportItem.info( reports.messages.ServiceActionStarted( reports.const.SERVICE_ACTION_START, "corosync-qdevice"))) com_cmd_start = qdevice_com.Start(lib_env.report_processor, skip_offline_nodes) com_cmd_start.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd_start)
def on_complete(self): if self._unreachable_nodes: self._report( ReportItem.warning( reports.messages.NodesToRemoveUnreachable( sorted(self._unreachable_nodes))))
def _validate_generic_container_options_update(container_el, options, force_options): validators_optional_options = [ validate.ValueNonnegativeInteger("masters"), validate.ValueNonnegativeInteger("promoted-max"), validate.ValuePositiveInteger("replicas"), validate.ValuePositiveInteger("replicas-per-host"), ] for val in validators_optional_options: val.empty_string_valid = True validators = [ validate.NamesIn( # allow to remove options even if they are not allowed GENERIC_CONTAINER_OPTIONS | _options_to_remove(options), option_type="container", **validate.set_warning(report_codes.FORCE_OPTIONS, force_options), ), # image is a mandatory attribute and cannot be removed validate.ValueNotEmpty("image", "image name"), ] + validators_optional_options # CIB does not allow both to be set. Deleting both is not a problem, # though. Deleting one while setting another also works and is further # checked bellow. if not (options.get("masters", "") == "" or options.get("promoted-max", "") == ""): validators.append( validate.MutuallyExclusive( ["masters", "promoted-max"], option_type="container", )) deprecation_reports = [] if options.get("masters"): # If the user wants to delete the masters option, do not report it is # deprecated. They may be removing it because they just found out it is # deprecated. deprecation_reports.append( ReportItem.warning( reports.messages.DeprecatedOption( "masters", ["promoted-max"], "container", ))) # Do not allow to set masters if promoted-max is set unless promoted-max is # going to be removed now. Do the same check also the other way around. CIB # only allows one of them to be set. if (options.get("masters") and container_el.get("promoted-max") and options.get("promoted-max") != ""): deprecation_reports.append( ReportItem.error( reports.messages.PrerequisiteOptionMustNotBeSet( "masters", "promoted-max", "container", "container"))) if (options.get("promoted-max") and container_el.get("masters") and options.get("masters") != ""): deprecation_reports.append( ReportItem.error( reports.messages.PrerequisiteOptionMustNotBeSet( "promoted-max", "masters", "container", "container"))) return (validate.ValidatorAll(validators).validate(options) + deprecation_reports)
def _defaults_update( env: LibraryEnvironment, cib_section_name: str, nvset_id: Optional[str], nvpairs: Mapping[str, str], pcs_command: reports.types.PcsCommand, ) -> None: cib = env.get_cib() id_provider = IdProvider(cib) if nvset_id is None: # Backward compatibility code to support an old use case where no id # was requested and provided and the first meta_attributes nvset was # created / updated. However, we check that there is only one nvset # present in the CIB to prevent breaking the configuration with # multiple nvsets in place. # This is to be supported as it provides means of easily managing # defaults if only one set of defaults is needed. # TODO move this to a separate lib command. if not nvpairs: return # Do not create new defaults element if we are only removing values # from it. only_removing = True for value in nvpairs.values(): if value != "": only_removing = False break if only_removing and not sections.exists(cib, cib_section_name): env.report_processor.report( ReportItem.warning(reports.messages.DefaultsCanBeOverriden())) return nvset_elements = nvpair_multi.find_nvsets( sections.get(cib, cib_section_name)) if len(nvset_elements) > 1: env.report_processor.report( reports.item.ReportItem.error( reports.messages.CibNvsetAmbiguousProvideNvsetId( pcs_command))) raise LibraryError() env.report_processor.report( ReportItem.warning(reports.messages.DefaultsCanBeOverriden())) if len(nvset_elements) == 1: nvpair_multi.nvset_update(nvset_elements[0], id_provider, nvpairs) elif only_removing: # do not create new nvset if there is none and we are only removing # nvpairs return else: nvpair_multi.nvset_append_new( sections.get(cib, cib_section_name), id_provider, nvpair_multi.NVSET_META, nvpairs, {}, ) env.push_cib() return nvset_elements, report_list = nvpair_multi.find_nvsets_by_ids( sections.get(cib, cib_section_name), [nvset_id]) if env.report_processor.report_list(report_list).has_errors: raise LibraryError() nvpair_multi.nvset_update(nvset_elements[0], id_provider, nvpairs) env.report_processor.report( ReportItem.warning(reports.messages.DefaultsCanBeOverriden())) env.push_cib()
def config_destroy( env: LibraryEnvironment, instance_name=None, ignore_config_load_problems=False, ): # pylint: disable=too-many-branches """ remove booth configuration files env string instance_name -- booth instance name bool ignore_config_load_problems -- delete as much as possible when unable to read booth configs for the given booth instance """ report_processor = env.report_processor booth_env = env.get_booth_env(instance_name) instance_name = booth_env.instance_name _ensure_live_env(env, booth_env) # TODO use constants in reports if resource.find_for_config( get_resources(env.get_cib()), booth_env.config_path, ): report_processor.report( ReportItem.error( reports.messages.BoothConfigIsUsed( instance_name, "in cluster resource", ) ) ) # Only systemd is currently supported. Initd does not supports multiple # instances (here specified by name) if external.is_systemctl(): if external.is_service_running( env.cmd_runner(), "booth", instance_name ): report_processor.report( ReportItem.error( reports.messages.BoothConfigIsUsed( instance_name, "(running in systemd)", ) ) ) if external.is_service_enabled( env.cmd_runner(), "booth", instance_name ): report_processor.report( ReportItem.error( reports.messages.BoothConfigIsUsed( instance_name, "(enabled in systemd)", ) ) ) if report_processor.has_errors: raise LibraryError() try: authfile_path = None booth_conf = booth_env.config.read_to_facade() authfile_path = booth_conf.get_authfile() except RawFileError as e: report_processor.report( raw_file_error_report( e, force_code=report_codes.FORCE_BOOTH_DESTROY, is_forced_or_warning=ignore_config_load_problems, ) ) except ParserErrorException as e: report_processor.report_list( booth_env.config.parser_exception_to_report_list( e, force_code=report_codes.FORCE_BOOTH_DESTROY, is_forced_or_warning=ignore_config_load_problems, ) ) if report_processor.has_errors: raise LibraryError() if authfile_path: authfile_dir, authfile_name = os.path.split(authfile_path) if (authfile_dir == settings.booth_config_dir) and authfile_name: try: key_file = FileInstance.for_booth_key(authfile_name) key_file.raw_file.remove(fail_if_file_not_found=False) except RawFileError as e: report_processor.report( raw_file_error_report( e, force_code=report_codes.FORCE_BOOTH_DESTROY, is_forced_or_warning=ignore_config_load_problems, ) ) else: report_processor.report( ReportItem.warning( reports.messages.BoothUnsupportedFileLocation( authfile_path, settings.booth_config_dir, file_type_codes.BOOTH_KEY, ) ) ) if report_processor.has_errors: raise LibraryError() try: booth_env.config.raw_file.remove() except RawFileError as e: report_processor.report(raw_file_error_report(e)) if report_processor.has_errors: raise LibraryError()
def remove_device(lib_env: LibraryEnvironment, skip_offline_nodes=False): """ Stop using quorum device, distribute and reload configs if live skip_offline_nodes continue even if not all nodes are accessible """ cfg = lib_env.get_corosync_conf() if not cfg.has_quorum_device(): raise LibraryError( ReportItem.error(reports.messages.QdeviceNotDefined())) model = cfg.get_quorum_device_model() cfg.remove_quorum_device() if lib_env.is_corosync_conf_live: report_processor = lib_env.report_processor # get nodes for communication cluster_nodes_names, report_list = get_existing_nodes_names( cfg, # Pcs is unable to communicate with nodes missing names. It cannot # send new corosync.conf to them. That might break the cluster. # Hence we error out. error_on_missing_name=True, ) if report_processor.report_list(report_list).has_errors: raise LibraryError() target_list = lib_env.get_node_target_factory().get_target_list( cluster_nodes_names, skip_non_existing=skip_offline_nodes, ) # fix quorum options for SBD to work properly if sbd.atb_has_to_be_enabled(lib_env.service_manager, cfg): lib_env.report_processor.report( ReportItem.warning( reports.messages.CorosyncQuorumAtbWillBeEnabledDueToSbd())) cfg.set_quorum_options({"auto_tie_breaker": "1"}) # disable qdevice lib_env.report_processor.report( ReportItem.info( reports.messages.ServiceActionStarted( reports.const.SERVICE_ACTION_DISABLE, "corosync-qdevice"))) com_cmd_disable = qdevice_com.Disable(lib_env.report_processor, skip_offline_nodes) com_cmd_disable.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd_disable) # stop qdevice lib_env.report_processor.report( ReportItem.info( reports.messages.ServiceActionStarted( reports.const.SERVICE_ACTION_STOP, "corosync-qdevice"))) com_cmd_stop = qdevice_com.Stop(lib_env.report_processor, skip_offline_nodes) com_cmd_stop.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd_stop) # handle model specific configuration if model == "net": lib_env.report_processor.report( ReportItem.info( reports.messages.QdeviceCertificateRemovalStarted())) com_cmd_client_destroy = qdevice_net_com.ClientDestroy( lib_env.report_processor, skip_offline_nodes) com_cmd_client_destroy.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd_client_destroy) lib_env.push_corosync_conf(cfg, skip_offline_nodes)