From a355686987e913c2034180302812431a12344dba Mon Sep 17 00:00:00 2001 From: Kairo Araujo Date: Mon, 4 Feb 2019 03:49:25 +0100 Subject: [PATCH] new module: aix_devices, manage AIX devices (#32290) * new module: aix_devices AIX devices management This module discovery, defines, removes and modifies attributes of AIX devices. * Added hide attributes that can be used for aliases Added hid attributes that can be used to manage aliases on en interfaces. * After tests: docs and attributes tests Fixed attributes tests and doc explaining how to use attributes with comma. * Fixed grammar on module description Fixed grammar on module description * Included test/legacy/aix_devices.yml for tests As discussed on IRC ansible-devel channes, was include the legacy tests for further manual tests. * Added 'attributes' as dictionary Added 'attributes' as a dictionary makes the configuration simple. * Changed the added version from 2.5 to 2.7 Fixed the shippable error from 2.5 to 2.7 ``` 2018-06-01 08:28:02 ERROR: Found 1 validate-modules issue(s) which need to be resolved: 2018-06-01 08:28:02 ERROR: lib/ansible/modules/system/aix_devices.py:0:0: E307 version_added should be 2.7. Currently 2.5 (75%) ``` * Various changes * Revert * Changed the tests to integration dir * Implement 'available' state 'available' state is the AIX state used, that works same as 'present' * The states were changed to AIX expressions and kept Ansible states. Makes sense keep the states names to AIX and use the Ansible 'standards' states. 'available' is 'present' 'removed' is 'absent' It makes easy to AIX sysadmins use the module, however it keep the Ansible meanings. * Fixed choices according with latest patchset (commit) * A few doc changes Nothing material --- lib/ansible/modules/system/aix_devices.py | 377 ++++++++++++++++++ test/integration/targets/aix_devices/aliases | 2 + .../targets/aix_devices/tasks/main.yml | 77 ++++ 3 files changed, 456 insertions(+) create mode 100644 lib/ansible/modules/system/aix_devices.py create mode 100644 test/integration/targets/aix_devices/aliases create mode 100644 test/integration/targets/aix_devices/tasks/main.yml diff --git a/lib/ansible/modules/system/aix_devices.py b/lib/ansible/modules/system/aix_devices.py new file mode 100644 index 00000000000..0171effb721 --- /dev/null +++ b/lib/ansible/modules/system/aix_devices.py @@ -0,0 +1,377 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, 2018 Kairo Araujo +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = r''' +--- +author: +- Kairo Araujo (@kairoaraujo) +module: aix_devices +short_description: Manages AIX devices +description: +- This module discovers, defines, removes and modifies attributes of AIX devices. +version_added: '2.8' +options: + attributes: + description: + - A list of device attributes. + type: list + device: + description: + - The name of the device. + - C(all) is valid to rescan C(available) all devices (AIX cfgmgr command). + type: str + required: true + force: + description: + - Forces action. + type: bool + default: no + recursive: + description: + - Removes or defines a device and children devices. + type: bool + default: no + state: + description: + - Controls the device state. + - C(available) (alias C(present)) rescan a specific device or all devices (when C(device) is not specified). + - C(removed) (alias C(absent) removes a device. + - C(defined) changes device to Defined state. + type: str + choices: [ available, defined, removed ] + default: available +''' + +EXAMPLES = r''' +- name: Scan new devices + aix_devices: + device: all + state: available + +- name: Scan new virtual devices (vio0) + aix_devices: + device: vio0 + state: available + +- name: Removing IP alias to en0 + aix_devices: + device: en0 + attributes: + delalias4: 10.0.0.100,255.255.255.0 + +- name: Removes ent2 + aix_devices: + device: ent2 + state: removed + +- name: Put device en2 in Defined + aix_devices: + device: en2 + state: defined + +- name: Removes ent4 (inexistent). + aix_devices: + device: ent4 + state: removed + +- name: Put device en4 in Defined (inexistent) + aix_devices: + device: en4 + state: defined + +- name: Put vscsi1 and children devices in Defined state. + aix_devices: + device: vscsi1 + recursive: yes + state: defined + +- name: Removes vscsi1 and children devices. + aix_devices: + device: vscsi1 + recursive: yes + state: removed + +- name: Changes en1 mtu to 9000 and disables arp. + aix_devices: + device: en1 + attributes: + mtu: 900 + arp: off + state: available + +- name: Configure IP, netmask and set en1 up. + aix_devices: + device: en1 + attributes: + netaddr: 192.168.0.100 + netmask: 255.255.255.0 + state: up + state: available + +- name: Adding IP alias to en0 + aix_devices: + device: en0 + attributes: + alias4: 10.0.0.100,255.255.255.0 + state: available +''' + +RETURN = r''' # ''' + +from ansible.module_utils.basic import AnsibleModule + + +def _check_device(module, device): + """ + Check if device already exists and the state. + Args: + module: Ansible module. + device: device to be checked. + + Returns: bool, device state + + """ + lsdev_cmd = module.get_bin_path('lsdev', True) + rc, lsdev_out, err = module.run_command(["%s" % lsdev_cmd, '-C', '-l', "%s" % device]) + + if rc != 0: + module.fail_json(msg="Failed to run lsdev", rc=rc, err=err) + + if lsdev_out: + device_state = lsdev_out.split()[1] + return True, device_state + + device_state = None + return False, device_state + + +def _check_device_attr(module, device, attr): + """ + + Args: + module: Ansible module. + device: device to check attributes. + attr: attribute to be checked. + + Returns: + + """ + lsattr_cmd = module.get_bin_path('lsattr', True) + rc, lsattr_out, err = module.run_command(["%s" % lsattr_cmd, '-El', "%s" % device, '-a', "%s" % attr]) + + hidden_attrs = ['delalias4', 'delalias6'] + + if rc == 255: + + if attr in hidden_attrs: + current_param = '' + else: + current_param = None + + return current_param + + elif rc != 0: + module.fail_json(msg="Failed to run lsattr: %s" % err, rc=rc, err=err) + + current_param = lsattr_out.split()[1] + return current_param + + +def discover_device(module, device): + """ Discover AIX devices.""" + cfgmgr_cmd = module.get_bin_path('cfgmgr', True) + + if device is not None: + device = "-l %s" % device + + else: + device = '' + + changed = True + msg = '' + if not module.check_mode: + rc, cfgmgr_out, err = module.run_command(["%s" % cfgmgr_cmd, "%s" % device]) + changed = True + msg = cfgmgr_out + + return changed, msg + + +def change_device_attr(module, attributes, device, force): + """ Change AIX device attribute. """ + + attr_changed = [] + attr_not_changed = [] + attr_invalid = [] + chdev_cmd = module.get_bin_path('chdev', True) + + for attr in list(attributes.keys()): + new_param = attributes[attr] + current_param = _check_device_attr(module, device, attr) + + if current_param is None: + attr_invalid.append(attr) + + elif current_param != new_param: + if force: + cmd = ["%s" % chdev_cmd, '-l', "%s" % device, '-a', "%s=%s" % (attr, attributes[attr]), "%s" % force] + else: + cmd = ["%s" % chdev_cmd, '-l', "%s" % device, '-a', "%s=%s" % (attr, attributes[attr])] + + if not module.check_mode: + rc, chdev_out, err = module.run_command(cmd) + if rc != 0: + module.exit_json(msg="Failed to run chdev.", rc=rc, err=err) + + attr_changed.append(attributes[attr]) + else: + attr_not_changed.append(attributes[attr]) + + if len(attr_changed) > 0: + changed = True + attr_changed_msg = "Attributes changed: %s. " % ','.join(attr_changed) + else: + changed = False + attr_changed_msg = '' + + if len(attr_not_changed) > 0: + attr_not_changed_msg = "Attributes already set: %s. " % ','.join(attr_not_changed) + else: + attr_not_changed_msg = '' + + if len(attr_invalid) > 0: + attr_invalid_msg = "Invalid attributes: %s " % ', '.join(attr_invalid) + else: + attr_invalid_msg = '' + + msg = "%s%s%s" % (attr_changed_msg, attr_not_changed_msg, attr_invalid_msg) + + return changed, msg + + +def remove_device(module, device, force, recursive, state): + """ Puts device in defined state or removes device. """ + + state_opt = { + 'removed': '-d', + 'absent': '-d', + 'defined': '' + } + + recursive_opt = { + True: '-R', + False: '' + } + + recursive = recursive_opt[recursive] + state = state_opt[state] + + changed = True + msg = '' + rmdev_cmd = module.get_bin_path('rmdev', True) + + if not module.check_mode: + if state: + rc, rmdev_out, err = module.run_command(["%s" % rmdev_cmd, "-l", "%s" % device, "%s" % recursive, "%s" % force]) + else: + rc, rmdev_out, err = module.run_command(["%s" % rmdev_cmd, "-l", "%s" % device, "%s" % recursive]) + + if rc != 0: + module.fail_json(msg="Failed to run rmdev", rc=rc, err=err) + + msg = rmdev_out + + return changed, msg + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + attributes=dict(type='dict'), + device=dict(type='str'), + force=dict(type='bool', default=False), + recursive=dict(type='bool', default=False), + state=dict(type='str', default='available', choices=['available', 'defined', 'removed']), + ), + supports_check_mode=True, + ) + + force_opt = { + True: '-f', + False: '', + } + + attributes = module.params['attributes'] + device = module.params['device'] + force = force_opt[module.params['force']] + recursive = module.params['recursive'] + state = module.params['state'] + + result = dict( + changed=False, + msg='', + ) + + if state == 'available' or state == 'present': + if attributes: + # change attributes on device + device_status, device_state = _check_device(module, device) + if device_status: + result['changed'], result['msg'] = change_device_attr(module, attributes, device, force) + else: + result['msg'] = "Device %s does not exist." % device + + else: + # discovery devices (cfgmgr) + if device and device != 'all': + device_status, device_state = _check_device(module, device) + if device_status: + # run cfgmgr on specific device + result['changed'], result['msg'] = discover_device(module, device) + + else: + result['msg'] = "Device %s does not exist." % device + + else: + result['changed'], result['msg'] = discover_device(module, device) + + elif state == 'removed' or state == 'absent' or state == 'defined': + if not device: + result['msg'] = "device is required to removed or defined state." + + else: + # Remove device + check_device, device_state = _check_device(module, device) + if check_device: + if state == 'defined' and device_state == 'Defined': + result['changed'] = False + result['msg'] = 'Device %s already in Defined' % device + + else: + result['changed'], result['msg'] = remove_device(module, device, force, recursive, state) + + else: + result['msg'] = "Device %s does not exist." % device + + else: + result['msg'] = "Unexpected state %s." % state + module.fail_json(**result) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/aix_devices/aliases b/test/integration/targets/aix_devices/aliases new file mode 100644 index 00000000000..e6cab07d715 --- /dev/null +++ b/test/integration/targets/aix_devices/aliases @@ -0,0 +1,2 @@ +# No AIX LPAR available +unsupported diff --git a/test/integration/targets/aix_devices/tasks/main.yml b/test/integration/targets/aix_devices/tasks/main.yml new file mode 100644 index 00000000000..cb98c82a029 --- /dev/null +++ b/test/integration/targets/aix_devices/tasks/main.yml @@ -0,0 +1,77 @@ +--- +- name: AIX devices tests + hosts: localhost + connection: local + + tasks: + - name: Scan new devices. + aix_devices: + device: all + state: present + + - name: Scan new virtual devices (vio0). + aix_devices: + device: vio0 + state: present + + - name: Removing IP alias to en0 + aix_devices: + device: en0 + attributes: + delalias4: 10.0.0.100,255.255.255.0 + + - name: Removes ent2. + aix_devices: + device: ent2 + state: absent + + - name: Put device en2 in Defined + aix_devices: + device: en2 + state: defined + + - name: Removes ent4 (inexistent). + aix_devices: + device: ent4 + state: absent + + - name: Put device en4 in Defined (inexistent) + aix_devices: + device: en4 + state: defined + + - name: Put vscsi1 and children devices in Defined state. + aix_devices: + device: vscsi1 + recursive: yes + state: defined + + - name: Removes vscsi1 and children devices. + aix_devices: + device: vscsi1 + recursive: yes + state: absent + + - name: Changes en1 mtu to 9000 and disables arp. + aix_devices: + device: en1 + attributes: + mtu: 900 + arp: off + state: present + + - name: Configure IP, netmask and set en1 up. + aix_devices: + device: en1 + attributes: + netaddr: 192.168.0.100 + netmask: 255.255.255.0 + state: up + state: present + + - name: Adding IP alias to en0 + aix_devices: + device: en0 + attributes: + alias4: 10.0.0.100,255.255.255.0 + state: present