def test_get_and_push_cib_version_upgrade_needed(self): (self.config .runner.cib.load(name="load_cib_old", filename="cib-empty-2.6.xml") .runner.cib.upgrade() .runner.cib.load(filename="cib-empty-2.8.xml") ) env = self.env_assist.get_env() env.get_cib(Version(2, 8, 0)) self.env_assist.assert_reports( [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)] )
def test_upgraded_lower_version(self, mock_upgrade, mock_get_cib): mock_get_cib.return_value = etree.tostring(self.cib).decode() assert_raise_library_error( lambda: lib.ensure_cib_version(self.mock_runner, self.cib, Version(2, 3, 5)), (Severity.ERROR, report_codes.CIB_UPGRADE_FAILED_TO_MINIMAL_REQUIRED_VERSION, { "required_version": "2.3.5", "current_version": "2.3.4" })) mock_upgrade.assert_called_once_with(self.mock_runner) mock_get_cib.assert_called_once_with(self.mock_runner)
def test_upgraded_higher_version(self, mock_upgrade, mock_get_cib): upgraded_cib = '<cib validate-with="pacemaker-2.3.6"/>' mock_get_cib.return_value = upgraded_cib assert_xml_equal( upgraded_cib, etree.tostring( lib.ensure_cib_version( self.mock_runner, self.cib, Version(2, 3, 5) ) ).decode() ) mock_upgrade.assert_called_once_with(self.mock_runner) mock_get_cib.assert_called_once_with(self.mock_runner)
def test_upgraded_lower_version_dont_fail(self, mock_upgrade, mock_get_cib): expected_cib = '<cib validate-with="pacemaker-2.3.4"/>' mock_get_cib.return_value = expected_cib actual_cib, was_upgraded = lib.ensure_cib_version( self.mock_runner, self.cib, Version(2, 3, 5), fail_if_version_not_met=False, ) assert_xml_equal(expected_cib, etree.tostring(actual_cib).decode()) self.assertFalse(was_upgraded) mock_upgrade.assert_called_once_with(self.mock_runner) mock_get_cib.assert_called_once_with(self.mock_runner)
def add_level( lib_env, level, target_type, target_value, devices, force_device=False, force_node=False ): """ Validate and add a new fencing level LibraryEnvironment lib_env -- environment 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 bool force_device -- continue even if a stonith device does not exist bool force_node -- continue even if a node (target) does not exist """ version_check = None if target_type == TARGET_TYPE_REGEXP: version_check = Version(2, 3, 0) elif target_type == TARGET_TYPE_ATTRIBUTE: version_check = Version(2, 4, 0) cib = lib_env.get_cib(version_check) cib_fencing_topology.add_level( lib_env.report_processor, get_fencing_topology(cib), get_resources(cib), level, target_type, target_value, devices, ClusterState( get_cluster_status_xml(lib_env.cmd_runner()) ).node_section.nodes, force_device, force_node ) lib_env.report_processor.send() lib_env.push_cib()
def test_cib_parse_error(self, mock_upgrade, mock_get_cib): mock_get_cib.return_value = "not xml" assert_raise_library_error( lambda: lib.ensure_cib_version( self.mock_runner, self.cib, Version(2, 3, 5) ), ( Severity.ERROR, report_codes.CIB_UPGRADE_FAILED, {"reason": start_tag_error_text(),}, ), ) mock_upgrade.assert_called_once_with(self.mock_runner) mock_get_cib.assert_called_once_with(self.mock_runner)
def test_everything(self): context_element = etree.fromstring("""<context id="a" />""") id_provider = IdProvider(context_element) nvpair_multi.nvset_append_new( context_element, id_provider, Version(3, 5, 0), nvpair_multi.NVSET_META, { "attr1": "value1", "attr-empty": "", "attr2": "value2" }, { "id": "custom-id", "score": "INFINITY", "empty-attr": "" }, nvset_rule=BoolExpr( BOOL_AND, [RscExpr("ocf", "pacemaker", "Dummy"), OpExpr("start", None)], ), ) assert_xml_equal( """ <context id="a"> <meta_attributes id="custom-id" score="INFINITY"> <rule id="custom-id-rule" boolean-op="and" score="INFINITY" > <rsc_expression id="custom-id-rule-rsc-ocf-pacemaker-Dummy" class="ocf" provider="pacemaker" type="Dummy" /> <op_expression id="custom-id-rule-op-start" name="start" /> </rule> <nvpair id="custom-id-attr1" name="attr1" value="value1" /> <nvpair id="custom-id-attr2" name="attr2" value="value2" /> </meta_attributes> </context> """, etree_to_str(context_element), )
def test_target_regexp_updates_cib( self, mock_get_cib, mock_status_xml, mock_status, mock_push_cib, mock_get_topology, mock_get_resources, mock_add_level, ): self.prepare_mocks( mock_get_cib, mock_status_xml, mock_status, mock_get_topology, mock_get_resources, ) lib_env = create_lib_env() lib.add_level( lib_env, "level", TARGET_TYPE_REGEXP, "target value", "devices", "force device", "force node", ) mock_add_level.assert_called_once_with( lib_env.report_processor, "topology el", "resources_el", "level", TARGET_TYPE_REGEXP, "target value", "devices", "nodes", "force device", "force node", ) mock_get_cib.assert_called_once_with(Version(2, 3, 0)) self.assert_mocks( mock_status_xml, mock_status, mock_get_topology, mock_get_resources, mock_push_cib, )
def _get_cib_version(cib, attribute, regexp, none_if_missing=False): version = cib.get(attribute) if version is None: if none_if_missing: return None raise LibraryError( reports.cib_load_error_invalid_format( "the attribute '{0}' of the element 'cib' is missing".format( attribute))) match = regexp.match(version) if not match: raise LibraryError( reports.cib_load_error_invalid_format( ("the attribute '{0}' of the element 'cib' has an invalid" " value: '{1}'").format(attribute, version))) return Version(int(match.group("major")), int(match.group("minor")), int(match.group("rev")) if match.group("rev") else None)
def get_cib( self, minimal_version: Optional[Version] = None, nice_to_have_version: Optional[Version] = None, ) -> _Element: if self.__loaded_cib_diff_source is not None: raise AssertionError("CIB has already been loaded") self.__loaded_cib_diff_source = get_cib_xml(self.cmd_runner()) self.__loaded_cib_to_modify = get_cib(self.__loaded_cib_diff_source) if ( nice_to_have_version is not None and minimal_version is not None and minimal_version >= nice_to_have_version ): nice_to_have_version = None for version, mandatory in ( (nice_to_have_version, False), (minimal_version, True), ): if version is not None: upgraded_cib, was_upgraded = ensure_cib_version( self.cmd_runner(), self.__loaded_cib_to_modify, version, fail_if_version_not_met=mandatory, ) if was_upgraded: self.__loaded_cib_to_modify = upgraded_cib self.__loaded_cib_diff_source = etree_to_str(upgraded_cib) if not self._cib_upgrade_reported: self.report_processor.report( ReportItem.info( reports.messages.CibUpgradeSuccessful() ) ) self._cib_upgrade_reported = True self.__loaded_cib_diff_source_feature_set = get_cib_crm_feature_set( self.__loaded_cib_to_modify, none_if_missing=True ) or Version(0, 0, 0) return self.__loaded_cib_to_modify
def test_options(self): context_element = etree.fromstring("""<context id="a" />""") id_provider = IdProvider(context_element) nvpair_multi.nvset_append_new( context_element, id_provider, Version(3, 5, 0), nvpair_multi.NVSET_META, {}, {"score": "INFINITY", "empty-attr": ""}, ) assert_xml_equal( """ <context id="a"> <meta_attributes id="a-meta_attributes" score="INFINITY" /> </context> """, etree_to_str(context_element), )
def test_custom_id(self): context_element = etree.fromstring("""<context id="a" />""") id_provider = IdProvider(context_element) nvpair_multi.nvset_append_new( context_element, id_provider, Version(3, 5, 0), nvpair_multi.NVSET_META, {}, {"id": "custom-id"}, ) assert_xml_equal( """ <context id="a"> <meta_attributes id="custom-id" /> </context> """, etree_to_str(context_element), )
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 get_cib(self, minimal_version=None): if self.__loaded_cib_diff_source is not None: raise AssertionError("CIB has already been loaded") self.__loaded_cib_diff_source = get_cib_xml(self.cmd_runner()) self.__loaded_cib_to_modify = get_cib(self.__loaded_cib_diff_source) if minimal_version is not None: upgraded_cib = ensure_cib_version(self.cmd_runner(), self.__loaded_cib_to_modify, minimal_version) if upgraded_cib is not None: self.__loaded_cib_to_modify = upgraded_cib self.__loaded_cib_diff_source = etree_to_str(upgraded_cib) if not self._cib_upgrade_reported: self.report_processor.process( reports.cib_upgrade_successful()) self._cib_upgrade_reported = True self.__loaded_cib_diff_source_feature_set = (get_cib_crm_feature_set( self.__loaded_cib_to_modify, none_if_missing=True) or Version(0, 0, 0)) return self.__loaded_cib_to_modify
def _get_cib_version(cib: _ElementTree, attribute: str, regexp: Pattern) -> Version: version = cib.getroot().get(attribute) if version is None: raise LibraryError( ReportItem.error( reports.messages.CibLoadErrorBadFormat( f"the attribute '{attribute}' of the element 'cib' " "is missing"))) match = regexp.match(version) if not match: raise LibraryError( ReportItem.error( reports.messages.CibLoadErrorBadFormat( f"the attribute '{attribute}' of the element 'cib' has " f"an invalid value: '{version}'"))) return Version( int(match.group("major")), int(match.group("minor")), int(match.group("rev")) if match.group("rev") else None, )
def get_cib(self, minimal_version: Optional[Version] = None) -> Element: if self.__loaded_cib_diff_source is not None: raise AssertionError("CIB has already been loaded") self.__loaded_cib_diff_source = get_cib_xml(self.cmd_runner()) self.__loaded_cib_to_modify = get_cib(self.__loaded_cib_diff_source) if minimal_version is not None: upgraded_cib = ensure_cib_version(self.cmd_runner(), self.__loaded_cib_to_modify, minimal_version) if upgraded_cib is not None: self.__loaded_cib_to_modify = upgraded_cib self.__loaded_cib_diff_source = etree_to_str(upgraded_cib) if not self._cib_upgrade_reported: self.report_processor.report( ReportItem.info( reports.messages.CibUpgradeSuccessful())) self._cib_upgrade_reported = True self.__loaded_cib_diff_source_feature_set = get_cib_crm_feature_set( self.__loaded_cib_to_modify, none_if_missing=True) or Version( 0, 0, 0) return self.__loaded_cib_to_modify
def _get_cib_version(cib, attribute, regexp, none_if_missing=False): version = cib.get(attribute) if version is None: if none_if_missing: return None raise LibraryError( ReportItem.error( reports.messages.CibLoadErrorBadFormat( f"the attribute '{attribute}' of the element 'cib' " "is missing"))) match = regexp.match(version) if not match: raise LibraryError( ReportItem.error( reports.messages.CibLoadErrorBadFormat( f"the attribute '{attribute}' of the element 'cib' has " f"an invalid value: '{version}'"))) return Version( int(match.group("major")), int(match.group("minor")), int(match.group("rev")) if match.group("rev") else None, )
from typing import NewType from pcs.common.tools import Version PcmkRoleType = NewType("PcmkRoleType", str) PCMK_ROLE_STARTED = PcmkRoleType("Started") PCMK_ROLE_STOPPED = PcmkRoleType("Stopped") PCMK_ROLE_PROMOTED = PcmkRoleType("Promoted") PCMK_ROLE_UNPROMOTED = PcmkRoleType("Unpromoted") PCMK_ROLE_PROMOTED_LEGACY = PcmkRoleType("Master") PCMK_ROLE_UNPROMOTED_LEGACY = PcmkRoleType("Slave") PCMK_ROLE_PROMOTED_PRIMARY = PCMK_ROLE_PROMOTED PCMK_ROLE_UNPROMOTED_PRIMARY = PCMK_ROLE_UNPROMOTED PCMK_ROLES_PROMOTED = (PCMK_ROLE_PROMOTED, PCMK_ROLE_PROMOTED_LEGACY) PCMK_ROLES_UNPROMOTED = (PCMK_ROLE_UNPROMOTED, PCMK_ROLE_UNPROMOTED_LEGACY) PCMK_ROLES_RUNNING = ((PCMK_ROLE_STARTED, ) + PCMK_ROLES_PROMOTED + PCMK_ROLES_UNPROMOTED) PCMK_ROLES = (PCMK_ROLE_STOPPED, ) + PCMK_ROLES_RUNNING PCMK_ACTION_START = "start" PCMK_ACTIONS = (PCMK_ACTION_START, "stop", "promote", "demote") PCMK_NEW_ROLES_CIB_VERSION = Version(3, 7, 0) PCMK_RULES_NODE_ATTR_EXPR_WITH_INT_TYPE_CIB_VERSION = Version(3, 5, 0) PCMK_ON_FAIL_DEMOTE_CIB_VERSION = Version(3, 4, 0)
def test_higher_version(self, mock_upgrade, mock_get_cib): self.assertTrue( lib.ensure_cib_version(self.mock_runner, self.cib, Version( 2, 3, 3)) is None) mock_upgrade.assert_not_called() mock_get_cib.assert_not_called()
def assert_lt_tuple(self, a, b): self.assert_lt(Version(*a), Version(*b))
def assert_eq_tuple(self, a, b): self.assert_eq(Version(*a), Version(*b))
def test_success_no_revision(self): self.assertEqual( Version(3, 1), lib.get_cib_crm_feature_set( etree.XML('<cib crm_feature_set="3.1" />')), )
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, ): # pylint: disable=too-many-arguments """ 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 {} required_cib_version = Version(2, 8, 0) if "promoted-max" in container_options: required_cib_version = Version(3, 0, 0) with resource_environment( env, wait, [bundle_id], required_cib_version=required_cib_version) 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_bundle(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 test_with_revision(self): self.assertEqual( Version(1, 2, 3), lib.get_pacemaker_version_by_which_cib_was_validated( etree.XML('<cib validate-with="pacemaker-1.2.3"/>')), )
def test_rule(self): context_element = etree.fromstring("""<context id="a" />""") id_provider = IdProvider(context_element) nvpair_multi.nvset_append_new( context_element, id_provider, Version(3, 5, 0), nvpair_multi.NVSET_META, {}, {}, nvset_rule=BoolExpr( BOOL_AND, [ RscExpr("ocf", "pacemaker", "Dummy"), OpExpr("start", None), BoolExpr( BOOL_OR, [ NodeAttrExpr( NODE_ATTR_OP_DEFINED, "attr1", None, None ), NodeAttrExpr( NODE_ATTR_OP_GT, "attr2", "5", NODE_ATTR_TYPE_NUMBER, ), ], ), ], ), ) assert_xml_equal( """ <context id="a"> <meta_attributes id="a-meta_attributes"> <rule id="a-meta_attributes-rule" boolean-op="and" score="INFINITY" > <rsc_expression id="a-meta_attributes-rule-rsc-ocf-pacemaker-Dummy" class="ocf" provider="pacemaker" type="Dummy" /> <op_expression id="a-meta_attributes-rule-op-start" name="start" /> <rule id="a-meta_attributes-rule-rule" boolean-op="or" score="0" > <expression id="a-meta_attributes-rule-rule-expr" operation="defined" attribute="attr1" /> <expression id="a-meta_attributes-rule-rule-expr-1" attribute="attr2" operation="gt" type="number" value="5" /> </rule> </rule> </meta_attributes> </context> """, etree_to_str(context_element), )
def test_get_and_push_cib_version_upgrade_not_needed(self): self.config.runner.cib.load(filename="cib-empty-2.6.xml") env = self.env_assist.get_env() env.get_cib(Version(2, 5, 0))
def create_into_bundle( env, resource_id, resource_agent_name, operation_list, 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, allow_not_accessible_resource=False, ): # pylint: disable=too-many-arguments, too-many-locals """ 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 operation_list 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 idle mechanism bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND bool allow_not_accessible_resource -- flag for FORCE_RESOURCE_IN_BUNDLE_NOT_ACCESSIBLE """ 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: id_provider = IdProvider(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, id_provider, resource_id, resource_agent, operation_list, meta_attributes, instance_attributes, allow_invalid_operation, allow_invalid_instance_attributes, use_default_operations, ) if ensure_disabled: resource.common.disable(primitive_element, id_provider) bundle_el = _find_bundle(resources_section, bundle_id) if not resource.bundle.is_pcmk_remote_accessible(bundle_el): env.report_processor.process( reports.get_problem_creator( report_codes.FORCE_RESOURCE_IN_BUNDLE_NOT_ACCESSIBLE, allow_not_accessible_resource)( reports.resource_in_bundle_not_accessible, bundle_id, resource_id)) resource.bundle.add_resource(bundle_el, primitive_element)
diff_cibs_xml, ensure_cib_version, ensure_wait_for_idle_support, get_cib, get_cib_xml, get_cluster_status_xml, push_cib_diff_xml, replace_cib_configuration, wait_for_idle, ) from pcs.lib.pacemaker.state import get_cluster_state_dom from pcs.lib.pacemaker.values import get_valid_timeout_seconds from pcs.lib.tools import write_tmpfile from pcs.lib.xml_tools import etree_to_str MIN_FEATURE_SET_VERSION_FOR_DIFF = Version(3, 0, 9) class LibraryEnvironment: # pylint: disable=too-many-instance-attributes, too-many-public-methods def __init__( self, logger, report_processor, user_login=None, user_groups=None, cib_data=None, corosync_conf_data=None, booth=None, known_hosts_getter=None,
from pcs.common.tools import Version from pcs.lib import reports from pcs.lib.cib import alert from pcs.lib.errors import LibraryError REQUIRED_CIB_VERSION = Version(2, 5, 0) def create_alert( lib_env, alert_id, path, instance_attribute_dict, meta_attribute_dict, description=None ): """ Create new alert. Raises LibraryError if path is not specified, or any other failure. lib_env -- LibraryEnvironment alert_id -- id of alert to be created, if None it will be generated path -- path to script for alert instance_attribute_dict -- dictionary of instance attributes meta_attribute_dict -- dictionary of meta attributes description -- alert description description """ if not path: raise LibraryError(reports.required_option_is_missing(["path"]))
def test_success(self): self.assertEqual( Version(3, 0, 9), lib.get_cib_crm_feature_set( etree.XML('<cib crm_feature_set="3.0.9" />')), )