Login
Log in using an SSO provider:
Fedora Account System
Red Hat Associate
Red Hat Customer
Login using a Red Hat Bugzilla account
Forgot Password
Create an Account
Red Hat Bugzilla – Attachment 1975179 Details for
Bug 2163953
[RFE] Provide means to export configured constraints as pcs commands
Home
New
Search
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.rh89 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]
additional fix
fix-constraint-export.patch (text/plain), 64.39 KB, created by
Tomas Jelinek
on 2023-07-11 15:02:21 UTC
(
hide
)
Description:
additional fix
Filename:
MIME Type:
Creator:
Tomas Jelinek
Created:
2023-07-11 15:02:21 UTC
Size:
64.39 KB
patch
obsolete
>From decc204070e4e717b310cbea0a1271353709c054 Mon Sep 17 00:00:00 2001 >From: Tomas Jelinek <tojeline@redhat.com> >Date: Mon, 10 Jul 2023 14:40:07 +0200 >Subject: [PATCH 1/2] fix exporting location constraints with rules > >--- > CHANGELOG.md | 13 ++ > pcs/cli/constraint/output/location.py | 14 +-- > pcs/lib/cib/rule/cib_to_str.py | 21 +++- > pcs/rule.py | 14 +++ > pcs_test/Makefile.am | 1 + > pcs_test/resources/cib-all.xml | 8 +- > .../cib-rule-with-spaces-in-date.xml | 44 +++++++ > pcs_test/resources/constraint-commands | 7 +- > pcs_test/resources/resource-commands | 7 +- > .../tier0/lib/cib/rule/test_cib_to_str.py | 93 +++++++++++++++ > pcs_test/tier0/lib/cib/rule/test_parser.py | 58 +++++++++ > pcs_test/tier1/constraint/test_config.py | 100 +++++++++++++--- > pcs_test/tier1/legacy/test_constraints.py | 111 ++++++++++++++++++ > pcs_test/tier1/legacy/test_rule.py | 24 ++++ > pcs_test/tools/constraints_dto.py | 44 ++++++- > 15 files changed, 518 insertions(+), 41 deletions(-) > create mode 100644 pcs_test/resources/cib-rule-with-spaces-in-date.xml > >diff --git a/CHANGELOG.md b/CHANGELOG.md >index 0fd34f3c2..dbeab43aa 100644 >--- a/CHANGELOG.md >+++ b/CHANGELOG.md >@@ -1,5 +1,18 @@ > # Change Log > >+## [Unreleased] >+ >+### Fixed >+- Exporting constraints with rules in form of pcs commands now escapes `#` and >+ fixes spaces in dates to make the commands valid ([rhbz#2163953]) >+ >+### Deprecated >+- Using spaces in dates in location constraint rules (using spaces in dates in >+ rules in other parts of configuration was never allowed) ([rhbz#2163953]) >+ >+[rhbz#2163953]: https://bugzilla.redhat.com/show_bug.cgi?id=2163953 >+ >+ > ## [0.11.6] - 2023-06-20 > > ### Added >diff --git a/pcs/cli/constraint/output/location.py b/pcs/cli/constraint/output/location.py >index 2713b7d0d..e289fc108 100644 >--- a/pcs/cli/constraint/output/location.py >+++ b/pcs/cli/constraint/output/location.py >@@ -1,5 +1,5 @@ >+import shlex > from collections import defaultdict >-from shlex import quote > from typing import ( > Callable, > Iterable, >@@ -149,7 +149,7 @@ def _plain_constraint_get_resource_for_cmd( > resource = f"resource%{constraint_dto.resource_id}" > else: > resource = f"regexp%{constraint_dto.resource_pattern}" >- return quote(resource) >+ return shlex.quote(resource) > > > def _plain_constraint_to_cmd( >@@ -157,9 +157,9 @@ def _plain_constraint_to_cmd( > ) -> list[str]: > result = [ > "pcs -- constraint location add {id} {resource} {node} {score}".format( >- id=quote(constraint_dto.attributes.constraint_id), >+ id=shlex.quote(constraint_dto.attributes.constraint_id), > resource=_plain_constraint_get_resource_for_cmd(constraint_dto), >- node=quote(str(constraint_dto.attributes.node)), >+ node=shlex.quote(str(constraint_dto.attributes.node)), > score=constraint_dto.attributes.score, > ) > ] >@@ -185,12 +185,12 @@ def _rule_to_cmd_pairs(rule: CibRuleExpressionDto) -> list[tuple[str, str]]: > > > def _add_rule_cmd(constraint_id: str, rule: CibRuleExpressionDto) -> list[str]: >- result = [f"pcs -- constraint rule add {quote(constraint_id)}"] >+ result = [f"pcs -- constraint rule add {shlex.quote(constraint_id)}"] > result.extend( > indent( > [ > pairs_to_cmd([("id", rule.id)] + _rule_to_cmd_pairs(rule)), >- rule.as_string, >+ shlex.join(shlex.split(rule.as_string)), > ], > indent_step=INDENT_STEP, > ) >@@ -221,7 +221,7 @@ def _plain_constraint_rule_to_cmd( > + _attributes_to_pairs(constraint_dto.attributes) > + _rule_to_cmd_pairs(first_rule) > ), >- first_rule.as_string, >+ shlex.join(shlex.split(first_rule.as_string)), > ], > indent_step=INDENT_STEP, > ) >diff --git a/pcs/lib/cib/rule/cib_to_str.py b/pcs/lib/cib/rule/cib_to_str.py >index b196d8f6b..29b67a8a9 100644 >--- a/pcs/lib/cib/rule/cib_to_str.py >+++ b/pcs/lib/cib/rule/cib_to_str.py >@@ -1,3 +1,4 @@ >+import re > from typing import ( > Dict, > cast, >@@ -17,6 +18,8 @@ class RuleToStr: > Export a rule XML element to a string which creates the same element > """ > >+ _date_separators_re = re.compile(r"\s*([TZ:.+-])\s*") >+ > def __init__(self) -> None: > # The cache prevents evaluating subtrees repeatedly. > self._cache: Dict[str, str] = {} >@@ -43,6 +46,16 @@ class RuleToStr: > ) > ) > >+ @staticmethod >+ def _date_to_str(date: str) -> str: >+ # remove spaces around separators >+ result = re.sub(RuleToStr._date_separators_re, r"\1", date) >+ # if there are any spaces left, replace the first one with T >+ result = re.sub(r"\s+", "T", result, count=1) >+ # keep all other spaces in place >+ # the date wouldn't be valid, but there is nothing more we can do >+ return result >+ > def _rule_to_str(self, rule_el: _Element) -> str: > # "and" is a documented pacemaker default > # https://clusterlabs.org/pacemaker/doc/en-US/Pacemaker/2.0/html-single/Pacemaker_Explained/index.html#_rule_properties >@@ -95,10 +108,10 @@ class RuleToStr: > string_parts.extend(["date", "in_range"]) > # CIB schema allows "start" + "duration" or optional "start" + "end" > if "start" in expr_el.attrib: >- string_parts.append(str(expr_el.get("start", ""))) >+ string_parts.append(self._date_to_str(expr_el.get("start", ""))) > string_parts.append("to") > if "end" in expr_el.attrib: >- string_parts.append(str(expr_el.get("end", ""))) >+ string_parts.append(self._date_to_str(expr_el.get("end", ""))) > if duration is not None: > string_parts.append("duration") > string_parts.append(self._attrs_to_str(duration)) >@@ -107,9 +120,9 @@ class RuleToStr: > # operation=="lt" + "end" > string_parts.extend(["date", str(expr_el.get("operation", ""))]) > if "start" in expr_el.attrib: >- string_parts.append(str(expr_el.get("start", ""))) >+ string_parts.append(self._date_to_str(expr_el.get("start", ""))) > if "end" in expr_el.attrib: >- string_parts.append(str(expr_el.get("end", ""))) >+ string_parts.append(self._date_to_str(expr_el.get("end", ""))) > return " ".join(string_parts) > > def _op_expr_to_str(self, expr_el: _Element) -> str: >diff --git a/pcs/rule.py b/pcs/rule.py >index 7c0e14007..172f1e1eb 100644 >--- a/pcs/rule.py >+++ b/pcs/rule.py >@@ -7,6 +7,7 @@ from typing import ( > ) > > from pcs import utils >+from pcs.cli.reports.output import deprecation_warning > from pcs.common import ( > const, > pacemaker, >@@ -893,6 +894,17 @@ class RuleParser(Parser): > class CibBuilder: > def __init__(self, cib_schema_version): > self.cib_schema_version = cib_schema_version >+ self.space_deprecation_printed = False >+ >+ # deprecated since pcs-0.11.7 >+ def date_space_deprecation(self, date): >+ if self.space_deprecation_printed or " " not in date: >+ return >+ self.space_deprecation_printed = True >+ deprecation_warning( >+ "Using spaces in date values is deprecated and will be removed. " >+ "Use 'T' as a delimiter between date and time." >+ ) > > def build(self, dom_element, syntactic_tree, rule_id=None): > dom_rule = self.add_element( >@@ -978,6 +990,7 @@ class CibBuilder: > "'%s' is not an ISO 8601 date" > % syntactic_tree.children[1].value > ) >+ self.date_space_deprecation(syntactic_tree.children[1].value) > dom_expression.setAttribute("operation", syntactic_tree.symbol_id) > if syntactic_tree.symbol_id == "gt": > dom_expression.setAttribute( >@@ -1008,6 +1021,7 @@ class CibBuilder: > "'%s' is not an ISO 8601 date" > % syntactic_tree.children[2].value > ) >+ self.date_space_deprecation(syntactic_tree.children[2].value) > dom_expression.setAttribute( > "end", syntactic_tree.children[2].value > ) >diff --git a/pcs_test/Makefile.am b/pcs_test/Makefile.am >index 738f66224..c1ba2e425 100644 >--- a/pcs_test/Makefile.am >+++ b/pcs_test/Makefile.am >@@ -20,6 +20,7 @@ EXTRA_DIST = \ > resources/cib-property.xml \ > resources/cib-resources.xml \ > resources/cib-all.xml \ >+ resources/cib-rule-with-spaces-in-date.xml \ > resources/cib-tags.xml \ > resources/controld_metadata.xml \ > resources/corosync-3nodes.conf \ >diff --git a/pcs_test/resources/cib-all.xml b/pcs_test/resources/cib-all.xml >index a44a546b4..b738d7e67 100644 >--- a/pcs_test/resources/cib-all.xml >+++ b/pcs_test/resources/cib-all.xml >@@ -135,11 +135,13 @@ > </rule> > </rsc_location> > <rsc_location id="loc_constr_with_not_expired_rule" rsc="R6-clone"> >- <rule id="loc_constr_with_not_expired_rule-rule" score="500" role="Unpromoted"> >- <date_expression id="loc_constr_with_not_expired_rule-rule-expr" operation="gt" start="2000-01-01"/> >+ <rule id="loc_constr_with_not_expired_rule-rule" boolean-op="and" score="500" role="Unpromoted"> >+ <expression id="loc_constr_with_not_expired_rule-rule-expr" operation="eq" attribute="#uname" value="node1"/> >+ <date_expression id="loc_constr_with_not_expired_rule-rule-expr-1" operation="gt" start="2000-01-01"/> > </rule> >- <rule id="loc_constr_with_not_expired_rule-rule-1" role="Promoted" score-attribute="test-attr"> >+ <rule id="loc_constr_with_not_expired_rule-rule-1" boolean-op="and" role="Promoted" score-attribute="test-attr"> > <date_expression id="loc_constr_with_not_expired_rule-rule-1-expr" operation="gt" start="2010-12-31"/> >+ <expression id="loc_constr_with_not_expired_rule-rule-1-expr-1" operation="eq" attribute="#uname" value="node1"/> > </rule> > </rsc_location> > <rsc_order first="R7" then="G2" score="-123" require-all="false" first-action="stop" then-action="stop" symmetrical="false" id="order-R7-G2-mandatory"/> >diff --git a/pcs_test/resources/cib-rule-with-spaces-in-date.xml b/pcs_test/resources/cib-rule-with-spaces-in-date.xml >new file mode 100644 >index 000000000..a68a12873 >--- /dev/null >+++ b/pcs_test/resources/cib-rule-with-spaces-in-date.xml >@@ -0,0 +1,44 @@ >+<cib crm_feature_set="3.17.4" validate-with="pacemaker-3.9" epoch="246" num_updates="0" admin_epoch="0" cib-last-written="Mon Jul 3 14:47:48 2023" update-origin="rh92-node1" update-client="cibadmin" update-user="root" have-quorum="1" dc-uuid="1"> >+ <configuration> >+ <crm_config> >+ <cluster_property_set id="cib-bootstrap-options"> >+ <nvpair id="cib-bootstrap-options-have-watchdog" name="have-watchdog" value="false"/> >+ <nvpair id="cib-bootstrap-options-dc-version" name="dc-version" value="2.1.6-3.el9-6fdc9deea29"/> >+ <nvpair id="cib-bootstrap-options-cluster-infrastructure" name="cluster-infrastructure" value="corosync"/> >+ <nvpair id="cib-bootstrap-options-cluster-name" name="cluster-name" value="rh92"/> >+ </cluster_property_set> >+ </crm_config> >+ <nodes> >+ <node id="1" uname="node1"/> >+ </nodes> >+ <!-- >+ commands used to create this CIB: >+ pcs resource create R1 ocf:pacemaker:Dummy; >+ pcs constraint location R1 rule \#uname eq node1 and date gt '2023-01-01 12:00' and date lt '2023-12-31 12:00' and date in_range '2023-01-01 12:00' to '2023-12-31 12:00'; >+ pcs constraint rule add location-R1 \#uname eq node1 and date gt '2023-01-01 12:00' and date lt '2023-12-31 12:00' and date in_range '2023-01-01 12:00' to '2023-12-31 12:00'; >+ --> >+ <resources> >+ <primitive id="R1" class="ocf" type="Dummy" provider="pacemaker"> >+ <operations> >+ <op name="monitor" interval="10s" timeout="20s" id="R1-monitor-interval-10s"/> >+ </operations> >+ </primitive> >+ </resources> >+ <constraints> >+ <rsc_location id="location-R1" rsc="R1"> >+ <rule id="location-R1-rule" boolean-op="and" score="INFINITY"> >+ <expression id="location-R1-rule-expr" operation="eq" attribute="#uname" value="node1"/> >+ <date_expression id="location-R1-rule-expr-1" operation="gt" start="2023-01-01 12:00"/> >+ <date_expression id="location-R1-rule-expr-2" operation="lt" end="2023-12-31 12:00"/> >+ <date_expression id="location-R1-rule-expr-3" operation="in_range" start="2023-01-01 12:00" end="2023-12-31 12:00"/> >+ </rule> >+ <rule id="location-R1-rule-1" boolean-op="and" score="INFINITY"> >+ <expression id="location-R1-rule-1-expr" operation="eq" attribute="#uname" value="node1"/> >+ <date_expression id="location-R1-rule-1-expr-1" operation="gt" start="2023-01-01 12:00"/> >+ <date_expression id="location-R1-rule-1-expr-2" operation="lt" end="2023-12-31 12:00"/> >+ <date_expression id="location-R1-rule-1-expr-3" operation="in_range" start="2023-01-01 12:00" end="2023-12-31 12:00"/> >+ </rule> >+ </rsc_location> >+ </constraints> >+ </configuration> >+</cib> >diff --git a/pcs_test/resources/constraint-commands b/pcs_test/resources/constraint-commands >index 096bdec06..759e71463 100644 >--- a/pcs_test/resources/constraint-commands >+++ b/pcs_test/resources/constraint-commands >@@ -5,15 +5,12 @@ pcs -- constraint location add location-R7-localhost-INFINITY resource%R7 localh > resource-discovery=always; > pcs -- constraint location add location-G2-localhost-INFINITY resource%G2 localhost INFINITY; > pcs -- constraint location add location-R-localhost-INFINITY 'regexp%R*' localhost INFINITY; >-pcs -- constraint location resource%B2 rule \ >- id=loc_constr_with_expired_rule-rule constraint-id=loc_constr_with_expired_rule score=500 \ >- date lt 2000-01-01; > pcs -- constraint location resource%R6-clone rule \ > id=loc_constr_with_not_expired_rule-rule constraint-id=loc_constr_with_not_expired_rule role=Unpromoted score=500 \ >- date gt 2000-01-01; >+ '#uname' eq node1 and date gt 2000-01-01; > pcs -- constraint rule add loc_constr_with_not_expired_rule \ > id=loc_constr_with_not_expired_rule-rule-1 role=Promoted score-attribute=test-attr \ >- date gt 2010-12-31; >+ date gt 2010-12-31 and '#uname' eq node1; > pcs -- constraint colocation add Promoted G1-clone with Stopped R6-clone -100 \ > id=colocation-G1-clone-R6-clone--100; > pcs -- constraint colocation \ >diff --git a/pcs_test/resources/resource-commands b/pcs_test/resources/resource-commands >index 80775e7e7..296d279c8 100644 >--- a/pcs_test/resources/resource-commands >+++ b/pcs_test/resources/resource-commands >@@ -15,14 +15,15 @@ pcs resource bundle create B2 \ > container docker \ > image=pcs:test; > pcs resource create R1 ocf:pacemaker:Dummy --no-default-ops bundle B2 --force; >-pcs resource create R2 ocf:pacemaker:Dummy --no-default-ops; >-pcs resource create R3 ocf:pacemaker:Dummy --no-default-ops; >-pcs resource create R4 ocf:pacemaker:Dummy --no-default-ops; >+pcs resource create R2 ocf:pacemaker:Stateful --no-default-ops; >+pcs resource create R3 ocf:pacemaker:Stateful --no-default-ops; >+pcs resource create R4 ocf:pacemaker:Stateful --no-default-ops; > pcs resource create R5 ocf:pacemaker:Dummy --no-default-ops; > pcs resource create R6 ocf:pacemaker:Dummy; > pcs resource create R7 ocf:pacemaker:Dummy --force \ > fake=looool envfile=/dev/null \ > op custom_action interval=10s OCF_CHECK_LEVEL=2 \ >+ migrate_to interval=0s id=R7-migrate_to-interval-0s timeout=20s enabled=0 record-pending=0 \ > meta m1=value1 meta2=valueofmeta2isthisverylongstring "anotherone=something'\"special" m10=value1 meta20=valueofmeta2isthisverylongstring "another one0=a + b = c"; > pcs stonith create S1 fence_kdump nodename=testnodename; > pcs stonith create S2 fence_kdump; >diff --git a/pcs_test/tier0/lib/cib/rule/test_cib_to_str.py b/pcs_test/tier0/lib/cib/rule/test_cib_to_str.py >index eaef7d4b8..4d93628a3 100644 >--- a/pcs_test/tier0/lib/cib/rule/test_cib_to_str.py >+++ b/pcs_test/tier0/lib/cib/rule/test_cib_to_str.py >@@ -5,3 +5,96 @@ > # pcs_test/tier0/lib/commands/test_cib_options.py. > # Therefore we don't duplicate those here. However, if there's a need to write > # specific tests here, feel free to do so. >+ >+ >+from unittest import TestCase >+ >+from pcs.lib.cib.rule.cib_to_str import RuleToStr >+ >+ >+class IsoToStr(TestCase): >+ # pylint: disable=protected-access >+ def test_no_change(self): >+ self.assertEqual(RuleToStr._date_to_str("2023-06"), "2023-06") >+ self.assertEqual(RuleToStr._date_to_str("202306"), "202306") >+ self.assertEqual(RuleToStr._date_to_str("2023-06-30"), "2023-06-30") >+ self.assertEqual(RuleToStr._date_to_str("20230630"), "20230630") >+ self.assertEqual( >+ RuleToStr._date_to_str("2023-06-30T16:30"), "2023-06-30T16:30" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630T1630"), "20230630T1630" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("2023-06-30T16:30Z"), "2023-06-30T16:30Z" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630T1630+2"), "20230630T1630+2" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("2023-06-30T16:30:40+2:00"), >+ "2023-06-30T16:30:40+2:00", >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630T1630+02:00"), "20230630T1630+02:00" >+ ) >+ >+ def test_remove_spaces(self): >+ self.assertEqual(RuleToStr._date_to_str("- 2023"), "-2023") >+ self.assertEqual(RuleToStr._date_to_str("+ 2023"), "+2023") >+ self.assertEqual(RuleToStr._date_to_str("2023- 06"), "2023-06") >+ self.assertEqual(RuleToStr._date_to_str("2023 -06- 30"), "2023-06-30") >+ self.assertEqual( >+ RuleToStr._date_to_str("2023-06-30 T16:30"), "2023-06-30T16:30" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630T 1630"), "20230630T1630" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("2023-06-30 T 16:30 Z"), "2023-06-30T16:30Z" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630 T 1630 + 2"), "20230630T1630+2" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str( >+ "2023 - 06 - 30 T 16 : 30 : 40 + 2: 00" >+ ), >+ "2023-06-30T16:30:40+2:00", >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630 T 1630+ 02:00"), >+ "20230630T1630+02:00", >+ ) >+ >+ def test_add_time_separator(self): >+ self.assertEqual( >+ RuleToStr._date_to_str("2023-06-30 16:30"), "2023-06-30T16:30" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630 1630"), "20230630T1630" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("2023-06-30 16:30 Z"), "2023-06-30T16:30Z" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630 1630 + 2"), "20230630T1630+2" >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("2023 - 06 - 30 16 : 30 : 40 + 2: 00"), >+ "2023-06-30T16:30:40+2:00", >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("20230630 1630+ 02:00"), >+ "20230630T1630+02:00", >+ ) >+ >+ def test_extra_spaces(self): >+ self.assertEqual( >+ RuleToStr._date_to_str("2023-06-30 16:30:40 +2 00"), >+ "2023-06-30T16:30:40+2 00", >+ ) >+ self.assertEqual( >+ RuleToStr._date_to_str("2023 06 30 16 30 +02"), >+ "2023T06 30 16 30+02", >+ ) >diff --git a/pcs_test/tier0/lib/cib/rule/test_parser.py b/pcs_test/tier0/lib/cib/rule/test_parser.py >index fd089ec86..37ae52f1b 100644 >--- a/pcs_test/tier0/lib/cib/rule/test_parser.py >+++ b/pcs_test/tier0/lib/cib/rule/test_parser.py >@@ -512,6 +512,14 @@ class Parser(TestCase): > DateUnaryExpr operator=GT date=2014-06-26""" > ), > ), >+ ( >+ "date gt 2014-06-26T12:00:00", >+ dedent( >+ """\ >+ BoolExpr AND >+ DateUnaryExpr operator=GT date=2014-06-26T12:00:00""" >+ ), >+ ), > ( > "date lt 2014-06-26", > dedent( >@@ -520,6 +528,14 @@ class Parser(TestCase): > DateUnaryExpr operator=LT date=2014-06-26""" > ), > ), >+ ( >+ "date lt 2014-06-26T12:00:00", >+ dedent( >+ """\ >+ BoolExpr AND >+ DateUnaryExpr operator=LT date=2014-06-26T12:00:00""" >+ ), >+ ), > ( > "date in_range 2014-06-26 to 2014-07-26", > dedent( >@@ -528,6 +544,14 @@ class Parser(TestCase): > DateInRangeExpr date_start=2014-06-26 date_end=2014-07-26""" > ), > ), >+ ( >+ "date in_range 2014-06-26T12:00:00 to 2014-07-26T13:00:00", >+ dedent( >+ """\ >+ BoolExpr AND >+ DateInRangeExpr date_start=2014-06-26T12:00:00 date_end=2014-07-26T13:00:00""" >+ ), >+ ), > ( > "date in_range to 2014-07-26", > dedent( >@@ -536,6 +560,14 @@ class Parser(TestCase): > DateInRangeExpr date_end=2014-07-26""" > ), > ), >+ ( >+ "date in_range to 2014-07-26T12:00:00", >+ dedent( >+ """\ >+ BoolExpr AND >+ DateInRangeExpr date_end=2014-07-26T12:00:00""" >+ ), >+ ), > ( > "date in_range 2014-06-26 to duration years=1", > dedent( >@@ -546,6 +578,16 @@ class Parser(TestCase): > )""" > ), > ), >+ ( >+ "date in_range 2014-06-26T12:00:00 to duration years=1", >+ dedent( >+ """\ >+ BoolExpr AND >+ DateInRangeExpr date_start=2014-06-26T12:00:00 duration_parts=( >+ years=1 >+ )""" >+ ), >+ ), > ( > "date in_range 2014-06-26 to duration a=1 b=2 a=3", > dedent( >@@ -876,6 +918,22 @@ class Parser(TestCase): > "#uname in_range 2014-06-26 to 2014-07-26", > (1, 8, 7, "Expected 'eq'"), > ), >+ ( >+ "date gt 2014-06-24 12:00:00", >+ (1, 20, 19, "Expected end of text"), >+ ), >+ ( >+ "date lt 2014-06-24 12:00:00", >+ (1, 20, 19, "Expected end of text"), >+ ), >+ ( >+ "date in_range 2014-06-26 12:00:00 to 2014-07-26", >+ (1, 15, 14, "Expected 'to'"), >+ ), >+ ( >+ "date in_range 2014-06-26 to 2014-07-26 12:00:00", >+ (1, 40, 39, "Expected end of text"), >+ ), > # braces > ("(#uname)", (1, 8, 7, "Expected 'eq'")), > ("(", (1, 2, 1, "Expected 'date'")), >diff --git a/pcs_test/tier1/constraint/test_config.py b/pcs_test/tier1/constraint/test_config.py >index 27aed9c09..b00012b4b 100644 >--- a/pcs_test/tier1/constraint/test_config.py >+++ b/pcs_test/tier1/constraint/test_config.py >@@ -72,18 +72,19 @@ class ConstraintConfigJson(TestCase): > > > class ConstraintConfigCmdMixin: >- # pylint: disable=invalid-name >+ orig_cib_file_path = get_test_resource("cib-all.xml") >+ > def setUp(self): >- orig_cib_file_path = get_test_resource("cib-all.xml") >+ # pylint: disable=invalid-name > self.new_cib_file = get_tmp_file(self._get_tmp_file_name()) >- self.pcs_runner_orig = PcsRunner(cib_file=orig_cib_file_path) >+ self.pcs_runner_orig = PcsRunner(cib_file=self.orig_cib_file_path) > self.pcs_runner_new = PcsRunner(cib_file=self.new_cib_file.name) > write_data_to_tmpfile( > fixture_cib.modify_cib_file( > get_test_resource("cib-empty.xml"), > resources=etree_to_str( > get_resources( >- XmlManipulation.from_file(orig_cib_file_path).tree >+ XmlManipulation.from_file(self.orig_cib_file_path).tree > ) > ), > ), >@@ -92,6 +93,7 @@ class ConstraintConfigCmdMixin: > self.maxDiff = None > > def tearDown(self): >+ # pylint: disable=invalid-name > self.new_cib_file.close() > > def _get_as_json(self, runner, use_all): >@@ -141,6 +143,68 @@ class ConstraintConfigCmd(ConstraintConfigCmdMixin, TestCase): > return "tier1_constraint_test_config_cib.xml" > > >+class ConstraintConfigCmdSpaceInDate(ConstraintConfigCmdMixin, TestCase): >+ # This class tests that pcs exports dates from location rules constraint >+ # with spaces replaced by T in pcs commands, so that they can be run and >+ # processed by pcs correctly. >+ orig_cib_file_path = get_test_resource("cib-rule-with-spaces-in-date.xml") >+ >+ @staticmethod >+ def _get_tmp_file_name(): >+ return "tier1_constraint_test_config_cib_date_space.xml" >+ >+ @staticmethod >+ def _replace(struct, search_replace): >+ if isinstance(struct, dict): >+ for key, val in struct.items(): >+ struct[key] = ConstraintConfigCmdSpaceInDate._replace( >+ val, search_replace >+ ) >+ return struct >+ if isinstance(struct, list): >+ return [ >+ ConstraintConfigCmdSpaceInDate._replace(val, search_replace) >+ for val in struct >+ ] >+ for search, replace in search_replace: >+ if struct == search: >+ return replace >+ return struct >+ >+ def _get_as_json(self, runner, use_all): >+ data = super()._get_as_json(runner, use_all) >+ data = self._replace( >+ data, >+ [ >+ ("2023-01-01 12:00", "2023-01-01T12:00"), >+ ("2023-12-31 12:00", "2023-12-31T12:00"), >+ ], >+ ) >+ return data >+ >+ def test_commands(self): >+ stdout, stderr, retval = self.pcs_runner_orig.run( >+ ["constraint", "config", "--output-format=cmd"] >+ ) >+ self.assertEqual(retval, 0) >+ self.assertEqual(stderr, "") >+ self.assertEqual( >+ stdout, >+ ( >+ "pcs -- constraint location resource%R1 rule \\\n" >+ " id=location-R1-rule constraint-id=location-R1 score=INFINITY \\\n" >+ " '#uname' eq node1 and date gt 2023-01-01T12:00 and " >+ "date lt 2023-12-31T12:00 and date in_range 2023-01-01T12:00 " >+ "to 2023-12-31T12:00;\n" >+ "pcs -- constraint rule add location-R1 \\\n" >+ " id=location-R1-rule-1 score=INFINITY \\\n" >+ " '#uname' eq node1 and date gt 2023-01-01T12:00 and " >+ "date lt 2023-12-31T12:00 and date in_range 2023-01-01T12:00 " >+ "to 2023-12-31T12:00\n" >+ ), >+ ) >+ >+ > class ConstraintConfigText(TestCase): > def setUp(self): > self.maxDiff = None >@@ -161,10 +225,12 @@ class ConstraintConfigText(TestCase): > resource pattern 'R*' prefers node 'localhost' with score INFINITY > resource 'R6-clone' > Rules: >- Rule: role=Unpromoted score=500 >+ Rule: boolean-op=and role=Unpromoted score=500 >+ Expression: #uname eq node1 > Expression: date gt 2000-01-01 >- Rule: role=Promoted score-attribute=test-attr >+ Rule: boolean-op=and role=Promoted score-attribute=test-attr > Expression: date gt 2010-12-31 >+ Expression: #uname eq node1 > Colocation Constraints: > Promoted resource 'G1-clone' with Stopped resource 'R6-clone' > score=-100 >@@ -225,10 +291,12 @@ class ConstraintConfigText(TestCase): > Expression: date lt 2000-01-01 > resource 'R6-clone' > Rules: >- Rule: role=Unpromoted score=500 >+ Rule: boolean-op=and role=Unpromoted score=500 >+ Expression: #uname eq node1 > Expression: date gt 2000-01-01 >- Rule: role=Promoted score-attribute=test-attr >+ Rule: boolean-op=and role=Promoted score-attribute=test-attr > Expression: date gt 2010-12-31 >+ Expression: #uname eq node1 > Colocation Constraints: > Promoted resource 'G1-clone' with Stopped resource 'R6-clone' > score=-100 >@@ -285,10 +353,12 @@ class ConstraintConfigText(TestCase): > resource pattern 'R*' prefers node 'localhost' with score INFINITY (id: location-R-localhost-INFINITY) > resource 'R6-clone' (id: loc_constr_with_not_expired_rule) > Rules: >- Rule: role=Unpromoted score=500 (id: loc_constr_with_not_expired_rule-rule) >- Expression: date gt 2000-01-01 (id: loc_constr_with_not_expired_rule-rule-expr) >- Rule: role=Promoted score-attribute=test-attr (id: loc_constr_with_not_expired_rule-rule-1) >+ Rule: boolean-op=and role=Unpromoted score=500 (id: loc_constr_with_not_expired_rule-rule) >+ Expression: #uname eq node1 (id: loc_constr_with_not_expired_rule-rule-expr) >+ Expression: date gt 2000-01-01 (id: loc_constr_with_not_expired_rule-rule-expr-1) >+ Rule: boolean-op=and role=Promoted score-attribute=test-attr (id: loc_constr_with_not_expired_rule-rule-1) > Expression: date gt 2010-12-31 (id: loc_constr_with_not_expired_rule-rule-1-expr) >+ Expression: #uname eq node1 (id: loc_constr_with_not_expired_rule-rule-1-expr-1) > Colocation Constraints: > Promoted resource 'G1-clone' with Stopped resource 'R6-clone' (id: colocation-G1-clone-R6-clone--100) > score=-100 >@@ -349,10 +419,12 @@ class ConstraintConfigText(TestCase): > Expression: date lt 2000-01-01 (id: loc_constr_with_expired_rule-rule-expr) > resource 'R6-clone' (id: loc_constr_with_not_expired_rule) > Rules: >- Rule: role=Unpromoted score=500 (id: loc_constr_with_not_expired_rule-rule) >- Expression: date gt 2000-01-01 (id: loc_constr_with_not_expired_rule-rule-expr) >- Rule: role=Promoted score-attribute=test-attr (id: loc_constr_with_not_expired_rule-rule-1) >+ Rule: boolean-op=and role=Unpromoted score=500 (id: loc_constr_with_not_expired_rule-rule) >+ Expression: #uname eq node1 (id: loc_constr_with_not_expired_rule-rule-expr) >+ Expression: date gt 2000-01-01 (id: loc_constr_with_not_expired_rule-rule-expr-1) >+ Rule: boolean-op=and role=Promoted score-attribute=test-attr (id: loc_constr_with_not_expired_rule-rule-1) > Expression: date gt 2010-12-31 (id: loc_constr_with_not_expired_rule-rule-1-expr) >+ Expression: #uname eq node1 (id: loc_constr_with_not_expired_rule-rule-1-expr-1) > Colocation Constraints: > Promoted resource 'G1-clone' with Stopped resource 'R6-clone' (id: colocation-G1-clone-R6-clone--100) > score=-100 >diff --git a/pcs_test/tier1/legacy/test_constraints.py b/pcs_test/tier1/legacy/test_constraints.py >index e5e8bd276..7e629a89f 100644 >--- a/pcs_test/tier1/legacy/test_constraints.py >+++ b/pcs_test/tier1/legacy/test_constraints.py >@@ -202,6 +202,117 @@ class ConstraintTest(unittest.TestCase, AssertPcsMixin): > ), > ) > >+ def test_constraint_rules_space_deprecated(self): >+ self.fixture_resources() >+ message = ( >+ "Deprecation Warning: Using spaces in date values is deprecated and " >+ "will be removed. Use 'T' as a delimiter between date and time.\n" >+ ) >+ self.assert_pcs_success( >+ "constraint location D1 rule".split() >+ + [ >+ "date", >+ "gt", >+ "2023-01-01 12:00 +3:00", >+ "and", >+ "date", >+ "lt", >+ "2023-12-31 12:00 -10:30", >+ "and", >+ "date", >+ "in_range", >+ "2023-01-01 12:00", >+ "to", >+ "2023-12-31 12:00", >+ ], >+ stderr_full=message, >+ ) >+ self.assert_pcs_success( >+ "constraint location D1 rule".split() >+ + ["date", "gt", "2023-01-01 12:00"], >+ stderr_full=message, >+ ) >+ self.assert_pcs_success( >+ "constraint location D1 rule".split() >+ + ["date", "lt", "2023-12-31 12:00"], >+ stderr_full=message, >+ ) >+ self.assert_pcs_success( >+ "constraint location D1 rule".split() >+ + [ >+ "date", >+ "in_range", >+ "2023-01-01 12:00", >+ "to", >+ "2023-12-31T12:00", >+ ], >+ stderr_full=message, >+ ) >+ self.assert_pcs_success( >+ "constraint location D1 rule".split() >+ + [ >+ "date", >+ "in_range", >+ "2023-01-01T12:00", >+ "to", >+ "2023-12-31 12:00", >+ ], >+ stderr_full=message, >+ ) >+ # when exporting the rules, spaces are replaced by T >+ self.assert_pcs_success( >+ "constraint config".split(), >+ dedent( >+ """\ >+ Location Constraints: >+ resource 'D1' >+ Rules: >+ Rule: boolean-op=and score=INFINITY >+ Expression: date gt 2023-01-01T12:00+3:00 >+ Expression: date lt 2023-12-31T12:00-10:30 >+ Expression: date in_range 2023-01-01T12:00 to 2023-12-31T12:00 >+ resource 'D1' >+ Rules: >+ Rule: score=INFINITY >+ Expression: date gt 2023-01-01T12:00 >+ resource 'D1' >+ Rules: >+ Rule: score=INFINITY >+ Expression: date lt 2023-12-31T12:00 >+ resource 'D1' >+ Rules: >+ Rule: score=INFINITY >+ Expression: date in_range 2023-01-01T12:00 to 2023-12-31T12:00 >+ resource 'D1' >+ Rules: >+ Rule: score=INFINITY >+ Expression: date in_range 2023-01-01T12:00 to 2023-12-31T12:00 >+ """ >+ ), >+ ) >+ self.assert_pcs_success( >+ "constraint config --output-format=cmd".split(), >+ dedent( >+ """\ >+ pcs -- constraint location resource%D1 rule \\ >+ id=location-D1-rule constraint-id=location-D1 score=INFINITY \\ >+ date gt 2023-01-01T12:00+3:00 and date lt 2023-12-31T12:00-10:30 and date in_range 2023-01-01T12:00 to 2023-12-31T12:00; >+ pcs -- constraint location resource%D1 rule \\ >+ id=location-D1-1-rule constraint-id=location-D1-1 score=INFINITY \\ >+ date gt 2023-01-01T12:00; >+ pcs -- constraint location resource%D1 rule \\ >+ id=location-D1-2-rule constraint-id=location-D1-2 score=INFINITY \\ >+ date lt 2023-12-31T12:00; >+ pcs -- constraint location resource%D1 rule \\ >+ id=location-D1-3-rule constraint-id=location-D1-3 score=INFINITY \\ >+ date in_range 2023-01-01T12:00 to 2023-12-31T12:00; >+ pcs -- constraint location resource%D1 rule \\ >+ id=location-D1-4-rule constraint-id=location-D1-4 score=INFINITY \\ >+ date in_range 2023-01-01T12:00 to 2023-12-31T12:00 >+ """ >+ ), >+ ) >+ > def testAdvancedConstraintRule(self): > self.fixture_resources() > stdout, stderr, retval = pcs( >diff --git a/pcs_test/tier1/legacy/test_rule.py b/pcs_test/tier1/legacy/test_rule.py >index eff3f8787..b8f37e5d1 100644 >--- a/pcs_test/tier1/legacy/test_rule.py >+++ b/pcs_test/tier1/legacy/test_rule.py >@@ -453,10 +453,18 @@ class ParserTest(TestCase): > "(gt (literal date) (literal 2014-06-26))", > str(self.parser.parse(["date", "gt", "2014-06-26"])), > ) >+ self.assertEqual( >+ "(gt (literal date) (literal 2014-06-26 12:00:00))", >+ str(self.parser.parse(["date", "gt", "2014-06-26 12:00:00"])), >+ ) > self.assertEqual( > "(lt (literal date) (literal 2014-06-26))", > str(self.parser.parse(["date", "lt", "2014-06-26"])), > ) >+ self.assertEqual( >+ "(lt (literal date) (literal 2014-06-26 12:00:00))", >+ str(self.parser.parse(["date", "lt", "2014-06-26 12:00:00"])), >+ ) > self.assertEqual( > "(in_range " > "(literal date) (literal 2014-06-26) (literal 2014-07-26)" >@@ -467,6 +475,22 @@ class ParserTest(TestCase): > ) > ), > ) >+ self.assertEqual( >+ "(in_range " >+ "(literal date) (literal 2014-06-26 12:00) (literal 2014-07-26 13:00)" >+ ")", >+ str( >+ self.parser.parse( >+ [ >+ "date", >+ "in_range", >+ "2014-06-26 12:00", >+ "to", >+ "2014-07-26 13:00", >+ ] >+ ) >+ ), >+ ) > self.assertEqual( > "(in_range " > "(literal date) " >diff --git a/pcs_test/tools/constraints_dto.py b/pcs_test/tools/constraints_dto.py >index f9c915103..c1ac74540 100644 >--- a/pcs_test/tools/constraints_dto.py >+++ b/pcs_test/tools/constraints_dto.py >@@ -160,6 +160,7 @@ def get_all_constraints( > "loc_constr_with_not_expired_rule-rule" > ), > options={ >+ "boolean-op": "and", > "role": "Unpromoted", > "score": "500", > }, >@@ -168,10 +169,26 @@ def get_all_constraints( > expressions=[ > CibRuleExpressionDto( > id="loc_constr_with_not_expired_rule-rule-expr", >- type=CibRuleExpressionType.DATE_EXPRESSION, >+ type=CibRuleExpressionType.EXPRESSION, > in_effect=rule_eval.get_rule_status( > "loc_constr_with_not_expired_rule-rule-expr" > ), >+ options={ >+ "operation": "eq", >+ "attribute": "#uname", >+ "value": "node1", >+ }, >+ date_spec=None, >+ duration=None, >+ expressions=[], >+ as_string="#uname eq node1", >+ ), >+ CibRuleExpressionDto( >+ id="loc_constr_with_not_expired_rule-rule-expr-1", >+ type=CibRuleExpressionType.DATE_EXPRESSION, >+ in_effect=rule_eval.get_rule_status( >+ "loc_constr_with_not_expired_rule-rule-expr-1" >+ ), > options={ > "operation": "gt", > "start": "2000-01-01", >@@ -180,9 +197,9 @@ def get_all_constraints( > duration=None, > expressions=[], > as_string="date gt 2000-01-01", >- ) >+ ), > ], >- as_string="date gt 2000-01-01", >+ as_string="#uname eq node1 and date gt 2000-01-01", > ), > CibRuleExpressionDto( > id="loc_constr_with_not_expired_rule-rule-1", >@@ -191,6 +208,7 @@ def get_all_constraints( > "loc_constr_with_not_expired_rule-rule-1" > ), > options={ >+ "boolean-op": "and", > "role": "Promoted", > "score-attribute": "test-attr", > }, >@@ -211,9 +229,25 @@ def get_all_constraints( > duration=None, > expressions=[], > as_string="date gt 2010-12-31", >- ) >+ ), >+ CibRuleExpressionDto( >+ id="loc_constr_with_not_expired_rule-rule-1-expr-1", >+ type=CibRuleExpressionType.EXPRESSION, >+ in_effect=rule_eval.get_rule_status( >+ "loc_constr_with_not_expired_rule-rule-1-expr-1" >+ ), >+ options={ >+ "operation": "eq", >+ "attribute": "#uname", >+ "value": "node1", >+ }, >+ date_spec=None, >+ duration=None, >+ expressions=[], >+ as_string="#uname eq node1", >+ ), > ], >- as_string="date gt 2010-12-31", >+ as_string="date gt 2010-12-31 and #uname eq node1", > ), > ], > lifetime=[], >-- >2.30.2 > > >From 1114072c6a992fc16bbf6e85afb0309dce218f6e Mon Sep 17 00:00:00 2001 >From: Tomas Jelinek <tojeline@redhat.com> >Date: Tue, 11 Jul 2023 15:08:39 +0200 >Subject: [PATCH 2/2] don't export unsupported constraints as pcs commands > >--- > CHANGELOG.md | 6 + > pcs/cli/constraint/output/all.py | 62 +++++----- > pcs/cli/constraint/output/colocation.py | 46 +++++--- > pcs/cli/constraint/output/location.py | 6 +- > pcs/cli/constraint/output/order.py | 15 ++- > pcs/cli/constraint/output/set.py | 12 +- > pcs/cli/constraint/output/ticket.py | 9 +- > pcs_test/Makefile.am | 1 + > .../cib-unexportable-constraints.xml | 109 ++++++++++++++++++ > pcs_test/tier1/constraint/test_config.py | 47 ++++++++ > 10 files changed, 256 insertions(+), 57 deletions(-) > create mode 100644 pcs_test/resources/cib-unexportable-constraints.xml > >diff --git a/CHANGELOG.md b/CHANGELOG.md >index dbeab43aa..0285c4840 100644 >--- a/CHANGELOG.md >+++ b/CHANGELOG.md >@@ -6,6 +6,12 @@ > - Exporting constraints with rules in form of pcs commands now escapes `#` and > fixes spaces in dates to make the commands valid ([rhbz#2163953]) > >+### Changed >+- When exporting constraints in form of pcs commands, constraints containing >+ options unsupported by pcs are not exported and a warning is printed instead. >+ Previously, the warnings were printed, but the constraints were exported >+ regardless. ([rhbz#2163953]) >+ > ### Deprecated > - Using spaces in dates in location constraint rules (using spaces in dates in > rules in other parts of configuration was never allowed) ([rhbz#2163953]) >diff --git a/pcs/cli/constraint/output/all.py b/pcs/cli/constraint/output/all.py >index 829584bbf..a173507ce 100644 >--- a/pcs/cli/constraint/output/all.py >+++ b/pcs/cli/constraint/output/all.py >@@ -47,38 +47,44 @@ def constraints_to_cmd(constraints_dto: CibConstraintsDto) -> list[list[str]]: > for location_set_dto in constraints_dto.location_set: > warn( > "Location set constraint with id " >- f"'{location_set_dto.attributes.constraint_id}'configured but it's " >- "not supported by this command" >+ f"'{location_set_dto.attributes.constraint_id}' configured but it's " >+ "not supported by this command." >+ " Command for creating the constraint is omitted." > ) > location_cmds = [] > for location_dto in constraints_dto.location: > location_cmds.extend(location.plain_constraint_to_cmd(location_dto)) >- return ( >- location_cmds >- + [ >- colocation.plain_constraint_to_cmd(colocation_dto) >- for colocation_dto in constraints_dto.colocation >- ] >- + [ >- colocation.set_constraint_to_cmd(colocation_set_dto) >- for colocation_set_dto in constraints_dto.colocation_set >- ] >- + [ >- order.plain_constraint_to_cmd(order_dto) >- for order_dto in constraints_dto.order >- ] >- + [ >- order.set_constraint_to_cmd(order_set_dto) >- for order_set_dto in constraints_dto.order_set >- ] >- + [ >- ticket.plain_constraint_to_cmd(ticket_dto) >- for ticket_dto in constraints_dto.ticket >- ] >- + [ >- ticket.set_constraint_to_cmd(ticket_set_dto) >- for ticket_set_dto in constraints_dto.ticket_set >- ] >+ return list( >+ filter( >+ None, >+ ( >+ location_cmds >+ + [ >+ colocation.plain_constraint_to_cmd(colocation_dto) >+ for colocation_dto in constraints_dto.colocation >+ ] >+ + [ >+ colocation.set_constraint_to_cmd(colocation_set_dto) >+ for colocation_set_dto in constraints_dto.colocation_set >+ ] >+ + [ >+ order.plain_constraint_to_cmd(order_dto) >+ for order_dto in constraints_dto.order >+ ] >+ + [ >+ order.set_constraint_to_cmd(order_set_dto) >+ for order_set_dto in constraints_dto.order_set >+ ] >+ + [ >+ ticket.plain_constraint_to_cmd(ticket_dto) >+ for ticket_dto in constraints_dto.ticket >+ ] >+ + [ >+ ticket.set_constraint_to_cmd(ticket_set_dto) >+ for ticket_set_dto in constraints_dto.ticket_set >+ ] >+ ), >+ ) > ) > > >diff --git a/pcs/cli/constraint/output/colocation.py b/pcs/cli/constraint/output/colocation.py >index 9c8db33be..7a13ed27b 100644 >--- a/pcs/cli/constraint/output/colocation.py >+++ b/pcs/cli/constraint/output/colocation.py >@@ -1,5 +1,8 @@ > from shlex import quote >-from typing import Iterable >+from typing import ( >+ Iterable, >+ Optional, >+) > > from pcs.cli.common.output import ( > INDENT_STEP, >@@ -120,13 +123,15 @@ def constraints_to_text( > def _attributes_to_cmd_pairs( > attributes_dto: CibConstraintColocationAttributesDto, > filter_out: StringCollection = tuple(), >-) -> list[tuple[str, str]]: >+) -> Optional[list[tuple[str, str]]]: > if attributes_dto.lifetime: > warn( > "Lifetime configuration detected in constraint " > f"'{attributes_dto.constraint_id}' but not supported by this " > "command." >+ " Command for creating the constraint is omitted." > ) >+ return None > unsupported_options = {"influence"} > result = [] > for pair in [("id", attributes_dto.constraint_id)] + _attributes_to_pairs( >@@ -136,8 +141,10 @@ def _attributes_to_cmd_pairs( > warn( > f"Option '{pair[0]}' detected in constraint " > f"'{attributes_dto.constraint_id}' but not supported by this " >- "command" >+ "command." >+ " Command for creating the constraint is omitted." > ) >+ return None > if pair[0] in filter_out: > continue > result.append(pair) >@@ -155,7 +162,17 @@ def plain_constraint_to_cmd( > "Resource instance(s) detected in constraint " > f"'{constraint_dto.attributes.constraint_id}' but not supported by " > "this command." >+ " Command for creating the constraint is omitted." > ) >+ return [] >+ if constraint_dto.node_attribute is not None: >+ warn( >+ "Option 'node_attribute' detected in constraint " >+ f"'{constraint_dto.attributes.constraint_id}' but not supported by " >+ "this command." >+ " Command for creating the constraint is omitted." >+ ) >+ return [] > result = [ > "pcs -- constraint colocation add {resource_role}{resource_id} with {with_resource_role}{with_resource_id}{score}".format( > resource_role=format_optional(constraint_dto.resource_role), >@@ -169,11 +186,12 @@ def plain_constraint_to_cmd( > ), > ) > ] >- params = pairs_to_cmd( >- _attributes_to_cmd_pairs( >- constraint_dto.attributes, filter_out=("score",) >- ) >+ pairs = _attributes_to_cmd_pairs( >+ constraint_dto.attributes, filter_out=("score",) > ) >+ if pairs is None: >+ return [] >+ params = pairs_to_cmd(pairs) > if params: > result.extend(indent([params], indent_step=INDENT_STEP)) > return result >@@ -184,12 +202,14 @@ def set_constraint_to_cmd( > ) -> list[str]: > result = ["pcs -- constraint colocation"] > for resource_set in constraint_dto.resource_sets: >- result.extend( >- indent( >- _set.resource_set_to_cmd(resource_set), indent_step=INDENT_STEP >- ) >- ) >- params = pairs_to_cmd(_attributes_to_cmd_pairs(constraint_dto.attributes)) >+ set_cmd_part = _set.resource_set_to_cmd(resource_set) >+ if not set_cmd_part: >+ return [] >+ result.extend(indent(set_cmd_part, indent_step=INDENT_STEP)) >+ pairs = _attributes_to_cmd_pairs(constraint_dto.attributes) >+ if pairs is None: >+ return [] >+ params = pairs_to_cmd(pairs) > if params: > result.extend(indent([f"setoptions {params}"], indent_step=INDENT_STEP)) > return result >diff --git a/pcs/cli/constraint/output/location.py b/pcs/cli/constraint/output/location.py >index e289fc108..25ac646ad 100644 >--- a/pcs/cli/constraint/output/location.py >+++ b/pcs/cli/constraint/output/location.py >@@ -240,13 +240,17 @@ def plain_constraint_to_cmd( > "Lifetime configuration detected in constraint " > f"'{constraint_dto.attributes.constraint_id}' but not supported by " > "this command." >+ " Command for creating the constraint is omitted." > ) >+ return [] > if constraint_dto.role: > warn( >- f"Resource role '{constraint_dto.role}' detected in constraint " >+ f"Resource role detected in constraint " > f"'{constraint_dto.attributes.constraint_id}' but not supported by " > "this command." >+ " Command for creating the constraint is omitted." > ) >+ return [] > if constraint_dto.attributes.rules: > return _plain_constraint_rule_to_cmd(constraint_dto) > return [_plain_constraint_to_cmd(constraint_dto)] >diff --git a/pcs/cli/constraint/output/order.py b/pcs/cli/constraint/output/order.py >index f407270d9..53fe546ad 100644 >--- a/pcs/cli/constraint/output/order.py >+++ b/pcs/cli/constraint/output/order.py >@@ -127,7 +127,9 @@ def plain_constraint_to_cmd( > "Resource instance(s) detected in constraint " > f"'{constraint_dto.attributes.constraint_id}' but not supported by " > "this command." >+ " Command for creating the constraint is omitted." > ) >+ return [] > result = [ > "pcs -- constraint order {first_action}{first_resource_id} then {then_action}{then_resource_id}".format( > first_action=format_optional(constraint_dto.first_action), >@@ -147,11 +149,10 @@ def set_constraint_to_cmd( > ) -> list[str]: > result = ["pcs -- constraint order"] > for resource_set in constraint_dto.resource_sets: >- result.extend( >- indent( >- _set.resource_set_to_cmd(resource_set), indent_step=INDENT_STEP >- ) >- ) >+ set_cmd_part = _set.resource_set_to_cmd(resource_set) >+ if not set_cmd_part: >+ return [] >+ result.extend(indent(set_cmd_part, indent_step=INDENT_STEP)) > pairs = [] > for pair in _attributes_to_cmd_pairs(constraint_dto.attributes): > # this list is based on pcs.lib.cib.constraint.order.ATTRIB >@@ -159,8 +160,10 @@ def set_constraint_to_cmd( > warn( > f"Option '{pair[0]}' detected in constraint " > f"'{constraint_dto.attributes.constraint_id}' but not " >- "supported by this command" >+ "supported by this command." >+ " Command for creating the constraint is omitted." > ) >+ return [] > pairs.append(pair) > if pairs: > result.extend( >diff --git a/pcs/cli/constraint/output/set.py b/pcs/cli/constraint/output/set.py >index 3b1fa31a7..5395ebf7a 100644 >--- a/pcs/cli/constraint/output/set.py >+++ b/pcs/cli/constraint/output/set.py >@@ -1,4 +1,7 @@ >-from typing import Sequence >+from typing import ( >+ Optional, >+ Sequence, >+) > > from pcs.cli.common.output import ( > INDENT_STEP, >@@ -81,7 +84,7 @@ def set_constraint_to_text( > return result > > >-def resource_set_to_cmd(resource_set: CibResourceSetDto) -> list[str]: >+def resource_set_to_cmd(resource_set: CibResourceSetDto) -> Optional[list[str]]: > filtered_pairs = [] > for pair in _resource_set_options_to_pairs(resource_set): > # this list is based on pcs.lib.cib.constraint.resource_set._ATTRIBUTES >@@ -89,9 +92,10 @@ def resource_set_to_cmd(resource_set: CibResourceSetDto) -> list[str]: > warn( > f"Option '{pair[0]}' detected in resource set " > f"'{resource_set.set_id}' but not " >- "supported by this command" >+ "supported by this command." >+ " Command for creating the constraint is omitted." > ) >- continue >+ return None > filtered_pairs.append(pair) > > return [ >diff --git a/pcs/cli/constraint/output/ticket.py b/pcs/cli/constraint/output/ticket.py >index e047226c5..d83e65b8d 100644 >--- a/pcs/cli/constraint/output/ticket.py >+++ b/pcs/cli/constraint/output/ticket.py >@@ -121,11 +121,10 @@ def set_constraint_to_cmd( > ) -> list[str]: > result = ["pcs -- constraint ticket"] > for resource_set in constraint_dto.resource_sets: >- result.extend( >- indent( >- _set.resource_set_to_cmd(resource_set), indent_step=INDENT_STEP >- ) >- ) >+ set_cmd_part = _set.resource_set_to_cmd(resource_set) >+ if not set_cmd_part: >+ return [] >+ result.extend(indent(set_cmd_part, indent_step=INDENT_STEP)) > params = pairs_to_cmd( > _attributes_to_cmd_pairs(constraint_dto.attributes) > + [("ticket", constraint_dto.attributes.ticket)] >diff --git a/pcs_test/Makefile.am b/pcs_test/Makefile.am >index c1ba2e425..64ef1d9ee 100644 >--- a/pcs_test/Makefile.am >+++ b/pcs_test/Makefile.am >@@ -22,6 +22,7 @@ EXTRA_DIST = \ > resources/cib-all.xml \ > resources/cib-rule-with-spaces-in-date.xml \ > resources/cib-tags.xml \ >+ resources/cib-unexportable-constraints.xml \ > resources/controld_metadata.xml \ > resources/corosync-3nodes.conf \ > resources/corosync-3nodes-qdevice.conf \ >diff --git a/pcs_test/resources/cib-unexportable-constraints.xml b/pcs_test/resources/cib-unexportable-constraints.xml >new file mode 100644 >index 000000000..642bad963 >--- /dev/null >+++ b/pcs_test/resources/cib-unexportable-constraints.xml >@@ -0,0 +1,109 @@ >+<cib epoch="1" num_updates="0" admin_epoch="0" validate-with="pacemaker-3.9" crm_feature_set="3.17.0" update-origin="rh7-3" update-client="crmd" cib-last-written="Thu Aug 23 16:49:17 2012" have-quorum="0" dc-uuid="2"> >+ <configuration> >+ <crm_config/> >+ <nodes/> >+ <resources> >+ <primitive id="R1" class="ocf" type="Dummy" provider="pacemaker"> >+ <operations> >+ <op name="monitor" interval="10s" timeout="20s" id="R1-monitor-interval-10s"/> >+ </operations> >+ </primitive> >+ <clone id="R2-clone"> >+ <primitive id="R2" class="ocf" type="Stateful" provider="pacemaker"> >+ <operations> >+ <op name="monitor" interval="10s" timeout="20s" role="Promoted" id="R2-monitor-interval-10s"/> >+ <op name="monitor" interval="11s" timeout="20s" role="Unpromoted" id="R2-monitor-interval-11s"/> >+ </operations> >+ </primitive> >+ <meta_attributes id="R2-clone-meta_attributes"> >+ <nvpair id="R2-clone-meta_attributes-promotable" name="promotable" value="true"/> >+ </meta_attributes> >+ </clone> >+ <primitive id="R3" class="ocf" type="Dummy" provider="pacemaker"> >+ <operations> >+ <op name="monitor" interval="10s" timeout="20s" id="R3-monitor-interval-10s"/> >+ </operations> >+ </primitive> >+ </resources> >+ <constraints> >+ <rsc_location id="location-OK" rsc="R1" node="node1" score="INFINITY"/> >+ <rsc_location id="location-role" rsc="R2-clone" node="node2" role="Promoted" score="INFINITY"/> >+ <rsc_location id="location-lifetime" rsc="R1" node="node2" score="INFINITY"> >+ <lifetime> >+ <rule id="location-lifetime-rule" score="-INFINITY" boolean-op="and"> >+ <date_expression id="location-lifetime-rule-expr" operation="lt" end="2023-07-11 16:46:01 +02:00"/> >+ </rule> >+ </lifetime> >+ </rsc_location> >+ <rsc_location id="location-set" node="node1" score="INFINITY"> >+ <resource_set id="location-set-set"> >+ <resource_ref id="R1"/> >+ <resource_ref id="R3"/> >+ </resource_set> >+ </rsc_location> >+ <rsc_colocation id="colocation-OK" rsc="R1" with-rsc="R3" score="INFINITY"/> >+ <rsc_colocation id="colocation-influence" rsc="R1" with-rsc="R3" score="INFINITY" influence="false"/> >+ <rsc_colocation id="colocation-lifetime" rsc="R1" with-rsc="R3" score="INFINITY"> >+ <lifetime> >+ <rule id="colocation-lifetime-rule" score="-INFINITY" boolean-op="and"> >+ <date_expression id="colocation-lifetime-rule-expr" operation="lt" end="2023-07-11 16:46:01 +02:00"/> >+ </rule> >+ </lifetime> >+ </rsc_colocation> >+ <rsc_colocation id="colocation-node-attribute" rsc="R1" with-rsc="R3" score="INFINITY" node-attribute="something"/> >+ <rsc_colocation id="colocation-set-OK"> >+ <resource_set id="colocation-set-OK-set"> >+ <resource_ref id="R1"/> >+ <resource_ref id="R3"/> >+ </resource_set> >+ </rsc_colocation> >+ <rsc_colocation id="colocation-set-ordering"> >+ <resource_set id="colocation-set-ordering-set" ordering="listed"> >+ <resource_ref id="R1"/> >+ <resource_ref id="R3"/> >+ </resource_set> >+ </rsc_colocation> >+ <!-- >+ These are not yet supported by pacemaker. They are currently defined >+ only in constraints-next.rng. Putting them into CIB makes pacemaker >+ unable to read the CIB. >+ <rsc_colocation id="colocation-rsc-instance" rsc="R3" rsc-instance="1" with-rsc="R1" score="INFINITY"/> >+ <rsc_colocation id="colocation-with-rsc-instance" rsc="R1" with-rsc-instance="1" with-rsc="R3" score="INFINITY"/> >+ --> >+ <rsc_order id="order-OK" first="R1" then="R3" first-action="start" then-action="start"/> >+ <rsc_order id="order-lifetime" first="R1" then="R3" first-action="start" then-action="start"> >+ <lifetime> >+ <rule id="order-lifetime-rule" score="-INFINITY" boolean-op="and"> >+ <date_expression id="order-lifetime-rule-expr" operation="lt" end="2023-07-11 16:46:01 +02:00"/> >+ </rule> >+ </lifetime> >+ </rsc_order> >+ <rsc_order id="order-set-OK"> >+ <resource_set id="order-set-OK-set"> >+ <resource_ref id="R1"/> >+ <resource_ref id="R3"/> >+ </resource_set> >+ </rsc_order> >+ <rsc_order id="order-set-require-all" require-all="false"> >+ <resource_set id="order-set-require-all-set"> >+ <resource_ref id="R1"/> >+ <resource_ref id="R3"/> >+ </resource_set> >+ </rsc_order> >+ <rsc_order id="order-set-ordering"> >+ <resource_set id="order-set-ordering-set" ordering="listed"> >+ <resource_ref id="R1"/> >+ <resource_ref id="R3"/> >+ </resource_set> >+ </rsc_order> >+ <!-- >+ These are not yet supported by pacemaker. They are currently defined >+ only in constraints-next.rng. Putting them into CIB makes pacemaker >+ unable to read the CIB. >+ <rsc_order id="order-first-instance" first="R2" first-instance="1" then="R3" first-action="start" then-action="start"/> >+ <rsc_order id="order-then-instance" first="R1" then="R2" then-instance="1" first-action="start" then-action="start"/> >+ --> >+ </constraints> >+ </configuration> >+ <status/> >+</cib> >diff --git a/pcs_test/tier1/constraint/test_config.py b/pcs_test/tier1/constraint/test_config.py >index b00012b4b..525aac259 100644 >--- a/pcs_test/tier1/constraint/test_config.py >+++ b/pcs_test/tier1/constraint/test_config.py >@@ -205,6 +205,53 @@ class ConstraintConfigCmdSpaceInDate(ConstraintConfigCmdMixin, TestCase): > ) > > >+class ConstraintConfigCmdUnsupported(TestCase): >+ def setUp(self): >+ self.maxDiff = None >+ self.pcs_runner = PcsRunner( >+ cib_file=get_test_resource("cib-unexportable-constraints.xml"), >+ ) >+ >+ def test_dont_export_unsupported_constraints(self): >+ stdout, stderr, retval = self.pcs_runner.run( >+ ["constraint", "config", "--output-format=cmd"] >+ ) >+ self.assertEqual(retval, 0) >+ sufix = "not supported by this command. Command for creating the constraint is omitted.\n" >+ self.assertEqual( >+ stderr, >+ ( >+ f"Warning: Location set constraint with id 'location-set' configured but it's {sufix}" >+ f"Warning: Resource role detected in constraint 'location-role' but {sufix}" >+ f"Warning: Lifetime configuration detected in constraint 'location-lifetime' but {sufix}" >+ f"Warning: Option 'influence' detected in constraint 'colocation-influence' but {sufix}" >+ f"Warning: Lifetime configuration detected in constraint 'colocation-lifetime' but {sufix}" >+ f"Warning: Option 'node_attribute' detected in constraint 'colocation-node-attribute' but {sufix}" >+ f"Warning: Option 'ordering' detected in resource set 'colocation-set-ordering-set' but {sufix}" >+ f"Warning: Option 'require-all' detected in constraint 'order-set-require-all' but {sufix}" >+ f"Warning: Option 'ordering' detected in resource set 'order-set-ordering-set' but {sufix}" >+ ), >+ ) >+ self.assertEqual( >+ stdout, >+ ( >+ "pcs -- constraint location add location-OK resource%R1 node1 INFINITY;\n" >+ "pcs -- constraint colocation add R1 with R3 INFINITY \\\n" >+ " id=colocation-OK;\n" >+ "pcs -- constraint colocation \\\n" >+ " set R1 R3 \\\n" >+ " setoptions id=colocation-set-OK;\n" >+ "pcs -- constraint order start R1 then start R3 \\\n" >+ " id=order-OK;\n" >+ "pcs -- constraint order start R1 then start R3 \\\n" >+ " id=order-lifetime;\n" >+ "pcs -- constraint order \\\n" >+ " set R1 R3 \\\n" >+ " setoptions id=order-set-OK\n" >+ ), >+ ) >+ >+ > class ConstraintConfigText(TestCase): > def setUp(self): > self.maxDiff = None >-- >2.30.2 >
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 2163953
: 1975179