diff --git a/lib/ansible/modules/storage/netapp/na_ontap_aggregate.py b/lib/ansible/modules/storage/netapp/na_ontap_aggregate.py new file mode 100644 index 00000000000..db3d39ee683 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_aggregate.py @@ -0,0 +1,359 @@ +#!/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_aggregate + +short_description: Manage NetApp ONTAP aggregates. +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: Sumit Kumar (sumit4@netapp.com), Suhas Bangalore Shekar (bsuhas@netapp.com) + +description: +- Create or destroy aggregates on NetApp cDOT. + +options: + + state: + description: + - Whether the specified aggregate should exist or not. + choices: ['present', 'absent'] + default: 'present' + + service_state: + description: + - Whether the specified aggregate should be enabled or disabled. Creates aggregate if doesnt exist. + choices: ['online', 'offline'] + + name: + required: true + description: + - The name of the aggregate to manage. + + rename: + description: + - The name of the aggregate that replaces the current name. + + nodes: + description: + - List of node for the aggregate + + disk_count: + description: + - Number of disks to place into the aggregate, including parity disks. + - The disks in this newly-created aggregate come from the spare disk pool. + - The smallest disks in this pool join the aggregate first, unless the C(disk-size) argument is provided. + - Either C(disk-count) or C(disks) must be supplied. Range [0..2^31-1]. + - Required when C(state=present). + + unmount_volumes: + type: bool + description: + - If set to "TRUE", this option specifies that all of the volumes hosted by the given aggregate are to be unmounted + - before the offline operation is executed. + - By default, the system will reject any attempt to offline an aggregate that hosts one or more online volumes. + +''' + +EXAMPLES = """ +- name: Create Aggregates + na_ontap_aggregate: + state: present + service_state: online + name: ansibleAggr + disk_count: 1 + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +- name: Manage Aggregates + na_ontap_aggregate: + state: present + service_state: offline + unmount_volumes: true + name: ansibleAggr + disk_count: 1 + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +- name: Rename Aggregates + na_ontap_aggregate: + state: present + service_state: online + name: ansibleAggr + rename: ansibleAggr2 + disk_count: 1 + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + +- name: Delete Aggregates + na_ontap_aggregate: + state: absent + service_state: offline + unmount_volumes: true + name: ansibleAggr + 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 NetAppOntapAggregate(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'), + service_state=dict(required=False, choices=['online', 'offline']), + name=dict(required=True, type='str'), + rename=dict(required=False, type='str'), + disk_count=dict(required=False, type='int', default=None), + nodes=dict(required=False, type='list'), + unmount_volumes=dict(required=False, type='bool'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[ + ('service_state', 'offline', ['unmount_volumes']) + ], + supports_check_mode=True + ) + + parameters = self.module.params + + # set up state variables + self.state = parameters['state'] + self.service_state = parameters['service_state'] + self.name = parameters['name'] + self.rename = parameters['rename'] + self.disk_count = parameters['disk_count'] + self.nodes = parameters['nodes'] + self.unmount_volumes = parameters['unmount_volumes'] + + 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_aggr(self): + """ + Checks if aggregate exists. + + :return: + True if aggregate found + False if aggregate is not found + :rtype: bool + """ + + aggr_get_iter = netapp_utils.zapi.NaElement('aggr-get-iter') + query_details = netapp_utils.zapi.NaElement.create_node_with_children( + 'aggr-attributes', **{'aggregate-name': self.name}) + + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(query_details) + aggr_get_iter.add_child_elem(query) + + try: + result = self.server.invoke_successfully(aggr_get_iter, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + # Error 13040 denotes an aggregate not being found. + if to_native(error.code) == "13040": + return False + else: + self.module.fail_json(msg=to_native( + error), exception=traceback.format_exc()) + + if (result.get_child_by_name('num-records') and + int(result.get_child_content('num-records')) >= 1): + return True + return False + + def aggregate_online(self): + """ + enable aggregate (online). + """ + online_aggr = netapp_utils.zapi.NaElement.create_node_with_children( + 'aggr-online', **{'aggregate': self.name, + 'force-online': 'true'}) + try: + self.server.invoke_successfully(online_aggr, + enable_tunneling=True) + return True + except netapp_utils.zapi.NaApiError as error: + if to_native(error.code) == "13060": + # Error 13060 denotes aggregate is already online + return False + else: + self.module.fail_json(msg='Error changing the state of aggregate %s to %s: %s' % + (self.name, self.service_state, + to_native(error)), + exception=traceback.format_exc()) + + def aggregate_offline(self): + """ + disable aggregate (offline). + """ + offline_aggr = netapp_utils.zapi.NaElement.create_node_with_children( + 'aggr-offline', **{'aggregate': self.name, + 'force-offline': 'false', + 'unmount-volumes': str(self.unmount_volumes)}) + try: + self.server.invoke_successfully(offline_aggr, + enable_tunneling=True) + return True + except netapp_utils.zapi.NaApiError as error: + if to_native(error.code) == "13042": + # Error 13042 denotes aggregate is already offline + return False + else: + self.module.fail_json(msg='Error changing the state of aggregate %s to %s: %s' % + (self.name, self.service_state, + to_native(error)), + exception=traceback.format_exc()) + + def create_aggr(self): + """ + create aggregate. + """ + aggr_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'aggr-create', **{'aggregate': self.name, + 'disk-count': str(self.disk_count)}) + if self.nodes is not None: + nodes_obj = netapp_utils.zapi.NaElement('nodes') + aggr_create.add_child_elem(nodes_obj) + for node in self.nodes: + nodes_obj.add_new_child('node-name', node) + try: + self.server.invoke_successfully(aggr_create, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error provisioning aggregate %s: %s" % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def delete_aggr(self): + """ + delete aggregate. + """ + aggr_destroy = netapp_utils.zapi.NaElement.create_node_with_children( + 'aggr-destroy', **{'aggregate': self.name}) + + try: + self.server.invoke_successfully(aggr_destroy, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error removing aggregate %s: %s" % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def rename_aggregate(self): + """ + rename aggregate. + """ + aggr_rename = netapp_utils.zapi.NaElement.create_node_with_children( + 'aggr-rename', **{'aggregate': self.name, + 'new-aggregate-name': + self.rename}) + + try: + self.server.invoke_successfully(aggr_rename, + enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error renaming aggregate %s: %s" % (self.name, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + '''Apply action to aggregate''' + changed = False + size_changed = False + aggregate_exists = self.get_aggr() + rename_aggregate = 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_aggregate", cserver) + + # check if anything needs to be changed (add/delete/update) + + if aggregate_exists: + if self.state == 'absent': + changed = True + + elif self.state == 'present': + if self.service_state: + changed = True + if self.rename is not None and self.name != \ + self.rename: + rename_aggregate = True + changed = True + + else: + if self.state == 'present': + # Aggregate does not exist, but requested state is present. + if (self.rename is None) and self.disk_count: + changed = True + + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if not aggregate_exists: + self.create_aggr() + if self.service_state == 'offline': + self.aggregate_offline() + else: + if self.service_state == 'online': + size_changed = self.aggregate_online() + elif self.service_state == 'offline': + size_changed = self.aggregate_offline() + if rename_aggregate: + self.rename_aggregate() + if not size_changed and not rename_aggregate: + changed = False + + elif self.state == 'absent': + if self.service_state == 'offline': + self.aggregate_offline() + self.delete_aggr() + self.module.exit_json(changed=changed) + + +def main(): + ''' Create object and call apply ''' + obj_aggr = NetAppOntapAggregate() + obj_aggr.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain_ports.py b/lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain_ports.py new file mode 100644 index 00000000000..d55b2ece595 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain_ports.py @@ -0,0 +1,234 @@ +#!/usr/bin/python + +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +module: na_ontap_broadcast_domain_ports +short_description: Manage NetApp Ontap broadcast domain ports +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: +- Chris Archibald (carchi@netapp.com), Kevin Hutton (khutton@netapp.com), Suhas Bangalore Shekar (bsuhas@netapp.com) +description: +- Modify Ontap broadcast domain ports +options: + state: + description: + - Whether the specified broadcast domain should exist or not. + choices: ['present', 'absent'] + default: present + vserver: + description: + - The name of the vserver + required: true + broadcast_domain: + description: + - Specify the broadcast_domain name + required: true + ipspace: + description: + - Specify the ipspace for the broadcast domain + ports: + description: + - Specify the list of ports associated with this broadcast domain. + +''' + +EXAMPLES = """ + - name: create broadcast domain ports + na_ontap_broadcast_domain_ports: + state=present + vserver={{ Vserver name }} + username={{ netapp_username }} + password={{ netapp_password }} + hostname={{ netapp_hostname }} + broadcast_domain=123kevin + ports=khutton-vsim1:e0d-13 + - name: delete broadcast domain ports + na_ontap_broadcast_domain_ports: + state=absent + vserver={{ Vserver name }} + username={{ netapp_username }} + password={{ netapp_password }} + hostname={{ netapp_hostname }} + broadcast_domain=123kevin + ports=khutton-vsim1:e0d-13 +""" + +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 NetAppOntapBroadcastDomainPorts(object): + """ + Create and Destroys Broadcast Domain Ports + """ + def __init__(self): + """ + Initialize the Ontap Net Route class + """ + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, choices=['present', 'absent'], default='present'), + vserver=dict(required=False, type='str'), + broadcast_domain=dict(required=True, type='str'), + ipspace=dict(required=False, type='str', default=None), + ports=dict(required=True, type='list'), + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + parameters = self.module.params + + # set up state variables + self.state = parameters['state'] + self.vserver = parameters['vserver'] + self.broadcast_domain = parameters['broadcast_domain'] + self.ipspace = parameters['ipspace'] + self.ports = parameters['ports'] + + if HAS_NETAPP_LIB is False: + self.module.fail_json(msg="the python NetApp-Lib module is required") + else: + self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) + return + + def get_broadcast_domain_ports(self): + """ + Return details about the broadcast domain ports + :param: + name : broadcast domain name + :return: Details about the broadcast domain. None if not found. + :rtype: dict + """ + domain_get_iter = netapp_utils.zapi.NaElement('net-port-broadcast-domain-get-iter') + broadcast_domain_info = netapp_utils.zapi.NaElement('net-port-broadcast-domain-info') + broadcast_domain_info.add_new_child('broadcast-domain', self.broadcast_domain) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(broadcast_domain_info) + domain_get_iter.add_child_elem(query) + result = self.server.invoke_successfully(domain_get_iter, True) + domain_exists = None + # check if broadcast domain exists + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) == 1: + domain_info = result.get_child_by_name('attributes-list').get_child_by_name('net-port-broadcast-domain-info') + domain_name = domain_info.get_child_content('broadcast-domain') + domain_ports = domain_info.get_child_content('port-info') + domain_exists = { + 'domain-name': domain_name, + 'ports': domain_ports + } + return domain_exists + + def create_broadcast_domain_ports(self): + """ + Creates new broadcast domain ports + """ + domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-add-ports') + domain_obj.add_new_child("broadcast-domain", self.broadcast_domain) + if self.ipspace: + domain_obj.add_new_child("ipspace", self.ipspace) + if self.ports: + ports_obj = netapp_utils.zapi.NaElement('ports') + domain_obj.add_child_elem(ports_obj) + for port in self.ports: + ports_obj.add_new_child('net-qualified-port-name', port) + try: + self.server.invoke_successfully(domain_obj, True) + return True + except netapp_utils.zapi.NaApiError as error: + # Error 18605 denotes port already being used. + if to_native(error.code) == "18605": + return False + else: + self.module.fail_json(msg='Error creating port for broadcast domain %s: %s' % + (self.broadcast_domain, to_native(error)), + exception=traceback.format_exc()) + + def delete_broadcast_domain_ports(self): + """ + Deletes broadcast domain ports + """ + domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-remove-ports') + domain_obj.add_new_child("broadcast-domain", self.broadcast_domain) + if self.ipspace: + domain_obj.add_new_child("ipspace", self.ipspace) + if self.ports: + ports_obj = netapp_utils.zapi.NaElement('ports') + domain_obj.add_child_elem(ports_obj) + for port in self.ports: + ports_obj.add_new_child('net-qualified-port-name', port) + try: + self.server.invoke_successfully(domain_obj, True) + return True + except netapp_utils.zapi.NaApiError as error: + # Error 13001 denotes port already not being used. + if to_native(error.code) == "13001": + return False + else: + self.module.fail_json(msg='Error deleting port for broadcast domain %s: %s' % + (self.broadcast_domain, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + """ + Run Module based on play book + """ + changed = False + broadcast_domain_details = self.get_broadcast_domain_ports() + broadcast_domain_exists = False + results = netapp_utils.get_cserver(self.server) + cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) + netapp_utils.ems_log_event("na_ontap_broadcast_domain_ports", cserver) + if broadcast_domain_details: + broadcast_domain_exists = True + if self.state == 'absent': # delete + changed = True + elif self.state == 'present': # create + changed = True + else: + pass + if changed: + if self.module.check_mode: + pass + else: + if broadcast_domain_exists: + if self.state == 'present': # execute create + changed = self.create_broadcast_domain_ports() + elif self.state == 'absent': # execute delete + changed = self.delete_broadcast_domain_ports() + self.module.exit_json(changed=changed) + + +def main(): + """ + Creates the NetApp Ontap Net Route object and runs the correct play task + """ + obj = NetAppOntapBroadcastDomainPorts() + obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_cifs.py b/lib/ansible/modules/storage/netapp/na_ontap_cifs.py new file mode 100644 index 00000000000..159e9f79ee2 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_cifs.py @@ -0,0 +1,248 @@ +#!/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) +# import untangle + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +author: "Archana Ganesan (garchana@netapp.com), Suhas Bangalore Shekar (bsuhas@netapp.com)" +description: + - "Create or destroy or modify(path) cifs-share on ONTAP" +extends_documentation_fragment: + - netapp.na_ontap +module: na_ontap_cifs +options: + path: + description: + The file system path that is shared through this CIFS share. The path is the full, user visible path relative + to the vserver root, and it might be crossing junction mount points. The path is in UTF8 and uses forward + slash as directory separator + required: false + vserver: + description: + - "Vserver containing the CIFS share." + required: true + share_name: + description: + The name of the CIFS share. The CIFS share name is a UTF-8 string with the following characters being + illegal; control characters from 0x00 to 0x1F, both inclusive, 0x22 (double quotes) + required: true + state: + choices: ['present', 'absent'] + description: + - "Whether the specified CIFS share should exist or not." + required: false + default: present +short_description: "Manage NetApp cifs-share" +version_added: "2.6" + +''' + +EXAMPLES = """ + - name: Create CIFS share + na_ontap_cifs: + state: present + share_name: cifsShareName + path: / + vserver: vserverName + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + - name: Delete CIFS share + na_ontap_cifs: + state: absent + share_name: cifsShareName + vserver: vserverName + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + - name: Modify path CIFS share + na_ontap_cifs: + state: present + share_name: pb_test + vserver: vserverName + path: / + 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 NetAppONTAPCifsShare(object): + """ + Methods to create/delete/modify(path) CIFS share + """ + + 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'), + share_name=dict(required=True, type='str'), + path=dict(required=False, type='str'), + vserver=dict(required=True, type='str') + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[ + ('state', 'present', ['share_name', 'path']) + ], + supports_check_mode=True + ) + + parameters = self.module.params + + # set up state variables + self.state = parameters['state'] + self.share_name = parameters['share_name'] + self.path = parameters['path'] + self.vserver = parameters['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_cifs_share(self): + """ + Return details about the cifs-share + :param: + name : Name of the cifs-share + :return: Details about the cifs-share. None if not found. + :rtype: dict + """ + cifs_iter = netapp_utils.zapi.NaElement('cifs-share-get-iter') + cifs_info = netapp_utils.zapi.NaElement('cifs-share') + cifs_info.add_new_child('share-name', self.share_name) + + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(cifs_info) + + cifs_iter.add_child_elem(query) + + result = self.server.invoke_successfully(cifs_iter, True) + + return_value = None + print(result.to_string()) + # check if query returns the expected cifs-share + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) == 1: + + cifs_acl = result.get_child_by_name('attributes-list').\ + get_child_by_name('cifs-share') + return_value = { + 'share': cifs_acl.get_child_content('share-name'), + 'path': cifs_acl.get_child_content('path'), + } + + return return_value + + def create_cifs_share(self): + """ + Create CIFS share + """ + cifs_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-share-create', **{'share-name': self.share_name, + 'path': self.path}) + + try: + self.server.invoke_successfully(cifs_create, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + + self.module.fail_json(msg='Error creating cifs-share %s: %s' + % (self.share_name, to_native(error)), + exception=traceback.format_exc()) + + def delete_cifs_share(self): + """ + Delete CIFS share + """ + cifs_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-share-delete', **{'share-name': self.share_name}) + + try: + self.server.invoke_successfully(cifs_delete, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error deleting cifs-share %s: %s' + % (self.share_name, to_native(error)), + exception=traceback.format_exc()) + + def modify_cifs_share(self): + """ + modilfy path for the given CIFS share + """ + cifs_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-share-modify', **{'share-name': self.share_name, + 'path': self.path}) + try: + self.server.invoke_successfully(cifs_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying cifs-share %s:%s' + % (self.share_name, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + '''Apply action to cifs share''' + changed = False + cifs_exists = False + netapp_utils.ems_log_event("na_ontap_cifs", self.server) + cifs_details = self.get_cifs_share() + if cifs_details: + cifs_exists = True + if self.state == 'absent': # delete + changed = True + elif self.state == 'present': + if cifs_details['path'] != self.path: # modify path + 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 cifs_exists: + self.create_cifs_share() + else: # execute modify path + self.modify_cifs_share() + elif self.state == 'absent': # execute delete + self.delete_cifs_share() + + self.module.exit_json(changed=changed) + + +def main(): + '''Execute action from playbook''' + cifs_obj = NetAppONTAPCifsShare() + cifs_obj.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_cifs_acl.py b/lib/ansible/modules/storage/netapp/na_ontap_cifs_acl.py new file mode 100644 index 00000000000..b1ecfdc849f --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_cifs_acl.py @@ -0,0 +1,234 @@ +#!/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 = ''' +author: "Archana Ganesan (garchana@netapp.com), Suhas Bangalore Shekar (bsuhas@netapp.com)" +description: + - "Create or destroy or modify cifs-share-access-controls on ONTAP" +extends_documentation_fragment: + - netapp.na_ontap +module: na_ontap_cifs_acl +options: + permission: + choices: ['no_access', 'read', 'change', 'full_control'] + description: + -"The access rights that the user or group has on the defined CIFS share." + share_name: + description: + - "The name of the cifs-share-access-control to manage." + required: true + state: + choices: ['present', 'absent'] + description: + - "Whether the specified CIFS share acl should exist or not." + default: present + vserver: + description: + - Name of the vserver to use. + required: true + user_or_group: + description: + - "The user or group name for which the permissions are listed." + required: true +short_description: "Manage NetApp cifs-share-access-control" +version_added: "2.6" + +''' + +EXAMPLES = """ + - name: Create CIFS share acl + na_ontap_cifs_acl: + state: present + share_name: cifsShareName + user_or_group: Everyone + permission: read + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + - name: Modify CIFS share acl permission + na_ontap_cifs_acl: + state: present + share_name: cifsShareName + permission: change + 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 NetAppONTAPCifsAcl(object): + """ + Methods to create/delete/modify CIFS share/user access-control + """ + + 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'), + vserver=dict(required=True, type='str'), + share_name=dict(required=True, type='str'), + user_or_group=dict(required=True, type='str'), + permission=dict(required=False, type='str', choices=['no_access', 'read', 'change', 'full_control']) + )) + self.module = AnsibleModule( + argument_spec=self.argument_spec, + required_if=[ + ('state', 'present', ['share_name', 'user_or_group', 'permission']) + ], + supports_check_mode=True + ) + parameters = self.module.params + # set up state variables + self.state = parameters['state'] + self.vserver = parameters['vserver'] + self.share_name = parameters['share_name'] + self.user_or_group = parameters['user_or_group'] + self.permission = parameters['permission'] + + 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_cifs_acl(self): + """ + Return details about the cifs-share-access-control + :param: + name : Name of the cifs-share-access-control + :return: Details about the cifs-share-access-control. None if not found. + :rtype: dict + """ + cifs_acl_iter = netapp_utils.zapi.NaElement('cifs-share-access-control-get-iter') + cifs_acl_info = netapp_utils.zapi.NaElement('cifs-share-access-control') + cifs_acl_info.add_new_child('share', self.share_name) + cifs_acl_info.add_new_child('user-or-group', self.user_or_group) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(cifs_acl_info) + cifs_acl_iter.add_child_elem(query) + result = self.server.invoke_successfully(cifs_acl_iter, True) + return_value = None + # check if query returns the expected cifs-share-access-control + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) == 1: + + cifs_acl = result.get_child_by_name('attributes-list').get_child_by_name('cifs-share-access-control') + return_value = { + 'share': cifs_acl.get_child_content('share'), + 'user-or-group': cifs_acl.get_child_content('user-or-group'), + 'permission': cifs_acl.get_child_content('permission') + } + + return return_value + + def create_cifs_acl(self): + """ + Create access control for the given CIFS share/user-group + """ + cifs_acl_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-share-access-control-create', **{'share': self.share_name, + 'user-or-group': self.user_or_group, + 'permission': self.permission}) + try: + self.server.invoke_successfully(cifs_acl_create, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + + self.module.fail_json(msg='Error creating cifs-share-access-control %s: %s' + % (self.share_name, to_native(error)), + exception=traceback.format_exc()) + + def delete_cifs_acl(self): + """ + Delete access control for the given CIFS share/user-group + """ + cifs_acl_delete = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-share-access-control-delete', **{'share': self.share_name, + 'user-or-group': self.user_or_group}) + try: + self.server.invoke_successfully(cifs_acl_delete, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error deleting cifs-share-access-control %s: %s' + % (self.share_name, to_native(error)), + exception=traceback.format_exc()) + + def modify_cifs_acl_permission(self): + """ + Change permission for the given CIFS share/user-group + """ + cifs_acl_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-share-access-control-modify', **{'share': self.share_name, + 'user-or-group': self.user_or_group, + 'permission': self.permission}) + try: + self.server.invoke_successfully(cifs_acl_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error modifying cifs-share-access-control permission %s:%s' + % (self.share_name, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + """ + Apply action to cifs-share-access-control + """ + changed = False + cifs_acl_exists = False + netapp_utils.ems_log_event("na_ontap_cifs_acl", self.server) + cifs_acl_details = self.get_cifs_acl() + if cifs_acl_details: + cifs_acl_exists = True + if self.state == 'absent': # delete + changed = True + elif self.state == 'present': + if cifs_acl_details['permission'] != self.permission: # rename + 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 cifs_acl_exists: + self.create_cifs_acl() + else: # execute modify + self.modify_cifs_acl_permission() + elif self.state == 'absent': # execute delete + self.delete_cifs_acl() + + self.module.exit_json(changed=changed) + + +def main(): + """ + Execute action from playbook + """ + cifs_acl = NetAppONTAPCifsAcl() + cifs_acl.apply() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/storage/netapp/na_ontap_cifs_server.py b/lib/ansible/modules/storage/netapp/na_ontap_cifs_server.py new file mode 100644 index 00000000000..46fe88335d6 --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_cifs_server.py @@ -0,0 +1,307 @@ +#!/usr/bin/python +""" this is cifs_server 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_cifs_server +short_description: cifs server configuration +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.6' +author: chhaya gunawat (chhayag@netapp.com) + +description: + - Creating / deleting and modifying the CIF server . + +options: + + state: + description: + - Whether the specified cifs_server should exist or not. + default: present + choices: ['present', 'absent'] + + service_state: + description: + - CIFS Server Administrative Status. + choices: ['stopped', 'started'] + + cifs_server_name: + description: + - Specifies the cifs_server name. + required: true + + admin_user_name: + description: + - Specifies the cifs server admin username. + + admin_password: + description: + - Specifies the cifs server admin password. + + domain: + description: + - The Fully Qualified Domain Name of the Windows Active Directory this CIFS server belongs to. + + workgroup: + description: + - The NetBIOS name of the domain or workgroup this CIFS server belongs to. + + + vserver: + description: + - The name of the vserver to use. + required: true + +''' + +EXAMPLES = ''' + - name: Create cifs_server + na_ontap_cifs_server: + state: present + vserver: svm1 + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + + - name: Delete cifs_server + na_ontap_cifs_server: + state: absent + cifs_server_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 NetAppOntapcifsServer(object): + """ + object to describe cifs_server 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'), + service_state=dict(required=False, choices=['stopped', 'started']), + cifs_server_name=dict(required=False, type='str'), + workgroup=dict(required=False, type='str', default=None), + domain=dict(required=False, type='str'), + admin_user_name=dict(required=False, type='str'), + admin_password=dict(required=False, type='str'), + + 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.cifs_server_name = params['cifs_server_name'] + self.workgroup = params['workgroup'] + self.domain = params['domain'] + self.vserver = params['vserver'] + self.service_state = params['service_state'] + self.admin_user_name = params['admin_user_name'] + self.admin_password = params['admin_password'] + + 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_cifs_server(self): + """ + Return details about the CIFS-server + :param: + name : Name of the name of the cifs_server + + :return: Details about the cifs_server. None if not found. + :rtype: dict + """ + cifs_server_info = netapp_utils.zapi.NaElement('cifs-server-get-iter') + cifs_server_attributes = netapp_utils.zapi.NaElement('cifs-server-config') + cifs_server_attributes.add_new_child('cifs-server', self.cifs_server_name) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(cifs_server_attributes) + cifs_server_info.add_child_elem(query) + result = self.server.invoke_successfully(cifs_server_info, True) + return_value = None + + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) >= 1: + + cifs_server_attributes = result.get_child_by_name('attributes-list').\ + get_child_by_name('cifs-server-config') + return_value = { + 'cifs_server_name': self.cifs_server_name, + 'administrative-status': cifs_server_attributes.get_child_content('administrative-status') + } + + return return_value + + def create_cifs_server(self): + """ + calling zapi to create cifs_server + """ + options = {'cifs-server': self.cifs_server_name, 'administrative-status': 'up' + if self.service_state == 'started' else 'down'} + if self.workgroup is not None: + options['workgroup'] = self.workgroup + if self.domain is not None: + options['domain'] = self.domain + if self.admin_user_name is not None: + options['admin-username'] = self.admin_user_name + if self.admin_password is not None: + options['admin-password'] = self.admin_password + + cifs_server_create = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-server-create', **options) + + try: + self.server.invoke_successfully(cifs_server_create, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as exc: + self.module.fail_json(msg='Error Creating cifs_server %s: %s' % + (self.cifs_server_name, to_native(exc)), exception=traceback.format_exc()) + + def delete_cifs_server(self): + """ + calling zapi to create cifs_server + """ + if self.cifs_server_name == 'up': + self.modify_cifs_server(admin_status='down') + + cifs_server_delete = netapp_utils.zapi.NaElement.create_node_with_children('cifs-server-delete') + + try: + self.server.invoke_successfully(cifs_server_delete, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as exc: + self.module.fail_json(msg='Error deleting cifs_server %s: %s' % (self.cifs_server_name, to_native(exc)), + exception=traceback.format_exc()) + + def modify_cifs_server(self, admin_status): + """ + RModify the cifs_server. + """ + cifs_server_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-server-modify', **{'cifs-server': self.cifs_server_name, + 'administrative-status': admin_status, 'vserver': self.vserver}) + try: + self.server.invoke_successfully(cifs_server_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error modifying cifs_server %s: %s' % (self.cifs_server_name, to_native(e)), + exception=traceback.format_exc()) + + def start_cifs_server(self): + """ + RModify the cifs_server. + """ + cifs_server_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-server-start') + try: + self.server.invoke_successfully(cifs_server_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error modifying cifs_server %s: %s' % (self.cifs_server_name, to_native(e)), + exception=traceback.format_exc()) + + def stop_cifs_server(self): + """ + RModify the cifs_server. + """ + cifs_server_modify = netapp_utils.zapi.NaElement.create_node_with_children( + 'cifs-server-stop') + try: + self.server.invoke_successfully(cifs_server_modify, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as e: + self.module.fail_json(msg='Error modifying cifs_server %s: %s' % (self.cifs_server_name, to_native(e)), + exception=traceback.format_exc()) + + def apply(self): + """ + calling all cifs_server features + """ + + changed = False + cifs_server_exists = False + netapp_utils.ems_log_event("na_ontap_cifs_server", self.server) + cifs_server_detail = self.get_cifs_server() + + if cifs_server_detail: + cifs_server_exists = True + + if self.state == 'present': + administrative_status = cifs_server_detail['administrative-status'] + if self.service_state == 'started' and administrative_status == 'down': + changed = True + if self.service_state == 'stopped' and administrative_status == 'up': + changed = True + else: + # we will delete the CIFs server + changed = True + else: + if self.state == 'present': + changed = True + + if changed: + if self.module.check_mode: + pass + else: + if self.state == 'present': + if not cifs_server_exists: + self.create_cifs_server() + + elif self.service_state == 'stopped': + self.stop_cifs_server() + + elif self.service_state == 'started': + self.start_cifs_server() + + elif self.state == 'absent': + self.delete_cifs_server() + + self.module.exit_json(changed=changed) + + +def main(): + cifs_server = NetAppOntapcifsServer() + cifs_server.apply() + + +if __name__ == '__main__': + main()