From ebc0ef882a90276103dc0494e9c203735ebba68a Mon Sep 17 00:00:00 2001 From: mizumm <26898888+mizumm@users.noreply.github.com> Date: Sat, 8 Jan 2022 10:03:51 -0500 Subject: [PATCH] ipmi_power: Add machine option to ensure the power state via the remote target address (#3968) * ipmi_power: Add machine option to ensure the power state via the remote target address * Fix yamllint sanity check error * Add changelog fragment entry * Apply suggestions from the code review * update to apply suggestions * Add version_added. Co-authored-by: Felix Fontein --- .../3968-ipmi_power-add-machine-option.yaml | 4 + .../remote_management/ipmi/ipmi_power.py | 142 ++++++++++++++++-- 2 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 changelogs/fragments/3968-ipmi_power-add-machine-option.yaml diff --git a/changelogs/fragments/3968-ipmi_power-add-machine-option.yaml b/changelogs/fragments/3968-ipmi_power-add-machine-option.yaml new file mode 100644 index 00000000000..bb9b31ad8d9 --- /dev/null +++ b/changelogs/fragments/3968-ipmi_power-add-machine-option.yaml @@ -0,0 +1,4 @@ +--- +minor_changes: + - ipmi_power - add ``machine`` option to ensure the power state via the remote target + address (https://github.com/ansible-collections/community.general/pull/3968). diff --git a/plugins/modules/remote_management/ipmi/ipmi_power.py b/plugins/modules/remote_management/ipmi/ipmi_power.py index e2d977f6e81..9abf167f609 100644 --- a/plugins/modules/remote_management/ipmi/ipmi_power.py +++ b/plugins/modules/remote_management/ipmi/ipmi_power.py @@ -50,14 +50,37 @@ - shutdown -- Have system request OS proper shutdown - reset -- Request system reset without waiting for OS - boot -- If system is off, then 'on', else 'reset'" + - Either this option or I(machine) is required. choices: ['on', 'off', shutdown, reset, boot] - required: true type: str timeout: description: - Maximum number of seconds before interrupt request. default: 300 type: int + machine: + description: + - Provide a list of the remote target address for the bridge IPMI request, + and the power status. + - Either this option or I(state) is required. + required: false + type: list + elements: dict + version_added: 4.3.0 + suboptions: + targetAddress: + description: + - Remote target address for the bridge IPMI request. + type: int + required: true + state: + description: + - Whether to ensure that the machine specified by I(targetAddress) in desired state. + - If this option is not set, the power state is set by I(state). + - If both this option and I(state) are set, this option takes precedence over I(state). + choices: ['on', 'off', shutdown, reset, boot] + type: str + requirements: - "python >= 2.6" - pyghmi @@ -67,9 +90,32 @@ RETURN = ''' powerstate: description: The current power state of the machine. - returned: success + returned: success and I(machine) is not provided type: str sample: on +status: + description: The current power state of the machine when the machine option is set. + returned: success and I(machine) is provided + type: list + elements: dict + version_added: 4.3.0 + contains: + powerstate: + description: The current power state of the machine specified by I(targetAddress). + type: str + targetAddress: + description: The remote target address. + type: int + sample: [ + { + "powerstate": "on", + "targetAddress": 48, + }, + { + "powerstate": "on", + "targetAddress": 50, + }, + ] ''' EXAMPLES = ''' @@ -79,12 +125,34 @@ user: admin password: password state: on + +- name: Ensure machines of which remote target address is 48 and 50 are powered off + community.general.ipmi_power: + name: test.testdomain.com + user: admin + password: password + state: off + machine: + - targetAddress: 48 + - targetAddress: 50 + +- name: Ensure machine of which remote target address is 48 is powered on, and 50 is powered off + community.general.ipmi_power: + name: test.testdomain.com + user: admin + password: password + machine: + - targetAddress: 48 + state: on + - targetAddress: 50 + state: off ''' import traceback import binascii PYGHMI_IMP_ERR = None +INVALID_TARGET_ADDRESS = 0x100 try: from pyghmi.ipmi import command except ImportError: @@ -99,13 +167,23 @@ def main(): argument_spec=dict( name=dict(required=True), port=dict(default=623, type='int'), - state=dict(required=True, choices=['on', 'off', 'shutdown', 'reset', 'boot']), + state=dict(choices=['on', 'off', 'shutdown', 'reset', 'boot']), user=dict(required=True, no_log=True), password=dict(required=True, no_log=True), key=dict(type='str', no_log=True), timeout=dict(default=300, type='int'), + machine=dict( + type='list', elements='dict', + options=dict( + targetAddress=dict(required=True, type='int'), + state=dict(type='str', choices=['on', 'off', 'shutdown', 'reset', 'boot']), + ), + ), ), supports_check_mode=True, + required_one_of=( + ['state', 'machine'], + ), ) if command is None: @@ -117,13 +195,14 @@ def main(): password = module.params['password'] state = module.params['state'] timeout = module.params['timeout'] + machine = module.params['machine'] try: if module.params['key']: key = binascii.unhexlify(module.params['key']) else: key = None - except Exception as e: + except Exception: module.fail_json(msg="Unable to convert 'key' from hex string.") # --- run command --- @@ -133,18 +212,55 @@ def main(): ) module.debug('ipmi instantiated - name: "%s"' % name) - current = ipmi_cmd.get_power() - if current['powerstate'] != state: - response = {'powerstate': state} if module.check_mode else ipmi_cmd.set_power(state, wait=timeout) - changed = True + changed = False + if machine is None: + current = ipmi_cmd.get_power() + if current['powerstate'] != state: + response = {'powerstate': state} if module.check_mode \ + else ipmi_cmd.set_power(state, wait=timeout) + changed = True + else: + response = current + + if 'error' in response: + module.fail_json(msg=response['error']) + + module.exit_json(changed=changed, **response) else: - response = current - changed = False + response = [] + for entry in machine: + taddr = entry['targetAddress'] + if taddr >= INVALID_TARGET_ADDRESS: + module.fail_json(msg="targetAddress should be set between 0 to 255.") + + try: + # bridge_request is supported on pyghmi 1.5.30 and later + current = ipmi_cmd.get_power(bridge_request={"addr": taddr}) + except TypeError: + module.fail_json( + msg="targetAddress isn't supported on the installed pyghmi.") + + if entry['state']: + tstate = entry['state'] + elif state: + tstate = state + else: + module.fail_json(msg="Either state or suboption of machine state should be set.") + + if current['powerstate'] != tstate: + changed = True + if not module.check_mode: + new = ipmi_cmd.set_power(tstate, wait=timeout, bridge_request={"addr": taddr}) + if 'error' in new: + module.fail_json(msg=new['error']) + + response.append( + {'targetAddress:': taddr, 'powerstate': new['powerstate']}) - if 'error' in response: - module.fail_json(msg=response['error']) + if current['powerstate'] == tstate or module.check_mode: + response.append({'targetAddress:': taddr, 'powerstate': tstate}) - module.exit_json(changed=changed, **response) + module.exit_json(changed=changed, status=response) except Exception as e: module.fail_json(msg=str(e))