def get_errors(self): """ Report why the element has not been found or booking its id failed """ if (self.element_found() or (self._book_errors is not None and not self._book_errors)): raise AssertionError( "Improper usage: cannot report errors when there are none") element = get_root( self._context_element).find(f'.//*[@id="{self._element_id}"]') if element is not None: if element.tag in self._tag_list: return [ reports.object_with_id_in_unexpected_context( element.tag, self._element_id, self._context_element.tag, self._context_element.attrib.get("id", "")) ] return [ reports.id_belongs_to_unexpected_type( self._element_id, expected_types=self._expected_types, current_type=element.tag) ] if self._book_errors is None: return [ reports.id_not_found( self._element_id, self._expected_types, self._context_element.tag, self._context_element.attrib.get("id", "")) ] return self._book_errors
def disable_safe(env, resource_ids, strict, wait): """ Disallow specified resource to be started by the cluster only if there is no effect on other resources LibraryEnvironment env -- strings resource_ids -- ids of the resources to be disabled bool strict -- if False, allow resources to be migrated mixed wait -- False: no wait, None: wait default timeout, int: wait timeout """ if not env.is_cib_live: raise LibraryError( reports.live_environment_required([file_type_codes.CIB])) with resource_environment( env, wait, resource_ids, _ensure_disabled_after_wait(True)) as resources_section: id_provider = IdProvider(resources_section) resource_el_list = _find_resources_or_raise(resources_section, resource_ids) env.report_processor.process_list( _resource_list_enable_disable(resource_el_list, resource.common.disable, id_provider, env.get_cluster_state())) inner_resources_names_set = set() for resource_el in resource_el_list: inner_resources_names_set.update({ inner_resource_el.get("id") for inner_resource_el in resource.common.get_all_inner_resources(resource_el) }) plaintext_status, transitions, dummy_cib = simulate_cib( env.cmd_runner(), get_root(resources_section)) simulated_operations = ( simulate_tools.get_operations_from_transitions(transitions)) other_affected = set() if strict: other_affected = set( simulate_tools.get_resources_from_operations( simulated_operations, exclude=resource_ids)) else: other_affected = set( simulate_tools.get_resources_left_stopped( simulated_operations, exclude=resource_ids) + simulate_tools.get_resources_left_demoted( simulated_operations, exclude=resource_ids)) # Stopping a clone stops all its inner resources. That should not block # stopping the clone. other_affected = other_affected - inner_resources_names_set if other_affected: raise LibraryError( reports.resource_disable_affects_other_resources( resource_ids, other_affected, plaintext_status, ))
def __get_nodes(corosync_conf=None, cib=None): corosync_nodes = corosync_conf.get_nodes() if corosync_conf else [] remote_and_guest_nodes = [] if cib is not None: cib_root = get_root(cib) remote_and_guest_nodes = (remote_node.find_node_list(cib_root) + guest_node.find_node_list(cib_root)) return corosync_nodes, remote_and_guest_nodes
def find_element_by_tag_and_id( tag, context_element, element_id, none_if_id_unused=False, id_description="" ): """ Return element with given tag and element_id under context_element. When element does not exists raises LibraryError or return None if specified in none_if_id_unused. etree.Element(Tree) context_element is part of tree for element scan string|list tag is expected tag (or list of tags) of search element string element_id is id of search element bool none_if_id_unused if the element is not found then return None if True or raise a LibraryError if False string id_description optional description for id """ tag_list = [tag] if is_string(tag) else tag element_list = context_element.xpath( './/*[({0}) and @id="{1}"]'.format( " or ".join(["self::{0}".format(one_tag) for one_tag in tag_list]), element_id ) ) if element_list: return element_list[0] element = get_root(context_element).find( './/*[@id="{0}"]'.format(element_id) ) if element is not None: raise LibraryError( reports.id_belongs_to_unexpected_type( element_id, expected_types=tag_list, current_type=element.tag ) if element.tag not in tag_list else reports.object_with_id_in_unexpected_context( element.tag, element_id, context_element.tag, context_element.attrib.get("id", "") ) ) if none_if_id_unused: return None raise LibraryError( reports.id_not_found( element_id, id_description if id_description else "/".join(tag_list), context_element.tag, context_element.attrib.get("id", "") ) )
def get_configuration_elements_by_id( tree: _Element, check_id: str ) -> List[_Element]: """ Return any configuration elements (not in status section of cib) with value of attribute id specified as 'check_id'; skip any and all elements having id attribute which does not actually serve as an id. tree -- any element in xml tree, whole tree (not only its subtree) will be searched check_id -- id to find """ # do not search in /cib/status, it may contain references to previously # existing and deleted resources and thus preventing creating them again # pacemaker creates an implicit resource for the pacemaker_remote # connection, which will be named the same as the value of the remote-node # attribute of the explicit resource. So the value of nvpair named # "remote-node" is considered to be id return cast( List[_Element], get_root(tree).xpath( """ ( /cib/*[name()!="status"] | /*[name()!="cib"] ) //*[ ( name()!="acl_target" and name()!="role" and name()!="obj_ref" and name()!="resource_ref" and @id=$check_id ) or ( name()="primitive" and meta_attributes[ nvpair[ @name="remote-node" and @value=$check_id ] ] ) ] """, check_id=check_id, ), )
def __get_nodes(corosync_conf=None, cib=None): corosync_nodes = corosync_conf.get_nodes() if corosync_conf else [] remote_and_guest_nodes = [] if cib is not None: cib_root = get_root(cib) remote_and_guest_nodes = ( remote_node.find_node_list(cib_root) + guest_node.find_node_list(cib_root) ) return corosync_nodes, remote_and_guest_nodes
def __get_nodes( corosync_conf: Optional[CorosyncConfigFacade] = None, cib: Optional[Element] = None, ) -> Tuple[Iterable[CorosyncNode], Iterable[PacemakerNode]]: corosync_nodes = corosync_conf.get_nodes() if corosync_conf else [] remote_and_guest_nodes: Iterable[PacemakerNode] = [] if cib is not None: cib_root = get_root(cib) remote_and_guest_nodes = remote_node.find_node_list( cib_root) + guest_node.find_node_list(cib_root) return corosync_nodes, remote_and_guest_nodes
def does_id_exist(tree, check_id): """ Checks to see if id exists in the xml dom passed tree cib etree node check_id id to check """ # do not search in /cib/status, it may contain references to previously # existing and deleted resources and thus preventing creating them again existing = get_root(tree).xpath( ('(/cib/*[name()!="status"]|/*[name()!="cib"])' '//*[name()!="acl_target" and name()!="role" and @id="{0}"]' ).format(check_id)) return len(existing) > 0
def create_with_set(constraint_section, tag_name, options, resource_set_list): if not resource_set_list: raise LibraryError( ReportItem.error(reports.messages.EmptyResourceSetList())) element = SubElement(constraint_section, tag_name) element.attrib.update(options) if tag_name == "rsc_order": all_resource_ids = [] for resource_set_item in resource_set_list: all_resource_ids.extend(resource_set_item["ids"]) resource_set.is_resource_in_same_group(get_root(constraint_section), all_resource_ids) for resource_set_item in resource_set_list: resource_set.create(element, resource_set_item) return element
def disable_simulate(env, resource_ids): """ Simulate disallowing specified resource to be started by the cluster LibraryEnvironment env -- strings resource_ids -- ids of the resources to be disabled """ if not env.is_cib_live: raise LibraryError( reports.live_environment_required([file_type_codes.CIB])) resources_section = get_resources(env.get_cib()) _disable_validate_and_edit_cib(env, resources_section, resource_ids) plaintext_status, dummy_transitions, dummy_cib = simulate_cib( env.cmd_runner(), get_root(resources_section)) return plaintext_status
def get_errors(self): """ Report why the element has not been found or booking its id failed """ if ( self.element_found() or (self._book_errors is not None and not self._book_errors) ): raise AssertionError( "Improper usage: cannot report errors when there are none" ) element = get_root(self._context_element).find( f'.//*[@id="{self._element_id}"]' ) if element is not None: if element.tag in self._tag_list: return [ reports.object_with_id_in_unexpected_context( element.tag, self._element_id, self._context_element.tag, self._context_element.attrib.get("id", "") ) ] return [ reports.id_belongs_to_unexpected_type( self._element_id, expected_types=self._expected_types, current_type=element.tag ) ] if self._book_errors is None: return [ reports.id_not_found( self._element_id, self._expected_types, self._context_element.tag, self._context_element.attrib.get("id", "") ) ] return self._book_errors
def remove(env: LibraryEnvironment, tag_list: Iterable[str]) -> None: """ Remove specified tags from a cib. env -- provides all for communication with externals tag_list -- list of tags for the removal """ with cib_tags_section(env) as tags_section: env.report_processor.report_list( tag.validate_remove_tag( get_constraints(get_root(tags_section)), tag_list, ) ) tag_elements, report_list = tag.find_tag_elements_by_ids( tags_section, tag_list, ) if env.report_processor.report_list(report_list).has_errors: raise LibraryError() tag.remove_tag(tag_elements)
def update( env: LibraryEnvironment, tag_id: str, idref_add: Sequence[str], idref_remove: Sequence[str], adjacent_idref: Optional[str] = None, put_after_adjacent: bool = False, ) -> None: """ Update specified tag by given id references. env -- provides all for communication with externals tag_id -- id of an existing tag to be updated idref_add -- reference ids to be added idref_remove -- reference ids to be removed adjacent_idref -- id of the element next to which the added elements will be put put_after_adjacent -- put elements after (True) or before (False) the adjacent element """ with cib_tags_section(env) as tags_section: validator = tag.ValidateTagUpdateByIds( tag_id, idref_add, idref_remove, adjacent_idref, ) if env.report_processor.report_list( validator.validate( get_resources(get_root(tags_section)), tags_section, )).has_errors: raise LibraryError() # check for mypy tag_element = validator.tag_element() if tag_element is not None: tag.add_obj_ref( tag_element, validator.add_obj_ref_element_list(), validator.adjacent_obj_ref_element(), put_after_adjacent, ) tag.remove_obj_ref(validator.remove_obj_ref_element_list())
def get_errors(self): """ Report why the element has not been found or booking its id failed """ if self.element_found() or (self._book_errors is not None and not self._book_errors): raise AssertionError( "Improper usage: cannot report errors when there are none") element = get_root( self._context_element).find(f'.//*[@id="{self._element_id}"]') if element is not None: if element.tag in self._tag_list: return [ ReportItem.error( reports.messages.ObjectWithIdInUnexpectedContext( element.tag, self._element_id, self._context_element.tag, self._context_element.attrib.get("id", ""), )) ] return [ ReportItem.error( reports.messages.IdBelongsToUnexpectedType( self._element_id, expected_types=self._expected_types, current_type=element.tag, )) ] if self._book_errors is None: return [ ReportItem.error( reports.messages.IdNotFound( self._element_id, sorted(self._expected_types), self._context_element.tag, self._context_element.attrib.get("id", ""), )) ] return self._book_errors
def does_id_exist(tree, check_id): """ Checks to see if id exists in the xml dom passed tree cib etree node check_id id to check """ # do not search in /cib/status, it may contain references to previously # existing and deleted resources and thus preventing creating them again #pacemaker creates an implicit resource for the pacemaker_remote connection, #which will be named the same as the value of the remote-node attribute of #the explicit resource. So the value of nvpair named "remote-node" is #considered to be id existing = get_root(tree).xpath(""" ( /cib/*[name()!="status"] | /*[name()!="cib"] ) //*[ ( name()!="acl_target" and name()!="role" and @id="{0}" ) or ( name()="primitive" and meta_attributes[ nvpair[ @name="remote-node" and @value="{0}" ] ] ) ] """.format(check_id)) return len(existing) > 0
def disable_safe(env, resource_ids, strict, wait): """ Disallow specified resource to be started by the cluster only if there is no effect on other resources LibraryEnvironment env -- strings resource_ids -- ids of the resources to be disabled bool strict -- if False, allow resources to be migrated mixed wait -- False: no wait, None: wait default timeout, int: wait timeout """ if not env.is_cib_live: raise LibraryError( reports.live_environment_required([file_type_codes.CIB])) with resource_environment( env, wait, resource_ids, _ensure_disabled_after_wait(True)) as resources_section: _disable_validate_and_edit_cib(env, resources_section, resource_ids) plaintext_status, transitions, dummy_cib = simulate_cib( env.cmd_runner(), get_root(resources_section)) simulated_operations = ( simulate_tools.get_operations_from_transitions(transitions)) other_affected = set() if strict: other_affected = set( simulate_tools.get_resources_from_operations( simulated_operations, exclude=resource_ids)) else: other_affected = set( simulate_tools.get_resources_left_stopped( simulated_operations, exclude=resource_ids) + simulate_tools.get_resources_left_demoted( simulated_operations, exclude=resource_ids)) if other_affected: raise LibraryError( reports.resource_disable_affects_other_resources( resource_ids, other_affected, plaintext_status, ))
def create( env: LibraryEnvironment, tag_id: str, idref_list: Sequence[str], ) -> None: """ Create a tag in a cib. env -- provides all for communication with externals tag_id -- identifier of new tag idref_list -- reference ids which we want to tag """ with cib_tags_section(env) as tags_section: env.report_processor.report_list( tag.validate_create_tag( get_resources(get_root(tags_section)), tag_id, idref_list, IdProvider(tags_section), ) ) if env.report_processor.has_errors: raise LibraryError() tag.create_tag(tags_section, tag_id, idref_list)
def create_in_group( env: LibraryEnvironment, stonith_id: str, stonith_agent_name: str, group_id: str, operations: Iterable[Mapping[str, str]], meta_attributes: Mapping[str, str], instance_attributes: Mapping[str, str], allow_absent_agent: bool = False, allow_invalid_operation: bool = False, allow_invalid_instance_attributes: bool = False, use_default_operations: bool = True, ensure_disabled: bool = False, adjacent_resource_id: Optional[str] = None, put_after_adjacent: bool = False, wait: WaitType = False, ): # pylint: disable=too-many-arguments, too-many-locals """ DEPRECATED Create stonith as resource in a cib and put it into defined group. env -- provides all for communication with externals stonith_id --an identifier of stonith resource stonith_agent_name -- contains name for the identification of agent group_id -- identificator for group to put stonith inside operations -- contains attributes for each entered operation meta_attributes -- contains attributes for primitive/meta_attributes instance_attributes -- contains attributes for primitive/instance_attributes allow_absent_agent -- a flag for allowing agent not installed in a system allow_invalid_operation -- a flag for allowing to use operations that are not listed in a stonith agent metadata allow_invalid_instance_attributes -- a flag for allowing to use instance attributes that are not listed in a stonith agent metadata or for allowing to not use the instance_attributes that are required in stonith agent metadata use_default_operations -- a flag for stopping of adding default cib operations (specified in a stonith agent) ensure_disabled -- flag that keeps resource in target-role "Stopped" adjacent_resource_id -- identify neighbor of a newly created stonith put_after_adjacent -- is flag to put a newly create resource befor/after adjacent stonith wait -- flag for controlling waiting for pacemaker idle mechanism """ runner = env.cmd_runner() agent_factory = ResourceAgentFacadeFactory(runner, env.report_processor) stonith_agent = _get_agent_facade( env.report_processor, agent_factory, stonith_agent_name, allow_absent_agent, ) if stonith_agent.metadata.provides_unfencing: meta_attributes = dict(meta_attributes, provides="unfencing") with resource_environment( env, wait, [stonith_id], _ensure_disabled_after_wait( ensure_disabled or resource.common.are_meta_disabled(meta_attributes), ), ) as resources_section: id_provider = IdProvider(resources_section) adjacent_resource_element = None if adjacent_resource_id: try: adjacent_resource_element = get_element_by_id( get_root(resources_section), adjacent_resource_id) except ElementNotFound: # We cannot continue without adjacent element because # the validator might produce misleading reports if env.report_processor.report( ReportItem.error( reports.messages.IdNotFound( adjacent_resource_id, []))).has_errors: raise LibraryError() from None try: group_element = get_element_by_id(get_root(resources_section), group_id) except ElementNotFound: group_id_reports: List[ReportItem] = [] validate_id(group_id, description="group name", reporter=group_id_reports) env.report_processor.report_list(group_id_reports) group_element = resource.group.append_new(resources_section, group_id) stonith_element = resource.primitive.create( env.report_processor, resources_section, id_provider, stonith_id, stonith_agent, operations, meta_attributes, instance_attributes, allow_invalid_operation, allow_invalid_instance_attributes, use_default_operations, ) if ensure_disabled: resource.common.disable(stonith_element, id_provider) if env.report_processor.report_list( resource.validations.validate_move_resources_to_group( group_element, [stonith_element], adjacent_resource_element, )).has_errors: raise LibraryError() resource.hierarchy.move_resources_to_group( group_element, [stonith_element], adjacent_resource_element, put_after_adjacent, )
def __init__(self, cib_element): """ etree cib_element -- any element of the xml to being check against """ self._cib = get_root(cib_element) self._booked_ids = set()
def update_scsi_devices_without_restart( runner: CommandRunner, cluster_state: _Element, resource_el: _Element, id_provider: IdProvider, devices_list: Iterable[str], ) -> None: """ Update scsi devices without restart of stonith resource or other resources. runner -- command runner instance cluster_state -- status of the cluster resource_el -- resource element being updated id_provider -- elements' ids generator device_list -- list of updated scsi devices """ resource_id = resource_el.get("id", "") roles_with_nodes = get_resource_state(cluster_state, resource_id) if "Started" not in roles_with_nodes: raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( f"resource '{resource_id}' is not running on any node", reason_type=reports.const. STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM_REASON_NOT_RUNNING, ))) if len(roles_with_nodes["Started"]) != 1: # TODO: do we want to be able update cloned fence_scsi? Or just case # when it's running on more than 1 node? It is possible but we need to # update more lrm_rsc_op elements raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( f"resource '{resource_id}' is running on more than 1 node") )) node_name = roles_with_nodes["Started"][0] new_instance_attrs = {"devices": ",".join(sorted(devices_list))} arrange_first_instance_attributes(resource_el, new_instance_attrs, id_provider) lrm_rsc_op_start_list = _get_lrm_rsc_op_elements(get_root(resource_el), resource_id, node_name, "start") if len(lrm_rsc_op_start_list) == 1: _update_digest_attrs_in_lrm_rsc_op( lrm_rsc_op_start_list[0], get_resource_digests( runner, resource_id, node_name, new_instance_attrs, ), ) else: raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( "lrm_rsc_op element for start operation was not found"))) monitor_attrs_list = _get_monitor_attrs(resource_el) lrm_rsc_op_monitor_list = _get_lrm_rsc_op_elements(get_root(resource_el), resource_id, node_name, "monitor") if len(lrm_rsc_op_monitor_list) != len(monitor_attrs_list): raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( ("number of lrm_rsc_op and op elements for monitor " "operation differs")))) for monitor_attrs in monitor_attrs_list: lrm_rsc_op_list = _get_lrm_rsc_op_elements( get_root(resource_el), resource_id, node_name, "monitor", monitor_attrs["interval"], ) if len(lrm_rsc_op_list) == 1: _update_digest_attrs_in_lrm_rsc_op( lrm_rsc_op_list[0], get_resource_digests( runner, resource_id, node_name, new_instance_attrs, crm_meta_attributes=monitor_attrs, ), ) else: raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( ("monitor lrm_rsc_op element for resource " f"'{resource_id}', node '{node_name}' and interval " f"'{monitor_attrs['interval']}' not found"))))
def __init__(self, cib_element: _Element): """ cib_element -- any element of the xml to check against """ self._cib = get_root(cib_element) self._booked_ids: Set[str] = set()
def get_nodes_remote(tree): return NodeAddressesList(remote_node.find_node_list(get_root(tree)))
def get_nodes_guest(tree): return NodeAddressesList(guest_node.find_node_list(get_root(tree)))
def get_node_names(cib: _Element) -> Set[str]: return { str(node.attrib["uname"]) for node in get_nodes(get_root(cib)).iterfind("./node") }