diff --git a/lib/ansible/modules/remote_management/ucs/ucs_managed_objects.py b/lib/ansible/modules/remote_management/ucs/ucs_managed_objects.py new file mode 100644 index 00000000000..a05e48475f3 --- /dev/null +++ b/lib/ansible/modules/remote_management/ucs/ucs_managed_objects.py @@ -0,0 +1,260 @@ +#!/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_managed_objects +short_description: Configures Managed Objects on Cisco UCS Manager +description: +- Configures Managed Objects on Cisco UCS Manager. +- The Python SDK module, Python class within the module (UCSM Class), and all properties must be directly specified. +- More information on the UCSM Python SDK and how to directly configure Managed Objects is available at L(UCSM Python SDK,http://ucsmsdk.readthedocs.io/). +- 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 that the Managed Objects are present and will create if needed. + - If C(absent), will verify that the Managed Objects are absent and will delete if needed. + choices: [ absent, present ] + default: present + objects: + description: + - List of managed objects to configure. Each managed object has suboptions the specify the Python SDK module, class, and properties to configure. + suboptions: + module: + description: + - Name of the Python SDK module implementing the required class. + required: yes + class_name: + description: + - Name of the Python class that will be used to configure the Managed Object. + required: yes + properties: + description: + - List of properties to configure on the Managed Object. See the UCSM Python SDK for information on properties for each class. + required: yes + children: + description: + - Optional list of child objects. Each child has its own module, class, and properties suboptions. + - The parent_mo_or_dn property for child objects is automatically set as the list of children is configured. + required: yes +requirements: +- ucsmsdk +author: +- David Soper (@dsoper2) +- CiscoUcs (@CiscoUcs) +version_added: '2.8' +''' + +EXAMPLES = r''' +- name: Configure Network Control Policy + ucs_managed_objects: + hostname: 172.16.143.150 + username: admin + password: password + objects: + - module: ucsmsdk.mometa.nwctrl.NwctrlDefinition + class: NwctrlDefinition + properties: + parent_mo_or_dn: org-root + cdp: enabled + descr: '' + lldp_receive: enabled + lldp_transmit: enabled + name: Enable-CDP-LLDP + +- name: Remove Network Control Policy + ucs_managed_objects: + hostname: 172.16.143.150 + username: admin + password: password + objects: + - module: ucsmsdk.mometa.nwctrl.NwctrlDefinition + class: NwctrlDefinition + properties: + parent_mo_or_dn: org-root + name: Enable-CDP-LLDP + state: absent + +- name: Configure Boot Policy Using JSON objects list with children + ucs_managed_objects: + hostname: 172.16.143.150 + username: admin + password: password + objects: + - { + "module": "ucsmsdk.mometa.lsboot.LsbootPolicy", + "class": "LsbootPolicy", + "properties": { + "parent_mo_or_dn": "org-root", + "name": "Python_SDS", + "enforce_vnic_name": "yes", + "boot_mode": "legacy", + "reboot_on_update": "no" + }, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootVirtualMedia", + "class": "LsbootVirtualMedia", + "properties": { + "access": "read-only-local", + "lun_id": "0", + "order": "2" + } + }, + { + "module": "ucsmsdk.mometa.lsboot.LsbootStorage", + "class": "LsbootStorage", + "properties": { + "order": "1" + }, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootLocalStorage", + "class": "LsbootLocalStorage", + "properties": {}, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootDefaultLocalImage", + "class": "LsbootDefaultLocalImage", + "properties": { + "order": "1" + } + } + ] + } + ] + } + ] + } + +- name: Remove Boot Policy Using JSON objects list + ucs_managed_objects: + hostname: 172.16.143.150 + username: admin + password: password + objects: + - { + "module": "ucsmsdk.mometa.lsboot.LsbootPolicy", + "class": "LsbootPolicy", + "properties": { + "parent_mo_or_dn": "org-root", + "name": "Python_SDS" + } + } + state: absent + + +''' + +RETURN = r''' +# +''' + +try: + from importlib import import_module + HAS_IMPORT_MODULE = True +except: + HAS_IMPORT_MODULE = False + +from copy import deepcopy +import json +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.remote_management.ucs import UCSModule, ucs_argument_spec + + +def traverse_objects(module, ucs, managed_object, mo=''): + props_match = False + + mo_module = import_module(managed_object['module']) + mo_class = getattr(mo_module, managed_object['class']) + + if not managed_object['properties'].get('parent_mo_or_dn'): + managed_object['properties']['parent_mo_or_dn'] = mo + + mo = mo_class(**managed_object['properties']) + + existing_mo = ucs.login_handle.query_dn(mo.dn) + + if module.params['state'] == 'absent': + # mo must exist, but all properties do not have to match + if existing_mo: + if not module.check_mode: + ucs.login_handle.remove_mo(existing_mo) + ucs.login_handle.commit() + ucs.result['changed'] = True + else: + if existing_mo: + # check mo props + kwargs = dict(managed_object['properties']) + # remove parent info and passwords because those aren't presented in the actual props + kwargs.pop('parent_mo_or_dn', None) + kwargs.pop('pwd', None) + kwargs.pop('password', None) + if existing_mo.check_prop_match(**kwargs): + props_match = True + + if not props_match: + if not module.check_mode: + try: + ucs.login_handle.add_mo(mo, modify_present=True) + ucs.login_handle.commit() + except Exception as e: + ucs.result['err'] = True + ucs.result['msg'] = "setup error: %s " % str(e) + + ucs.result['changed'] = True + + if managed_object.get('children'): + for child in managed_object['children']: + # explicit deep copy of child object since traverse_objects may modify parent mo information + copy_of_child = deepcopy(child) + traverse_objects(module, ucs, copy_of_child, mo) + + +def main(): + object_dict = dict( + module=dict(type='str', required=True), + class_name=dict(type='str', aliases=['class'], required=True), + properties=dict(type='dict', required=True), + children=dict(type='list'), + ) + argument_spec = ucs_argument_spec + argument_spec.update( + objects=dict(type='list', elements='dict', options=object_dict, required=True), + state=dict(type='str', choices=['present', 'absent'], default='present'), + ) + + module = AnsibleModule( + argument_spec, + supports_check_mode=True, + ) + + if not HAS_IMPORT_MODULE: + module.fail_json(msg='import_module is required for this module') + ucs = UCSModule(module) + + ucs.result['err'] = False + # note that all objects specified in the object list report a single result (including a single changed). + ucs.result['changed'] = False + + for managed_object in module.params['objects']: + traverse_objects(module, ucs, managed_object) + + if ucs.result['err']: + module.fail_json(**ucs.result) + module.exit_json(**ucs.result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/ucs_managed_objects/aliases b/test/integration/targets/ucs_managed_objects/aliases new file mode 100644 index 00000000000..0689fba3e4a --- /dev/null +++ b/test/integration/targets/ucs_managed_objects/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_managed_objects/tasks/main.yml b/test/integration/targets/ucs_managed_objects/tasks/main.yml new file mode 100644 index 00000000000..a3b38b8ad41 --- /dev/null +++ b/test/integration/targets/ucs_managed_objects/tasks/main.yml @@ -0,0 +1,360 @@ +# 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: Remove Network Control Policy + ucs_managed_objects: &managed_objects_absent + <<: *login_info + objects: + - module: ucsmsdk.mometa.nwctrl.NwctrlDefinition + class: NwctrlDefinition + properties: + parent_mo_or_dn: org-root + name: Enable-CDP-LLDP + state: absent + + +# Test present (check_mode) +- name: Configure Network Control Policy + ucs_managed_objects: &managed_objects_present + <<: *login_info + objects: + - module: ucsmsdk.mometa.nwctrl.NwctrlDefinition + class: NwctrlDefinition + properties: + parent_mo_or_dn: org-root + cdp: enabled + descr: '' + lldp_receive: enabled + lldp_transmit: enabled + name: Enable-CDP-LLDP + check_mode: yes + register: cm_managed_objects_present + + +# Present (normal mode) +- name: Managed Objects present (normal mode) + ucs_managed_objects: *managed_objects_present + register: nm_managed_objects_present + + +# Test present again (idempotent) +- name: Managed Objects present again (check_mode) + ucs_managed_objects: *managed_objects_present + check_mode: yes + register: cm_managed_objects_present_again + + +# Present again (normal mode) +- name: Managed Objects present again (normal mode) + ucs_managed_objects: *managed_objects_present + register: nm_managed_objects_present_again + + +# Verfiy present +- name: Verify Managed Objects present results + assert: + that: + - cm_managed_objects_present.changed == nm_managed_objects_present.changed == true + - cm_managed_objects_present_again.changed == nm_managed_objects_present_again.changed == false + + +# Test change (check_mode) +- name: Managed Objects change (check_mode) + ucs_managed_objects: &managed_objects_change + <<: *login_info + objects: + - module: ucsmsdk.mometa.nwctrl.NwctrlDefinition + class: NwctrlDefinition + properties: + parent_mo_or_dn: org-root + cdp: enabled + descr: Testing Ansible + lldp_receive: enabled + lldp_transmit: enabled + name: Enable-CDP-LLDP + check_mode: yes + register: cm_managed_objects_change + + +# Change (normal mode) +- name: Managed Objects change (normal mode) + ucs_managed_objects: *managed_objects_change + register: nm_managed_objects_change + + +# Test change again (idempotent) +- name: Managed Objects again (check_mode) + ucs_managed_objects: *managed_objects_change + check_mode: yes + register: cm_managed_objects_change_again + + +# Change again (normal mode) +- name: Managed Objects change again (normal mode) + ucs_managed_objects: *managed_objects_change + register: nm_managed_objects_change_again + + +# Verfiy change +- name: Verify Managed Objects change results + assert: + that: + - cm_managed_objects_change.changed == nm_managed_objects_change.changed == true + - cm_managed_objects_change_again.changed == nm_managed_objects_change_again.changed == false + + +# Teardown (clean environment) +- name: Managed Objects absent (check_mode) + ucs_managed_objects: *managed_objects_absent + check_mode: yes + register: cm_managed_objects_absent + + +# Absent (normal mode) +- name: Managed Objects absent (normal mode) + ucs_managed_objects: *managed_objects_absent + register: nm_managed_objects_absent + + +# Test absent again (idempotent) +- name: Managed Objects absent again (check_mode) + ucs_managed_objects: *managed_objects_absent + check_mode: yes + register: cm_managed_objects_absent_again + + +# Absent again (normal mode) +- name: Managed Objects absent again (normal mode) + ucs_managed_objects: *managed_objects_absent + register: nm_managed_objects_absent_again + + +# Verfiy absent +- name: Verify Managed Objects absent results + assert: + that: + - cm_managed_objects_absent.changed == nm_managed_objects_absent.changed == true + - cm_managed_objects_absent_again.changed == nm_managed_objects_absent_again.changed == false + + +# Setup Boot Policy (clean environment) +- name: Remove Boot Policy + ucs_managed_objects: &boot_managed_objects_absent + <<: *login_info + objects: + - { + "module": "ucsmsdk.mometa.lsboot.LsbootPolicy", + "class": "LsbootPolicy", + "properties": { + "parent_mo_or_dn": "org-root", + "name": "Python_SDS" + } + } + state: absent + + +# Test present (check_mode) +- name: Configure Boot Policy + ucs_managed_objects: &boot_managed_objects_present + <<: *login_info + objects: + - { + "module": "ucsmsdk.mometa.lsboot.LsbootPolicy", + "class": "LsbootPolicy", + "properties": { + "parent_mo_or_dn": "org-root", + "name": "Python_SDS", + "enforce_vnic_name": "yes", + "boot_mode": "legacy", + "reboot_on_update": "no" + }, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootVirtualMedia", + "class": "LsbootVirtualMedia", + "properties": { + "access": "read-only-local", + "lun_id": "0", + "order": "2" + } + }, + { + "module": "ucsmsdk.mometa.lsboot.LsbootStorage", + "class": "LsbootStorage", + "properties": { + "order": "1" + }, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootLocalStorage", + "class": "LsbootLocalStorage", + "properties": {}, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootDefaultLocalImage", + "class": "LsbootDefaultLocalImage", + "properties": { + "order": "1" + } + } + ] + } + ] + } + ] + } + check_mode: yes + register: cm_boot_managed_objects_present + + +# Present (normal mode) +- name: Boot Managed Objects present (normal mode) + ucs_managed_objects: *boot_managed_objects_present + register: nm_boot_managed_objects_present + + +# Test present again (idempotent) +- name: Boot Managed Objects present again (check_mode) + ucs_managed_objects: *boot_managed_objects_present + check_mode: yes + register: cm_boot_managed_objects_present_again + + +# Present again (normal mode) +- name: Boot Managed Objects present again (normal mode) + ucs_managed_objects: *boot_managed_objects_present + register: nm_boot_managed_objects_present_again + + +# Verfiy present +- name: Verify Boot Managed Objects present results + assert: + that: + - cm_boot_managed_objects_present.changed == nm_boot_managed_objects_present.changed == true + - cm_boot_managed_objects_present_again.changed == nm_boot_managed_objects_present_again.changed == false + + +# Test change (check_mode) +- name: Boot Managed Objects change (check_mode) + ucs_managed_objects: &boot_managed_objects_change + <<: *login_info + objects: + - { + "module": "ucsmsdk.mometa.lsboot.LsbootPolicy", + "class": "LsbootPolicy", + "properties": { + "parent_mo_or_dn": "org-root", + "name": "Python_SDS", + "enforce_vnic_name": "yes", + "boot_mode": "legacy", + "reboot_on_update": "yes" + }, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootVirtualMedia", + "class": "LsbootVirtualMedia", + "properties": { + "access": "read-only-local", + "lun_id": "0", + "order": "2" + } + }, + { + "module": "ucsmsdk.mometa.lsboot.LsbootStorage", + "class": "LsbootStorage", + "properties": { + "order": "1" + }, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootLocalStorage", + "class": "LsbootLocalStorage", + "properties": {}, + "children": [ + { + "module": "ucsmsdk.mometa.lsboot.LsbootDefaultLocalImage", + "class": "LsbootDefaultLocalImage", + "properties": { + "order": "1" + } + } + ] + } + ] + } + ] + } + check_mode: yes + register: cm_boot_managed_objects_change + + +# Change (normal mode) +- name: Boot Managed Objects change (normal mode) + ucs_managed_objects: *boot_managed_objects_change + register: nm_boot_managed_objects_change + + +# Test change again (idempotent) +- name: Boot Managed Objects again (check_mode) + ucs_managed_objects: *boot_managed_objects_change + check_mode: yes + register: cm_boot_managed_objects_change_again + + +# Change again (normal mode) +- name: Boot Managed Objects change again (normal mode) + ucs_managed_objects: *boot_managed_objects_change + register: nm_boot_managed_objects_change_again + + +# Verfiy change +- name: Verify Boot Managed Objects change results + assert: + that: + - cm_boot_managed_objects_change.changed == nm_boot_managed_objects_change.changed == true + - cm_boot_managed_objects_change_again.changed == nm_boot_managed_objects_change_again.changed == false + + +# Teardown (clean environment) +- name: Boot Managed Objects absent (check_mode) + ucs_managed_objects: *boot_managed_objects_absent + check_mode: yes + register: cm_boot_managed_objects_absent + + +# Absent (normal mode) +- name: Boot Managed Objects absent (normal mode) + ucs_managed_objects: *boot_managed_objects_absent + register: nm_boot_managed_objects_absent + + +# Test absent again (idempotent) +- name: Boot Managed Objects absent again (check_mode) + ucs_managed_objects: *boot_managed_objects_absent + check_mode: yes + register: cm_boot_managed_objects_absent_again + + +# Absent again (normal mode) +- name: Boot Managed Objects absent again (normal mode) + ucs_managed_objects: *boot_managed_objects_absent + register: nm_boot_managed_objects_absent_again + + +# Verfiy absent +- name: Verify Boot Managed Objects absent results + assert: + that: + - cm_boot_managed_objects_absent.changed == nm_boot_managed_objects_absent.changed == true + - cm_boot_managed_objects_absent_again.changed == nm_boot_managed_objects_absent_again.changed == false