pcs/SOURCES/bz2163953-01-constraint-fix...

1408 lines
62 KiB
Diff

From cb31390fb04623250db814d08e84a0a1981916c7 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/3] fix exporting location constraints with rules
---
pcs/cli/constraint/output/all.py | 62 ++++----
pcs/cli/constraint/output/colocation.py | 46 ++++--
pcs/cli/constraint/output/location.py | 20 ++-
pcs/cli/constraint/output/order.py | 15 +-
pcs/cli/constraint/output/set.py | 12 +-
pcs/cli/constraint/output/ticket.py | 9 +-
pcs/lib/cib/rule/cib_to_str.py | 21 ++-
pcs/rule.py | 14 ++
pcs_test/Makefile.am | 2 +
pcs_test/resources/cib-all.xml | 8 +-
.../cib-rule-with-spaces-in-date.xml | 44 ++++++
.../cib-unexportable-constraints.xml | 109 +++++++++++++
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 | 147 ++++++++++++++++--
pcs_test/tier1/legacy/test_constraints.py | 111 +++++++++++++
pcs_test/tier1/legacy/test_rule.py | 24 +++
pcs_test/tools/constraints_dto.py | 44 +++++-
20 files changed, 755 insertions(+), 98 deletions(-)
create mode 100644 pcs_test/resources/cib-rule-with-spaces-in-date.xml
create mode 100644 pcs_test/resources/cib-unexportable-constraints.xml
diff --git a/pcs/cli/constraint/output/all.py b/pcs/cli/constraint/output/all.py
index 829584bb..a173507c 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 9c8db33b..7a13ed27 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 2713b7d0..25ac646a 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,
)
@@ -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 f407270d..53fe546a 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 3b1fa31a..5395ebf7 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 e047226c..d83e65b8 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/lib/cib/rule/cib_to_str.py b/pcs/lib/cib/rule/cib_to_str.py
index b196d8f6..29b67a8a 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 7c0e1400..172f1e1e 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 738f6622..64ef1d9e 100644
--- a/pcs_test/Makefile.am
+++ b/pcs_test/Makefile.am
@@ -20,7 +20,9 @@ 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/cib-unexportable-constraints.xml \
resources/controld_metadata.xml \
resources/corosync-3nodes.conf \
resources/corosync-3nodes-qdevice.conf \
diff --git a/pcs_test/resources/cib-all.xml b/pcs_test/resources/cib-all.xml
index a44a546b..b738d7e6 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 00000000..a68a1287
--- /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/cib-unexportable-constraints.xml b/pcs_test/resources/cib-unexportable-constraints.xml
new file mode 100644
index 00000000..642bad96
--- /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/resources/constraint-commands b/pcs_test/resources/constraint-commands
index 096bdec0..759e7146 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 80775e7e..296d279c 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 eaef7d4b..4d93628a 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 fd089ec8..37ae52f1 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 27aed9c0..525aac25 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,115 @@ 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 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
@@ -161,10 +272,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 +338,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 +400,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 +466,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 e5e8bd27..7e629a89 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 eff3f878..b8f37e5d 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 f9c91510..c1ac7454 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.41.0