From 58239671c0f48751c343e15b92b5a93d40f43fb5 Mon Sep 17 00:00:00 2001 From: David Soper Date: Thu, 21 Dec 2017 14:34:13 -0600 Subject: [PATCH] Ucsm WWNN/WWPN pools (#34017) * vhba template and integration test * SAN Connectivity Policies and integration test * VSANs and integration tests * WWNN/WWPN Pools and integration test * WWNN/WWPN Pools and integration test * option description spelled out (aliased to descr) removed docs for list of dictionaries syntax follow ACI indent style state absent only requires dn name match (not full prop match) --- .../remote_management/ucs/ucs_wwn_pool.py | 224 ++++++++++++++++++ test/integration/targets/ucs_wwn_pool/aliases | 6 + .../targets/ucs_wwn_pool/tasks/main.yml | 129 ++++++++++ 3 files changed, 359 insertions(+) create mode 100644 lib/ansible/modules/remote_management/ucs/ucs_wwn_pool.py create mode 100644 test/integration/targets/ucs_wwn_pool/aliases create mode 100644 test/integration/targets/ucs_wwn_pool/tasks/main.yml diff --git a/lib/ansible/modules/remote_management/ucs/ucs_wwn_pool.py b/lib/ansible/modules/remote_management/ucs/ucs_wwn_pool.py new file mode 100644 index 00000000000..b0cd10b2f23 --- /dev/null +++ b/lib/ansible/modules/remote_management/ucs/ucs_wwn_pool.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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''' +--- +module: ucs_wwn_pool +short_description: Configures WWNN or WWPN pools on Cisco UCS Manager +description: +- Configures WWNNs or WWPN pools on Cisco UCS Manager. +- Examples can be used with the UCS Platform Emulator U(https://communities.cisco.com/ucspe). +extends_documentation_fragment: ucs +options: + state: + description: + - If C(present), will verify WWNNs/WWPNs are present and will create if needed. + - If C(absent), will verify WWNNs/WWPNs are absent and will delete if needed. + choices: [present, absent] + default: present + name: + description: + - Name of the WWNN/WWPN + required: yes + purpose: + description: + - Specify node (WWNN) or port (WWPN) + - Optional if state absent + choices: [node, port] + required: yes + descrption: + description: + - Description for the WWNN/WWPN pool + aliases: [ descr ] + order: + description: + - Assignment order + choices: [default, sequential] + default: default + first_addr: + description: First WWNN/WWPN address in the WWN addresses block + last_addr: + description: Last WWNN/WWPN address in the WWN addresses block + org_dn: + description: + - Org dn (distinguished name) + default: org-root +requirements: +- ucsmsdk +author: +- David Soper (@dsoper2) +- CiscoUcs (@CiscoUcs) +version_added: '2.5' +''' + +EXAMPLES = r''' +- name: Configure WWNN/WWPN pools + ucs_wwn_pool: + hostname: 172.16.143.150 + username: admin + password: password + name: WWNN-Pool + purpose: node + first_addr: 20:00:00:25:B5:48:00:00 + last_addr: 20:00:00:25:B5:48:00:0F + ucs_wwn_pool: + hostname: 172.16.143.150 + username: admin + password: password + name: WWPN-Pool-A + purpose: port + order: sequential + first_addr: 20:00:00:25:B5:48:0A:00 + last_addr: 20:00:00:25:B5:48:0A:0F + +- name: Remove WWNN/WWPN pools + ucs_wwn_pool: + hostname: 172.16.143.150 + username: admin + password: password + name: WWNN-Pool + state: absent + ucs_wwn_pool: + hostname: 172.16.143.150 + username: admin + password: password + name: WWPN-Pool-A + state: absent +''' + +RETURN = r''' +# +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.remote_management.ucs import UCSModule, ucs_argument_spec + + +def main(): + argument_spec = ucs_argument_spec + argument_spec.update( + org_dn=dict(type='str', default='org-root'), + name=dict(type='str'), + purpose=dict(type='str', choices=['node', 'port']), + descr=dict(type='str'), + order=dict(type='str', default='default', choices=['default', 'sequential']), + first_addr=dict(type='str'), + last_addr=dict(type='str'), + state=dict(type='str', default='present', choices=['present', 'absent']), + wwn_list=dict(type='list'), + ) + + # Note that use of wwn_list is an experimental feature which allows multiple resource updates with a single UCSM connection. + # Support for wwn_list may change or be removed once persistent UCS connections are supported. + # Either wwn_list or name is required (user can specify either a list or single resource). + + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + required_one_of=[ + ['wwn_list', 'name'] + ], + mutually_exclusive=[ + ['wwn_list', 'name'] + ], + ) + ucs = UCSModule(module) + + err = False + + from ucsmsdk.mometa.fcpool.FcpoolInitiators import FcpoolInitiators + from ucsmsdk.mometa.fcpool.FcpoolBlock import FcpoolBlock + + changed = False + try: + # Only documented use is a single resource, but to also support experimental + # feature allowing multiple updates all params are converted to a wwn_list below. + + if module.params['wwn_list']: + # directly use the list (single resource and list are mutually exclusive + wwn_list = module.params['wwn_list'] + else: + # single resource specified, create list from the current params + wwn_list = [module.params] + for wwn in wwn_list: + mo_exists = False + props_match = False + # set default params. Done here to set values for lists which can't be done in the argument_spec + if not wwn.get('descr'): + wwn['descr'] = '' + if not wwn.get('order'): + wwn['order'] = 'default' + # dn is /wwn-pool- for WWNN or WWPN + dn = module.params['org_dn'] + '/wwn-pool-' + wwn['name'] + + mo = ucs.login_handle.query_dn(dn) + if mo: + mo_exists = True + + if module.params['state'] == 'absent': + if mo_exists: + if not module.check_mode: + ucs.login_handle.remove_mo(mo) + ucs.login_handle.commit() + changed = True + else: + # append purpose param with suffix used by UCSM + purpose_param = wwn['purpose'] + '-wwn-assignment' + if mo_exists: + # check top-level mo props + kwargs = {} + kwargs['assignment_order'] = wwn['order'] + kwargs['descr'] = wwn['descr'] + kwargs['purpose'] = purpose_param + if (mo.check_prop_match(**kwargs)): + # top-level props match, check next level mo/props + if 'last_addr' in wwn and 'first_addr' in wwn: + block_dn = dn + '/block-' + wwn['first_addr'].upper() + '-' + wwn['last_addr'].upper() + mo_1 = ucs.login_handle.query_dn(block_dn) + if mo_1: + props_match = True + else: + props_match = True + + if not props_match: + if not module.check_mode: + # create if mo does not already exist + mo = FcpoolInitiators( + parent_mo_or_dn=module.params['org_dn'], + name=wwn['name'], + descr=wwn['descr'], + assignment_order=wwn['order'], + purpose=purpose_param, + ) + if 'last_addr' in wwn and 'first_addr' in wwn: + mo_1 = FcpoolBlock( + parent_mo_or_dn=mo, + to=wwn['last_addr'], + r_from=wwn['first_addr'], + ) + + ucs.login_handle.add_mo(mo, True) + ucs.login_handle.commit() + changed = True + + except Exception as e: + err = True + ucs.result['msg'] = "setup error: %s " % str(e) + + ucs.result['changed'] = changed + if err: + module.fail_json(**ucs.result) + module.exit_json(**ucs.result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/ucs_wwn_pool/aliases b/test/integration/targets/ucs_wwn_pool/aliases new file mode 100644 index 00000000000..30cc9de4a27 --- /dev/null +++ b/test/integration/targets/ucs_wwn_pool/aliases @@ -0,0 +1,6 @@ +# Not enabled, but can be used with the UCS Platform Emulator or UCS hardware. +# Example integration_config.yml: +# --- +# ucs_hostname: 172.16.143.136 +# ucs_username: admin +# ucs_password: password diff --git a/test/integration/targets/ucs_wwn_pool/tasks/main.yml b/test/integration/targets/ucs_wwn_pool/tasks/main.yml new file mode 100644 index 00000000000..f380a3efe4b --- /dev/null +++ b/test/integration/targets/ucs_wwn_pool/tasks/main.yml @@ -0,0 +1,129 @@ +# Test code for the UCS modules +# Copyright 2017, David Soper (@dsoper2) + +- name: Test that we have a UCS host, UCS username, and UCS password + fail: + msg: 'Please define the following variables: ucs_hostname, ucs_username and ucs_password.' + when: ucs_hostname is not defined or ucs_username is not defined or ucs_password is not defined + + +# Setup (clean environment) +- name: WWNN/WWPN Pools absent + ucs_wwn_pool: &wwn_pool_absent + hostname: "{{ ucs_hostname }}" + username: "{{ ucs_username }}" + password: "{{ ucs_password }}" + name: WWPN-Pool-A + state: absent + + +# Test present (check_mode) +- name: WWNN/WWPN Pools present (check_mode) + ucs_wwn_pool: &wwn_pool_present + hostname: "{{ ucs_hostname }}" + username: "{{ ucs_username }}" + password: "{{ ucs_password }}" + name: WWPN-Pool-A + purpose: port + order: sequential + first_addr: 20:00:00:25:B5:48:0A:00 + last_addr: 20:00:00:25:B5:48:0A:0F + check_mode: yes + register: cm_wwn_pool_present + + +# Present (normal mode) +- name: WWNN/WWPN Pools present (normal mode) + ucs_wwn_pool: *wwn_pool_present + register: nm_wwn_pool_present + + +# Test present again (idempotent) +- name: WWNN/WWPN Pools present again (check_mode) + ucs_wwn_pool: *wwn_pool_present + check_mode: yes + register: cm_wwn_pool_present_again + + +# Present again (normal mode) +- name: WWNN/WWPN Pools present again (normal mode) + ucs_wwn_pool: *wwn_pool_present + register: nm_wwn_pool_present_again + + +# Verfiy present +- name: Verify WWNN/WWPN Pools present results + assert: + that: + - cm_wwn_pool_present.changed == nm_wwn_pool_present.changed == true + - cm_wwn_pool_present_again.changed == nm_wwn_pool_present_again.changed == false + + +# Test change (check_mode) +- name: WWNN/WWPN Pools description change (check_mode) + ucs_wwn_pool: &wwn_pool_change + <<: *wwn_pool_present + descr: Testing Ansible + check_mode: yes + register: cm_wwn_pool_descr_change + + +# Change (normal mode) +- name: WWNN/WWPN Pools description change (normal mode) + ucs_wwn_pool: *wwn_pool_change + register: nm_wwn_pool_descr_change + + +# Test change again (idempotent) +- name: WWNN/WWPN Pools description again (check_mode) + ucs_wwn_pool: *wwn_pool_change + check_mode: yes + register: cm_wwn_pool_descr_change_again + + +# Change again (normal mode) +- name: WWNN/WWPN Pools description change again (normal mode) + ucs_wwn_pool: *wwn_pool_change + register: nm_wwn_pool_descr_change_again + + +# Verfiy change +- name: Verify WWNN/WWPN Pools change results + assert: + that: + - cm_wwn_pool_descr_change.changed == nm_wwn_pool_descr_change.changed == true + - cm_wwn_pool_descr_change_again.changed == nm_wwn_pool_descr_change_again.changed == false + + +# Teardown (clean environment) +- name: WWNN/WWPN Pools absent (check_mode) + ucs_wwn_pool: *wwn_pool_absent + check_mode: yes + register: cm_wwn_pool_absent + + +# Absent (normal mode) +- name: WWNN/WWPN Pools absent (normal mode) + ucs_wwn_pool: *wwn_pool_absent + register: nm_wwn_pool_absent + + +# Test absent again (idempotent) +- name: WWNN/WWPN Pools absent again (check_mode) + ucs_wwn_pool: *wwn_pool_absent + check_mode: yes + register: cm_wwn_pool_absent_again + + +# Absent again (normal mode) +- name: WWNN/WWPN Pools absent again (normal mode) + ucs_wwn_pool: *wwn_pool_absent + register: nm_wwn_pool_absent_again + + +# Verfiy absent +- name: Verify WWNN/WWPN Pools absent results + assert: + that: + - cm_wwn_pool_absent.changed == nm_wwn_pool_absent.changed == true + - cm_wwn_pool_absent_again.changed == nm_wwn_pool_absent_again.changed == false