diff --git a/lib/ansible/module_utils/remote_management/__init__.py b/lib/ansible/module_utils/remote_management/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/remote_management/ucs.py b/lib/ansible/module_utils/remote_management/ucs.py new file mode 100644 index 00000000000..e58fe2daf6f --- /dev/null +++ b/lib/ansible/module_utils/remote_management/ucs.py @@ -0,0 +1,90 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016 Red Hat Inc. +# (c) 2017 Cisco Systems Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + + +try: + import ucsmsdk + HAS_UCSMSDK = True +except: + HAS_UCSMSDK = False + +ucs_argument_spec = dict( + hostname=dict(type='str', required=True), + username=dict(type='str', default='admin'), + password=dict(type='str', required=True, no_log=True), + port=dict(type='int', default=None), + use_ssl=dict(type='bool', default=True), + use_proxy=dict(type='bool', default=True), + proxy=dict(type='str', default=None), +) + + +class UCSModule(): + + def __init__(self, module): + self.module = module + self.result = {} + if not HAS_UCSMSDK: + self.module.fail_json(msg='ucsmsdk is required for this module') + self.login() + + def __del__(self): + self.logout() + + def login(self): + from ucsmsdk.ucshandle import UcsHandle + + # use_proxy=yes (default) and proxy=None (default) should be using the system defined proxy + # use_proxy=yes (default) and proxy=value should use the provided proxy + # use_proxy=no (user) should not be using a proxy + if self.module.params['use_proxy']: + proxy = self.module.params['proxy'] + else: + # force no proxy to be used. Note that proxy=None in UcsHandle will + # use the system proxy so we must set to something else + proxy = {} + + try: + handle = UcsHandle(ip=self.module.params['hostname'], + username=self.module.params['username'], + password=self.module.params['password'], + port=self.module.params['port'], + secure=self.module.params['use_ssl'], + proxy=proxy) + handle.login() + except Exception as e: + self.result['msg'] = str(e) + self.module.fail_json(**self.result) + self.login_handle = handle + + def logout(self): + if self.login_handle: + self.login_handle.logout() + return True + return False diff --git a/lib/ansible/modules/remote_management/ucs/__init__.py b/lib/ansible/modules/remote_management/ucs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/modules/remote_management/ucs/ucs_macpool.py b/lib/ansible/modules/remote_management/ucs/ucs_macpool.py new file mode 100644 index 00000000000..3c01fa0914e --- /dev/null +++ b/lib/ansible/modules/remote_management/ucs/ucs_macpool.py @@ -0,0 +1,145 @@ +#!/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_macpool +short_description: Configures MAC address pools on Cisco UCS Manager +description: +- Configures MAC address pools and MAC pool blocks 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 MAC pool is present and will create if needed. + - If C(absent), will verify MAC pool is absent and will delete if needed. + choices: [present, absent] + default: present + mac_list: + description: + - List of MAC pools which contain the following properties + - name (Name of the MAC pool (required)) + - descr (Description for the MAC pool) + - order (Assignment order which is default or sequential) + - first_addr (First MAC address in the MAC addresses block) + - last_addr (Last MAC address in the MAC addresses block) + required: yes + 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 MAC address pool + ucs_macpool: + hostname: 172.16.143.150 + username: admin + password: password + mac_list: + - name: mac-A + first_addr: 00:25:B5:00:66:00 + last_addr: 00:25:B5:00:67:F3 + order: sequential +''' + +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(mac_list=dict(required=True, type='list'), + org_dn=dict(type='str', default='org-root'), + state=dict(default='present', choices=['present', 'absent'], type='str')) + module = AnsibleModule(argument_spec, + supports_check_mode=True) + ucs = UCSModule(module) + + err = False + + from ucsmsdk.mometa.macpool.MacpoolPool import MacpoolPool + from ucsmsdk.mometa.macpool.MacpoolBlock import MacpoolBlock + + changed = False + try: + for mac in module.params['mac_list']: + exists = False + dn = module.params['org_dn'] + '/mac-pool-' + mac['name'] + mo = ucs.login_handle.query_dn(dn) + if mo: + # check top-level mo props + kwargs = {} + if 'order' in mac: + kwargs['assignment_order'] = mac['order'] + if 'descr' in mac: + kwargs['descr'] = mac['descr'] + if (mo.check_prop_match(**kwargs)): + # top-level props match, check next level mo/props + if 'last_addr' in mac and 'first_addr' in mac: + block_dn = dn + '/block-' + mac['first_addr'].upper() + '-' + mac['last_addr'].upper() + mo_1 = ucs.login_handle.query_dn(block_dn) + if mo_1: + exists = True + else: + exists = True + + if module.params['state'] == 'absent': + if exists: + if not module.check_mode: + ucs.login_handle.remove_mo(mo) + ucs.login_handle.commit() + changed = True + else: + if not exists: + if not module.check_mode: + # create if mo does not already exist + if 'order' not in mac: + mac['order'] = 'default' + if 'descr' not in mac: + mac['descr'] = '' + mo = MacpoolPool(parent_mo_or_dn=module.params['org_dn'], + name=mac['name'], + descr=mac['descr'], + assignment_order=mac['order']) + + if 'last_addr' in mac and 'first_addr' in mac: + mo_1 = MacpoolBlock(parent_mo_or_dn=mo, + to=mac['last_addr'], + r_from=mac['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/lib/ansible/utils/module_docs_fragments/ucs.py b/lib/ansible/utils/module_docs_fragments/ucs.py new file mode 100644 index 00000000000..4555e457ba5 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/ucs.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016 Red Hat Inc. +# (c) 2017 Cisco Systems Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + + +class ModuleDocFragment(object): + # Cisco UCS doc fragment + DOCUMENTATION = ''' +options: + hostname: + description: + - IP address or hostname of Cisco UCS Manager. + type: str + required: yes + username: + description: + - Username for Cisco UCS Manager authentication. + type: str + default: admin + password: + description: + - Password for Cisco UCS Manager authentication. + type: str + required: yes + port: + description: + - Port number to be used during connection (by default uses 443 for https and 80 for http connection). + type: int + use_ssl: + description: + - If C(no), an HTTP connection will be used instead of the default HTTPS connection. + type: bool + default: yes + use_proxy: + description: + - If C(no), will not use the proxy as defined by system environment variable. + type: bool + default: yes + proxy: + description: + - If use_proxy is no, specfies proxy to be used for connection. + e.g. 'http://proxy.xy.z:8080' + type: str +''' diff --git a/test/integration/targets/ucs_macpool/aliases b/test/integration/targets/ucs_macpool/aliases new file mode 100644 index 00000000000..30cc9de4a27 --- /dev/null +++ b/test/integration/targets/ucs_macpool/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_macpool/tasks/main.yml b/test/integration/targets/ucs_macpool/tasks/main.yml new file mode 100644 index 00000000000..8bc6980a069 --- /dev/null +++ b/test/integration/targets/ucs_macpool/tasks/main.yml @@ -0,0 +1,142 @@ +# 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: MAC pools absent + ucs_macpool: &macpools_absent + hostname: "{{ ucs_hostname }}" + username: "{{ ucs_username }}" + password: "{{ ucs_password }}" + mac_list: + - name: mac-A + - name: mac-B + state: absent + + +# Test present (check_mode) +- name: MAC pools present (check_mode) + ucs_macpool: &macpools_present + hostname: "{{ ucs_hostname }}" + username: "{{ ucs_username }}" + password: "{{ ucs_password }}" + mac_list: + - name: mac-A + first_addr: 00:25:B5:00:66:00 + last_addr: 00:25:B5:00:67:F3 + order: sequential + - name: mac-B + first_addr: 00:25:B5:0b:66:00 + last_addr: 00:25:B5:0b:67:F3 + check_mode: yes + register: cm_macpools_present + + +# Present (normal mode) +- name: MAC pools present (normal mode) + ucs_macpool: *macpools_present + register: nm_macpools_present + + +# Test present again (idempotent) +- name: MAC pools present again (check_mode) + ucs_macpool: *macpools_present + check_mode: yes + register: cm_macpools_present_again + + +# Present again (normal mode) +- name: MAC pools present again (normal mode) + ucs_macpool: *macpools_present + register: nm_macpools_present_again + + +# Verfiy present +- name: Verify MAC present results + assert: + that: + - cm_macpools_present.changed == nm_macpools_present.changed == true + - cm_macpools_present_again.changed == nm_macpools_present_again.changed == false + + +# Test change (check_mode) +- name: MAC pools description change (check_mode) + ucs_macpool: &macpools_change + <<: *macpools_present + mac_list: + - name: mac-A + descr: Testing Ansible + first_addr: 00:25:B5:00:66:00 + last_addr: 00:25:B5:00:67:F3 + order: sequential + - name: mac-B + first_addr: 00:25:B5:0b:66:00 + last_addr: 00:25:B5:0b:67:F3 + check_mode: yes + register: cm_macpools_descr_change + + +# Change (normal mode) +- name: MAC pools description change (normal mode) + ucs_macpool: *macpools_change + register: nm_macpools_descr_change + + +# Test change again (idempotent) +- name: MAC pools description again (check_mode) + ucs_macpool: *macpools_change + check_mode: yes + register: cm_macpools_descr_change_again + + +# Change again (normal mode) +- name: MAC pools description change again (normal mode) + ucs_macpool: *macpools_change + register: nm_macpools_descr_change_again + + +# Verfiy change +- name: Verify MAC change results + assert: + that: + - cm_macpools_descr_change.changed == nm_macpools_descr_change.changed == true + - cm_macpools_descr_change_again.changed == nm_macpools_descr_change_again.changed == false + + +# Teardown (clean environment) +- name: MAC pools absent (check_mode) + ucs_macpool: *macpools_absent + check_mode: yes + register: cm_macpools_absent + + +# Absent (normal mode) +- name: MAC pools absent (normal mode) + ucs_macpool: *macpools_absent + register: nm_macpools_absent + + +# Test absent again (idempotent) +- name: MAC pools absent again (check_mode) + ucs_macpool: *macpools_absent + check_mode: yes + register: cm_macpools_absent_again + + +# Absent again (normal mode) +- name: MAC pools absent again (normal mode) + ucs_macpool: *macpools_absent + register: nm_macpools_absent_again + + +# Verfiy absent +- name: Verify MAC absent results + assert: + that: + - cm_macpools_absent.changed == nm_macpools_absent.changed == true + - cm_macpools_absent_again.changed == nm_macpools_absent_again.changed == false