From 9f1b75db0e384b57411af5dfa878018fa9a04fc6 Mon Sep 17 00:00:00 2001 From: David Soper Date: Tue, 20 Nov 2018 06:34:49 -0600 Subject: [PATCH] Ucs disk group policy (#43578) * ucs_disk_group_policy module and integration tests * Additional refactor based on review in other modules. * Fix issue with automatic config and add virtual_drive config. Integration tests added for automatic config and virtual_drive config. * Code review updates (documentation items) * update version added to 2.8 --- .../ucs/ucs_disk_group_policy.py | 423 ++++++++++++++++++ .../targets/ucs_disk_group_policy/aliases | 7 + .../ucs_disk_group_policy/tasks/main.yml | 193 ++++++++ 3 files changed, 623 insertions(+) create mode 100644 lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py create mode 100644 test/integration/targets/ucs_disk_group_policy/aliases create mode 100644 test/integration/targets/ucs_disk_group_policy/tasks/main.yml diff --git a/lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py b/lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py new file mode 100644 index 0000000000..093eeb94d7 --- /dev/null +++ b/lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py @@ -0,0 +1,423 @@ +#!/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_disk_group_policy +short_description: Configures disk group policies on Cisco UCS Manager +description: +- Configures disk group policies on Cisco UCS Manager. +- Examples can be used with the L(UCS Platform Emulator,https://communities.cisco.com/ucspe). +extends_documentation_fragment: ucs +options: + state: + description: + - Desired state of the disk group policy. + - If C(present), will verify that the disk group policy is present and will create if needed. + - If C(absent), will verify that the disk group policy is absent and will delete if needed. + choices: [present, absent] + default: present + name: + description: + - The name of the disk group policy. + This name can be between 1 and 16 alphanumeric characters. + - "You cannot use spaces or any special characters other than - (hyphen), \"_\" (underscore), : (colon), and . (period)." + - You cannot change this name after the policy is created. + required: yes + description: + description: + - The user-defined description of the storage profile. + Enter up to 256 characters. + "You can use any characters or spaces except the following:" + "` (accent mark), \ (backslash), ^ (carat), \" (double quote), = (equal sign), > (greater than), < (less than), or ' (single quote)." + aliases: [ descr ] + raid_level: + description: + - "The RAID level for the disk group policy. This can be one of the following:" + - "stripe - UCS Manager shows RAID 0 Striped" + - "mirror - RAID 1 Mirrored" + - "mirror-stripe - RAID 10 Mirrored and Striped" + - "stripe-parity - RAID 5 Striped Parity" + - "stripe-dual-parity - RAID 6 Striped Dual Parity" + - "stripe-parity-stripe - RAID 50 Striped Parity and Striped" + - "stripe-dual-parity-stripe - RAID 60 Striped Dual Parity and Striped" + choices: [stripe, mirror, mirror-stripe, stripe-parity, stripe-dual-parity, stripe-parity-stripe, stripe-dual-parity-stripe] + default: stripe + configuration_mode: + description: + - "Disk group configuration mode. Choose one of the following:" + - "automatic - Automatically configures the disks in the disk group." + - "manual - Enables you to manually configure the disks in the disk group." + choices: [automatic, manual] + default: automatic + num_drives: + description: + - Specify the number of drives for the disk group. + - This can be from 0 to 24. + - Option only applies when configuration mode is automatic. + default: 1 + drive_type: + description: + - Specify the drive type to use in the drive group. + - "This can be one of the following:" + - "unspecified — Selects the first available drive type, and applies that to all drives in the group." + - "HDD — Hard disk drive" + - "SSD — Solid state drive" + - Option only applies when configuration mode is automatic. + choices: [unspecified, HDD, SSD] + default: unspecified + num_ded_hot_spares: + description: + - Specify the number of hot spares for the disk group. + - This can be from 0 to 24. + - Option only applies when configuration mode is automatic. + default: unspecified + num_glob_hot_spares: + description: + - Specify the number of global hot spares for the disk group. + - This can be from 0 to 24. + - Option only applies when configuration mode is automatic. + default: unspecified + min_drive_size: + description: + - Specify the minimum drive size or unspecified to allow all drive sizes. + - This can be from 0 to 10240 GB. + - Option only applies when configuration mode is automatic. + default: 'unspecified' + use_remaining_disks: + description: + - Specifies whether you can use all the remaining disks in the disk group or not. + - Option only applies when configuration mode is automatic. + choices: ['yes', 'no'] + default: 'no' + manual_disks: + description: + - List of manually configured disks. + - Options are only used when you choose manual configuration_mode. + suboptions: + name: + description: + - The name of the local LUN. + required: yes + slot_num: + description: + - The slot number of the specific disk. + role: + description: + - "The role of the disk. This can be one of the following:" + - "normal - Normal" + - "ded-hot-spare - Dedicated Hot Spare" + - "glob-hot-spare - Glob Hot Spare" + span_id: + description: + - The Span ID of the specific disk. + default: 'unspecified' + state: + description: + - If C(present), will verify disk slot is configured within policy. + If C(absent), will verify disk slot is absent from policy. + choices: [ present, absent ] + default: present + virtual_drive: + description: + - Configuraiton of virtual drive options. + suboptions: + access_policy: + description: + - Configure access policy to virtual drive. + choices: [blocked, hidden, platform-default, read-only, read-write, transport-ready] + default: platform-default + drive_cache: + description: + - Configure drive caching. + choices: [disable, enable, no-change, platform-default] + default: platform-default + io_policy: + description: + - Direct or Cached IO path. + choices: [cached, direct, platform-default] + default: platform-default + read_policy: + description: + - Read access policy to virtual drive. + choices: [normal, platform-default, read-ahead] + default: platform-default + strip_size: + description: + - Virtual drive strip size. + choices: [ present, absent ] + default: platform-default + write_cache_policy: + description: + - Write back cache policy. + choices: [always-write-back, platform-default, write-back-good-bbu, write-through] + default: platform-default + org_dn: + description: + - The distinguished name (dn) of the organization where the resource is assigned. + default: org-root +requirements: +- ucsmsdk +author: +- Sindhu Sudhir (@sisudhir) +- David Soper (@dsoper2) +- CiscoUcs (@CiscoUcs) +- Brett Johnson (@sdbrett) +version_added: '2.8' +''' + +EXAMPLES = r''' +- name: Configure Disk Group Policy + ucs_disk_group_policy: + hostname: 172.16.143.150 + username: admin + password: password + name: DEE-DG + raid_level: mirror + configuration_mode: manual + manual_disks: + - slot_num: '1' + role: normal + - slot_num: '2' + role: normal + +- name: Remove Disk Group Policy + ucs_disk_group_policy: + name: DEE-DG + hostname: 172.16.143.150 + username: admin + password: password + state: absent + +- name: Remove Disk from Policy + ucs_disk_group_policy: + hostname: 172.16.143.150 + username: admin + password: password + name: DEE-DG + description: Testing Ansible + raid_level: stripe + configuration_mode: manual + manual_disks: + - slot_num: '1' + role: normal + - slot_num: '2' + role: normal + state: absent + virtual_drive: + access_policy: platform-default + io_policy: direct + strip_size: 64KB +''' + +RETURN = r''' +# +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.remote_management.ucs import UCSModule, ucs_argument_spec + + +def configure_disk_policy(ucs, module, dn): + from ucsmsdk.mometa.lstorage.LstorageDiskGroupConfigPolicy import LstorageDiskGroupConfigPolicy + from ucsmsdk.mometa.lstorage.LstorageDiskGroupQualifier import LstorageDiskGroupQualifier + from ucsmsdk.mometa.lstorage.LstorageLocalDiskConfigRef import LstorageLocalDiskConfigRef + + if not module.check_mode: + try: + # create if mo does not already exist + mo = LstorageDiskGroupConfigPolicy( + parent_mo_or_dn=module.params['org_dn'], + name=module.params['name'], + descr=module.params['description'], + raid_level=module.params['raid_level'], + ) + if module.params['configuration_mode'] == 'automatic': + LstorageDiskGroupQualifier( + parent_mo_or_dn=mo, + num_drives=module.params['num_drives'], + drive_type=module.params['drive_type'], + use_remaining_disks=module.params['use_remaining_disks'], + num_ded_hot_spares=module.params['num_ded_hot_spares'], + num_glob_hot_spares=module.params['num_glob_hot_spares'], + min_drive_size=module.params['min_drive_size'], + ) + else: # configuration_mode == 'manual' + for disk in module.params['manual_disks']: + if disk['state'] == 'absent': + child_dn = dn + '/slot-' + disk['slot_num'] + mo_1 = ucs.login_handle.query_dn(child_dn) + if mo_1: + ucs.login_handle.remove_mo(mo_1) + else: # state == 'present' + LstorageLocalDiskConfigRef( + parent_mo_or_dn=mo, + slot_num=disk['slot_num'], + role=disk['role'], + span_id=disk['span_id'], + ) + + if module.params['virtual_drive']: + _configure_virtual_drive(module, mo) + + ucs.login_handle.add_mo(mo, True) + ucs.login_handle.commit() + except Exception as e: # generic Exception handling because SDK can throw a variety + ucs.result['msg'] = "setup error: %s " % str(e) + module.fail_json(**ucs.result) + + ucs.result['changed'] = True + + +def check_disk_policy_props(ucs, module, mo, dn): + props_match = True + + # check top-level mo props + kwargs = dict(descr=module.params['description']) + kwargs['raid_level'] = module.params['raid_level'] + if mo.check_prop_match(**kwargs): + # top-level props match, check next level mo/props + if module.params['configuration_mode'] == 'automatic': + child_dn = dn + '/disk-group-qual' + mo_1 = ucs.login_handle.query_dn(child_dn) + if mo_1: + kwargs = dict(num_drives=module.params['num_drives']) + kwargs['drive_type'] = module.params['drive_type'] + kwargs['use_remaining_disks'] = module.params['use_remaining_disks'] + kwargs['num_ded_hot_spares'] = module.params['num_ded_hot_spares'] + kwargs['num_glob_hot_spares'] = module.params['num_glob_hot_spares'] + kwargs['min_drive_size'] = module.params['min_drive_size'] + props_match = mo_1.check_prop_match(**kwargs) + + else: # configuration_mode == 'manual' + for disk in module.params['manual_disks']: + child_dn = dn + '/slot-' + disk['slot_num'] + mo_1 = ucs.login_handle.query_dn(child_dn) + if mo_1: + if disk['state'] == 'absent': + props_match = False + else: # state == 'present' + kwargs = dict(slot_num=disk['slot_num']) + kwargs['role'] = disk['role'] + kwargs['span_id'] = disk['span_id'] + if not mo_1.check_prop_match(**kwargs): + props_match = False + break + if props_match: + if module.params['virtual_drive']: + props_match = check_virtual_drive_props(ucs, module, dn) + else: + props_match = False + return props_match + + +def check_virtual_drive_props(ucs, module, dn): + child_dn = dn + '/virtual-drive-def' + mo_1 = ucs.login_handle.query_dn(child_dn) + return mo_1.check_prop_match(**module.params['virtual_drive']) + + +def _configure_virtual_drive(module, mo): + from ucsmsdk.mometa.lstorage.LstorageVirtualDriveDef import LstorageVirtualDriveDef + LstorageVirtualDriveDef(parent_mo_or_dn=mo, **module.params['virtual_drive']) + + +def _virtual_drive_argument_spec(): + return dict( + access_policy=dict(type=str, default='platform-default', + choices=["blocked", "hidden", "platform-default", "read-only", "read-write", + "transport-ready"]), + drive_cache=dict(type=str, default='platform-default', + choices=["disable", "enable", "no-change", "platform-default"]), + io_policy=dict(type=str, default='platform-default', + choices=["cached", "direct", "platform-default"]), + read_policy=dict(type=str, default='platform-default', + choices=["normal", "platform-default", "read-ahead"]), + strip_size=dict(type=str, default='platform-default', + choices=["1024KB", "128KB", "16KB", "256KB", "32KB", "512KB", "64KB", "8KB", + "platform-default"]), + write_cache_policy=dict(type=str, default='platform-default', + choices=["always-write-back", "platform-default", "write-back-good-bbu", + "write-through"]), + ) + + +def main(): + manual_disk = dict( + slot_num=dict(type='str', required=True), + role=dict(type='str', default='normal', choices=['normal', 'ded-hot-spare', 'glob-hot-spare']), + span_id=dict(type='str', default='unspecified'), + state=dict(type='str', default='present', choices=['present', 'absent']), + ) + + argument_spec = ucs_argument_spec + argument_spec.update( + org_dn=dict(type='str', default='org-root'), + name=dict(type='str', required=True), + description=dict(type='str', aliases=['descr'], default=''), + raid_level=dict( + type='str', + default='stripe', + choices=[ + 'stripe', + 'mirror', + 'mirror-stripe', + 'stripe-parity', + 'stripe-dual-parity', + 'stripe-parity-stripe', + 'stripe-dual-parity-stripe', + ], + ), + num_drives=dict(type='str', default='1'), + configuration_mode=dict(type='str', default='automatic', choices=['automatic', 'manual']), + num_ded_hot_spares=dict(type='str', default='unspecified'), + num_glob_hot_spares=dict(type='str', default='unspecified'), + drive_type=dict(type='str', default='unspecified', choices=['unspecified', 'HDD', 'SSD']), + use_remaining_disks=dict(type='str', default='no', choices=['yes', 'no']), + min_drive_size=dict(type='str', default='unspecified'), + manual_disks=dict(type='list', elements='dict', options=manual_disk), + state=dict(type='str', default='present', choices=['present', 'absent']), + virtual_drive=dict(type='dict', elements='dict', options=_virtual_drive_argument_spec()), + ) + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + ) + ucs = UCSModule(module) + # UCSModule creation above verifies ucsmsdk is present and exits on failure. + # Additional imports are done below or in called functions. + + ucs.result['changed'] = False + props_match = False + # dn is /disk-group-config- + dn = module.params['org_dn'] + '/disk-group-config-' + module.params['name'] + + mo = ucs.login_handle.query_dn(dn) + if mo: + if module.params['state'] == 'absent': + # mo must exist but all properties do not have to match + if not module.check_mode: + ucs.login_handle.remove_mo(mo) + ucs.login_handle.commit() + ucs.result['changed'] = True + else: # state == 'present' + props_match = check_disk_policy_props(ucs, module, mo, dn) + + if module.params['state'] == 'present' and not props_match: + configure_disk_policy(ucs, module, dn) + + module.exit_json(**ucs.result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/ucs_disk_group_policy/aliases b/test/integration/targets/ucs_disk_group_policy/aliases new file mode 100644 index 0000000000..0689fba3e4 --- /dev/null +++ b/test/integration/targets/ucs_disk_group_policy/aliases @@ -0,0 +1,7 @@ +# 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 +unsupported diff --git a/test/integration/targets/ucs_disk_group_policy/tasks/main.yml b/test/integration/targets/ucs_disk_group_policy/tasks/main.yml new file mode 100644 index 0000000000..717f609674 --- /dev/null +++ b/test/integration/targets/ucs_disk_group_policy/tasks/main.yml @@ -0,0 +1,193 @@ +# Test code for the UCS modules +# Copyright 2018, 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 + vars: + login_info: &login_info + hostname: "{{ ucs_hostname }}" + username: "{{ ucs_username }}" + password: "{{ ucs_password }}" + +# Setup (clean environment) +- name: Disk Group Policy absent + ucs_disk_group_policy: &disk_group_policy_absent + <<: *login_info + name: DEE-DG + state: absent + + +# Test present (check_mode) +- name: Disk Group Policy present (check_mode) + ucs_disk_group_policy: &disk_group_policy_present + <<: *login_info + name: DEE-DG + raid_level: mirror + configuration_mode: manual + manual_disks: + - slot_num: '1' + role: normal + - slot_num: '2' + role: normal + check_mode: yes + register: cm_disk_group_policy_present + + +# Present (normal mode) +- name: Disk Group Policy present (normal mode) + ucs_disk_group_policy: *disk_group_policy_present + register: nm_disk_group_policy_present + + +# Test present again (idempotent) +- name: Disk Group Policy present again (check_mode) + ucs_disk_group_policy: *disk_group_policy_present + check_mode: yes + register: cm_disk_group_policy_present_again + + +# Present again (normal mode) +- name: Disk Group Policy present again (normal mode) + ucs_disk_group_policy: *disk_group_policy_present + register: nm_disk_group_policy_present_again + + +# Verfiy present +- name: Verify Disk Group Policy present results + assert: + that: + - cm_disk_group_policy_present.changed == nm_disk_group_policy_present.changed == true + - cm_disk_group_policy_present_again.changed == nm_disk_group_policy_present_again.changed == false + + +# Test change (check_mode) +- name: Disk Group Policy change (check_mode) + ucs_disk_group_policy: &disk_group_policy_change + <<: *login_info + name: DEE-DG + description: Testing Ansible + raid_level: stripe + configuration_mode: manual + manual_disks: + - slot_num: '1' + role: normal + - slot_num: '2' + role: normal + state: absent + check_mode: yes + register: cm_disk_group_policy_change + + +# Change (normal mode) +- name: Disk Group Policy change (normal mode) + ucs_disk_group_policy: *disk_group_policy_change + register: nm_disk_group_policy_change + + +# Test change again (idempotent) +- name: Disk Group Policy again (check_mode) + ucs_disk_group_policy: *disk_group_policy_change + check_mode: yes + register: cm_disk_group_policy_change_again + + +# Change again (normal mode) +- name: Disk Group Policy change again (normal mode) + ucs_disk_group_policy: *disk_group_policy_change + register: nm_disk_group_policy_change_again + + +# Verfiy change +- name: Verify Disk Group Policy change results + assert: + that: + - cm_disk_group_policy_change.changed == nm_disk_group_policy_change.changed == true + - cm_disk_group_policy_change_again.changed == nm_disk_group_policy_change_again.changed == false + + +# Clean environment for next tests +- name: Disk Group Policy absent for automatic testing + ucs_disk_group_policy: &disk_group_policy_absent_auto + <<: *login_info + name: DEE-DG + state: absent + + +# Test automatic configuration mode (check_mode) +- name: Disk Group Policy automatic (check_mode) + ucs_disk_group_policy: &disk_group_policy_auto + <<: *login_info + name: DEE-DG + raid_level: mirror + configuration_mode: automatic + drive_type: SSD + num_drives: 2 + virtual_drive: + access_policy: platform-default + io_policy: direct + strip_size: 64KB + check_mode: yes + register: cm_disk_group_policy_auto + + +# Automatic configuration (normal mode) +- name: Disk Group Policy automatic (normal mode) + ucs_disk_group_policy: *disk_group_policy_auto + register: nm_disk_group_policy_auto + + +# Test automatic configuration again (idempotent) +- name: Disk Group Policy automatic again (check_mode) + ucs_disk_group_policy: *disk_group_policy_auto + check_mode: yes + register: cm_disk_group_policy_auto_again + + +# Automatic configuration again (normal mode) +- name: Disk Group Policy automatic again (normal mode) + ucs_disk_group_policy: *disk_group_policy_auto + register: nm_disk_group_policy_auto_again + + +# Verfiy Automatic configuration +- name: Verify Disk Group Policy automatic results + assert: + that: + - cm_disk_group_policy_auto.changed == nm_disk_group_policy_auto.changed == true + - cm_disk_group_policy_auto_again.changed == nm_disk_group_policy_auto_again.changed == false + + +# Teardown (clean environment) +- name: Disk Group Policy absent (check_mode) + ucs_disk_group_policy: *disk_group_policy_absent + check_mode: yes + register: cm_disk_group_policy_absent + + +# Absent (normal mode) +- name: Disk Group Policy absent (normal mode) + ucs_disk_group_policy: *disk_group_policy_absent + register: nm_disk_group_policy_absent + + +# Test absent again (idempotent) +- name: Disk Group Policy absent again (check_mode) + ucs_disk_group_policy: *disk_group_policy_absent + check_mode: yes + register: cm_disk_group_policy_absent_again + + +# Absent again (normal mode) +- name: Disk Group Policy absent again (normal mode) + ucs_disk_group_policy: *disk_group_policy_absent + register: nm_disk_group_policy_absent_again + + +# Verfiy absent +- name: Verify Disk Group Policy absent results + assert: + that: + - cm_disk_group_policy_absent.changed == nm_disk_group_policy_absent.changed == true + - cm_disk_group_policy_absent_again.changed == nm_disk_group_policy_absent_again.changed == false