def add_level(lib_env: LibraryEnvironment, 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) if lib_env.report_processor.has_errors: raise LibraryError() lib_env.push_cib()
def push_cib(self, custom_cib=None, wait=False): """ Push previously loaded instance of CIB or a custom CIB etree custom_cib -- push a custom CIB instead of a loaded instance (allows to push an externally provided CIB and replace the one in the cluster completely) mixed wait -- how many seconds to wait for pacemaker to process new CIB or False for not waiting at all """ if custom_cib is not None: if self.__loaded_cib_diff_source is not None: raise AssertionError( "CIB has been loaded, cannot push custom CIB" ) return self.__push_cib_full(custom_cib, wait) if self.__loaded_cib_diff_source is None: raise AssertionError("CIB has not been loaded") # Push by diff works with crm_feature_set > 3.0.8, see # https://bugzilla.redhat.com/show_bug.cgi?id=1488044 for details. We # only check the version if a CIB has been loaded, otherwise the push # fails anyway. By my testing it seems that only the source CIB's # version matters. if self.__loaded_cib_diff_source_feature_set < Version(3, 0, 9): self.report_processor.process( reports.cib_push_forced_full_due_to_crm_feature_set( Version(3, 0, 9), self.__loaded_cib_diff_source_feature_set ) ) return self.__push_cib_full(self.__loaded_cib_to_modify, wait=wait) return self.__push_cib_diff(wait=wait)
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 nice_to_have_cib_version = None if nvset_rule: # Parse the rule to see if we need to upgrade CIB schema. All errors # would be properly reported by a validator called bellow, so we can # safely ignore them here. try: rule_tree = parse_rule(nvset_rule) if has_rsc_or_op_expression(rule_tree): required_cib_version = Version(3, 4, 0) if has_node_attr_expr_with_type_integer(rule_tree): nice_to_have_cib_version = Version(3, 5, 0) except RuleParseError: pass cib = env.get_cib( minimal_version=required_cib_version, nice_to_have_version=nice_to_have_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, get_pacemaker_version_by_which_cib_was_validated(cib), 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 test_satisfied_nice_to_have_greater_than_required(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, 7, 0), Version(2, 8, 0)) self.env_assist.assert_reports( [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)])
def test_nice_to_have_equal_required(self): (self.config.runner.cib.load( name="load_cib_old", filename="cib-empty-3.1.xml").runner.cib.upgrade().runner.cib.load( filename="cib-empty-3.3.xml")) env = self.env_assist.get_env() env.get_cib(Version(3, 3, 0), Version(3, 3, 0)) self.env_assist.assert_reports( [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)])
def _get_required_cib_version_for_container(container_type, container_options): if container_type == "podman": return Version(3, 2, 0) if "promoted-max" in container_options: return Version(3, 0, 0) if container_type == "rkt": return Version(2, 10, 0) return Version(2, 8, 0)
def test_higher_version(self, mock_upgrade, mock_get_cib): actual_cib, was_upgraded = lib.ensure_cib_version( self.mock_runner, self.cib, Version(2, 3, 3)) self.assertEqual(self.cib, actual_cib) self.assertFalse(was_upgraded) mock_upgrade.assert_not_called() mock_get_cib.assert_not_called()
def assert_cib(tree, expected_xml, schema_version=None): if schema_version is None: schema_version = Version(3, 5, 0) xml = etree.fromstring('<root id="X"/>') rule.rule_to_cib(xml, IdProvider(xml), schema_version, tree) assert_xml_equal('<root id="X">' + expected_xml + "</root>", etree_to_str(xml))
def test_nvpairs(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"}, {}, ) assert_xml_equal( """ <context id="a"> <meta_attributes id="a-meta_attributes"> <nvpair id="a-meta_attributes-attr1" name="attr1" value="value1" /> <nvpair id="a-meta_attributes-attr2" name="attr2" value="value2" /> </meta_attributes> </context> """, etree_to_str(context_element), )
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" />') ) )
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 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 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 test_target_attribute_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_ATTRIBUTE, "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_ATTRIBUTE, "target value", "devices", "nodes", "force device", "force node" ) mock_get_cib.assert_called_once_with(Version(2, 4, 0)) self.assert_mocks( mock_status_xml, mock_status, mock_get_topology, mock_get_resources, mock_push_cib )
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 build_expression(self, dom_element, syntactic_tree): dom_expression = self.add_element( dom_element, "expression", dom_element.getAttribute("id") + "-expr" ) dom_expression.setAttribute("operation", syntactic_tree.symbol_id) dom_expression.setAttribute( "attribute", syntactic_tree.children[0].value ) if not isinstance(syntactic_tree, SymbolPrefix): child = syntactic_tree.children[1] if isinstance(child, SymbolType): # rhbz#1869399 # Pcs was always accepting 'integer', while CIB was only # supporting 'number' (and 'string' and 'version'). Pacemaker # was documenting it as 'integer' and was treating it as # integer (not float). With CIB schema 3.5.0, both 'integer' # and 'number' are accepted by CIB. For older schemas, we turn # 'integer' to 'number'. if ( self.cib_schema_version < Version(3, 5, 0) and child.symbol_id == "integer" ): dom_expression.setAttribute("type", "number") else: dom_expression.setAttribute("type", child.symbol_id) child = child.children[0] dom_expression.setAttribute("value", child.value)
def _export_node_attr(self, parent_el: _Element, expr: NodeAttrExpr) -> _Element: element = etree.SubElement( parent_el, "expression", { "id": create_subelement_id(parent_el, "expr", self.id_provider), "attribute": expr.attr_name, "operation": expr.operator.lower(), }, ) if expr.attr_value: element.attrib["value"] = expr.attr_value if expr.attr_type: # rhbz#1869399 # Pcs was always accepting 'integer', while CIB was only supporting # 'number' (and 'string' and 'version'). Pacemaker was documenting # it as 'integer' and was treating it as integer (not float). With # CIB schema 3.5.0, both 'integer' and 'number' are accepted by # CIB. For older schemas, we turn 'integer' to 'number'. if (self.cib_schema_version < Version(3, 5, 0) and expr.attr_type == NODE_ATTR_TYPE_INTEGER): element.attrib["type"] = "number" else: element.attrib["type"] = expr.attr_type.lower() return element
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 test_upgraded_higher_version(self, mock_upgrade, mock_get_cib): expected_cib = '<cib validate-with="pacemaker-2.3.6"/>' mock_get_cib.return_value = expected_cib actual_cib, was_upgraded = lib.ensure_cib_version( self.mock_runner, self.cib, Version(2, 3, 5)) assert_xml_equal(expected_cib, etree.tostring(actual_cib).decode()) self.assertTrue(was_upgraded) 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_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_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_type_number_old_schema(self): self.assert_cib( NodeAttrExpr(NODE_ATTR_OP_EQ, "#uname", "12345", NODE_ATTR_TYPE_NUMBER), """ <expression attribute="#uname" id="X-expr" operation="eq" type="number" value="12345" /> """, Version(3, 4, 0), )
def test_major(self): ver = Version(2) self.assert_asterisk((2, None, None), *ver) self.assertEqual(ver.major, 2) self.assertEqual(ver[0], 2) self.assertEqual(ver.minor, None) self.assertEqual(ver[1], None) self.assertEqual(ver.revision, None) self.assertEqual(ver[2], None) self.assertEqual(ver.as_full_tuple, (2, 0, 0)) self.assertEqual(str(ver), "2") self.assertEqual(str(ver.normalize()), "2.0.0")
def test_major_minor_revision(self): ver = Version(2, 3, 4) self.assert_asterisk((2, 3, 4), *ver) self.assertEqual(ver.major, 2) self.assertEqual(ver[0], 2) self.assertEqual(ver.minor, 3) self.assertEqual(ver[1], 3) self.assertEqual(ver.revision, 4) self.assertEqual(ver[2], 4) self.assertEqual(ver.as_full_tuple, (2, 3, 4)) self.assertEqual(str(ver), "2.3.4") self.assertEqual(str(ver.normalize()), "2.3.4")
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_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 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 _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