Login
[x]
Log in using an account from:
Fedora Account System
Red Hat Associate
Red Hat Customer
Or login using a Red Hat Bugzilla account
Forgot Password
Login:
Hide Forgot
Create an Account
Red Hat Bugzilla – Attachment 919059 Details for
Bug 1104656
support for complex rules and better handling of invalid rules
[?]
New
Simple Search
Advanced Search
My Links
Browse
Requests
Reports
Current State
Search
Tabular reports
Graphical reports
Duplicates
Other Reports
User Changes
Plotly Reports
Bug Status
Bug Severity
Non-Defaults
|
Product Dashboard
Help
Page Help!
Bug Writing Guidelines
What's new
Browser Support Policy
5.0.4.rh83 Release notes
FAQ
Guides index
User guide
Web Services
Contact
Legal
This site requires JavaScript to be enabled to function correctly, please enable it.
[patch]
proposed fix
0001-Improved-support-for-rules.patch (text/plain), 143.69 KB, created by
Tomas Jelinek
on 2014-07-18 12:00:54 UTC
(
hide
)
Description:
proposed fix
Filename:
MIME Type:
Creator:
Tomas Jelinek
Created:
2014-07-18 12:00:54 UTC
Size:
143.69 KB
patch
obsolete
>From 7fdf139193f77e4985ff1c6e5c36d41b188f698e Mon Sep 17 00:00:00 2001 >From: Tomas Jelinek <tojeline@redhat.com> >Date: Wed, 4 Jun 2014 17:21:40 +0200 >Subject: [PATCH] Improved support for rules > >* replaced rule parser >* added support for > * mixed 'and' 'or' rules > * parentheses > * duration in date rules > * value types >* added values validation >* capable of handling invalid rules providing explanatory error messages >--- > pcs/constraint.py | 114 +-- > pcs/pcs.8 | 36 +- > pcs/rule.py | 975 ++++++++++++++++++++++ > pcs/test/Makefile | 1 + > pcs/test/test_constraints.py | 154 +++- > pcs/test/test_resource.py | 12 +- > pcs/test/test_rule.py | 1878 ++++++++++++++++++++++++++++++++++++++++++ > pcs/test/test_utils.py | 83 +- > pcs/usage.py | 35 +- > pcs/utils.py | 187 +---- > 10 files changed, 3173 insertions(+), 302 deletions(-) > create mode 100644 pcs/rule.py > create mode 100644 pcs/test/test_rule.py > >diff --git a/pcs/constraint.py b/pcs/constraint.py >index 4f0a0d7..e2181ee 100644 >--- a/pcs/constraint.py >+++ b/pcs/constraint.py >@@ -2,6 +2,7 @@ import sys > import usage > import utils > import resource >+import rule as rule_utils > import xml.dom.minidom > import xml.etree.ElementTree as ET > from xml.dom.minidom import getDOMImplementation >@@ -564,7 +565,6 @@ def location_show(argv): > rschashon = {} > rschashoff = {} > ruleshash = defaultdict(list) >- datespechash = defaultdict(list) > all_loc_constraints = constraintsElement.getElementsByTagName('rsc_location') > > print "Location Constraints:" >@@ -576,50 +576,9 @@ def location_show(argv): > lc_role = rsc_loc.getAttribute("role") > lc_name = "Resource: " + lc_rsc > >- rules = rsc_loc.getElementsByTagName('rule') >- if len(rules) > 0: >- for rule in rules: >- exphash = [] >- rule_score = rule.getAttribute("score") >- rule_string = "" >- for n,v in rule.attributes.items(): >- if n != "id": >- rule_string += n + "=" + v + " " >- rule_id = rule.getAttribute("id") >- constraint_id = rule.parentNode.getAttribute("id") >- for exp in rule.childNodes: >- if exp.nodeType == xml.dom.minidom.Node.TEXT_NODE: >- continue >- exp_string = "" >- exp_string_start = "" >- if exp.tagName == "expression": >- if "value" in exp.attributes.keys(): >- exp_string_start = exp.getAttribute("attribute") + " " + exp.getAttribute("operation") + " " + exp.getAttribute("value") >- else: >- exp_string_start = exp.getAttribute("operation") + " " + exp.getAttribute("attribute") >- >- for n,v in exp.attributes.items(): >- if exp.tagName == "expression" and (n == "attribute" or n == "operation" or n == "value"): >- continue >- if n != "id": >- exp_string += n + "=" + v + " " >- if n == "operation" and v == "date_spec": >- for ds in exp.childNodes: >- if ds.nodeType == xml.dom.minidom.Node.TEXT_NODE: >- continue >- ds_string = "" >- for n2,v2 in ds.attributes.items(): >- if n2 != "id": >- ds_string += n2 + "=" + v2+ " " >- datespechash[exp.getAttribute("id")].append([ds.getAttribute("id"),ds_string]) >- >- exp_full_string = "" >- if exp_string_start != "": >- exp_full_string = exp_string_start + " " >- exp_full_string += exp_string >- >- exphash.append([exp.getAttribute("id"),exp_full_string]) >- ruleshash[lc_name].append([rule_id, rule_string, exphash, constraint_id]) >+ for child in rsc_loc.childNodes: >+ if child.nodeType == child.ELEMENT_NODE and child.tagName == "rule": >+ ruleshash[lc_name].append(child) > > # NEED TO FIX FOR GROUP LOCATION CONSTRAINTS (where there are children of > # rsc_location) >@@ -677,7 +636,7 @@ def location_show(argv): > if (options[3] != ""): > print "(role: "+options[3]+")", > print "Score: "+ options[2] >- show_location_rules(ruleshash,showDetail,datespechash) >+ show_location_rules(ruleshash,showDetail) > else: > rsclist.sort() > for rsc in rsclist: >@@ -709,35 +668,23 @@ def location_show(argv): > print > miniruleshash={} > miniruleshash["Resource: " + rsc] = ruleshash["Resource: " + rsc] >- show_location_rules(miniruleshash,showDetail,datespechash, True) >+ show_location_rules(miniruleshash,showDetail, True) > >-def show_location_rules(ruleshash,showDetail,datespechash,noheader=False): >+def show_location_rules(ruleshash,showDetail,noheader=False): > for rsc in ruleshash: > constrainthash= defaultdict(list) > if not noheader: > print " " + rsc >- for res_id,rule,exphash,constraint_id in ruleshash[rsc]: >- constrainthash[constraint_id].append([res_id,rule,exphash]) >+ for rule in ruleshash[rsc]: >+ constraint_id = rule.parentNode.getAttribute("id") >+ constrainthash[constraint_id].append(rule) > > for constraint_id in constrainthash.keys(): > print " Constraint: " + constraint_id >- for res_id, rule, exphash in constrainthash[constraint_id]: >- print " Rule: " + rule, >- if showDetail: >- print "(id:%s)" % (res_id), >- print "" >- for exp_id,exp in exphash: >- exp = exp.replace("operation=date_spec ","") >- print " Expression: " + exp, >- if showDetail: >- print "(id:%s)" % (exp_id), >- print "" >- for ds_id, ds in datespechash[exp_id]: >- print " Date Spec: " + ds, >- if showDetail: >- print "(id:%s)" % (ds_id), >- print "" >- >+ for rule in constrainthash[constraint_id]: >+ print rule_utils.ExportDetailed().get_string( >+ rule, showDetail, " " >+ ) > > def location_prefer(argv): > rsc = argv.pop(0) >@@ -837,14 +784,15 @@ def location_rule(argv): > > argv.pop(0) > >- cib = utils.get_cib_etree() >- constraints = cib.find(".//constraints") >- lc = ET.SubElement(constraints,"rsc_location") >+ cib = utils.get_cib_dom() >+ constraints = cib.getElementsByTagName("constraints")[0] >+ lc = cib.createElement("rsc_location") >+ constraints.appendChild(lc) > lc_id = utils.find_unique_id(cib, "location-" + res_name) >- lc.set("id", lc_id) >- lc.set("rsc", res_name) >+ lc.setAttribute("id", lc_id) >+ lc.setAttribute("rsc", res_name) > >- utils.rule_add(lc, argv) >+ rule_utils.dom_rule_add(lc, argv) > > utils.replace_cib_configuration(cib) > >@@ -1054,24 +1002,22 @@ def constraint_rule(argv): > > constraint_id = None > rule_id = None >- cib = utils.get_cib_etree() > > if command == "add": > constraint_id = argv.pop(0) >- constraint = None >- >- for a in cib.findall(".//configuration//*"): >- if a.get("id") == constraint_id and a.tag == "rsc_location": >- found = True >- constraint = a >- >- if not found: >+ cib = utils.get_cib_dom() >+ constraint = utils.dom_get_element_with_id( >+ cib.getElementsByTagName("constraints")[0], >+ "rsc_location", >+ constraint_id >+ ) >+ if not constraint: > utils.err("Unable to find constraint: " + constraint_id) >- >- utils.rule_add(constraint, argv) >+ rule_utils.dom_rule_add(constraint, argv) > utils.replace_cib_configuration(cib) > > elif command in ["remove","delete"]: >+ cib = utils.get_cib_etree() > temp_id = argv.pop(0) > constraints = cib.find('.//constraints') > loc_cons = cib.findall('.//rsc_location') >diff --git a/pcs/pcs.8 b/pcs/pcs.8 >index bc4be62..c37f4ac 100644 >--- a/pcs/pcs.8 >+++ b/pcs/pcs.8 >@@ -359,18 +359,26 @@ Create a location constraint on a resource to prefer the specified node and scor > location <resource id> avoids <node[=score]>... > Create a location constraint on a resource to avoid the specified node and score (default score: INFINITY) > .TP >-location <resource id> rule [role=master|slave] [score=<score>] <expression> >+location <resource id> rule [id=<rule id>] [role=master|slave] [score=<score>|score-attribute=<attribute>] <expression> > Creates a location rule on the specified resource where the expression looks like one of the following: > .br > defined|not_defined <attribute> > .br >- <attribute> lt|gt|lte|gte|eq|ne <value> >+ <attribute> lt|gt|lte|gte|eq|ne [string|integer|version] <value> > .br >- date [start=<start>] [end=<end>] operation=gt|lt|in\-range >+ date gt|lt <date> >+.br >+ date in_range <date> to <date> >+.br >+ date in_range <date> to duration <duration options>... > .br > date\-spec <date spec options>... > .br > <expression> and|or <expression> >+.br >+ ( <expression> ) >+.br >+where duration options and date spec options are: hours, monthdays, weekdays, yeardays, months, weeks, years, weekyears, moon. If score is ommited it defaults to INFINITY. If id is ommited one is generated from the resource id. > .TP > location show [resources|nodes [node id|resource id]...] [\fB\-\-full\fR] > List all the current location constraints, if 'resources' is specified location constraints are displayed per resource (default), if 'nodes' is specified location constraints are displayed per node. If specific nodes or resources are specified then we only show information about them. If \fB\-\-full\fR is specified show the internal constraint id's as well. >@@ -411,8 +419,26 @@ Remove constraint(s) or constraint rules with the specified id(s) > ref <resource>... > List constraints referencing specified resource > .TP >-rule add <constraint id> [<rule type>] [score=<score>] [id=<rule id>] <expression|date_expression|date_spec>... >-Add a rule to a constraint, if score is omitted it defaults to INFINITY, if id is omitted one is generated from the constraint id. The <rule type> should be 'expression' or 'date_expression' >+rule add <constraint id> [id=<rule id>] [role=master|slave] [score=<score>|score-attribute=<attribute>] <expression> >+Add a rule to a constraint where the expression looks like one of the following: >+.br >+ defined|not_defined <attribute> >+.br >+ <attribute> lt|gt|lte|gte|eq|ne [string|integer|version] <value> >+.br >+ date gt|lt <date> >+.br >+ date in_range <date> to <date> >+.br >+ date in_range <date> to duration <duration options>... >+.br >+ date\-spec <date spec options>... >+.br >+ <expression> and|or <expression> >+.br >+ ( <expression> ) >+.br >+where duration options and date spec options are: hours, monthdays, weekdays, yeardays, months, weeks, years, weekyears, moon If score is ommited it defaults to INFINITY. If id is ommited one is generated from the constraint id. > .TP > rule remove <rule id> > Remove a rule if a rule id is specified, if rule is last rule in its constraint, the constraint will be removed >diff --git a/pcs/rule.py b/pcs/rule.py >new file mode 100644 >index 0000000..9409203 >--- /dev/null >+++ b/pcs/rule.py >@@ -0,0 +1,975 @@ >+import re >+import xml.dom.minidom >+import utils >+ >+# main functions >+ >+def dom_rule_add(dom_element, argv): >+ options = { >+ "id": None, >+ "role": None, >+ "score": None, >+ "score-attribute": None >+ } >+ >+ # parse options >+ while argv: >+ found = False >+ option = argv.pop(0) >+ for name in options: >+ if option.startswith(name + "="): >+ options[name] = option.split("=", 1)[1] >+ found = True >+ break >+ if not found: >+ argv.insert(0, option) >+ break >+ >+ # validate options >+ if options["score"] and options["score-attribute"]: >+ utils.err("can not specify both score and score-attribute") >+ if options["score"] and not utils.is_score(options["score"]): >+ # preserving legacy behaviour >+ print ( >+ "Warning: invalid score '%s', setting score-attribute=pingd instead" >+ % options["score"] >+ ) >+ options["score-attribute"] = "pingd" >+ options["score"] = None >+ if options["role"] and options["role"] not in ["master", "slave"]: >+ utils.err( >+ "invalid role '%s', use 'master' or 'slave'" % options["role"] >+ ) >+ if options["id"]: >+ id_valid, id_error = utils.validate_xml_id(options["id"], 'rule id') >+ if not id_valid: >+ utils.err(id_error) >+ if utils.does_id_exist(dom_element.ownerDocument, options["id"]): >+ utils.err( >+ "id '%s' is already in use, please specify another one" >+ % options["id"] >+ ) >+ >+ # parse rule >+ if not argv: >+ utils.err("no rule expression was specified") >+ try: >+ dom_rule = CibBuilder().build( >+ dom_element, >+ RuleParser().parse(TokenPreprocessor().run(argv)), >+ options["id"] >+ ) >+ except SyntaxError as e: >+ utils.err( >+ "'%s' is not a valid rule expression: %s" >+ % (" ".join(argv), e) >+ ) >+ except UnexpectedEndOfInput as e: >+ utils.err( >+ "'%s' is not a valid rule expression: unexpected end of rule" >+ % " ".join(argv) >+ ) >+ except (ParserException, CibBuilderException) as e: >+ utils.err("'%s' is not a valid rule expression" % " ".join(argv)) >+ >+ # add options into rule xml >+ if not options["score"] and not options["score-attribute"]: >+ options["score"] = "INFINITY" >+ for name, value in options.iteritems(): >+ if name != "id" and value is not None: >+ dom_rule.setAttribute(name, value) >+ # score or score-attribute is required for the nested rules in order to have >+ # valid CIB, pacemaker does not use the score of the nested rules >+ for rule in dom_rule.getElementsByTagName("rule"): >+ rule.setAttribute("score", "0") >+ if dom_element.hasAttribute("score"): >+ dom_element.removeAttribute("score") >+ if dom_element.hasAttribute("node"): >+ dom_element.removeAttribute("node") >+ return dom_element >+ >+ >+class ExportDetailed(object): >+ >+ def get_string(self, rule, show_detail, indent=""): >+ self.show_detail = show_detail >+ return indent + ("\n" + indent).join(self.list_rule(rule)) >+ >+ def list_rule(self, rule): >+ rule_parts = ["Rule: %s" % " ".join(self.list_attributes(rule))] >+ for child in rule.childNodes: >+ if child.nodeType == xml.dom.minidom.Node.TEXT_NODE: >+ continue >+ if child.tagName == "expression": >+ self.indent_append(rule_parts, self.list_expression(child)) >+ elif child.tagName == "date_expression": >+ self.indent_append(rule_parts, self.list_date_expression(child)) >+ elif child.tagName == "rule": >+ self.indent_append(rule_parts, self.list_rule(child)) >+ return rule_parts >+ >+ def list_expression(self, expression): >+ if "value" in expression.attributes.keys(): >+ exp_parts = [ >+ expression.getAttribute("attribute"), >+ expression.getAttribute("operation") >+ ] >+ if expression.hasAttribute("type"): >+ exp_parts.append(expression.getAttribute("type")) >+ exp_parts.append(expression.getAttribute("value")) >+ else: >+ exp_parts = [ >+ expression.getAttribute("operation"), >+ expression.getAttribute("attribute") >+ ] >+ if self.show_detail: >+ exp_parts.append(" (id:%s)" % expression.getAttribute("id")) >+ return ["Expression: %s" % " ".join(exp_parts)] >+ >+ def list_date_expression(self, expression): >+ operation = expression.getAttribute("operation") >+ if operation == "date_spec": >+ date_spec_parts = self.list_attributes( >+ expression.getElementsByTagName("date_spec")[0] >+ ) >+ exp_parts = ["Expression:"] >+ if self.show_detail: >+ exp_parts.append(" (id:%s)" % expression.getAttribute("id")) >+ return self.indent_append( >+ [" ".join(exp_parts)], >+ ["Date Spec: %s" % " ".join(date_spec_parts)] >+ ) >+ elif operation == "in_range": >+ exp_parts = ["date", "in_range"] >+ if expression.hasAttribute("start"): >+ exp_parts.extend([expression.getAttribute("start"), "to"]) >+ if expression.hasAttribute("end"): >+ exp_parts.append(expression.getAttribute("end")) >+ durations = expression.getElementsByTagName("duration") >+ if durations: >+ exp_parts.append("duration") >+ duration_parts = self.list_attributes(durations[0]) >+ if self.show_detail: >+ exp_parts.append(" (id:%s)" % expression.getAttribute("id")) >+ result = ["Expression: %s" % " ".join(exp_parts)] >+ if durations: >+ self.indent_append( >+ result, >+ ["Duration: %s" % " ".join(duration_parts)] >+ ) >+ return result >+ else: >+ exp_parts = ["date", expression.getAttribute("operation")] >+ if expression.hasAttribute("start"): >+ exp_parts.append(expression.getAttribute("start")) >+ if expression.hasAttribute("end"): >+ exp_parts.append(expression.getAttribute("end")) >+ if self.show_detail: >+ exp_parts.append(" (id:%s)" % expression.getAttribute("id")) >+ return ["Expression: " + " ".join(exp_parts)] >+ >+ def list_attributes(self, element): >+ attributes = [ >+ "%s=%s" % (name, value) >+ for name, value in element.attributes.items() if name != "id" >+ ] >+ if self.show_detail: >+ attributes.append(" (id:%s)" % (element.getAttribute("id"))) >+ return attributes >+ >+ def indent_append(self, target, source, indent=" "): >+ for part in source: >+ target.append(indent + part) >+ return target >+ >+ >+# generic parser >+ >+class SymbolBase(object): >+ >+ END = "{end}" >+ LITERAL = "{literal}" >+ >+ symbol_id = None >+ left_binding_power = 0 >+ >+ def null_denotation(self): >+ raise SyntaxError("unexpected '%s'" % self.label()) >+ >+ def left_denotation(self, left): >+ raise SyntaxError( >+ "unexpected '%s' after '%s'" % (self.label(), left.label()) >+ ) >+ >+ def is_end(self): >+ return self.symbol_id == SymbolBase.END >+ >+ def is_literal(self): >+ return self.symbol_id == SymbolBase.LITERAL >+ >+ def label(self): >+ return self.symbol_id >+ >+ def __str__(self): >+ return "(%s)" % self.symbol_id >+ >+ >+class SymbolLiteral(SymbolBase): >+ >+ def __init__(self, value): >+ self.value = value >+ >+ def null_denotation(self): >+ return self >+ >+ def label(self): >+ return "end" if self.is_end() else str(self.value) >+ >+ def __str__(self): >+ return "(end)" if self.is_end() else "(literal %s)" % self.value >+ >+ >+class SymbolParenthesisOpen(SymbolBase): >+ >+ expression_func = None >+ advance_func = None >+ close_symbol_id = None >+ >+ def null_denotation(self): >+ expression = self.expression_func() >+ self.advance_func(self.close_symbol_id) >+ return expression >+ >+ >+class SymbolOperator(SymbolBase): >+ >+ expression_func = None >+ allowed_child_ids = None >+ >+ def __init__(self): >+ self.children = [] >+ >+ def is_allowed_child(self, child_symbol, child_position): >+ return ( >+ not self.allowed_child_ids >+ or >+ not self.allowed_child_ids[child_position] >+ or >+ child_symbol.symbol_id in self.allowed_child_ids[child_position] >+ ) >+ >+ def __str__(self): >+ string = " ".join([ >+ str(part) >+ for part in [self.symbol_id] + self.children >+ ]) >+ return "(" + string + ")" >+ >+ >+class SymbolPrefix(SymbolOperator): >+ >+ def null_denotation(self): >+ self.children.append(self.expression_func(self.left_binding_power)) >+ if not self.is_allowed_child(self.children[0], 0): >+ raise SyntaxError( >+ "unexpected '%s' after '%s'" >+ % (self.children[0].label(), self.symbol_id) >+ ) >+ return self >+ >+ >+class SymbolType(SymbolPrefix): >+ >+ value_re = None >+ >+ def null_denotation(self): >+ super(SymbolType, self).null_denotation() >+ if self.value_re and not self.value_re.match(self.children[0].value): >+ raise SyntaxError( >+ "invalid %s value '%s'" >+ % (self.symbol_id, self.children[0].value) >+ ) >+ return self >+ >+ >+class SymbolInfix(SymbolOperator): >+ >+ def left_denotation(self, left): >+ self.children.append(left) >+ if not self.is_allowed_child(self.children[0], 0): >+ raise SyntaxError( >+ "unexpected '%s' before '%s'" % (left.label(), self.symbol_id) >+ ) >+ self.children.append(self.expression_func(self.left_binding_power)) >+ if not self.is_allowed_child(self.children[1], 1): >+ raise SyntaxError( >+ "unexpected '%s' after '%s'" >+ % (self.children[1].label(), self.symbol_id) >+ ) >+ return self >+ >+ >+class SymbolTernary(SymbolOperator): >+ >+ advance_func = None >+ symbol_second_id = None >+ >+ def left_denotation(self, left): >+ self.children.append(left) >+ if not self.is_allowed_child(self.children[0], 0): >+ raise SyntaxError( >+ "unexpected '%s' before '%s'" % (left.label(), self.symbol_id) >+ ) >+ self.children.append(self.expression_func(self.left_binding_power)) >+ if not self.is_allowed_child(self.children[1], 1): >+ raise SyntaxError( >+ "unexpected '%s' after '%s'" >+ % (self.children[1].label(), self.symbol_id) >+ ) >+ self.advance_func(self.symbol_second_id) >+ self.children.append(self.expression_func(self.left_binding_power)) >+ if not self.is_allowed_child(self.children[2], 2): >+ raise SyntaxError( >+ "unexpected '%s' after '%s ... %s'" >+ % ( >+ self.children[2].label(), >+ self.symbol_id, >+ self.symbol_second_id >+ ) >+ ) >+ return self >+ >+ >+class SymbolTable(object): >+ >+ def __init__(self): >+ self.table = dict() >+ >+ def has_symbol(self, symbol_id): >+ return symbol_id in self.table >+ >+ def get_symbol(self, symbol_id): >+ return self.table[symbol_id] >+ >+ def new_symbol( >+ self, symbol_id, superclass, binding_power=0, expression_func=None, >+ advance_func=None >+ ): >+ if not self.has_symbol(symbol_id): >+ class SymbolClass(superclass): >+ pass >+ SymbolClass.__name__ = "symbol_" + symbol_id >+ SymbolClass.symbol_id = symbol_id >+ SymbolClass.left_binding_power = binding_power >+ if expression_func: >+ SymbolClass.expression_func = expression_func >+ if advance_func: >+ SymbolClass.advance_func = advance_func >+ self.table[symbol_id] = SymbolClass >+ return SymbolClass >+ return self.get_symbol(symbol_id) >+ >+ >+class Parser(object): >+ >+ def __init__(self): >+ self.current_symbol = None >+ self.current_symbol_index = -1 >+ self.program = list() >+ self.symbol_table = SymbolTable() >+ self.new_symbol_literal(SymbolBase.LITERAL) >+ self.new_symbol_literal(SymbolBase.END) >+ >+ def new_symbol_literal(self, symbol_id): >+ return self.symbol_table.new_symbol(symbol_id, SymbolLiteral) >+ >+ def new_symbol_prefix(self, symbol_id, binding_power): >+ return self.symbol_table.new_symbol( >+ symbol_id, SymbolPrefix, binding_power, self.expression >+ ) >+ >+ def new_symbol_type(self, symbol_id, binding_power): >+ return self.symbol_table.new_symbol( >+ symbol_id, SymbolType, binding_power, self.expression >+ ) >+ >+ def new_symbol_infix(self, symbol_id, binding_power): >+ return self.symbol_table.new_symbol( >+ symbol_id, SymbolInfix, binding_power, self.expression >+ ) >+ >+ def new_symbol_ternary(self, symbol_id, second_id, binding_power): >+ self.symbol_table.new_symbol(second_id, SymbolBase) >+ symbol_class = self.symbol_table.new_symbol( >+ symbol_id, SymbolTernary, binding_power, self.expression, >+ self.advance >+ ) >+ symbol_class.symbol_second_id = second_id >+ return symbol_class >+ >+ def new_symbol_parenthesis(self, symbol_id, closing_id): >+ self.symbol_table.new_symbol(closing_id, SymbolBase) >+ symbol_class = self.symbol_table.new_symbol( >+ symbol_id, SymbolParenthesisOpen, 0, self.expression, self.advance >+ ) >+ symbol_class.close_symbol_id = closing_id >+ return symbol_class >+ >+ def symbolize(self, program): >+ symbolized_program = list() >+ literal_class = self.symbol_table.get_symbol(SymbolBase.LITERAL) >+ for token in program: >+ if ( >+ self.symbol_table.has_symbol(token) >+ and >+ ( >+ len(symbolized_program) < 1 >+ or >+ not isinstance(symbolized_program[-1], SymbolType) >+ ) >+ ): >+ symbolized = self.symbol_table.get_symbol(token)() >+ else: >+ symbolized = literal_class(token) >+ symbolized_program.append(symbolized) >+ symbolized_program.append( >+ self.symbol_table.get_symbol(SymbolBase.END)(None) >+ ) >+ return symbolized_program >+ >+ def advance(self, expected_symbol_id=None): >+ if ( >+ expected_symbol_id >+ and >+ self.current_symbol.symbol_id != expected_symbol_id >+ ): >+ if self.current_symbol.is_end(): >+ raise SyntaxError("missing '%s'" % expected_symbol_id) >+ raise SyntaxError( >+ "expecting '%s', got '%s'" >+ % (expected_symbol_id, self.current_symbol.label()) >+ ) >+ self.current_symbol_index += 1 >+ if self.current_symbol_index >= len(self.program): >+ raise UnexpectedEndOfInput() >+ self.current_symbol = self.program[self.current_symbol_index] >+ return self >+ >+ def expression(self, right_binding_power=0): >+ symbol = self.current_symbol >+ self.advance() >+ left = symbol.null_denotation() >+ while right_binding_power < self.current_symbol.left_binding_power: >+ symbol = self.current_symbol >+ self.advance() >+ left = symbol.left_denotation(left) >+ return left >+ >+ def parse(self, program): >+ self.current_symbol = None >+ self.current_symbol_index = -1 >+ self.program = self.symbolize(program) >+ self.advance() >+ result = self.expression() >+ symbol = self.current_symbol >+ if not symbol.is_end(): >+ raise SyntaxError("unexpected '%s'" % symbol.label()) >+ return result >+ >+ >+class ParserException(Exception): >+ pass >+ >+ >+class UnexpectedEndOfInput(ParserException): >+ pass >+ >+ >+class SyntaxError(ParserException): >+ pass >+ >+ >+# rule parser specific code >+ >+class DateCommonValue(object): >+ >+ allowed_items = [ >+ "hours", "monthdays", "weekdays", "yeardays", "months", "weeks", >+ "years", "weekyears", "moon", >+ ] >+ KEYWORD = None >+ >+ def __init__(self, parts_string, keyword=None): >+ self.parts = dict() >+ for part in parts_string.split(): >+ if not self.accepts_part(part): >+ raise SyntaxError( >+ "unexpected '%s' in %s" % (part, keyword) >+ ) >+ if "=" not in part: >+ raise SyntaxError( >+ "missing =value after '%s' in %s" % (part, keyword) >+ ) >+ name, value = part.split("=", 1) >+ if value == "": >+ raise SyntaxError( >+ "missing value after '%s' in %s" % (part, keyword) >+ ) >+ self.parts[name] = value >+ if not self.parts: >+ raise SyntaxError( >+ "missing one of '%s=' in %s" >+ % ("=', '".join(DateCommonValue.allowed_items), keyword) >+ ) >+ self.validate() >+ >+ def validate(self): >+ return self >+ >+ @classmethod >+ def accepts_part(cls, part): >+ for name in cls.allowed_items: >+ if part == name or part.startswith(name + "="): >+ return True >+ return False >+ >+ def __str__(self): >+ return " ".join( >+ ["%s=%s" % (name, value) for name, value in self.parts.iteritems()] >+ ) >+ >+ >+class DateSpecValue(DateCommonValue): >+ >+ KEYWORD = "date-spec" >+ part_re = re.compile("^(?P<since>\d+)(-(?P<until>\d+))?$") >+ part_limits = { >+ "hours" : (0, 23), >+ "monthdays" : (0, 31), >+ "weekdays" : (1, 7), >+ "yeardays" : (1, 366), >+ "months" : (1, 12), >+ "weeks" : (1, 53), >+ "weekyears" : (1, 53), >+ "moon" : (0, 7), >+ } >+ >+ def __init__(self, parts_string): >+ super(DateSpecValue, self).__init__(parts_string, self.KEYWORD) >+ >+ def validate(self): >+ for name, value in self.parts.iteritems(): >+ if not self.valid_part(name, value): >+ raise SyntaxError( >+ "invalid %s '%s' in '%s'" >+ % (name, value, DateSpecValue.KEYWORD) >+ ) >+ return self >+ >+ def valid_part(self, name, value): >+ match = DateSpecValue.part_re.match(value) >+ if not match: >+ return False >+ match_dict = match.groupdict() >+ if not self.valid_part_limits(name, match_dict["since"]): >+ return False >+ if match_dict["until"]: >+ if not self.valid_part_limits(name, match_dict["since"]): >+ return False >+ if int(match_dict["since"]) >= int(match_dict["until"]): >+ return False >+ return True >+ >+ def valid_part_limits(self, name, value): >+ if name not in DateSpecValue.part_limits: >+ return True >+ limits = DateSpecValue.part_limits[name] >+ return limits[0] <= int(value) <= limits[1] >+ >+ >+class DateDurationValue(DateCommonValue): >+ >+ KEYWORD = "duration" >+ >+ def __init__(self, parts_string): >+ super(DateDurationValue, self).__init__(parts_string, self.KEYWORD) >+ >+ def validate(self): >+ for name, value in self.parts.iteritems(): >+ if not value.isdigit(): >+ raise SyntaxError( >+ "invalid %s '%s' in '%s'" >+ % (name, value, DateDurationValue.KEYWORD) >+ ) >+ return self >+ >+ >+class SymbolTypeDateCommon(SymbolType): >+ >+ date_value_class = None >+ >+ def null_denotation(self): >+ symbol = self.expression_func(self.left_binding_power) >+ symbol.value = self.date_value_class(symbol.value) >+ self.children.append(symbol) >+ return self >+ >+ >+class SymbolTernaryInRange(SymbolTernary): >+ >+ allowed_child_ids = [ >+ [SymbolBase.LITERAL], >+ [SymbolBase.LITERAL], >+ [SymbolBase.LITERAL, DateDurationValue.KEYWORD] >+ ] >+ symbol_second_id = "to" >+ >+ def is_allowed_child(self, child_symbol, child_position): >+ return ( >+ super(SymbolTernaryInRange, self).is_allowed_child( >+ child_symbol, child_position >+ ) >+ and >+ (child_position != 0 or child_symbol.value == "date") >+ ) >+ >+ def left_denotation(self, left): >+ super(SymbolTernaryInRange, self).left_denotation(left) >+ for child in self.children[1:]: >+ if child.is_literal() and not utils.is_iso8601_date(child.value): >+ raise SyntaxError( >+ "invalid date '%s' in 'in_range ... to'" % child.value >+ ) >+ return self >+ >+ >+class RuleParser(Parser): >+ >+ comparison_list = ["eq", "ne", "lt", "gt", "lte", "gte", "in_range"] >+ date_comparison_list = ["gt", "lt", "in_range"] >+ prefix_list = ["defined", "not_defined"] >+ boolean_list = ["and", "or"] >+ simple_type_list = ["string", "integer", "version"] >+ parenthesis_open = "(" >+ parenthesis_close = ")" >+ >+ def __init__(self): >+ super(RuleParser, self).__init__() >+ >+ for operator in RuleParser.comparison_list: >+ if operator == "in_range": >+ continue >+ symbol_class = self.new_symbol_infix(operator, 50) >+ symbol_class.allowed_child_ids = [ >+ [SymbolBase.LITERAL], >+ [SymbolBase.LITERAL] + RuleParser.simple_type_list >+ ] >+ >+ self.symbol_table.new_symbol( >+ "in_range", SymbolTernaryInRange, 50, self.expression, self.advance >+ ) >+ self.symbol_table.new_symbol("to", SymbolBase) >+ >+ for operator in RuleParser.prefix_list: >+ symbol_class = self.new_symbol_prefix(operator, 60) >+ symbol_class.allowed_child_ids = [[SymbolBase.LITERAL]] >+ >+ for operator in RuleParser.simple_type_list: >+ symbol_class = self.new_symbol_type(operator, 70) >+ self.symbol_table.get_symbol("integer").value_re = re.compile("^-?\d+$") >+ self.symbol_table.get_symbol("version").value_re = re.compile( >+ "^\d+(\.\d+)*$" >+ ) >+ symbol_class = self.new_symbol_type_date(DateSpecValue, 70) >+ symbol_class = self.new_symbol_type_date(DateDurationValue, 70) >+ >+ for operator in RuleParser.boolean_list: >+ symbol_class = self.new_symbol_infix(operator, 40) >+ symbol_class.allowed_child_ids = [ >+ RuleParser.comparison_list >+ + RuleParser.prefix_list >+ + [DateSpecValue.KEYWORD] >+ + RuleParser.boolean_list >+ ] * 2 >+ >+ self.new_symbol_parenthesis( >+ RuleParser.parenthesis_open, RuleParser.parenthesis_close >+ ) >+ >+ def parse(self, program): >+ syntactic_tree = super(RuleParser, self).parse(program) >+ if ( >+ syntactic_tree.is_literal() >+ or >+ ( >+ isinstance(syntactic_tree, SymbolType) >+ and not >+ ( >+ isinstance(syntactic_tree, SymbolTypeDateCommon) >+ and >+ syntactic_tree.date_value_class == DateSpecValue >+ ) >+ ) >+ ): >+ raise SyntaxError( >+ "missing one of '%s'" >+ % "', '".join( >+ RuleParser.comparison_list + RuleParser.prefix_list >+ + [DateSpecValue.KEYWORD] >+ ) >+ ) >+ return syntactic_tree >+ >+ def new_symbol_type_date(self, date_value_class, binding_power): >+ symbol_class = self.symbol_table.new_symbol( >+ date_value_class.KEYWORD, SymbolTypeDateCommon, binding_power, >+ self.expression >+ ) >+ symbol_class.date_value_class = date_value_class >+ return symbol_class >+ >+ >+# cib builder >+ >+class CibBuilder(object): >+ >+ def build(self, dom_element, syntactic_tree, rule_id=None): >+ dom_rule = self.add_element( >+ dom_element, >+ "rule", >+ rule_id if rule_id else dom_element.getAttribute("id") + "-rule" >+ ) >+ self.build_rule(dom_rule, syntactic_tree) >+ return dom_rule >+ >+ def build_rule(self, dom_rule, syntactic_tree): >+ if isinstance(syntactic_tree, SymbolOperator): >+ if syntactic_tree.symbol_id in RuleParser.boolean_list: >+ self.build_boolean(dom_rule, syntactic_tree) >+ elif ( >+ syntactic_tree.symbol_id in RuleParser.date_comparison_list >+ and >+ syntactic_tree.children[0].value == 'date' >+ and >+ syntactic_tree.children[1].is_literal() >+ ): >+ self.build_date_expression(dom_rule, syntactic_tree) >+ elif ( >+ isinstance(syntactic_tree, SymbolTypeDateCommon) >+ and >+ syntactic_tree.date_value_class == DateSpecValue >+ ): >+ self.build_datespec(dom_rule, syntactic_tree) >+ else: >+ self.build_expression(dom_rule, syntactic_tree) >+ else: >+ raise InvalidSyntacticTree(syntactic_tree) >+ >+ def build_datespec(self, dom_element, syntactic_tree): >+ dom_expression = self.add_element( >+ dom_element, >+ "date_expression", >+ dom_element.getAttribute("id") + "-expr" >+ ) >+ dom_expression.setAttribute("operation", "date_spec") >+ dom_datespec = self.add_element( >+ dom_expression, >+ "date_spec", >+ dom_expression.getAttribute("id") + "-datespec" >+ ) >+ for key, value in syntactic_tree.children[0].value.parts.iteritems(): >+ dom_datespec.setAttribute(key, value) >+ >+ 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): >+ dom_expression.setAttribute( >+ "type", >+ "number" if child.symbol_id == "integer" else child.symbol_id >+ ) >+ child = child.children[0] >+ dom_expression.setAttribute("value", child.value) >+ >+ def build_date_expression(self, dom_element, syntactic_tree): >+ dom_expression = self.add_element( >+ dom_element, >+ "date_expression", >+ dom_element.getAttribute("id") + "-expr" >+ ) >+ dom_expression.setAttribute("operation", syntactic_tree.symbol_id) >+ if syntactic_tree.symbol_id == 'gt': >+ dom_expression.setAttribute( >+ "start", syntactic_tree.children[1].value >+ ) >+ elif syntactic_tree.symbol_id == 'lt': >+ dom_expression.setAttribute( >+ "end", syntactic_tree.children[1].value >+ ) >+ elif syntactic_tree.symbol_id == 'in_range': >+ dom_expression.setAttribute( >+ "start", syntactic_tree.children[1].value >+ ) >+ if ( >+ isinstance(syntactic_tree.children[2], SymbolTypeDateCommon) >+ and >+ syntactic_tree.children[2].date_value_class == DateDurationValue >+ ): >+ dom_duration = self.add_element( >+ dom_expression, >+ "duration", >+ dom_expression.getAttribute("id") + "-duration" >+ ) >+ duration = syntactic_tree.children[2].children[0].value >+ for key, value in duration.parts.iteritems(): >+ dom_duration.setAttribute(key, value) >+ else: >+ dom_expression.setAttribute( >+ "end", syntactic_tree.children[2].value >+ ) >+ >+ def build_boolean(self, dom_element, syntactic_tree): >+ dom_element.setAttribute("boolean-op", syntactic_tree.symbol_id) >+ for subtree in syntactic_tree.children: >+ if ( >+ subtree.symbol_id in RuleParser.boolean_list >+ and >+ subtree.symbol_id != syntactic_tree.symbol_id >+ ): >+ self.build( >+ dom_element, >+ subtree, >+ dom_element.getAttribute("id") + "-rule" >+ ) >+ else: >+ self.build_rule(dom_element, subtree) >+ >+ def add_element(self, parent, tag_name, element_id): >+ dom = parent.ownerDocument >+ child = parent.appendChild(dom.createElement(tag_name)) >+ child.setAttribute("id", utils.find_unique_id(dom, element_id)) >+ return child >+ >+ >+class CibBuilderException(Exception): >+ pass >+ >+ >+class InvalidSyntacticTree(CibBuilderException): >+ pass >+ >+ >+# token preprocessing >+ >+class TokenPreprocessor(object): >+ >+ def run(self, token_list): >+ return self.convert_legacy_date( >+ self.join_date_common( >+ self.separate_parenthesis(token_list) >+ ) >+ ) >+ >+ def separate_parenthesis(self, input_list): >+ output_list = [] >+ for token in input_list: >+ if not ( >+ RuleParser.parenthesis_open in token >+ or >+ RuleParser.parenthesis_close in token >+ ): >+ output_list.append(token) >+ else: >+ part = [] >+ for char in token: >+ if char in [ >+ RuleParser.parenthesis_open, >+ RuleParser.parenthesis_close >+ ]: >+ if part: >+ output_list.append("".join(part)) >+ part = [] >+ output_list.append(char) >+ else: >+ part.append(char) >+ if part: >+ output_list.append("".join(part)) >+ return output_list >+ >+ def join_date_common(self, input_list): >+ output_list = [] >+ token_parts = [] >+ in_datecommon = False >+ for token in input_list: >+ if in_datecommon: >+ if DateCommonValue.accepts_part(token): >+ token_parts.append(token) >+ elif ( >+ token == "operation=date_spec" >+ and >+ token_parts[0] == DateSpecValue.KEYWORD >+ ): >+ pass # gracefully ignoring for backwards compatibility >+ else: >+ in_datecommon = False >+ output_list.append(token_parts[0]) >+ if len(token_parts) > 1: >+ output_list.append(" ".join(token_parts[1:])) >+ output_list.append(token) >+ token_parts = [] >+ elif token in [DateSpecValue.KEYWORD, DateDurationValue.KEYWORD]: >+ in_datecommon = True >+ token_parts = [token] >+ else: >+ output_list.append(token) >+ if token_parts: >+ output_list.append(token_parts[0]) >+ if len(token_parts) > 1: >+ output_list.append(" ".join(token_parts[1:])) >+ return output_list >+ >+ def convert_legacy_date(self, input_list): >+ output_list = [] >+ in_date = False >+ date_start = "" >+ date_end = "" >+ token_parts = [] >+ for token in input_list: >+ if in_date: >+ token_parts.append(token) >+ if token.startswith("start="): >+ date_start = token[len("start="):] >+ elif token.startswith("end="): >+ date_end = token[len("end="):] >+ else: >+ if token == "gt" and date_start and not date_end: >+ output_list.extend(["date", "gt", date_start]) >+ elif token == "lt" and not date_start and date_end: >+ output_list.extend(["date", "lt", date_end]) >+ elif token == "in_range" and date_start and date_end: >+ output_list.extend( >+ ["date", "in_range", date_start, "to", date_end] >+ ) >+ else: >+ output_list.extend(token_parts) >+ token_parts = [] >+ date_start = date_end = "" >+ in_date = False >+ elif token == 'date': >+ token_parts = ['date'] >+ in_date = True >+ else: >+ output_list.append(token) >+ if token_parts: >+ output_list.extend(token_parts) >+ return output_list >+ >diff --git a/pcs/test/Makefile b/pcs/test/Makefile >index 76d54bb..bd2d0c1 100644 >--- a/pcs/test/Makefile >+++ b/pcs/test/Makefile >@@ -11,6 +11,7 @@ test: > python test_utils.py ${pyunit_flags} > python test_cluster.py ${pyunit_flags} > python test_resource.py ${pyunit_flags} >+ python test_rule.py ${pyunit_flags} > python test_constraints.py ${pyunit_flags} > python test_stonith.py ${pyunit_flags} > python test_properties.py ${pyunit_flags} >diff --git a/pcs/test/test_constraints.py b/pcs/test/test_constraints.py >index ed0bd29..50a1856 100644 >--- a/pcs/test/test_constraints.py >+++ b/pcs/test/test_constraints.py >@@ -64,11 +64,11 @@ class ConstraintTest(unittest.TestCase): > > output, returnVal = pcs(temp_cib, "constraint location C1-group rule score=pingd defined pingd") > assert returnVal == 0 >- assert output == "", [output] >+ assert output == "Warning: invalid score 'pingd', setting score-attribute=pingd instead\n", [output] > > output, returnVal = pcs(temp_cib, "constraint location D3 rule score=pingd defined pingd") > assert returnVal == 0 >- assert output == "", [output] >+ assert output == "Warning: invalid score 'pingd', setting score-attribute=pingd instead\n", [output] > > output, returnVal = pcs(temp_cib, "constraint location D4 rule score=INFINITY date start=2005-001 gt") > assert returnVal == 0 >@@ -92,7 +92,48 @@ class ConstraintTest(unittest.TestCase): > > output, returnVal = pcs(temp_cib, "constraint --full") > assert returnVal == 0 >- ac (output,'Location Constraints:\n Resource: C1-group\n Constraint: location-C1-group\n Rule: score-attribute=pingd (id:location-C1-group-rule) \n Expression: defined pingd (id:location-C1-group-rule-expr-1) \n Resource: D1\n Constraint: location-D1\n Rule: score=222 (id:location-D1-rule) \n Expression: #uname eq c00n03 (id:location-D1-rule-expr-1) \n Resource: D2\n Constraint: location-D2\n Rule: score=-INFINITY (id:location-D2-rule) \n Expression: #uname eq c00n04 (id:location-D2-rule-expr-1) \n Resource: D3\n Constraint: location-D3-2\n Rule: score=-INFINITY boolean-op=and (id:location-D3-2-rule) \n Expression: not_defined pingd (id:location-D3-2-rule-expr-1) \n Expression: pingd lte 0 (id:location-D3-2-rule-expr-2) \n Constraint: location-D3-1\n Rule: score=-INFINITY boolean-op=or (id:location-D3-1-rule) \n Expression: not_defined pingd (id:location-D3-1-rule-expr-1) \n Expression: pingd lte 0 (id:location-D3-1-rule-expr-2) \n Constraint: location-D3\n Rule: score-attribute=pingd (id:location-D3-rule) \n Expression: defined pingd (id:location-D3-rule-expr-1) \n Resource: D4\n Constraint: location-D4\n Rule: score=INFINITY (id:location-D4-rule) \n Expression: start=2005-001 operation=gt (id:location-D4-rule-expr-1) \n Resource: D5\n Constraint: location-D5\n Rule: score=INFINITY (id:location-D5-rule) \n Expression: start=2005-001 operation=in_range end=2006-001 (id:location-D5-rule-expr-1) \n Resource: D6\n Constraint: location-D6\n Rule: score=INFINITY (id:location-D6-rule) \n Expression: (id:location-D6-rule-expr-1) \n Date Spec: years=2005 (id:location-D6-rule-expr-1-datespec) \nOrdering Constraints:\nColocation Constraints:\n') >+ ac(output, """\ >+Location Constraints: >+ Resource: C1-group >+ Constraint: location-C1-group >+ Rule: score-attribute=pingd (id:location-C1-group-rule) >+ Expression: defined pingd (id:location-C1-group-rule-expr) >+ Resource: D1 >+ Constraint: location-D1 >+ Rule: score=222 (id:location-D1-rule) >+ Expression: #uname eq c00n03 (id:location-D1-rule-expr) >+ Resource: D2 >+ Constraint: location-D2 >+ Rule: score=-INFINITY (id:location-D2-rule) >+ Expression: #uname eq c00n04 (id:location-D2-rule-expr) >+ Resource: D3 >+ Constraint: location-D3-2 >+ Rule: score=-INFINITY boolean-op=and (id:location-D3-2-rule) >+ Expression: not_defined pingd (id:location-D3-2-rule-expr) >+ Expression: pingd lte 0 (id:location-D3-2-rule-expr-1) >+ Constraint: location-D3-1 >+ Rule: score=-INFINITY boolean-op=or (id:location-D3-1-rule) >+ Expression: not_defined pingd (id:location-D3-1-rule-expr) >+ Expression: pingd lte 0 (id:location-D3-1-rule-expr-1) >+ Constraint: location-D3 >+ Rule: score-attribute=pingd (id:location-D3-rule) >+ Expression: defined pingd (id:location-D3-rule-expr) >+ Resource: D4 >+ Constraint: location-D4 >+ Rule: score=INFINITY (id:location-D4-rule) >+ Expression: date gt 2005-001 (id:location-D4-rule-expr) >+ Resource: D5 >+ Constraint: location-D5 >+ Rule: score=INFINITY (id:location-D5-rule) >+ Expression: date in_range 2005-001 to 2006-001 (id:location-D5-rule-expr) >+ Resource: D6 >+ Constraint: location-D6 >+ Rule: score=INFINITY (id:location-D6-rule) >+ Expression: (id:location-D6-rule-expr) >+ Date Spec: years=2005 (id:location-D6-rule-expr-datespec) >+Ordering Constraints: >+Colocation Constraints: >+""") > > o,r = pcs("constraint remove location-C1-group") > ac(o,"") >@@ -104,7 +145,40 @@ class ConstraintTest(unittest.TestCase): > > output, returnVal = pcs(temp_cib, "constraint --full") > assert returnVal == 0 >- ac (output,'Location Constraints:\n Resource: D1\n Constraint: location-D1\n Rule: score=222 (id:location-D1-rule) \n Expression: #uname eq c00n03 (id:location-D1-rule-expr-1) \n Resource: D2\n Constraint: location-D2\n Rule: score=-INFINITY (id:location-D2-rule) \n Expression: #uname eq c00n04 (id:location-D2-rule-expr-1) \n Resource: D3\n Constraint: location-D3-2\n Rule: score=-INFINITY boolean-op=and (id:location-D3-2-rule) \n Expression: not_defined pingd (id:location-D3-2-rule-expr-1) \n Expression: pingd lte 0 (id:location-D3-2-rule-expr-2) \n Constraint: location-D3-1\n Rule: score=-INFINITY boolean-op=or (id:location-D3-1-rule) \n Expression: not_defined pingd (id:location-D3-1-rule-expr-1) \n Expression: pingd lte 0 (id:location-D3-1-rule-expr-2) \n Constraint: location-D3\n Rule: score-attribute=pingd (id:location-D3-rule) \n Expression: defined pingd (id:location-D3-rule-expr-1) \n Resource: D5\n Constraint: location-D5\n Rule: score=INFINITY (id:location-D5-rule) \n Expression: start=2005-001 operation=in_range end=2006-001 (id:location-D5-rule-expr-1) \n Resource: D6\n Constraint: location-D6\n Rule: score=INFINITY (id:location-D6-rule) \n Expression: (id:location-D6-rule-expr-1) \n Date Spec: years=2005 (id:location-D6-rule-expr-1-datespec) \nOrdering Constraints:\nColocation Constraints:\n') >+ ac(output, """\ >+Location Constraints: >+ Resource: D1 >+ Constraint: location-D1 >+ Rule: score=222 (id:location-D1-rule) >+ Expression: #uname eq c00n03 (id:location-D1-rule-expr) >+ Resource: D2 >+ Constraint: location-D2 >+ Rule: score=-INFINITY (id:location-D2-rule) >+ Expression: #uname eq c00n04 (id:location-D2-rule-expr) >+ Resource: D3 >+ Constraint: location-D3-2 >+ Rule: score=-INFINITY boolean-op=and (id:location-D3-2-rule) >+ Expression: not_defined pingd (id:location-D3-2-rule-expr) >+ Expression: pingd lte 0 (id:location-D3-2-rule-expr-1) >+ Constraint: location-D3-1 >+ Rule: score=-INFINITY boolean-op=or (id:location-D3-1-rule) >+ Expression: not_defined pingd (id:location-D3-1-rule-expr) >+ Expression: pingd lte 0 (id:location-D3-1-rule-expr-1) >+ Constraint: location-D3 >+ Rule: score-attribute=pingd (id:location-D3-rule) >+ Expression: defined pingd (id:location-D3-rule-expr) >+ Resource: D5 >+ Constraint: location-D5 >+ Rule: score=INFINITY (id:location-D5-rule) >+ Expression: date in_range 2005-001 to 2006-001 (id:location-D5-rule-expr) >+ Resource: D6 >+ Constraint: location-D6 >+ Rule: score=INFINITY (id:location-D6-rule) >+ Expression: (id:location-D6-rule-expr) >+ Date Spec: years=2005 (id:location-D6-rule-expr-datespec) >+Ordering Constraints: >+Colocation Constraints: >+""") > > def testAdvancedConstraintRule(self): > o,r = pcs(temp_cib, "constraint location D1 rule score=INFINITY not_defined pingd or pingd lte 0") >@@ -113,7 +187,16 @@ class ConstraintTest(unittest.TestCase): > > output, returnVal = pcs(temp_cib, "constraint --full") > assert returnVal == 0 >- ac (output,'Location Constraints:\n Resource: D1\n Constraint: location-D1\n Rule: score=INFINITY boolean-op=or (id:location-D1-rule) \n Expression: not_defined pingd (id:location-D1-rule-expr-1) \n Expression: pingd lte 0 (id:location-D1-rule-expr-2) \nOrdering Constraints:\nColocation Constraints:\n') >+ ac(output, """\ >+Location Constraints: >+ Resource: D1 >+ Constraint: location-D1 >+ Rule: score=INFINITY boolean-op=or (id:location-D1-rule) >+ Expression: not_defined pingd (id:location-D1-rule-expr) >+ Expression: pingd lte 0 (id:location-D1-rule-expr-1) >+Ordering Constraints: >+Colocation Constraints: >+""") > > def testEmptyConstraints(self): > output, returnVal = pcs(temp_cib, "constraint") >@@ -529,7 +612,24 @@ Ordering Constraints: > > o, r = pcs(temp_cib, "constraint --full") > assert r == 0 >- ac(o,'Location Constraints:\n Resource: D1\n Constraint: location-D1-rh7-1-INFINITY\n Rule: score=INFINITY (id:location-D1-rh7-1-INFINITY-rule) \n Expression: #uname eq rh7-1 (id:location-D1-rh7-1-INFINITY-rule-expr-1) \n Rule: score=INFINITY (id:location-D1-rh7-1-INFINITY-rule-1) \n Expression: #uname eq rh7-1 (id:location-D1-rh7-1-INFINITY-rule-1-expr-1) \n Rule: score=INFINITY (id:location-D1-rh7-1-INFINITY-rule-2) \n Expression: #uname eq rh7-1 (id:location-D1-rh7-1-INFINITY-rule-2-expr-1) \n Resource: D2\n Constraint: location-D2-rh7-2-INFINITY\n Rule: score=INFINITY (id:location-D2-rh7-2-INFINITY-rule) \n Expression: (id:location-D2-rh7-2-INFINITY-rule-expr-1) \n Date Spec: hours=9-16 weekdays=1-5 (id:location-D2-rh7-2-INFINITY-rule-expr-1-datespec) \nOrdering Constraints:\nColocation Constraints:\n') >+ ac(o, """\ >+Location Constraints: >+ Resource: D1 >+ Constraint: location-D1-rh7-1-INFINITY >+ Rule: score=INFINITY (id:location-D1-rh7-1-INFINITY-rule) >+ Expression: #uname eq rh7-1 (id:location-D1-rh7-1-INFINITY-rule-expr) >+ Rule: score=INFINITY (id:location-D1-rh7-1-INFINITY-rule-1) >+ Expression: #uname eq rh7-1 (id:location-D1-rh7-1-INFINITY-rule-1-expr) >+ Rule: score=INFINITY (id:location-D1-rh7-1-INFINITY-rule-2) >+ Expression: #uname eq rh7-1 (id:location-D1-rh7-1-INFINITY-rule-2-expr) >+ Resource: D2 >+ Constraint: location-D2-rh7-2-INFINITY >+ Rule: score=INFINITY (id:location-D2-rh7-2-INFINITY-rule) >+ Expression: (id:location-D2-rh7-2-INFINITY-rule-expr) >+ Date Spec: hours=9-16 weekdays=1-5 (id:location-D2-rh7-2-INFINITY-rule-expr-datespec) >+Ordering Constraints: >+Colocation Constraints: >+""") > > o, r = pcs(temp_cib, "constraint rule remove location-D1-rh7-1-INFINITY-rule-1") > ac(o,"Removing Rule: location-D1-rh7-1-INFINITY-rule-1\n") >@@ -540,14 +640,36 @@ Ordering Constraints: > > o, r = pcs(temp_cib, "constraint --full") > assert r == 0 >- ac (o,'Location Constraints:\n Resource: D1\n Constraint: location-D1-rh7-1-INFINITY\n Rule: score=INFINITY (id:location-D1-rh7-1-INFINITY-rule) \n Expression: #uname eq rh7-1 (id:location-D1-rh7-1-INFINITY-rule-expr-1) \n Resource: D2\n Constraint: location-D2-rh7-2-INFINITY\n Rule: score=INFINITY (id:location-D2-rh7-2-INFINITY-rule) \n Expression: (id:location-D2-rh7-2-INFINITY-rule-expr-1) \n Date Spec: hours=9-16 weekdays=1-5 (id:location-D2-rh7-2-INFINITY-rule-expr-1-datespec) \nOrdering Constraints:\nColocation Constraints:\n') >+ ac(o, """\ >+Location Constraints: >+ Resource: D1 >+ Constraint: location-D1-rh7-1-INFINITY >+ Rule: score=INFINITY (id:location-D1-rh7-1-INFINITY-rule) >+ Expression: #uname eq rh7-1 (id:location-D1-rh7-1-INFINITY-rule-expr) >+ Resource: D2 >+ Constraint: location-D2-rh7-2-INFINITY >+ Rule: score=INFINITY (id:location-D2-rh7-2-INFINITY-rule) >+ Expression: (id:location-D2-rh7-2-INFINITY-rule-expr) >+ Date Spec: hours=9-16 weekdays=1-5 (id:location-D2-rh7-2-INFINITY-rule-expr-datespec) >+Ordering Constraints: >+Colocation Constraints: >+""") > > o, r = pcs(temp_cib, "constraint rule remove location-D1-rh7-1-INFINITY-rule") > assert r == 0 and o == "Removing Constraint: location-D1-rh7-1-INFINITY\n", o > > o, r = pcs(temp_cib, "constraint --full") > assert r == 0 >- ac (o,'Location Constraints:\n Resource: D2\n Constraint: location-D2-rh7-2-INFINITY\n Rule: score=INFINITY (id:location-D2-rh7-2-INFINITY-rule) \n Expression: (id:location-D2-rh7-2-INFINITY-rule-expr-1) \n Date Spec: hours=9-16 weekdays=1-5 (id:location-D2-rh7-2-INFINITY-rule-expr-1-datespec) \nOrdering Constraints:\nColocation Constraints:\n') >+ ac(o, """\ >+Location Constraints: >+ Resource: D2 >+ Constraint: location-D2-rh7-2-INFINITY >+ Rule: score=INFINITY (id:location-D2-rh7-2-INFINITY-rule) >+ Expression: (id:location-D2-rh7-2-INFINITY-rule-expr) >+ Date Spec: hours=9-16 weekdays=1-5 (id:location-D2-rh7-2-INFINITY-rule-expr-datespec) >+Ordering Constraints: >+Colocation Constraints: >+""") > > o,r = pcs("constraint location D1 rule role=master") > ac (o,"Error: no rule expression was specified\n") >@@ -575,7 +697,15 @@ Ordering Constraints: > assert r == 0 > > o,r = pcs("constraint --full") >- ac(o,"Location Constraints:\n Resource: stateful0\n Constraint: location-stateful0\n Rule: score=INFINITY role=master (id:location-stateful0-rule) \n Expression: #uname eq rh7-1 (id:location-stateful0-rule-expr-1) \nOrdering Constraints:\nColocation Constraints:\n") >+ ac(o, """\ >+Location Constraints: >+ Resource: stateful0 >+ Constraint: location-stateful0 >+ Rule: score=INFINITY role=master (id:location-stateful0-rule) >+ Expression: #uname eq rh7-1 (id:location-stateful0-rule-expr) >+Ordering Constraints: >+Colocation Constraints: >+""") > assert r == 0 > > o,r = pcs("resource create stateful1 Dummy --master") >@@ -583,15 +713,15 @@ Ordering Constraints: > assert r == 0 > > o,r = pcs("constraint location stateful1 rule rulename '#uname' eq rh7-1") >- ac(o,"Error: 'rulename #uname eq rh7-1' is not a valid rule expression\n") >+ ac(o,"Error: 'rulename #uname eq rh7-1' is not a valid rule expression: unexpected '#uname'\n") > assert r == 1 > > o,r = pcs("constraint location stateful1 rule role=master rulename '#uname' eq rh7-1") >- ac(o,"Error: 'rulename #uname eq rh7-1' is not a valid rule expression\n") >+ ac(o,"Error: 'rulename #uname eq rh7-1' is not a valid rule expression: unexpected '#uname'\n") > assert r == 1 > > o,r = pcs("constraint location stateful1 rule role=master 25") >- ac(o,"Error: '25' is not a valid rule expression\n") >+ ac(o,"Error: '25' is not a valid rule expression: missing one of 'eq', 'ne', 'lt', 'gt', 'lte', 'gte', 'in_range', 'defined', 'not_defined', 'date-spec'\n") > assert r == 1 > > o,r = pcs("constraint location D1 prefers rh7-1=foo") >diff --git a/pcs/test/test_resource.py b/pcs/test/test_resource.py >index 12913a8..b94bcf2 100644 >--- a/pcs/test/test_resource.py >+++ b/pcs/test/test_resource.py >@@ -1220,9 +1220,9 @@ Colocation Constraints: > Location Constraints: > Resource: dummy > Constraint: cli-prefer-dummy >- Rule: score=INFINITY boolean-op=and (id:cli-prefer-rule-dummy) >- Expression: #uname eq rh7-1 type=string (id:cli-prefer-expr-dummy) >- Expression: operation=lt end={datetime} (id:cli-prefer-lifetime-end-dummy) >+ Rule: score=INFINITY boolean-op=and (id:cli-prefer-rule-dummy) >+ Expression: #uname eq string rh7-1 (id:cli-prefer-expr-dummy) >+ Expression: date lt {datetime} (id:cli-prefer-lifetime-end-dummy) > Ordering Constraints: > Colocation Constraints: > """) >@@ -1251,9 +1251,9 @@ Colocation Constraints: > Location Constraints: > Resource: dummy > Constraint: cli-ban-dummy-on-rh7-1 >- Rule: score=-INFINITY boolean-op=and (id:cli-ban-dummy-on-rh7-1-rule) >- Expression: #uname eq rh7-1 type=string (id:cli-ban-dummy-on-rh7-1-expr) >- Expression: operation=lt end={datetime} (id:cli-ban-dummy-on-rh7-1-lifetime) >+ Rule: score=-INFINITY boolean-op=and (id:cli-ban-dummy-on-rh7-1-rule) >+ Expression: #uname eq string rh7-1 (id:cli-ban-dummy-on-rh7-1-expr) >+ Expression: date lt {datetime} (id:cli-ban-dummy-on-rh7-1-lifetime) > Ordering Constraints: > Colocation Constraints: > """) >diff --git a/pcs/test/test_rule.py b/pcs/test/test_rule.py >new file mode 100644 >index 0000000..a5542a6 >--- /dev/null >+++ b/pcs/test/test_rule.py >@@ -0,0 +1,1878 @@ >+import os >+import sys >+import shutil >+import unittest >+import xml.dom.minidom >+parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) >+sys.path.insert(0, parentdir) >+from pcs_test_functions import pcs,ac >+import rule >+ >+empty_cib = "empty.xml" >+temp_cib = "temp.xml" >+ >+class DateValueTest(unittest.TestCase): >+ >+ def testParse(self): >+ for value, item in enumerate(rule.DateCommonValue.allowed_items, 1): >+ self.assertEquals( >+ str(value), >+ rule.DateCommonValue("%s=%s" % (item, value)).parts[item] >+ ) >+ >+ value = rule.DateCommonValue( >+ "hours=1 monthdays=2 weekdays=3 yeardays=4 months=5 weeks=6 " >+ "years=7 weekyears=8 moon=9" >+ ) >+ self.assertEquals("1", value.parts["hours"]) >+ self.assertEquals("2", value.parts["monthdays"]) >+ self.assertEquals("3", value.parts["weekdays"]) >+ self.assertEquals("4", value.parts["yeardays"]) >+ self.assertEquals("5", value.parts["months"]) >+ self.assertEquals("6", value.parts["weeks"]) >+ self.assertEquals("7", value.parts["years"]) >+ self.assertEquals("8", value.parts["weekyears"]) >+ self.assertEquals("9", value.parts["moon"]) >+ >+ value = rule.DateCommonValue("hours=1 monthdays=2 hours=3") >+ self.assertEquals("2", value.parts["monthdays"]) >+ self.assertEquals("3", value.parts["hours"]) >+ >+ value = rule.DateCommonValue(" hours=1 monthdays=2 hours=3 ") >+ self.assertEquals("2", value.parts["monthdays"]) >+ self.assertEquals("3", value.parts["hours"]) >+ >+ self.assertSyntaxError( >+ "missing one of 'hours=', 'monthdays=', 'weekdays=', 'yeardays=', " >+ "'months=', 'weeks=', 'years=', 'weekyears=', 'moon=' in date-spec", >+ "", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "missing value after 'hours=' in date-spec", >+ "hours=", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "missing =value after 'hours' in date-spec", >+ "hours", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "unexpected 'foo=bar' in date-spec", >+ "foo=bar", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "unexpected 'foo=bar' in date-spec", >+ "hours=1 foo=bar", >+ rule.DateSpecValue >+ ) >+ >+ def testDurationValidate(self): >+ for value, item in enumerate(rule.DateCommonValue.allowed_items, 1): >+ self.assertEquals( >+ str(value), >+ rule.DateDurationValue("%s=%s" % (item, value)).parts[item] >+ ) >+ for item in rule.DateCommonValue.allowed_items: >+ self.assertSyntaxError( >+ "invalid %s '%s' in 'duration'" % (item, "foo"), >+ "%s=foo" % item, >+ rule.DateDurationValue >+ ) >+ self.assertSyntaxError( >+ "invalid %s '%s' in 'duration'" % (item, "-1"), >+ "%s=-1" % item, >+ rule.DateDurationValue >+ ) >+ self.assertSyntaxError( >+ "invalid %s '%s' in 'duration'" % (item, "2foo"), >+ "%s=2foo" % item, >+ rule.DateDurationValue >+ ) >+ >+ def testDateSpecValidation(self): >+ for item in rule.DateCommonValue.allowed_items: >+ value = 1 >+ self.assertEquals( >+ str(value), >+ rule.DateSpecValue("%s=%s" % (item, value)).parts[item] >+ ) >+ self.assertEquals( >+ "%s-%s" % (value, value + 1), >+ rule.DateSpecValue( >+ "%s=%s-%s" % (item, value, value + 1) >+ ).parts[item] >+ ) >+ self.assertEquals( >+ "hours=9-16 weekdays=1-5", >+ str(rule.DateSpecValue("hours=9-16 weekdays=1-5")) >+ ) >+ for item in rule.DateCommonValue.allowed_items: >+ self.assertSyntaxError( >+ "invalid %s '%s' in 'date-spec'" % (item, "foo"), >+ "%s=foo" % item, >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid %s '%s' in 'date-spec'" % (item, "1-foo"), >+ "%s=1-foo" % item, >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid %s '%s' in 'date-spec'" % (item, "foo-1"), >+ "%s=foo-1" % item, >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid %s '%s' in 'date-spec'" % (item, "1-2-3"), >+ "%s=1-2-3" % item, >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid %s '%s' in 'date-spec'" % (item, "2-1"), >+ "%s=2-1" % item, >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid hours '24' in 'date-spec'", >+ "hours=24", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid monthdays '32' in 'date-spec'", >+ "monthdays=32", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid weekdays '8' in 'date-spec'", >+ "weekdays=8", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid yeardays '367' in 'date-spec'", >+ "yeardays=367", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid months '13' in 'date-spec'", >+ "months=13", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid weeks '54' in 'date-spec'", >+ "weeks=54", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid weekyears '54' in 'date-spec'", >+ "weekyears=54", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid moon '8' in 'date-spec'", >+ "moon=8", >+ rule.DateSpecValue >+ ) >+ self.assertSyntaxError( >+ "invalid hours '12-8' in 'date-spec'", >+ "hours=12-8", >+ rule.DateSpecValue >+ ) >+ >+ def assertSyntaxError(self, syntax_error, parts_string, value_class=None): >+ value_class = value_class if value_class else rule.DateCommonValue >+ self.assertRaises(rule.SyntaxError, value_class, parts_string) >+ try: >+ value_class(parts_string) >+ except rule.SyntaxError as e: >+ self.assertEquals(syntax_error, str(e)) >+ >+ >+class ParserTest(unittest.TestCase): >+ >+ def setUp(self): >+ self.parser = rule.RuleParser() >+ >+ def testEmptyInput(self): >+ self.assertRaises(rule.UnexpectedEndOfInput, self.parser.parse, []) >+ >+ def testSingleLiteral(self): >+ self.assertSyntaxError( >+ "missing one of 'eq', 'ne', 'lt', 'gt', 'lte', 'gte', 'in_range', " >+ "'defined', 'not_defined', 'date-spec'", >+ ["#uname"] >+ ) >+ self.assertSyntaxError( >+ "missing one of 'eq', 'ne', 'lt', 'gt', 'lte', 'gte', 'in_range', " >+ "'defined', 'not_defined', 'date-spec'", >+ ["string", "node1"] >+ ) >+ >+ def testSingleLiteralDatespec(self): >+ self.assertEquals( >+ "(date-spec (literal hours=1))", >+ str(self.parser.parse(["date-spec", "hours=1"])) >+ ) >+ self.assertEquals( >+ "(date-spec (literal hours=1-14 months=1 monthdays=20-30))", >+ str(self.parser.parse([ >+ "date-spec", "hours=1-14 months=1 monthdays=20-30" >+ ])) >+ ) >+ self.assertUnexpectedEndOfInput(["date-spec"]) >+ >+ def testSimpleExpression(self): >+ self.assertEquals( >+ "(eq (literal #uname) (literal node1))", >+ str(self.parser.parse(["#uname", "eq", "node1"])) >+ ) >+ self.assertEquals( >+ "(ne (literal #uname) (literal node2))", >+ str(self.parser.parse(["#uname", "ne", "node2"])) >+ ) >+ self.assertEquals( >+ "(gt (literal int) (literal 123))", >+ str(self.parser.parse(["int", "gt", "123"])) >+ ) >+ self.assertEquals( >+ "(gte (literal int) (literal 123))", >+ str(self.parser.parse(["int", "gte", "123"])) >+ ) >+ self.assertEquals( >+ "(lt (literal int) (literal 123))", >+ str(self.parser.parse(["int", "lt", "123"])) >+ ) >+ self.assertEquals( >+ "(lte (literal int) (literal 123))", >+ str(self.parser.parse(["int", "lte", "123"])) >+ ) >+ >+ def testSimpleExpressionBad(self): >+ self.assertSyntaxError( >+ "unexpected 'eq'", >+ ["eq"] >+ ) >+ self.assertUnexpectedEndOfInput(["#uname", "eq"]) >+ self.assertSyntaxError( >+ "unexpected 'node1'", >+ ["#uname", "node1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'eq'", >+ ["eq", "#uname"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'eq'", >+ ["eq", "lt"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'string' before 'eq'", >+ ["string", "#uname", "eq", "node1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'date-spec' before 'eq'", >+ ["date-spec", "hours=1", "eq", "node1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'date-spec' after 'eq'", >+ ["#uname", "eq", "date-spec", "hours=1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'duration' before 'eq'", >+ ["duration", "hours=1", "eq", "node1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'duration' after 'eq'", >+ ["#uname", "eq", "duration", "hours=1"] >+ ) >+ >+ def testDefinedExpression(self): >+ self.assertEquals( >+ "(defined (literal pingd))", >+ str(self.parser.parse(["defined", "pingd"])) >+ ) >+ self.assertEquals( >+ "(not_defined (literal pingd))", >+ str(self.parser.parse(["not_defined", "pingd"])) >+ ) >+ >+ def testDefinedExpressionBad(self): >+ self.assertUnexpectedEndOfInput(["defined"]) >+ self.assertUnexpectedEndOfInput(["not_defined"]) >+ self.assertSyntaxError( >+ "unexpected 'eq'", >+ ["defined", "eq"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'and'", >+ ["defined", "and"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'string' after 'defined'", >+ ["defined", "string", "pingd"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'date-spec' after 'defined'", >+ ["defined", "date-spec", "hours=1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'duration' after 'defined'", >+ ["defined", "duration", "hours=1"] >+ ) >+ >+ def testTypeExpression(self): >+ self.assertEquals( >+ "(eq (literal #uname) (string (literal node1)))", >+ str(self.parser.parse(["#uname", "eq", "string", "node1"])) >+ ) >+ self.assertEquals( >+ "(eq (literal #uname) (integer (literal 12345)))", >+ str(self.parser.parse(["#uname", "eq", "integer", "12345"])) >+ ) >+ self.assertEquals( >+ "(eq (literal #uname) (integer (literal -12345)))", >+ str(self.parser.parse(["#uname", "eq", "integer", "-12345"])) >+ ) >+ self.assertEquals( >+ "(eq (literal #uname) (version (literal 1)))", >+ str(self.parser.parse(["#uname", "eq", "version", "1"])) >+ ) >+ self.assertEquals( >+ "(eq (literal #uname) (version (literal 1.2.3)))", >+ str(self.parser.parse(["#uname", "eq", "version", "1.2.3"])) >+ ) >+ self.assertEquals( >+ "(eq (literal #uname) (string (literal string)))", >+ str(self.parser.parse(["#uname", "eq", "string", "string"])) >+ ) >+ self.assertEquals( >+ "(eq (literal #uname) (string (literal and)))", >+ str(self.parser.parse(["#uname", "eq", "string", "and"])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(ne (literal #uname) (string (literal integer))) " >+ "(ne (literal #uname) (string (literal version)))" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "ne", "string", "integer", >+ "and", >+ "#uname", "ne", "string", "version" >+ ])) >+ ) >+ >+ def testTypeExpressionBad(self): >+ self.assertUnexpectedEndOfInput(["string"]) >+ self.assertUnexpectedEndOfInput(["#uname", "eq", "string"]) >+ self.assertSyntaxError( >+ "unexpected 'string' before 'eq'", >+ ["string", "#uname", "eq", "node1"] >+ ) >+ self.assertSyntaxError( >+ "invalid integer value 'node1'", >+ ["#uname", "eq", "integer", "node1"] >+ ) >+ self.assertSyntaxError( >+ "invalid version value 'node1'", >+ ["#uname", "eq", "version", "node1"] >+ ) >+ >+ def testDateExpression(self): >+ self.assertEquals( >+ "(gt (literal date) (literal 2014-06-26))", >+ str(self.parser.parse(["date", "gt", "2014-06-26"])) >+ ) >+ self.assertEquals( >+ "(lt (literal date) (literal 2014-06-26))", >+ str(self.parser.parse(["date", "lt", "2014-06-26"])) >+ ) >+ self.assertEquals( >+ "(in_range " >+ "(literal date) (literal 2014-06-26) (literal 2014-07-26)" >+ ")", >+ str(self.parser.parse([ >+ "date", "in_range", "2014-06-26", "to", "2014-07-26" >+ ])) >+ ) >+ self.assertEquals( >+ "(in_range " >+ "(literal date) " >+ "(literal 2014-06-26) (duration (literal years=1))" >+ ")", >+ str(self.parser.parse([ >+ "date", "in_range", "2014-06-26", "to", "duration", "years=1" >+ ])) >+ ) >+ >+ def testDateExpressionBad(self): >+ self.assertUnexpectedEndOfInput( >+ ["date", "in_range"] >+ ) >+ self.assertSyntaxError( >+ "missing 'to'", >+ ["date", "in_range", '2014-06-26'] >+ ) >+ self.assertUnexpectedEndOfInput( >+ ["date", "in_range", "2014-06-26", "to"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'in_range'", >+ ["in_range", '2014-06-26', "to", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "expecting 'to', got 'eq'", >+ ["date", "in_range", '#uname', "eq", "node1", "to", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "invalid date '#uname' in 'in_range ... to'", >+ ["date", "in_range", "2014-06-26", "to", '#uname', "eq", "node1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'defined' after 'in_range'", >+ ["date", "in_range", "defined", "pingd", "to", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'defined' after 'in_range ... to'", >+ ["date", "in_range", "2014-06-26", "to", "defined", "pingd"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'string' before 'in_range'", >+ ["string", "date", "in_range", '2014-06-26', "to", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'string' after 'in_range'", >+ ["date", "in_range", "string", '2014-06-26', "to", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'string' after 'in_range ... to'", >+ ["date", "in_range", '2014-06-26', "to", "string", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'string' after '2014-06-26'", >+ ["date", "in_range", '2014-06-26', "string", "to", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "unexpected '#uname' before 'in_range'", >+ ["#uname", "in_range", '2014-06-26', "to", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "invalid date '2014-13-26' in 'in_range ... to'", >+ ["date", "in_range", '2014-13-26', "to", "2014-07-26"] >+ ) >+ self.assertSyntaxError( >+ "invalid date '2014-13-26' in 'in_range ... to'", >+ ["date", "in_range", '2014-06-26', "to", "2014-13-26"] >+ ) >+ >+ def testAndOrExpression(self): >+ self.assertEquals( >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(ne (literal #uname) (literal node2))" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "ne", "node1", "and", "#uname", "ne", "node2" >+ ])) >+ ) >+ self.assertEquals( >+ "(or " >+ "(eq (literal #uname) (literal node1)) " >+ "(eq (literal #uname) (literal node2))" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "eq", "node1", "or", "#uname", "eq", "node2" >+ ])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(ne (literal #uname) (literal node2))" >+ ") " >+ "(ne (literal #uname) (literal node3))" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "ne", "node1", >+ "and", "#uname", "ne", "node2", >+ "and", "#uname", "ne", "node3" >+ ])) >+ ) >+ self.assertEquals( >+ "(or " >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(ne (literal #uname) (literal node2))" >+ ") " >+ "(eq (literal #uname) (literal node3))" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "ne", "node1", >+ "and", "#uname", "ne", "node2", >+ "or", "#uname", "eq", "node3" >+ ])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(or " >+ "(eq (literal #uname) (literal node1)) " >+ "(eq (literal #uname) (literal node2))" >+ ") " >+ "(ne (literal #uname) (literal node3))" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "eq", "node1", >+ "or", "#uname", "eq", "node2", >+ "and", "#uname", "ne", "node3" >+ ])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(defined (literal pingd)) " >+ "(lte (literal pingd) (literal 1))" >+ ")", >+ str(self.parser.parse([ >+ "defined", "pingd", "and", "pingd", "lte", "1" >+ ])) >+ ) >+ self.assertEquals( >+ "(or " >+ "(gt (literal pingd) (literal 1)) " >+ "(not_defined (literal pingd))" >+ ")", >+ str(self.parser.parse([ >+ "pingd", "gt", "1", "or", "not_defined", "pingd" >+ ])) >+ ) >+ >+ def testAndOrExpressionDateSpec(self): >+ self.assertEquals( >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(date-spec (literal hours=1-12))" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "ne", "node1", "and", "date-spec", "hours=1-12" >+ ])) >+ ) >+ self.assertEquals( >+ "(or " >+ "(date-spec (literal monthdays=1-12)) " >+ "(ne (literal #uname) (literal node1))" >+ ")", >+ str(self.parser.parse([ >+ "date-spec", "monthdays=1-12", "or", "#uname", "ne", "node1" >+ ])) >+ ) >+ self.assertEquals( >+ "(or " >+ "(date-spec (literal monthdays=1-10)) " >+ "(date-spec (literal monthdays=11-20))" >+ ")", >+ str(self.parser.parse([ >+ "date-spec", "monthdays=1-10", >+ "or", >+ "date-spec", "monthdays=11-20" >+ ])) >+ ) >+ >+ def testAndOrExpressionDate(self): >+ self.assertEquals( >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(in_range " >+ "(literal date) (literal 2014-06-26) (literal 2014-07-26)" >+ ")" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "ne", "node1", >+ "and", >+ "date", "in_range", "2014-06-26", "to", "2014-07-26" >+ ])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(in_range " >+ "(literal date) (literal 2014-06-26) (literal 2014-07-26)" >+ ") " >+ "(ne (literal #uname) (literal node1))" >+ ")", >+ str(self.parser.parse([ >+ "date", "in_range", "2014-06-26", "to", "2014-07-26", >+ "and", >+ "#uname", "ne", "node1" >+ ])) >+ ) >+ >+ def testAndOrExpressionBad(self): >+ self.assertSyntaxError( >+ "unexpected 'and'", >+ ["and"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'or'", >+ ["or"] >+ ) >+ self.assertSyntaxError( >+ "unexpected '#uname' before 'and'", >+ ["#uname", "and", "node1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected '#uname' before 'or'", >+ ["#uname", "or", "node1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected '#uname' before 'or'", >+ ["#uname", "or", "eq"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'node2' after 'and'", >+ ["#uname", "eq", "node1", "and", "node2"] >+ ) >+ self.assertUnexpectedEndOfInput(["#uname", "eq", "node1", "and"]) >+ self.assertUnexpectedEndOfInput( >+ ["#uname", "eq", "node1", "and", "#uname", "eq"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'and'", >+ ["and", "#uname", "eq", "node1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'duration' after 'and'", >+ ["#uname", "ne", "node1", "and", "duration", "hours=1"] >+ ) >+ self.assertSyntaxError( >+ "unexpected 'duration' before 'or'", >+ ["duration", "monthdays=1", "or", "#uname", "ne", "node1"] >+ ) >+ >+ def testParenthesizedExpression(self): >+ self.assertSyntaxError( >+ "missing one of 'eq', 'ne', 'lt', 'gt', 'lte', 'gte', 'in_range', " >+ "'defined', 'not_defined', 'date-spec'", >+ ["(", "#uname", ")"] >+ ) >+ self.assertEquals( >+ "(date-spec (literal hours=1))", >+ str(self.parser.parse(["(", "date-spec", "hours=1", ")"])) >+ ) >+ self.assertEquals( >+ "(eq (literal #uname) (literal node1))", >+ str(self.parser.parse(["(", "#uname", "eq", "node1", ")"])) >+ ) >+ self.assertEquals( >+ "(defined (literal pingd))", >+ str(self.parser.parse(["(", "defined", "pingd", ")"])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(ne (literal #uname) (literal node2))" >+ ")", >+ str(self.parser.parse([ >+ "(", >+ "#uname", "ne", "node1", "and", "#uname", "ne", "node2", >+ ")" >+ ])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(ne (literal #uname) (literal node2))" >+ ")", >+ str(self.parser.parse([ >+ "(", "#uname", "ne", "node1", ")", >+ "and", >+ "(", "#uname", "ne", "node2", ")" >+ ])) >+ ) >+ self.assertEquals( >+ "(or " >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(ne (literal #uname) (literal node2))" >+ ") " >+ "(eq (literal #uname) (literal node3))" >+ ")", >+ str(self.parser.parse([ >+ "(", >+ "#uname", "ne", "node1", "and", "#uname", "ne", "node2", >+ ")", >+ "or", "#uname", "eq", "node3" >+ ])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(or " >+ "(ne (literal #uname) (literal node2)) " >+ "(eq (literal #uname) (literal node3))" >+ ")" >+ ")", >+ str(self.parser.parse([ >+ "#uname", "ne", "node1", >+ "and", >+ "(", "#uname", "ne", "node2", "or", "#uname", "eq", "node3", ")" >+ ])) >+ ) >+ self.assertEquals( >+ "(and " >+ "(ne (literal #uname) (literal node1)) " >+ "(or " >+ "(ne (literal #uname) (literal node2)) " >+ "(eq (literal #uname) (literal node3))" >+ ")" >+ ")", >+ str(self.parser.parse([ >+ "(", "(", >+ "(", "#uname", "ne", "node1", ")", >+ "and", >+ "(", "(", >+ "(", "#uname", "ne", "node2", ")", >+ "or", >+ "(", "#uname", "eq", "node3", ")", >+ ")", ")", >+ ")", ")" >+ ])) >+ ) >+ self.assertEquals( >+ "(in_range " >+ "(literal date) (literal 2014-06-26) (literal 2014-07-26)" >+ ")", >+ str(self.parser.parse([ >+ "(", "date", "in_range", "2014-06-26", "to", "2014-07-26", ")" >+ ])) >+ ) >+ >+ def testParenthesizedExpressionBad(self): >+ self.assertUnexpectedEndOfInput(["("]) >+ self.assertSyntaxError( >+ "unexpected ')'", >+ ["(", ")"] >+ ) >+ self.assertSyntaxError( >+ "missing ')'", >+ ["(", "#uname"] >+ ) >+ self.assertUnexpectedEndOfInput(["(", "#uname", "eq"]) >+ self.assertSyntaxError( >+ "missing ')'", >+ ["(", "#uname", "eq", "node1"] >+ ) >+ >+ def assertUnexpectedEndOfInput(self, program): >+ self.assertRaises(rule.UnexpectedEndOfInput, self.parser.parse, program) >+ >+ def assertSyntaxError(self, syntax_error, program): >+ self.assertRaises( >+ rule.SyntaxError, self.parser.parse, program >+ ) >+ try: >+ self.parser.parse(program) >+ except rule.SyntaxError as e: >+ self.assertEquals(syntax_error, str(e)) >+ >+ >+class CibBuilderTest(unittest.TestCase): >+ >+ def setUp(self): >+ self.parser = rule.RuleParser() >+ self.builder = rule.CibBuilder() >+ >+ def testSingleLiteralDatespec(self): >+ self.assertExpressionXml( >+ ["date-spec", "hours=1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <date_expression id="location-dummy-rule-expr" operation="date_spec"> >+ <date_spec hours="1" id="location-dummy-rule-expr-datespec"/> >+ </date_expression> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date-spec", "hours=1-14 monthdays=20-30 months=1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <date_expression id="location-dummy-rule-expr" operation="date_spec"> >+ <date_spec hours="1-14" id="location-dummy-rule-expr-datespec" monthdays="20-30" months="1"/> >+ </date_expression> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def testSimpleExpression(self): >+ self.assertExpressionXml( >+ ["#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["#uname", "ne", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="ne" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["#uname", "gt", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="gt" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["#uname", "gte", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="gte" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["#uname", "lt", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="lt" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["#uname", "lte", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="lte" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def testTypeExpression(self): >+ self.assertExpressionXml( >+ ["#uname", "eq", "string", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" type="string" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["#uname", "eq", "integer", "12345"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" type="number" value="12345"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["#uname", "eq", "version", "1.2.3"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" type="version" value="1.2.3"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def testDefinedExpression(self): >+ self.assertExpressionXml( >+ ["defined", "pingd"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="pingd" id="location-dummy-rule-expr" operation="defined"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["not_defined", "pingd"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="pingd" id="location-dummy-rule-expr" operation="not_defined"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def testDateExpression(self): >+ self.assertExpressionXml( >+ ["date", "gt", "2014-06-26"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <date_expression id="location-dummy-rule-expr" operation="gt" start="2014-06-26"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date", "lt", "2014-06-26"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <date_expression end="2014-06-26" id="location-dummy-rule-expr" operation="lt"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date", "in_range", "2014-06-26", "to", "2014-07-26"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <date_expression end="2014-07-26" id="location-dummy-rule-expr" operation="in_range" start="2014-06-26"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date", "in_range", "2014-06-26", "to", "duration", "years=1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <date_expression id="location-dummy-rule-expr" operation="in_range" start="2014-06-26"> >+ <duration id="location-dummy-rule-expr-duration" years="1"/> >+ </date_expression> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def testNotDateExpression(self): >+ self.assertExpressionXml( >+ ["date", "eq", "2014-06-26"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="date" id="location-dummy-rule-expr" operation="eq" value="2014-06-26"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date", "gt", "string", "2014-06-26"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="date" id="location-dummy-rule-expr" operation="gt" type="string" value="2014-06-26"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date", "gt", "integer", "12345"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="date" id="location-dummy-rule-expr" operation="gt" type="number" value="12345"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date", "gt", "version", "1.2.3"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule"> >+ <expression attribute="date" id="location-dummy-rule-expr" operation="gt" type="version" value="1.2.3"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def testAndOrExpression(self): >+ self.assertExpressionXml( >+ ["#uname", "ne", "node1", "and", "#uname", "ne", "node2"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="and" id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="ne" value="node1"/> >+ <expression attribute="#uname" id="location-dummy-rule-expr-1" operation="ne" value="node2"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["#uname", "eq", "node1", "or", "#uname", "eq", "node2"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="or" id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node1"/> >+ <expression attribute="#uname" id="location-dummy-rule-expr-1" operation="eq" value="node2"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ [ >+ "#uname", "ne", "node1", >+ "and", "#uname", "ne", "node2", >+ "and", "#uname", "ne", "node3" >+ ], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="and" id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="ne" value="node1"/> >+ <expression attribute="#uname" id="location-dummy-rule-expr-1" operation="ne" value="node2"/> >+ <expression attribute="#uname" id="location-dummy-rule-expr-2" operation="ne" value="node3"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ [ >+ "#uname", "ne", "node1", >+ "and", "#uname", "ne", "node2", >+ "or", "#uname", "eq", "node3" >+ ], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="or" id="location-dummy-rule"> >+ <rule boolean-op="and" id="location-dummy-rule-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr" operation="ne" value="node1"/> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr-1" operation="ne" value="node2"/> >+ </rule> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node3"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ [ >+ "#uname", "eq", "node1", >+ "or", "#uname", "eq", "node2", >+ "and", "#uname", "ne", "node3" >+ ], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="and" id="location-dummy-rule"> >+ <rule boolean-op="or" id="location-dummy-rule-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr" operation="eq" value="node1"/> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr-1" operation="eq" value="node2"/> >+ </rule> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="ne" value="node3"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["defined", "pingd", "and", "pingd", "lte", "1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="and" id="location-dummy-rule"> >+ <expression attribute="pingd" id="location-dummy-rule-expr" operation="defined"/> >+ <expression attribute="pingd" id="location-dummy-rule-expr-1" operation="lte" value="1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["pingd", "gt", "1", "or", "not_defined", "pingd"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="or" id="location-dummy-rule"> >+ <expression attribute="pingd" id="location-dummy-rule-expr" operation="gt" value="1"/> >+ <expression attribute="pingd" id="location-dummy-rule-expr-1" operation="not_defined"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def testAndOrExpressionDateSpec(self): >+ self.assertExpressionXml( >+ ["#uname", "ne", "node1", "and", "date-spec", "hours=1-12"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="and" id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="ne" value="node1"/> >+ <date_expression id="location-dummy-rule-expr-1" operation="date_spec"> >+ <date_spec hours="1-12" id="location-dummy-rule-expr-1-datespec"/> >+ </date_expression> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date-spec", "monthdays=1-12", "or", "#uname", "ne", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="or" id="location-dummy-rule"> >+ <date_expression id="location-dummy-rule-expr" operation="date_spec"> >+ <date_spec id="location-dummy-rule-expr-datespec" monthdays="1-12"/> >+ </date_expression> >+ <expression attribute="#uname" id="location-dummy-rule-expr-1" operation="ne" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["date-spec", "monthdays=1-10", "or", "date-spec", "monthdays=11-20"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="or" id="location-dummy-rule"> >+ <date_expression id="location-dummy-rule-expr" operation="date_spec"> >+ <date_spec id="location-dummy-rule-expr-datespec" monthdays="1-10"/> >+ </date_expression> >+ <date_expression id="location-dummy-rule-expr-1" operation="date_spec"> >+ <date_spec id="location-dummy-rule-expr-1-datespec" monthdays="11-20"/> >+ </date_expression> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def testParenthesizedExpression(self): >+ self.assertExpressionXml( >+ [ >+ "(", >+ "#uname", "ne", "node1", "and", "#uname", "ne", "node2", >+ ")", >+ "or", "#uname", "eq", "node3" >+ ], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="or" id="location-dummy-rule"> >+ <rule boolean-op="and" id="location-dummy-rule-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr" operation="ne" value="node1"/> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr-1" operation="ne" value="node2"/> >+ </rule> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node3"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ [ >+ "#uname", "ne", "node1", >+ "and", >+ "(", "#uname", "ne", "node2", "or", "#uname", "eq", "node3", ")" >+ ], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="and" id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="ne" value="node1"/> >+ <rule boolean-op="or" id="location-dummy-rule-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr" operation="ne" value="node2"/> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr-1" operation="eq" value="node3"/> >+ </rule> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ [ >+ "(", >+ "#uname", "ne", "node1", "and", "#uname", "ne", "node2", >+ ")", >+ "or", >+ "(", >+ "#uname", "ne", "node3", "and", "#uname", "ne", "node4", >+ ")", >+ ], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="or" id="location-dummy-rule"> >+ <rule boolean-op="and" id="location-dummy-rule-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr" operation="ne" value="node1"/> >+ <expression attribute="#uname" id="location-dummy-rule-rule-expr-1" operation="ne" value="node2"/> >+ </rule> >+ <rule boolean-op="and" id="location-dummy-rule-rule-1"> >+ <expression attribute="#uname" id="location-dummy-rule-rule-1-expr" operation="ne" value="node3"/> >+ <expression attribute="#uname" id="location-dummy-rule-rule-1-expr-1" operation="ne" value="node4"/> >+ </rule> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ [ >+ "(", >+ "#uname", "ne", "node1", "and", "#uname", "ne", "node2", >+ ")", >+ "and", >+ "(", >+ "#uname", "ne", "node3", "and", "#uname", "ne", "node4", >+ ")", >+ ], >+ """ >+<rsc_location id="location-dummy"> >+ <rule boolean-op="and" id="location-dummy-rule"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="ne" value="node1"/> >+ <expression attribute="#uname" id="location-dummy-rule-expr-1" operation="ne" value="node2"/> >+ <expression attribute="#uname" id="location-dummy-rule-expr-2" operation="ne" value="node3"/> >+ <expression attribute="#uname" id="location-dummy-rule-expr-3" operation="ne" value="node4"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def assertExpressionXml(self, rule_expression, rule_xml): >+ cib_dom = xml.dom.minidom.parse("empty.xml") >+ constraints = cib_dom.getElementsByTagName("constraints")[0] >+ constraint_el = constraints.appendChild( >+ cib_dom.createElement("rsc_location") >+ ) >+ constraint_el.setAttribute("id", "location-dummy") >+ ac( >+ self.builder.build( >+ constraint_el, >+ self.parser.parse(rule_expression) >+ ).parentNode.toprettyxml(indent=" "), >+ rule_xml.lstrip().rstrip(" ") >+ ) >+ >+ >+class TokenPreprocessorTest(unittest.TestCase): >+ >+ def setUp(self): >+ self.preprocessor = rule.TokenPreprocessor() >+ >+ def testNoChanges(self): >+ self.assertEquals([], self.preprocessor.run([])) >+ self.assertEquals( >+ ["#uname", "eq", "node1"], >+ self.preprocessor.run(["#uname", "eq", "node1"]) >+ ) >+ >+ def testDateSpec(self): >+ self.assertEquals( >+ ["date-spec"], >+ self.preprocessor.run(["date-spec"]) >+ ) >+ self.assertEquals( >+ ["date-spec", "hours=14"], >+ self.preprocessor.run(["date-spec", "hours=14"]) >+ ) >+ self.assertEquals( >+ ["date-spec", "hours weeks=6 months= moon=1"], >+ self.preprocessor.run( >+ ["date-spec", "hours", "weeks=6", "months=", "moon=1"] >+ ) >+ ) >+ self.assertEquals( >+ ["date-spec", "foo", "hours=14"], >+ self.preprocessor.run(["date-spec", "foo", "hours=14"]) >+ ) >+ self.assertEquals( >+ ["date-spec", "hours=14", "foo", "hours=14"], >+ self.preprocessor.run(["date-spec", "hours=14", "foo", "hours=14"]) >+ ) >+ self.assertEquals( >+ [ >+ "date-spec", >+ "hours=1 monthdays=2 weekdays=3 yeardays=4 months=5 " >+ "weeks=6 years=7 weekyears=8 moon=9" >+ ], >+ self.preprocessor.run([ >+ "date-spec", >+ "hours=1", "monthdays=2", "weekdays=3", "yeardays=4", >+ "months=5","weeks=6", "years=7", "weekyears=8", "moon=9" >+ ]) >+ ) >+ self.assertEquals( >+ ["#uname", "eq", "node1", "or", "date-spec", "hours=14"], >+ self.preprocessor.run([ >+ "#uname", "eq", "node1", "or", "date-spec", "hours=14" >+ ]) >+ ) >+ self.assertEquals( >+ ["date-spec", "hours=14", "or", "#uname", "eq", "node1"], >+ self.preprocessor.run([ >+ "date-spec", "hours=14", "or", "#uname", "eq", "node1", >+ ]) >+ ) >+ >+ def testDuration(self): >+ self.assertEquals( >+ ["duration"], >+ self.preprocessor.run(["duration"]) >+ ) >+ self.assertEquals( >+ ["duration", "hours=14"], >+ self.preprocessor.run(["duration", "hours=14"]) >+ ) >+ self.assertEquals( >+ ["duration", "hours weeks=6 months= moon=1"], >+ self.preprocessor.run( >+ ["duration", "hours", "weeks=6", "months=", "moon=1"] >+ ) >+ ) >+ self.assertEquals( >+ ["duration", "foo", "hours=14"], >+ self.preprocessor.run(["duration", "foo", "hours=14"]) >+ ) >+ self.assertEquals( >+ ["duration", "hours=14", "foo", "hours=14"], >+ self.preprocessor.run(["duration", "hours=14", "foo", "hours=14"]) >+ ) >+ self.assertEquals( >+ [ >+ "duration", >+ "hours=1 monthdays=2 weekdays=3 yeardays=4 months=5 " >+ "weeks=6 years=7 weekyears=8 moon=9" >+ ], >+ self.preprocessor.run([ >+ "duration", >+ "hours=1", "monthdays=2", "weekdays=3", "yeardays=4", >+ "months=5","weeks=6", "years=7", "weekyears=8", "moon=9" >+ ]) >+ ) >+ self.assertEquals( >+ ["#uname", "eq", "node1", "or", "duration", "hours=14"], >+ self.preprocessor.run([ >+ "#uname", "eq", "node1", "or", "duration", "hours=14" >+ ]) >+ ) >+ self.assertEquals( >+ ["duration", "hours=14", "or", "#uname", "eq", "node1"], >+ self.preprocessor.run([ >+ "duration", "hours=14", "or", "#uname", "eq", "node1", >+ ]) >+ ) >+ >+ def testOperationDatespec(self): >+ self.assertEquals( >+ ["date-spec", "weeks=6 moon=1"], >+ self.preprocessor.run( >+ ["date-spec", "operation=date_spec", "weeks=6", "moon=1"] >+ ) >+ ) >+ self.assertEquals( >+ ["date-spec", "weeks=6 moon=1"], >+ self.preprocessor.run( >+ ["date-spec", "weeks=6", "operation=date_spec", "moon=1"] >+ ) >+ ) >+ self.assertEquals( >+ ["date-spec", "weeks=6", "foo", "moon=1"], >+ self.preprocessor.run( >+ ["date-spec", "weeks=6", "operation=date_spec", "foo", "moon=1"] >+ ) >+ ) >+ self.assertEquals( >+ ["date-spec", "weeks=6", "foo", "operation=date_spec", "moon=1"], >+ self.preprocessor.run( >+ ["date-spec", "weeks=6", "foo", "operation=date_spec", "moon=1"] >+ ) >+ ) >+ self.assertEquals( >+ ["date-spec", "weeks=6 moon=1"], >+ self.preprocessor.run( >+ ["date-spec", "weeks=6", "moon=1", "operation=date_spec"] >+ ) >+ ) >+ self.assertEquals( >+ ["date-spec", "weeks=6 moon=1", "foo"], >+ self.preprocessor.run( >+ ["date-spec", "weeks=6", "moon=1", "operation=date_spec", "foo"] >+ ) >+ ) >+ self.assertEquals( >+ ["date-spec"], >+ self.preprocessor.run( >+ ["date-spec", "operation=date_spec"] >+ ) >+ ) >+ self.assertEquals( >+ ["date-spec", "weeks=6", "operation=foo", "moon=1"], >+ self.preprocessor.run( >+ ["date-spec", "weeks=6", "operation=foo", "moon=1"] >+ ) >+ ) >+ >+ def testDateLegacySyntax(self): >+ # valid syntax >+ self.assertEquals( >+ ["date", "gt", "2014-06-26"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "gt" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "lt", "2014-06-26"], >+ self.preprocessor.run([ >+ "date", "end=2014-06-26", "lt" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "in_range", "2014-06-26", "to", "2014-07-26"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "end=2014-07-26", "in_range" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "in_range", "2014-06-26", "to", "2014-07-26"], >+ self.preprocessor.run([ >+ "date", "end=2014-07-26", "start=2014-06-26", "in_range" >+ ]) >+ ) >+ >+ self.assertEquals( >+ ["date", "gt", "2014-06-26", "foo"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "gt", "foo" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "lt", "2014-06-26", "foo"], >+ self.preprocessor.run([ >+ "date", "end=2014-06-26", "lt", "foo" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "in_range", "2014-06-26", "to", "2014-07-26", "foo"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "end=2014-07-26", "in_range", "foo" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "in_range", "2014-06-26", "to", "2014-07-26", "foo"], >+ self.preprocessor.run([ >+ "date", "end=2014-07-26", "start=2014-06-26", "in_range", "foo" >+ ]) >+ ) >+ >+ # invalid syntax - no change >+ self.assertEquals( >+ ["date"], >+ self.preprocessor.run([ >+ "date" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "start=2014-06-26"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "end=2014-06-26"], >+ self.preprocessor.run([ >+ "date", "end=2014-06-26" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "start=2014-06-26", "end=2014-07-26"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "end=2014-07-26" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "start=2014-06-26", "end=2014-07-26", "lt"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "end=2014-07-26", "lt" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "start=2014-06-26", "lt", "foo"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "lt", "foo" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "start=2014-06-26", "end=2014-07-26", "gt", "foo"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "end=2014-07-26", "gt", "foo" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "end=2014-06-26", "gt"], >+ self.preprocessor.run([ >+ "date", "end=2014-06-26", "gt" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "start=2014-06-26", "in_range", "foo"], >+ self.preprocessor.run([ >+ "date", "start=2014-06-26", "in_range", "foo" >+ ]) >+ ) >+ self.assertEquals( >+ ["date", "end=2014-07-26", "in_range"], >+ self.preprocessor.run([ >+ "date", "end=2014-07-26", "in_range" >+ ]) >+ ) >+ self.assertEquals( >+ ["foo", "start=2014-06-26", "gt"], >+ self.preprocessor.run([ >+ "foo", "start=2014-06-26", "gt" >+ ]) >+ ) >+ self.assertEquals( >+ ["foo", "end=2014-06-26", "lt"], >+ self.preprocessor.run([ >+ "foo", "end=2014-06-26", "lt" >+ ]) >+ ) >+ >+ def testParenthesis(self): >+ self.assertEquals( >+ ["("], >+ self.preprocessor.run(["("]) >+ ) >+ self.assertEquals( >+ [")"], >+ self.preprocessor.run([")"]) >+ ) >+ self.assertEquals( >+ ["(", "(", ")", ")"], >+ self.preprocessor.run(["(", "(", ")", ")"]) >+ ) >+ self.assertEquals( >+ ["(", "(", ")", ")"], >+ self.preprocessor.run(["(())"]) >+ ) >+ self.assertEquals( >+ ["a", "(", "b", ")", "c"], >+ self.preprocessor.run(["a", "(", "b", ")", "c"]) >+ ) >+ self.assertEquals( >+ ["a", "(", "b", "c", ")", "d"], >+ self.preprocessor.run(["a", "(", "b", "c", ")", "d"]) >+ ) >+ self.assertEquals( >+ ["a", ")", "b", "(", "c"], >+ self.preprocessor.run(["a", ")", "b", "(", "c"]) >+ ) >+ self.assertEquals( >+ ["a", "(", "b", ")", "c"], >+ self.preprocessor.run(["a", "(b)", "c"]) >+ ) >+ self.assertEquals( >+ ["a", "(", "b", ")", "c"], >+ self.preprocessor.run(["a(", "b", ")c"]) >+ ) >+ self.assertEquals( >+ ["a", "(", "b", ")", "c"], >+ self.preprocessor.run(["a(b)c"]) >+ ) >+ self.assertEquals( >+ ["aA", "(", "bB", ")", "cC"], >+ self.preprocessor.run(["aA(bB)cC"]) >+ ) >+ self.assertEquals( >+ ["(", "aA", "(", "bB", ")", "cC", ")"], >+ self.preprocessor.run(["(aA(bB)cC)"]) >+ ) >+ self.assertEquals( >+ ["(", "aA", "(", "(", "bB", ")", "cC", ")"], >+ self.preprocessor.run(["(aA(", "(bB)cC)"]) >+ ) >+ self.assertEquals( >+ ["(", "aA", "(", "(", "(", "bB", ")", "cC", ")"], >+ self.preprocessor.run(["(aA(", "(", "(bB)cC)"]) >+ ) >+ >+ >+class DomRuleAddTest(unittest.TestCase): >+ >+ def setUp(self): >+ shutil.copy(empty_cib, temp_cib) >+ output, returnVal = pcs(temp_cib, "resource create dummy1 Dummy") >+ assert returnVal == 0 and output == "" >+ >+ def test_success_xml(self): >+ self.assertExpressionXml( >+ ["#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule" score="INFINITY"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["id=myRule", "#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="myRule" score="INFINITY"> >+ <expression attribute="#uname" id="myRule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["score=INFINITY", "#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule" score="INFINITY"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["score=100", "#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule" score="100"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["score-attribute=pingd", "#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule" score-attribute="pingd"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["role=master", "#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule" role="master" score="INFINITY"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["role=slave", "#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="location-dummy-rule" role="slave" score="INFINITY"> >+ <expression attribute="#uname" id="location-dummy-rule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ self.assertExpressionXml( >+ ["score=100", "id=myRule", "role=master", "#uname", "eq", "node1"], >+ """ >+<rsc_location id="location-dummy"> >+ <rule id="myRule" role="master" score="100"> >+ <expression attribute="#uname" id="myRule-expr" operation="eq" value="node1"/> >+ </rule> >+</rsc_location> >+ """ >+ ) >+ >+ def test_success(self): >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule #uname eq node1" >+ ) >+ ac(output, "") >+ self.assertEquals(0, returnVal) >+ >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule id=MyRule score=100 role=master #uname eq node2" >+ ) >+ ac(output, "") >+ self.assertEquals(0, returnVal) >+ >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule id=complexRule (#uname eq node3 and foo gt version 1.2) or (date-spec hours=12-23 weekdays=1-5 and date in_range 2014-07-26 to duration months=1)" >+ ) >+ ac(output, "") >+ self.assertEquals(0, returnVal) >+ >+ output, returnVal = pcs(temp_cib, "constraint location show --full") >+ ac(output, """\ >+Location Constraints: >+ Resource: dummy1 >+ Constraint: location-dummy1 >+ Rule: score=INFINITY (id:location-dummy1-rule) >+ Expression: #uname eq node1 (id:location-dummy1-rule-expr) >+ Constraint: location-dummy1-1 >+ Rule: score=100 role=master (id:MyRule) >+ Expression: #uname eq node2 (id:MyRule-expr) >+ Constraint: location-dummy1-2 >+ Rule: score=INFINITY boolean-op=or (id:complexRule) >+ Rule: score=0 boolean-op=and (id:complexRule-rule) >+ Expression: #uname eq node3 (id:complexRule-rule-expr) >+ Expression: foo gt version 1.2 (id:complexRule-rule-expr-1) >+ Rule: score=0 boolean-op=and (id:complexRule-rule-1) >+ Expression: (id:complexRule-rule-1-expr) >+ Date Spec: hours=12-23 weekdays=1-5 (id:complexRule-rule-1-expr-datespec) >+ Expression: date in_range 2014-07-26 to duration (id:complexRule-rule-1-expr-1) >+ Duration: months=1 (id:complexRule-rule-1-expr-1-duration) >+""") >+ self.assertEquals(0, returnVal) >+ >+ output, returnVal = pcs(temp_cib, "constraint location show") >+ ac(output, """\ >+Location Constraints: >+ Resource: dummy1 >+ Constraint: location-dummy1 >+ Rule: score=INFINITY >+ Expression: #uname eq node1 >+ Constraint: location-dummy1-1 >+ Rule: score=100 role=master >+ Expression: #uname eq node2 >+ Constraint: location-dummy1-2 >+ Rule: score=INFINITY boolean-op=or >+ Rule: score=0 boolean-op=and >+ Expression: #uname eq node3 >+ Expression: foo gt version 1.2 >+ Rule: score=0 boolean-op=and >+ Expression: >+ Date Spec: hours=12-23 weekdays=1-5 >+ Expression: date in_range 2014-07-26 to duration >+ Duration: months=1 >+""") >+ self.assertEquals(0, returnVal) >+ >+ def test_invalid_score(self): >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule score=pingd defined pingd" >+ ) >+ ac( >+ output, >+ "Warning: invalid score 'pingd', setting score-attribute=pingd " >+ "instead\n" >+ ) >+ self.assertEquals(0, returnVal) >+ >+ output, returnVal = pcs(temp_cib, "constraint location show --full") >+ ac(output, """\ >+Location Constraints: >+ Resource: dummy1 >+ Constraint: location-dummy1 >+ Rule: score-attribute=pingd (id:location-dummy1-rule) >+ Expression: defined pingd (id:location-dummy1-rule-expr) >+""") >+ self.assertEquals(0, returnVal) >+ >+ def test_invalid_rule(self): >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule score=100" >+ ) >+ ac(output, "Error: no rule expression was specified\n") >+ self.assertEquals(1, returnVal) >+ >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule #uname eq" >+ ) >+ ac( >+ output, >+ "Error: '#uname eq' is not a valid rule expression: unexpected end " >+ "of rule\n" >+ ) >+ self.assertEquals(1, returnVal) >+ >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule string #uname eq node1" >+ ) >+ ac( >+ output, >+ "Error: 'string #uname eq node1' is not a valid rule expression: " >+ "unexpected 'string' before 'eq'\n" >+ ) >+ self.assertEquals(1, returnVal) >+ >+ def test_ivalid_options(self): >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule role=foo #uname eq node1" >+ ) >+ ac(output, "Error: invalid role 'foo', use 'master' or 'slave'\n") >+ self.assertEquals(1, returnVal) >+ >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule score=100 score-attribute=pingd #uname eq node1" >+ ) >+ ac(output, "Error: can not specify both score and score-attribute\n") >+ self.assertEquals(1, returnVal) >+ >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule id=1foo #uname eq node1" >+ ) >+ ac( >+ output, >+ "Error: invalid rule id '1foo', '1' is not a valid first character " >+ "for a rule id\n" >+ ) >+ self.assertEquals(1, returnVal) >+ >+ output, returnVal = pcs(temp_cib, "constraint location show --full") >+ ac(output, "Location Constraints:\n") >+ self.assertEquals(0, returnVal) >+ >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule id=MyRule #uname eq node1" >+ ) >+ ac(output, "") >+ self.assertEquals(0, returnVal) >+ >+ output, returnVal = pcs(temp_cib, "constraint location show --full") >+ ac(output, """\ >+Location Constraints: >+ Resource: dummy1 >+ Constraint: location-dummy1 >+ Rule: score=INFINITY (id:MyRule) >+ Expression: #uname eq node1 (id:MyRule-expr) >+""") >+ self.assertEquals(0, returnVal) >+ >+ output, returnVal = pcs( >+ temp_cib, >+ "constraint location dummy1 rule id=MyRule #uname eq node1" >+ ) >+ ac( >+ output, >+ "Error: id 'MyRule' is already in use, please specify another one\n" >+ ) >+ self.assertEquals(1, returnVal) >+ >+ def assertExpressionXml(self, rule_expression, rule_xml): >+ cib_dom = xml.dom.minidom.parse("empty.xml") >+ constraints = cib_dom.getElementsByTagName("constraints")[0] >+ constraint_el = constraints.appendChild( >+ cib_dom.createElement("rsc_location") >+ ) >+ constraint_el.setAttribute("id", "location-dummy") >+ rule.dom_rule_add(constraint_el, rule_expression) >+ ac( >+ constraint_el.toprettyxml(indent=" "), >+ rule_xml.lstrip().rstrip(" ") >+ ) >+ >+ >+if __name__ == "__main__": >+ unittest.main() >+ >diff --git a/pcs/test/test_utils.py b/pcs/test/test_utils.py >index 64d872c..fe38182 100644 >--- a/pcs/test/test_utils.py >+++ b/pcs/test/test_utils.py >@@ -7,17 +7,11 @@ sys.path.insert(0, parentdir) > import utils > > class UtilsTest(unittest.TestCase): >- def testDomGetResources(self): >- def assert_element_id(node, node_id): >- self.assertTrue( >- isinstance(node, xml.dom.minidom.Element), >- "element with id '%s' not found" % node_id >- ) >- self.assertEquals(node.getAttribute("id"), node_id) > >+ def testDomGetResources(self): > def test_dom_get(method, dom, ok_ids, bad_ids): > for element_id in ok_ids: >- assert_element_id(method(dom, element_id), element_id) >+ self.assert_element_id(method(dom, element_id), element_id) > for element_id in bad_ids: > self.assertFalse(method(dom, element_id)) > >@@ -130,23 +124,66 @@ class UtilsTest(unittest.TestCase): > ) > > >- assert_element_id( >+ self.assert_element_id( > utils.dom_get_clone_ms_resource(cib_dom, "myClone"), > "myClonedResource" > ) >- assert_element_id( >+ self.assert_element_id( > utils.dom_get_clone_ms_resource(cib_dom, "myGroupClone"), > "myClonedGroup" > ) >- assert_element_id( >+ self.assert_element_id( > utils.dom_get_clone_ms_resource(cib_dom, "myMaster"), > "myMasteredResource" > ) >- assert_element_id( >+ self.assert_element_id( > utils.dom_get_clone_ms_resource(cib_dom, "myGroupMaster"), > "myMasteredGroup" > ) > >+ def testGetElementWithId(self): >+ dom = xml.dom.minidom.parseString(""" >+ <aa> >+ <bb id="bb1"/> >+ <bb/> >+ <bb id="bb2"> >+ <cc id="cc1"/> >+ </bb> >+ <bb id="bb3"> >+ <cc id="cc2"/> >+ </bb> >+ </aa> >+ """).documentElement >+ >+ self.assert_element_id( >+ utils.dom_get_element_with_id(dom, "bb", "bb1"), "bb1" >+ ) >+ self.assert_element_id( >+ utils.dom_get_element_with_id(dom, "bb", "bb2"), "bb2" >+ ) >+ self.assert_element_id( >+ utils.dom_get_element_with_id(dom, "cc", "cc1"), "cc1" >+ ) >+ self.assert_element_id( >+ utils.dom_get_element_with_id( >+ utils.dom_get_element_with_id(dom, "bb", "bb2"), >+ "cc", >+ "cc1" >+ ), >+ "cc1" >+ ) >+ self.assertEquals(None, utils.dom_get_element_with_id(dom, "dd", "bb1")) >+ self.assertEquals(None, utils.dom_get_element_with_id(dom, "bb", "bb4")) >+ self.assertEquals(None, utils.dom_get_element_with_id(dom, "bb", "cc1")) >+ self.assertEquals( >+ None, >+ utils.dom_get_element_with_id( >+ utils.dom_get_element_with_id(dom, "bb", "bb2"), >+ "cc", >+ "cc2" >+ ) >+ ) >+ > def testValidateXmlId(self): > self.assertEquals((True, ""), utils.validate_xml_id("dummy")) > self.assertEquals((True, ""), utils.validate_xml_id("DUMMY")) >@@ -218,5 +255,27 @@ class UtilsTest(unittest.TestCase): > utils.validate_xml_id("dummy?", "test id") > ) > >+ def testIsIso8601Date(self): >+ self.assertTrue(utils.is_iso8601_date("2014-07-03")) >+ self.assertTrue(utils.is_iso8601_date("2014-07-03T11:35:14")) >+ self.assertTrue(utils.is_iso8601_date("20140703")) >+ self.assertTrue(utils.is_iso8601_date("2014-W27-4")) >+ self.assertTrue(utils.is_iso8601_date("2014-184")) >+ >+ self.assertFalse(utils.is_iso8601_date("")) >+ self.assertFalse(utils.is_iso8601_date("foo")) >+ self.assertFalse(utils.is_iso8601_date("2014-07-32")) >+ self.assertFalse(utils.is_iso8601_date("2014-13-03")) >+ self.assertFalse(utils.is_iso8601_date("2014-W27-8")) >+ self.assertFalse(utils.is_iso8601_date("2014-367")) >+ >+ def assert_element_id(self, node, node_id): >+ self.assertTrue( >+ isinstance(node, xml.dom.minidom.Element), >+ "element with id '%s' not found" % node_id >+ ) >+ self.assertEquals(node.getAttribute("id"), node_id) >+ >+ > if __name__ == "__main__": > unittest.main() >diff --git a/pcs/usage.py b/pcs/usage.py >index 07a2cbd..8f80949 100644 >--- a/pcs/usage.py >+++ b/pcs/usage.py >@@ -742,14 +742,22 @@ Commands: > Create a location constraint on a resource to avoid the specified > node and score (default score: INFINITY) > >- location <resource id> rule [role=master|slave] [score=<score>] <expression> >+ location <resource id> rule [id=<rule id>] [role=master|slave] >+ [score=<score>|score-attribute=<attribute>] <expression> > Creates a location rule on the specified resource where the expression > looks like one of the following: > defined|not_defined <attribute> >- <attribute> lt|gt|lte|gte|eq|ne <value> >- date [start=<start>] [end=<end>] operation=gt|lt|in-range >+ <attribute> lt|gt|lte|gte|eq|ne [string|integer|version] <value> >+ date gt|lt <date> >+ date in_range <date> to <date> >+ date in_range <date> to duration <duration options>... > date-spec <date spec options>... > <expression> and|or <expression> >+ ( <expression> ) >+ where duration options and date spec options are: hours, monthdays, >+ weekdays, yeardays, months, weeks, years, weekyears, moon >+ If score is ommited it defaults to INFINITY. If id is ommited one is >+ generated from the resource id. > > location show [resources|nodes [node id|resource id]...] [--full] > List all the current location constraints, if 'resources' is specified >@@ -819,11 +827,22 @@ Commands: > ref <resource>... > List constraints referencing specified resource > >- rule add <constraint id> [<rule type>] [score=<score>] [id=<rule id>] >- <expression|date_expression|date_spec>... >- Add a rule to a constraint, if score is omitted it defaults to >- INFINITY, if id is omitted one is generated from the constraint id. >- The <rule type> should be 'expression' or 'date_expression' >+ rule add <constraint id> [id=<rule id>] [role=master|slave] >+ [score=<score>|score-attribute=<attribute>] <expression> >+ Add a rule to a constraint where the expression looks like one of >+ the following: >+ defined|not_defined <attribute> >+ <attribute> lt|gt|lte|gte|eq|ne [string|integer|version] <value> >+ date gt|lt <date> >+ date in_range <date> to <date> >+ date in_range <date> to duration <duration options>... >+ date-spec <date spec options>... >+ <expression> and|or <expression> >+ ( <expression> ) >+ where duration options and date spec options are: hours, monthdays, >+ weekdays, yeardays, months, weeks, years, weekyears, moon >+ If score is ommited it defaults to INFINITY. If id is ommited one is >+ generated from the constraint id. > > rule remove <rule id> > Remove a rule if a rule id is specified, if rule is last rule in its >diff --git a/pcs/utils.py b/pcs/utils.py >index fd80ae3..b0d1e19 100644 >--- a/pcs/utils.py >+++ b/pcs/utils.py >@@ -589,7 +589,7 @@ def run(args, ignore_stderr=False, string_for_stdin=None): > err("Unable to write to file: " + filename) > > command = args[0] >- if command[0:3] == "crm" or command == "cibadmin" or command == "cman_tool": >+ if command[0:3] == "crm" or command in ["cibadmin", "cman_tool", "iso8601"]: > args[0] = settings.pacemaker_binaries + command > if command[0:8] == "corosync": > args[0] = settings.corosync_binaries + command >@@ -772,6 +772,12 @@ def is_valid_constraint_resource(resource_id): > does_exist("//clone[@id='"+resource_id+"']") or \ > does_exist("//master[@id='"+resource_id+"']") > >+def dom_get_element_with_id(dom, tag_name, element_id): >+ for elem in dom.getElementsByTagName(tag_name): >+ if elem.hasAttribute("id") and elem.getAttribute("id") == element_id: >+ return elem >+ return None >+ > # Check if resoure is started (or stopped) for 'wait' seconds > def is_resource_started(resource,wait,stopped=False): > expire_time = int(time.time()) + wait >@@ -979,134 +985,6 @@ def does_id_exist(dom, check_id): > return True > return False > >-# Adds the specified rule to the element in the dom >-def rule_add(elem, argv): >-# Check if valid rule argv >- rule_type = "expression" >- if len(argv) != 0: >- if argv[0] == "date": >- rule_type = "date_expression" >- >- if rule_type != "expression" and rule_type != "date_expression": >- err("rule_type must either be expression or date_expression") >- >- args = resource.convert_args_to_tuples(argv) >- dict_args = dict() >- for k,v in args: >- dict_args[k] = v >-# if rule_type == "expression": >-# if "operation" not in dict_args or "attribute" not in dict_args: >-# err("with rule_type: expression you must specify an attribute and operation") >-# elif rule_type == "date_expression": >-# if "operation" not in dict_args or ("start" not in dict_args and "stop" not in dict_args): >-# err("with rule_type: date_expression you must specify an operation and a start/end") >- >- >- exp_arg = [] >- for arg in argv: >- if arg.find('=') == -1: >- exp_arg.append(arg) >- if len(exp_arg) == 0: >- err("no rule expression was specified") >- >- rule_boolean = "" >- if ("or" in exp_arg and "and" not in exp_arg): >- rule_boolean = "or" >- elif ("or" not in exp_arg and "and" in exp_arg): >- rule_boolean = "and" >- >- if not ((exp_arg[0] in ["defined","not_defined", "date", "date-spec"]) or (len(exp_arg) >= 2 and exp_arg[1] in ["lt","gt","lte","gte","eq","ne"])): >- err("'%s' is not a valid rule expression" % " ".join(exp_arg)) >- >- date_spec = False >- >- new_exp_arg = [[]] >- count = 0 >- for item in exp_arg: >- if item != "or" and item != "and": >- new_exp_arg[count].append(item) >- else: >- new_exp_arg.append([]) >- count = count + 1 >- >- for exp_arg in new_exp_arg: >- if len(exp_arg) >= 1: >- if exp_arg[0] == "date": >- args.append(("operation",exp_arg[1])) >- rule_type = "date_expression" >- elif exp_arg[0] == "date-spec": >- args.append(("operation","date_spec")) >- rule_type = "date_expression" >- date_spec = True >- elif len(exp_arg) >= 2 and exp_arg[1] in ["lt","gt","lte","gte","eq","ne"] and len(exp_arg) >= 3: >- args.append(("attribute",exp_arg[0])) >- args.append(("operation",exp_arg[1])) >- args.append(("value",exp_arg[2])) >- elif exp_arg[0] in ["defined","not_defined"]: >- args.append(("attribute",exp_arg[1])) >- args.append(("operation",exp_arg[0])) >- if rule_boolean != "": >- args.append(rule_boolean) >- if args[-1] == "or" or args[-1] == "and": >- args.pop(-1) >- >- rule = ET.SubElement(elem,"rule") >- expression = ET.SubElement(rule,rule_type) >- if date_spec: >- subexpression = ET.SubElement(expression,"date_spec") >- >- >- for arg in args: >- if arg == "or" or arg == "and": >- expression = ET.SubElement(rule,rule_type) >- if date_spec: >- subexpression = ET.SubElement(expression,"date_spec") >- continue >- if arg[0] == "id": >- id_valid, id_error = validate_xml_id(arg[1], 'rule id') >- if not id_valid: >- err(id_error) >- rule.set(arg[0], arg[1]) >- elif arg[0] == "score": >- if is_score_or_opt(arg[1]): >- rule.set(arg[0], arg[1]) >- else: >- rule.set("score-attribute","pingd") >- elif arg[0] == "role": >- rule.set(arg[0], arg[1]) >- else: >- if date_spec: >- if arg[0] == "operation": >- expression.set(arg[0],arg[1]) >- else: >- subexpression.set(arg[0],arg[1]) >- else: >- expression.set(arg[0],arg[1]) >- >- if rule.get("score") == None and rule.get("score-attribute") == None: >- rule.set("score", "INFINITY") >- >- if rule_boolean != "": >- rule.set("boolean-op", rule_boolean) >- >- dom = get_cib_dom() >- if rule.get("id") == None: >- rule.set("id", find_unique_id(dom,elem.get("id") + "-rule")) >- >- count = 1 >- for exp_child in rule: >- if exp_child.get("id") == None: >- exp_child.set("id", find_unique_id(dom,rule.get("id") + "-expr-"+str(count))) >- count = count + 1 >- if date_spec and subexpression.get("id") == None: >- subexpression.set("id", find_unique_id(dom, expression.get("id")+"-datespec")) >- if "score" in elem.attrib: >- del elem.attrib["score"] >- if "node" in elem.attrib: >- del elem.attrib["node"] >- return elem >- >- > # Returns check_id if it doesn't exist in the dom, otherwise it adds an integer > # to the end of the id and increments it until a unique id is found > def find_unique_id(dom, check_id): >@@ -1238,52 +1116,6 @@ def setAttribute(a_type, a_name, a_value): > if retval != 0: > print output > >-def getExpression(dom, element, argv, id_suffix=""): >- if len(argv) < 2: >- return None >- >- unary_expression = False >- if len(argv) == 2 and (argv[0] == "defined" or argv[0] == "not_defined"): >- expression = dom.createElement("expression") >- unary_expression = True >- expression.setAttribute("operation", argv[0]) >- expression.setAttribute("attribute",argv[1]) >- expression.setAttribute("id", find_unique_id (dom, element.getAttribute("id") + "-expr")) >- return expression >- elif argv[0] == "date": >- expression = dom.createElement("date_expression") >- expression.setAttribute("id", find_unique_id (dom, element.getAttribute("id") + "-rule" + id_suffix)) >- date_expression = True >- argv.pop(0) >- count = 0 >- for i in range(0,len(argv)): >- val = argv[i].split('=') >- expression.setAttribute(val[0], val[1]) >- if val[0] == "operation" and val[1] == "date_spec": >- date_spec = getDateSpec(dom, expression, argv[(i+1):]) >- expression.appendChild(date_spec) >- break >- expression.setAttribute("id", find_unique_id (dom, element.getAttribute("id") + "-dateexpr" + id_suffix)) >- return expression >- elif len(argv) == 3 and argv[1] in ["lt","gt","lte","gte","eq","ne"]: >- expression = dom.createElement("expression") >- expression.setAttribute("attribute", argv[0]) >- expression.setAttribute("operation", argv[1]) >- expression.setAttribute("value", argv[2]) >- expression.setAttribute("id", find_unique_id (dom, element.getAttribute("id") + "-expr" + id_suffix)) >- return expression >- else: >- return None >- >- >-def getDateSpec(dom, element, argv): >- date_spec = dom.createElement("date_spec") >- for val in argv: >- if val.find('=') != -1: >- date_spec.setAttribute(val.split('=')[0],val.split('=')[1]) >- date_spec.setAttribute("id", find_unique_id(dom,element.getAttribute("id") + "-datespec")) >- return date_spec >- > def getTerminalSize(fd=1): > """ > Returns height and width of current terminal. First tries to get >@@ -1498,6 +1330,11 @@ def validate_xml_id(var, description="id"): > ) > return True, "" > >+def is_iso8601_date(var): >+ # using pacemaker tool to check if a value is a valid pacemaker iso8601 date >+ output, retVal = run(["iso8601", "-d", var]) >+ return retVal == 0 >+ > def is_systemctl(): > if os.path.exists('/usr/bin/systemctl'): > return True >-- >1.9.1 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 1104656
: 919059