def get_rule_in_effect_status( runner: CommandRunner, cib_xml: str, rule_id: str ) -> CibRuleInEffectStatus: """ Figure out if a rule is in effect, expired or not yet in effect runner -- a class for running external processes cib_xml -- CIB containing rules rule_id -- ID of the rule to be checked """ # TODO Once crm_rule is capable of evaluating more than one rule per go, we # should make use of it. Running the tool for each rule may really slow pcs # down. translation_map = { 0: CibRuleInEffectStatus.IN_EFFECT, 110: CibRuleInEffectStatus.EXPIRED, 111: CibRuleInEffectStatus.NOT_YET_IN_EFFECT, # 105:non-existent # 112: undetermined (rule is too complicated for current implementation) } dummy_stdout, dummy_stderr, retval = runner.run( [__exec("crm_rule"), "--check", "--rule", rule_id, "--xml-text", "-"], stdin_string=cib_xml, ) return translation_map.get(retval, CibRuleInEffectStatus.UNKNOWN)
def wrap_element_by_master(cib_file, resource_id, master_id=None): cib_tree = etree.parse(cib_file, etree.XMLParser(huge_tree=True)).getroot() element = cib_tree.find(f'.//*[@id="{resource_id}"]') final_master_id = (master_id if master_id is not None else f"{resource_id}-master") master_element = _xml_to_element(f""" <master id="{final_master_id}"> </master> """) element.getparent().append(master_element) master_element.append(element) final_xml = etree_to_str(cib_tree) environ = dict(os.environ) environ["CIB_file"] = cib_file runner = CommandRunner(mock.MagicMock(logging.Logger), MockLibraryReportProcessor(), environ) stdout, stderr, retval = runner.run([ os.path.join(settings.pacemaker_binaries, "cibadmin"), "--replace", "--scope", "resources", "--xml-pipe", ], stdin_string=final_xml) assert retval == 0, ("Error running wrap_element_by_master:\n" + stderr + "\n" + stdout)
def resource_cleanup( runner: CommandRunner, resource: Optional[str] = None, node: Optional[str] = None, operation: Optional[str] = None, interval: Optional[str] = None, strict: bool = False, ): cmd = [__exec("crm_resource"), "--cleanup"] if resource: cmd.extend(["--resource", resource]) if node: cmd.extend(["--node", node]) if operation: cmd.extend(["--operation", operation]) if interval: cmd.extend(["--interval", interval]) if strict: cmd.extend(["--force"]) stdout, stderr, retval = runner.run(cmd) if retval != 0: raise LibraryError( ReportItem.error( reports.messages.ResourceCleanupError( join_multilines([stderr, stdout]), resource, node ) ) ) # usefull output (what has been done) goes to stderr return join_multilines([stdout, stderr])
def get_cluster_status_text( runner: CommandRunner, hide_inactive_resources: bool, verbose: bool, ) -> Tuple[str, List[str]]: cmd = [__exec("crm_mon"), "--one-shot"] if not hide_inactive_resources: cmd.append("--inactive") if verbose: cmd.extend(["--show-detail", "--show-node-attributes", "--failcounts"]) # by default, pending and failed actions are displayed # with verbose==True, we display the whole history if is_fence_history_supported_status(runner): cmd.append("--fence-history=3") stdout, stderr, retval = runner.run(cmd) if retval != 0: raise CrmMonErrorException( ReportItem.error( reports.messages.CrmMonError(join_multilines([stderr, stdout])) ) ) warnings: List[str] = [] if stderr.strip(): warnings = [ line for line in stderr.strip().splitlines() if verbose or not line.startswith("DEBUG: ") ] return stdout.strip(), warnings
def resource_refresh( runner: CommandRunner, resource: Optional[str] = None, node: Optional[str] = None, strict: bool = False, force: bool = False, ): if not force and not node and not resource: summary = ClusterState(get_cluster_status_dom(runner)).summary operations = summary.nodes.attrs.count * summary.resources.attrs.count if operations > __RESOURCE_REFRESH_OPERATION_COUNT_THRESHOLD: raise LibraryError( ReportItem( reports.item.ReportItemSeverity.error(reports.codes.FORCE), reports.messages.ResourceRefreshTooTimeConsuming( __RESOURCE_REFRESH_OPERATION_COUNT_THRESHOLD), )) cmd = [__exec("crm_resource"), "--refresh"] if resource: cmd.extend(["--resource", resource]) if node: cmd.extend(["--node", node]) if strict: cmd.extend(["--force"]) stdout, stderr, retval = runner.run(cmd) if retval != 0: raise LibraryError( ReportItem.error( reports.messages.ResourceRefreshError( join_multilines([stderr, stdout]), resource, node))) # usefull output (what has been done) goes to stderr return join_multilines([stdout, stderr])
def wait_for_idle(runner: CommandRunner, timeout: int) -> None: """ Run waiting command. Raise LibraryError if command failed. runner -- preconfigured object for running external programs timeout -- waiting timeout in seconds, wait indefinitely if non-positive integer """ args = [__exec("crm_resource"), "--wait"] if timeout > 0: args.append("--timeout={0}".format(timeout)) stdout, stderr, retval = runner.run(args) if retval != 0: # Usefull info goes to stderr - not only error messages, a list of # pending actions in case of timeout goes there as well. # We use stdout just to be sure if that's get changed. if retval == __EXITCODE_WAIT_TIMEOUT: raise LibraryError( ReportItem.error( reports.messages.WaitForIdleTimedOut( join_multilines([stderr, stdout])))) raise LibraryError( ReportItem.error( reports.messages.WaitForIdleError( join_multilines([stderr, stdout]))))
def _is_in_pcmk_tool_help(runner: CommandRunner, tool: str, text_list: Iterable[str]) -> bool: stdout, stderr, dummy_retval = runner.run([__exec(tool), "--help-all"]) # Help goes to stderr but we check stdout as well if that gets changed. Use # generators in all to return early. return (all(text in stderr for text in text_list) or all(text in stdout for text in text_list))
def _load_metadata_xml( runner: CommandRunner, agent_name: ResourceAgentName ) -> str: """ Run pacemaker tool to get raw metadata from an agent runner -- external processes runner agent_name -- name of an agent whose metadata we want to get """ env_path = ":".join( [ # otherwise pacemaker cannot run RHEL fence agents to get their # metadata settings.fence_agent_binaries, # otherwise heartbeat and cluster-glue agents don't work "/bin", # otherwise heartbeat and cluster-glue agents don't work "/usr/bin", ] ) stdout, stderr, retval = runner.run( [settings.crm_resource_binary, "--show-metadata", agent_name.full_name], env_extend={"PATH": env_path}, ) if retval != 0: raise UnableToGetAgentMetadata(agent_name.full_name, stderr.strip()) return stdout.strip()
def list_resource_agents_standards(runner: CommandRunner) -> List[str]: """ Return a list of resource agents standards (ocf, lsb, ...) on the local host """ # retval is the number of standards found stdout, dummy_stderr, dummy_retval = runner.run( [settings.crm_resource_binary, "--list-standards"]) return sorted(set(split_multiline(stdout)), key=str.lower)
def list_resource_agents_ocf_providers(runner: CommandRunner) -> List[str]: """ Return a list of resource agents ocf providers on the local host """ # retval is the number of providers found stdout, dummy_stderr, dummy_retval = runner.run( [settings.crm_resource_binary, "--list-ocf-providers"]) return sorted(set(split_multiline(stdout)), key=str.lower)
def list_resource_agents_ocf_providers(runner: CommandRunner) -> List[str]: """ Return list of resource agents ocf providers on the local host """ # retval is number of providers found stdout, dummy_stderr, dummy_retval = runner.run( [settings.crm_resource_binary, "--list-ocf-providers"] ) return _prepare_agent_list(stdout)
def get_cluster_status_xml_raw(runner: CommandRunner) -> Tuple[str, str, int]: """ Run pacemaker tool to get XML status. This function doesn't do any processing. Usually, using get_cluster_status_dom is preferred instead. runner -- a class for running external processes """ return runner.run( [__exec("crm_mon"), "--one-shot", "--inactive", "--output-as", "xml"])
def fixture_to_cib(cib_file, xml): environ = dict(os.environ) environ["CIB_file"] = cib_file runner = CommandRunner(mock.MagicMock(logging.Logger), MockLibraryReportProcessor(), environ) stdout, stderr, retval = runner.run( ["cibadmin", "--create", "--scope", "resources", "--xml-text", xml]) assert retval == 0, ("Error running fixture_to_cib:\n" + stderr + "\n" + stdout)
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 list_resource_agents_standards(runner: CommandRunner) -> List[str]: """ Return list of resource agents standards (ocf, lsb, ... ) on the local host """ # retval is number of standards found stdout, dummy_stderr, dummy_retval = runner.run( [settings.crm_resource_binary, "--list-standards"] ) # we are only interested in RESOURCE agents ignored_standards = frozenset(["stonith"]) return _prepare_agent_list(stdout, ignored_standards)
def list_resource_agents( runner: CommandRunner, standard_provider: str ) -> List[str]: """ Return list of resource agents for specified standard on the local host runner standard_provider -- standard[:provider], e.g. lsb, ocf, ocf:pacemaker """ # retval is 0 on success, anything else when no agents found stdout, dummy_stderr, retval = runner.run( [settings.crm_resource_binary, "--list-agents", standard_provider] ) if retval != 0: return [] return _prepare_agent_list(stdout)
def list_resource_agents( runner: CommandRunner, standard_provider: StandardProviderTuple) -> List[str]: """ Return a list of resource agents of the specified standard on the local host standard_provider -- standard[:provider], e.g. lsb, ocf, ocf:pacemaker """ # retval is 0 on success, anything else when no agents were found stdout, dummy_stderr, retval = runner.run([ settings.crm_resource_binary, "--list-agents", (f"{standard_provider.standard}:{standard_provider.provider}" if standard_provider.provider else standard_provider.standard), ]) return (sorted(set(split_multiline(stdout)) - _IGNORED_AGENTS, key=str.lower) if retval == 0 else [])
def _load_fake_agent_metadata_xml( runner: CommandRunner, agent_name: FakeAgentName ) -> str: """ Run pacemaker tool to get raw metadata from pacemaker runner -- external processes runner agent_name -- name of pacemaker part whose metadata we want to get """ name_to_executable = { const.PACEMAKER_FENCED: settings.pacemaker_fenced, } if agent_name not in name_to_executable: raise UnableToGetAgentMetadata(agent_name, "Unknown agent") stdout, stderr, dummy_retval = runner.run( [name_to_executable[agent_name], "metadata"] ) metadata = stdout.strip() if not metadata: raise UnableToGetAgentMetadata(agent_name, stderr.strip()) return metadata
def diff_cibs_xml( runner: CommandRunner, reporter: ReportProcessor, cib_old_xml: str, cib_new_xml: str, ) -> str: """ Return xml diff of two CIBs runner reporter cib_old_xml -- original CIB cib_new_xml -- modified CIB """ with tools.get_tmp_cib(reporter, cib_old_xml) as cib_old_tmp_file, tools.get_tmp_cib( reporter, cib_new_xml) as cib_new_tmp_file: stdout, stderr, retval = runner.run([ __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 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_ticket_status_text(runner: CommandRunner) -> Tuple[str, str, int]: stdout, stderr, retval = runner.run([__exec("crm_ticket"), "--details"]) return stdout.strip(), stderr.strip(), retval
def get_resource_digests( runner: CommandRunner, resource_id: str, node_name: str, resource_options: Dict[str, str], crm_meta_attributes: Optional[Dict[str, Optional[str]]] = None, ) -> Dict[str, Optional[str]]: """ Get set of digests for a resource using crm_resource utility. There are 3 types of digests: all, nonreloadable and nonprivate. Resource can have one or more digests types depending on the resource parameters. runner -- command runner instance resource_id -- resource id node_name -- name of the node where resource is running resource_options -- resource options with updated values crm_meta_attributes -- parameters of a monitor operation """ # pylint: disable=too-many-locals if crm_meta_attributes is None: crm_meta_attributes = {} command = [ __exec("crm_resource"), "--digests", "--resource", resource_id, "--node", node_name, "--output-as", "xml", *[f"{key}={value}" for key, value in resource_options.items()], *[ f"CRM_meta_{key}={value}" for key, value in crm_meta_attributes.items() if value is not None ], ] stdout, stderr, retval = runner.run(command) def error_exception(message): return LibraryError( ReportItem.error( reports.messages.UnableToGetResourceOperationDigests(message))) try: dom = _get_api_result_dom(stdout) except (etree.XMLSyntaxError, etree.DocumentInvalid) as e: raise error_exception(join_multilines([stderr, stdout])) from e if retval != 0: status = _get_status_from_api_result(dom) raise error_exception( join_multilines([status.message] + list(status.errors))) digests = {} for digest_type in ["all", "nonprivate", "nonreloadable"]: xpath_result = cast( List[str], dom.xpath( "./digests/digest[@type=$digest_type]/@hash", digest_type=digest_type, ), ) digests[digest_type] = xpath_result[0] if xpath_result else None if not any(digests.values()): raise error_exception(join_multilines([stderr, stdout])) return digests