Server IP : 85.214.239.14 / Your IP : 3.144.98.43 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /lib/python3/dist-packages/ansible_collections/netapp/ontap/plugins/modules/ |
Upload File : |
#!/usr/bin/python # (c) 2018-2022, 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 DOCUMENTATION = ''' short_description: NetApp ONTAP Create/Delete portset author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> description: - Create/Delete ONTAP portset, modify ports in a portset. - Modify type(protocol) is not supported in ONTAP. extends_documentation_fragment: - netapp.ontap.netapp.na_ontap module: na_ontap_portset options: state: description: - If you want to create a portset. default: present type: str vserver: required: true description: - Name of the SVM. type: str name: required: true description: - Name of the port set to create. type: str type: description: - Required for create in ZAPI. - Default value is mixed if not specified at the time of creation in REST. - Protocols accepted for this portset. choices: ['fcp', 'iscsi', 'mixed'] type: str force: description: - If 'false' or not specified, the request will fail if there are any igroups bound to this portset. - If 'true', forcibly destroy the portset, even if there are existing igroup bindings. type: bool default: False ports: description: - Specify the ports associated with this portset. Should be comma separated. - It represents the expected state of a list of ports at any time, and replaces the current value of ports. - Adds a port if it is specified in expected state but not in current state. - Deletes a port if it is in current state but not in expected state. type: list elements: str version_added: 2.8.0 ''' EXAMPLES = """ - name: Create Portset netapp.ontap.na_ontap_portset: state: present vserver: vserver_name name: portset_name ports: a1 type: "{{ protocol type }}" username: "{{ netapp username }}" password: "{{ netapp password }}" hostname: "{{ netapp hostname }}" - name: Modify ports in portset netapp.ontap.na_ontap_portset: state: present vserver: vserver_name name: portset_name ports: a1,a2 username: "{{ netapp username }}" password: "{{ netapp password }}" hostname: "{{ netapp hostname }}" - name: Delete Portset netapp.ontap.na_ontap_portset: state: absent vserver: vserver_name name: portset_name force: True type: "{{ protocol type }}" username: "{{ netapp username }}" password: "{{ netapp password }}" hostname: "{{ netapp hostname }}" """ RETURN = """ """ import traceback from ansible.module_utils.basic import AnsibleModule from ansible.module_utils._text import to_native import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic class NetAppONTAPPortset: """ Methods to create or delete portset """ def __init__(self): self.argument_spec = netapp_utils.na_ontap_host_argument_spec() self.argument_spec.update(dict( state=dict(required=False, type='str', default='present'), vserver=dict(required=True, type='str'), name=dict(required=True, type='str'), type=dict(required=False, type='str', choices=[ 'fcp', 'iscsi', 'mixed']), force=dict(required=False, type='bool', default=False), ports=dict(required=False, type='list', elements='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if 'ports' in self.parameters: self.parameters['ports'] = list(set([port.strip() for port in self.parameters['ports']])) if '' in self.parameters['ports'] and self.parameters['state'] == 'present': self.module.fail_json(msg="Error: invalid value specified for ports") # Setup REST API. self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() self.uuid, self.lifs_info = None, {} if self.use_rest and not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1): msg = 'REST requires ONTAP 9.9.1 or later for portset APIs.' if self.parameters['use_rest'].lower() == 'always': self.module.fail_json(msg='Error: %s' % msg) if self.parameters['use_rest'].lower() == 'auto': self.module.warn('Falling back to ZAPI: %s' % msg) self.use_rest = False if not self.use_rest: if not netapp_utils.has_netapp_lib(): self.module.fail_json(msg=netapp_utils.netapp_lib_is_required()) self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) def portset_get_iter(self): """ Compose NaElement object to query current portset using vserver, portset-name and portset-type parameters :return: NaElement object for portset-get-iter with query """ portset_get = netapp_utils.zapi.NaElement('portset-get-iter') query = netapp_utils.zapi.NaElement('query') portset_info = netapp_utils.zapi.NaElement('portset-info') portset_info.add_new_child('vserver', self.parameters['vserver']) portset_info.add_new_child('portset-name', self.parameters['name']) query.add_child_elem(portset_info) portset_get.add_child_elem(query) return portset_get def portset_get(self): """ Get current portset info :return: Dictionary of current portset details if query successful, else return None """ if self.use_rest: return self.portset_get_rest() portset_get_iter = self.portset_get_iter() result, portset_info = None, dict() try: result = self.server.invoke_successfully(portset_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching portset %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) # return portset details if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0: portset_get_info = result.get_child_by_name('attributes-list').get_child_by_name('portset-info') portset_info['type'] = portset_get_info.get_child_content('portset-type') if int(portset_get_info.get_child_content('portset-port-total')) > 0: ports = portset_get_info.get_child_by_name('portset-port-info') portset_info['ports'] = [port.get_content() for port in ports.get_children()] else: portset_info['ports'] = [] return portset_info return None def create_portset(self): """ Create a portset """ if self.use_rest: return self.create_portset_rest() if self.parameters.get('type') is None: self.module.fail_json(msg='Error: Missing required parameter for create (type)') portset_info = netapp_utils.zapi.NaElement("portset-create") portset_info.add_new_child("portset-name", self.parameters['name']) portset_info.add_new_child("portset-type", self.parameters['type']) try: self.server.invoke_successfully( portset_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error creating portset %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def delete_portset(self): """ Delete a portset """ if self.use_rest: return self.delete_portset_rest() portset_info = netapp_utils.zapi.NaElement("portset-destroy") portset_info.add_new_child("portset-name", self.parameters['name']) if self.parameters.get('force'): portset_info.add_new_child("force", str(self.parameters['force'])) try: self.server.invoke_successfully( portset_info, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg="Error deleting portset %s: %s" % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def remove_ports(self, ports): """ Removes all existing ports from portset :return: None """ for port in ports: self.modify_port(port, 'portset-remove', 'removing') def add_ports(self, ports=None): """ Add the list of ports to portset :return: None """ if ports is None: ports = self.parameters.get('ports') # don't add if ports is None if ports is None: return for port in ports: self.modify_port(port, 'portset-add', 'adding') def modify_port(self, port, zapi, action): """ Add or remove an port to/from a portset """ options = {'portset-name': self.parameters['name'], 'portset-port-name': port} portset_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(portset_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error %s port in portset %s: %s' % (action, self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def portset_get_rest(self): api = "protocols/san/portsets" query = {'name': self.parameters['name'], 'svm.name': self.parameters['vserver']} fields = 'uuid,protocol,interfaces' record, error = rest_generic.get_one_record(self.rest_api, api, query, fields) if error: self.module.fail_json(msg='Error fetching portset %s: %s' % (self.parameters['name'], to_native(error))) portset_info = None if record: portset_info = self.form_portset_info(record) return portset_info def form_portset_info(self, record): self.uuid = record['uuid'] # if type is not set, assign current type # for avoiding incompatible network interface error in modify portset. if self.parameters.get('type') is None: self.parameters['type'] = record['protocol'] portset_info = { 'type': record['protocol'], 'ports': [] } if 'interfaces' in record: for lif in record['interfaces']: for key, value in lif.items(): if key in ['fc', 'ip']: # add current lifs type and uuid to self.lifs for modify and delete purpose. self.lifs_info[value['name']] = {'lif_type': key, 'uuid': value['uuid']} # This will form ports list for fcp, iscsi and mixed protocols. portset_info['ports'].append(value['name']) return portset_info def create_portset_rest(self): api = "protocols/san/portsets" body = {'name': self.parameters['name'], 'svm.name': self.parameters['vserver']} if 'type' in self.parameters: body['protocol'] = self.parameters['type'] if self.lifs_info: body['interfaces'] = [{self.lifs_info[lif]['lif_type']: {'name': lif}} for lif in self.lifs_info] dummy, error = rest_generic.post_async(self.rest_api, api, body) if error: self.module.fail_json(msg="Error creating portset %s: %s" % (self.parameters['name'], to_native(error))) def delete_portset_rest(self): api = "protocols/san/portsets" # Default value is False if 'force' not in parameters. query = {'allow_delete_while_bound': self.parameters.get('force', False)} dummy, error = rest_generic.delete_async(self.rest_api, api, self.uuid, query) if error: self.module.fail_json(msg="Error deleting portset %s: %s" % (self.parameters['name'], to_native(error))) def modify_portset_rest(self, ports_to_add, ports_to_remove): if ports_to_add: self.add_ports_to_portset(ports_to_add) for port in ports_to_remove: self.remove_port_from_portset(port) def add_ports_to_portset(self, ports_to_add): api = 'protocols/san/portsets/%s/interfaces' % self.uuid body = {'records': [{self.lifs_info[port]['lif_type']: {'name': port}} for port in ports_to_add]} dummy, error = rest_generic.post_async(self.rest_api, api, body) if error: self.module.fail_json(msg='Error adding port in portset %s: %s' % (self.parameters['name'], to_native(error))) def remove_port_from_portset(self, port_to_remove): api = 'protocols/san/portsets/%s/interfaces' % self.uuid dummy, error = rest_generic.delete_async(self.rest_api, api, self.lifs_info[port_to_remove]['uuid']) if error: self.module.fail_json(msg='Error removing port in portset %s: %s' % (self.parameters['name'], to_native(error))) def get_san_lifs_rest(self, san_lifs): # list of lifs not present in the vserver missing_lifs = [] record, record2, error, error2 = None, None, None, None for lif in san_lifs: if self.parameters.get('type') in [None, 'mixed', 'iscsi']: record, error = self.get_san_lif_type_uuid(lif, 'ip') if self.parameters.get('type') in [None, 'mixed', 'fcp']: record2, error2 = self.get_san_lif_type_uuid(lif, 'fc') if error is None and error2 is not None and record: # ignore error on fc if ip interface is found error2 = None if error2 is None and error is not None and record2: # ignore error on ip if fc interface is found error = None if error or error2: errors = [to_native(err) for err in (error, error2) if err] self.module.fail_json(msg='Error fetching lifs details for %s: %s' % (lif, ' - '.join(errors)), exception=traceback.format_exc()) if record: self.lifs_info[lif] = {'lif_type': 'ip', 'uuid': record['uuid']} if record2: self.lifs_info[lif] = {'lif_type': 'fc', 'uuid': record2['uuid']} if record is None and record2 is None: missing_lifs.append(lif) if missing_lifs and self.parameters['state'] == 'present': error_msg = 'Error: lifs: %s of type %s not found in vserver %s' % \ (', '.join(missing_lifs), self.parameters.get('type', 'fcp or iscsi'), self.parameters['vserver']) self.module.fail_json(msg=error_msg) def get_san_lif_type_uuid(self, lif, portset_type): api = 'network/%s/interfaces' % portset_type query = {'name': lif, 'svm.name': self.parameters['vserver']} record, error = rest_generic.get_one_record(self.rest_api, api, query) return record, error def apply(self): """ Applies action from playbook """ current, modify = self.portset_get(), None # get lifs type and uuid which is not present in current. if self.use_rest and self.parameters['state'] == 'present': self.get_san_lifs_rest([port for port in self.parameters['ports'] if port not in self.lifs_info]) cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': if self.parameters.get('type') and self.parameters['type'] != current['type']: self.module.fail_json(msg="modify protocol(type) not supported and %s already exists in vserver %s under different type" % (self.parameters['name'], self.parameters['vserver'])) modify = self.na_helper.get_modified_attributes(current, self.parameters) if self.na_helper.changed and not self.module.check_mode: if cd_action == 'create': self.create_portset() # REST handles create and add ports in create api call itself. if not self.use_rest: self.add_ports() elif cd_action == 'delete': self.delete_portset() elif modify: add_ports = set(self.parameters['ports']) - set(current['ports']) remove_ports = set(current['ports']) - set(self.parameters['ports']) if self.use_rest: self.modify_portset_rest(add_ports, remove_ports) else: self.add_ports(add_ports) self.remove_ports(remove_ports) result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify) self.module.exit_json(**result) def main(): """ Execute action from playbook """ portset_obj = NetAppONTAPPortset() portset_obj.apply() if __name__ == '__main__': main()