diff --git a/changelogs/fragments/10665-pacemaker-resource-clone.yml b/changelogs/fragments/10665-pacemaker-resource-clone.yml new file mode 100644 index 0000000000..c24420c598 --- /dev/null +++ b/changelogs/fragments/10665-pacemaker-resource-clone.yml @@ -0,0 +1,2 @@ +minor_changes: + - pacemaker_resource - add ``state=cloned`` for cloning pacemaker resources or groups (https://github.com/ansible-collections/community.general/issues/10322, https://github.com/ansible-collections/community.general/pull/10665). diff --git a/plugins/module_utils/pacemaker.py b/plugins/module_utils/pacemaker.py index 03a5432148..032b23f9b9 100644 --- a/plugins/module_utils/pacemaker.py +++ b/plugins/module_utils/pacemaker.py @@ -13,6 +13,7 @@ from ansible_collections.community.general.plugins.module_utils.cmd_runner impor _state_map = { "present": "create", "absent": "remove", + "cloned": "clone", "status": "status", "enabled": "enable", "disabled": "disable", @@ -65,6 +66,8 @@ def pacemaker_runner(module, **kwargs): resource_operation=cmd_runner_fmt.as_func(fmt_resource_operation), resource_meta=cmd_runner_fmt.stack(cmd_runner_fmt.as_opt_val)("meta"), resource_argument=cmd_runner_fmt.as_func(fmt_resource_argument), + resource_clone_ids=cmd_runner_fmt.as_list(), + resource_clone_meta=cmd_runner_fmt.as_list(), apply_all=cmd_runner_fmt.as_bool("--all"), agent_validation=cmd_runner_fmt.as_bool("--agent-validation"), wait=cmd_runner_fmt.as_opt_eq_val("--wait"), diff --git a/plugins/modules/pacemaker_resource.py b/plugins/modules/pacemaker_resource.py index 3f278d3cd2..6d5763f004 100644 --- a/plugins/modules/pacemaker_resource.py +++ b/plugins/modules/pacemaker_resource.py @@ -27,13 +27,14 @@ options: state: description: - Indicate desired state for cluster resource. - - The state V(cleanup) has been added in community.general 11.3.0. - choices: [present, absent, enabled, disabled, cleanup] + - The states V(cleanup) and V(cloned) have been added in community.general 11.3.0. + - If O(state=cloned) or O(state=present), you can set O(resource_clone_ids) and O(resource_clone_meta) to determine exactly what and how to clone. + choices: [present, absent, cloned, enabled, disabled, cleanup] default: present type: str name: description: - - Specify the resource name to create. + - Specify the resource name to create or clone to. - This is required if O(state=present), O(state=absent), O(state=enabled), or O(state=disabled). type: str resource_type: @@ -95,6 +96,18 @@ options: - Options to associate with resource action. type: list elements: str + resource_clone_ids: + description: + - List of clone resource IDs to clone from. + type: list + elements: str + version_added: 11.3.0 + resource_clone_meta: + description: + - List of metadata to associate with clone resource. + type: list + elements: str + version_added: 11.3.0 wait: description: - Timeout period for polling the resource creation. @@ -142,7 +155,7 @@ class PacemakerResource(StateModuleHelper): module = dict( argument_spec=dict( state=dict(type='str', default='present', choices=[ - 'present', 'absent', 'enabled', 'disabled', 'cleanup']), + 'present', 'absent', 'cloned', 'enabled', 'disabled', 'cleanup']), name=dict(type='str'), resource_type=dict(type='dict', options=dict( resource_name=dict(type='str'), @@ -159,6 +172,8 @@ class PacemakerResource(StateModuleHelper): argument_action=dict(type='str', choices=['clone', 'master', 'group', 'promotable']), argument_option=dict(type='list', elements='str'), )), + resource_clone_ids=dict(type='list', elements='str'), + resource_clone_meta=dict(type='list', elements='str'), wait=dict(type='int', default=300), ), required_if=[ @@ -194,6 +209,10 @@ class PacemakerResource(StateModuleHelper): ('out', result[1] if result[1] != "" else None), ('err', result[2])]) + def fmt_as_stack_argument(self, value, arg): + if value is not None: + return [x for k in value for x in (arg, k)] + def state_absent(self): force = get_pacemaker_maintenance_mode(self.runner) with self.runner('cli_action state name force', output_process=self._process_command_output(True, "does not exist"), check_mode_skip=True) as ctx: @@ -201,10 +220,19 @@ class PacemakerResource(StateModuleHelper): def state_present(self): with self.runner( - 'cli_action state name resource_type resource_option resource_operation resource_meta resource_argument wait', + 'cli_action state name resource_type resource_option resource_operation resource_meta resource_argument ' + 'resource_clone_ids resource_clone_meta wait', output_process=self._process_command_output(not get_pacemaker_maintenance_mode(self.runner), "already exists"), check_mode_skip=True) as ctx: - ctx.run(cli_action='resource') + ctx.run(cli_action='resource', resource_clone_ids=self.fmt_as_stack_argument(self.module.params["resource_clone_ids"], "clone")) + + def state_cloned(self): + with self.runner( + 'cli_action state name resource_clone_ids resource_clone_meta wait', + output_process=self._process_command_output( + not get_pacemaker_maintenance_mode(self.runner), + "already a clone resource"), check_mode_skip=True) as ctx: + ctx.run(cli_action='resource', resource_clone_meta=self.fmt_as_stack_argument(self.module.params["resource_clone_meta"], "meta")) def state_enabled(self): with self.runner('cli_action state name', output_process=self._process_command_output(True, "Starting"), check_mode_skip=True) as ctx: diff --git a/tests/unit/plugins/modules/test_pacemaker_resource.yaml b/tests/unit/plugins/modules/test_pacemaker_resource.yaml index 3a6c74a7eb..a2f56840b8 100644 --- a/tests/unit/plugins/modules/test_pacemaker_resource.yaml +++ b/tests/unit/plugins/modules/test_pacemaker_resource.yaml @@ -78,6 +78,10 @@ test_cases: argument_action: group argument_option: - test_group + resource_clone_ids: + - test_clone + resource_clone_meta: + - test_clone_meta1=789 wait: 200 output: changed: true @@ -100,7 +104,7 @@ test_cases: dc-version=2.1.9-1.fc41-7188dbf have-watchdog=false err: "" - - command: [/testbin/pcs, resource, create, virtual-ip, ocf:heartbeat:IPaddr2, "ip=[192.168.2.1]", op, start, timeout=1200, op, stop, timeout=1200, op, monitor, timeout=1200, meta, test_meta1=123, meta, test_meta2=456, --group, test_group, --wait=200] + - command: [/testbin/pcs, resource, create, virtual-ip, ocf:heartbeat:IPaddr2, "ip=[192.168.2.1]", op, start, timeout=1200, op, stop, timeout=1200, op, monitor, timeout=1200, meta, test_meta1=123, meta, test_meta2=456, --group, test_group, clone, test_clone, test_clone_meta1=789, --wait=200] environ: *env-def rc: 0 out: "Assumed agent name 'ocf:heartbeat:IPaddr2'" @@ -496,3 +500,72 @@ test_cases: rc: 0 out: "NO resources configured" err: "" + - id: test_clone_minimal_input_resource_not_exist + input: + state: cloned + name: virtual-ip + output: + failed: true + msg: "pcs failed with error (rc=1): Error: unable to find group or resource: virtual-ip" + mocks: + run_command: + - command: [/testbin/pcs, resource, status, virtual-ip] + environ: *env-def + rc: 1 + out: "" + err: "Error: resource or tag id 'virtual-ip' not found" + - command: [/testbin/pcs, property, config] + environ: *env-def + rc: 1 + out: | + Cluster Properties: cib-bootstrap-options + cluster-infrastructure=corosync + cluster-name=hacluster + dc-version=2.1.9-1.fc41-7188dbf + have-watchdog=false + err: "" + - command: [/testbin/pcs, resource, clone, virtual-ip, --wait=300] + environ: *env-def + rc: 1 + out: "" + err: "Error: unable to find group or resource: virtual-ip" + - id: test_clone_filled_input_resource_exists + input: + state: cloned + name: virtual-ip + resource_clone_ids: + - test_clone + resource_clone_meta: + - test_clone_meta1=789 + wait: 200 + output: + changed: true + previous_value: " * virtual-ip\t(ocf:heartbeat:IPAddr2):\t Started" + value: " * Clone Set: virtual-ip-clone [virtual-ip]\t(ocf:heartbeat:IPAddr2):\t Started" + mocks: + run_command: + - command: [/testbin/pcs, resource, status, virtual-ip] + environ: *env-def + rc: 0 + out: " * virtual-ip\t(ocf:heartbeat:IPAddr2):\t Started" + err: "" + - command: [/testbin/pcs, property, config] + environ: *env-def + rc: 1 + out: | + Cluster Properties: cib-bootstrap-options + cluster-infrastructure=corosync + cluster-name=hacluster + dc-version=2.1.9-1.fc41-7188dbf + have-watchdog=false + err: "" + - command: [/testbin/pcs, resource, clone, virtual-ip, test_clone, meta, test_clone_meta1=789, --wait=200] + environ: *env-def + rc: 0 + out: "" + err: "" + - command: [/testbin/pcs, resource, status, virtual-ip] + environ: *env-def + rc: 0 + out: " * Clone Set: virtual-ip-clone [virtual-ip]\t(ocf:heartbeat:IPAddr2):\t Started" + err: ""