From e32897f4d97c64910e7ae02a9d3e60e83dc80249 Mon Sep 17 00:00:00 2001 From: Bulat Gaifullin Date: Thu, 14 Jul 2016 18:38:20 +0400 Subject: [PATCH] Add ipmi modules for power and boot management (#2550) * Add ipmi modules for power and boot management * ipmi_power - module for power management Parameters: - name - port - user - password - state - timeout * ipmi_boot - module for boot order management Parameters: - name - port - user - password - bootdev - state - persist - uefi * Fixed copyright * Supported check mode Also added description for RETURN * Added ipmi to list of excludes of tests for python2.4 * added no_log=True for secrets * added type for port and mark bootdev as required field --- .travis.yml | 2 +- bmc/__init__.py | 0 bmc/ipmi/__init__.py | 0 bmc/ipmi/ipmi_boot.py | 186 +++++++++++++++++++++++++++++++++++++++++ bmc/ipmi/ipmi_power.py | 138 ++++++++++++++++++++++++++++++ 5 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 bmc/__init__.py create mode 100644 bmc/ipmi/__init__.py create mode 100644 bmc/ipmi/ipmi_boot.py create mode 100644 bmc/ipmi/ipmi_power.py diff --git a/.travis.yml b/.travis.yml index 05a60a8dcb2..7f1ae7126cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -117,7 +117,7 @@ install: - pip install git+https://github.com/ansible/ansible.git@devel#egg=ansible - pip install git+https://github.com/sivel/ansible-testing.git#egg=ansible_testing script: - - python2.4 -m compileall -fq -x 'cloud/|monitoring/zabbix.*\.py|/dnf\.py|/layman\.py|/maven_artifact\.py|clustering/(consul.*|znode)\.py|notification/pushbullet\.py|database/influxdb/influxdb.*\.py|database/mssql/mssql_db\.py|/letsencrypt\.py|network/f5/bigip.*\.py' . + - python2.4 -m compileall -fq -x 'cloud/|monitoring/zabbix.*\.py|/dnf\.py|/layman\.py|/maven_artifact\.py|clustering/(consul.*|znode)\.py|notification/pushbullet\.py|database/influxdb/influxdb.*\.py|database/mssql/mssql_db\.py|/letsencrypt\.py|network/f5/bigip.*\.py|bmc/ipmi/.*\.py' . - python2.6 -m compileall -fq . - python2.7 -m compileall -fq . - python3.4 -m compileall -fq . -x $(echo "$PY3_EXCLUDE_LIST"| tr ' ' '|') diff --git a/bmc/__init__.py b/bmc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bmc/ipmi/__init__.py b/bmc/ipmi/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bmc/ipmi/ipmi_boot.py b/bmc/ipmi/ipmi_boot.py new file mode 100644 index 00000000000..e8f13d8bd7c --- /dev/null +++ b/bmc/ipmi/ipmi_boot.py @@ -0,0 +1,186 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +try: + from pyghmi.ipmi import command +except ImportError: + command = None + +from ansible.module_utils.basic import * + + +DOCUMENTATION = ''' +--- +module: ipmi_boot +short_description: Management of order of boot devices +description: + - Use this module to manage order of boot devices +version_added: "2.2" +options: + name: + description: + - Hostname or ip address of the BMC. + required: true + port: + description: + - Remote RMCP port. + required: false + type: int + default: 623 + user: + description: + - Username to use to connect to the BMC. + required: true + password: + description: + - Password to connect to the BMC. + required: true + default: null + bootdev: + description: + - Set boot device to use on next reboot + required: true + choices: + - network -- Request network boot + - hd -- Boot from hard drive + - safe -- Boot from hard drive, requesting 'safe mode' + - optical -- boot from CD/DVD/BD drive + - setup -- Boot into setup utility + - default -- remove any IPMI directed boot device request + state: + description: + - Whether to ensure that boot devices is desired. + default: present + choices: + - present -- Request system turn on + - absent -- Request system turn on + persistent: + description: + - If set, ask that system firmware uses this device beyond next boot. + Be aware many systems do not honor this. + required: false + type: boolean + default: false + uefiboot: + description: + - If set, request UEFI boot explicitly. + Strictly speaking, the spec suggests that if not set, the system should BIOS boot and offers no "don't care" option. + In practice, this flag not being set does not preclude UEFI boot on any system I've encountered. + required: false + type: boolean + default: false +requirements: + - "python >= 2.6" + - pyghmi +author: "Bulat Gaifullin (gaifullinbf@gmail.com)" +''' + +RETURN = ''' +bootdev: + description: The boot device name which will be used beyond next boot. + returned: success + type: string + sample: default +persistent: + description: If True, system firmware will use this device beyond next boot. + returned: success + type: bool + sample: false +uefimode: + description: If True, system firmware will use UEFI boot explicitly beyond next boot. + returned: success + type: bool + sample: false +''' + +EXAMPLES = ''' +# Ensure bootdevice is HD. +- ipmi_boot: name="test.testdomain.com" user="admin" password="password" bootdev="hd" +# Ensure bootdevice is not Network +- ipmi_boot: name="test.testdomain.com" user="admin" password="password" bootdev="network" state=absent +''' + +# ================================================== + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + port=dict(default=623, type='int'), + user=dict(required=True, no_log=True), + password=dict(required=True, no_log=True), + state=dict(default='present', choices=['present', 'absent']), + bootdev=dict(required=True, choices=['network', 'hd', 'safe', 'optical', 'setup', 'default']), + persistent=dict(default=False, type='bool'), + uefiboot=dict(default=False, type='bool') + ), + supports_check_mode=True, + ) + + if command is None: + module.fail_json(msg='the python pyghmi module is required') + + name = module.params['name'] + port = module.params['port'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + bootdev = module.params['bootdev'] + persistent = module.params['persistent'] + uefiboot = module.params['uefiboot'] + request = dict() + + if state == 'absent' and bootdev == 'default': + module.fail_json(msg="The bootdev 'default' cannot be used with state 'absent'.") + + # --- run command --- + try: + ipmi_cmd = command.Command( + bmc=name, userid=user, password=password, port=port + ) + module.debug('ipmi instantiated - name: "%s"' % name) + current = ipmi_cmd.get_bootdev() + # uefimode may not supported by BMC, so use desired value as default + current.setdefault('uefimode', uefiboot) + if state == 'present' and current != dict(bootdev=bootdev, persistent=persistent, uefimode=uefiboot): + request = dict(bootdev=bootdev, uefiboot=uefiboot, persist=persistent) + elif state == 'absent' and current['bootdev'] == bootdev: + request = dict(bootdev='default') + else: + module.exit_json(changed=False, **current) + + if module.check_mode: + response = dict(bootdev=request['bootdev']) + else: + response = ipmi_cmd.set_bootdev(**request) + + if 'error' in response: + module.fail_json(msg=response['error']) + + if 'persist' in request: + response['persistent'] = request['persist'] + if 'uefiboot' in request: + response['uefimode'] = request['uefiboot'] + + module.exit_json(changed=True, **response) + except Exception as e: + module.fail_json(msg=str(e)) + +if __name__ == '__main__': + main() diff --git a/bmc/ipmi/ipmi_power.py b/bmc/ipmi/ipmi_power.py new file mode 100644 index 00000000000..c6cc8df0301 --- /dev/null +++ b/bmc/ipmi/ipmi_power.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +try: + from pyghmi.ipmi import command +except ImportError: + command = None + +from ansible.module_utils.basic import * + + +DOCUMENTATION = ''' +--- +module: ipmi_power +short_description: Power management for machine +description: + - Use this module for power management +version_added: "2.2" +options: + name: + description: + - Hostname or ip address of the BMC. + required: true + port: + description: + - Remote RMCP port. + required: false + type: int + default: 623 + user: + description: + - Username to use to connect to the BMC. + required: true + password: + description: + - Password to connect to the BMC. + required: true + default: null + state: + description: + - Whether to ensure that the machine in desired state. + required: true + choices: + - on -- Request system turn on + - off -- Request system turn off without waiting for OS to shutdown + - shutdown -- Have system request OS proper shutdown + - reset -- Request system reset without waiting for OS + - boot -- If system is off, then 'on', else 'reset' + timeout: + description: + - Maximum number of seconds before interrupt request. + required: false + type: int + default: 300 +requirements: + - "python >= 2.6" + - pyghmi +author: "Bulat Gaifullin (gaifullinbf@gmail.com)" +''' + +RETURN = ''' +powerstate: + description: The current power state of the machine. + returned: success + type: string + sample: on +''' + +EXAMPLES = ''' +# Ensure machine is powered on. +- ipmi_power: name="test.testdomain.com" user="admin" password="password" state="on" +''' + +# ================================================== + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + port=dict(default=623, type='int'), + state=dict(required=True, choices=['on', 'off', 'shutdown', 'reset', 'boot']), + user=dict(required=True, no_log=True), + password=dict(required=True, no_log=True), + timeout=dict(default=300, type='int'), + ), + supports_check_mode=True, + ) + + if command is None: + module.fail_json(msg='the python pyghmi module is required') + + name = module.params['name'] + port = module.params['port'] + user = module.params['user'] + password = module.params['password'] + state = module.params['state'] + timeout = module.params['timeout'] + + # --- run command --- + try: + ipmi_cmd = command.Command( + bmc=name, userid=user, password=password, port=port + ) + 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 + else: + response = current + changed = False + + if 'error' in response: + module.fail_json(msg=response['error']) + + module.exit_json(changed=changed, **response) + except Exception as e: + module.fail_json(msg=str(e)) + +if __name__ == '__main__': + main()