def _find_resource_elements_for_operation( report_processor: ReportProcessor, resources_section, booth_env, allow_multiple, ): booth_element_list = resource.find_for_config( resources_section, booth_env.config_path, ) if not booth_element_list: report_processor.report( ReportItem.error( reports.messages.BoothNotExistsInCib(booth_env.instance_name) ) ) elif len(booth_element_list) > 1: report_processor.report( ReportItem( severity=get_severity( report_codes.FORCE_BOOTH_REMOVE_FROM_CIB, allow_multiple, ), message=reports.messages.BoothMultipleTimesInCib( booth_env.instance_name, ), ) ) if report_processor.has_errors: raise LibraryError() return booth_element_list
def _check_if_atb_can_be_disabled( runner, report_processor: ReportProcessor, corosync_conf, was_enabled, force=False, ): """ Check whenever auto_tie_breaker can be changed without affecting SBD. Raises LibraryError if change of ATB will affect SBD functionality. runner -- CommandRunner report_processor -- report processor corosync_conf -- corosync conf facade was_enabled -- True if ATB was enabled, False otherwise force -- force change """ if (was_enabled and not corosync_conf.is_enabled_auto_tie_breaker() and sbd.is_auto_tie_breaker_needed(runner, corosync_conf)): report_processor.report( reports.corosync_quorum_atb_cannot_be_disabled_due_to_sbd( ReportItemSeverity.WARNING if force else ReportItemSeverity.ERROR, None if force else report_codes.FORCE_OPTIONS)) if report_processor.has_errors: raise LibraryError()
def initialize_block_devices(report_processor: ReportProcessor, cmd_runner, device_list, option_dict): """ Initialize devices with specified options in option_dict. Raise LibraryError on failure. report_processor -- report processor cmd_runner -- CommandRunner device_list -- list of strings option_dict -- dictionary of options and their values """ report_processor.report( reports.sbd_device_initialization_started(device_list)) cmd = [settings.sbd_binary] for device in device_list: cmd += ["-d", device] for option, value in sorted(option_dict.items()): cmd += [DEVICE_INITIALIZATION_OPTIONS_MAPPING[option], str(value)] cmd.append("create") _, std_err, ret_val = cmd_runner.run(cmd) if ret_val != 0: raise LibraryError( reports.sbd_device_initialization_error(device_list, std_err)) report_processor.report( reports.sbd_device_initialization_success(device_list))
def find_valid_resource_agent_by_name( report_processor: ReportProcessor, runner: CommandRunner, name: str, allowed_absent=False, absent_agent_supported=True, ): """ Return instance of ResourceAgent corresponding to name report_processor -- tool for warning/info/error reporting runner -- tool for launching external commands name -- specifies a searched agent absent_agent_supported -- flag decides if is possible to allow to return absent agent: if is produced forceable/no-forcable error """ if ":" not in name: agent = guess_exactly_one_resource_agent_full_name(runner, name) report_processor.report( ReportItem.info( reports.messages.AgentNameGuessed(name, agent.get_name()))) return agent return _find_valid_agent_by_name( report_processor, runner, name, ResourceAgent, AbsentResourceAgent if allowed_absent else None, absent_agent_supported=absent_agent_supported, )
def _find_valid_agent_by_name( report_processor: reports.ReportProcessor, runner: CommandRunner, name: str, present_agent_class: Type[CrmAgent], absent_agent_class: Optional[ Type[Union[AbsentResourceAgent, AbsentStonithAgent]] ], absent_agent_supported: bool = True, ) -> CrmAgent: try: return present_agent_class(runner, name).validate_metadata() except (InvalidResourceAgentName, InvalidStonithAgentName) as e: raise LibraryError(resource_agent_error_to_report_item(e)) from e except UnableToGetAgentMetadata as e: if not absent_agent_supported: raise LibraryError(resource_agent_error_to_report_item(e)) from e if not absent_agent_class: raise LibraryError( resource_agent_error_to_report_item(e, forceable=True) ) from e report_processor.report( resource_agent_error_to_report_item( e, severity=reports.ReportItemSeverity.WARNING, ) ) return absent_agent_class(runner, name)
def _find_valid_agent_by_name( report_processor: ReportProcessor, runner: CommandRunner, name, PresentAgentClass, AbsentAgentClass, absent_agent_supported=True, ): # pylint: disable=invalid-name try: return PresentAgentClass(runner, name).validate_metadata() except (InvalidResourceAgentName, InvalidStonithAgentName) as e: raise LibraryError(resource_agent_error_to_report_item(e)) from e except UnableToGetAgentMetadata as e: if not absent_agent_supported: raise LibraryError(resource_agent_error_to_report_item(e)) from e if not AbsentAgentClass: raise LibraryError( resource_agent_error_to_report_item(e, forceable=True)) from e report_processor.report( resource_agent_error_to_report_item( e, severity=ReportItemSeverity.WARNING, )) return AbsentAgentClass(runner, name)
def _check_if_atb_can_be_disabled( service_manager: ServiceManagerInterface, report_processor: ReportProcessor, corosync_conf: CorosyncConfFacade, was_enabled: bool, force: bool = False, ) -> None: """ Check whenever auto_tie_breaker can be changed without affecting SBD. Raises LibraryError if change of ATB will affect SBD functionality. service_manager -- report_processor -- report processor corosync_conf -- corosync conf facade was_enabled -- True if ATB was enabled, False otherwise force -- force change """ if (was_enabled and not corosync_conf.is_enabled_auto_tie_breaker() and sbd.is_auto_tie_breaker_needed(service_manager, corosync_conf)): report_processor.report( ReportItem( severity=reports.item.get_severity( reports.codes.FORCE, force, ), message=(reports.messages. CorosyncQuorumAtbCannotBeDisabledDueToSbd()), )) if report_processor.has_errors: raise LibraryError()
def _complete_agent_list( runner: CommandRunner, report_processor: ReportProcessor, agent_names: Iterable[ResourceAgentName], describe: bool, search: Optional[str], ) -> List[Dict[str, Any]]: agent_factory = ResourceAgentFacadeFactory(runner, report_processor) search_lower = search.lower() if search else None agent_list = [] for name in agent_names: if search_lower and search_lower not in name.full_name.lower(): continue try: metadata = ( agent_factory.facade_from_parsed_name(name).metadata if describe else name_to_void_metadata(name) ) agent_list.append(_agent_metadata_to_dict(metadata, describe)) except ResourceAgentError as e: report_processor.report( resource_agent_error_to_report_item( e, ReportItemSeverity.warning() ) ) return agent_list
def ensure_recipient_value_is_unique( reporter: ReportProcessor, alert, recipient_value, recipient_id="", allow_duplicity=False, ): """ Ensures that recipient_value is unique in alert. reporter -- report processor alert -- alert recipient_value -- recipient value recipient_id -- recipient id of to which value belongs to allow_duplicity -- if True only warning will be shown if value already exists """ recipient_list = alert.xpath( "./recipient[@value='{value}' and @id!='{id}']".format( value=recipient_value, id=recipient_id)) if recipient_list: reporter.report( ReportItem( severity=reports.item.get_severity( reports.codes.FORCE, allow_duplicity, ), message=reports.messages.CibAlertRecipientAlreadyExists( alert.get("id", None), recipient_value, ), )) if reporter.has_errors: raise LibraryError()
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 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 ensure_recipient_value_is_unique(reporter: ReportProcessor, alert, recipient_value, recipient_id="", allow_duplicity=False): """ Ensures that recipient_value is unique in alert. reporter -- report processor alert -- alert recipient_value -- recipient value recipient_id -- recipient id of to which value belongs to allow_duplicity -- if True only warning will be shown if value already exists """ recipient_list = alert.xpath( "./recipient[@value='{value}' and @id!='{id}']".format( value=recipient_value, id=recipient_id)) if recipient_list: reporter.report( reports.cib_alert_recipient_already_exists( alert.get("id", None), recipient_value, Severities.WARNING if allow_duplicity else Severities.ERROR, forceable=( None if allow_duplicity else report_codes.FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE))) if reporter.has_errors: raise LibraryError()
def diff_cibs_xml( runner: CommandRunner, reporter: ReportProcessor, cib_old_xml, cib_new_xml, ): """ Return xml diff of two CIBs runner reporter string cib_old_xml -- original CIB string cib_new_xml -- modified CIB """ try: cib_old_tmp_file = write_tmpfile(cib_old_xml) reporter.report( ReportItem.debug( reports.messages.TmpFileWrite( cib_old_tmp_file.name, cib_old_xml ) ) ) cib_new_tmp_file = write_tmpfile(cib_new_xml) reporter.report( ReportItem.debug( reports.messages.TmpFileWrite( cib_new_tmp_file.name, cib_new_xml ) ) ) except EnvironmentError as e: raise LibraryError( ReportItem.error(reports.messages.CibSaveTmpError(str(e))) ) from e command = [ __exec("crm_diff"), "--original", cib_old_tmp_file.name, "--new", cib_new_tmp_file.name, "--no-version", ] # 0 (CRM_EX_OK) - success with no difference # 1 (CRM_EX_ERROR) - success with difference # 64 (CRM_EX_USAGE) - usage error # 65 (CRM_EX_DATAERR) - XML fragments not parseable stdout, stderr, retval = runner.run(command) if retval == 0: return "" if retval > 1: raise LibraryError( ReportItem.error( reports.messages.CibDiffError( stderr.strip(), cib_old_xml, cib_new_xml ) ) ) return stdout.strip()
def get_tmp_cib(report_processor: reports.ReportProcessor, data: str) -> Any: try: with get_tmp_file(data) as tmp_cib_file: report_processor.report( reports.ReportItem.debug( reports.messages.TmpFileWrite(tmp_cib_file.name, data))) yield tmp_cib_file except EnvironmentError as e: raise LibraryError( reports.ReportItem.error(reports.messages.CibSaveTmpError( str(e)))) from e
def create_tmp_cib(report_processor: reports.ReportProcessor, data: str) -> Any: try: tmp_file = write_tmpfile(data) report_processor.report( reports.ReportItem.debug( reports.messages.TmpFileWrite(tmp_file.name, data))) return tmp_file except EnvironmentError as e: raise LibraryError( reports.ReportItem.error(reports.messages.CibSaveTmpError( str(e)))) from e
def _get_agent_metadata( runner: CommandRunner, report_processor: ReportProcessor, agent_name: ResourceAgentNameDto, ) -> ResourceAgentMetadata: agent_factory = ResourceAgentFacadeFactory(runner, report_processor) try: return agent_factory.facade_from_parsed_name( ResourceAgentName.from_dto(agent_name)).metadata except ResourceAgentError as e: report_processor.report(resource_agent_error_to_report_item(e)) raise LibraryError() from e
def _service_disable( report_processor: ReportProcessor, service_manager: ServiceManagerInterface, service: str, ) -> None: try: service_manager.disable(service) except ManageServiceError as e: raise LibraryError(service_exception_to_report(e)) from e report_processor.report( ReportItem.info( reports.messages.ServiceActionSucceeded( reports.const.SERVICE_ACTION_DISABLE, "quorum device")))
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 prepare( report_processor: ReportProcessor, raw_operation_list: Iterable[ResourceOperationFilteredIn], default_operation_list: Iterable[CibResourceOperationDto], allowed_operation_name_list: Iterable[str], new_role_names_supported: bool, allow_invalid: bool = False, ) -> List[ResourceOperationFilteredOut]: """ Return operation_list prepared from raw_operation_list and default_operation_list. report_processor -- tool for warning/info/error reporting raw_operation_list -- user entered operations that require follow-up care default_operation_list -- operations defined as default by (most probably) a resource agent allowed_operation_name_list -- operation names defined by a resource agent allow_invalid -- flag for validation skipping """ operations_to_validate = _operations_to_normalized(raw_operation_list) report_list: ReportItemList = [] report_list.extend( _validate_operation_list(operations_to_validate, allowed_operation_name_list, allow_invalid)) operation_list = _normalized_to_operations(operations_to_validate, new_role_names_supported) report_list.extend(validate_different_intervals(operation_list)) if report_processor.report_list(report_list).has_errors: raise LibraryError() report_list, remaining_default_operations = uniquify_operations_intervals( _get_remaining_defaults( operation_list, default_operation_list, )) if report_processor.report_list(report_list).has_errors: raise LibraryError() return [ _filter_op_dict(op, new_role_names_supported) for op in complete_operations_options(operation_list) + [ operation_dto_to_legacy_dict(op, {}) for op in remaining_default_operations ] ]
def prepare( report_processor: ReportProcessor, raw_operation_list, default_operation_list, allowed_operation_name_list, allow_invalid=False, ): """ Return operation_list prepared from raw_operation_list and default_operation_list. report_processor is tool for warning/info/error reporting list of dicts raw_operation_list are entered operations that require follow-up care list of dicts default_operation_list are operations defined as default by (most probably) resource agent bool allow_invalid is flag for validation skipping """ operations_to_validate = operations_to_normalized(raw_operation_list) report_list: ReportItemList = [] report_list.extend( validate_operation_list(operations_to_validate, allowed_operation_name_list, allow_invalid)) operation_list = normalized_to_operations(operations_to_validate) report_list.extend(validate_different_intervals(operation_list)) if report_processor.report_list(report_list).has_errors: raise LibraryError() return complete_all_intervals(operation_list) + get_remaining_defaults( report_processor, operation_list, default_operation_list)
def _find_resources_to_remove( cib, report_processor: ReportProcessor, node_type, node_identifier, allow_remove_multiple_nodes, find_resources ): resource_element_list = find_resources(get_resources(cib), node_identifier) if not resource_element_list: raise LibraryError(reports.node_not_found(node_identifier, node_type)) if len(resource_element_list) > 1: if report_processor.report( reports.get_problem_creator( report_codes.FORCE_REMOVE_MULTIPLE_NODES, allow_remove_multiple_nodes )( reports.multiple_result_found, "resource", [resource.attrib["id"] for resource in resource_element_list], node_identifier ) ).has_errors: raise LibraryError() return resource_element_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 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 _update_scsi_devices_get_element_and_devices( runner: CommandRunner, report_processor: ReportProcessor, cib: _Element, stonith_id: str, ) -> Tuple[_Element, List[str]]: """ Do checks and return stonith element and list of current scsi devices. Raise LibraryError if checks fail. runner -- command runner instance report_processor -- tool for warning/info/error reporting cib -- cib element stonith_id -- id of stonith resource """ if not is_getting_resource_digest_supported(runner): raise LibraryError( ReportItem.error( reports.messages. StonithRestartlessUpdateOfScsiDevicesNotSupported())) ( stonith_el, report_list, ) = resource.stonith.validate_stonith_restartless_update(cib, stonith_id) if report_processor.report_list(report_list).has_errors: raise LibraryError() # for mypy, this should not happen because exception would be raised if stonith_el is None: raise AssertionError("stonith element is None") current_device_list = get_value(INSTANCE_ATTRIBUTES_TAG, stonith_el, "devices") if current_device_list is None: raise AssertionError("current_device_list is None") return stonith_el, current_device_list.split(",")
def check_is_without_duplication(report_processor: ReportProcessor, constraint_section, element, are_duplicate, export_element, duplication_alowed=False): duplicate_element_list = [ duplicate_element for duplicate_element in constraint_section.findall(".//" + element.tag) if (element is not duplicate_element and are_duplicate(element, duplicate_element)) ] if not duplicate_element_list: return if report_processor.report( reports.duplicate_constraints_exist( element.tag, [ export_element(duplicate_element) for duplicate_element in duplicate_element_list ], ReportItemSeverity.WARNING if duplication_alowed else ReportItemSeverity.ERROR, forceable=None if duplication_alowed else report_codes.FORCE_CONSTRAINT_DUPLICATE, )).has_errors: raise LibraryError()
def _find_resources_to_remove( cib, report_processor: ReportProcessor, node_type, node_identifier, allow_remove_multiple_nodes, find_resources, ): resource_element_list = find_resources(get_resources(cib), node_identifier) if not resource_element_list: raise LibraryError( ReportItem.error( reports.messages.NodeNotFound(node_identifier, [node_type]))) if len(resource_element_list) > 1: if report_processor.report( ReportItem( severity=reports.item.get_severity( reports.codes.FORCE, allow_remove_multiple_nodes, ), message=reports.messages.MultipleResultsFound( "resource", [ resource.attrib["id"] for resource in resource_element_list ], node_identifier, ), )).has_errors: raise LibraryError() return resource_element_list
def add_level( reporter: ReportProcessor, topology_el: _Element, resources_el: _Element, level, target_type, target_value, devices, cluster_status_nodes: Sequence[StateElement], force_device=False, force_node=False, ): # pylint: disable=too-many-arguments """ Validate and add a new fencing level. Raise LibraryError if not valid. reporter -- report processor etree topology_el -- etree element to add the level to etree resources_el -- etree element with resources definitions int|string level -- level (index) of the new fencing level constant target_type -- the new fencing level target value type mixed target_value -- the new fencing level target value Iterable devices -- list of stonith devices for the new fencing level Iterable cluster_status_nodes -- list of status of existing cluster nodes bool force_device -- continue even if a stonith device does not exist bool force_node -- continue even if a node (target) does not exist """ report_list, valid_level = _validate_level(level) reporter.report_list( report_list + _validate_target( cluster_status_nodes, target_type, target_value, force_node ) + _validate_devices(resources_el, devices, force_device) ) if reporter.has_errors: raise LibraryError() reporter.report_list( _validate_level_target_devices_does_not_exist( topology_el, level, target_type, target_value, devices ) ) if reporter.has_errors: raise LibraryError() _append_level_element( topology_el, valid_level, target_type, target_value, devices )
def prepare_set(find_valid_id, resource_set, report_processor: reports.ReportProcessor): """return resource_set with corrected ids""" if report_processor.report_list(_validate_options( resource_set["options"])).has_errors: raise LibraryError() return { "ids": [find_valid_id(id) for id in resource_set["ids"]], "options": resource_set["options"], }
def get_service_manager( cmd_runner: CommandRunner, report_processor: reports.ReportProcessor, ) -> services.interfaces.ServiceManagerInterface: executor = _CmdExecutor(cmd_runner) drivers: List[services.interfaces.ServiceManagerInterface] = [ services.drivers.SystemdDriver(executor, settings.systemctl_binary, settings.systemd_unit_path), services.drivers.SysVInitRhelDriver(executor, settings.service_binary, settings.chkconfig_binary), ] for driver in drivers: if driver.is_current_system_supported(): return driver report_processor.report( reports.ReportItem.warning( reports.messages.ServiceUnableToDetectInitSystem())) return _NoOpDriver(report_processor)
def _check_qdevice_not_used(reporter: ReportProcessor, runner, model, force=False): _check_model(model) connected_clusters: List[str] = [] if model == "net": try: status = qdevice_net.qdevice_status_cluster_text(runner) connected_clusters = qdevice_net.qdevice_connected_clusters(status) except qdevice_net.QnetdNotRunningException: pass if connected_clusters: reporter.report( reports.qdevice_used_by_clusters( connected_clusters, ReportItemSeverity.WARNING if force else ReportItemSeverity.ERROR, None if force else report_codes.FORCE_QDEVICE_USED)) if reporter.has_errors: raise LibraryError()