From 027f8dcafd79cee76bb04462638e4a2f1d45e201 Mon Sep 17 00:00:00 2001 From: David Soper Date: Thu, 21 Dec 2017 14:30:10 -0600 Subject: [PATCH] Ucsm vsans (#34016) * vhba template and integration test * SAN Connectivity Policies and integration test * VSANs and integration tests * WWNN/WWPN Pools and integration test * VSANs module and integration test * 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_vsans.py | 191 ++++++++++++++++++ test/integration/targets/ucs_vsans/aliases | 6 + .../targets/ucs_vsans/tasks/main.yml | 127 ++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 lib/ansible/modules/remote_management/ucs/ucs_vsans.py create mode 100644 test/integration/targets/ucs_vsans/aliases create mode 100644 test/integration/targets/ucs_vsans/tasks/main.yml diff --git a/lib/ansible/modules/remote_management/ucs/ucs_vsans.py b/lib/ansible/modules/remote_management/ucs/ucs_vsans.py new file mode 100644 index 00000000000..f5f0f9aba33 --- /dev/null +++ b/lib/ansible/modules/remote_management/ucs/ucs_vsans.py @@ -0,0 +1,191 @@ +#!/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_vsans +short_description: Configures VSANs on Cisco UCS Manager +description: +- Configures VSANs 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 VSANs are present and will create if needed. + - If C(absent), will verify VSANs are absent and will delete if needed. + choices: [present, absent] + default: present + name: + description: + - Name of the VSAN + required: yes + vsan_id: + description: + - VSAN ID + - Optional for state absent + required: yes + vlan_id: + description: + - VLAN ID (for FCoE traffic) + - Optional for state absent + required: yes + fc_zoning: + description: + - Enable/Disable FC Zoning + - Do not enable local zoning if FI is connected to an upstream FC/FCoE switch + choices: [disabled, enabled] + default: disabled + fabric: + description: + - Which fabric + choices: [common, A, B] + default: common +requirements: +- ucsmsdk +author: +- David Soper (@dsoper2) +- CiscoUcs (@CiscoUcs) +version_added: '2.5' +''' + +EXAMPLES = r''' +- name: Configure VSAN + ucs_vsans: + hostname: 172.16.143.150 + username: admin + password: password + name: vsan110 + vsan_id: '110' + vlan_id: '110' + +- name: Remove VSAN + ucs_vsans: + hostname: 172.16.143.150 + username: admin + password: password + name: vsan110 +''' + +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( + name=dict(type='str'), + vsan_id=dict(type='str'), + vlan_id=dict(type='str'), + fc_zoning=dict(type='str', default='disabled', choices=['disabled', 'enabled']), + fabric=dict(type='str', default='common', choices=['common', 'A', 'B']), + state=dict(type='str', default='present', choices=['present', 'absent']), + vsan_list=dict(type='list'), + ) + + # Note that use of vsan_list is an experimental feature which allows multiple resource updates with a single UCSM connection. + # Support for vsan_list may change or be removed once persistent UCS connections are supported. + # Either vsan_list or name/vsan_id/vlan_id is required (user can specify either a list or single resource). + + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + required_one_of=[ + ['vsan_list', 'name'] + ], + mutually_exclusive=[ + ['vsan_list', 'name'] + ], + ) + ucs = UCSModule(module) + + err = False + + from ucsmsdk.mometa.fabric.FabricVsan import FabricVsan + + 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 vsan_list below. + + if module.params['vsan_list']: + # directly use the list (single resource and list are mutually exclusive + vsan_list = module.params['vsan_list'] + else: + # single resource specified, create list from the current params + vsan_list = [module.params] + for vsan in vsan_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 vsan.get('fc_zoning'): + vsan['fc_zoning'] = 'disabled' + if not vsan.get('fabric'): + vsan['fabric'] = 'common' + # dn is fabric/san/net- for common vsans or fabric/san/[A or B]/net- for A or B + dn_base = 'fabric/san' + if vsan['fabric'] != 'common': + dn_base += '/' + vsan['fabric'] + dn = dn_base + '/net-' + vsan['name'] + + mo = ucs.login_handle.query_dn(dn) + if mo: + mo_exists = True + + if module.params['state'] == 'absent': + # mo must exist but all properties do not have to match + if mo_exists: + if not module.check_mode: + ucs.login_handle.remove_mo(mo) + ucs.login_handle.commit() + changed = True + else: + if mo_exists: + # check top-level mo props + kwargs = {} + kwargs['id'] = vsan['vsan_id'] + kwargs['fcoe_vlan'] = vsan['vlan_id'] + kwargs['zoning_state'] = vsan['fc_zoning'] + if (mo.check_prop_match(**kwargs)): + props_match = True + + if not props_match: + if not module.check_mode: + # create if mo does not already exist + mo = FabricVsan( + parent_mo_or_dn=dn_base, + name=vsan['name'], + id=vsan['vsan_id'], + fcoe_vlan=vsan['vlan_id'], + zoning_state=vsan['fc_zoning'], + ) + + 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_vsans/aliases b/test/integration/targets/ucs_vsans/aliases new file mode 100644 index 00000000000..30cc9de4a27 --- /dev/null +++ b/test/integration/targets/ucs_vsans/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_vsans/tasks/main.yml b/test/integration/targets/ucs_vsans/tasks/main.yml new file mode 100644 index 00000000000..a1469ca92cd --- /dev/null +++ b/test/integration/targets/ucs_vsans/tasks/main.yml @@ -0,0 +1,127 @@ +# 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: VSANs absent + ucs_vsans: &vsans_absent + hostname: "{{ ucs_hostname }}" + username: "{{ ucs_username }}" + password: "{{ ucs_password }}" + name: vsan110 + state: absent + + +# Test present (check_mode) +- name: VSANs present (check_mode) + ucs_vsans: &vsans_present + hostname: "{{ ucs_hostname }}" + username: "{{ ucs_username }}" + password: "{{ ucs_password }}" + name: vsan110 + vsan_id: '110' + vlan_id: '110' + check_mode: yes + register: cm_vsans_present + + +# Present (normal mode) +- name: VSANs present (normal mode) + ucs_vsans: *vsans_present + register: nm_vsans_present + + +# Test present again (idempotent) +- name: VSANs present again (check_mode) + ucs_vsans: *vsans_present + check_mode: yes + register: cm_vsans_present_again + + +# Present again (normal mode) +- name: VSANs present again (normal mode) + ucs_vsans: *vsans_present + register: nm_vsans_present_again + + +# Verfiy present +- name: Verify VSANs present results + assert: + that: + - cm_vsans_present.changed == nm_vsans_present.changed == true + - cm_vsans_present_again.changed == nm_vsans_present_again.changed == false + + +# Test change (check_mode) +- name: VSANs VLAN change (check_mode) + ucs_vsans: &vsans_change + <<: *vsans_present + vlan_id: '10' + check_mode: yes + register: cm_vsans_vlan_change + + +# Change (normal mode) +- name: VSANs VLAN change (normal mode) + ucs_vsans: *vsans_change + register: nm_vsans_vlan_change + + +# Test change again (idempotent) +- name: VSANs VLAN change again (check_mode) + ucs_vsans: *vsans_change + check_mode: yes + register: cm_vsans_vlan_change_again + + +# Change again (normal mode) +- name: VSANs VLAN change again (normal mode) + ucs_vsans: *vsans_change + register: nm_vsans_vlan_change_again + + +# Verfiy change +- name: Verify VSANs change results + assert: + that: + - cm_vsans_vlan_change.changed == nm_vsans_vlan_change.changed == true + - cm_vsans_vlan_change_again.changed == nm_vsans_vlan_change_again.changed == false + + +# Teardown (clean environment) +- name: VSANs absent (check_mode) + ucs_vsans: *vsans_absent + check_mode: yes + register: cm_vsans_absent + + +# Absent (normal mode) +- name: VSANs absent (normal mode) + ucs_vsans: *vsans_absent + register: nm_vsans_absent + + +# Test absent again (idempotent) +- name: VSANs absent again (check_mode) + ucs_vsans: *vsans_absent + check_mode: yes + register: cm_vsans_absent_again + + +# Absent again (normal mode) +- name: VSANs absent again (normal mode) + ucs_vsans: *vsans_absent + register: nm_vsans_absent_again + + +# Verfiy absent +- name: Verify VSANs absent results + assert: + that: + - cm_vsans_absent.changed == nm_vsans_absent.changed == true + - cm_vsans_absent_again.changed == nm_vsans_absent_again.changed == false