From 8b5f34e11034dbe3ffeeeb081735eae9e76c8194 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Fri, 18 May 2018 17:17:01 -0700 Subject: [PATCH] Add Na_ontap_broadcast_domain module. (#39753) * add first module * fix case * Create na_ontap * Fix small pep8 errors * Fixes for issues found by Amit * fixes for amit * fix doc * get doc to compile --- lib/ansible/module_utils/netapp.py | 73 ++++- .../netapp/na_ontap_broadcast_domain.py | 253 ++++++++++++++++++ .../utils/module_docs_fragments/netapp.py | 42 ++- 3 files changed, 363 insertions(+), 5 deletions(-) create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain.py diff --git a/lib/ansible/module_utils/netapp.py b/lib/ansible/module_utils/netapp.py index e9236c9742c..ac0c85e8e1f 100644 --- a/lib/ansible/module_utils/netapp.py +++ b/lib/ansible/module_utils/netapp.py @@ -78,12 +78,22 @@ def has_sf_sdk(): return HAS_SF_SDK -def ontap_sf_host_argument_spec(): +def na_ontap_host_argument_spec(): return dict( hostname=dict(required=True, type='str'), username=dict(required=True, type='str', aliases=['user']), password=dict(required=True, type='str', aliases=['pass'], no_log=True), + https=dict(required=False, type='bool', default=False) + ) + + +def ontap_sf_host_argument_spec(): + + return dict( + hostname=dict(required=True, type='str'), + username=dict(required=True, type='str', aliases=['user']), + password=dict(required=True, type='str', aliases=['pass'], no_log=True) ) @@ -102,6 +112,34 @@ def create_sf_connection(module, port=None): module.fail_json(msg="the python SolidFire SDK module is required") +def setup_na_ontap_zapi(module, vserver=None): + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + https = module.params['https'] + + if HAS_NETAPP_LIB: + # set up zapi + server = zapi.NaServer(hostname) + server.set_username(username) + server.set_password(password) + if vserver: + server.set_vserver(vserver) + # Todo : Replace hard-coded values with configurable parameters. + server.set_api_version(major=1, minor=21) + # default is HTTP + if https is True: + server.set_port(443) + server.set_transport_type('HTTPS') + else: + server.set_port(80) + server.set_transport_type('HTTP') + server.set_server_type('FILER') + return server + else: + module.fail_json(msg="the python NetApp-Lib module is required") + + def setup_ontap_zapi(module, vserver=None): hostname = module.params['hostname'] username = module.params['username'] @@ -174,3 +212,36 @@ def request(url, data=None, headers=None, method='GET', use_proxy=True, raise Exception(resp_code, data) else: return resp_code, data + + +def ems_log_event(source, server, name="Ansible", id="12345", version="1.1", + category="Information", event="setup", autosupport="false"): + ems_log = zapi.NaElement('ems-autosupport-log') + # Host name invoking the API. + ems_log.add_new_child("computer-name", name) + # ID of event. A user defined event-id, range [0..2^32-2]. + ems_log.add_new_child("event-id", id) + # Name of the application invoking the API. + ems_log.add_new_child("event-source", source) + # Version of application invoking the API. + ems_log.add_new_child("app-version", version) + # Application defined category of the event. + ems_log.add_new_child("category", category) + # Description of event to log. An application defined message to log. + ems_log.add_new_child("event-description", event) + ems_log.add_new_child("log-level", "6") + ems_log.add_new_child("auto-support", autosupport) + server.invoke_successfully(ems_log, True) + + +def get_cserver(server): + vserver_info = zapi.NaElement('vserver-get-iter') + query_details = zapi.NaElement.create_node_with_children('vserver-info', **{'vserver-type': 'admin'}) + query = zapi.NaElement('query') + query.add_child_elem(query_details) + vserver_info.add_child_elem(query) + result = server.invoke_successfully(vserver_info, + enable_tunneling=False) + attribute_list = result.get_child_by_name('attributes-list') + vserver_list = attribute_list.get_child_by_name('vserver-info') + return vserver_list.get_child_content('vserver-name') diff --git a/lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain.py b/lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain.py new file mode 100644 index 00000000000..9181082549a --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain.py @@ -0,0 +1,253 @@ +#!/usr/bin/python + +# (c) 2018, NetApp, Inc +# 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 = ''' +module: na_ontap_broadcast_domain +short_description: Manage NetApp ONTAP broadcast domains. +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Chris Archibald (carchi8py@gmail.com), Kevin Hutton (khutton@netapp.com), Suhas Bangalore Shekar (bsuhas@netapp.com) +description: +- Modify a ONTAP broadcast domain. +options: + state: + description: + - Whether the specified broadcast domain should exist or not. + choices: ['present', 'absent'] + default: present + broadcast_domain: + description: + - Specify the broadcast_domain name + required: true + mtu: + description: + - Specify the required mtu for the broadcast domain + ipspace: + description: + - Specify the required ipspace for the broadcast domain + ports: + description: + - Specify the ports associated with this broadcast domain. Should be comma separated + +''' + +EXAMPLES = """ + - name: create broadcast domain + na_ontap_broadcast_domain: + state=present + username={{ netapp_username }} + password={{ netapp_password }} + hostname={{ netapp_hostname }} + broadcast_domain=123kevin + mtu=1000 + ipspace=Default + ports=khutton-vsim1:e0d-12,khutton-vsim1:e0d-13 + - name: delete broadcast domain + na_ontap_broadcast_domain: + state=absent + username={{ netapp_username }} + password={{ netapp_password }} + hostname={{ netapp_hostname }} + broadcast_domain=123kevin + mtu=1000 + ipspace=Default + - name: modify broadcast domain + na_ontap_broadcast_domain: + state=absent + username={{ netapp_username }} + password={{ netapp_password }} + hostname={{ netapp_hostname }} + broadcast_domain=123kevin + mtu=1100 + ipspace=Default +""" + +RETURN = """ + + +""" +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +import ansible.module_utils.netapp as netapp_utils + +HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() + + +class NetAppOntapBroadcastDomain(object): + """ + Create, Modifies and Destroys a Broadcast domain + """ + def __init__(self): + """ + Initialize the ONTAP Broadcast Domain class + """ + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=['present', 'absent'], default='present'), + broadcast_domain=dict(required=True, type='str'), + ipspace=dict(required=False, type='str'), + mtu=dict(required=False, type='str'), + ports=dict(required=False, type='list'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + parameters = self.module.params + + # set up state variables + self.state = parameters['state'] + self.broadcast_domain = parameters['broadcast_domain'] + self.ipspace = parameters['ipspace'] + self.mtu = parameters['mtu'] + self.ports = parameters['ports'] + + if HAS_NETAPP_LIB is False: + self.module.fail_json(msg="the python NetApp-Lib module is required") + else: + self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) + return + + def get_broadcast_domain(self): + """ + Return details about the broadcast domain + :param: + name : broadcast domain name + :return: Details about the broadcas domain. None if not found. + :rtype: dict + """ + domain_get_iter = netapp_utils.zapi.NaElement('net-port-broadcast-domain-get-iter') + broadcast_domain_info = netapp_utils.zapi.NaElement('net-port-broadcast-domain-info') + broadcast_domain_info.add_new_child('broadcast-domain', self.broadcast_domain) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(broadcast_domain_info) + domain_get_iter.add_child_elem(query) + result = self.server.invoke_successfully(domain_get_iter, True) + domain_exists = None + # check if job exists + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) == 1: + domain_info = result.get_child_by_name('attributes-list').\ + get_child_by_name('net-port-broadcast-domain-info') + domain_name = domain_info.get_child_content('broadcast-domain') + domain_mtu = domain_info.get_child_content('mtu') + domain_ipspace = domain_info.get_child_content('ipspace') + domain_exists = { + 'domain-name': domain_name, + 'mtu': domain_mtu, + 'ipspace': domain_ipspace + } + return domain_exists + + def create_broadcast_domain(self): + """ + Creates a new broadcast domain + """ + domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-create') + domain_obj.add_new_child("broadcast-domain", self.broadcast_domain) + if self.ipspace: + domain_obj.add_new_child("ipspace", self.ipspace) + domain_obj.add_new_child("mtu", self.mtu) + if self.ports: + ports_obj = netapp_utils.zapi.NaElement('ports') + domain_obj.add_child_elem(ports_obj) + for port in self.ports: + ports_obj.add_new_child('net-qualified-port-name', port) + try: + self.server.invoke_successfully(domain_obj, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error creating broadcast domain %s: %s' % + (self.broadcast_domain, to_native(error)), + exception=traceback.format_exc()) + + def delete_broadcast_domain(self): + """ + Deletes a broadcast domain + """ + domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-destroy') + domain_obj.add_new_child("broadcast-domain", self.broadcast_domain) + if self.ipspace: + domain_obj.add_new_child("ipspace", self.ipspace) + try: + self.server.invoke_successfully(domain_obj, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error deleting broadcast domain %s: %s' % + (self.broadcast_domain, to_native(error)), + exception=traceback.format_exc()) + + def modify_broadcast_domain(self): + """ + Modifies ipspace and mtu options of a broadcast domain + """ + domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-modify') + domain_obj.add_new_child("broadcast-domain", self.broadcast_domain) + if self.ipspace: + domain_obj.add_new_child("ipspace", self.ipspace) + if self.mtu: + domain_obj.add_new_child("mtu", self.mtu) + try: + self.server.invoke_successfully(domain_obj, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying broadcast domain %s: %s' % + (self.broadcast_domain, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + """ + Run Module based on play book + """ + changed = False + broadcast_domain_details = self.get_broadcast_domain() + broadcast_domain_exists = False + results = netapp_utils.get_cserver(self.server) + cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) + netapp_utils.ems_log_event("na_ontap_broadcast_domain", cserver) + if broadcast_domain_details: + broadcast_domain_exists = True + if self.state == 'absent': # delete + changed = True + elif self.state == 'present': # modify + if (self.mtu and self.mtu != broadcast_domain_details['mtu']) or \ + (self.ipspace and self.ipspace != broadcast_domain_details['ipspace']): + changed = True + else: + if self.state == 'present': # create + changed = True + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': # execute create + if not broadcast_domain_exists: + self.create_broadcast_domain() + else: # execute modify + self.modify_broadcast_domain() + elif self.state == 'absent': # execute delete + self.delete_broadcast_domain() + self.module.exit_json(changed=changed) + + +def main(): + """ + Creates the NetApp ONTAP Broadcast Domain Object that can be created, deleted and modified. + """ + obj = NetAppOntapBroadcastDomain() + obj.apply() + +if __name__ == '__main__': + main() diff --git a/lib/ansible/utils/module_docs_fragments/netapp.py b/lib/ansible/utils/module_docs_fragments/netapp.py index 3629f2c4538..653d4d1d20a 100644 --- a/lib/ansible/utils/module_docs_fragments/netapp.py +++ b/lib/ansible/utils/module_docs_fragments/netapp.py @@ -1,5 +1,5 @@ # -# (c) 2016, Sumit Kumar +# (c) 2018, Sumit Kumar , chris Archibald # # This file is part of Ansible # @@ -28,8 +28,8 @@ notes: - Ansible modules are available for the following NetApp Storage Platforms: E-Series, ONTAP, SolidFire """ - # Documentation fragment for ONTAP - ONTAP = """ + # Documentation fragment for ONTAP (na_ontap) + NA_ONTAP = """ options: hostname: required: true @@ -46,14 +46,48 @@ options: description: - Password for the specified user. aliases: ['pass'] + https: + description: + - Enable and disabled https + type: bool + default: false + +requirements: + - A physical or virtual clustered Data ONTAP system. The modules were developed with Clustered Data ONTAP 9.3 + - Ansible 2.6 + - netapp-lib (2017.10.30). Install using 'pip install netapp-lib' + - To enable http on the cluster you must run the following commands 'set -privilege advanced;' 'system services web modify -http-enabled true;' +notes: + - The modules prefixed with na\\_ontap are built to support the ONTAP storage platform. + + """ + + # Documentation fragment for ONTAP (na_cdot) + ONTAP = """ +options: + hostname: + required: true + description: + - The hostname or IP address of the ONTAP instance. + username: + required: true + description: + - This can be a Cluster-scoped or SVM-scoped account, depending on whether a Cluster-level or SVM-level API is required. + For more information, please read the documentation U(https://goo.gl/BRu78Z). + aliases: ['user'] + password: + required: true + description: + - Password for the specified user. + aliases: ['pass'] requirements: - A physical or virtual clustered Data ONTAP system. The modules were developed with Clustered Data ONTAP 8.3 - Ansible 2.2 - netapp-lib (2015.9.25). Install using 'pip install netapp-lib' notes: - - The modules prefixed with C(netapp\\_cdot) are built to support the ONTAP storage platform. + - The modules prefixed with na\\_cdot are built to support the ONTAP storage platform. """