class NetAppOntapLUN(object): ''' create, modify, delete LUN ''' 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'), size=dict(type='int'), size_unit=dict(default='gb', choices=[ 'bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb' ], type='str'), force_resize=dict(default=False, type='bool'), force_remove=dict(default=False, type='bool'), force_remove_fenced=dict(default=False, type='bool'), flexvol_name=dict(type='str'), vserver=dict(required=True, type='str'), os_type=dict(required=False, type='str', aliases=['ostype']), qos_policy_group=dict(required=False, type='str'), space_reserve=dict(required=False, type='bool', default=True), space_allocation=dict(required=False, type='bool', default=False), use_exact_size=dict(required=False, type='bool', default=True), san_application_template=dict( type='dict', options=dict( use_san_application=dict(type='bool', default=True), name=dict(required=True, type='str'), igroup_name=dict(type='str'), lun_count=dict(type='int'), protection_type=dict( type='dict', options=dict(local_policy=dict(type='str'), )), storage_service=dict( type='str', choices=['value', 'performance', 'extreme']), tiering=dict( type='dict', options=dict( control=dict(type='str', choices=[ 'required', 'best_effort', 'disallowed' ]), policy=dict(type='str', choices=[ 'all', 'auto', 'none', 'snapshot-only' ]), object_stores=dict( type='list', elements='str') # create only )), )))) self.module = AnsibleModule(argument_spec=self.argument_spec, supports_check_mode=True) # set up state variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('size') is not None: self.parameters['size'] *= netapp_utils.POW2_BYTE_MAP[ self.parameters['size_unit']] 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']) # REST API for application/applications if needed self.rest_api, self.rest_app = self.setup_rest_application() def setup_rest_application(self): use_application_template = self.na_helper.safe_get( self.parameters, ['san_application_template', 'use_san_application']) rest_api, rest_app = None, None if use_application_template: if self.parameters.get('flexvol_name') is not None: self.module.fail_json( msg= "'flexvol_name' option is not supported when san_application_template is present" ) rest_api = netapp_utils.OntapRestAPI(self.module) name = self.na_helper.safe_get( self.parameters, ['san_application_template', 'name'], allow_sparse_dict=False) rest_app = RestApplication(rest_api, self.parameters['vserver'], name) elif self.parameters.get('flexvol_name') is None: self.module.fail_json( msg= "flexvol_name option is required when san_application_template is not present" ) return rest_api, rest_app def get_luns(self, lun_path=None): """ Return list of LUNs matching vserver and volume names. :return: list of LUNs in XML format. :rtype: list """ luns = [] tag = None if lun_path is None and self.parameters.get('flexvol_name') is None: return luns query_details = netapp_utils.zapi.NaElement('lun-info') query_details.add_new_child('vserver', self.parameters['vserver']) if lun_path is not None: query_details.add_new_child('lun_path', lun_path) else: query_details.add_new_child('volume', self.parameters['flexvol_name']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) while True: lun_info = netapp_utils.zapi.NaElement('lun-get-iter') lun_info.add_child_elem(query) if tag: lun_info.add_new_child('tag', tag, True) result = self.server.invoke_successfully(lun_info, True) if result.get_child_by_name('num-records') and int( result.get_child_content('num-records')) >= 1: attr_list = result.get_child_by_name('attributes-list') luns.extend(attr_list.get_children()) tag = result.get_child_content('next-tag') if tag is None: break return luns def get_lun_details(self, lun): """ Extract LUN details, from XML to python dict :return: Details about the lun :rtype: dict """ return_value = dict() return_value['size'] = int(lun.get_child_content('size')) bool_attr_map = { 'is-space-alloc-enabled': 'space_allocation', 'is-space-reservation-enabled': 'space_reserve' } for attr in bool_attr_map: value = lun.get_child_content(attr) if value is not None: return_value[ bool_attr_map[attr]] = self.na_helper.get_value_for_bool( True, value) str_attr_map = { 'name': 'name', 'path': 'path', 'qos-policy-group': 'qos_policy_group', 'multiprotocol-type': 'os_type' } for attr in str_attr_map: value = lun.get_child_content(attr) if value is not None: return_value[str_attr_map[attr]] = value # Find out if the lun is attached attached_to = None lun_id = None if lun.get_child_content('mapped') == 'true': lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-map-list-info', **{'path': lun.get_child_content('path')}) result = self.server.invoke_successfully(lun_map_list, enable_tunneling=True) igroups = result.get_child_by_name('initiator-groups') if igroups: for igroup_info in igroups.get_children(): igroup = igroup_info.get_child_content( 'initiator-group-name') attached_to = igroup lun_id = igroup_info.get_child_content('lun-id') return_value.update({'attached_to': attached_to, 'lun_id': lun_id}) return return_value def find_lun(self, luns, name, lun_path=None): """ Return lun record matching name or path :return: lun record :rtype: XML or None if not found """ for lun in luns: path = lun.get_child_content('path') if lun_path is not None: if lun_path == path: return lun else: if name == path: return lun _rest, _splitter, found_name = path.rpartition('/') if found_name == name: return lun return None def get_lun(self, name, lun_path=None): """ Return details about the LUN :return: Details about the lun :rtype: dict """ luns = self.get_luns(lun_path) lun = self.find_lun(luns, name, lun_path) if lun is not None: return self.get_lun_details(lun) return None def get_luns_from_app(self): app_details, error = self.rest_app.get_application_details() self.fail_on_error(error) if app_details is not None: app_details['paths'] = self.get_lun_paths_from_app() return app_details def get_lun_paths_from_app(self): """Get luns path for SAN application""" backing_storage, error = self.rest_app.get_application_component_backing_storage( ) self.fail_on_error(error) # {'luns': [{'path': '/vol/ansibleLUN/ansibleLUN_1', ... if backing_storage is not None: return [lun['path'] for lun in backing_storage.get('luns', [])] return None def get_lun_path_from_backend(self, name): """returns lun path matching name if found in backing_storage retruns None if not found """ lun_paths = self.get_lun_paths_from_app() match = "/%s" % name for path in lun_paths: if path.endswith(match): return path return None def create_san_app_component(self): '''Create SAN application component''' required_options = ('name', 'size') for option in required_options: if self.parameters.get(option) is None: self.module.fail_json( msg='Error: "%s" is required to create san application.' % option) application_component = dict( name=self.parameters['name'], total_size=self.parameters['size'], lun_count=1 # default value, may be overriden below ) for attr in ('igroup_name', 'lun_count', 'storage_service'): value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None: application_component[attr] = value for attr in ('os_type', 'qos_policy_group'): value = self.na_helper.safe_get(self.parameters, [attr]) if value is not None: if attr == 'qos_policy_group': attr = 'qos' value = dict(policy=dict(name=value)) application_component[attr] = value tiering = self.na_helper.safe_get( self.parameters, ['nas_application_template', 'tiering']) if tiering is not None: application_component['tiering'] = dict() for attr in ('control', 'policy', 'object_stores'): value = tiering.get(attr) if attr == 'object_stores' and value is not None: value = [dict(name=x) for x in value] if value is not None: application_component['tiering'][attr] = value return application_component def create_san_app_body(self): '''Create body for san template''' # TODO: # Should we support new_igroups? # It may raise idempotency issues if the REST call fails if the igroup already exists. # And we already have na_ontap_igroups. san = { 'application_components': [self.create_san_app_component()], } for attr in ('protection_type', ): value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None: # we expect value to be a dict, but maybe an empty dict value = self.na_helper.filter_out_none_entries(value) if value: san[attr] = value for attr in ('os_type', ): value = self.na_helper.safe_get(self.parameters, [attr]) if value is not None: san[attr] = value body, error = self.rest_app.create_application_body('san', san) return body, error def create_san_application(self): '''Use REST application/applications san template to create one or more LUNs''' body, error = self.create_san_app_body() self.fail_on_error(error) dummy, error = self.rest_app.create_application(body) self.fail_on_error(error) def delete_san_application(self): '''Use REST application/applications san template to delete one or more LUNs''' dummy, error = self.rest_app.delete_application() self.fail_on_error(error) def create_lun(self): """ Create LUN with requested name and size """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) options = { 'path': path, 'size': str(self.parameters['size']), 'space-reservation-enabled': str(self.parameters['space_reserve']), 'space-allocation-enabled': str(self.parameters['space_allocation']), 'use-exact-size': str(self.parameters['use_exact_size']) } if self.parameters.get('os_type') is not None: options['ostype'] = self.parameters['os_type'] if self.parameters.get('qos_policy_group') is not None: options['qos-policy-group'] = self.parameters['qos_policy_group'] lun_create = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-create-by-size', **options) try: self.server.invoke_successfully(lun_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json( msg="Error provisioning lun %s of size %s: %s" % (self.parameters['name'], self.parameters['size'], to_native(exc)), exception=traceback.format_exc()) def delete_lun(self, path): """ Delete requested LUN """ lun_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-destroy', **{ 'path': path, 'force': str(self.parameters['force_remove']), 'destroy-fenced-lun': str(self.parameters['force_remove_fenced']) }) try: self.server.invoke_successfully(lun_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error deleting lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) def resize_lun(self, path): """ Resize requested LUN. :return: True if LUN was actually re-sized, false otherwise. :rtype: bool """ lun_resize = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-resize', **{ 'path': path, 'size': str(self.parameters['size']), 'force': str(self.parameters['force_resize']) }) try: self.server.invoke_successfully(lun_resize, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: if to_native(exc.code) == "9042": # Error 9042 denotes the new LUN size being the same as the # old LUN size. This happens when there's barely any difference # in the two sizes. For example, from 8388608 bytes to # 8194304 bytes. This should go away if/when the default size # requested/reported to/from the controller is changed to a # larger unit (MB/GB/TB). return False else: self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) return True def set_lun_value(self, path, key, value): key_to_zapi = dict(qos_policy_group=('lun-set-qos-policy-group', 'qos-policy-group'), space_allocation=('lun-set-space-alloc', 'enable'), space_reserve=('lun-set-space-reservation-info', 'enable')) if key in key_to_zapi: zapi, option = key_to_zapi[key] else: self.module.fail_json(msg="option %s cannot be modified to %s" % (key, value)) options = dict(path=path) if option == 'enable': options[option] = self.na_helper.get_value_for_bool(False, value) else: options[option] = value lun_set = netapp_utils.zapi.NaElement.create_node_with_children( zapi, **options) try: self.server.invoke_successfully(lun_set, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error setting lun option %s: %s" % (key, to_native(exc)), exception=traceback.format_exc()) return def modify_lun(self, path, modify): """ update LUN properties (except size or name) """ for key, value in modify.items(): self.set_lun_value(path, key, value) def rename_lun(self, path, new_path): """ rename LUN """ lun_move = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-move', **{ 'path': path, 'new-path': new_path }) try: self.server.invoke_successfully(lun_move, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error moving lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) 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 apply(self): results = dict() warnings = list() netapp_utils.ems_log_event("na_ontap_lun", self.server) app_cd_action = None if self.rest_app: app_current, error = self.rest_app.get_application_uuid() self.fail_on_error(error) app_cd_action = self.na_helper.get_cd_action( app_current, self.parameters) if app_cd_action == 'create' and self.parameters.get( 'size') is None: self.module.fail_json( msg="size is a required parameter for create.") # For LUNs created using a SAN application, we're getting lun paths from the backing storage lun_path, from_lun_path = None, None from_name = self.parameters.get('from_name') if self.rest_app and app_cd_action is None and app_current: lun_path = self.get_lun_path_from_backend(self.parameters['name']) if from_name is not None: from_lun_path = self.get_lun_path_from_backend(from_name) if app_cd_action is None: # actions at LUN level current = self.get_lun(self.parameters['name'], lun_path) if current is not None and lun_path is None: lun_path = current['path'] cd_action = self.na_helper.get_cd_action(current, self.parameters) modify, rename = None, None if cd_action == 'create' and from_name is not None: # create by renaming existing LUN, if it really exists old_lun = self.get_lun(from_name, from_lun_path) rename = self.na_helper.is_rename_action(old_lun, current) if rename is None: self.module.fail_json( msg="Error renaming lun: %s does not exist" % from_name) if rename: current = old_lun if from_lun_path is None: from_lun_path = current['path'] head, _sep, tail = from_lun_path.rpartition(from_name) if tail: self.module.fail_json( msg= "Error renaming lun: %s does not match lun_path %s" % (from_name, from_lun_path)) lun_path = head + self.parameters['name'] results['renamed'] = True cd_action = None if cd_action == 'create' and self.parameters.get('size') is None: self.module.fail_json( msg="size is a required parameter for create.") if cd_action is None and self.parameters['state'] == 'present': # we already handled rename if required current.pop('name', None) modify = self.na_helper.get_modified_attributes( current, self.parameters) results['modify'] = dict(modify) if cd_action and self.rest_app and app_cd_action is None and app_current: msg = 'This module does not support %s a LUN by name %s a SAN application.' %\ ('adding', 'to') if cd_action == 'create' else ('removing', 'from') warnings.append(msg) cd_action = None self.na_helper.changed = False if self.na_helper.changed and not self.module.check_mode: if app_cd_action == 'create': self.create_san_application() elif app_cd_action == 'delete': self.rest_app.delete_application() elif cd_action == 'create': self.create_lun() elif cd_action == 'delete': self.delete_lun(lun_path) else: if rename: self.rename_lun(from_lun_path, lun_path) size_changed = False if modify and 'size' in modify: # Ensure that size was actually changed. Please # read notes in 'resize_lun' function for details. size_changed = self.resize_lun(lun_path) modify.pop('size') if modify: self.modify_lun(lun_path, modify) if not modify and not rename: # size may not have changed self.na_helper.changed = size_changed results['changed'] = self.na_helper.changed self.module.exit_json(**results)
class NetAppOntapLUN(object): ''' create, modify, delete LUN ''' 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'), size=dict(type='int'), size_unit=dict(default='gb', choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'], type='str'), comment=dict(required=False, type='str'), force_resize=dict(default=False, type='bool'), force_remove=dict(default=False, type='bool'), force_remove_fenced=dict(default=False, type='bool'), flexvol_name=dict(type='str'), vserver=dict(required=True, type='str'), os_type=dict(required=False, type='str', aliases=['ostype']), qos_policy_group=dict(required=False, type='str'), qos_adaptive_policy_group=dict(required=False, type='str'), space_reserve=dict(required=False, type='bool', default=True), space_allocation=dict(required=False, type='bool', default=False), use_exact_size=dict(required=False, type='bool', default=True), san_application_template=dict(type='dict', options=dict( use_san_application=dict(type='bool', default=True), name=dict(required=True, type='str'), igroup_name=dict(type='str'), lun_count=dict(type='int'), protection_type=dict(type='dict', options=dict( local_policy=dict(type='str'), )), storage_service=dict(type='str', choices=['value', 'performance', 'extreme']), tiering=dict(type='dict', options=dict( control=dict(type='str', choices=['required', 'best_effort', 'disallowed']), policy=dict(type='str', choices=['all', 'auto', 'none', 'snapshot-only']), object_stores=dict(type='list', elements='str') # create only )), total_size=dict(type='int'), total_size_unit=dict(choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'], type='str'), scope=dict(type='str', choices=['application', 'auto', 'lun'], default='auto'), )) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, mutually_exclusive=[('qos_policy_group', 'qos_adaptive_policy_group')] ) # set up state variables self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('size') is not None: self.parameters['size'] *= netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']] if self.na_helper.safe_get(self.parameters, ['san_application_template', 'total_size']) is not None: unit = self.na_helper.safe_get(self.parameters, ['san_application_template', 'total_size_unit']) if unit is None: unit = self.parameters['size_unit'] self.parameters['san_application_template']['total_size'] *= netapp_utils.POW2_BYTE_MAP[unit] self.warnings = list() self.debug = dict() # self.debug['got'] = 'empty' # uncomment to enable collecting data 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']) # REST API for application/applications if needed self.rest_api, self.rest_app = self.setup_rest_application() def setup_rest_application(self): use_application_template = self.na_helper.safe_get(self.parameters, ['san_application_template', 'use_san_application']) rest_api, rest_app = None, None if use_application_template: if self.parameters.get('flexvol_name') is not None: self.module.fail_json(msg="'flexvol_name' option is not supported when san_application_template is present") rest_api = netapp_utils.OntapRestAPI(self.module) name = self.na_helper.safe_get(self.parameters, ['san_application_template', 'name'], allow_sparse_dict=False) rest_app = RestApplication(rest_api, self.parameters['vserver'], name) elif self.parameters.get('flexvol_name') is None: self.module.fail_json(msg="flexvol_name option is required when san_application_template is not present") return rest_api, rest_app def get_luns(self, lun_path=None): """ Return list of LUNs matching vserver and volume names. :return: list of LUNs in XML format. :rtype: list """ luns = [] tag = None if lun_path is None and self.parameters.get('flexvol_name') is None: return luns query_details = netapp_utils.zapi.NaElement('lun-info') query_details.add_new_child('vserver', self.parameters['vserver']) if lun_path is not None: query_details.add_new_child('lun_path', lun_path) else: query_details.add_new_child('volume', self.parameters['flexvol_name']) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(query_details) while True: lun_info = netapp_utils.zapi.NaElement('lun-get-iter') lun_info.add_child_elem(query) if tag: lun_info.add_new_child('tag', tag, True) result = self.server.invoke_successfully(lun_info, True) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: attr_list = result.get_child_by_name('attributes-list') luns.extend(attr_list.get_children()) tag = result.get_child_content('next-tag') if tag is None: break return luns def get_lun_details(self, lun): """ Extract LUN details, from XML to python dict :return: Details about the lun :rtype: dict """ return_value = dict() return_value['size'] = int(lun.get_child_content('size')) bool_attr_map = { 'is-space-alloc-enabled': 'space_allocation', 'is-space-reservation-enabled': 'space_reserve' } for attr in bool_attr_map: value = lun.get_child_content(attr) if value is not None: return_value[bool_attr_map[attr]] = self.na_helper.get_value_for_bool(True, value) str_attr_map = { 'comment': 'comment', 'multiprotocol-type': 'os_type', 'name': 'name', 'path': 'path', 'qos-policy-group': 'qos_policy_group', 'qos-adaptive-policy-group': 'qos_adaptive_policy_group', } for attr in str_attr_map: value = lun.get_child_content(attr) if value is None and attr in ('comment', 'qos-policy-group', 'qos-adaptive-policy-group'): value = '' if value is not None: return_value[str_attr_map[attr]] = value # Find out if the lun is attached attached_to = None lun_id = None if lun.get_child_content('mapped') == 'true': lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-map-list-info', **{'path': lun.get_child_content('path')}) result = self.server.invoke_successfully( lun_map_list, enable_tunneling=True) igroups = result.get_child_by_name('initiator-groups') if igroups: for igroup_info in igroups.get_children(): igroup = igroup_info.get_child_content( 'initiator-group-name') attached_to = igroup lun_id = igroup_info.get_child_content('lun-id') return_value.update({ 'attached_to': attached_to, 'lun_id': lun_id }) return return_value def find_lun(self, luns, name, lun_path=None): """ Return lun record matching name or path :return: lun record :rtype: XML or None if not found """ for lun in luns: path = lun.get_child_content('path') if lun_path is not None: if lun_path == path: return lun else: if name == path: return lun _rest, _splitter, found_name = path.rpartition('/') if found_name == name: return lun return None def get_lun(self, name, lun_path=None): """ Return details about the LUN :return: Details about the lun :rtype: dict """ luns = self.get_luns(lun_path) lun = self.find_lun(luns, name, lun_path) if lun is not None: return self.get_lun_details(lun) return None def get_luns_from_app(self): app_details, error = self.rest_app.get_application_details() self.fail_on_error(error) if app_details is not None: app_details['paths'] = self.get_lun_paths_from_app() return app_details def get_lun_paths_from_app(self): """Get luns path for SAN application""" backing_storage, error = self.rest_app.get_application_component_backing_storage() self.fail_on_error(error) # {'luns': [{'path': '/vol/ansibleLUN/ansibleLUN_1', ... if backing_storage is not None: return [lun['path'] for lun in backing_storage.get('luns', [])] return None def get_lun_path_from_backend(self, name): """returns lun path matching name if found in backing_storage retruns None if not found """ lun_paths = self.get_lun_paths_from_app() match = "/%s" % name for path in lun_paths: if path.endswith(match): return path return None def create_san_app_component(self, modify): '''Create SAN application component''' if modify: required_options = ['name'] action = 'modify' if 'lun_count' in modify: required_options.append('total_size') else: required_options = ('name', 'total_size') action = 'create' for option in required_options: if self.parameters.get(option) is None: self.module.fail_json(msg="Error: '%s' is required to %s a san application." % (option, action)) application_component = dict(name=self.parameters['name']) if not modify: application_component['lun_count'] = 1 # default value for create, may be overriden below for attr in ('igroup_name', 'lun_count', 'storage_service'): if not modify or attr in modify: value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None: application_component[attr] = value for attr in ('os_type', 'qos_policy_group', 'qos_adaptive_policy_group', 'total_size'): if not modify or attr in modify: value = self.na_helper.safe_get(self.parameters, [attr]) if value is not None: # only one of them can be present at most if attr in ('qos_policy_group', 'qos_adaptive_policy_group'): attr = 'qos' value = dict(policy=dict(name=value)) application_component[attr] = value tiering = self.na_helper.safe_get(self.parameters, ['nas_application_template', 'tiering']) if tiering is not None and not modify: application_component['tiering'] = dict() for attr in ('control', 'policy', 'object_stores'): value = tiering.get(attr) if attr == 'object_stores' and value is not None: value = [dict(name=x) for x in value] if value is not None: application_component['tiering'][attr] = value return application_component def create_san_app_body(self, modify=None): '''Create body for san template''' # TODO: # Should we support new_igroups? # It may raise idempotency issues if the REST call fails if the igroup already exists. # And we already have na_ontap_igroups. san = { 'application_components': [self.create_san_app_component(modify)], } for attr in ('protection_type',): if not modify or attr in modify: value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None: # we expect value to be a dict, but maybe an empty dict value = self.na_helper.filter_out_none_entries(value) if value: san[attr] = value for attr in ('os_type',): if not modify: # not supported for modify operation, but required at applicaiton component level value = self.na_helper.safe_get(self.parameters, [attr]) if value is not None: san[attr] = value body, error = self.rest_app.create_application_body('san', san) return body, error def create_san_application(self): '''Use REST application/applications san template to create one or more LUNs''' body, error = self.create_san_app_body() self.fail_on_error(error) dummy, error = self.rest_app.create_application(body) self.fail_on_error(error) def modify_san_application(self, modify): '''Use REST application/applications san template to add one or more LUNs''' body, error = self.create_san_app_body(modify) self.fail_on_error(error) # these cannot be present when using PATCH body.pop('name') body.pop('svm') body.pop('smart_container') dummy, error = self.rest_app.patch_application(body) self.fail_on_error(error) def convert_to_san_application(self, scope): '''First convert volume to smart container using POST Second modify app to add new luns using PATCH ''' # dummy modify, so that we don't fill in the body modify = dict(dummy='dummy') body, error = self.create_san_app_body(modify) self.fail_on_error(error) dummy, error = self.rest_app.create_application(body) self.fail_on_error(error) app_current, error = self.rest_app.get_application_uuid() self.fail_on_error(error) if app_current is None: self.module.fail_json('Error: failed to create smart container for %s' % self.parameters['name']) app_modify, app_modify_warning = self.app_changes(scope) if app_modify_warning is not None: self.warnings.append(app_modify_warning) if app_modify: self.modify_san_application(app_modify) def delete_san_application(self): '''Use REST application/applications san template to delete one or more LUNs''' dummy, error = self.rest_app.delete_application() self.fail_on_error(error) def create_lun(self): """ Create LUN with requested name and size """ path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name']) options = {'path': path, 'size': str(self.parameters['size']), 'space-reservation-enabled': str(self.parameters['space_reserve']), 'space-allocation-enabled': str(self.parameters['space_allocation']), 'use-exact-size': str(self.parameters['use_exact_size'])} if self.parameters.get('comment') is not None: options['comment'] = self.parameters['comment'] if self.parameters.get('os_type') is not None: options['ostype'] = self.parameters['os_type'] if self.parameters.get('qos_policy_group') is not None: options['qos-policy-group'] = self.parameters['qos_policy_group'] if self.parameters.get('qos_adaptive_policy_group') is not None: options['qos-adaptive-policy-group'] = self.parameters['qos_adaptive_policy_group'] lun_create = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-create-by-size', **options) try: self.server.invoke_successfully(lun_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error provisioning lun %s of size %s: %s" % (self.parameters['name'], self.parameters['size'], to_native(exc)), exception=traceback.format_exc()) def delete_lun(self, path): """ Delete requested LUN """ lun_delete = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-destroy', **{'path': path, 'force': str(self.parameters['force_remove']), 'destroy-fenced-lun': str(self.parameters['force_remove_fenced'])}) try: self.server.invoke_successfully(lun_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error deleting lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) def resize_lun(self, path): """ Resize requested LUN. :return: True if LUN was actually re-sized, false otherwise. :rtype: bool """ lun_resize = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-resize', **{'path': path, 'size': str(self.parameters['size']), 'force': str(self.parameters['force_resize'])}) try: self.server.invoke_successfully(lun_resize, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: if to_native(exc.code) == "9042": # Error 9042 denotes the new LUN size being the same as the # old LUN size. This happens when there's barely any difference # in the two sizes. For example, from 8388608 bytes to # 8194304 bytes. This should go away if/when the default size # requested/reported to/from the controller is changed to a # larger unit (MB/GB/TB). return False else: self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) return True def set_lun_value(self, path, key, value): key_to_zapi = dict( comment=('lun-set-comment', 'comment'), # The same ZAPI is used for both QOS attributes qos_policy_group=('lun-set-qos-policy-group', 'qos-policy-group'), qos_adaptive_policy_group=('lun-set-qos-policy-group', 'qos-adaptive-policy-group'), space_allocation=('lun-set-space-alloc', 'enable'), space_reserve=('lun-set-space-reservation-info', 'enable') ) if key in key_to_zapi: zapi, option = key_to_zapi[key] else: self.module.fail_json(msg="option %s cannot be modified to %s" % (key, value)) options = dict(path=path) if option == 'enable': options[option] = self.na_helper.get_value_for_bool(False, value) else: options[option] = value lun_set = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) try: self.server.invoke_successfully(lun_set, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error setting lun option %s: %s" % (key, to_native(exc)), exception=traceback.format_exc()) return def modify_lun(self, path, modify): """ update LUN properties (except size or name) """ for key, value in modify.items(): self.set_lun_value(path, key, value) def rename_lun(self, path, new_path): """ rename LUN """ lun_move = netapp_utils.zapi.NaElement.create_node_with_children( 'lun-move', **{'path': path, 'new-path': new_path}) try: self.server.invoke_successfully(lun_move, enable_tunneling=True) except netapp_utils.zapi.NaApiError as exc: self.module.fail_json(msg="Error moving lun %s: %s" % (path, to_native(exc)), exception=traceback.format_exc()) 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 set_total_size(self, validate): # fix total_size attribute, report error if total_size is missing (or size is missing) attr = 'total_size' value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr]) if value is not None or not validate: self.parameters[attr] = value return lun_count = self.na_helper.safe_get(self.parameters, ['san_application_template', 'lun_count']) value = self.parameters.get('size') if value is not None and (lun_count is None or lun_count == 1): self.parameters[attr] = value return self.module.fail_json("Error: 'total_size' is a required SAN application template attribute when creating a LUN application") def validate_app_create(self): # fix total_size attribute self.set_total_size(validate=True) def validate_app_changes(self, modify, warning): errors = list() for key in modify: if key not in ('lun_count', 'total_size'): errors.append("Error: the following application parameter cannot be modified: %s: %s." % (key, modify[key])) if 'lun_count' in modify: for attr in ('total_size', 'os_type', 'igroup_name'): value = self.parameters.get(attr) if value is None: value = self.na_helper.safe_get(self.parameters['san_application_template'], [attr]) if value is None: errors.append('Error: %s is a required parameter when increasing lun_count.' % attr) else: modify[attr] = value if warning: errors.append('Error: %s' % warning) if errors: self.module.fail_json(msg='\n'.join(errors)) if 'total_size' in modify: self.set_total_size(validate=False) if warning: # can't change total_size, let's ignore it self.warnings.append(warning) modify.pop('total_size') def app_changes(self, scope): # find and validate app changes app_current, error = self.rest_app.get_application_details('san') self.fail_on_error(error) # save application name, as it is overriden in the flattening operation app_name = app_current['name'] # there is an issue with total_size not reflecting the real total_size, and some additional overhead provisioned_size = self.na_helper.safe_get(app_current, ['statistics', 'space', 'provisioned']) if provisioned_size is None: provisioned_size = 0 if self.debug: self.debug['app_current'] = app_current # will be updated below as it is mutable self.debug['got'] = copy.deepcopy(app_current) # fixed copy # flatten app_current = app_current['san'] # app template app_current.update(app_current['application_components'][0]) # app component del app_current['application_components'] # if component name does not match, assume a change at LUN level comp_name = app_current['name'] if comp_name != self.parameters['name']: msg = "desired component/volume name: %s does not match existing component name: %s" % (self.parameters['name'], comp_name) if scope == 'application': self.module.fail_json(msg='Error: ' + msg + ". scope=%s" % scope) return None, msg + ". scope=%s, assuming 'lun' scope." % scope # restore app name app_current['name'] = app_name # ready to compare, except for a quirk in size handling total_size = app_current['total_size'] desired = dict(self.parameters['san_application_template']) desired_size = desired.get('total_size') warning = None if desired_size is not None: if desired_size < total_size: self.module.fail_json("Error: can't reduce size: total_size=%d, provisioned=%d, requested=%d" % (total_size, provisioned_size, desired_size)) elif desired_size > total_size and desired_size < provisioned_size: # we can't increase, but we can't say it is a problem, as the size is already bigger! warning = "requested size is too small: total_size=%d, provisioned=%d, requested=%d" % (total_size, provisioned_size, desired_size) # preserve change state before calling modify in case an ignorable total_size change is the only change changed = self.na_helper.changed app_modify = self.na_helper.get_modified_attributes(app_current, desired) self.validate_app_changes(app_modify, warning) if not app_modify: self.na_helper.changed = changed app_modify = None return app_modify, None def apply(self): results = dict() netapp_utils.ems_log_event("na_ontap_lun", self.server) app_cd_action, app_modify, lun_cd_action, lun_modify, lun_rename = None, None, None, None, None app_modify_warning = None actions = list() if self.rest_app: scope = self.na_helper.safe_get(self.parameters, ['san_application_template', 'scope']) app_current, error = self.rest_app.get_application_uuid() self.fail_on_error(error) if scope == 'lun' and app_current is None: self.module.fail_json('Application not found: %s. scope=%s.' % (self.na_helper.safe_get(self.parameters, ['san_application_template', 'name']), scope)) else: # no application template, fall back to LUN only scope = 'lun' if self.rest_app and scope != 'lun': app_cd_action = self.na_helper.get_cd_action(app_current, self.parameters) if app_cd_action == 'create': # check if target volume already exists cp_volume_name = self.parameters['name'] volume, error = rest_volume.get_volume(self.rest_api, self.parameters['vserver'], cp_volume_name) self.fail_on_error(error) if volume is not None: if scope == 'application': # volume already exists, but not as part of this application app_cd_action = 'convert' else: # default name already in use, ask user to clarify intent msg = "Error: volume '%s' already exists. Please use a different group name, or use 'application' scope. scope=%s" self.module.fail_json(msg=msg % (cp_volume_name, scope)) if app_cd_action is not None: actions.append('app_%s' % app_cd_action) if app_cd_action == 'create': self.validate_app_create() if app_cd_action is None and app_current is not None: app_modify, app_modify_warning = self.app_changes(scope) if app_modify: actions.append('app_modify') results['app_modify'] = dict(app_modify) if app_cd_action is None and scope != 'application': # actions at LUN level lun_path, from_lun_path = None, None from_name = self.parameters.get('from_name') if self.rest_app and app_current: # For LUNs created using a SAN application, we're getting lun paths from the backing storage lun_path = self.get_lun_path_from_backend(self.parameters['name']) if from_name is not None: from_lun_path = self.get_lun_path_from_backend(from_name) current = self.get_lun(self.parameters['name'], lun_path) if current is not None and lun_path is None: lun_path = current['path'] lun_cd_action = self.na_helper.get_cd_action(current, self.parameters) if lun_cd_action == 'create' and from_name is not None: # create by renaming existing LUN, if it really exists old_lun = self.get_lun(from_name, from_lun_path) lun_rename = self.na_helper.is_rename_action(old_lun, current) if lun_rename is None: self.module.fail_json(msg="Error renaming lun: %s does not exist" % from_name) if lun_rename: current = old_lun if from_lun_path is None: from_lun_path = current['path'] head, _sep, tail = from_lun_path.rpartition(from_name) if tail: self.module.fail_json(msg="Error renaming lun: %s does not match lun_path %s" % (from_name, from_lun_path)) lun_path = head + self.parameters['name'] lun_cd_action = None actions.append('lun_rename') app_modify_warning = None # reset warning as we found a match if lun_cd_action is not None: actions.append('lun_%s' % lun_cd_action) if lun_cd_action is None and self.parameters['state'] == 'present': # we already handled rename if required current.pop('name', None) lun_modify = self.na_helper.get_modified_attributes(current, self.parameters) if lun_modify: actions.append('lun_modify') results['lun_modify'] = dict(lun_modify) app_modify_warning = None # reset warning as we found a match if lun_cd_action and self.rest_app and app_current: msg = 'This module does not support %s a LUN by name %s a SAN application.' %\ ('adding', 'to') if lun_cd_action == 'create' else ('removing', 'from') if scope == 'auto': # ignore LUN not found, as name can be a group name self.warnings.append(msg + ". scope=%s, assuming 'application'" % scope) if not app_modify: self.na_helper.changed = False elif scope == 'lun': self.module.fail_json(msg=msg + ". scope=%s." % scope) lun_cd_action = None if lun_cd_action == 'create' and self.parameters.get('size') is None: self.module.fail_json(msg="size is a required parameter for create.") if self.na_helper.changed and not self.module.check_mode: if app_cd_action == 'create': self.create_san_application() elif app_cd_action == 'convert': self.convert_to_san_application(scope) elif app_cd_action == 'delete': self.rest_app.delete_application() elif lun_cd_action == 'create': self.create_lun() elif lun_cd_action == 'delete': self.delete_lun(lun_path) else: if app_modify: self.modify_san_application(app_modify) if lun_rename: self.rename_lun(from_lun_path, lun_path) size_changed = False if lun_modify and 'size' in lun_modify: # Ensure that size was actually changed. Please # read notes in 'resize_lun' function for details. size_changed = self.resize_lun(lun_path) lun_modify.pop('size') if lun_modify: self.modify_lun(lun_path, lun_modify) if not lun_modify and not lun_rename and not app_modify: # size may not have changed self.na_helper.changed = size_changed if app_modify_warning: self.warnings.append(app_modify_warning) results['changed'] = self.na_helper.changed results['actions'] = actions if self.warnings: results['warnings'] = self.warnings results.update(self.debug) self.module.exit_json(**results)
class NetAppONTAPQuotas(object): '''Class with quotas 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'), vserver=dict(required=True, type='str'), volume=dict(required=True, type='str'), quota_target=dict(required=False, type='str'), qtree=dict(required=False, type='str', default=""), type=dict(required=False, type='str', choices=['user', 'group', 'tree']), policy=dict(required=False, type='str'), set_quota_status=dict(required=False, type='bool'), perform_user_mapping=dict(required=False, type='bool'), file_limit=dict(required=False, type='str'), disk_limit=dict(required=False, type='str'), soft_file_limit=dict(required=False, type='str'), soft_disk_limit=dict(required=False, type='str'), threshold=dict(required=False, type='str'), activate_quota_on_change=dict(required=False, type='str', choices=['resize', 'reinitialize', 'none'], default='resize') )) self.module = AnsibleModule( argument_spec=self.argument_spec, required_by={ 'policy': ['quota_target', 'type'], 'perform_user_mapping': ['quota_target', 'type'], 'file_limit': ['quota_target', 'type'], 'disk_limit': ['quota_target', 'type'], 'soft_file_limit': ['quota_target', 'type'], 'soft_disk_limit': ['quota_target', 'type'], 'threshold': ['quota_target', 'type'], }, required_together=[['quota_target', 'type']], supports_check_mode=True ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # converted blank parameter to * as shown in vsim if self.parameters.get('quota_target') == "": self.parameters['quota_target'] = '*' 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_quota_status(self): """ Return details about the quota status :param: name : volume name :return: status of the quota. None if not found. :rtype: dict """ quota_status_get = netapp_utils.zapi.NaElement('quota-status') quota_status_get.translate_struct({ 'volume': self.parameters['volume'] }) try: result = self.server.invoke_successfully(quota_status_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quotas status info: %s' % to_native(error), exception=traceback.format_exc()) if result: return result['status'] return None def get_quotas(self): """ Get quota details :return: name of volume if quota exists, None otherwise """ if self.parameters.get('type') is None: return None quota_get = netapp_utils.zapi.NaElement('quota-list-entries-iter') query = { 'query': { 'quota-entry': { 'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'vserver': self.parameters['vserver'] } } } quota_get.translate_struct(query) if self.parameters.get('policy'): quota_get['query']['quota-entry'].add_new_child('policy', self.parameters['policy']) try: result = self.server.invoke_successfully(quota_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quotas info: %s' % to_native(error), exception=traceback.format_exc()) if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1: # if quota-target is '*', the query treats it as a wildcard. But a blank entry is represented as '*'. # Hence the need to loop through all records to find a match. for quota_entry in result.get_child_by_name('attributes-list').get_children(): quota_target = quota_entry.get_child_content('quota-target') if quota_target == self.parameters['quota_target']: return_values = {'volume': quota_entry.get_child_content('volume'), 'file_limit': quota_entry.get_child_content('file-limit'), 'disk_limit': quota_entry.get_child_content('disk-limit'), 'soft_file_limit': quota_entry.get_child_content('soft-file-limit'), 'soft_disk_limit': quota_entry.get_child_content('soft-disk-limit'), 'threshold': quota_entry.get_child_content('threshold')} value = self.na_helper.safe_get(quota_entry, ['perform-user-mapping']) if value is not None: return_values['perform_user_mapping'] = self.na_helper.get_value_for_bool(True, value) return return_values return None def quota_entry_set(self): """ Adds a quota entry """ options = {'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree']} if self.parameters.get('file_limit'): options['file-limit'] = self.parameters['file_limit'] if self.parameters.get('disk_limit'): options['disk-limit'] = self.parameters['disk_limit'] if self.parameters.get('perform_user_mapping') is not None: options['perform-user-mapping'] = str(self.parameters['perform_user_mapping']) if self.parameters.get('soft_file_limit'): options['soft-file-limit'] = self.parameters['soft_file_limit'] if self.parameters.get('soft_disk_limit'): options['soft-disk-limit'] = self.parameters['soft_disk_limit'] if self.parameters.get('threshold'): options['threshold'] = self.parameters['threshold'] if self.parameters.get('policy'): options['policy'] = self.parameters['policy'] set_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-set-entry', **options) try: self.server.invoke_successfully(set_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error adding/modifying quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def quota_entry_delete(self): """ Deletes a quota entry """ options = {'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree']} set_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-delete-entry', **options) if self.parameters.get('policy'): set_entry.add_new_child('policy', self.parameters['policy']) try: self.server.invoke_successfully(set_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def quota_entry_modify(self, modify_attrs): """ Modifies a quota entry """ options = {'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree']} options.update(modify_attrs) if self.parameters.get('file_limit'): options['file-limit'] = self.parameters['file_limit'] if self.parameters.get('disk_limit'): options['disk-limit'] = self.parameters['disk_limit'] if self.parameters.get('perform_user_mapping') is not None: options['perform-user-mapping'] = str(self.parameters['perform_user_mapping']) if self.parameters.get('soft_file_limit'): options['soft-file-limit'] = self.parameters['soft_file_limit'] if self.parameters.get('soft_disk_limit'): options['soft-disk-limit'] = self.parameters['soft_disk_limit'] if self.parameters.get('threshold'): options['threshold'] = self.parameters['threshold'] if self.parameters.get('policy'): options['policy'] = str(self.parameters['policy']) modify_entry = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-modify-entry', **options) try: self.server.invoke_successfully(modify_entry, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error modifying quota entry %s: %s' % (self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def on_or_off_quota(self, status): """ on or off quota """ quota = netapp_utils.zapi.NaElement.create_node_with_children( status, **{'volume': self.parameters['volume']}) try: self.server.invoke_successfully(quota, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error setting %s for %s: %s' % (status, self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def resize_quota(self): """ resize quota """ quota = netapp_utils.zapi.NaElement.create_node_with_children( 'quota-resize', **{'volume': self.parameters['volume']}) try: self.server.invoke_successfully(quota, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error setting %s for %s: %s' % ('quota-resize', self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def apply(self): """ Apply action to quotas """ netapp_utils.ems_log_event("na_ontap_quotas", self.server) cd_action = None modify_quota_status = None modify_quota = None quota_status = None current = self.get_quotas() if self.parameters.get('type') is not None: cd_action = self.na_helper.get_cd_action(current, self.parameters) if cd_action is None: modify_quota = self.na_helper.get_modified_attributes(current, self.parameters) if 'set_quota_status' in self.parameters or modify_quota: quota_status = self.get_quota_status() if 'set_quota_status' in self.parameters and quota_status is not None: quota_status_action = self.na_helper.get_modified_attributes( {'set_quota_status': True if quota_status == 'on' else False}, self.parameters) if quota_status_action: modify_quota_status = 'quota-on' if quota_status_action['set_quota_status'] else 'quota-off' if modify_quota is not None and modify_quota_status is None and quota_status == 'on': # do we need to resize or reinitialize: if self.parameters['activate_quota_on_change'] in ['resize', 'reinitialize']: modify_quota_status = self.parameters['activate_quota_on_change'] if self.na_helper.changed: if self.module.check_mode: pass else: if cd_action == 'create': self.quota_entry_set() elif cd_action == 'delete': self.quota_entry_delete() elif modify_quota is not None: for key in list(modify_quota): modify_quota[key.replace("_", "-")] = modify_quota.pop(key) self.quota_entry_modify(modify_quota) if modify_quota_status in ['quota-off', 'quota-on']: self.on_or_off_quota(modify_quota_status) elif modify_quota_status == 'resize': self.resize_quota() elif modify_quota_status == 'reinitialize': self.on_or_off_quota('quota-off') time.sleep(10) # status switch interval self.on_or_off_quota('quota-on') self.module.exit_json(changed=self.na_helper.changed)