community.general/plugins/modules/pacemaker_resource.py
Dexter 6bbd1dd7f5
pacemaker: add cluster maintenance mode checks (#10194)
* feat(maintenance): Add cluster maintenance mode checks for pacemaker

* bug(fix): Correct substring typo and unit test

This commit corrects a substring check for determining if the pacemaker
cluster is in maintenance mode. Additionally, unit test is corrected
with correct output from pacemaker when in maintenance mode.

* feat(maintenance): Add force argument for absent resources

This commit adds in --force argument for resources intended to be absent
within a cluster that is in maintenance mode. Without this argument, the
cluster will not attempt to remove the resource due to maintenance mode.
The resource will be declared as orphaned and exiting maintenance mode
will allow the cluster to remove the resource completely.

* refactor(review): Apply code review changes

This commit adds refactors to enhance code quality.

* doc(changelog): Add fragment for maintenance mode addition
2025-06-07 17:52:32 +02:00

230 lines
7.6 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2025, Dexter Le <dextersydney2001@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: pacemaker_resource
short_description: Manage pacemaker resources
author:
- Dexter Le (@munchtoast)
version_added: 10.5.0
description:
- This module can manage resources in a Pacemaker cluster using the pacemaker CLI.
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
state:
description:
- Indicate desired state for cluster resource.
choices: [ present, absent, enabled, disabled ]
default: present
type: str
name:
description:
- Specify the resource name to create.
required: true
type: str
resource_type:
description:
- Resource type to create.
type: dict
suboptions:
resource_name:
description:
- Specify the resource type name.
type: str
resource_standard:
description:
- Specify the resource type standard.
type: str
resource_provider:
description:
- Specify the resource type providers.
type: str
resource_option:
description:
- Specify the resource option to create.
type: list
elements: str
default: []
resource_operation:
description:
- List of operations to associate with resource.
type: list
elements: dict
default: []
suboptions:
operation_action:
description:
- Operation action to associate with resource.
type: str
operation_option:
description:
- Operation option to associate with action.
type: list
elements: str
resource_meta:
description:
- List of meta to associate with resource.
type: list
elements: str
resource_argument:
description:
- Action to associate with resource.
type: dict
suboptions:
argument_action:
description:
- Action to apply to resource.
type: str
choices: [ clone, master, group, promotable ]
argument_option:
description:
- Options to associate with resource action.
type: list
elements: str
wait:
description:
- Timeout period for polling the resource creation.
type: int
default: 300
'''
EXAMPLES = '''
---
- name: Create pacemaker resource
hosts: localhost
gather_facts: false
tasks:
- name: Create virtual-ip resource
community.general.pacemaker_resource:
state: present
name: virtual-ip
resource_type:
resource_name: IPaddr2
resource_option:
- "ip=[192.168.2.1]"
resource_argument:
argument_action: group
argument_option:
- master
resource_operation:
- operation_action: monitor
operation_option:
- interval=20
'''
RETURN = '''
cluster_resources:
description: The cluster resource output message.
type: str
sample: "Assumed agent name ocf:heartbeat:IPaddr2 (deduced from IPaddr2)"
returned: always
'''
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
from ansible_collections.community.general.plugins.module_utils.pacemaker import pacemaker_runner, get_pacemaker_maintenance_mode
class PacemakerResource(StateModuleHelper):
module = dict(
argument_spec=dict(
state=dict(type='str', default='present', choices=[
'present', 'absent', 'enabled', 'disabled']),
name=dict(type='str', required=True),
resource_type=dict(type='dict', options=dict(
resource_name=dict(type='str'),
resource_standard=dict(type='str'),
resource_provider=dict(type='str'),
)),
resource_option=dict(type='list', elements='str', default=list()),
resource_operation=dict(type='list', elements='dict', default=list(), options=dict(
operation_action=dict(type='str'),
operation_option=dict(type='list', elements='str'),
)),
resource_meta=dict(type='list', elements='str'),
resource_argument=dict(type='dict', options=dict(
argument_action=dict(type='str', choices=['clone', 'master', 'group', 'promotable']),
argument_option=dict(type='list', elements='str'),
)),
wait=dict(type='int', default=300),
),
required_if=[('state', 'present', ['resource_type', 'resource_option'])],
supports_check_mode=True,
)
default_state = "present"
def __init_module__(self):
self.runner = pacemaker_runner(self.module, cli_action='resource')
self._maintenance_mode_runner = pacemaker_runner(self.module, cli_action='property')
self.vars.set('previous_value', self._get())
self.vars.set('value', self.vars.previous_value, change=True, diff=True)
def _process_command_output(self, fail_on_err, ignore_err_msg=""):
def process(rc, out, err):
if fail_on_err and rc != 0 and err and ignore_err_msg not in err:
self.do_raise('pcs failed with error (rc={0}): {1}'.format(rc, err))
out = out.rstrip()
return None if out == "" else out
return process
def _get(self):
with self.runner('state name', output_process=self._process_command_output(False)) as ctx:
return ctx.run(state='status')
def state_absent(self):
runner_args = ['state', 'name', 'force']
force = get_pacemaker_maintenance_mode(self._maintenance_mode_runner)
with self.runner(runner_args, output_process=self._process_command_output(True, "does not exist"), check_mode_skip=True) as ctx:
ctx.run(force=force)
self.vars.set('value', self._get())
self.vars.stdout = ctx.results_out
self.vars.stderr = ctx.results_err
self.vars.cmd = ctx.cmd
def state_present(self):
with self.runner(
'state name resource_type resource_option resource_operation resource_meta resource_argument wait',
output_process=self._process_command_output(not get_pacemaker_maintenance_mode(self._maintenance_mode_runner), "already exists"),
check_mode_skip=True) as ctx:
ctx.run()
self.vars.set('value', self._get())
self.vars.stdout = ctx.results_out
self.vars.stderr = ctx.results_err
self.vars.cmd = ctx.cmd
def state_enabled(self):
with self.runner('state name', output_process=self._process_command_output(True, "Starting"), check_mode_skip=True) as ctx:
ctx.run()
self.vars.set('value', self._get())
self.vars.stdout = ctx.results_out
self.vars.stderr = ctx.results_err
self.vars.cmd = ctx.cmd
def state_disabled(self):
with self.runner('state name', output_process=self._process_command_output(True, "Stopped"), check_mode_skip=True) as ctx:
ctx.run()
self.vars.set('value', self._get())
self.vars.stdout = ctx.results_out
self.vars.stderr = ctx.results_err
self.vars.cmd = ctx.cmd
def main():
PacemakerResource.execute()
if __name__ == '__main__':
main()