def _validate_map_ids_exist(bundle_el, map_type, map_label, id_list): report_list = [] for id in id_list: try: find_element_by_tag_and_id( map_type, bundle_el, id, id_description=map_label ) except LibraryError as e: report_list.extend(e.args) return report_list
def _find_resources_or_raise( resources_section, resource_ids, additional_search=None ): if not additional_search: additional_search = lambda x: [x] report_list = [] resource_el_list = [] resource_tags = ( resource.clone.ALL_TAGS + [resource.group.TAG, resource.primitive.TAG, resource.bundle.TAG] ) for res_id in resource_ids: try: resource_el_list.extend( additional_search( find_element_by_tag_and_id( resource_tags, resources_section, res_id ) ) ) except LibraryError as e: report_list.extend(e.args) if report_list: raise LibraryError(*report_list) return resource_el_list
def find_valid_resource_id(report_processor, cib, in_clone_allowed, id): parent_tags = resource.clone.ALL_TAGS + [resource.bundle.TAG] resource_element = find_element_by_tag_and_id( 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"] if in_clone_allowed: report_processor.process( reports.resource_for_constraint_is_multiinstance( resource_element.attrib["id"], "clone" if clone.tag == "master" else clone.tag, clone.attrib["id"], ReportItemSeverity.WARNING, )) return resource_element.attrib["id"] raise LibraryError( reports.resource_for_constraint_is_multiinstance( resource_element.attrib["id"], "clone" if clone.tag == "master" else clone.tag, clone.attrib["id"], ReportItemSeverity.ERROR, #repair to clone is workaround for web ui, so we put only information #about one forceable possibility forceable=report_codes.FORCE_CONSTRAINT_MULTIINSTANCE_RESOURCE))
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_resources_or_raise(resources_section, resource_ids, additional_search=None): if not additional_search: additional_search = lambda x: [x] report_list = [] resource_el_list = [] resource_tags = ( resource.clone.ALL_TAGS + [resource.group.TAG, resource.primitive.TAG, resource.bundle.TAG]) for res_id in resource_ids: try: resource_el_list.extend( additional_search( find_element_by_tag_and_id( resource_tags, resources_section, res_id, # pacemaker-2.0 deprecated masters. We treat masters as # clones. Do not report we were looking for a master, # say we were looking for a clone instead. Since we # look for all resource types including clones, just # drop the master. id_types=[ tag for tag in resource_tags if tag != resource.clone.TAG_MASTER ]))) except LibraryError as e: report_list.extend(e.args) if report_list: raise LibraryError(*report_list) return resource_el_list
def test_raises_when_is_under_another_context(self): tree = etree.fromstring(""" <cib> <resources> <group id="g1"><primitive id="a"/></group> <group id="g2"><primitive id="b"/></group> </resources> </cib> """) assert_raise_library_error( lambda: lib.find_element_by_tag_and_id( "primitive", tree.find('.//resources/group[@id="g2"]'), "a" ), ( severities.ERROR, report_codes.OBJECT_WITH_ID_IN_UNEXPECTED_CONTEXT, { "type": "primitive", "id": "a", "expected_context_type": "group", "expected_context_id": "g2", }, ), )
def node_add_guest( env, node_name, resource_id, options, allow_incomplete_distribution=False, allow_pacemaker_remote_service_fail=False, wait=False, ): """ setup resource (resource_id) as guest node and setup node as guest LibraryEnvironment env provides all for communication with externals string resource_id -- specifies resource that should be guest node dict options could contain keys remote-node, remote-port, remote-addr, remote-connect-timeout bool allow_incomplete_distribution -- is a flag for allowing successfully finish this command even if is file distribution not succeeded bool allow_pacemaker_remote_service_fail -- is a flag for allowing successfully finish this command even if starting/enabling pacemaker_remote not succeeded mixed wait is flag for controlling waiting for pacemaker iddle mechanism """ _ensure_consistently_live_env(env) env.ensure_wait_satisfiable(wait) cib = env.get_cib() current_nodes = get_nodes(env.get_corosync_conf(), cib) report_list = guest_node.validate_set_as_guest(cib, current_nodes, node_name, options) try: resource_element = find_element_by_tag_and_id(primitive.TAG, get_resources(cib), resource_id) report_list.extend(guest_node.validate_is_not_guest(resource_element)) except LibraryError as e: report_list.extend(e.args) env.report_processor.process_list(report_list) guest_node.set_as_guest( resource_element, node_name, options.get("remote-addr", None), options.get("remote-port", None), options.get("remote-connect-timeout", None), ) _prepare_pacemaker_remote_environment( env, current_nodes, guest_node.get_host_from_options(node_name, options), allow_incomplete_distribution, allow_pacemaker_remote_service_fail, ) env.push_cib(cib, wait) if wait: _ensure_resource_running(env, resource_id)
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 find_valid_resource_id(report_processor, cib, in_clone_allowed, _id): parent_tags = resource.clone.ALL_TAGS + [resource.bundle.TAG] resource_element = find_element_by_tag_and_id( 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"] if in_clone_allowed: report_processor.process( reports.resource_for_constraint_is_multiinstance( resource_element.attrib["id"], "clone" if clone.tag == "master" else clone.tag, clone.attrib["id"], ReportItemSeverity.WARNING, ) ) return resource_element.attrib["id"] raise LibraryError(reports.resource_for_constraint_is_multiinstance( resource_element.attrib["id"], "clone" if clone.tag == "master" else clone.tag, clone.attrib["id"], ReportItemSeverity.ERROR, #repair to clone is workaround for web ui, so we put only information #about one forceable possibility forceable=report_codes.FORCE_CONSTRAINT_MULTIINSTANCE_RESOURCE ))
def _find( tag, acl_section, element_id, none_if_id_unused=False, id_types=None ): return find_element_by_tag_and_id( tag, acl_section, element_id, id_types=id_types, none_if_id_unused=none_if_id_unused, )
def _find(tag, acl_section, element_id, none_if_id_unused=False, id_types=None): return find_element_by_tag_and_id( tag, acl_section, element_id, id_types=id_types, none_if_id_unused=none_if_id_unused, )
def bundle_update( env, bundle_id, container_options=None, network_options=None, port_map_add=None, port_map_remove=None, storage_map_add=None, storage_map_remove=None, force_options=False, wait=False, ): """ Modify an existing bundle (does not touch encapsulated resources) LibraryEnvironment env -- provides communication with externals string bundle_id -- id of the bundle to modify dict container_options -- container options to modify dict network_options -- network options to modify list of dict port_map_add -- list of port mapping options to add list of string port_map_remove -- list of port mapping ids to remove list of dict storage_map_add -- list of storage mapping options to add list of string storage_map_remove -- list of storage mapping ids to remove bool force_options -- return warnings instead of forceable errors mixed wait -- False: no wait, None: wait default timeout, int: wait timeout """ container_options = container_options or {} network_options = network_options or {} port_map_add = port_map_add or [] port_map_remove = port_map_remove or [] storage_map_add = storage_map_add or [] storage_map_remove = storage_map_remove or [] with resource_environment( env, wait, [bundle_id], # bundles are always enabled, currently there is no way to disable them disabled_after_wait=False, required_cib_version=(2, 8, 0)) as resources_section: id_provider = IdProvider(resources_section) bundle_element = find_element_by_tag_and_id(resource.bundle.TAG, resources_section, bundle_id) env.report_processor.process_list( resource.bundle.validate_update(id_provider, bundle_element, container_options, network_options, port_map_add, port_map_remove, storage_map_add, storage_map_remove, force_options)) resource.bundle.update(id_provider, bundle_element, container_options, network_options, port_map_add, port_map_remove, storage_map_add, storage_map_remove)
def test_returns_element_when_exists_one_of_tags(self): tree = etree.fromstring(""" <cib> <resources> <group id="a"/> <primitive id="b"/> </resources> </cib> """) element = lib.find_element_by_tag_and_id(["group", "primitive"], tree.find(".//resources"), "a") self.assertEqual("group", element.tag) self.assertEqual("a", element.attrib["id"])
def _find( tag, acl_section, element_id, none_if_id_unused=False, id_description=None ): if tag not in TAG_DESCRIPTION_MAP.keys(): raise AssertionError("Unknown acl tag '{0}'".format(tag)) return find_element_by_tag_and_id( tag, acl_section, element_id, id_description=id_description if id_description else TAG_DESCRIPTION_MAP[tag] , none_if_id_unused=none_if_id_unused, )
def provide_group(resources_section, group_id): """ Provide group with id=group_id. Create new group if group with id=group_id does not exists. etree.Element resources_section is place where new group will be appended string group_id is id of group """ group_element = find_element_by_tag_and_id(TAG, resources_section, group_id, none_if_id_unused=True) if group_element is None: group_element = etree.SubElement(resources_section, TAG, id=group_id) return group_element
def test_returns_element_when_exists_one_of_tags(self): tree = etree.fromstring(""" <cib> <resources> <group id="a"/> <primitive id="b"/> </resources> </cib> """) element = lib.find_element_by_tag_and_id( ["group", "primitive"], tree.find(".//resources"), "a" ) self.assertEqual("group", element.tag) self.assertEqual("a", element.attrib["id"])
def provide_group(resources_section, group_id): """ Provide group with id=group_id. Create new group if group with id=group_id does not exists. etree.Element resources_section is place where new group will be appended string group_id is id of group """ group_element = find_element_by_tag_and_id( TAG, resources_section, group_id, none_if_id_unused=True ) if group_element is None: group_element = etree.SubElement(resources_section, TAG, id=group_id) return group_element
def place_resource( group_element, primitive_element, adjacent_resource_id=None, put_after_adjacent=False, ): """ Add resource into group. This function is also applicable for a modification of the resource position because the primitive element is replanted from anywhere (including group itself) to concrete place inside group. etree.Element group_element is element where to put primitive_element etree.Element primitive_element is element for placement string adjacent_resource_id is id of the existing resource in group. primitive_element will be put beside adjacent_resource_id if specified. bool put_after_adjacent is flag where put primitive_element: before adjacent_resource_id if put_after_adjacent=False after adjacent_resource_id if put_after_adjacent=True Note that it make sense only if adjacent_resource_id is specified """ if primitive_element.attrib["id"] == adjacent_resource_id: raise LibraryError( ReportItem.error( reports.messages.CannotGroupResourceNextToItself( adjacent_resource_id))) if not adjacent_resource_id: group_element.append(primitive_element) return adjacent_resource = find_element_by_tag_and_id( "primitive", group_element, adjacent_resource_id, ) if put_after_adjacent and adjacent_resource.getnext() is None: group_element.append(primitive_element) return index = group_element.index(adjacent_resource.getnext( ) if put_after_adjacent else adjacent_resource) group_element.insert(index, primitive_element)
def find_valid_resource_id( report_processor, cib, can_repair_to_clone, in_clone_allowed, id ): resource_element = find_element_by_tag_and_id( resource.clone.ALL_TAGS + [resource.primitive.TAG, resource.group.TAG], cib, id, id_description="resource" ) if resource_element.tag in resource.clone.ALL_TAGS: return resource_element.attrib["id"] clone = find_parent(resource_element, resource.clone.ALL_TAGS) if clone is None: return resource_element.attrib["id"] if can_repair_to_clone: #this is workaround for web ui, console should not use it, so we do not #warn about it return clone.attrib["id"] if in_clone_allowed: report_processor.process( reports.resource_for_constraint_is_multiinstance( resource_element.attrib["id"], clone.tag, clone.attrib["id"], ReportItemSeverity.WARNING, ) ) return resource_element.attrib["id"] raise LibraryError(reports.resource_for_constraint_is_multiinstance( resource_element.attrib["id"], clone.tag, clone.attrib["id"], ReportItemSeverity.ERROR, #repair to clone is workaround for web ui, so we put only information #about one forceable possibility forceable=report_codes.FORCE_CONSTRAINT_MULTIINSTANCE_RESOURCE ))
def place_resource( group_element, primitive_element, adjacent_resource_id=None, put_after_adjacent=False ): """ Add resource into group. This function is also applicable for a modification of the resource position because the primitive element is replanted from anywhere (including group itself) to concrete place inside group. etree.Element group_element is element where to put primitive_element etree.Element primitive_element is element for placement string adjacent_resource_id is id of the existing resource in group. primitive_element will be put beside adjacent_resource_id if specified. bool put_after_adjacent is flag where put primitive_element: before adjacent_resource_id if put_after_adjacent=False after adjacent_resource_id if put_after_adjacent=True Note that it make sense only if adjacent_resource_id is specified """ if primitive_element.attrib["id"] == adjacent_resource_id: raise LibraryError(reports.resource_cannot_be_next_to_itself_in_group( adjacent_resource_id, group_element.attrib["id"], )) if not adjacent_resource_id: return group_element.append(primitive_element) adjacent_resource = find_element_by_tag_and_id( "primitive", group_element, adjacent_resource_id, id_description="resource", ) if put_after_adjacent and adjacent_resource.getnext() is None: return group_element.append(primitive_element) index = group_element.index( adjacent_resource.getnext() if put_after_adjacent else adjacent_resource ) group_element.insert(index, primitive_element)
def validate_resource_instance_attributes_update( resource_agent, instance_attributes, resource_id, resources_section, force=False ): return ( resource_agent.validate_parameters_update( get_nvset_as_dict( "instance_attributes", find_element_by_tag_and_id( "primitive", resources_section, resource_id ) ), instance_attributes, force=force, ) + validate_unique_instance_attributes( resource_agent, instance_attributes, resources_section, resource_id=resource_id, force=force, ) )
def create_into_bundle( env, resource_id, resource_agent_name, operations, meta_attributes, instance_attributes, bundle_id, allow_absent_agent=False, allow_invalid_operation=False, allow_invalid_instance_attributes=False, use_default_operations=True, ensure_disabled=False, wait=False, allow_not_suitable_command=False, ): """ Create a new resource in a cib and put it into an existing bundle LibraryEnvironment env provides all for communication with externals string resource_id is identifier of resource string resource_agent_name contains name for the identification of agent list of dict operations contains attributes for each entered operation dict meta_attributes contains attributes for primitive/meta_attributes dict instance_attributes contains attributes for primitive/instance_attributes string bundle_id is id of an existing bundle to put the created resource in bool allow_absent_agent is a flag for allowing agent that is not installed in a system bool allow_invalid_operation is a flag for allowing to use operations that are not listed in a resource agent metadata bool allow_invalid_instance_attributes is a flag for allowing to use instance attributes that are not listed in a resource agent metadata or for allowing to not use the instance_attributes that are required in resource agent metadata bool use_default_operations is a flag for stopping stopping of adding default cib operations (specified in a resource agent) bool ensure_disabled is flag that keeps resource in target-role "Stopped" mixed wait is flag for controlling waiting for pacemaker iddle mechanism bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND """ resource_agent = get_agent( env.report_processor, env.cmd_runner(), resource_agent_name, allow_absent_agent, ) with resource_environment( env, wait, [resource_id], _ensure_disabled_after_wait( ensure_disabled or resource.common.are_meta_disabled(meta_attributes) ), required_cib_version=Version(2, 8, 0) ) as resources_section: _check_special_cases( env, resource_agent, resources_section, resource_id, meta_attributes, instance_attributes, allow_not_suitable_command ) primitive_element = resource.primitive.create( env.report_processor, resources_section, resource_id, resource_agent, operations, meta_attributes, instance_attributes, allow_invalid_operation, allow_invalid_instance_attributes, use_default_operations, ) if ensure_disabled: resource.common.disable(primitive_element) resource.bundle.add_resource( find_element_by_tag_and_id( resource.bundle.TAG, resources_section, bundle_id ), primitive_element )
def bundle_update( env, bundle_id, container_options=None, network_options=None, port_map_add=None, port_map_remove=None, storage_map_add=None, storage_map_remove=None, meta_attributes=None, force_options=False, wait=False, ): """ Modify an existing bundle (does not touch encapsulated resources) LibraryEnvironment env -- provides communication with externals string bundle_id -- id of the bundle to modify dict container_options -- container options to modify dict network_options -- network options to modify list of dict port_map_add -- list of port mapping options to add list of string port_map_remove -- list of port mapping ids to remove list of dict storage_map_add -- list of storage mapping options to add list of string storage_map_remove -- list of storage mapping ids to remove dict meta_attributes -- meta attributes to update bool force_options -- return warnings instead of forceable errors mixed wait -- False: no wait, None: wait default timeout, int: wait timeout """ container_options = container_options or {} network_options = network_options or {} port_map_add = port_map_add or [] port_map_remove = port_map_remove or [] storage_map_add = storage_map_add or [] storage_map_remove = storage_map_remove or [] meta_attributes = meta_attributes or {} with resource_environment( env, wait, [bundle_id], required_cib_version=Version(2, 8, 0) ) as resources_section: # no need to run validations related to remote and guest nodes as those # nodes can only be created from primitive resources id_provider = IdProvider(resources_section) bundle_element = find_element_by_tag_and_id( resource.bundle.TAG, resources_section, bundle_id ) env.report_processor.process_list( resource.bundle.validate_update( id_provider, bundle_element, container_options, network_options, port_map_add, port_map_remove, storage_map_add, storage_map_remove, # TODO meta attributes - there is no validation for now force_options ) ) resource.bundle.update( id_provider, bundle_element, container_options, network_options, port_map_add, port_map_remove, storage_map_add, storage_map_remove, meta_attributes )
def node_add_guest( env, node_name, resource_id, options, skip_offline_nodes=False, allow_incomplete_distribution=False, allow_pacemaker_remote_service_fail=False, wait=False, ): """ Make a guest node from the specified resource LibraryEnvironment env -- provides all for communication with externals string resource_id -- specifies resource that should become a guest node dict options -- guest node options (remote-port, remote-addr, remote-connect-timeout) bool skip_offline_nodes -- if True, ignore when some nodes are offline bool allow_incomplete_distribution -- if True, allow this command to finish successfully even if file distribution did not succeed bool allow_pacemaker_remote_service_fail -- if True, allow this command to finish successfully even if starting/enabling pacemaker_remote did not succeed mixed wait -- a flag for controlling waiting for pacemaker idle mechanism """ # TODO # * make the node name mandatory and the node address optional # * in this function interface and comment # * in cli - do not fill the addr if not specified # * in usage and man page # * get a target factory from lib.env # * use the factory to turn node names to targets # * this will create reports for unknown node names (not authenticated) # * if the node addr is not specified, use the first addr from the matching # target # * pass the target to communication functions instead of the node name # * do not create targets again and again in each function _ensure_consistently_live_env(env) env.ensure_wait_satisfiable(wait) cib = env.get_cib() existing_nodes_names, existing_nodes_addrs = get_existing_nodes_names_addrs( env.get_corosync_conf(), cib) report_list = guest_node.validate_set_as_guest(cib, existing_nodes_names, existing_nodes_addrs, node_name, options) try: resource_element = find_element_by_tag_and_id(primitive.TAG, get_resources(cib), resource_id) report_list.extend(guest_node.validate_is_not_guest(resource_element)) except LibraryError as e: report_list.extend(e.args) env.report_processor.process_list(report_list) guest_node.set_as_guest( resource_element, node_name, options.get("remote-addr", None), options.get("remote-port", None), options.get("remote-connect-timeout", None), ) _prepare_pacemaker_remote_environment( env, existing_nodes_names, node_name, skip_offline_nodes, allow_incomplete_distribution, allow_pacemaker_remote_service_fail, ) env.push_cib(wait=wait) if wait: _ensure_resource_running(env, resource_id)
def validate_resource_instance_attributes_update( resource_agent: ResourceAgentFacade, instance_attributes: Mapping[str, str], resource_id: str, resources_section: _Element, force: bool = False, ) -> reports.ReportItemList: # TODO This function currently accepts the updated resource as a string and # finds the corresponding xml element by itself. This is needed as the # function is called from old pcs code which uses dom while pcs.lib uses # lxml. Once resource update command is moved to pcs.lib, this function # will be fixed to accept the updated resource as an element instead of a # string. report_items: reports.ReportItemList = [] current_instance_attrs = get_nvset_as_dict( INSTANCE_ATTRIBUTES_TAG, find_element_by_tag_and_id(TAG, resources_section, resource_id), ) if resource_agent.metadata.agent_exists: report_items += validate.ValidatorAll( resource_agent.get_validators_allowed_parameters(force) ).validate( # Do not report unknown parameters already set in the CIB. It would # be confusing to report an error in an option not actually created # now. { name: value for name, value in instance_attributes.items() if name not in current_instance_attrs } ) report_items += validate.ValidatorAll( resource_agent.get_validators_deprecated_parameters() ).validate( { name: value for name, value in instance_attributes.items() # Allow removing deprecated parameters if value != "" # we create a custom report for stonith parameter "action" and not ( resource_agent.metadata.name.is_stonith and name == "action" ) } ) # Check that required parameters have not been removed. This is # complicated by two facts: # * parameters may by deprecated by other parameters, setting one # required parameter from such group is enough # * we only want to report errors related to attributes to be updated final_attrs = dict(current_instance_attrs) for name, value in instance_attributes.items(): if value == "": final_attrs.pop(name, None) else: final_attrs[name] = value report_items += validate.ValidatorAll( # Limit validation only to parameters entered now in an update # command. We don't want to report missing parameters not mentioned # in a command now, that would be confusing to users. resource_agent.get_validators_required_parameters( force, only_parameters=instance_attributes.keys() ) ).validate(final_attrs) if resource_agent.metadata.name.is_stonith: report_items += _validate_stonith_action(instance_attributes, force) if resource_agent.metadata.agent_exists: report_items += _validate_unique_instance_attributes( resource_agent.metadata, instance_attributes, resources_section, resource_id=resource_id, force=force, ) return report_items