From ca7490b61ba3b943f414b2572ad6bfac1928f49e Mon Sep 17 00:00:00 2001
From: Chris Archibald <carchi@netapp.com>
Date: Thu, 24 May 2018 12:56:51 -0700
Subject: [PATCH] third set of netapp modules (#40488)

* thrid set of netapp modules

* fix issues

* fix issues

* fixed cmp issues

* fix 2 spaces

* fixes

* Change to documentation

* fixes
---
 .../storage/netapp/na_ontap_interface.py      | 364 ++++++++++++++++++
 .../modules/storage/netapp/na_ontap_iscsi.py  | 273 +++++++++++++
 .../storage/netapp/na_ontap_job_schedule.py   | 236 ++++++++++++
 .../storage/netapp/na_ontap_license.py        | 319 +++++++++++++++
 .../storage/netapp/na_ontap_lun_map.py        | 226 +++++++++++
 5 files changed, 1418 insertions(+)
 create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_interface.py
 create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_iscsi.py
 create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_job_schedule.py
 create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_license.py
 create mode 100644 lib/ansible/modules/storage/netapp/na_ontap_lun_map.py

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()