diff --git a/lib/ansible/modules/storage/netapp/na_ontap_interface.py b/lib/ansible/modules/storage/netapp/na_ontap_interface.py new file mode 100644 index 00000000000..033ca99cfc6 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_interface.py @@ -0,0 +1,364 @@ +#!/usr/bin/python +""" this is interface module + + (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_interface +short_description: ONTAP LIF configuration + +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: chhaya gunawat (chhayag@netapp.com) + +description: + - Creating / deleting and modifying the LIF. + +options: + state: + description: + - Whether the specified interface should exist or not. + choices: ['present', 'absent'] + default: present + + interface_name: + description: + - Specifies the logical interface (LIF) name. + required: true + + home_node: + description: + - Specifies the LIF's home node. + - Required when C(state=present). + + home_port: + description: + - Specifies the LIF's home port. + - Required when C(state=present) + + role: + description: + - Specifies the role of the LIF. + - Required when C(state=present). + + address: + description: + - Specifies the LIF's IP address. + - Required when C(state=present) + + netmask: + description: + - Specifies the LIF's netmask. + - Required when C(state=present). + + vserver: + description: + - The name of the vserver to use. + required: true + + firewall_policy: + description: + - Specifies the firewall policy for the LIF. + + failover_policy: + description: + - Specifies the failover policy for the LIF. + + admin_status: + choices: ['up', 'down'] + description: + - Specifies the administrative status of the LIF. + + is_auto_revert: + description: + If true, data LIF will revert to its home node under certain circumstances such as startup, and load balancing + migration capability is disabled automatically + + protocols: + description: + Specifies the list of data protocols configured on the LIF. By default, the values in this element are nfs, cifs and fcache. + Other supported protocols are iscsi and fcp. A LIF can be configured to not support any data protocols by specifying 'none'. + Protocol values of none, iscsi or fcp can't be combined with any other data protocol(s). + +''' + +EXAMPLES = ''' + - name: Create interface + na_ontap_interface: + state: present + interface_name: data2 + home_port: e0d + home_node: laurentn-vsim1 + role: data + protocols: nfs + admin_status: up + failover_policy: local-only + firewall_policy: mgmt + is_auto_revert: true + address: 10.10.10.10 + netmask: 255.255.255.0 + vserver: svm1 + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + + - name: Delete interface + na_ontap_interface: + state: absent + interface_name: data2 + vserver: svm1 + 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 NetAppOntapInterface(object): + ''' object to describe interface info ''' + + 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'), + interface_name=dict(required=True, type='str'), + home_node=dict(required=False, type='str', default=None), + home_port=dict(required=False, type='str'), + role=dict(required=False, type='str'), + address=dict(required=False, type='str'), + netmask=dict(required=False, type='str'), + vserver=dict(required=True, type='str'), + firewall_policy=dict(required=False, type='str', default=None), + failover_policy=dict(required=False, type='str', default=None), + admin_status=dict(required=False, choices=['up', 'down']), + is_auto_revert=dict(required=False, type='str', default=None), + protocols=dict(required=False, type='list') + + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + params = self.module.params + + # set up state variables + self.state = params['state'] + self.interface_name = params['interface_name'] + self.home_node = params['home_node'] + self.home_port = params['home_port'] + self.role = params['role'] + self.vserver = params['vserver'] + self.address = params['address'] + self.netmask = params['netmask'] + self.admin_status = params['admin_status'] + self.failover_policy = params['failover_policy'] + self.firewall_policy = params['firewall_policy'] + self.is_auto_revert = params['is_auto_revert'] + self.protocols = params['protocols'] + + 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_interface(self): + """ + Return details about the interface + :param: + name : Name of the name of the interface + + :return: Details about the interface. None if not found. + :rtype: dict + """ + interface_info = netapp_utils.zapi.NaElement('net-interface-get-iter') + interface_attributes = netapp_utils.zapi.NaElement( + 'net-interface-info') + interface_attributes.add_new_child( + 'interface-name', self.interface_name) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(interface_attributes) + interface_info.add_child_elem(query) + result = self.server.invoke_successfully(interface_info, True) + return_value = None + + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) >= 1: + + interface_attributes = result.get_child_by_name('attributes-list').\ + get_child_by_name('net-interface-info') + return_value = { + 'interface_name': self.interface_name, + 'admin_status': interface_attributes.get_child_content('administrative-status'), + 'home_port': interface_attributes.get_child_content('home-port'), + 'home_node': interface_attributes.get_child_content('home-node'), + 'address': interface_attributes.get_child_content('address'), + 'netmask': interface_attributes.get_child_content('netmask'), + 'failover_policy': interface_attributes.get_child_content('failover-policy'), + 'firewall_policy': interface_attributes.get_child_content('firewall-policy'), + 'is_auto_revert': interface_attributes.get_child_content('is-auto-revert'), + } + return return_value + + def create_interface(self): + ''' calling zapi to create interface ''' + + options = {'interface-name': self.interface_name, + 'vserver': self.vserver} + if self.home_port is not None: + options['home-port'] = self.home_port + if self.home_node is not None: + options['home-node'] = self.home_node + if self.address is not None: + options['address'] = self.address + if self.netmask is not None: + options['netmask'] = self.netmask + if self.role is not None: + options['role'] = self.role + if self.failover_policy is not None: + options['failover-policy'] = self.failover_policy + if self.firewall_policy is not None: + options['firewall-policy'] = self.firewall_policy + if self.is_auto_revert is not None: + options['is-auto-revert'] = self.is_auto_revert + if self.admin_status is not None: + options['administrative-status'] = self.admin_status + + interface_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'net-interface-create', **options) + if self.protocols is not None: + data_protocols_obj = netapp_utils.zapi.NaElement('data-protocols') + interface_create.add_child_elem(data_protocols_obj) + for protocol in self.protocols: + data_protocols_obj.add_new_child('data-protocol', protocol) + + try: + self.server.invoke_successfully(interface_create, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as exc: + self.module.fail_json(msg='Error Creating interface %s: %s' % + (self.interface_name, to_native(exc)), exception=traceback.format_exc()) + + def delete_interface(self, current_status): + ''' calling zapi to delete interface ''' + if current_status == 'up': + self.admin_status = 'down' + self.modify_interface() + + interface_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'net-interface-delete', **{'interface-name': self.interface_name, + 'vserver': self.vserver}) + try: + self.server.invoke_successfully(interface_delete, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as exc: + self.module.fail_json(msg='Error deleting interface %s: %s' % (self.interface_name, to_native(exc)), + exception=traceback.format_exc()) + + def modify_interface(self): + """ + Modify the interface. + """ + options = {'interface-name': self.interface_name, + 'vserver': self.vserver + } + if self.admin_status is not None: + options['administrative-status'] = self.admin_status + if self.failover_policy is not None: + options['failover-policy'] = self.failover_policy + if self.firewall_policy is not None: + options['firewall-policy'] = self.firewall_policy + if self.is_auto_revert is not None: + options['is-auto-revert'] = self.is_auto_revert + if self.netmask is not None: + options['netmask'] = self.netmask + if self.address is not None: + options['address'] = self.address + + interface_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'net-interface-modify', **options) + try: + self.server.invoke_successfully(interface_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error modifying interface %s: %s' % (self.interface_name, to_native(e)), + exception=traceback.format_exc()) + + def apply(self): + ''' calling all interface features ''' + changed = False + interface_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_interface", cserver) + interface_detail = self.get_interface() + if interface_detail: + interface_exists = True + if self.state == 'absent': + changed = True + + elif self.state == 'present': + if (self.admin_status is not None and self.admin_status != interface_detail['admin_status']) or \ + (self.address is not None and self.address != interface_detail['address']) or \ + (self.netmask is not None and self.netmask != interface_detail['netmask']) or \ + (self.failover_policy is not None and self.failover_policy != interface_detail['failover_policy']) or \ + (self.firewall_policy is not None and self.firewall_policy != interface_detail['firewall_policy']) or \ + (self.is_auto_revert is not None and self.is_auto_revert != interface_detail['is_auto_revert']): + changed = True + else: + if self.state == 'present': + changed = True + + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if interface_exists is False: + self.create_interface() + else: + self.modify_interface() + + elif self.state == 'absent': + self.delete_interface(interface_detail['admin_status']) + + self.module.exit_json(changed=changed) + + +def main(): + interface = NetAppOntapInterface() + interface.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_iscsi.py b/lib/ansible/modules/storage/netapp/na_ontap_iscsi.py new file mode 100644 index 00000000000..6f83a30d8c2 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_iscsi.py @@ -0,0 +1,273 @@ +#!/usr/bin/python + +# (c) 2017, 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_iscsi + +short_description: Manage NetApp Ontap iscsi service +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: +- Chhaya Gunawat (chhayag@netapp.com), Laurent Nicolas (laurentn@netapp.com) + +description: +- create, delete, start, stop iscsi service on svm. + +options: + + state: + description: + - Whether the service should be present or deleted. + choices: ['present', 'absent'] + default: present + + service_state: + description: + - Whether the specified service should running . + choices: ['started', 'stopped'] + + vserver: + required: true + description: + - The name of the vserver to use. + +''' + +EXAMPLES = """ +- name: Create iscsi service + na_ontap_iscsi: + state: present + service_state: started + vserver: ansibleVServer + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +- name: Stop Iscsi service + na_ontap_iscsi: + state: present + service_state: stopped + vserver: ansibleVServer + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +- name: Delete Iscsi service + na_ontap_iscsi: + state: absent + 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 NetAppOntapISCSI(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'), + service_state=dict(required=False, choices=[ + 'started', 'stopped'], default=None), + vserver=dict(required=True, type='str'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + params = self.module.params + + # set up state variables + self.state = params['state'] + self.service_state = params['service_state'] + if self.state == 'present' and self.service_state is None: + self.service_state = 'started' + self.vserver = params['vserver'] + self.is_started = None + + 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_iscsi(self): + """ + Return details about the iscsi service + + :return: Details about the iscsi service + :rtype: dict + """ + iscsi_info = netapp_utils.zapi.NaElement('iscsi-service-get-iter') + iscsi_attributes = netapp_utils.zapi.NaElement('iscsi-service-info') + + iscsi_attributes.add_new_child('vserver', self.vserver) + + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(iscsi_attributes) + + iscsi_info.add_child_elem(query) + + result = self.server.invoke_successfully(iscsi_info, True) + return_value = None + + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) >= 1: + + iscsi = result.get_child_by_name( + 'attributes-list').get_child_by_name('iscsi-service-info') + if iscsi: + is_started = iscsi.get_child_content('is-available') == 'true' + return_value = { + 'is_started': is_started + } + + return return_value + + def create_iscsi_service(self): + """ + Create iscsi service and start if requested + """ + iscsi_service = netapp_utils.zapi.NaElement.create_node_with_children( + 'iscsi-service-create', + **{'start': 'true' if self.state == 'started' else 'false' + }) + + try: + self.server.invoke_successfully( + iscsi_service, enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error creating iscsi service: % s" + % (to_native(e)), + exception=traceback.format_exc()) + + def delete_iscsi_service(self): + """ + Delete the iscsi service + """ + if self.is_started: + self.stop_iscsi_service() + + iscsi_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'iscsi-service-destroy') + + try: + self.server.invoke_successfully( + iscsi_delete, enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error deleting iscsi service \ + on vserver %s: %s" + % (self.vserver, to_native(e)), + exception=traceback.format_exc()) + + def stop_iscsi_service(self): + """ + Stop iscsi service + """ + + iscsi_stop = netapp_utils.zapi.NaElement.create_node_with_children( + 'iscsi-service-stop') + + try: + self.server.invoke_successfully(iscsi_stop, enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error Stopping iscsi service \ + on vserver %s: %s" + % (self.vserver, to_native(e)), + exception=traceback.format_exc()) + + def start_iscsi_service(self): + """ + Start iscsi service + """ + iscsi_start = netapp_utils.zapi.NaElement.create_node_with_children( + 'iscsi-service-start') + + try: + self.server.invoke_successfully(iscsi_start, enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error starting iscsi service \ + on vserver %s: %s" + % (self.vserver, to_native(e)), + exception=traceback.format_exc()) + + def apply(self): + property_changed = False + iscsi_service_exists = False + netapp_utils.ems_log_event("na_ontap_iscsi", self.server) + iscsi_service_detail = self.get_iscsi() + + if iscsi_service_detail: + self.is_started = iscsi_service_detail['is_started'] + iscsi_service_exists = True + + if self.state == 'absent': + property_changed = True + + elif self.state == 'present': + is_started = 'started' if self.is_started else 'stopped' + property_changed = is_started != self.service_state + + else: + if self.state == 'present': + property_changed = True + + if property_changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if not iscsi_service_exists: + self.create_iscsi_service() + elif self.service_state == 'started': + self.start_iscsi_service() + else: + self.stop_iscsi_service() + + elif self.state == 'absent': + self.delete_iscsi_service() + + changed = property_changed + # TODO: include other details about the lun (size, etc.) + self.module.exit_json(changed=changed) + + +def main(): + v = NetAppOntapISCSI() + v.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_job_schedule.py b/lib/ansible/modules/storage/netapp/na_ontap_job_schedule.py new file mode 100644 index 00000000000..712e9b3b8d3 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_job_schedule.py @@ -0,0 +1,236 @@ +#!/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_job_schedule +short_description: Manage NetApp Ontap Job Schedule +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: +- Archana Ganesan (garchana@netapp.com), Suhas Bangalore Shekar (bsuhas@netapp.com) +description: +- Create/Delete/Modify_minute job-schedules on ONTAP +options: + state: + description: + - Whether the specified job schedule should exist or not. + choices: ['present', 'absent'] + default: present + name: + description: + - The name of the job-schedule to manage. + required: true + job_minutes: + description: + - The minute(s) of each hour when the job should be run. + Job Manager cron scheduling minute. + -1 represents all minutes and + only supported for cron schedule create and modify. + Range is [-1..59] +''' + +EXAMPLES = """ + - name: Create Job + na_ontap_job_schedule: + state: present + name: jobName + job_minutes: jobMinutes + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + - name: Delete Job + na_ontap_job_schedule: + state: present + name: jobName + 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 NetAppONTAPJob(object): + '''Class with job schedule cron 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'), + name=dict(required=True, type='str'), + job_minutes=dict(required=False, type='int'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[ + ('state', 'present', ['name', 'job_minutes']) + ], + supports_check_mode=True + ) + + parameters = self.module.params + + # set up state variables + self.state = parameters['state'] + self.name = parameters['name'] + self.job_minutes = parameters['job_minutes'] + + 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_job_schedule(self): + """ + Return details about the job + :param: + name : Job name + :return: Details about the Job. None if not found. + :rtype: dict + """ + job_get_iter = netapp_utils.zapi.NaElement( + 'job-schedule-cron-get-iter') + job_schedule_info = netapp_utils.zapi.NaElement( + 'job-schedule-cron-info') + job_schedule_info.add_new_child('job-schedule-name', self.name) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(job_schedule_info) + job_get_iter.add_child_elem(query) + result = self.server.invoke_successfully(job_get_iter, True) + return_value = None + # check if job exists + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) >= 1: + job_exists_info = result.get_child_by_name('attributes-list').\ + get_child_by_name('job-schedule-cron-info') + return_value = { + 'name': job_exists_info.get_child_content('job-schedule-name'), + 'job_minutes': job_exists_info.get_child_by_name( + 'job-schedule-cron-minute') + .get_child_content('cron-minute') + } + return return_value + + def create_job_schedule(self): + """ + Creates a job schedule + """ + job_schedule_create = netapp_utils.zapi\ + .NaElement.create_node_with_children( + 'job-schedule-cron-create', + **{'job-schedule-name': self.name}) + job_schedule_create.add_node_with_children( + 'job-schedule-cron-minute', + **{'cron-minute': str(self.job_minutes)}) + try: + self.server.invoke_successfully(job_schedule_create, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error creating job schedule %s: %s' + % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def delete_job_schedule(self): + """ + Delete a job schedule + """ + job_schedule_delete = netapp_utils.zapi\ + .NaElement.create_node_with_children( + 'job-schedule-cron-destroy', + **{'job-schedule-name': self.name}) + try: + self.server.invoke_successfully(job_schedule_delete, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error deleting job schedule %s: %s' + % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def modify_minute_job_schedule(self): + """ + modify a job schedule + """ + job_schedule_modify_minute = netapp_utils.zapi\ + .NaElement.create_node_with_children( + 'job-schedule-cron-modify', + **{'job-schedule-name': self.name}) + job_schedule_modify_minute.add_node_with_children( + 'job-schedule-cron-minute', + **{'cron-minute': str(self.job_minutes)}) + try: + self.server.invoke_successfully(job_schedule_modify_minute, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying job schedule %s: %s' + % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + """ + Apply action to job-schedule + """ + changed = False + job_schedule_exists = 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_job_schedule", cserver) + job_details = self.get_job_schedule() + if job_details: + job_schedule_exists = True + if self.state == 'absent': # delete + changed = True + elif self.state == 'present': # modify + if job_details['job_minutes'] != str(self.job_minutes): + 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 job_schedule_exists: + self.create_job_schedule() + else: # execute modify minute + self.modify_minute_job_schedule() + elif self.state == 'absent': # execute delete + self.delete_job_schedule() + self.module.exit_json(changed=changed) + + +def main(): + '''Execute action''' + job_obj = NetAppONTAPJob() + job_obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_license.py b/lib/ansible/modules/storage/netapp/na_ontap_license.py new file mode 100644 index 00000000000..8549753533e --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_license.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_license + +short_description: Manage NetApp ONTAP protocol and feature licenses +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Sumit Kumar (sumit4@netapp.com), Archana Ganesan (garchana@netapp.com), Suhas Bangalore Shekar (bsuhas@netapp.com) + +description: +- Add or remove licenses on NetApp ONTAP. + +options: + state: + description: + - Whether the specified license should exist or not. + choices: ['present', 'absent'] + default: present + + remove_unused: + description: + - Remove licenses that have no controller affiliation in the cluster. + type: bool + + remove_expired: + description: + - Remove licenses that have expired in the cluster. + type: bool + + serial_number: + description: + Serial number of the node associated with the license. + This parameter is used primarily when removing license for a specific service. + + license_names: + description: + - List of license-names to delete. + suboptions: + base: + description: + - Cluster Base License + nfs: + description: + - NFS License + cifs: + description: + - CIFS License + iscsi: + description: + - iSCSI License + fcp: + description: + - FCP License + cdmi: + description: + - CDMI License + snaprestore: + description: + - SnapRestore License + snapmirror: + description: + - SnapMirror License + flexclone: + description: + - FlexClone License + snapvault: + description: + - SnapVault License + snaplock: + description: + - SnapLock License + snapmanagersuite: + description: + - SnapManagerSuite License + snapprotectapps: + description: + - SnapProtectApp License + v_storageattach: + description: + - Virtual Attached Storage License + + license_codes: + description: + - List of license codes to be added. + +''' + + +EXAMPLES = """ +- name: Add licenses + na_ontap_license: + state: present + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + serial_number: ################# + license_codes: CODE1,CODE2 + +- name: Remove licenses + na_ontap_license: + state: absent + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + remove_unused: false + remove_expired: true + serial_number: ################# + license_names: nfs,cifs +""" + +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() + + +def local_cmp(a, b): + return (a > b) - (a < b) + + +class NetAppOntapLicense(object): + '''ONTAP license class''' + + 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'), + serial_number=dict(required=False, type='str'), + remove_unused=dict(default=None, type='bool'), + remove_expired=dict(default=None, type='bool'), + license_codes=dict(default=None, type='list'), + license_names=dict(default=None, type='list'), + )) + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=False, + required_if=[ + ('state', 'absent', ['serial_number', 'license_names'])] + ) + parameters = self.module.params + # set up state variables + self.state = parameters['state'] + self.serial_number = parameters['serial_number'] + self.remove_unused = parameters['remove_unused'] + self.remove_expired = parameters['remove_expired'] + self.license_codes = parameters['license_codes'] + self.license_names = parameters['license_names'] + + 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_licensing_status(self): + """ + Check licensing status + + :return: package (key) and licensing status (value) + :rtype: dict + """ + license_status = netapp_utils.zapi.NaElement( + 'license-v2-status-list-info') + result = None + try: + result = self.server.invoke_successfully(license_status, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error checking license status: %s" % + to_native(error), exception=traceback.format_exc()) + + return_dictionary = {} + license_v2_status = result.get_child_by_name('license-v2-status') + if license_v2_status: + for license_v2_status_info in license_v2_status.get_children(): + package = license_v2_status_info.get_child_content('package') + status = license_v2_status_info.get_child_content('method') + return_dictionary[package] = status + + return return_dictionary + + def remove_licenses(self, package_name): + """ + Remove requested licenses + :param: + package_name: Name of the license to be deleted + """ + license_delete = netapp_utils.zapi.NaElement('license-v2-delete') + license_delete.add_new_child('serial-number', self.serial_number) + license_delete.add_new_child('package', package_name) + try: + self.server.invoke_successfully(license_delete, + enable_tunneling=False) + return True + except netapp_utils.zapi.NaApiError as error: + # Error 15661 - Object not found + if to_native(error.code) == "15661": + return False + else: + self.module.fail_json(msg="Error removing license %s" % + to_native(error), exception=traceback.format_exc()) + + def remove_unused_licenses(self): + """ + Remove unused licenses + """ + remove_unused = netapp_utils.zapi.NaElement('license-v2-delete-unused') + try: + self.server.invoke_successfully(remove_unused, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error removing unused licenses: %s" % + to_native(error), exception=traceback.format_exc()) + + def remove_expired_licenses(self): + """ + Remove expired licenses + """ + remove_expired = netapp_utils.zapi.NaElement( + 'license-v2-delete-expired') + try: + self.server.invoke_successfully(remove_expired, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error removing expired licenses: %s" % + to_native(error), exception=traceback.format_exc()) + + def add_licenses(self): + """ + Add licenses + """ + license_add = netapp_utils.zapi.NaElement('license-v2-add') + codes = netapp_utils.zapi.NaElement('codes') + for code in self.license_codes: + codes.add_new_child('license-code-v2', str(code.strip().lower())) + license_add.add_child_elem(codes) + try: + self.server.invoke_successfully(license_add, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error adding licenses: %s" % + to_native(error), exception=traceback.format_exc()) + + def apply(self): + '''Call add, delete or modify methods''' + changed = False + create_license = False + remove_license = 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_license", cserver) + # Add / Update licenses. + license_status = self.get_licensing_status() + + if self.state == 'absent': # delete + changed = True + else: # add or update + if self.license_codes is not None: + create_license = True + changed = True + if self.remove_unused is not None: + remove_license = True + changed = True + if self.remove_expired is not None: + remove_license = True + changed = True + if changed: + if self.state == 'present': # execute create + if create_license: + self.add_licenses() + if self.remove_unused is not None: + self.remove_unused_licenses() + if self.remove_expired is not None: + self.remove_expired_licenses() + if create_license or remove_license: + new_license_status = self.get_licensing_status() + if local_cmp(license_status, new_license_status) == 0: + changed = False + else: # execute delete + license_deleted = False + for package in self.license_names: + license_deleted |= self.remove_licenses(package) + changed = license_deleted + + self.module.exit_json(changed=changed) + + +def main(): + '''Apply license operations''' + obj = NetAppOntapLicense() + obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_lun_map.py b/lib/ansible/modules/storage/netapp/na_ontap_lun_map.py new file mode 100644 index 00000000000..bc688703d9c --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_lun_map.py @@ -0,0 +1,226 @@ +#!/usr/bin/python + +""" this is lun mapping module + + (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_lun_map + +short_description: Manage NetApp Ontap lun maps +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: chhaya gunawat (chhayag@netapp.com) + +description: +- Map and unmap luns on NetApp Ontap. + +options: + + state: + description: + - Whether the specified lun should exist or not. + choices: ['present', 'absent'] + default: present + + initiator_group_name: + description: + - Initiator group to map to the given LUN. + required: true + + path: + description: + - Path of the LUN.. + - Required when C(state=present). + + vserver: + required: true + description: + - The name of the vserver to use. + + lun_id: + description: + - LUN ID assigned for the map. + + +""" + +EXAMPLES = """ +- name: Create lun mapping + na_ontap_lun_map: + state: present + initiator_group_name: ansibleIgroup3234 + path: /vol/iscsi_path/iscsi_lun + vserver: ci_dev + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +- name: Unmap Lun + na_ontap_lun_map: + state: absent + initiator_group_name: ansibleIgroup3234 + path: /vol/iscsi_path/iscsi_lun + vserver: ci_dev + 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 NetAppOntapLUNMap(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'), + initiator_group_name=dict(required=True, type='str'), + path=dict(type='str'), + vserver=dict(required=True, type='str'), + lun_id=dict(required=False, type='str', default=None)), + ) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[ + ('state', 'present', ['path']) + ], + supports_check_mode=True + ) + + p = self.module.params + + # set up state variables + self.state = p['state'] + self.initiator_group_name = p['initiator_group_name'] + self.path = p['path'] + self.vserver = p['vserver'] + self.lun_id = p['lun_id'] + + 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_lun_map(self): + """ + Return details about the LUN map + + :return: Details about the lun map + :rtype: dict + """ + lun_info = netapp_utils.zapi.NaElement('lun-map-list-info') + lun_info.add_new_child('path', self.path) + result = self.server.invoke_successfully(lun_info, True) + return_value = None + igroups = result.get_child_by_name('initiator-groups') + if igroups: + for igroup_info in igroups.get_children(): + initiator_group_name = igroup_info.get_child_content('initiator-group-name') + lun_id = igroup_info.get_child_content('lun-id') + if initiator_group_name == self.initiator_group_name: + return_value = { + 'lun_id': lun_id + } + break + + return return_value + + def create_lun_map(self): + """ + Create LUN map + """ + options = {'path': self.path, 'initiator-group': self.initiator_group_name} + if self.lun_id is not None: + options['lun-id'] = self.lun_id + lun_map_create = netapp_utils.zapi.NaElement.create_node_with_children('lun-map', **options) + + try: + self.server.invoke_successfully(lun_map_create, enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error mapping lun %s of initiator_group_name %s: %s" % + (self.path, self.initiator_group_name, to_native(e)), + exception=traceback.format_exc()) + + def delete_lun_map(self): + """ + Unmap LUN map + """ + lun_map_delete = netapp_utils.zapi.NaElement.create_node_with_children('lun-unmap', **{'path': self.path, 'initiator-group': self.initiator_group_name}) + + try: + self.server.invoke_successfully(lun_map_delete, enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg="Error unmapping lun %s of initiator_group_name %s: %s" % + (self.path, self.initiator_group_name, to_native(e)), + exception=traceback.format_exc()) + + def apply(self): + property_changed = False + lun_map_exists = False + netapp_utils.ems_log_event("na_ontap_lun_map", self.server) + lun_map_detail = self.get_lun_map() + + if lun_map_detail: + lun_map_exists = True + + if self.state == 'absent': + property_changed = True + + elif self.state == 'present': + pass + + else: + if self.state == 'present': + property_changed = True + + if property_changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + # TODO delete this line in next release + if not lun_map_exists: + self.create_lun_map() + + elif self.state == 'absent': + self.delete_lun_map() + + changed = property_changed + # TODO: include other details about the lun (size, etc.) + self.module.exit_json(changed=changed) + + +def main(): + v = NetAppOntapLUNMap() + v.apply() + + +if __name__ == '__main__': + main()