a8b1b4c1d8
- Added BuildRequires: debugedit - for generating MiniDebugInfo - triggered by removing find-debuginfo.sh from rpm - Make use of filters when extracting tarballs to enhance security if provided by Python (pcs config restore command) - Exporting constraints with rules in form of pcs commands now escapes # and fixes spaces in dates to make the commands valid - Constraints containing options unsupported by pcs are not exported and a warning is printed instead - Using spaces in dates in location constraint rules is deprecated
1408 lines
62 KiB
Diff
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
|
|
|