pacemaker_resource: Add cloning support for resources and groups (#10665)
Some checks are pending
EOL CI / EOL Sanity (Ⓐ2.16) (push) Waiting to run
EOL CI / EOL Units (Ⓐ2.16+py2.7) (push) Waiting to run
EOL CI / EOL Units (Ⓐ2.16+py3.11) (push) Waiting to run
EOL CI / EOL Units (Ⓐ2.16+py3.6) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/1/) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/2/) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/3/) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/1/) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/2/) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/3/) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/1/) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/2/) (push) Waiting to run
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/3/) (push) Waiting to run
nox / Run extra sanity tests (push) Waiting to run

* add clone state for pacemaker_resource

* add changelog fragment

* Additional description entry for comment header

* Apply suggestions from code review

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update plugins/modules/pacemaker_resource.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* fix formatting for yamllint

* Apply code review suggestions

* refactor state name to cloned

* Update plugins/modules/pacemaker_resource.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Apply suggestions from code review

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* Apply suggestions from code review

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
This commit is contained in:
Dexter 2025-09-07 15:24:01 -04:00 committed by GitHub
commit 3baa13a3e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 113 additions and 7 deletions

View file

@ -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).

View file

@ -13,6 +13,7 @@ from ansible_collections.community.general.plugins.module_utils.cmd_runner impor
_state_map = { _state_map = {
"present": "create", "present": "create",
"absent": "remove", "absent": "remove",
"cloned": "clone",
"status": "status", "status": "status",
"enabled": "enable", "enabled": "enable",
"disabled": "disable", "disabled": "disable",
@ -65,6 +66,8 @@ def pacemaker_runner(module, **kwargs):
resource_operation=cmd_runner_fmt.as_func(fmt_resource_operation), resource_operation=cmd_runner_fmt.as_func(fmt_resource_operation),
resource_meta=cmd_runner_fmt.stack(cmd_runner_fmt.as_opt_val)("meta"), resource_meta=cmd_runner_fmt.stack(cmd_runner_fmt.as_opt_val)("meta"),
resource_argument=cmd_runner_fmt.as_func(fmt_resource_argument), 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"), apply_all=cmd_runner_fmt.as_bool("--all"),
agent_validation=cmd_runner_fmt.as_bool("--agent-validation"), agent_validation=cmd_runner_fmt.as_bool("--agent-validation"),
wait=cmd_runner_fmt.as_opt_eq_val("--wait"), wait=cmd_runner_fmt.as_opt_eq_val("--wait"),

View file

@ -27,13 +27,14 @@ options:
state: state:
description: description:
- Indicate desired state for cluster resource. - Indicate desired state for cluster resource.
- The state V(cleanup) has been added in community.general 11.3.0. - The states V(cleanup) and V(cloned) have been added in community.general 11.3.0.
choices: [present, absent, enabled, disabled, cleanup] - 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 default: present
type: str type: str
name: name:
description: 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). - This is required if O(state=present), O(state=absent), O(state=enabled), or O(state=disabled).
type: str type: str
resource_type: resource_type:
@ -95,6 +96,18 @@ options:
- Options to associate with resource action. - Options to associate with resource action.
type: list type: list
elements: str 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: wait:
description: description:
- Timeout period for polling the resource creation. - Timeout period for polling the resource creation.
@ -142,7 +155,7 @@ class PacemakerResource(StateModuleHelper):
module = dict( module = dict(
argument_spec=dict( argument_spec=dict(
state=dict(type='str', default='present', choices=[ state=dict(type='str', default='present', choices=[
'present', 'absent', 'enabled', 'disabled', 'cleanup']), 'present', 'absent', 'cloned', 'enabled', 'disabled', 'cleanup']),
name=dict(type='str'), name=dict(type='str'),
resource_type=dict(type='dict', options=dict( resource_type=dict(type='dict', options=dict(
resource_name=dict(type='str'), resource_name=dict(type='str'),
@ -159,6 +172,8 @@ class PacemakerResource(StateModuleHelper):
argument_action=dict(type='str', choices=['clone', 'master', 'group', 'promotable']), argument_action=dict(type='str', choices=['clone', 'master', 'group', 'promotable']),
argument_option=dict(type='list', elements='str'), 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), wait=dict(type='int', default=300),
), ),
required_if=[ required_if=[
@ -194,6 +209,10 @@ class PacemakerResource(StateModuleHelper):
('out', result[1] if result[1] != "" else None), ('out', result[1] if result[1] != "" else None),
('err', result[2])]) ('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): def state_absent(self):
force = get_pacemaker_maintenance_mode(self.runner) 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: 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): def state_present(self):
with self.runner( 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"), output_process=self._process_command_output(not get_pacemaker_maintenance_mode(self.runner), "already exists"),
check_mode_skip=True) as ctx: 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): 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: with self.runner('cli_action state name', output_process=self._process_command_output(True, "Starting"), check_mode_skip=True) as ctx:

View file

@ -78,6 +78,10 @@ test_cases:
argument_action: group argument_action: group
argument_option: argument_option:
- test_group - test_group
resource_clone_ids:
- test_clone
resource_clone_meta:
- test_clone_meta1=789
wait: 200 wait: 200
output: output:
changed: true changed: true
@ -100,7 +104,7 @@ test_cases:
dc-version=2.1.9-1.fc41-7188dbf dc-version=2.1.9-1.fc41-7188dbf
have-watchdog=false have-watchdog=false
err: "" 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 environ: *env-def
rc: 0 rc: 0
out: "Assumed agent name 'ocf:heartbeat:IPaddr2'" out: "Assumed agent name 'ocf:heartbeat:IPaddr2'"
@ -496,3 +500,72 @@ test_cases:
rc: 0 rc: 0
out: "NO resources configured" out: "NO resources configured"
err: "" 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: ""