diff --git a/.gitignore b/.gitignore index 4643006..3a056e0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,18 +2,18 @@ SOURCES/backports-3.24.1.gem SOURCES/childprocess-4.1.0.gem SOURCES/dacite-1.8.1.tar.gz SOURCES/ethon-0.16.0.gem -SOURCES/ffi-1.15.5.gem +SOURCES/ffi-1.16.3.gem SOURCES/mustermann-3.0.0.gem SOURCES/nio4r-2.5.9.gem -SOURCES/pcs-0.11.6.tar.gz -SOURCES/pcs-web-ui-0.1.17.tar.gz -SOURCES/pcs-web-ui-node-modules-0.1.17.tar.xz -SOURCES/puma-6.3.0.gem +SOURCES/pcs-0.11.7.tar.gz +SOURCES/pcs-web-ui-0.1.18.tar.gz +SOURCES/pcs-web-ui-node-modules-0.1.18.tar.xz +SOURCES/puma-6.4.0.gem SOURCES/pyagentx-0.4.pcs.2.tar.gz -SOURCES/rack-2.2.7.gem -SOURCES/rack-protection-3.0.6.gem +SOURCES/rack-2.2.8.gem +SOURCES/rack-protection-3.1.0.gem SOURCES/rack-test-2.1.0.gem SOURCES/ruby2_keywords-0.0.5.gem -SOURCES/sinatra-3.0.6.gem -SOURCES/tilt-2.2.0.gem -SOURCES/tornado-6.3.2.tar.gz +SOURCES/sinatra-3.1.0.gem +SOURCES/tilt-2.3.0.gem +SOURCES/tornado-6.3.3.tar.gz diff --git a/.pcs.metadata b/.pcs.metadata index 93db717..255bf22 100644 --- a/.pcs.metadata +++ b/.pcs.metadata @@ -2,18 +2,18 @@ 81639c8886342e01d189c10a6beab6ad0526dc4e SOURCES/childprocess-4.1.0.gem 07b26abbf7ff0dcba5c7f9e814ff7eebafefb058 SOURCES/dacite-1.8.1.tar.gz 5b56a68268708c474bef04550639ded3add5e946 SOURCES/ethon-0.16.0.gem -97632b7975067266c0b39596de0a4c86d9330658 SOURCES/ffi-1.15.5.gem +10e4cf0e11ef4581ec4ad5fe2cdf3c78b6077d39 SOURCES/ffi-1.16.3.gem e892678aaf02ccb27f3a6cd58482cda00aea6ce8 SOURCES/mustermann-3.0.0.gem 2f65d371f5f37460ad74afcedcb97d2b41a46806 SOURCES/nio4r-2.5.9.gem -18622c035d84535340ff004083170497f8cb5414 SOURCES/pcs-0.11.6.tar.gz -2d6a9f30b5c14351fc6c68d9a5519dc9f756c6ec SOURCES/pcs-web-ui-0.1.17.tar.gz -2b799265059e4c2008187ca32233cf4e04a9a2fb SOURCES/pcs-web-ui-node-modules-0.1.17.tar.xz -77e706abd5f50cc4b6e175931dda25e902c46018 SOURCES/puma-6.3.0.gem +3aec6fd614169e4d0272a71eb3688ad3a54f91b3 SOURCES/pcs-0.11.7.tar.gz +59d3e570bcbb7b3bcb2b9bf519425b2036e0faad SOURCES/pcs-web-ui-0.1.18.tar.gz +252cc42bf9715209c67981da06f2791a91c2f3fb SOURCES/pcs-web-ui-node-modules-0.1.18.tar.xz +d6049c4555f3c9d198e6eb1d7e53ce9b68e175ff SOURCES/puma-6.4.0.gem 3176b2f2b332c2b6bf79fe882e83feecf3d3f011 SOURCES/pyagentx-0.4.pcs.2.tar.gz -f60fea8846d18d6389fe22ef431fac9e9a8d796d SOURCES/rack-2.2.7.gem -c212026bdfdb35923d1e46fed2c2c5ac1fd8faa1 SOURCES/rack-protection-3.0.6.gem +f0cdb53d6ed96f91851c32bccf9b21c4662afd2d SOURCES/rack-2.2.8.gem +d34d1d308e3a1028c85bd0a7e4ba1d4f1ec0f725 SOURCES/rack-protection-3.1.0.gem ae09ea83748b55875edc3708fffba90db180cb8e SOURCES/rack-test-2.1.0.gem d017b9e4d1978e0b3ccc3e2a31493809e4693cd3 SOURCES/ruby2_keywords-0.0.5.gem -71fb81a5bad00452c5fdaeacf74e053415a7c2e5 SOURCES/sinatra-3.0.6.gem -6a06f99324246b8d4c443ce596789e64a0c05fe4 SOURCES/tilt-2.2.0.gem -877e6035bee39ed749f2b05bffb515fc772101e3 SOURCES/tornado-6.3.2.tar.gz +cd57dfa17b103c514dd0b107ebda6ee4bfb6b0d4 SOURCES/sinatra-3.1.0.gem +4a38a9a55887b2882182a2c5771e592efe514e5e SOURCES/tilt-2.3.0.gem +4db49c4d5570e6fdc7ec845335bb341ebd5346a7 SOURCES/tornado-6.3.3.tar.gz diff --git a/SOURCES/bz2163953-01-constraint-fixes.patch b/SOURCES/bz2163953-01-constraint-fixes.patch deleted file mode 100644 index 150a37e..0000000 --- a/SOURCES/bz2163953-01-constraint-fixes.patch +++ /dev/null @@ -1,1407 +0,0 @@ -From cb31390fb04623250db814d08e84a0a1981916c7 Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek -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 @@ - - - -- -- -+ -+ -+ - -- -+ - -+ - - - -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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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 - diff --git a/SOURCES/bz2217850-01-fix-exporting-location-constraints-with-rules.patch b/SOURCES/bz2217850-01-fix-exporting-location-constraints-with-rules.patch deleted file mode 100644 index d3cdb06..0000000 --- a/SOURCES/bz2217850-01-fix-exporting-location-constraints-with-rules.patch +++ /dev/null @@ -1,302 +0,0 @@ -From 24a8e84e3f81fc846a8d60dc636c9d42fc7a0cd8 Mon Sep 17 00:00:00 2001 -From: Miroslav Lisik -Date: Tue, 4 Jul 2023 21:43:38 +0200 -Subject: [PATCH 2/3] fix displaying duplicate records in property commands - ---- - pcs/cli/cluster_property/output.py | 65 +++++++++---------- - .../cli/cluster_property/test_command.py | 15 +++++ - .../tier0/cli/cluster_property/test_output.py | 31 ++++++--- - .../lib/commands/test_cluster_property.py | 28 ++++++++ - 4 files changed, 93 insertions(+), 46 deletions(-) - -diff --git a/pcs/cli/cluster_property/output.py b/pcs/cli/cluster_property/output.py -index c538c5c1..c9c46d1c 100644 ---- a/pcs/cli/cluster_property/output.py -+++ b/pcs/cli/cluster_property/output.py -@@ -31,21 +31,15 @@ class PropertyConfigurationFacade: - readonly_properties: StringCollection, - ) -> None: - self._properties = properties -+ self._first_nvpair_set = ( -+ self._properties[0].nvpairs if self._properties else [] -+ ) - self._properties_metadata = properties_metadata - self._readonly_properties = readonly_properties -- self._defaults_map = { -- metadata.name: metadata.default -- for metadata in self._properties_metadata -- if metadata.default is not None -+ self._defaults_map = self.get_defaults(include_advanced=True) -+ self._name_nvpair_dto_map = { -+ nvpair_dto.name: nvpair_dto for nvpair_dto in self._first_nvpair_set - } -- self._name_nvpair_dto_map = ( -- { -- nvpair_dto.name: nvpair_dto -- for nvpair_dto in self._properties[0].nvpairs -- } -- if self._properties -- else {} -- ) - - @classmethod - def from_properties_dtos( -@@ -105,17 +99,6 @@ class PropertyConfigurationFacade: - return value - return self._defaults_map.get(property_name, custom_default) - -- @staticmethod -- def _filter_names_advanced( -- metadata: ResourceAgentParameterDto, -- property_names: Optional[StringSequence] = None, -- include_advanced: bool = False, -- ) -> bool: -- return bool( -- (not property_names and (include_advanced or not metadata.advanced)) -- or (property_names and metadata.name in property_names) -- ) -- - def get_defaults( - self, - property_names: Optional[StringSequence] = None, -@@ -123,11 +106,10 @@ class PropertyConfigurationFacade: - ) -> dict[str, str]: - return { - metadata.name: metadata.default -- for metadata in self._properties_metadata -- if metadata.default is not None -- and self._filter_names_advanced( -- metadata, property_names, include_advanced -+ for metadata in self.get_properties_metadata( -+ property_names, include_advanced - ) -+ if metadata.default is not None - } - - def get_properties_metadata( -@@ -135,23 +117,34 @@ class PropertyConfigurationFacade: - property_names: Optional[StringSequence] = None, - include_advanced: bool = False, - ) -> Sequence[ResourceAgentParameterDto]: -- return [ -- metadata -- for metadata in self._properties_metadata -- if self._filter_names_advanced( -- metadata, property_names, include_advanced -- ) -- ] -+ if property_names: -+ filtered_metadata = [ -+ metadata -+ for metadata in self._properties_metadata -+ if metadata.name in property_names -+ ] -+ else: -+ filtered_metadata = [ -+ metadata -+ for metadata in self._properties_metadata -+ if include_advanced or not metadata.advanced -+ ] -+ deduplicated_metadata = { -+ metadata.name: metadata for metadata in filtered_metadata -+ } -+ return list(deduplicated_metadata.values()) - - def get_name_value_default_list(self) -> list[tuple[str, str, bool]]: - name_value_default_list = [ - (nvpair_dto.name, nvpair_dto.value, False) -- for nvpair_dto in self._name_nvpair_dto_map.values() -+ for nvpair_dto in self._first_nvpair_set - ] - name_value_default_list.extend( - [ - (metadata_dto.name, metadata_dto.default, True) -- for metadata_dto in self._properties_metadata -+ for metadata_dto in self.get_properties_metadata( -+ include_advanced=True -+ ) - if metadata_dto.name not in self._name_nvpair_dto_map - and metadata_dto.default is not None - ] -diff --git a/pcs_test/tier0/cli/cluster_property/test_command.py b/pcs_test/tier0/cli/cluster_property/test_command.py -index b54d0e58..f8cc2afa 100644 ---- a/pcs_test/tier0/cli/cluster_property/test_command.py -+++ b/pcs_test/tier0/cli/cluster_property/test_command.py -@@ -21,6 +21,21 @@ from pcs_test.tools.misc import dict_to_modifiers - - FIXTURE_PROPERTY_METADATA = ClusterPropertyMetadataDto( - properties_metadata=[ -+ ResourceAgentParameterDto( -+ name="property_name", -+ shortdesc="Duplicate property", -+ longdesc=None, -+ type="string", -+ default="duplicate_default", -+ enum_values=None, -+ required=False, -+ advanced=False, -+ deprecated=False, -+ deprecated_by=[], -+ deprecated_desc=None, -+ unique_group=None, -+ reloadable=False, -+ ), - ResourceAgentParameterDto( - name="property_name", - shortdesc=None, -diff --git a/pcs_test/tier0/cli/cluster_property/test_output.py b/pcs_test/tier0/cli/cluster_property/test_output.py -index 0ce8f6a8..59d33466 100644 ---- a/pcs_test/tier0/cli/cluster_property/test_output.py -+++ b/pcs_test/tier0/cli/cluster_property/test_output.py -@@ -21,6 +21,7 @@ FIXTURE_TWO_PROPERTY_SETS = [ - CibNvpairDto(id="", name="readonly2", value="ro_val2"), - CibNvpairDto(id="", name="property2", value="val2"), - CibNvpairDto(id="", name="property1", value="val1"), -+ CibNvpairDto(id="", name="property1", value="duplicate_val1"), - ], - ), - CibNvsetDto( -@@ -39,6 +40,7 @@ FIXTURE_READONLY_PROPERTIES_LIST = ["readonly1", "readonly2"] - FIXTURE_TEXT_OUTPUT_FIRST_SET = dedent( - """\ - Cluster Properties: id1 score=150 -+ property1=duplicate_val1 - property1=val1 - property2=val2 - readonly1=ro_val1 -@@ -75,6 +77,7 @@ def fixture_property_metadata( - - - FIXTURE_PROPERTY_METADATA_LIST = [ -+ fixture_property_metadata(name="property1", default="duplicate_default1"), - fixture_property_metadata(name="property1", default="default1"), - fixture_property_metadata(name="property2", default="default2"), - fixture_property_metadata( -@@ -136,7 +139,7 @@ class TestPropertyConfigurationFacadeGetPropertyValue(TestCase): - ) - - def test_property_value_from_first_set(self): -- self.assertEqual(self.facade.get_property_value("property1"), "val1") -+ self.assertEqual(self.facade.get_property_value("property2"), "val2") - - def test_property_value_from_second_set(self): - self.assertEqual(self.facade.get_property_value("property3"), None) -@@ -152,6 +155,11 @@ class TestPropertyConfigurationFacadeGetPropertyValue(TestCase): - "custom", - ) - -+ def test_property_with_multiple_values(self): -+ self.assertEqual( -+ self.facade.get_property_value("property1"), "duplicate_val1" -+ ) -+ - - class TestPropertyConfigurationFacadeGetPropertyValueOrDefault(TestCase): - def setUp(self): -@@ -163,7 +171,7 @@ class TestPropertyConfigurationFacadeGetPropertyValueOrDefault(TestCase): - - def test_property_value_from_first_set(self): - self.assertEqual( -- self.facade.get_property_value_or_default("property1"), "val1" -+ self.facade.get_property_value_or_default("property2"), "val2" - ) - - def test_property_value_not_in_set(self): -@@ -239,21 +247,22 @@ class TestPropertyConfigurationFacadeGetPropertiesMetadata(TestCase): - ) - - def test_metadata_without_advanced(self): -- metadata = FIXTURE_PROPERTY_METADATA_LIST[0:2] -- self.assertEqual(self.facade.get_properties_metadata(), metadata) -+ metadata = FIXTURE_PROPERTY_METADATA_LIST[1:3] -+ self.assertCountEqual(self.facade.get_properties_metadata(), metadata) - - def test_metadata_with_advanced(self): -- metadata = FIXTURE_PROPERTY_METADATA_LIST -- self.assertEqual( -- self.facade.get_properties_metadata(include_advanced=True), metadata -+ metadata = FIXTURE_PROPERTY_METADATA_LIST[1:] -+ self.assertCountEqual( -+ self.facade.get_properties_metadata(include_advanced=True), -+ metadata, - ) - - def test_metadata_specified(self): - metadata = ( -- FIXTURE_PROPERTY_METADATA_LIST[0:1] -+ FIXTURE_PROPERTY_METADATA_LIST[1:2] - + FIXTURE_PROPERTY_METADATA_LIST[-1:] - ) -- self.assertEqual( -+ self.assertCountEqual( - self.facade.get_properties_metadata( - property_names=["property4", "property1"] - ), -@@ -275,6 +284,7 @@ class TestPropertyConfigurationFacadeGetNameValueDefaultList(TestCase): - ("readonly2", "ro_val2", False), - ("property2", "val2", False), - ("property1", "val1", False), -+ ("property1", "duplicate_val1", False), - ("property3", "default3", True), - ("property4", "default4", True), - ] -@@ -503,7 +513,8 @@ class TestPropertiesToCmd(TestCase): - """\ - pcs property set --force -- \\ - property2=val2 \\ -- property1=val1 -+ property1=val1 \\ -+ property1=duplicate_val1 - """ - ) - self.assert_lines(facade, output) -diff --git a/pcs_test/tier0/lib/commands/test_cluster_property.py b/pcs_test/tier0/lib/commands/test_cluster_property.py -index c7cb7ae5..c02761a0 100644 ---- a/pcs_test/tier0/lib/commands/test_cluster_property.py -+++ b/pcs_test/tier0/lib/commands/test_cluster_property.py -@@ -911,6 +911,10 @@ class TestGetProperties(TestCase): - ) - self.env_assist.assert_reports([]) - -+ @mock.patch( -+ "pcs.lib.cib.rule.in_effect.has_rule_in_effect_status_tool", -+ lambda: True, -+ ) - def test_evaluate_expired_but_no_set_rule(self): - self.config.runner.cib.load( - crm_config=fixture_crm_config_properties([("set_id", {})]) -@@ -924,6 +928,30 @@ class TestGetProperties(TestCase): - ), - ) - -+ @mock.patch( -+ "pcs.lib.cib.rule.in_effect.has_rule_in_effect_status_tool", -+ lambda: False, -+ ) -+ def test_evaluate_expired_no_status_tool(self): -+ self.config.runner.cib.load( -+ crm_config=fixture_crm_config_properties([("set_id", {})]) -+ ) -+ self.assertEqual( -+ self.command(evaluate_expired=True), -+ ListCibNvsetDto( -+ nvsets=[ -+ CibNvsetDto(id="set_id", options={}, rule=None, nvpairs=[]) -+ ] -+ ), -+ ) -+ self.env_assist.assert_reports( -+ [ -+ fixture.warn( -+ reports.codes.RULE_IN_EFFECT_STATUS_DETECTION_NOT_SUPPORTED, -+ ) -+ ] -+ ) -+ - - class TestGetPropertiesMetadata(MetadataErrorMixin, TestCase): - _load_cib_when_metadata_error = False --- -2.41.0 - diff --git a/SOURCES/bz2219407-01-use-a-filter-when-extracting-a-config-backup-tarball.patch b/SOURCES/bz2219407-01-use-a-filter-when-extracting-a-config-backup-tarball.patch deleted file mode 100644 index f86e29a..0000000 --- a/SOURCES/bz2219407-01-use-a-filter-when-extracting-a-config-backup-tarball.patch +++ /dev/null @@ -1,55 +0,0 @@ -From e47799cbdd588649872efd24d6bcfa78acb23ecb Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek -Date: Tue, 11 Jul 2023 14:09:17 +0200 -Subject: [PATCH 3/3] use a filter when extracting a config backup tarball - ---- - pcs/config.py | 26 ++++++++++++++++++++++++-- - 1 file changed, 24 insertions(+), 2 deletions(-) - -diff --git a/pcs/config.py b/pcs/config.py -index 56c49aae..d750f52f 100644 ---- a/pcs/config.py -+++ b/pcs/config.py -@@ -488,14 +488,36 @@ def config_restore_local(infile_name, infile_obj): - if "rename" in extract_info and extract_info["rename"]: - if tmp_dir is None: - tmp_dir = tempfile.mkdtemp() -- tarball.extractall(tmp_dir, [tar_member_info]) -+ if hasattr(tarfile, "data_filter"): -+ # Safe way of extraction is available since Python 3.12, -+ # hasattr above checks if it's available. -+ # It's also backported to 3.11.4, 3.10.12, 3.9.17. -+ # It may be backported to older versions in downstream. -+ tarball.extractall( -+ tmp_dir, [tar_member_info], filter="data" -+ ) -+ else: -+ # Unsafe way of extraction -+ # Remove once we don't support Python 3.8 and older -+ tarball.extractall(tmp_dir, [tar_member_info]) - path_full = extract_info["path"] - shutil.move( - os.path.join(tmp_dir, tar_member_info.name), path_full - ) - else: - dir_path = os.path.dirname(extract_info["path"]) -- tarball.extractall(dir_path, [tar_member_info]) -+ if hasattr(tarfile, "data_filter"): -+ # Safe way of extraction is available since Python 3.12, -+ # hasattr above checks if it's available. -+ # It's also backported to 3.11.4, 3.10.12, 3.9.17. -+ # It may be backported to older versions in downstream. -+ tarball.extractall( -+ dir_path, [tar_member_info], filter="data" -+ ) -+ else: -+ # Unsafe way of extracting -+ # Remove once we don't support Python 3.8 and older -+ tarball.extractall(dir_path, [tar_member_info]) - path_full = os.path.join(dir_path, tar_member_info.name) - file_attrs = extract_info["attrs"] - os.chmod(path_full, file_attrs["mode"]) --- -2.41.0 - diff --git a/SOURCES/bz2222788-01-fix-assets-url-relative-absolute.patch b/SOURCES/bz2222788-01-fix-assets-url-relative-absolute.patch deleted file mode 100644 index 33ee7c6..0000000 --- a/SOURCES/bz2222788-01-fix-assets-url-relative-absolute.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 074e24f039a5bdfa81d17feb390dd342eda5ba73 Mon Sep 17 00:00:00 2001 -From: Ivan Devat -Date: Fri, 14 Jul 2023 14:44:20 +0200 -Subject: [PATCH] fix assets url (relative -> absolute) - ---- - .bin/build.sh | 16 +++++++++++----- - 1 file changed, 11 insertions(+), 5 deletions(-) - -diff --git a/.bin/build.sh b/.bin/build.sh -index 2113eaae..c7f0b4a0 100755 ---- a/.bin/build.sh -+++ b/.bin/build.sh -@@ -74,10 +74,15 @@ inject_built_assets() { - } - - fix_asset_paths() { -- # All assets in index.html uses absolute path. This function makes them -- # relative. The index.html is also used by development server which needs -- # absolute paths. There is no copy/edit phase in development server, so it is -- # done here. -+ # All assets in index.html uses absolute path. The index.html is also used by -+ # development server which needs absolute paths. There is no copy/edit phase -+ # in development server, so it is done here. -+ # Here is the absolute path prefixed according to pcsd url namespace for -+ # webui. -+ # WARNING: Don't use relative path. It works well in dashboard but in the -+ # cluster detail the resulting url contains word "cluster" inside, so instead -+ # of "/ui/static/..." we get "/ui/cluster/static" and asset loading fails. -+ # see: https://bugzilla.redhat.com/show_bug.cgi?id=2222788 - html=$1 - path_prefix=$2 - js_path="$3/" -@@ -130,6 +135,7 @@ adapt_for_environment() { - } - - use_current_node_modules=${BUILD_USE_CURRENT_NODE_MODULES:-"false"} -+url_prefix=${PCSD_BUILD_URL_PREFIX:-"/ui"} - node_modules=$(get_path "appNodeModules") - node_modules_backup="${node_modules}.build-backup" - export BUILD_DIR="${BUILD_DIR:-$(realpath "$(dirname "$0")"/../build)}" -@@ -155,7 +161,7 @@ adapt_for_environment \ - static/js/adapterCockpit.js \ - "${PCSD_UINIX_SOCKET:-"/var/run/pcsd.socket"}" - --fix_asset_paths "$BUILD_DIR"/index.html "." \ -+fix_asset_paths "$BUILD_DIR"/index.html "$url_prefix" \ - static/js \ - static/css \ - manifest.json \ --- -2.41.0 - diff --git a/SOURCES/do-not-support-cluster-setup-with-udp-u-transport.patch b/SOURCES/do-not-support-cluster-setup-with-udp-u-transport.patch index 8127728..168a20f 100644 --- a/SOURCES/do-not-support-cluster-setup-with-udp-u-transport.patch +++ b/SOURCES/do-not-support-cluster-setup-with-udp-u-transport.patch @@ -1,4 +1,4 @@ -From 948eb79f2f4057ac8143242b29e8c164ee4516ed Mon Sep 17 00:00:00 2001 +From cf1e0cc06a94804a4a98a12ee06d09e5786bad1b Mon Sep 17 00:00:00 2001 From: Ivan Devat Date: Tue, 20 Nov 2018 15:03:56 +0100 Subject: [PATCH] do not support cluster setup with udp(u) transport in RHEL9 @@ -9,7 +9,7 @@ Subject: [PATCH] do not support cluster setup with udp(u) transport in RHEL9 2 files changed, 3 insertions(+) diff --git a/pcs/pcs.8.in b/pcs/pcs.8.in -index 2baf0009..9b2f7388 100644 +index 55f4b4a9..8cc9360d 100644 --- a/pcs/pcs.8.in +++ b/pcs/pcs.8.in @@ -479,6 +479,8 @@ By default, encryption is enabled with cipher=aes256 and hash=sha256. To disable @@ -22,10 +22,10 @@ index 2baf0009..9b2f7388 100644 .br Transport options are: ip_version, netmtu diff --git a/pcs/usage.py b/pcs/usage.py -index df149e5b..c915b513 100644 +index cc6c5803..a7d4b24b 100644 --- a/pcs/usage.py +++ b/pcs/usage.py -@@ -1486,6 +1486,7 @@ Commands: +@@ -1482,6 +1482,7 @@ Commands: hash=sha256. To disable encryption, set cipher=none and hash=none. Transports udp and udpu: @@ -34,5 +34,5 @@ index df149e5b..c915b513 100644 support traffic encryption nor compression. Transport options are: -- -2.41.0 +2.43.0 diff --git a/SPECS/pcs.spec b/SPECS/pcs.spec index 421e6cf..60f2a13 100644 --- a/SPECS/pcs.spec +++ b/SPECS/pcs.spec @@ -1,6 +1,6 @@ Name: pcs -Version: 0.11.6 -Release: 3%{?dist} +Version: 0.11.7 +Release: 1%{?dist} # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ # https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses # GPL-2.0-only: pcs @@ -20,32 +20,32 @@ ExclusiveArch: i686 x86_64 s390x ppc64le aarch64 # When specifying a commit, use its long hash %global version_or_commit %{version} -# %%global version_or_commit 3e479bdb68dc900523a743e7dcb759b501385555 +# %%global version_or_commit aaa16e0de986890e6ca3038f907bbad331e41a87 %global pcs_source_name %{name}-%{version_or_commit} # ui_commit can be determined by hash, tag or branch -%global ui_commit 0.1.17 -%global ui_modules_version 0.1.17 +%global ui_commit 0.1.18 +%global ui_modules_version 0.1.18 %global ui_src_name pcs-web-ui-%{ui_commit} %global pcs_snmp_pkg_name pcs-snmp %global pyagentx_version 0.4.pcs.2 -%global tornado_version 6.3.2 +%global tornado_version 6.3.3 %global dacite_version 1.8.1 %global version_rubygem_backports 3.24.1 %global version_rubygem_childprocess 4.1.0 %global version_rubygem_ethon 0.16.0 -%global version_rubygem_ffi 1.15.5 +%global version_rubygem_ffi 1.16.3 %global version_rubygem_mustermann 3.0.0 %global version_rubygem_nio4r 2.5.9 -%global version_rubygem_puma 6.3.0 -%global version_rubygem_rack 2.2.7 -%global version_rubygem_rack_protection 3.0.6 +%global version_rubygem_puma 6.4.0 +%global version_rubygem_rack 2.2.8 +%global version_rubygem_rack_protection 3.1.0 %global version_rubygem_rack_test 2.1.0 %global version_rubygem_ruby2_keywords 0.0.5 -%global version_rubygem_sinatra 3.0.6 -%global version_rubygem_tilt 2.2.0 +%global version_rubygem_sinatra 3.1.0 +%global version_rubygem_tilt 2.3.0 %global required_pacemaker_version 2.1.0 @@ -101,13 +101,9 @@ Source101: https://github.com/ClusterLabs/pcs-web-ui/releases/download/%{ui_comm # pcs patches: <= 200 # Patch0: bzNUMBER-01-name.patch Patch0: do-not-support-cluster-setup-with-udp-u-transport.patch -Patch1: bz2163953-01-constraint-fixes.patch -Patch2: bz2217850-01-fix-exporting-location-constraints-with-rules.patch -Patch3: bz2219407-01-use-a-filter-when-extracting-a-config-backup-tarball.patch # ui patches: >200 # Patch201: bzNUMBER-01-name.patch -Patch201: bz2222788-01-fix-assets-url-relative-absolute.patch # git for patches BuildRequires: git-core @@ -291,16 +287,12 @@ update_times_patch(){ %autosetup -D -T -b 100 -a 101 -S git -n %{ui_src_name} -N %autopatch -p1 -m 201 # update_times_patch %%{PATCH201} -update_times_patch %{PATCH201} # patch pcs sources %autosetup -S git -n %{pcs_source_name} -N %autopatch -p1 -M 200 # update_times_patch %%{PATCH0} update_times_patch %{PATCH0} -update_times_patch %{PATCH1} -update_times_patch %{PATCH2} -update_times_patch %{PATCH3} # generate .tarball-version if building from an untagged commit, not a released version # autogen uses git-version-gen which uses .tarball-version for generating version number @@ -338,7 +330,10 @@ cp -f %SOURCE44 rpm/ %define debug_package %{nil} ./autogen.sh -%{configure} --enable-local-build --enable-use-local-cache-only --enable-individual-bundling --enable-booth-enable-authfile-set --enable-booth-enable-authfile-unset PYTHON=%{__python3} ruby_CFLAGS="%{optflags}" ruby_LIBS="%{build_ldflags}" +%{configure} --enable-local-build --enable-use-local-cache-only \ + --enable-individual-bundling --with-pcsd-default-cipherlist='PROFILE=SYSTEM' \ + --enable-booth-enable-authfile-unset --enable-booth-enable-authfile-set \ + PYTHON=%{__python3} ruby_CFLAGS="%{optflags}" ruby_LIBS="%{build_ldflags}" make all # build pcs-web-ui @@ -350,6 +345,10 @@ pwd %make_install +# RHEL-7716 - fix rubygem permissions - remove write access for owner's group +# and other users +chmod --recursive g-w,o-w ${RPM_BUILD_ROOT}%{_libdir}/%{rubygem_bundle_dir} + # install pcs-web-ui cp -r %{_builddir}/%{ui_src_name}/build ${RPM_BUILD_ROOT}%{_libdir}/%{pcsd_public_dir}/ui @@ -537,6 +536,26 @@ run_all_tests %license pyagentx_LICENSE.txt %changelog +* Fri Jan 05 2024 Michal Pospisil - 0.11.7-1 +- Rebased to the latest sources (see CHANGELOG.md) + Resolves: RHEL-7740 + +* Mon Nov 13 2023 Michal Pospisil - 0.11.6-6 +- Rebased to the latest upstream sources (see CHANGELOG.md) + Resolves: RHEL-7582, RHEL-7583, RHEL-7669, RHEL-7672, RHEL-7697, RHEL-7698, RHEL-7700, RHEL-7703, RHEL-7719, RHEL-7725, RHEL-7730, RHEL-7738, RHEL-7739, RHEL-7740, RHEL-7744, RHEL-7746 +- TLS cipher setting in pcsd now follows system-wide crypto policies by default + Resolves: RHEL-7724 +- Tightened permissions of bundled rubygems to be 755 or stricter + Resolves: RHEL-7716 + +* Thu Nov 2 2023 Michal Pospisil - 0.11.6-5 +- No changes, fixing an error in a new quality control process +- Resolves: RHEL-15217 + +* Fri Oct 27 2023 Michal Pospisil - 0.11.6-4 +- No changes, testing a new quality control process +- Resolves: RHEL-15217 + * Fri Jul 14 2023 Michal Pospisil - 0.11.6-3 - Refreshing any page in pcs-web-ui no longer causes it to display a blank page - Resolves: rhbz#2222788