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_not_valid_rule(self): test_data = [ ("resource", (1, 9, 8, "Expected <resource name>")), ("op", (1, 3, 2, "Expected <operation name>")), ("resource ::rA and", (1, 15, 14, "Expected end of text")), ("resource ::rA and op ", (1, 15, 14, "Expected end of text")), ("resource ::rA and (", (1, 15, 14, "Expected end of text")), ] for rule_string, exception_data in test_data: with self.subTest(rule_string=rule_string): with self.assertRaises(rule.RuleParseError) as cm: rule.parse_rule(rule_string, allow_rsc_expr=True, allow_op_expr=True) e = cm.exception self.assertEqual(exception_data, (e.lineno, e.colno, e.pos, e.msg)) self.assertEqual(rule_string, e.rule_string)
def validate(self, force_options: bool = False) -> reports.ReportItemList: report_list: reports.ReportItemList = [] # Nvpair dict is intentionally not validated: it may contain any keys # and values. This can change in the future and then we add a # validation. Until then there is really nothing to validate there. # validate nvset options validators = [ validate.NamesIn( ("id", "score"), severity=reports.item.get_severity(reports.codes.FORCE_OPTIONS, force_options), ), # with id_provider it validates that the id is available as well validate.ValueId("id", option_name_for_report="id", id_provider=self._id_provider), validate.ValueScore("score"), ] report_list.extend( validate.ValidatorAll(validators).validate(self._nvset_options)) # parse and validate rule # TODO write and call parsed rule validation and cleanup and tests if self._nvset_rule: try: # Allow flags are set to True always, the parsed rule tree is # checked in the validator instead. That gives us better error # messages, such as "op expression cannot be used in this # context" instead of a universal "parse error". self._nvset_rule_parsed = parse_rule(self._nvset_rule, allow_rsc_expr=True, allow_op_expr=True) report_list.extend( RuleValidator( self._nvset_rule_parsed, allow_rsc_expr=self._allow_rsc_expr, allow_op_expr=self._allow_op_expr, ).get_reports()) except RuleParseError as e: report_list.append( reports.ReportItem.error( reports.messages.RuleExpressionParseError( e.rule_string, e.msg, e.rule_line, e.lineno, e.colno, e.pos, ))) return report_list
def validate(self, force_options: bool = False) -> reports.ReportItemList: report_list: reports.ReportItemList = [] # Nvpair dict is intentionally not validated: it may contain any keys # and values. This can change in the future and then we add a # validation. Until then there is really nothing to validate there. # validate nvset options validators = [ validate.NamesIn( ("id", "score"), severity=reports.item.get_severity(reports.codes.FORCE, force_options), ), # with id_provider it validates that the id is available as well validate.ValueId("id", option_name_for_report="id", id_provider=self._id_provider), validate.ValueScore("score"), ] report_list.extend( validate.ValidatorAll(validators).validate(self._nvset_options)) # parse and validate rule if self._nvset_rule: try: self._nvset_rule_parsed = parse_rule(self._nvset_rule) report_list.extend( RuleValidator( self._nvset_rule_parsed, allow_rsc_expr=self._allow_rsc_expr, allow_op_expr=self._allow_op_expr, allow_node_attr_expr=self._allow_node_attr_expr, ).get_reports()) except RuleParseError as e: report_list.append( reports.ReportItem.error( reports.messages.RuleExpressionParseError( e.rule_string, e.msg, e.rule_line, e.lineno, e.colno, e.pos, ))) return report_list
def test_success_parse_to_tree(self): test_data = [ ("", "BoolExpr AND"), ( "resource ::", dedent("""\ BoolExpr AND RscExpr"""), ), ( "resource ::dummy", dedent("""\ BoolExpr AND RscExpr type=dummy"""), ), ( "resource ocf::", dedent("""\ BoolExpr AND RscExpr standard=ocf"""), ), ( "resource :pacemaker:", dedent("""\ BoolExpr AND RscExpr provider=pacemaker"""), ), ( "resource systemd::Dummy", dedent("""\ BoolExpr AND RscExpr standard=systemd type=Dummy"""), ), ( "resource ocf:pacemaker:", dedent("""\ BoolExpr AND RscExpr standard=ocf provider=pacemaker"""), ), ( "resource :pacemaker:Dummy", dedent("""\ BoolExpr AND RscExpr provider=pacemaker type=Dummy"""), ), ( "resource ocf:pacemaker:Dummy", dedent("""\ BoolExpr AND RscExpr standard=ocf provider=pacemaker type=Dummy"""), ), ( "op monitor", dedent("""\ BoolExpr AND OpExpr name=monitor"""), ), ( "op monitor interval=10", dedent("""\ BoolExpr AND OpExpr name=monitor interval=10"""), ), ( "resource ::dummy and op monitor", dedent("""\ BoolExpr AND RscExpr type=dummy OpExpr name=monitor"""), ), ( "resource ::dummy or op monitor interval=15s", dedent("""\ BoolExpr OR RscExpr type=dummy OpExpr name=monitor interval=15s"""), ), ( "op monitor and resource ::dummy", dedent("""\ BoolExpr AND OpExpr name=monitor RscExpr type=dummy"""), ), ( "op monitor interval=5min or resource ::dummy", dedent("""\ BoolExpr OR OpExpr name=monitor interval=5min RscExpr type=dummy"""), ), ( "(resource ::dummy or resource ::delay) and op monitor", dedent("""\ BoolExpr AND BoolExpr OR RscExpr type=dummy RscExpr type=delay OpExpr name=monitor"""), ), ( "(op start and op stop) or resource ::dummy", dedent("""\ BoolExpr OR BoolExpr AND OpExpr name=start OpExpr name=stop RscExpr type=dummy"""), ), ( "op monitor or (resource ::dummy and resource ::delay)", dedent("""\ BoolExpr OR OpExpr name=monitor BoolExpr AND RscExpr type=dummy RscExpr type=delay"""), ), ( "resource ::dummy and (op start or op stop)", dedent("""\ BoolExpr AND RscExpr type=dummy BoolExpr OR OpExpr name=start OpExpr name=stop"""), ), ( "resource ::dummy and resource ::delay and op monitor", dedent("""\ BoolExpr AND RscExpr type=dummy RscExpr type=delay OpExpr name=monitor"""), ), ( "resource ::rA or resource ::rB or resource ::rC and op monitor", dedent("""\ BoolExpr AND BoolExpr OR RscExpr type=rA RscExpr type=rB RscExpr type=rC OpExpr name=monitor"""), ), ( "op start and op stop and op monitor or resource ::delay", dedent("""\ BoolExpr OR BoolExpr AND OpExpr name=start OpExpr name=stop OpExpr name=monitor RscExpr type=delay"""), ), ( "(resource ::rA or resource ::rB or resource ::rC) and (op oX or op oY or op oZ)", dedent("""\ BoolExpr AND BoolExpr OR RscExpr type=rA RscExpr type=rB RscExpr type=rC BoolExpr OR OpExpr name=oX OpExpr name=oY OpExpr name=oZ"""), ), ] for rule_string, rule_tree in test_data: with self.subTest(rule_string=rule_string): self.assertEqual( rule_tree, _parsed_to_str( rule.parse_rule(rule_string, allow_rsc_expr=True, allow_op_expr=True)), )
def test_not_valid_rule(self): test_data = [ # node attr misc ("#uname", (1, 7, 6, "Expected 'eq'")), ("string node1", (1, 8, 7, "Expected 'eq'")), # node attr unary ("defined", (1, 8, 7, "Expected <attribute name>")), ("not_defined", (1, 12, 11, "Expected <attribute name>")), ("defined string pingd", (1, 16, 15, "Expected end of text")), ("defined date-spec hours=1", (1, 19, 18, "Expected end of text")), ("defined duration hours=1", (1, 18, 17, "Expected end of text")), # node attr binary ("eq", (1, 3, 2, "Expected 'eq'")), ("#uname eq", (1, 10, 9, "Expected <attribute value>")), ("#uname node1", (1, 8, 7, "Expected 'eq'")), ("eq #uname", (1, 4, 3, "Expected 'eq'")), ("eq lt", (1, 6, 5, "Expected <attribute value>")), ("string #uname eq node1", (1, 8, 7, "Expected 'eq'")), ("date-spec hours=1 eq node1", (1, 19, 18, "Expected end of text")), ( "#uname eq date-spec hours=1", (1, 21, 20, "Expected end of text"), ), ("duration hours=1 eq node1", (1, 10, 9, "Expected 'eq'")), ("#uname eq duration hours=1", (1, 20, 19, "Expected end of text")), # node attr binary with optional parts ("string", (1, 7, 6, "Expected 'eq'")), ("#uname eq string", (1, 17, 16, "Expected <attribute value>")), ("string #uname eq node1", (1, 8, 7, "Expected 'eq'")), # resource, op ("resource", (1, 9, 8, "Expected 'eq'")), ("op", (1, 3, 2, "Expected 'eq'")), ("resource ::rA and", (1, 15, 14, "Expected end of text")), ("resource ::rA and op ", (1, 15, 14, "Expected end of text")), ("resource ::rA and (", (1, 15, 14, "Expected end of text")), # and, or ("and", (1, 4, 3, "Expected 'eq'")), ("or", (1, 3, 2, "Expected 'eq'")), ("#uname and node1", (1, 8, 7, "Expected 'eq'")), ("#uname or node1", (1, 8, 7, "Expected 'eq'")), ("#uname or eq", (1, 8, 7, "Expected 'eq'")), ("#uname eq node1 and node2", (1, 17, 16, "Expected end of text")), ("#uname eq node1 and", (1, 17, 16, "Expected end of text")), ( "#uname eq node1 and #uname eq", (1, 17, 16, "Expected end of text"), ), ("and #uname eq node1", (1, 5, 4, "Expected 'eq'")), ( "#uname ne node1 and duration hours=1", (1, 17, 16, "Expected end of text"), ), ( "duration monthdays=1 or #uname ne node1", (1, 10, 9, "Expected 'eq'"), ), # date ("date in_range", (1, 14, 13, "Expected 'to'")), ("date in_range 2014-06-26", (1, 15, 14, "Expected 'to'")), ("date in_range 2014-06-26 to", (1, 28, 27, "Expected <date>")), ("in_range 2014-06-26 to 2014-07-26", (1, 10, 9, "Expected 'eq'")), ( "date in_range #uname eq node1 to 2014-07-26", (1, 15, 14, "Expected 'to'"), ), ( "date in_range 2014-06-26 to #uname eq node1", (1, 36, 35, "Expected end of text"), ), ( "date in_range defined pingd to 2014-07-26", (1, 15, 14, "Expected 'to'"), ), ( "date in_range 2014-06-26 to defined pingd", (1, 37, 36, "Expected end of text"), ), ( "string date in_range 2014-06-26 to 2014-07-26", (1, 8, 7, "Expected 'eq'"), ), ( "date in_range string 2014-06-26 to 2014-07-26", (1, 15, 14, "Expected 'to'"), ), ( "date in_range 2014-06-26 to string 2014-07-26", (1, 36, 35, "Expected end of text"), ), ( "date in_range 2014-06-26 string to 2014-07-26", (1, 15, 14, "Expected 'to'"), ), ( "#uname in_range 2014-06-26 to 2014-07-26", (1, 8, 7, "Expected 'eq'"), ), # braces ("(#uname)", (1, 8, 7, "Expected 'eq'")), ("(", (1, 2, 1, "Expected 'date'")), ("()", (1, 2, 1, "Expected 'date'")), ("(#uname", (1, 8, 7, "Expected 'eq'")), ("(#uname eq", (1, 11, 10, "Expected <attribute value>")), # pyparsing 2 uses double quotes, pyparsing 3 uses single quotes ("(#uname eq node1", (1, 17, 16, {"Expected ')'", 'Expected ")"'})), ] for rule_string, exception_data in test_data: with self.subTest(rule_string=rule_string): with self.assertRaises(rule.RuleParseError) as cm: rule.parse_rule(rule_string) e = cm.exception if isinstance(exception_data[3], set): self.assertIn(e.msg, exception_data[3]) self.assertEqual(exception_data[0:3], (e.lineno, e.colno, e.pos)) else: self.assertEqual(exception_data, (e.lineno, e.colno, e.pos, e.msg)) self.assertEqual(rule_string, e.rule_string)
def _assert_success(self, rule_string, rule_tree): self.assertEqual( rule_tree, _parsed_to_str(rule.parse_rule(rule_string)), )