diff --git a/lib/ansible/modules/storage/netapp/na_ontap_nfs.py b/lib/ansible/modules/storage/netapp/na_ontap_nfs.py new file mode 100644 index 00000000000..376bf6db00a --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_nfs.py @@ -0,0 +1,374 @@ +#!/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_nfs +short_description: Manage Ontap NFS status +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Suhas Bangalore Shekar (bsuhas@netapp.com) +description: +- Enable or disable nfs on ONTAP +options: + state: + description: + - Whether nfs should exist or not. + choices: ['present', 'absent'] + default: present + service_state: + description: + - Whether the specified nfs should be enabled or disabled. Creates nfs service if doesnt exist. + choices: ['started', 'stopped'] + vserver: + description: + - Name of the vserver to use. + required: true + nfsv3: + description: + - status of nfsv3. + choices: ['enabled', 'disabled'] + nfsv4: + description: + - status of nfsv4. + choices: ['enabled', 'disabled'] + nfsv41: + description: + - status of nfsv41. + aliases: ['nfsv4.1'] + choices: ['enabled', 'disabled'] + vstorage_state: + description: + - status of vstorage_state. + choices: ['enabled', 'disabled'] + nfsv4_id_domain: + description: + - Name of the nfsv4_id_domain to use. + tcp: + description: + - Enable TCP. + choices: ['enabled', 'disabled'] + udp: + description: + - Enable UDP. + choices: ['enabled', 'disabled'] +""" + +EXAMPLES = """ + - name: change nfs status + na_ontap_nfs: + state: present + service_state: stopped + vserver: vs_hack + nfsv3: disabled + nfsv4: disabled + nfsv41: enabled + tcp: disabled + udp: disabled + vstorage_state: disabled + nfsv4_id_domain: example.com + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" +""" + +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 NetAppONTAPNFS(object): + """ object initialize and class methods """ + def __init__(self): + + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), + service_state=dict(required=False, choices=['started', 'stopped']), + vserver=dict(required=True, type='str'), + nfsv3=dict(required=False, default=None, choices=['enabled', 'disabled']), + nfsv4=dict(required=False, default=None, choices=['enabled', 'disabled']), + nfsv41=dict(required=False, default=None, choices=['enabled', 'disabled'], aliases=['nfsv4.1']), + vstorage_state=dict(required=False, default=None, choices=['enabled', 'disabled']), + tcp=dict(required=False, default=None, choices=['enabled', 'disabled']), + udp=dict(required=False, default=None, choices=['enabled', 'disabled']), + nfsv4_id_domain=dict(required=False, type='str', default=None), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + parameters = self.module.params + + # set up service_state variables + self.state = parameters['state'] + self.service_state = parameters['service_state'] + self.vserver = parameters['vserver'] + self.nfsv3 = parameters['nfsv3'] + self.nfsv4 = parameters['nfsv4'] + self.nfsv41 = parameters['nfsv41'] + self.vstorage_state = parameters['vstorage_state'] + self.nfsv4_id_domain = parameters['nfsv4_id_domain'] + self.udp = parameters['udp'] + self.tcp = parameters['tcp'] + + 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, vserver=self.vserver) + + def get_nfs_service(self): + """ + Return details about nfs + :param: + name : name of the vserver + :return: Details about nfs. None if not found. + :rtype: dict + """ + nfs_get_iter = netapp_utils.zapi.NaElement('nfs-service-get-iter') + nfs_info = netapp_utils.zapi.NaElement('nfs-info') + nfs_info.add_new_child('vserver', self.vserver) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(nfs_info) + nfs_get_iter.add_child_elem(query) + result = self.server.invoke_successfully(nfs_get_iter, True) + nfs_details = None + # check if job exists + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) >= 1: + attributes_list = result.get_child_by_name('attributes-list').get_child_by_name('nfs-info') + is_nfsv3_enabled = attributes_list.get_child_content('is-nfsv3-enabled') + is_nfsv40_enabled = attributes_list.get_child_content('is-nfsv40-enabled') + is_nfsv41_enabled = attributes_list.get_child_content('is-nfsv41-enabled') + is_vstorage_enabled = attributes_list.get_child_content('is-vstorage-enabled') + nfsv4_id_domain_value = attributes_list.get_child_content('nfsv4-id-domain') + is_tcp_enabled = attributes_list.get_child_content('is-tcp-enabled') + is_udp_enabled = attributes_list.get_child_content('is-udp-enabled') + nfs_details = { + 'is_nfsv3_enabled': is_nfsv3_enabled, + 'is_nfsv40_enabled': is_nfsv40_enabled, + 'is_nfsv41_enabled': is_nfsv41_enabled, + 'is_vstorage_enabled': is_vstorage_enabled, + 'nfsv4_id_domain': nfsv4_id_domain_value, + 'is_tcp_enabled': is_tcp_enabled, + 'is_udp_enabled': is_udp_enabled + } + return nfs_details + + def get_nfs_status(self): + """ + Return status of nfs + :param: + name : Name of the vserver + :return: status of nfs. None if not found. + :rtype: boolean + """ + nfs_status = netapp_utils.zapi.NaElement('nfs-status') + result = self.server.invoke_successfully(nfs_status, True) + return_value = result.get_child_content('is-enabled') + + return return_value + + def enable_nfs(self): + """ + enable nfs (online). If the NFS service was not explicitly created, + this API will create one with default options. + """ + nfs_enable = netapp_utils.zapi.NaElement.create_node_with_children('nfs-enable') + try: + self.server.invoke_successfully(nfs_enable, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error changing the service_state of nfs %s to %s: %s' % + (self.vserver, self.service_state, to_native(error)), + exception=traceback.format_exc()) + + def disable_nfs(self): + """ + disable nfs (offline). + """ + nfs_disable = netapp_utils.zapi.NaElement.create_node_with_children('nfs-disable') + try: + self.server.invoke_successfully(nfs_disable, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error changing the service_state of nfs %s to %s: %s' % + (self.vserver, self.service_state, to_native(error)), + exception=traceback.format_exc()) + + def modify_nfs(self): + """ + modify nfs service + """ + nfs_modify = netapp_utils.zapi.NaElement('nfs-service-modify') + if self.nfsv3 == 'enabled': + nfs_modify.add_new_child('is-nfsv3-enabled', 'true') + elif self.nfsv3 == 'disabled': + nfs_modify.add_new_child('is-nfsv3-enabled', 'false') + if self.nfsv4 == 'enabled': + nfs_modify.add_new_child('is-nfsv40-enabled', 'true') + elif self.nfsv4 == 'disabled': + nfs_modify.add_new_child('is-nfsv40-enabled', 'false') + if self.nfsv41 == 'enabled': + nfs_modify.add_new_child('is-nfsv41-enabled', 'true') + elif self.nfsv41 == 'disabled': + nfs_modify.add_new_child('is-nfsv41-enabled', 'false') + if self.vstorage_state == 'enabled': + nfs_modify.add_new_child('is-vstorage-enabled', 'true') + elif self.vstorage_state == 'disabled': + nfs_modify.add_new_child('is-vstorage-enabled', 'false') + if self.tcp == 'enabled': + nfs_modify.add_new_child('is-tcp-enabled', 'true') + elif self.tcp == 'disabled': + nfs_modify.add_new_child('is-tcp-enabled', 'false') + if self.udp == 'enabled': + nfs_modify.add_new_child('is-udp-enabled', 'true') + elif self.udp == 'disabled': + nfs_modify.add_new_child('is-udp-enabled', 'false') + try: + self.server.invoke_successfully(nfs_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying nfs: %s' + % (to_native(error)), + exception=traceback.format_exc()) + + def modify_nfsv4_id_domain(self): + """ + modify nfs service + """ + nfsv4_id_domain_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'nfs-service-modify', **{'nfsv4-id-domain': self.nfsv4_id_domain}) + if nfsv4_id_domain_modify is not None: + try: + self.server.invoke_successfully(nfsv4_id_domain_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying nfs: %s' + % (to_native(error)), + exception=traceback.format_exc()) + + def delete_nfs(self): + """ + delete nfs service. + """ + nfs_delete = netapp_utils.zapi.NaElement.create_node_with_children('nfs-service-destroy') + try: + self.server.invoke_successfully(nfs_delete, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error deleting nfs: %s' % + (to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + """Apply action to nfs""" + changed = False + nfs_exists = False + modify_nfs = False + enable_nfs = False + disable_nfs = False + netapp_utils.ems_log_event("na_ontap_nfs", self.server) + nfs_enabled = self.get_nfs_status() + nfs_service_details = self.get_nfs_service() + is_nfsv4_id_domain_changed = False + + def state_changed(expected, current): + if expected == "enabled" and current == "true": + return False + if expected == "disabled" and current == "false": + return False + return True + + def is_modify_needed(): + if (((self.nfsv3 is not None) and state_changed(self.nfsv3, nfs_service_details['is_nfsv3_enabled'])) or + ((self.nfsv4 is not None) and state_changed(self.nfsv4, nfs_service_details['is_nfsv40_enabled'])) or + ((self.nfsv41 is not None) and state_changed(self.nfsv41, nfs_service_details['is_nfsv41_enabled'])) or + ((self.tcp is not None) and state_changed(self.tcp, nfs_service_details['is_tcp_enabled'])) or + ((self.udp is not None) and state_changed(self.udp, nfs_service_details['is_udp_enabled'])) or + ((self.vstorage_state is not None) and state_changed(self.vstorage_state, nfs_service_details['is_vstorage_enabled']))): + return True + return False + + def is_domain_changed(): + if (self.nfsv4_id_domain is not None) and (self.nfsv4_id_domain != nfs_service_details['nfsv4_id_domain']): + return True + return False + + if nfs_service_details: + nfs_exists = True + if self.state == 'absent': # delete + changed = True + elif self.state == 'present': # modify + if self.service_state == 'started' and nfs_enabled == 'false': + enable_nfs = True + changed = True + elif self.service_state == 'stopped' and nfs_enabled == 'true': + disable_nfs = True + changed = True + if is_modify_needed(): + modify_nfs = True + changed = True + if is_domain_changed(): + is_nfsv4_id_domain_changed = True + 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 nfs_exists: + self.enable_nfs() + nfs_service_details = self.get_nfs_service() + if self.service_state == 'stopped': + self.disable_nfs() + if is_modify_needed(): + self.modify_nfs() + if is_domain_changed(): + self.modify_nfsv4_id_domain() + else: + if enable_nfs: + self.enable_nfs() + elif disable_nfs: + self.disable_nfs() + if modify_nfs: + self.modify_nfs() + if is_nfsv4_id_domain_changed: + self.modify_nfsv4_id_domain() + elif self.state == 'absent': # execute delete + self.delete_nfs() + + self.module.exit_json(changed=changed) + + +def main(): + """ Create object and call apply """ + obj = NetAppONTAPNFS() + obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_ntp.py b/lib/ansible/modules/storage/netapp/na_ontap_ntp.py new file mode 100644 index 00000000000..29fdc5a9c43 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_ntp.py @@ -0,0 +1,224 @@ +#!/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_ntp +short_description: Create/Delete/modify_version ONTAP NTP server +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Suhas Bangalore Shekar (bsuhas@netapp.com), Archana Ganesan (garchana@netapp.com) +description: +- Create or delete or modify ntp server in ONTAP +options: + state: + description: + - Whether the specified ntp server should exist or not. + choices: ['present', 'absent'] + default: 'present' + server_name: + description: + - The name of the ntp server to manage. + required: True + version: + description: + - give version for ntp server + choices: ['auto', '3', '4'] + default: 'auto' +""" + +EXAMPLES = """ + - name: Create NTP server + na_ontap_ntp: + state: present + version: auto + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + - name: Delete NTP server + na_ontap_ntp: + state: absent + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" +""" + +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 NetAppOntapNTPServer(object): + """ object initialize and class methods """ + def __init__(self): + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=[ + 'present', 'absent'], default='present'), + server_name=dict(required=True, type='str'), + version=dict(required=False, type='str', default='auto', + choices=['auto', '3', '4']), + )) + + 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.server_name = parameters['server_name'] + self.version = parameters['version'] + + 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) + + def get_ntp_server(self): + """ + Return details about the ntp server + :param: + name : Name of the server_name + :return: Details about the ntp server. None if not found. + :rtype: dict + """ + ntp_iter = netapp_utils.zapi.NaElement('ntp-server-get-iter') + ntp_info = netapp_utils.zapi.NaElement('ntp-server-info') + ntp_info.add_new_child('server-name', self.server_name) + + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(ntp_info) + + ntp_iter.add_child_elem(query) + result = self.server.invoke_successfully(ntp_iter, True) + return_value = None + + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) == 1: + + ntp_server_name = result.get_child_by_name('attributes-list').\ + get_child_by_name('ntp-server-info').\ + get_child_content('server-name') + server_version = result.get_child_by_name('attributes-list').\ + get_child_by_name('ntp-server-info').\ + get_child_content('version') + return_value = { + 'server-name': ntp_server_name, + 'version': server_version + } + + return return_value + + def create_ntp_server(self): + """ + create ntp server. + """ + ntp_server_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'ntp-server-create', **{'server-name': self.server_name, + 'version': self.version + }) + + try: + self.server.invoke_successfully(ntp_server_create, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error creating ntp server %s: %s' + % (self.server_name, to_native(error)), + exception=traceback.format_exc()) + + def delete_ntp_server(self): + """ + delete ntp server. + """ + ntp_server_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'ntp-server-delete', **{'server-name': self.server_name}) + + try: + self.server.invoke_successfully(ntp_server_delete, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error deleting ntp server %s: %s' + % (self.server_name, to_native(error)), + exception=traceback.format_exc()) + + def modify_version(self): + """ + modify the version. + """ + ntp_modify_versoin = netapp_utils.zapi.NaElement.create_node_with_children( + 'ntp-server-modify', + **{'server-name': self.server_name, 'version': self.version}) + try: + self.server.invoke_successfully(ntp_modify_versoin, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying version for ntp server %s: %s' + % (self.server_name, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + """Apply action to ntp-server""" + + changed = False + ntp_modify = 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_ntp", cserver) + ntp_server_details = self.get_ntp_server() + if ntp_server_details is not None: + if self.state == 'absent': # delete + changed = True + elif self.state == 'present' and self.version: + # modify version + if self.version != ntp_server_details['version']: + ntp_modify = True + changed = True + else: + if self.state == 'present': # create + changed = True + + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if ntp_server_details is None: + self.create_ntp_server() + elif ntp_modify: + self.modify_version() + elif self.state == 'absent': + self.delete_ntp_server() + + self.module.exit_json(changed=changed) + + +def main(): + """ Create object and call apply """ + ntp_obj = NetAppOntapNTPServer() + ntp_obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_qtree.py b/lib/ansible/modules/storage/netapp/na_ontap_qtree.py new file mode 100644 index 00000000000..34bf940fc23 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_qtree.py @@ -0,0 +1,233 @@ +#!/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_qtree + +short_description: Manage qtrees +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Sumit Kumar (sumit4@netapp.com) + +description: +- Create or destroy Qtrees. + +options: + + state: + description: + - Whether the specified Qtree should exist or not. + choices: ['present', 'absent'] + default: 'present' + + name: + description: + - The name of the Qtree to manage. + required: true + + new_name: + description: + - New name of the Qtree to be renamed. + + flexvol_name: + description: + - The name of the FlexVol the Qtree should exist on. Required when C(state=present). + + vserver: + description: + - The name of the vserver to use. + required: true + +''' + +EXAMPLES = """ +- name: Create QTree + na_ontap_qtree: + state: present + name: ansibleQTree + flexvol_name: ansibleVolume + vserver: ansibleVServer + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +- name: Rename QTree + na_ontap_qtree: + state: present + name: ansibleQTree + flexvol_name: ansibleVolume + vserver: ansibleVServer + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" +""" + +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 NetAppOntapQTree(object): + + def __init__(self): + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=[ + 'present', 'absent'], default='present'), + name=dict(required=True, type='str'), + new_name=dict(required=False, type='str'), + flexvol_name=dict(type='str'), + vserver=dict(required=True, type='str'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[ + ('state', 'present', ['flexvol_name']) + ], + supports_check_mode=True + ) + + p = self.module.params + + # set up state variables + self.state = p['state'] + self.name = p['name'] + self.new_name = p['new_name'] + self.flexvol_name = p['flexvol_name'] + self.vserver = p['vserver'] + + 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, vserver=self.vserver) + + def get_qtree(self): + """ + Checks if the qtree exists. + + :return: + True if qtree found + False if qtree is not found + :rtype: bool + """ + + qtree_list_iter = netapp_utils.zapi.NaElement('qtree-list-iter') + query_details = netapp_utils.zapi.NaElement.create_node_with_children( + 'qtree-info', **{'vserver': self.vserver, + 'volume': self.flexvol_name, + 'qtree': self.name}) + + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(query_details) + qtree_list_iter.add_child_elem(query) + + result = self.server.invoke_successfully(qtree_list_iter, + enable_tunneling=True) + + if (result.get_child_by_name('num-records') and + int(result.get_child_content('num-records')) >= 1): + return True + else: + return False + + def create_qtree(self): + qtree_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'qtree-create', **{'volume': self.flexvol_name, + 'qtree': self.name}) + + try: + self.server.invoke_successfully(qtree_create, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error provisioning qtree %s: %s" % (self.name, to_native(e)), + exception=traceback.format_exc()) + + def delete_qtree(self): + path = '/vol/%s/%s' % (self.flexvol_name, self.name) + qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'qtree-delete', **{'qtree': path}) + + try: + self.server.invoke_successfully(qtree_delete, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error deleting qtree %s: %s" % (path, to_native(e)), + exception=traceback.format_exc()) + + def rename_qtree(self): + path = '/vol/%s/%s' % (self.flexvol_name, self.name) + new_path = '/vol/%s/%s' % (self.flexvol_name, self.new_name) + qtree_rename = netapp_utils.zapi.NaElement.create_node_with_children( + 'qtree-rename', **{'qtree': path, + 'new-qtree-name': new_path}) + + try: + self.server.invoke_successfully(qtree_rename, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error renaming qtree %s: %s" % (self.name, to_native(e)), + exception=traceback.format_exc()) + + def apply(self): + changed = False + qtree_exists = False + rename_qtree = False + netapp_utils.ems_log_event("na_ontap_qtree", self.server) + qtree_detail = self.get_qtree() + if qtree_detail: + qtree_exists = True + if self.state == 'absent': # delete + changed = True + else: # rename + if self.new_name and self.name != self.new_name: + changed = True + rename_qtree = True + else: + if self.state == 'present': # create + changed = True + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if not qtree_exists: + self.create_qtree() + elif rename_qtree: + self.rename_qtree() + elif self.state == 'absent': + self.delete_qtree() + + self.module.exit_json(changed=changed) + + +def main(): + v = NetAppOntapQTree() + v.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_service_processor_network.py b/lib/ansible/modules/storage/netapp/na_ontap_service_processor_network.py new file mode 100644 index 00000000000..5e925b0b123 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_service_processor_network.py @@ -0,0 +1,264 @@ +#!/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_service_processor_network +short_description: Manage NetApp Ontap service processor network +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: +- Chris Archibald (carchi@netapp.com), Kevin Hutton (khutton@netapp.com) +description: +- Modify a Ontap service processor network +options: + state: + description: + - Whether the specified service processor network should exist or not. + choices: ['present'] + default: present + address_type: + description: + - Specify address class. + required: true + choices: ['ipv4', 'ipv6'] + is_enabled: + description: + - Specify whether to enable or disable the service processor network. + required: true + choices: ['true', 'false'] + node: + description: + - The node where the the service processor network should be enabled + required: true + dhcp: + description: + - Specify dhcp type. + choices: ['v4', 'none'] + gateway_ip_address: + description: + - Specify the gateway ip. + ip_address: + description: + - Specify the service processor ip address. + netmask: + description: + - Specify the service processor netmask. + prefix_length: + description: + - Specify the service processor prefix_length. +''' + +EXAMPLES = """ + - name: Modify Service Processor Network + na_ontap_service_processor_network: + state=present + address_type=ipv4 + is_enabled=true + dhcp=v4 + node=FPaaS-A300-01 + node={{ netapp_node }} + username={{ netapp_username }} + password={{ netapp_password }} + hostname={{ netapp_hostname }} +""" + +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 NetAppOntapServiceProcessorNetwork(object): + """ + Modify a Service Processor Network + """ + + def __init__(self): + """ + Initialize the NetAppOntapServiceProcessorNetwork class + """ + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=['present'], default='present'), + address_type=dict(required=True, choices=['ipv4', 'ipv6']), + is_enabled=dict(required=True, choices=['true', 'false']), + node=dict(required=True, type='str'), + dhcp=dict(required=False, choices=['v4', 'none']), + gateway_ip_address=dict(required=False, type='str'), + ip_address=dict(required=False, type='str'), + netmask=dict(required=False, type='str'), + prefix_length=dict(required=False, type='int'), + + )) + + 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.address_type = parameters['address_type'] + self.dhcp = parameters['dhcp'] + self.gateway_ip_address = parameters['gateway_ip_address'] + self.ip_address = parameters['ip_address'] + self.is_enabled = parameters['is_enabled'] + self.netmask = parameters['netmask'] + self.node = parameters['node'] + self.prefix_length = parameters['prefix_length'] + + 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, vserver=None) + return + + def get_service_processor_network(self): + """ + Return details about service processor network + :param: + name : name of the vserver + :return: Details about service processor network. None if not found. + :rtype: dict + """ + spn_get_iter = netapp_utils.zapi.NaElement( + 'service-processor-network-get-iter') + spn_info = netapp_utils.zapi.NaElement( + 'service-processor-network-info') + spn_info.add_new_child('node', self.node) + spn_info.add_new_child('address-type', self.address_type) + spn_info.add_new_child('is-enabled', self.is_enabled) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(spn_info) + spn_get_iter.add_child_elem(query) + result = self.server.invoke_successfully(spn_get_iter, True) + sp_network_details = None + # check if job exists + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) >= 1: + attributes_list = result.get_child_by_name('attributes-list').\ + get_child_by_name('service-processor-network-info') + node_value = attributes_list.get_child_content('node') + address_type_value = attributes_list.get_child_content( + 'address-type') + dhcp_value = attributes_list.get_child_content('dhcp') + gateway_ip_address_value = attributes_list.get_child_content( + 'gateway-ip-address') + ip_address_value = attributes_list.get_child_content('ip-address') + is_enabled_value = attributes_list.get_child_content('is-enabled') + netmask_value = attributes_list.get_child_content('netmask') + prefix_length_value = attributes_list.get_child_content( + 'prefix-length') + sp_network_details = { + 'node_value': node_value, + 'address_type_value': address_type_value, + 'dhcp_value': dhcp_value, + 'gateway_ip_address_value': gateway_ip_address_value, + 'ip_address_value': ip_address_value, + 'is_enabled_value': is_enabled_value, + 'netmask_value': netmask_value, + 'prefix_length_value': prefix_length_value + } + return sp_network_details + + def modify_service_processor_network(self): + """ + Modify a service processor network + """ + service_obj = netapp_utils.zapi.NaElement( + 'service-processor-network-modify') + service_obj.add_new_child("node", self.node) + service_obj.add_new_child("address-type", self.address_type) + service_obj.add_new_child("is-enabled", self.is_enabled) + + if self.dhcp: + service_obj.add_new_child("dhcp", self.dhcp) + if self.gateway_ip_address: + service_obj.add_new_child( + "gateway-ip-address", self.gateway_ip_address) + if self.ip_address: + service_obj.add_new_child("ip-address", self.ip_address) + if self.netmask: + service_obj.add_new_child("netmask", self.netmask) + if self.prefix_length is not None: + service_obj.add_new_child("prefix-length", str(self.prefix_length)) + + try: + result = self.server.invoke_successfully(service_obj, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying \ + service processor network: %s' + % (to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + """ + Run Module based on play book + """ + changed = False + results = netapp_utils.get_cserver(self.server) + cserver = netapp_utils.setup_ontap_zapi( + module=self.module, vserver=results) + netapp_utils.ems_log_event( + "na_ontap_service_processor_network", cserver) + spn_details = self.get_service_processor_network() + spn_exists = False + if spn_details: + spn_exists = True + if self.state == 'present': # modify + if (self.dhcp and + self.dhcp != spn_details['dhcp_value']) or \ + (self.gateway_ip_address and + self.gateway_ip_address != spn_details['gateway_ip_address_value']) or \ + (self.ip_address and + self.ip_address != spn_details['ip_address_value']) or \ + (self.netmask and + self.netmask != spn_details['netmask_value']) or \ + (self.prefix_length and str(self.prefix_length) + != spn_details['prefix_length_value']): + changed = True + else: + pass + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': # execute modify + if spn_exists: + self.modify_service_processor_network() + self.module.exit_json(changed=changed) + + +def main(): + """ + Create the NetApp Ontap Service Processor Network Object and modify it + """ + + obj = NetAppOntapServiceProcessorNetwork() + obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_snapshot.py b/lib/ansible/modules/storage/netapp/na_ontap_snapshot.py new file mode 100644 index 00000000000..fac6a2160ec --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_snapshot.py @@ -0,0 +1,319 @@ +#!/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_snapshot +short_description: Manage NetApp Sanpshots +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: +- Chris Archibald (carchi@netapp.com), Kevin Hutton (khutton@netapp.com) +description: +- Create/Modify/Delete Ontap snapshots +options: + state: + description: + - If you want to create/modify a snapshot, or delete it. + choices: ['present', 'absent'] + default: present + snapshot: + description: + Name of the snapshot to be managed. + The maximum string length is 256 characters. + required: true + volume: + description: + - Name of the volume on which the snapshot is to be created. + required: true + async_bool: + description: + - If true, the snapshot is to be created asynchronously. + type: bool + comment: + description: + A human readable comment attached with the snapshot. + The size of the comment can be at most 255 characters. + snapmirror_label: + description: + A human readable SnapMirror Label attached with the snapshot. + Size of the label can be at most 31 characters. + ignore_owners: + description: + - if this field is true, snapshot will be deleted + even if some other processes are accessing it. + type: bool + snapshot_instance_uuid: + description: + - The 128 bit unique snapshot identifier expressed in the form of UUID. + vserver: + description: + - The Vserver name + new_comment: + description: + A human readable comment attached with the snapshot. + The size of the comment can be at most 255 characters. + This will replace the existing comment +''' +EXAMPLES = """ + - name: create SnapShot + tags: + - create + na_ontap_snapshot: + state=present + snapshot={{ snapshot name }} + volume={{ vol name }} + comment="i am a comment" + vserver={{ vserver name }} + username={{ netapp username }} + password={{ netapp password }} + hostname={{ netapp hostname }} + - name: delete SnapShot + tags: + - delete + na_ontap_snapshot: + state=absent + snapshot={{ snapshot name }} + volume={{ vol name }} + vserver={{ vserver name }} + username={{ netapp username }} + password={{ netapp password }} + hostname={{ netapp hostname }} + - name: modify SnapShot + tags: + - modify + na_ontap_snapshot: + state=present + snapshot={{ snapshot name }} + new_comment="New comments are great" + volume={{ vol name }} + vserver={{ vserver name }} + username={{ netapp username }} + password={{ netapp password }} + hostname={{ netapp hostname }} +""" + +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 NetAppOntapSnapshot(object): + """ + Creates, modifies, and deletes a Snapshot + """ + + def __init__(self): + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=[ + 'present', 'absent'], default='present'), + snapshot=dict(required=True, type="str"), + volume=dict(required=True, type="str"), + async_bool=dict(required=False, type="bool", default=False), + comment=dict(required=False, type="str"), + snapmirror_label=dict(required=False, type="str"), + ignore_owners=dict(required=False, type="bool", default=False), + snapshot_instance_uuid=dict(required=False, type="str"), + vserver=dict(required=True, type="str"), + new_comment=dict(required=False, type="str"), + + )) + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + parameters = self.module.params + + # set up state variables + # These are the required variables + self.state = parameters['state'] + self.snapshot = parameters['snapshot'] + self.vserver = parameters['vserver'] + # these are the optional variables for creating a snapshot + self.volume = parameters['volume'] + self.async_bool = parameters['async_bool'] + self.comment = parameters['comment'] + self.snapmirror_label = parameters['snapmirror_label'] + # these are the optional variables for deleting a snapshot\ + self.ignore_owners = parameters['ignore_owners'] + self.snapshot_instance_uuid = parameters['snapshot_instance_uuid'] + # These are the optional for Modify. + # You can NOT change a snapcenter name + self.new_comment = parameters['new_comment'] + + 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, vserver=self.vserver) + return + + def create_snapshot(self): + """ + Creates a new snapshot + """ + snapshot_obj = netapp_utils.zapi.NaElement("snapshot-create") + + # set up required variables to create a snapshot + snapshot_obj.add_new_child("snapshot", self.snapshot) + snapshot_obj.add_new_child("volume", self.volume) + # Set up optional variables to create a snapshot + if self.async_bool: + snapshot_obj.add_new_child("async", self.async_bool) + if self.comment: + snapshot_obj.add_new_child("comment", self.comment) + if self.snapmirror_label: + snapshot_obj.add_new_child( + "snapmirror-label", self.snapmirror_label) + try: + self.server.invoke_successfully(snapshot_obj, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error creating snapshot %s: %s' % + (self.snapshot, to_native(error)), + exception=traceback.format_exc()) + + def delete_snapshot(self): + """ + Deletes an existing snapshot + """ + snapshot_obj = netapp_utils.zapi.NaElement("snapshot-delete") + + # Set up required variables to delete a snapshot + snapshot_obj.add_new_child("snapshot", self.snapshot) + snapshot_obj.add_new_child("volume", self.volume) + # set up optional variables to delete a snapshot + if self.ignore_owners: + snapshot_obj.add_new_child("ignore-owners", self.ignore_owners) + if self.snapshot_instance_uuid: + snapshot_obj.add_new_child( + "snapshot-instance-uuid", self.snapshot_instance_uuid) + try: + self.server.invoke_successfully(snapshot_obj, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error deleting snapshot %s: %s' % + (self.snapshot, to_native(error)), + exception=traceback.format_exc()) + + def modify_snapshot(self): + """ + Modify an existing snapshot + :return: + """ + snapshot_obj = netapp_utils.zapi.NaElement("snapshot-modify-iter") + # Create query object, this is the existing object + query = netapp_utils.zapi.NaElement("query") + snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") + snapshot_info_obj.add_new_child("name", self.snapshot) + query.add_child_elem(snapshot_info_obj) + snapshot_obj.add_child_elem(query) + + # this is what we want to modify in the snapshot object + attributes = netapp_utils.zapi.NaElement("attributes") + snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") + snapshot_info_obj.add_new_child("name", self.snapshot) + snapshot_info_obj.add_new_child("comment", self.new_comment) + attributes.add_child_elem(snapshot_info_obj) + snapshot_obj.add_child_elem(attributes) + try: + self.server.invoke_successfully(snapshot_obj, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying snapshot %s: %s' % + (self.snapshot, to_native(error)), + exception=traceback.format_exc()) + + def does_snapshot_exist(self): + """ + Checks to see if a snapshot exists or not + :return: Return True if a snapshot exists, false if it dosn't + """ + snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter") + desired_attr = netapp_utils.zapi.NaElement("desired-attributes") + snapshot_info = netapp_utils.zapi.NaElement('snapshot-info') + comment = netapp_utils.zapi.NaElement('comment') + # add more desired attributes that are allowed to be modified + snapshot_info.add_child_elem(comment) + desired_attr.add_child_elem(snapshot_info) + snapshot_obj.add_child_elem(desired_attr) + # compose query + query = netapp_utils.zapi.NaElement("query") + snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") + snapshot_info_obj.add_new_child("name", self.snapshot) + snapshot_info_obj.add_new_child("volume", self.volume) + query.add_child_elem(snapshot_info_obj) + snapshot_obj.add_child_elem(query) + result = self.server.invoke_successfully(snapshot_obj, True) + return_value = None + # TODO: Snapshot with the same name will mess this up, + # need to fix that later + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) == 1: + attributes_list = result.get_child_by_name('attributes-list') + snap_info = attributes_list.get_child_by_name('snapshot-info') + return_value = {'comment': snap_info.get_child_content('comment')} + return return_value + + def apply(self): + """ + Check to see which play we should run + """ + changed = False + comment_changed = False + netapp_utils.ems_log_event("na_ontap_snapshot", self.server) + existing_snapshot = self.does_snapshot_exist() + if existing_snapshot is not None: + if self.state == 'absent': + changed = True + elif self.state == 'present' and self.new_comment: + if existing_snapshot['comment'] != self.new_comment: + comment_changed = True + changed = True + else: + if self.state == 'present': + changed = True + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if not existing_snapshot: + self.create_snapshot() + elif comment_changed: + self.modify_snapshot() + elif self.state == 'absent': + if existing_snapshot: + self.delete_snapshot() + + self.module.exit_json(changed=changed) + + +def main(): + """ + Creates, modifies, and deletes a Snapshot + """ + + obj = NetAppOntapSnapshot() + obj.apply() + + +if __name__ == '__main__': + main()