class NetAppOntapIgroupInitiator(object): 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'), names=dict(required=True, type='list', elements='str', aliases=['name']), initiator_group=dict(required=True, type='str'), force_remove=dict(required=False, type='bool', default=False), vserver=dict(required=True, type='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 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.parameters['vserver']) def get_initiators(self): """ Get the existing list of initiators from an igroup :rtype: list() or None """ igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter') attributes = dict(query={'initiator-group-info': {'initiator-group-name': self.parameters['initiator_group'], 'vserver': self.parameters['vserver']}}) igroup_info.translate_struct(attributes) result, current = None, [] try: result = self.server.invoke_successfully(igroup_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching igroup info %s: %s' % (self.parameters['initiator_group'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: igroup_info = result.get_child_by_name('attributes-list').get_child_by_name('initiator-group-info') if igroup_info.get_child_by_name('initiators') is not None: current = [initiator['initiator-name'] for initiator in igroup_info['initiators'].get_children()] return current def modify_initiator(self, initiator_name, zapi): """ Add or remove an initiator to/from an igroup """ options = {'initiator-group-name': self.parameters['initiator_group'], 'initiator': initiator_name, 'force': 'true' if zapi == 'igroup-remove' and self.parameters['force_remove'] else 'false'} initiator_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(initiator_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying igroup initiator %s: %s' % (initiator_name, to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): netapp_utils.ems_log_event("na_ontap_igroup_initiator", self.server) def apply(self): self.autosupport_log() initiators = self.get_initiators() for initiator in self.parameters['names']: present = None initiator = self.na_helper.sanitize_wwn(initiator) if initiator in initiators: present = True cd_action = self.na_helper.get_cd_action(present, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.modify_initiator(initiator, 'igroup-add') elif cd_action == 'delete': self.modify_initiator(initiator, 'igroup-remove') self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapIgroup(object): 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'), from_name=dict(required=False, type='str', default=None), ostype=dict(required=False, type='str'), initiator_group_type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed']), initiators=dict(required=False, type='list', elements='str', aliases=['initiator']), vserver=dict(required=True, type='str'), force_remove_initiator=dict(required=False, type='bool', default=False), bind_portset=dict(required=False, type='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 self.module.params.get('initiators') is not None: self.parameters['initiators'] = [ self.na_helper.sanitize_wwn(initiator) for initiator in self.module.params['initiators'] ] 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.parameters['vserver']) def get_igroup(self, name): """ Return details about the igroup :param: name : Name of the igroup :return: Details about the igroup. None if not found. :rtype: dict """ igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter') attributes = dict( query={ 'initiator-group-info': { 'initiator-group-name': name, 'vserver': self.parameters['vserver'] } }) igroup_info.translate_struct(attributes) result, current = None, None try: result = self.server.invoke_successfully(igroup_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching igroup info %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: igroup = result.get_child_by_name( 'attributes-list').get_child_by_name('initiator-group-info') initiators = [] if igroup.get_child_by_name('initiators'): current_initiators = igroup['initiators'].get_children() for initiator in current_initiators: initiators.append(initiator['initiator-name']) current = {'initiators': initiators} return current def add_initiators(self): """ Add the list of initiators to igroup :return: None """ # don't add if initiators is empty string if self.parameters.get('initiators') == [ '' ] or self.parameters.get('initiators') is None: return for initiator in self.parameters['initiators']: self.modify_initiator(initiator, 'igroup-add') def remove_initiators(self, initiators): """ Removes all existing initiators from igroup :return: None """ for initiator in initiators: self.modify_initiator(initiator, 'igroup-remove') def modify_initiator(self, initiator, zapi): """ Add or remove an initiator to/from an igroup """ options = { 'initiator-group-name': self.parameters['name'], 'initiator': initiator } igroup_modify = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(igroup_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json( msg='Error modifying igroup initiator %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_igroup(self): """ Create the igroup. """ options = {'initiator-group-name': self.parameters['name']} if self.parameters.get('ostype') is not None: options['os-type'] = self.parameters['ostype'] if self.parameters.get('initiator_group_type') is not None: options['initiator-group-type'] = self.parameters[ 'initiator_group_type'] if self.parameters.get('bind_portset') is not None: options['bind-portset'] = self.parameters['bind_portset'] igroup_create = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-create', **options) try: self.server.invoke_successfully(igroup_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error provisioning igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.add_initiators() def delete_igroup(self): """ Delete the igroup. """ igroup_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-destroy', **{ 'initiator-group-name': self.parameters['name'], 'force': 'true' if self.parameters['force_remove_initiator'] else 'false' }) try: self.server.invoke_successfully(igroup_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_igroup(self): """ Rename the igroup. """ igroup_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-rename', **{ 'initiator-group-name': self.parameters['from_name'], 'initiator-group-new-name': str(self.parameters['name']) }) try: self.server.invoke_successfully(igroup_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def autosupport_log(self): netapp_utils.ems_log_event("na_ontap_igroup", self.server) def apply(self): self.autosupport_log() current = self.get_igroup(self.parameters['name']) # rename and create are mutually exclusive rename, cd_action, modify = None, None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action( self.get_igroup(self.parameters['from_name']), current) else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes( current, self.parameters) if self.na_helper.changed: if self.module.check_mode: pass else: if rename: self.rename_igroup() elif cd_action == 'create': self.create_igroup() elif cd_action == 'delete': self.delete_igroup() if modify: self.remove_initiators(current['initiators']) self.add_initiators() self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapIgroup(object): """Create/Delete/Rename Igroups and Modify initiators list""" 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'), from_name=dict(required=False, type='str', default=None), os_type=dict(required=False, type='str', aliases=['ostype']), igroups=dict(required=False, type='list', elements='str'), initiator_group_type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed'], aliases=['protocol']), initiators=dict(required=False, type='list', elements='str', aliases=['initiator']), vserver=dict(required=True, type='str'), force_remove_initiator=dict(required=False, type='bool', default=False, aliases=['allow_delete_while_mapped']), bind_portset=dict(required=False, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, mutually_exclusive=[('igroups', 'initiators')] ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) self.rest_modify_zapi_to_rest = dict( # initiator_group_type (protocol) cannot be changed after create bind_portset='portset', name='name', os_type='os_type' ) if self.module.params.get('initiators') is not None: self.parameters['initiators'] = [self.na_helper.sanitize_wwn(initiator) for initiator in self.module.params['initiators']] self.rest_api = OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() def too_old_for_rest(minimum_generation, minimum_major): return self.use_rest and self.rest_api.get_ontap_version() < (minimum_generation, minimum_major) ontap_99_options = ['bind_portset'] if too_old_for_rest(9, 9) and any(x in self.parameters for x in ontap_99_options): self.module.warn('Warning: falling back to ZAPI: %s' % self.rest_api.options_require_ontap_version(ontap_99_options, version='9.9')) self.use_rest = False ontap_99_options = ['igroups'] if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9) and any(x in self.parameters for x in ontap_99_options): self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version(ontap_99_options, version='9.9')) if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9): if 'igroups' in self.parameters: # we may need to remove existing initiators self.parameters['initiators'] = list() elif 'initiators' in self.parameters: # we may need to remove existing igroups self.parameters['igroups'] = list() if not self.use_rest: 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.parameters['vserver']) def fail_on_error(self, error, stack=False): if error is None: return elements = dict(msg="Error: %s" % error) if stack: elements['stack'] = traceback.format_stack() self.module.fail_json(**elements) def get_igroup_rest(self, name): api = "protocols/san/igroups" fields = 'name,uuid,svm,initiators,os_type,protocol' if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9): fields += ',igroups' query = dict(name=name, fields=fields) query['svm.name'] = self.parameters['vserver'] response, error = self.rest_api.get(api, query) igroup, error = rrh.check_for_0_or_1_records(api, response, error) self.fail_on_error(error) if igroup: try: igroup_details = dict( name=igroup['name'], uuid=igroup['uuid'], vserver=igroup['svm']['name'], os_type=igroup['os_type'], initiator_group_type=igroup['protocol'], name_to_uuid=dict() ) except KeyError as exc: self.module.fail_json(msg='Error: unexpected igroup body: %s, KeyError on %s' % (str(igroup), str(exc))) igroup_details['name_to_key'] = dict() for attr in ('igroups', 'initiators'): if attr in igroup: igroup_details[attr] = [item['name'] for item in igroup[attr]] # for initiators, there is no uuid, so we're using name as the key igroup_details['name_to_uuid'][attr] = dict((item['name'], item.get('uuid', item['name'])) for item in igroup[attr]) else: igroup_details[attr] = [] igroup_details['name_to_uuid'][attr] = dict() return igroup_details return None def get_igroup(self, name): """ Return details about the igroup :param: name : Name of the igroup :return: Details about the igroup. None if not found. :rtype: dict """ if self.use_rest: return self.get_igroup_rest(name) igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter') attributes = dict(query={'initiator-group-info': {'initiator-group-name': name, 'vserver': self.parameters['vserver']}}) igroup_info.translate_struct(attributes) current = None try: result = self.server.invoke_successfully(igroup_info, True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching igroup info %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: igroup_info = result.get_child_by_name('attributes-list') initiator_group_info = igroup_info.get_child_by_name('initiator-group-info') initiators = [] if initiator_group_info.get_child_by_name('initiators'): current_initiators = initiator_group_info['initiators'].get_children() for initiator in current_initiators: initiators.append(initiator['initiator-name']) current = { 'initiators': initiators, # place holder, not used for ZAPI 'name_to_uuid': dict(initiators=dict()) } zapi_to_params = { 'vserver': 'vserver', 'initiator-group-os-type': 'os_type', 'initiator-group-portset-name': 'bind_portset', 'initiator-group-type': 'initiator_group_type' } for attr in zapi_to_params: value = igroup_info.get_child_content(attr) if value is not None: current[zapi_to_params[attr]] = value return current def check_what_is_valid(self, what): if self.use_rest and what in ('igroups', 'initiators'): return if what == 'initiators': return raise KeyError('what=%s' % what) def add_initiators_or_igroups_rest(self, uuid, what, names): self.check_what_is_valid(what) api = "protocols/san/igroups/%s/%s" % (uuid, what) records = [dict(name=name) for name in names] body = dict(records=records) dummy, error = self.rest_api.post(api, body) self.fail_on_error(error) def add_initiators_or_igroups(self, uuid, what, current_names): """ Add the list of desired initiators to igroup unless they are already set :return: None """ self.check_what_is_valid(what) # don't add if initiators/igroups is empty string if self.parameters.get(what) == [''] or self.parameters.get(what) is None: return names_to_add = [name for name in self.parameters[what] if name not in current_names] if self.use_rest and uuid is not None and names_to_add: self.add_initiators_or_igroups_rest(uuid, what, names_to_add) else: for name in names_to_add: self.modify_initiator(name, 'igroup-add') def delete_initiator_or_igroup_rest(self, uuid, what, name): self.check_what_is_valid(what) api = "protocols/san/igroups/%s/%s/%s" % (uuid, what, name) dummy, error = self.rest_api.delete(api) self.fail_on_error(error) def remove_initiators_or_igroups(self, uuid, what, current_names, mapping): """ Removes current names from igroup unless they are still desired :return: None """ self.check_what_is_valid(what) for name in current_names: if name not in self.parameters.get(what, list()): if self.use_rest: self.delete_initiator_or_igroup_rest(uuid, what, mapping[name]) else: self.modify_initiator(name, 'igroup-remove') def modify_initiator(self, initiator, zapi): """ Add or remove an initiator to/from an igroup """ options = {'initiator-group-name': self.parameters['name'], 'initiator': initiator} igroup_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(igroup_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying igroup initiator %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def create_igroup_rest(self): api = "protocols/san/igroups" body = dict( name=self.parameters['name'], os_type=self.parameters['os_type']) body['svm'] = dict(name=self.parameters['vserver']) mapping = dict( initiator_group_type='protocol', bind_portset='portset', igroups='igroups', initiators='initiators' ) for option in mapping: value = self.parameters.get(option) if value is not None: if option in ('igroups', 'initiators'): # we may have an empty list, ignore it value = [dict(name=name) for name in value] if value else None if value is not None: body[mapping[option]] = value dummy, error = self.rest_api.post(api, body) self.fail_on_error(error) def create_igroup(self): """ Create the igroup. """ if self.use_rest: self.create_igroup_rest() return options = {'initiator-group-name': self.parameters['name']} if self.parameters.get('os_type') is not None: options['os-type'] = self.parameters['os_type'] if self.parameters.get('initiator_group_type') is not None: options['initiator-group-type'] = self.parameters['initiator_group_type'] if self.parameters.get('bind_portset') is not None: options['bind-portset'] = self.parameters['bind_portset'] igroup_create = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-create', **options) try: self.server.invoke_successfully(igroup_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error provisioning igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) self.add_initiators_or_igroups(None, 'initiators', []) def modify_igroup_rest(self, uuid, modify): api = "protocols/san/igroups/%s" % uuid body = dict() for option in modify: if option not in self.rest_modify_zapi_to_rest: self.module.fail_json(msg='Error: modifying %s is not supported in REST' % option) body[self.rest_modify_zapi_to_rest[option]] = modify[option] if body: dummy, error = self.rest_api.patch(api, body) self.fail_on_error(error) def delete_igroup_rest(self, uuid): api = "protocols/san/igroups/%s" % uuid if self.parameters['force_remove_initiator']: query = dict(allow_delete_while_mapped=True) else: query = None dummy, error = self.rest_api.delete(api, params=query) self.fail_on_error(error) def delete_igroup(self, uuid): """ Delete the igroup. """ if self.use_rest: self.delete_igroup_rest(uuid) return igroup_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-destroy', **{'initiator-group-name': self.parameters['name'], 'force': 'true' if self.parameters['force_remove_initiator'] else 'false'}) try: self.server.invoke_successfully(igroup_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def rename_igroup(self): """ Rename the igroup. """ if self.use_rest: self.module.fail_json('Internal error, should not call rename, but use modify') igroup_rename = netapp_utils.zapi.NaElement.create_node_with_children( 'igroup-rename', **{'initiator-group-name': self.parameters['from_name'], 'initiator-group-new-name': str(self.parameters['name'])}) try: self.server.invoke_successfully(igroup_rename, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error renaming igroup %s: %s' % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) def report_error_in_modify(self, modify, context): if modify: if len(modify) > 1: tag = 'any of ' else: tag = '' self.module.fail_json(msg='Error: modifying %s %s is not supported in %s' % (tag, str(modify), context)) def validate_modify(self, modify): """Identify options that cannot be modified for REST or ZAPI """ if not modify: return modify_local = dict(modify) modify_local.pop('igroups', None) modify_local.pop('initiators', None) if not self.use_rest: self.report_error_in_modify(modify_local, 'ZAPI') return for option in modify: if option in self.rest_modify_zapi_to_rest: modify_local.pop(option) self.report_error_in_modify(modify_local, 'REST') def autosupport_log(self): if not self.use_rest: netapp_utils.ems_log_event("na_ontap_igroup", self.server) def is_rename_action(self, cd_action, current): old = self.get_igroup(self.parameters['from_name']) rename = self.na_helper.is_rename_action(old, current) if rename is None: self.module.fail_json(msg='Error: igroup with from_name=%s not found' % self.parameters.get('from_name')) if rename: current = old cd_action = None return cd_action, rename, current def apply(self): self.autosupport_log() uuid = None rename, modify = None, None current = self.get_igroup(self.parameters['name']) cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action == 'create' and self.parameters.get('from_name'): cd_action, rename, current = self.is_rename_action(cd_action, current) if cd_action is None and self.parameters['state'] == 'present': modify = self.na_helper.get_modified_attributes(current, self.parameters) # a change in name is handled in rename for ZAPI, but REST can use modify if self.use_rest: rename = False else: modify.pop('name', None) if current and self.use_rest: uuid = current['uuid'] if cd_action == 'create' and self.use_rest and 'os_type' not in self.parameters: self.module.fail_json(msg='Error: os_type is a required parameter when creating an igroup with REST') self.validate_modify(modify) if self.na_helper.changed and not self.module.check_mode: if rename: self.rename_igroup() elif cd_action == 'create': self.create_igroup() elif cd_action == 'delete': self.delete_igroup(uuid) if modify: for attr in ('igroups', 'initiators'): if attr in current: # we need to remove everything first self.remove_initiators_or_igroups(uuid, attr, current[attr], current['name_to_uuid'][attr]) for attr in ('igroups', 'initiators'): if attr in current: self.add_initiators_or_igroups(uuid, attr, current[attr]) modify.pop(attr, None) if modify: self.modify_igroup_rest(uuid, modify) self.module.exit_json(changed=self.na_helper.changed, current=current, modify=modify)