diff --git a/SOURCES/bz1843079-01-upgrade-CIB-schema-for-on-fail-demote.patch b/SOURCES/bz1843079-01-upgrade-CIB-schema-for-on-fail-demote.patch new file mode 100644 index 0000000..c74ad24 --- /dev/null +++ b/SOURCES/bz1843079-01-upgrade-CIB-schema-for-on-fail-demote.patch @@ -0,0 +1,1307 @@ +From 62b970d5e9edbdd68dc006193b0e606fb7ae7cdd Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek +Date: Thu, 2 Jul 2020 15:18:29 +0200 +Subject: [PATCH 1/3] upgrade CIB schema for on-fail=demote + +--- + pcs/lib/cib/resource/operations.py | 9 +- + pcs/lib/commands/remote_node.py | 73 +++-- + pcs/lib/commands/resource.py | 269 ++++++++++-------- + pcs/resource.py | 51 +++- + pcs_test/resources/cib-empty-3.3.xml | 2 +- + pcs_test/resources/cib-empty-3.4.xml | 2 +- + .../tier0/lib/cib/test_resource_operations.py | 9 +- + .../remote_node/test_node_add_remote.py | 54 +++- + .../commands/resource/test_resource_create.py | 237 ++++++++++++++- + pcs_test/tier1/cib_resource/test_create.py | 2 +- + pcs_test/tier1/legacy/test_resource.py | 23 ++ + pcs_test/tools/misc.py | 6 + + pcsd/capabilities.xml | 42 +++ + 13 files changed, 594 insertions(+), 185 deletions(-) + +diff --git a/pcs/lib/cib/resource/operations.py b/pcs/lib/cib/resource/operations.py +index 131e0a49..79d00685 100644 +--- a/pcs/lib/cib/resource/operations.py ++++ b/pcs/lib/cib/resource/operations.py +@@ -39,13 +39,14 @@ ATTRIBUTES = [ + ] + + ON_FAIL_VALUES = [ +- "ignore", + "block", +- "stop", +- "restart", +- "standby", ++ "demote", + "fence", ++ "ignore", ++ "restart", + "restart-container", ++ "standby", ++ "stop", + ] + + BOOLEAN_VALUES = [ +diff --git a/pcs/lib/commands/remote_node.py b/pcs/lib/commands/remote_node.py +index 6a2656a5..575e8044 100644 +--- a/pcs/lib/commands/remote_node.py ++++ b/pcs/lib/commands/remote_node.py +@@ -1,3 +1,10 @@ ++from typing import ( ++ Iterable, ++ Mapping, ++ Optional, ++ Union, ++) ++ + from pcs import settings + from pcs.common import reports + from pcs.common.file import RawFileError +@@ -13,6 +20,10 @@ from pcs.lib.cib.tools import ( + ElementSearcher, + get_resources, + ) ++ ++# TODO lib.commands should never import each other. This is to be removed when ++# the 'resource create' commands are overhauled. ++from pcs.lib.commands.resource import get_required_cib_version_for_primitive + from pcs.lib.communication.nodes import ( + DistributeFiles, + GetHostInfo, +@@ -24,6 +35,7 @@ from pcs.lib.communication.tools import ( + run as run_com, + run_and_raise, + ) ++from pcs.lib.corosync.config_facade import ConfigFacade as CorosyncConfigFacade + from pcs.lib.env import LibraryEnvironment + from pcs.lib.errors import LibraryError + from pcs.lib.file.instance import FileInstance +@@ -33,6 +45,9 @@ from pcs.lib.pacemaker import state + from pcs.lib.pacemaker.live import remove_node + + ++WaitType = Union[None, bool, int] ++ ++ + def _reports_skip_new_node(new_node_name, reason_type): + assert reason_type in {"unreachable", "not_live_cib"} + return [ +@@ -220,19 +235,19 @@ def _ensure_resource_running(env: LibraryEnvironment, resource_id): + + + def node_add_remote( +- env, +- node_name, +- node_addr, +- operations, +- meta_attributes, +- instance_attributes, +- skip_offline_nodes=False, +- allow_incomplete_distribution=False, +- allow_pacemaker_remote_service_fail=False, +- allow_invalid_operation=False, +- allow_invalid_instance_attributes=False, +- use_default_operations=True, +- wait=False, ++ env: LibraryEnvironment, ++ node_name: str, ++ node_addr: Optional[str], ++ operations: Iterable[Mapping[str, str]], ++ meta_attributes: Mapping[str, str], ++ instance_attributes: Mapping[str, str], ++ skip_offline_nodes: bool = False, ++ allow_incomplete_distribution: bool = False, ++ allow_pacemaker_remote_service_fail: bool = False, ++ allow_invalid_operation: bool = False, ++ allow_invalid_instance_attributes: bool = False, ++ use_default_operations: bool = True, ++ wait: WaitType = False, + ): + # pylint: disable=too-many-arguments + # pylint: disable=too-many-branches +@@ -241,34 +256,36 @@ def node_add_remote( + """ + create an ocf:pacemaker:remote resource and use it as a remote node + +- LibraryEnvironment env -- provides all for communication with externals +- string node_name -- the name of the new node +- mixed node_addr -- the address of the new node or None for default +- list of dict operations -- attributes for each entered operation +- dict meta_attributes -- attributes for primitive/meta_attributes +- dict instance_attributes -- attributes for primitive/instance_attributes +- bool skip_offline_nodes -- if True, ignore when some nodes are offline +- bool allow_incomplete_distribution -- if True, allow this command to ++ env -- provides all for communication with externals ++ node_name -- the name of the new node ++ node_addr -- the address of the new node or None for default ++ operations -- attributes for each entered operation ++ meta_attributes -- attributes for primitive/meta_attributes ++ instance_attributes -- attributes for primitive/instance_attributes ++ skip_offline_nodes -- if True, ignore when some nodes are offline ++ allow_incomplete_distribution -- if True, allow this command to + finish successfully even if file distribution did not succeed +- bool allow_pacemaker_remote_service_fail -- if True, allow this command to ++ allow_pacemaker_remote_service_fail -- if True, allow this command to + finish successfully even if starting/enabling pacemaker_remote did not + succeed +- bool allow_invalid_operation -- if True, allow to use operations that ++ allow_invalid_operation -- if True, allow to use operations that + are not listed in a resource agent metadata +- bool allow_invalid_instance_attributes -- if True, allow to use instance ++ allow_invalid_instance_attributes -- if True, allow to use instance + attributes that are not listed in a resource agent metadata and allow to + omit required instance_attributes +- bool use_default_operations -- if True, add operations specified in ++ use_default_operations -- if True, add operations specified in + a resource agent metadata to the resource +- mixed wait -- a flag for controlling waiting for pacemaker idle mechanism ++ wait -- a flag for controlling waiting for pacemaker idle mechanism + """ + env.ensure_wait_satisfiable(wait) + + report_processor = env.report_processor +- cib = env.get_cib() ++ cib = env.get_cib( ++ minimal_version=get_required_cib_version_for_primitive(operations) ++ ) + id_provider = IdProvider(cib) + if env.is_cib_live: +- corosync_conf = env.get_corosync_conf() ++ corosync_conf: Optional[CorosyncConfigFacade] = env.get_corosync_conf() + else: + corosync_conf = None + report_processor.report( +diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py +index 75826c9d..db4b7bb3 100644 +--- a/pcs/lib/commands/resource.py ++++ b/pcs/lib/commands/resource.py +@@ -61,6 +61,9 @@ from pcs.lib.resource_agent import ( + from pcs.lib.validate import ValueTimeInterval + + ++WaitType = Union[None, bool, int] ++ ++ + @contextmanager + def resource_environment( + env, +@@ -262,44 +265,43 @@ def _get_required_cib_version_for_container( + + + def create( +- env, +- resource_id, +- resource_agent_name, +- operation_list, +- meta_attributes, +- instance_attributes, +- allow_absent_agent=False, +- allow_invalid_operation=False, +- allow_invalid_instance_attributes=False, +- use_default_operations=True, +- ensure_disabled=False, +- wait=False, +- allow_not_suitable_command=False, ++ env: LibraryEnvironment, ++ resource_id: str, ++ resource_agent_name: str, ++ operation_list: Iterable[Mapping[str, str]], ++ meta_attributes: Mapping[str, str], ++ instance_attributes: Mapping[str, str], ++ allow_absent_agent: bool = False, ++ allow_invalid_operation: bool = False, ++ allow_invalid_instance_attributes: bool = False, ++ use_default_operations: bool = True, ++ ensure_disabled: bool = False, ++ wait: WaitType = False, ++ allow_not_suitable_command: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ +- Create resource in a cib. ++ Create a primitive resource in a cib. + +- LibraryEnvironment env provides all for communication with externals +- string resource_id is identifier of resource +- string resource_agent_name contains name for the identification of agent +- list of dict operation_list contains attributes for each entered operation +- dict meta_attributes contains attributes for primitive/meta_attributes +- dict instance_attributes contains attributes for +- primitive/instance_attributes +- bool allow_absent_agent is a flag for allowing agent that is not installed ++ env -- provides all for communication with externals ++ resource_id -- is identifier of resource ++ resource_agent_name -- contains name for the identification of agent ++ operation_list -- contains attributes for each entered operation ++ meta_attributes -- contains attributes for primitive/meta_attributes ++ instance_attributes -- contains attributes for primitive/instance_attributes ++ allow_absent_agent -- is a flag for allowing agent that is not installed + in a system +- bool allow_invalid_operation is a flag for allowing to use operations that ++ allow_invalid_operation -- is a flag for allowing to use operations that + are not listed in a resource agent metadata +- bool allow_invalid_instance_attributes is a flag for allowing to use ++ allow_invalid_instance_attributes -- is a flag for allowing to use + instance attributes that are not listed in a resource agent metadata + or for allowing to not use the instance_attributes that are required in + resource agent metadata +- bool use_default_operations is a flag for stopping stopping of adding ++ use_default_operations -- is a flag for stopping stopping of adding + default cib operations (specified in a resource agent) +- bool ensure_disabled is flag that keeps resource in target-role "Stopped" +- mixed wait is flag for controlling waiting for pacemaker idle mechanism +- bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND ++ ensure_disabled -- is flag that keeps resource in target-role "Stopped" ++ wait -- is flag for controlling waiting for pacemaker idle mechanism ++ allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND + """ + resource_agent = get_agent( + env.report_processor, +@@ -315,6 +317,9 @@ def create( + ensure_disabled + or resource.common.are_meta_disabled(meta_attributes) + ), ++ required_cib_version=get_required_cib_version_for_primitive( ++ operation_list ++ ), + ) as resources_section: + id_provider = IdProvider(resources_section) + _check_special_cases( +@@ -345,46 +350,45 @@ def create( + + + def create_as_clone( +- env, +- resource_id, +- resource_agent_name, +- operation_list, +- meta_attributes, +- instance_attributes, +- clone_meta_options, +- allow_absent_agent=False, +- allow_invalid_operation=False, +- allow_invalid_instance_attributes=False, +- use_default_operations=True, +- ensure_disabled=False, +- wait=False, +- allow_not_suitable_command=False, ++ env: LibraryEnvironment, ++ resource_id: str, ++ resource_agent_name: str, ++ operation_list: Iterable[Mapping[str, str]], ++ meta_attributes: Mapping[str, str], ++ instance_attributes: Mapping[str, str], ++ clone_meta_options: Mapping[str, str], ++ allow_absent_agent: bool = False, ++ allow_invalid_operation: bool = False, ++ allow_invalid_instance_attributes: bool = False, ++ use_default_operations: bool = True, ++ ensure_disabled: bool = False, ++ wait: WaitType = False, ++ allow_not_suitable_command: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ +- Create resource in a clone ++ Create a primitive resource in a clone + +- LibraryEnvironment env provides all for communication with externals +- string resource_id is identifier of resource +- string resource_agent_name contains name for the identification of agent +- list of dict operation_list contains attributes for each entered operation +- dict meta_attributes contains attributes for primitive/meta_attributes +- dict instance_attributes contains attributes for +- primitive/instance_attributes +- dict clone_meta_options contains attributes for clone/meta_attributes +- bool allow_absent_agent is a flag for allowing agent that is not installed ++ env -- provides all for communication with externals ++ resource_id -- is identifier of resource ++ resource_agent_name -- contains name for the identification of agent ++ operation_list -- contains attributes for each entered operation ++ meta_attributes -- contains attributes for primitive/meta_attributes ++ instance_attributes -- contains attributes for primitive/instance_attributes ++ clone_meta_options -- contains attributes for clone/meta_attributes ++ allow_absent_agent -- is a flag for allowing agent that is not installed + in a system +- bool allow_invalid_operation is a flag for allowing to use operations that ++ allow_invalid_operation -- is a flag for allowing to use operations that + are not listed in a resource agent metadata +- bool allow_invalid_instance_attributes is a flag for allowing to use ++ allow_invalid_instance_attributes -- is a flag for allowing to use + instance attributes that are not listed in a resource agent metadata + or for allowing to not use the instance_attributes that are required in + resource agent metadata +- bool use_default_operations is a flag for stopping stopping of adding ++ use_default_operations -- is a flag for stopping stopping of adding + default cib operations (specified in a resource agent) +- bool ensure_disabled is flag that keeps resource in target-role "Stopped" +- mixed wait is flag for controlling waiting for pacemaker idle mechanism +- bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND ++ ensure_disabled -- is flag that keeps resource in target-role "Stopped" ++ wait -- is flag for controlling waiting for pacemaker idle mechanism ++ allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND + """ + resource_agent = get_agent( + env.report_processor, +@@ -401,6 +405,9 @@ def create_as_clone( + or resource.common.are_meta_disabled(meta_attributes) + or resource.common.is_clone_deactivated_by_meta(clone_meta_options) + ), ++ required_cib_version=get_required_cib_version_for_primitive( ++ operation_list ++ ), + ) as resources_section: + id_provider = IdProvider(resources_section) + _check_special_cases( +@@ -437,49 +444,50 @@ def create_as_clone( + + + def create_in_group( +- env, +- resource_id, +- resource_agent_name, +- group_id, +- operation_list, +- meta_attributes, +- instance_attributes, +- allow_absent_agent=False, +- allow_invalid_operation=False, +- allow_invalid_instance_attributes=False, +- use_default_operations=True, +- ensure_disabled=False, +- adjacent_resource_id=None, +- put_after_adjacent=False, +- wait=False, +- allow_not_suitable_command=False, ++ env: LibraryEnvironment, ++ resource_id: str, ++ resource_agent_name: str, ++ group_id: str, ++ operation_list: Iterable[Mapping[str, str]], ++ meta_attributes: Mapping[str, str], ++ instance_attributes: Mapping[str, str], ++ allow_absent_agent: bool = False, ++ allow_invalid_operation: bool = False, ++ allow_invalid_instance_attributes: bool = False, ++ use_default_operations: bool = True, ++ ensure_disabled: bool = False, ++ adjacent_resource_id: Optional[str] = None, ++ put_after_adjacent: bool = False, ++ wait: WaitType = False, ++ allow_not_suitable_command: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ + Create resource in a cib and put it into defined group + +- LibraryEnvironment env provides all for communication with externals +- string resource_id is identifier of resource +- string resource_agent_name contains name for the identification of agent +- string group_id is identificator for group to put primitive resource inside +- list of dict operation_list contains attributes for each entered operation +- dict meta_attributes contains attributes for primitive/meta_attributes +- bool allow_absent_agent is a flag for allowing agent that is not installed ++ env -- provides all for communication with externals ++ resource_id -- is identifier of resource ++ resource_agent_name -- contains name for the identification of agent ++ group_id -- is identificator for group to put primitive resource inside ++ operation_list -- contains attributes for each entered operation ++ meta_attributes -- contains attributes for primitive/meta_attributes ++ instance_attributes -- contains attributes for primitive/instance_attributes ++ allow_absent_agent -- is a flag for allowing agent that is not installed + in a system +- bool allow_invalid_operation is a flag for allowing to use operations that ++ allow_invalid_operation -- is a flag for allowing to use operations that + are not listed in a resource agent metadata +- bool allow_invalid_instance_attributes is a flag for allowing to use ++ allow_invalid_instance_attributes -- is a flag for allowing to use + instance attributes that are not listed in a resource agent metadata + or for allowing to not use the instance_attributes that are required in + resource agent metadata +- bool use_default_operations is a flag for stopping stopping of adding ++ use_default_operations -- is a flag for stopping stopping of adding + default cib operations (specified in a resource agent) +- bool ensure_disabled is flag that keeps resource in target-role "Stopped" +- string adjacent_resource_id identify neighbor of a newly created resource +- bool put_after_adjacent is flag to put a newly create resource befor/after ++ ensure_disabled -- is flag that keeps resource in target-role "Stopped" ++ adjacent_resource_id -- identify neighbor of a newly created resource ++ put_after_adjacent -- is flag to put a newly create resource befor/after + adjacent resource +- mixed wait is flag for controlling waiting for pacemaker idle mechanism +- bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND ++ wait -- is flag for controlling waiting for pacemaker idle mechanism ++ allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND + """ + resource_agent = get_agent( + env.report_processor, +@@ -495,6 +503,9 @@ def create_in_group( + ensure_disabled + or resource.common.are_meta_disabled(meta_attributes) + ), ++ required_cib_version=get_required_cib_version_for_primitive( ++ operation_list ++ ), + ) as resources_section: + id_provider = IdProvider(resources_section) + _check_special_cases( +@@ -532,48 +543,48 @@ def create_in_group( + + + def create_into_bundle( +- env, +- resource_id, +- resource_agent_name, +- operation_list, +- meta_attributes, +- instance_attributes, +- bundle_id, +- allow_absent_agent=False, +- allow_invalid_operation=False, +- allow_invalid_instance_attributes=False, +- use_default_operations=True, +- ensure_disabled=False, +- wait=False, +- allow_not_suitable_command=False, +- allow_not_accessible_resource=False, ++ env: LibraryEnvironment, ++ resource_id: str, ++ resource_agent_name: str, ++ operation_list: Iterable[Mapping[str, str]], ++ meta_attributes: Mapping[str, str], ++ instance_attributes: Mapping[str, str], ++ bundle_id: str, ++ allow_absent_agent: bool = False, ++ allow_invalid_operation: bool = False, ++ allow_invalid_instance_attributes: bool = False, ++ use_default_operations: bool = True, ++ ensure_disabled: bool = False, ++ wait: WaitType = False, ++ allow_not_suitable_command: bool = False, ++ allow_not_accessible_resource: bool = False, + ): + # pylint: disable=too-many-arguments, too-many-locals + """ + Create a new resource in a cib and put it into an existing bundle + +- LibraryEnvironment env provides all for communication with externals +- string resource_id is identifier of resource +- string resource_agent_name contains name for the identification of agent +- list of dict operation_list contains attributes for each entered operation +- dict meta_attributes contains attributes for primitive/meta_attributes +- dict instance_attributes contains attributes for ++ env -- provides all for communication with externals ++ resource_id -- is identifier of resource ++ resource_agent_name -- contains name for the identification of agent ++ operation_list -- contains attributes for each entered operation ++ meta_attributes -- contains attributes for primitive/meta_attributes ++ instance_attributes -- contains attributes for + primitive/instance_attributes +- string bundle_id is id of an existing bundle to put the created resource in +- bool allow_absent_agent is a flag for allowing agent that is not installed ++ bundle_id -- is id of an existing bundle to put the created resource in ++ allow_absent_agent -- is a flag for allowing agent that is not installed + in a system +- bool allow_invalid_operation is a flag for allowing to use operations that ++ allow_invalid_operation -- is a flag for allowing to use operations that + are not listed in a resource agent metadata +- bool allow_invalid_instance_attributes is a flag for allowing to use ++ allow_invalid_instance_attributes -- is a flag for allowing to use + instance attributes that are not listed in a resource agent metadata + or for allowing to not use the instance_attributes that are required in + resource agent metadata +- bool use_default_operations is a flag for stopping stopping of adding ++ use_default_operations -- is a flag for stopping stopping of adding + default cib operations (specified in a resource agent) +- bool ensure_disabled is flag that keeps resource in target-role "Stopped" +- mixed wait is flag for controlling waiting for pacemaker idle mechanism +- bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND +- bool allow_not_accessible_resource -- flag for ++ ensure_disabled -- is flag that keeps resource in target-role "Stopped" ++ wait -- is flag for controlling waiting for pacemaker idle mechanism ++ allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND ++ allow_not_accessible_resource -- flag for + FORCE_RESOURCE_IN_BUNDLE_NOT_ACCESSIBLE + """ + resource_agent = get_agent( +@@ -582,6 +593,11 @@ def create_into_bundle( + resource_agent_name, + allow_absent_agent, + ) ++ required_cib_version = get_required_cib_version_for_primitive( ++ operation_list ++ ) ++ if not required_cib_version: ++ required_cib_version = Version(2, 8, 0) + with resource_environment( + env, + wait, +@@ -590,7 +606,7 @@ def create_into_bundle( + ensure_disabled + or resource.common.are_meta_disabled(meta_attributes) + ), +- required_cib_version=Version(2, 8, 0), ++ required_cib_version=required_cib_version, + ) as resources_section: + id_provider = IdProvider(resources_section) + _check_special_cases( +@@ -1070,9 +1086,7 @@ def disable_simulate( + + + def enable( +- env: LibraryEnvironment, +- resource_or_tag_ids: Iterable[str], +- wait: Optional[Union[bool, int]], ++ env: LibraryEnvironment, resource_or_tag_ids: Iterable[str], wait: WaitType, + ): + """ + Allow specified resources to be started by the cluster +@@ -1689,3 +1703,12 @@ def _find_resources_expand_tags_or_raise( + return resource.common.expand_tags_to_resources( + resources_section, resource_or_tag_el_list, + ) ++ ++ ++def get_required_cib_version_for_primitive( ++ op_list: Iterable[Mapping[str, str]] ++) -> Optional[Version]: ++ for op in op_list: ++ if op.get("on-fail", "") == "demote": ++ return Version(3, 4, 0) ++ return None +diff --git a/pcs/resource.py b/pcs/resource.py +index e835fc99..9a3bd0ee 100644 +--- a/pcs/resource.py ++++ b/pcs/resource.py +@@ -355,6 +355,21 @@ def resource_op_add_cmd(lib, argv, modifiers): + if not argv: + raise CmdLineInputError() + res_id = argv.pop(0) ++ ++ # Check if we need to upgrade cib schema. ++ # To do that, argv must be parsed, which is duplication of parsing in ++ # resource_operation_add. But we need to upgrade the cib first before ++ # calling that function. Hopefully, this will be fixed in the new pcs ++ # architecture. ++ ++ # argv[0] is an operation name ++ op_properties = utils.convert_args_to_tuples(argv[1:]) ++ for key, value in op_properties: ++ if key == "on-fail" and value == "demote": ++ utils.checkAndUpgradeCIB(3, 4, 0) ++ break ++ ++ # add the requested operation + utils.replace_cib_configuration( + resource_operation_add(utils.get_cib_dom(), res_id, argv) + ) +@@ -895,8 +910,6 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True): + if len(args) < 2: + raise CmdLineInputError() + res_id = args.pop(0) +- cib_xml = utils.get_cib() +- dom = utils.get_cib_dom(cib_xml=cib_xml) + + # Extract operation arguments + ra_values, op_values, meta_values = parse_resource_options(args) +@@ -907,6 +920,28 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True): + wait_timeout = utils.validate_wait_get_timeout() + wait = True + ++ # Check if we need to upgrade cib schema. ++ # To do that, argv must be parsed, which is duplication of parsing below. ++ # But we need to upgrade the cib first before calling that function. ++ # Hopefully, this will be fixed in the new pcs architecture. ++ ++ cib_upgraded = False ++ for op_argv in op_values: ++ if cib_upgraded: ++ break ++ if len(op_argv) < 2: ++ continue ++ # argv[0] is an operation name ++ op_vars = utils.convert_args_to_tuples(op_argv[1:]) ++ for k, v in op_vars: ++ if k == "on-fail" and v == "demote": ++ utils.checkAndUpgradeCIB(3, 4, 0) ++ cib_upgraded = True ++ break ++ ++ cib_xml = utils.get_cib() ++ dom = utils.get_cib_dom(cib_xml=cib_xml) ++ + resource = utils.dom_get_resource(dom, res_id) + if not resource: + clone = utils.dom_get_clone(dom, res_id) +@@ -994,21 +1029,21 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True): + else: + operations = operations[0] + +- for element in op_values: +- if not element: ++ for op_argv in op_values: ++ if not op_argv: + continue + +- op_name = element[0] ++ op_name = op_argv[0] + if op_name.find("=") != -1: + utils.err( + "%s does not appear to be a valid operation action" % op_name + ) + +- if len(element) < 2: ++ if len(op_argv) < 2: + continue + + op_role = "" +- op_vars = utils.convert_args_to_tuples(element[1:]) ++ op_vars = utils.convert_args_to_tuples(op_argv[1:]) + + for k, v in op_vars: + if k == "role": +@@ -1032,7 +1067,7 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True): + dom = resource_operation_add( + dom, + res_id, +- element, ++ op_argv, + validate_strict=False, + before_op=updating_op_before, + ) +diff --git a/pcs_test/resources/cib-empty-3.3.xml b/pcs_test/resources/cib-empty-3.3.xml +index 3a44fe08..4de94b6e 100644 +--- a/pcs_test/resources/cib-empty-3.3.xml ++++ b/pcs_test/resources/cib-empty-3.3.xml +@@ -1,4 +1,4 @@ +- ++ + + + +diff --git a/pcs_test/resources/cib-empty-3.4.xml b/pcs_test/resources/cib-empty-3.4.xml +index dcd4ff44..e677462d 100644 +--- a/pcs_test/resources/cib-empty-3.4.xml ++++ b/pcs_test/resources/cib-empty-3.4.xml +@@ -1,4 +1,4 @@ +- ++ + + + +diff --git a/pcs_test/tier0/lib/cib/test_resource_operations.py b/pcs_test/tier0/lib/cib/test_resource_operations.py +index 5e556cf4..e5be7a54 100644 +--- a/pcs_test/tier0/lib/cib/test_resource_operations.py ++++ b/pcs_test/tier0/lib/cib/test_resource_operations.py +@@ -316,13 +316,14 @@ class ValidateOperation(TestCase): + option_value="b", + option_name="on-fail", + allowed_values=[ +- "ignore", + "block", +- "stop", +- "restart", +- "standby", ++ "demote", + "fence", ++ "ignore", ++ "restart", + "restart-container", ++ "standby", ++ "stop", + ], + cannot_be_empty=False, + forbidden_characters=None, +diff --git a/pcs_test/tier0/lib/commands/remote_node/test_node_add_remote.py b/pcs_test/tier0/lib/commands/remote_node/test_node_add_remote.py +index 725ec68a..4da3fa0a 100644 +--- a/pcs_test/tier0/lib/commands/remote_node/test_node_add_remote.py ++++ b/pcs_test/tier0/lib/commands/remote_node/test_node_add_remote.py +@@ -118,7 +118,7 @@ FIXTURE_RESOURCES_TEMPLATE = """ + interval="0s" name="migrate_to" timeout="60" + /> + + + + """ +-FIXTURE_RESOURCES = FIXTURE_RESOURCES_TEMPLATE.format(server="remote-host") ++FIXTURE_RESOURCES = FIXTURE_RESOURCES_TEMPLATE.format( ++ server="remote-host", onfail="" ++) + + + class AddRemote(TestCase): +@@ -178,12 +180,48 @@ class AddRemote(TestCase): + .local.push_existing_authkey_to_remote(NODE_NAME, NODE_DEST_LIST) + .local.run_pacemaker_remote(NODE_NAME, NODE_DEST_LIST) + .env.push_cib( +- resources=FIXTURE_RESOURCES_TEMPLATE.format(server=NODE_NAME) ++ resources=FIXTURE_RESOURCES_TEMPLATE.format( ++ server=NODE_NAME, onfail="" ++ ) + ) + ) + node_add_remote(self.env_assist.get_env(), node_addr=NODE_NAME) + self.env_assist.assert_reports(REPORTS) + ++ def test_cib_upgrade_on_onfail_demote(self): ++ self._config_success_base() ++ self.config.runner.cib.load( ++ filename="cib-empty-3.4.xml", instead="runner.cib.load", ++ ) ++ self.config.runner.cib.upgrade(before="runner.cib.load") ++ self.config.runner.cib.load( ++ filename="cib-empty-3.3.xml", ++ name="load_cib_old_version", ++ before="runner.cib.upgrade", ++ ) ++ self.config.env.push_cib( ++ resources=FIXTURE_RESOURCES_TEMPLATE.format( ++ server="remote-host", onfail='on-fail="demote"' ++ ), ++ instead="env.push_cib", ++ ) ++ node_add_remote( ++ self.env_assist.get_env(), ++ operations=[ ++ { ++ "name": "monitor", ++ "timeout": "30", ++ "interval": "60s", ++ "on-fail": "demote", ++ } ++ ], ++ ) ++ self.env_assist.assert_reports( ++ REPORTS.info( ++ "cib_upgrade_successful", reports.codes.CIB_UPGRADE_SUCCESSFUL ++ ) ++ ) ++ + def test_node_name_conflict_report_is_unique(self): + ( + self.config.runner.cib.load( +@@ -623,7 +661,7 @@ class NotLive(TestCase): + .runner.pcmk.load_agent(agent_name="ocf:pacemaker:remote") + .env.push_cib( + resources=FIXTURE_RESOURCES_TEMPLATE.format( +- server=NODE_ADDR_PCSD ++ server=NODE_ADDR_PCSD, onfail="" + ) + ) + ) +@@ -648,7 +686,9 @@ class NotLive(TestCase): + self.config.runner.cib.load() + .runner.pcmk.load_agent(agent_name="ocf:pacemaker:remote") + .env.push_cib( +- resources=FIXTURE_RESOURCES_TEMPLATE.format(server=NODE_NAME) ++ resources=FIXTURE_RESOURCES_TEMPLATE.format( ++ server=NODE_NAME, onfail="" ++ ) + ) + ) + node_add_remote(self.env_assist.get_env(), no_node_addr=True) +@@ -672,7 +712,9 @@ class NotLive(TestCase): + self.config.runner.cib.load() + .runner.pcmk.load_agent(agent_name="ocf:pacemaker:remote") + .env.push_cib( +- resources=FIXTURE_RESOURCES_TEMPLATE.format(server="addr") ++ resources=FIXTURE_RESOURCES_TEMPLATE.format( ++ server="addr", onfail="" ++ ) + ) + ) + node_add_remote(self.env_assist.get_env(), node_addr="addr") +diff --git a/pcs_test/tier0/lib/commands/resource/test_resource_create.py b/pcs_test/tier0/lib/commands/resource/test_resource_create.py +index dc70ce22..a040a9d9 100644 +--- a/pcs_test/tier0/lib/commands/resource/test_resource_create.py ++++ b/pcs_test/tier0/lib/commands/resource/test_resource_create.py +@@ -35,13 +35,19 @@ def create( + ) + + +-def create_group(env, wait=TIMEOUT, disabled=False, meta_attributes=None): ++def create_group( ++ env, ++ wait=TIMEOUT, ++ disabled=False, ++ meta_attributes=None, ++ operation_list=None, ++): + return resource.create_in_group( + env, + "A", + "ocf:heartbeat:Dummy", + "G", +- operation_list=[], ++ operation_list=operation_list if operation_list else [], + meta_attributes=meta_attributes if meta_attributes else {}, + instance_attributes={}, + wait=wait, +@@ -50,13 +56,18 @@ def create_group(env, wait=TIMEOUT, disabled=False, meta_attributes=None): + + + def create_clone( +- env, wait=TIMEOUT, disabled=False, meta_attributes=None, clone_options=None ++ env, ++ wait=TIMEOUT, ++ disabled=False, ++ meta_attributes=None, ++ clone_options=None, ++ operation_list=None, + ): + return resource.create_as_clone( + env, + "A", + "ocf:heartbeat:Dummy", +- operation_list=[], ++ operation_list=operation_list if operation_list else [], + meta_attributes=meta_attributes if meta_attributes else {}, + instance_attributes={}, + clone_meta_options=clone_options if clone_options else {}, +@@ -71,12 +82,13 @@ def create_bundle( + disabled=False, + meta_attributes=None, + allow_not_accessible_resource=False, ++ operation_list=None, + ): + return resource.create_into_bundle( + env, + "A", + "ocf:heartbeat:Dummy", +- operation_list=[], ++ operation_list=operation_list if operation_list else [], + meta_attributes=meta_attributes if meta_attributes else {}, + instance_attributes={}, + bundle_id="B", +@@ -576,6 +588,60 @@ class Create(TestCase): + ] + ) + ++ def test_cib_upgrade_on_onfail_demote(self): ++ self.config.runner.cib.load( ++ filename="cib-empty-3.3.xml", ++ instead="runner.cib.load", ++ name="load_cib_old_version", ++ ) ++ self.config.runner.cib.upgrade() ++ self.config.runner.cib.load(filename="cib-empty-3.4.xml") ++ self.config.env.push_cib( ++ resources=""" ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ """ ++ ) ++ ++ create( ++ self.env_assist.get_env(), ++ operation_list=[ ++ { ++ "name": "monitor", ++ "timeout": "10", ++ "interval": "10", ++ "on-fail": "demote", ++ } ++ ], ++ ) ++ self.env_assist.assert_reports( ++ [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)] ++ ) ++ + + class CreateWait(TestCase): + def setUp(self): +@@ -746,6 +812,66 @@ class CreateInGroup(TestCase): + + create_group(self.env_assist.get_env(), wait=False) + ++ def test_cib_upgrade_on_onfail_demote(self): ++ self.config.remove(name="runner.pcmk.can_wait") ++ self.config.runner.cib.load( ++ filename="cib-empty-3.3.xml", ++ instead="runner.cib.load", ++ name="load_cib_old_version", ++ ) ++ self.config.runner.cib.upgrade() ++ self.config.runner.cib.load(filename="cib-empty-3.4.xml") ++ self.config.env.push_cib( ++ resources=""" ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ """ ++ ) ++ ++ create_group( ++ self.env_assist.get_env(), ++ operation_list=[ ++ { ++ "name": "monitor", ++ "timeout": "10", ++ "interval": "10", ++ "on-fail": "demote", ++ } ++ ], ++ wait=False, ++ ) ++ self.env_assist.assert_reports( ++ [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)] ++ ) ++ + def test_fail_wait(self): + self.config.env.push_cib( + resources=fixture_cib_resources_xml_group_simplest, +@@ -859,6 +985,62 @@ class CreateAsClone(TestCase): + ) + create_clone(self.env_assist.get_env(), wait=False) + ++ def test_cib_upgrade_on_onfail_demote(self): ++ self.config.remove(name="runner.pcmk.can_wait") ++ self.config.runner.cib.load( ++ filename="cib-empty-3.3.xml", ++ instead="runner.cib.load", ++ name="load_cib_old_version", ++ ) ++ self.config.runner.cib.upgrade() ++ self.config.runner.cib.load(filename="cib-empty-3.4.xml") ++ self.config.env.push_cib( ++ resources=""" ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ """ ++ ) ++ ++ create_clone( ++ self.env_assist.get_env(), ++ operation_list=[ ++ { ++ "name": "monitor", ++ "timeout": "10", ++ "interval": "10", ++ "on-fail": "demote", ++ } ++ ], ++ wait=False, ++ ) ++ self.env_assist.assert_reports( ++ [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)] ++ ) ++ + def test_fail_wait(self): + self.config.env.push_cib( + resources=fixture_cib_resources_xml_clone_simplest, +@@ -1168,7 +1350,7 @@ class CreateInToBundle(TestCase): + name="migrate_to" timeout="20" + /> + + +- """ ++ """, ++ onfail="" + ) + ) + # fmt: on +@@ -1290,6 +1473,42 @@ class CreateInToBundle(TestCase): + [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)] + ) + ++ def test_cib_upgrade_on_onfail_demote(self): ++ self.config.runner.pcmk.load_agent() ++ self.config.runner.cib.load( ++ filename="cib-empty-3.3.xml", name="load_cib_old_version", ++ ) ++ self.config.runner.cib.upgrade() ++ self.config.runner.cib.load( ++ filename="cib-empty-3.4.xml", resources=self.fixture_resources_pre ++ ) ++ self.config.env.push_cib( ++ resources=self.fixture_resource_post_simple_without_network.format( ++ network=""" ++ ++ """, ++ onfail='on-fail="demote"', ++ ) ++ ) ++ ++ create_bundle( ++ self.env_assist.get_env(), ++ operation_list=[ ++ { ++ "name": "monitor", ++ "timeout": "20", ++ "interval": "10", ++ "on-fail": "demote", ++ } ++ ], ++ wait=False, ++ ) ++ self.env_assist.assert_reports( ++ [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)] ++ ) ++ + def test_simplest_resource(self): + ( + self.config.runner.pcmk.load_agent() +@@ -1504,7 +1723,7 @@ class CreateInToBundle(TestCase): + .env.push_cib( + resources=( + self.fixture_resource_post_simple_without_network.format( +- network="" ++ network="", onfail="" + ) + ) + ) +@@ -1540,7 +1759,7 @@ class CreateInToBundle(TestCase): + .env.push_cib( + resources=( + self.fixture_resource_post_simple_without_network.format( +- network=network ++ network=network, onfail="" + ) + ) + ) +diff --git a/pcs_test/tier1/cib_resource/test_create.py b/pcs_test/tier1/cib_resource/test_create.py +index 977d627a..5ff83b71 100644 +--- a/pcs_test/tier1/cib_resource/test_create.py ++++ b/pcs_test/tier1/cib_resource/test_create.py +@@ -1143,7 +1143,7 @@ class FailOrWarnOp(ResourceTest): + " monitor on-fail=Abc", + ( + "Error: 'Abc' is not a valid on-fail value, use 'block', " +- "'fence', 'ignore', 'restart', 'restart-container', " ++ "'demote', 'fence', 'ignore', 'restart', 'restart-container', " + "'standby', 'stop'\n" + ERRORS_HAVE_OCURRED + ), + ) +diff --git a/pcs_test/tier1/legacy/test_resource.py b/pcs_test/tier1/legacy/test_resource.py +index 7ffcc83b..107ff406 100644 +--- a/pcs_test/tier1/legacy/test_resource.py ++++ b/pcs_test/tier1/legacy/test_resource.py +@@ -22,6 +22,7 @@ from pcs_test.tools.misc import ( + is_minimum_pacemaker_version, + outdent, + skip_unless_pacemaker_supports_bundle, ++ skip_unless_pacemaker_supports_op_onfail_demote, + skip_unless_crm_rule, + write_data_to_tmpfile, + write_file_to_tmpfile, +@@ -723,6 +724,28 @@ monitor interval=60s OCF_CHECK_LEVEL=1 (OPTest7-monitor-interval-60s) + ), + ) + ++ @skip_unless_pacemaker_supports_op_onfail_demote() ++ def test_add_operation_onfail_demote_upgrade_cib(self): ++ write_file_to_tmpfile(rc("cib-empty-3.3.xml"), self.temp_cib) ++ self.assert_pcs_success( ++ "resource create --no-default-ops R ocf:pacemaker:Dummy" ++ ) ++ self.assert_pcs_success( ++ "resource op add R start on-fail=demote", ++ stdout_full="Cluster CIB has been upgraded to latest version\n", ++ ) ++ ++ @skip_unless_pacemaker_supports_op_onfail_demote() ++ def test_update_add_operation_onfail_demote_upgrade_cib(self): ++ write_file_to_tmpfile(rc("cib-empty-3.3.xml"), self.temp_cib) ++ self.assert_pcs_success( ++ "resource create --no-default-ops R ocf:pacemaker:Dummy" ++ ) ++ self.assert_pcs_success( ++ "resource update R op start on-fail=demote", ++ stdout_full="Cluster CIB has been upgraded to latest version\n", ++ ) ++ + def _test_delete_remove_operation(self, command): + assert command in {"delete", "remove"} + +diff --git a/pcs_test/tools/misc.py b/pcs_test/tools/misc.py +index 33d78002..820f1e79 100644 +--- a/pcs_test/tools/misc.py ++++ b/pcs_test/tools/misc.py +@@ -253,6 +253,12 @@ def skip_unless_pacemaker_supports_rsc_and_op_rules(): + ) + + ++def skip_unless_pacemaker_supports_op_onfail_demote(): ++ return skip_unless_cib_schema_version( ++ (3, 4, 0), "resource operations with 'on-fail' option set to 'demote'" ++ ) ++ ++ + def skip_if_service_enabled(service_name): + return skipUnless( + not is_service_enabled(runner, service_name), +diff --git a/pcsd/capabilities.xml b/pcsd/capabilities.xml +index 6e1886cb..09983354 100644 +--- a/pcsd/capabilities.xml ++++ b/pcsd/capabilities.xml +@@ -465,6 +465,13 @@ + pcs commands: cluster node ( add-remote | delete-remote | remove-remote ) + + ++ ++ ++ Support for "demote" value of resource operation's "on-fail" option ++ ++ pcs commands: cluster node add-remote ++ ++ + + + +@@ -1056,6 +1063,13 @@ + pcs commands: resource create ... op + + ++ ++ ++ Support for "demote" value of resource operation's "on-fail" option ++ ++ pcs commands: resource create ... op ++ ++ + + + Wait for the created resource to start. +@@ -1105,6 +1119,13 @@ + pcs commands: resource update ... op + + ++ ++ ++ Support for "demote" value of resource operation's "on-fail" option ++ ++ pcs commands: resource update ... op ++ ++ + + + Wait for the changes to take effect. +@@ -1143,6 +1164,13 @@ + pcs commands: resource op ( add | delete | remove ) + + ++ ++ ++ Support for "demote" value of resource operation's "on-fail" option ++ ++ pcs commands: resource op add ++ ++ + + + +@@ -1555,6 +1583,20 @@ + pcs commands: stonith create ... op + + ++ ++ ++ Set resource operations when creating a stonith resource. ++ ++ pcs commands: stonith create ... op ++ ++ ++ ++ ++ Support for "demote" value of resource operation's "on-fail" option ++ ++ pcs commands: stonith create ... op ++ ++ + + + Wait for the created resource to start. +-- +2.25.4 + diff --git a/SOURCES/bz1857295-01-Fix-tag-removal-in-resource-unclone-ungroup-commands.patch b/SOURCES/bz1857295-01-Fix-tag-removal-in-resource-unclone-ungroup-commands.patch new file mode 100644 index 0000000..9f46bb1 --- /dev/null +++ b/SOURCES/bz1857295-01-Fix-tag-removal-in-resource-unclone-ungroup-commands.patch @@ -0,0 +1,402 @@ +From 4a986e8ee0610b1c85a04e38042e4073d41207a4 Mon Sep 17 00:00:00 2001 +From: Miroslav Lisik +Date: Mon, 13 Jul 2020 12:59:09 +0200 +Subject: [PATCH 2/3] Fix tag removal in resource 'unclone/ungroup' commands + and extend test coverage + +--- + pcs/resource.py | 2 +- + .../tier1/cib_resource/test_clone_unclone.py | 73 +++++++-- + .../tier1/cib_resource/test_group_ungroup.py | 143 +++++++++++++++--- + pcs_test/tools/cib.py | 10 +- + 4 files changed, 187 insertions(+), 41 deletions(-) + +diff --git a/pcs/resource.py b/pcs/resource.py +index 9a3bd0ee..49d28ef0 100644 +--- a/pcs/resource.py ++++ b/pcs/resource.py +@@ -2027,7 +2027,7 @@ def remove_resource_references( + if obj_ref.getAttribute("id") == resource_id: + tag = obj_ref.parentNode + tag.removeChild(obj_ref) +- if tag.getElementsByTagName(obj_ref).length == 0: ++ if tag.getElementsByTagName("obj_ref").length == 0: + remove_resource_references( + dom, tag.getAttribute("id"), output=output, + ) +diff --git a/pcs_test/tier1/cib_resource/test_clone_unclone.py b/pcs_test/tier1/cib_resource/test_clone_unclone.py +index c9c6a29e..2633801a 100644 +--- a/pcs_test/tier1/cib_resource/test_clone_unclone.py ++++ b/pcs_test/tier1/cib_resource/test_clone_unclone.py +@@ -55,6 +55,38 @@ FIXTURE_RESOURCES = """ + ) + + ++FIXTURE_CONSTRAINTS_CONFIG_XML = """ ++ ++ ++ ++ ++""" ++ ++ ++FIXTURE_TAGS_CONFIG_XML = """ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++""" ++ ++ ++FIXTURE_TAGS_RESULT_XML = """ ++ ++ ++ ++ ++ ++""" ++ ++ + class Unclone( + TestCase, + get_assert_pcs_effect_mixin( +@@ -66,6 +98,22 @@ class Unclone( + ): + empty_cib = rc("cib-empty.xml") + ++ def assert_tags_xml(self, expected_xml): ++ self.assert_resources_xml_in_cib( ++ expected_xml, ++ get_cib_part_func=lambda cib: etree.tostring( ++ etree.parse(cib).findall(".//tags")[0], ++ ), ++ ) ++ ++ def assert_constraint_xml(self, expected_xml): ++ self.assert_resources_xml_in_cib( ++ expected_xml, ++ get_cib_part_func=lambda cib: etree.tostring( ++ etree.parse(cib).findall(".//constraints")[0], ++ ), ++ ) ++ + def setUp(self): + # pylint: disable=invalid-name + self.temp_cib = get_tmp_file("tier1_cib_resource_group_ungroup") +@@ -75,18 +123,7 @@ class Unclone( + "resources", FIXTURE_CLONE, FIXTURE_DUMMY, + ) + xml_manip.append_to_first_tag_name( +- "configuration", +- """ +- +- +- +- +- +- +- +- +- +- """, ++ "configuration", FIXTURE_TAGS_CONFIG_XML, + ) + xml_manip.append_to_first_tag_name( + "constraints", +@@ -95,8 +132,8 @@ class Unclone( + rsc="C-clone" score="INFINITY"/> + """, + """ +- ++ + """, + ) + write_data_to_tmpfile(str(xml_manip), self.temp_cib) +@@ -111,6 +148,8 @@ class Unclone( + "Error: could not find resource: NonExistentClone\n", + ) + self.assert_resources_xml_in_cib(FIXTURE_CLONE_AND_RESOURCE) ++ self.assert_tags_xml(FIXTURE_TAGS_CONFIG_XML) ++ self.assert_constraint_xml(FIXTURE_CONSTRAINTS_CONFIG_XML) + + def test_not_clone_resource(self): + self.assert_pcs_fail( +@@ -118,9 +157,15 @@ class Unclone( + "Error: 'Dummy' is not a clone resource\n", + ) + self.assert_resources_xml_in_cib(FIXTURE_CLONE_AND_RESOURCE) ++ self.assert_tags_xml(FIXTURE_TAGS_CONFIG_XML) ++ self.assert_constraint_xml(FIXTURE_CONSTRAINTS_CONFIG_XML) + + def test_unclone_clone_id(self): + self.assert_effect("resource unclone C-clone", FIXTURE_RESOURCES) ++ self.assert_tags_xml(FIXTURE_TAGS_RESULT_XML) ++ self.assert_constraint_xml("") + + def test_unclone_resoruce_id(self): + self.assert_effect("resource unclone C", FIXTURE_RESOURCES) ++ self.assert_tags_xml(FIXTURE_TAGS_RESULT_XML) ++ self.assert_constraint_xml("") +diff --git a/pcs_test/tier1/cib_resource/test_group_ungroup.py b/pcs_test/tier1/cib_resource/test_group_ungroup.py +index f86e9890..88cc315d 100644 +--- a/pcs_test/tier1/cib_resource/test_group_ungroup.py ++++ b/pcs_test/tier1/cib_resource/test_group_ungroup.py +@@ -64,14 +64,63 @@ FIXTURE_AGROUP_XML = fixture_group_xml( + ) + + +-class TestGroupMixin( +- get_assert_pcs_effect_mixin( +- lambda cib: etree.tostring( +- # pylint:disable=undefined-variable +- etree.parse(cib).findall(".//resources")[0] +- ) +- ), +-): ++FIXTURE_CONSTRAINTS_CONFIG_XML = """ ++ ++ ++ ++ ++""" ++ ++FIXTURE_CLONE_TAG_CONSTRAINTS = """ ++ ++ ++ ++ ++""" ++ ++ ++FIXTURE_CLONE_CONSTRAINT = """ ++ ++ ++ ++""" ++ ++ ++FIXTURE_TAGS_CONFIG_XML = """ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++""" ++ ++ ++FIXTURE_TAGS_RESULT_XML = """ ++ ++ ++ ++ ++ ++ ++ ++""" ++ ++ ++class TestGroupMixin: + empty_cib = rc("cib-empty.xml") + + def setUp(self): +@@ -81,17 +130,7 @@ class TestGroupMixin( + xml_manip = XmlManipulation.from_file(self.empty_cib) + xml_manip.append_to_first_tag_name("resources", FIXTURE_AGROUP_XML) + xml_manip.append_to_first_tag_name( +- "configuration", +- """ +- +- +- +- +- +- +- +- +- """, ++ "configuration", FIXTURE_TAGS_CONFIG_XML, + ) + xml_manip.append_to_first_tag_name( + "constraints", +@@ -100,8 +139,8 @@ class TestGroupMixin( + rsc="AGroup" score="INFINITY"/> + """, + """ +- ++ + """, + ) + write_data_to_tmpfile(str(xml_manip), self.temp_cib) +@@ -111,9 +150,33 @@ class TestGroupMixin( + self.temp_cib.close() + + +-class GroupDeleteRemoveUngroupBase(TestGroupMixin): ++class GroupDeleteRemoveUngroupBase( ++ get_assert_pcs_effect_mixin( ++ lambda cib: etree.tostring( ++ # pylint:disable=undefined-variable ++ etree.parse(cib).findall(".//resources")[0] ++ ) ++ ), ++ TestGroupMixin, ++): + command = None + ++ def assert_tags_xml(self, expected_xml): ++ self.assert_resources_xml_in_cib( ++ expected_xml, ++ get_cib_part_func=lambda cib: etree.tostring( ++ etree.parse(cib).findall(".//tags")[0], ++ ), ++ ) ++ ++ def assert_constraint_xml(self, expected_xml): ++ self.assert_resources_xml_in_cib( ++ expected_xml, ++ get_cib_part_func=lambda cib: etree.tostring( ++ etree.parse(cib).findall(".//constraints")[0], ++ ), ++ ) ++ + def test_nonexistent_group(self): + self.assert_pcs_fail( + f"resource {self.command} NonExistentGroup", +@@ -122,6 +185,8 @@ class GroupDeleteRemoveUngroupBase(TestGroupMixin): + self.assert_resources_xml_in_cib( + fixture_resources_xml([FIXTURE_AGROUP_XML]), + ) ++ self.assert_tags_xml(FIXTURE_TAGS_CONFIG_XML) ++ self.assert_constraint_xml(FIXTURE_CONSTRAINTS_CONFIG_XML) + + def test_not_a_group_id(self): + self.assert_pcs_fail( +@@ -130,6 +195,8 @@ class GroupDeleteRemoveUngroupBase(TestGroupMixin): + self.assert_resources_xml_in_cib( + fixture_resources_xml([FIXTURE_AGROUP_XML]), + ) ++ self.assert_tags_xml(FIXTURE_TAGS_CONFIG_XML) ++ self.assert_constraint_xml(FIXTURE_CONSTRAINTS_CONFIG_XML) + + def test_whole_group(self): + self.assert_effect( +@@ -142,10 +209,12 @@ class GroupDeleteRemoveUngroupBase(TestGroupMixin): + ], + ), + output=( +- "Removing Constraint - location-T1-rh7-1-INFINITY\n" ++ "Removing Constraint - location-TagGroupOnly-rh7-1-INFINITY\n" + "Removing Constraint - location-AGroup-rh7-1-INFINITY\n" + ), + ) ++ self.assert_tags_xml(FIXTURE_TAGS_RESULT_XML) ++ self.assert_constraint_xml("") + + def test_specified_resources(self): + self.assert_effect( +@@ -160,6 +229,26 @@ class GroupDeleteRemoveUngroupBase(TestGroupMixin): + ], + ), + ) ++ self.assert_tags_xml(FIXTURE_TAGS_CONFIG_XML) ++ self.assert_constraint_xml(FIXTURE_CONSTRAINTS_CONFIG_XML) ++ ++ def test_all_resources(self): ++ self.assert_effect( ++ f"resource {self.command} AGroup A1 A2 A3", ++ fixture_resources_xml( ++ [ ++ fixture_primitive_xml("A1"), ++ fixture_primitive_xml("A2"), ++ fixture_primitive_xml("A3"), ++ ], ++ ), ++ output=( ++ "Removing Constraint - location-TagGroupOnly-rh7-1-INFINITY\n" ++ "Removing Constraint - location-AGroup-rh7-1-INFINITY\n" ++ ), ++ ) ++ self.assert_tags_xml(FIXTURE_TAGS_RESULT_XML) ++ self.assert_constraint_xml("") + + def test_cloned_group(self): + self.assert_pcs_success("resource clone AGroup") +@@ -172,6 +261,8 @@ class GroupDeleteRemoveUngroupBase(TestGroupMixin): + [fixture_clone_xml("AGroup-clone", FIXTURE_AGROUP_XML)], + ) + ) ++ self.assert_tags_xml(FIXTURE_TAGS_CONFIG_XML) ++ self.assert_constraint_xml(FIXTURE_CLONE_TAG_CONSTRAINTS) + + def test_cloned_group_all_resorces_specified(self): + self.assert_pcs_success("resource clone AGroup") +@@ -184,6 +275,8 @@ class GroupDeleteRemoveUngroupBase(TestGroupMixin): + [fixture_clone_xml("AGroup-clone", FIXTURE_AGROUP_XML)], + ) + ) ++ self.assert_tags_xml(FIXTURE_TAGS_CONFIG_XML) ++ self.assert_constraint_xml(FIXTURE_CLONE_TAG_CONSTRAINTS) + + def test_cloned_group_with_one_resource(self): + self.assert_pcs_success("resource clone AGroup") +@@ -199,8 +292,10 @@ class GroupDeleteRemoveUngroupBase(TestGroupMixin): + fixture_primitive_xml("A2"), + ], + ), +- output="Removing Constraint - location-T1-rh7-1-INFINITY\n", ++ output="Removing Constraint - location-TagGroupOnly-rh7-1-INFINITY\n", + ) ++ self.assert_tags_xml(FIXTURE_TAGS_RESULT_XML) ++ self.assert_constraint_xml(FIXTURE_CLONE_CONSTRAINT) + + + class ResourceUngroup(GroupDeleteRemoveUngroupBase, TestCase): +diff --git a/pcs_test/tools/cib.py b/pcs_test/tools/cib.py +index d52176cf..5eaaa92e 100644 +--- a/pcs_test/tools/cib.py ++++ b/pcs_test/tools/cib.py +@@ -30,8 +30,14 @@ def xml_format(xml_string): + + def get_assert_pcs_effect_mixin(get_cib_part): + class AssertPcsEffectMixin(AssertPcsMixin): +- def assert_resources_xml_in_cib(self, expected_xml_resources): +- xml = get_cib_part(self.temp_cib) ++ def assert_resources_xml_in_cib( ++ self, expected_xml_resources, get_cib_part_func=None, ++ ): ++ self.temp_cib.seek(0) ++ if get_cib_part_func is not None: ++ xml = get_cib_part_func(self.temp_cib) ++ else: ++ xml = get_cib_part(self.temp_cib) + try: + assert_xml_equal(expected_xml_resources, xml.decode()) + except AssertionError as e: +-- +2.25.4 + 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 800145e..85d8125 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 c0fff964cc07e3a9fbdea85da33abe3329c653a3 Mon Sep 17 00:00:00 2001 +From 8507983ea60f8cfd03a5cdfae2fb564dbe0ef1d2 Mon Sep 17 00:00:00 2001 From: Ivan Devat Date: Tue, 20 Nov 2018 15:03:56 +0100 Subject: [PATCH 3/3] do not support cluster setup with udp(u) transport diff --git a/SPECS/pcs.spec b/SPECS/pcs.spec index 3a207a6..daeb006 100644 --- a/SPECS/pcs.spec +++ b/SPECS/pcs.spec @@ -1,6 +1,6 @@ Name: pcs Version: 0.10.6 -Release: 2%{?dist} +Release: 3%{?dist} # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ # https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses # GPLv2: pcs @@ -114,6 +114,8 @@ Source101: https://github.com/idevat/pcs-web-ui/releases/download/%{ui_commit}/p # Patch1: name.patch Patch1: bz1817547-01-resource-and-operation-defaults.patch Patch2: bz1805082-01-fix-resource-stonith-refresh-documentation.patch +Patch3: bz1843079-01-upgrade-CIB-schema-for-on-fail-demote.patch +Patch4: bz1857295-01-Fix-tag-removal-in-resource-unclone-ungroup-commands.patch # Downstream patches do not come from upstream. They adapt pcs for specific # RHEL needs. @@ -282,6 +284,8 @@ update_times_patch(){ # update_times_patch %%{PATCH1} update_times_patch %{PATCH1} update_times_patch %{PATCH2} +update_times_patch %{PATCH3} +update_times_patch %{PATCH4} update_times_patch %{PATCH101} cp -f %SOURCE1 pcsd/public/images @@ -358,6 +362,11 @@ pwd # build bundled rubygems (in main install it is disabled by BUILD_GEMS=false) mkdir -p %{rubygem_bundle_dir} +# The '-g' cflags option is needed for generation of MiniDebugInfo for shared +# libraries from rubygem extensions +# Currently used rubygems with extensions: eventmachine, ffi, json, thin +# There was rpmdiff issue with missing .gnu_debugdata section +# see https://docs.engineering.redhat.com/display/HTD/rpmdiff-elf-stripping gem install \ --force --verbose --no-rdoc --no-ri -l --no-user-install \ -i %{rubygem_bundle_dir} \ @@ -377,7 +386,7 @@ gem install \ %{rubygem_cache_dir}/thin-%{version_rubygem_thin}.gem \ %{rubygem_cache_dir}/tilt-%{version_rubygem_tilt}.gem \ -- '--with-ldflags="-Wl,-z,relro -Wl,-z,ibt -Wl,-z,now -Wl,--gc-sections"' \ - '--with-cflags="-O2 -ffunction-sections"' + '--with-cflags="-g -O2 -ffunction-sections"' # prepare license files # some rubygems do not have a license file (ruby2_keywords, thin) @@ -398,19 +407,6 @@ mv %{rubygem_bundle_dir}/gems/rack-test-%{version_rubygem_rack_test}/MIT-LICENSE mv %{rubygem_bundle_dir}/gems/sinatra-%{version_rubygem_sinatra}/LICENSE sinatra_LICENSE mv %{rubygem_bundle_dir}/gems/tilt-%{version_rubygem_tilt}/COPYING tilt_COPYING -# We can remove files required for gem compilation -rm -rf %{rubygem_bundle_dir}/gems/eventmachine-%{version_rubygem_eventmachine}/ext -rm -rf %{rubygem_bundle_dir}/gems/ffi-%{version_rubygem_ffi}/ext -rm -rf %{rubygem_bundle_dir}/gems/json-%{version_rubygem_json}/ext -rm -rf %{rubygem_bundle_dir}/gems/thin-%{version_rubygem_thin}/ext - - -# With this file there is "File is not stripped" problem during rpmdiff -# See https://docs.engineering.redhat.com/display/HTD/rpmdiff-elf-stripping -for fname in `find %{rubygem_bundle_dir}/extensions -type f -name "*.so"`; do - strip ${fname} -done - # build web ui and put it to pcsd make -C %{pcsd_public_dir}/%{ui_src_name} build mv %{pcsd_public_dir}/%{ui_src_name}/build pcsd/public/ui @@ -435,18 +431,29 @@ make install \ rubyhdrdir="%{_includedir}" \ includedir="%{_includedir}" -# With this file there is "File is not stripped" problem during rpmdiff -# See https://docs.engineering.redhat.com/display/HTD/rpmdiff-elf-stripping -for fname in `find ${RPM_BUILD_ROOT}%{pcs_libdir}/pcs/bundled/packages/tornado/ -type f -name "*.so"`; do - strip ${fname} -done - # symlink favicon into pcsd directories ln -fs /etc/favicon.png ${RPM_BUILD_ROOT}%{pcs_libdir}/%{pcsd_public_dir}/images/favicon.png #after the ruby gem compilation we do not need ruby gems in the cache rm -r -v $RPM_BUILD_ROOT%{pcs_libdir}/%{rubygem_cache_dir} +# We are not building debug package for pcs but we need to add MiniDebuginfo +# to the bundled shared libraries from rubygem extensions in order to satisfy +# rpmdiff's binary stripping checker. +# Therefore we call find-debuginfo.sh script manually in order to strip +# binaries and add MiniDebugInfo with .gnu_debugdata section +/usr/lib/rpm/find-debuginfo.sh -j2 -m -i -S debugsourcefiles.list +# find-debuginfo.sh generated some files into /usr/lib/debug and +# /usr/src/debug/ that we don't want in the package +rm -rf $RPM_BUILD_ROOT%{pcs_libdir}/debug +rm -rf $RPM_BUILD_ROOT%{_prefix}/src/debug + +# We can remove files required for gem compilation +rm -rf $RPM_BUILD_ROOT%{pcs_libdir}/%{rubygem_bundle_dir}/gems/eventmachine-%{version_rubygem_eventmachine}/ext +rm -rf $RPM_BUILD_ROOT%{pcs_libdir}/%{rubygem_bundle_dir}/gems/ffi-%{version_rubygem_ffi}/ext +rm -rf $RPM_BUILD_ROOT%{pcs_libdir}/%{rubygem_bundle_dir}/gems/json-%{version_rubygem_json}/ext +rm -rf $RPM_BUILD_ROOT%{pcs_libdir}/%{rubygem_bundle_dir}/gems/thin-%{version_rubygem_thin}/ext + %check # In the building environment LC_CTYPE is set to C which causes tests to fail # due to python prints a warning about it to stderr. The following environment @@ -613,6 +620,12 @@ remove_all_tests %license pyagentx_LICENSE.txt %changelog +* Thu Jul 16 2020 Miroslav Lisik - 0.10.6-3 +- Added Upgrade CIB if user specifies on-fail=demote +- Fixed rpmdiff issue with binary stripping checker +- Fixed removing non-empty tag by removing tagged resource group or clone +- Resolves: rhbz#1843079 rhbz#1857295 + * Thu Jun 25 2020 Miroslav Lisik - 0.10.6-2 - Added resource and operation defaults that apply to specific resource/operation types - Added Requires/BuildRequires: python3-pyparsing