Server IP : 85.214.239.14 / Your IP : 18.118.205.21 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 : /usr/lib/python3/dist-packages/ansible_collections/netapp/ontap/plugins/modules/ |
Upload File : |
#!/usr/bin/python # (c) 2018-2023, 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 ''' na_ontap_quotas ''' DOCUMENTATION = ''' module: na_ontap_quotas short_description: NetApp ONTAP Quotas extends_documentation_fragment: - netapp.ontap.netapp.na_ontap version_added: 2.8.0 author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> description: - Set/Modify/Delete quota on ONTAP options: state: description: - Whether the specified quota should exist or not. choices: ['present', 'absent'] default: present type: str vserver: required: true description: - Name of the vserver to use. type: str volume: description: - The name of the volume that the quota resides on. required: true type: str quota_target: description: - The quota target of the type specified. - Required to create or modify a rule. - users and group takes quota_target value in REST. - For default user and group quota rules, the quota_target must be specified as "". type: str qtree: description: - Name of the qtree for the quota. - For user or group rules, it can be the qtree name or "" if no qtree. - For tree type rules, this field must be "". default: "" type: str type: description: - The type of quota rule - Required to create or modify a rule. choices: ['user', 'group', 'tree'] type: str policy: description: - Name of the quota policy from which the quota rule should be obtained. - Only supported with ZAPI. - Multiple alternative quota policies (active and backup) are not supported in REST. - REST manages the quota rules of the active policy. type: str set_quota_status: description: - Whether the specified volume should have quota status on or off. type: bool perform_user_mapping: description: - Whether quota management will perform user mapping for the user specified in quota-target. - User mapping can be specified only for a user quota rule. type: bool aliases: ['user_mapping'] version_added: 20.12.0 file_limit: description: - The number of files that the target can have. - use '-' to reset file limit. type: str disk_limit: description: - The amount of disk space that is reserved for the target. - Expects a number followed with B (for bytes), KB, MB, GB, TB. - If the unit is not present KB is used by default. - Examples - 10MB, 20GB, 1TB, 20B, 10. - In REST, if limit is less than 1024 bytes, the value is rounded up to 1024 bytes. - use '-' to reset disk limit. type: str soft_file_limit: description: - The number of files the target would have to exceed before a message is logged and an SNMP trap is generated. - use '-' to reset soft file limit. type: str soft_disk_limit: description: - The amount of disk space the target would have to exceed before a message is logged and an SNMP trap is generated. - See C(disk_limit) for format description. - In REST, if limit is less than 1024 bytes, the value is rounded up to 1024 bytes. - use '-' to reset soft disk limit. type: str threshold: description: - The amount of disk space the target would have to exceed before a message is logged. - See C(disk_limit) for format description. - Only supported with ZAPI. type: str activate_quota_on_change: description: - Method to use to activate quota on a change. - Default value is 'resize' in ZAPI. - With REST, Changes to quota rule limits C(file_limit), C(disk_limit), C(soft_file_limit), and C(soft_disk_limit) are applied automatically without requiring a quota resize operation. choices: ['resize', 'reinitialize', 'none'] type: str version_added: 20.12.0 ''' EXAMPLES = """ - name: Create quota rule in ZAPI. netapp.ontap.na_ontap_quotas: state: present vserver: ansible volume: ansible quota_target: user1 type: user policy: ansible file_limit: 2 disk_limit: 3 set_quota_status: True hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: Resize quota netapp.ontap.na_ontap_quotas: state: present vserver: ansible volume: ansible quota_target: user1 type: user policy: ansible file_limit: 2 disk_limit: 3 set_quota_status: True activate_quota_on_change: resize hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: Reinitialize quota netapp.ontap.na_ontap_quotas: state: present vserver: ansible volume: ansible quota_target: user1 type: user policy: ansible file_limit: 2 disk_limit: 3 set_quota_status: True activate_quota_on_change: reinitialize hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: modify quota netapp.ontap.na_ontap_quotas: state: present vserver: ansible volume: ansible quota_target: user1 type: user policy: ansible file_limit: 2 disk_limit: 3 threshold: 3 set_quota_status: False hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: Delete quota netapp.ontap.na_ontap_quotas: state: absent vserver: ansible volume: ansible quota_target: /vol/ansible type: user policy: ansible hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: Add/Set quota rule for type user in REST. netapp.ontap.na_ontap_quotas: state: present vserver: ansible volume: ansible quota_target: "user1,user2" qtree: qtree type: user file_limit: 2 disk_limit: 3 set_quota_status: True hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: Modify quota reset file limit and modify disk limit. netapp.ontap.na_ontap_quotas: state: present vserver: ansible volume: ansible quota_target: "user1,user2" qtree: qtree type: user file_limit: "-" disk_limit: 100 set_quota_status: True hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: Add/Set quota rule for type group in REST. netapp.ontap.na_ontap_quotas: state: present vserver: ansible volume: ansible quota_target: group1 qtree: qtree type: group file_limit: 2 disk_limit: 3 set_quota_status: True hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: Add/Set quota rule for type qtree in REST. netapp.ontap.na_ontap_quotas: state: present vserver: ansible volume: ansible quota_target: qtree1 type: qtree file_limit: 2 disk_limit: 3 set_quota_status: True hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" """ RETURN = """ """ import time import traceback import re 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 import rest_generic import ansible_collections.netapp.ontap.plugins.module_utils.rest_response_helpers as rrh class NetAppONTAPQuotas: '''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', aliases=['user_mapping']), 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']) )) self.module = AnsibleModule( argument_spec=self.argument_spec, supports_check_mode=True, 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')] ) self.na_helper = NetAppModule() self.parameters = self.na_helper.set_parameters(self.module.params) # Set up Rest API self.rest_api = netapp_utils.OntapRestAPI(self.module) unsupported_rest_properties = ['policy', 'threshold'] self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, unsupported_rest_properties) self.volume_uuid = None # volume UUID after quota rule creation, used for on or off quota status self.quota_uuid = None self.warn_msg = None self.validate_parameters_ZAPI_REST() 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 validate_parameters_ZAPI_REST(self): if self.use_rest: if self.parameters.get('type') == 'tree': if self.parameters['qtree']: self.module.fail_json(msg="Error: Qtree cannot be specified for a tree type rule, it should be ''.") # valid qtree name for ZAPI is /vol/vol_name/qtree_name and REST is qtree_name. if '/' in self.parameters.get('quota_target', ''): self.parameters['quota_target'] = self.parameters['quota_target'].split('/')[-1] for quota_limit in ['file_limit', 'disk_limit', 'soft_file_limit', 'soft_disk_limit']: if self.parameters.get(quota_limit) == '-1': self.parameters[quota_limit] = '-' else: # converted blank parameter to * as shown in vsim if self.parameters.get('quota_target') == "": self.parameters['quota_target'] = '*' if not self.parameters.get('activate_quota_on_change'): self.parameters['activate_quota_on_change'] = 'resize' size_format_error_message = "input string is not a valid size format. A valid size format is constructed as" \ "<integer><size unit>. For example, '10MB', '10KB'. Only numeric input is also valid." \ "The default unit size is KB." if self.parameters.get('disk_limit') and self.parameters['disk_limit'] != '-' and not self.convert_to_kb_or_bytes('disk_limit'): self.module.fail_json(msg='disk_limit %s' % size_format_error_message) if self.parameters.get('soft_disk_limit') and self.parameters['soft_disk_limit'] != '-' and not self.convert_to_kb_or_bytes('soft_disk_limit'): self.module.fail_json(msg='soft_disk_limit %s' % size_format_error_message) if self.parameters.get('threshold') and self.parameters['threshold'] != '-' and not self.convert_to_kb_or_bytes('threshold'): self.module.fail_json(msg='threshold %s' % size_format_error_message) 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()) return result['status'] def get_quotas_with_retry(self, get_request, policy): return_values = None if policy is not None: get_request['query']['quota-entry'].add_new_child('policy', policy) try: result = self.server.invoke_successfully(get_request, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: # Bypass a potential issue in ZAPI when policy is not set in the query # https://github.com/ansible-collections/netapp.ontap/issues/4 # BURT1076601 Loop detected in next() for table quota_rules_zapi if policy is None and 'Reason - 13001:success' in to_native(error): result = None return_values = self.debug_quota_get_error(error) else: self.module.fail_json(msg='Error fetching quotas info for policy %s: %s' % (policy, to_native(error)), exception=traceback.format_exc()) return result, return_values def get_quotas(self, policy=None): """ Get quota details :return: name of volume if quota exists, None otherwise """ if self.parameters.get('type') is None: return None if policy is None: policy = self.parameters.get('policy') 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'], 'qtree': self.parameters['qtree'] or '""' } } } quota_get.translate_struct(query) result, return_values = self.get_quotas_with_retry(quota_get, policy) if result is None: return return_values 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 get_quota_policies(self): """ Get list of quota policies :return: list of quota policies (empty list if None found) """ quota_policy_get = netapp_utils.zapi.NaElement('quota-policy-get-iter') query = { 'query': { 'quota-policy-info': { 'vserver': self.parameters['vserver'] } } } quota_policy_get.translate_struct(query) try: result = self.server.invoke_successfully(quota_policy_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching quota policies: %s' % to_native(error), exception=traceback.format_exc()) return ([policy['policy-name'] for policy in result['attributes-list'].get_children()] if result.get_child_by_name('attributes-list') else []) def debug_quota_get_error(self, error): policies = self.get_quota_policies() entries = {} for policy in policies: entries[policy] = self.get_quotas(policy) if len(policies) == 1: self.module.warn('retried with success using policy="%s" on "13001:success" ZAPI error.' % policy) return entries[policies[0]] self.module.fail_json(msg='Error fetching quotas info: %s - current vserver policies: %s, details: %s' % (to_native(error), policies, entries)) 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']} self.set_zapi_options(options) 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 """ for key in list(modify_attrs): modify_attrs[key.replace("_", "-")] = modify_attrs.pop(key) options = {'volume': self.parameters['volume'], 'quota-target': self.parameters['quota_target'], 'quota-type': self.parameters['type'], 'qtree': self.parameters['qtree']} options.update(modify_attrs) self.set_zapi_options(options) 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 set_zapi_options(self, options): 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'] def on_or_off_quota(self, status, cd_action=None): """ 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: if cd_action == 'delete' and status == 'quota-on' and '14958:No valid quota rules found' in to_native(error): # ignore error on quota-on, as all rules have been deleted self.module.warn('Last rule deleted, quota is off.') return 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, cd_action=None): """ 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: if cd_action == 'delete' and '14958:No valid quota rules found' in to_native(error): # ignore error on quota-on, as all rules have been deleted self.module.warn('Last rule deleted, but quota is on as resize is not allowed.') return self.module.fail_json(msg='Error setting %s for %s: %s' % ('quota-resize', self.parameters['volume'], to_native(error)), exception=traceback.format_exc()) def get_quotas_rest(self): """ Retrieves quotas with rest API. If type is user then it returns all possible combinations of user name records. Report api is used to fetch file and disk limit info """ if not self.use_rest: return self.get_quotas() query = {'svm.name': self.parameters.get('vserver'), 'volume.name': self.parameters.get('volume'), 'type': self.parameters.get('type'), 'fields': 'svm.uuid,' 'svm.name,' 'space.hard_limit,' 'files.hard_limit,' 'user_mapping,' 'qtree.name,' 'type,' 'space.soft_limit,' 'files.soft_limit,' 'volume.uuid,' 'users.name,' 'group.name,'} # set qtree name in query for type user and group if not ''. if self.parameters['qtree']: query['qtree.name'] = self.parameters['qtree'] if self.parameters.get('quota_target'): type = self.parameters['type'] field_name = 'users.name' if type == 'user' else 'group.name' if type == 'group' else 'qtree.name' query[field_name] = self.parameters['quota_target'] api = 'storage/quota/rules' # If type: user, get quota rules api returns users which has name starts with input target user names. # Example of users list in a record: # users: [{'name': 'quota_user'}], users: [{'name': 'quota_user'}, {'name': 'quota'}] records, error = rest_generic.get_0_or_more_records(self.rest_api, api, query) if error: self.module.fail_json(msg="Error on getting quota rule info: %s" % error) if records: record = None for item in records: # along with user/group, qtree should also match to get current quota. # for type user/group if qtree is not set in create, its not returned in GET, make desired qtree None if ''. desired_qtree = self.parameters['qtree'] if self.parameters.get('qtree') else None current_qtree = self.na_helper.safe_get(item, ['qtree', 'name']) type = self.parameters.get('type') if type in ['user', 'group']: if desired_qtree != current_qtree: continue if type == 'user': desired_users = self.parameters['quota_target'].split(',') current_users = [user['name'] for user in item['users']] if set(current_users) == set(desired_users): record = item break elif item['group']['name'] == self.parameters['quota_target']: record = item break # for type tree, desired quota_target should match current tree. elif type == 'tree' and current_qtree == self.parameters['quota_target']: record = item break if record: self.volume_uuid = record['volume']['uuid'] self.quota_uuid = record['uuid'] current = { 'soft_file_limit': self.na_helper.safe_get(record, ['files', 'soft_limit']), 'disk_limit': self.na_helper.safe_get(record, ['space', 'hard_limit']), 'soft_disk_limit': self.na_helper.safe_get(record, ['space', 'soft_limit']), 'file_limit': self.na_helper.safe_get(record, ['files', 'hard_limit']), 'perform_user_mapping': self.na_helper.safe_get(record, ['user_mapping']), } # Rest allows reset quota limits using '-', convert None to '-' to avoid idempotent issue. current['soft_file_limit'] = '-' if current['soft_file_limit'] is None else str(current['soft_file_limit']) current['disk_limit'] = '-' if current['disk_limit'] is None else str(current['disk_limit']) current['soft_disk_limit'] = '-' if current['soft_disk_limit'] is None else str(current['soft_disk_limit']) current['file_limit'] = '-' if current['file_limit'] is None else str(current['file_limit']) return current return None def quota_entry_set_rest(self): """ quota_entry_set with rest API. for type: 'user' and 'group', quota_target is used. value for user, group and qtree should be passed as '' """ if not self.use_rest: return self.quota_entry_set() body = {'svm.name': self.parameters.get('vserver'), 'volume.name': self.parameters.get('volume'), 'type': self.parameters.get('type'), 'qtree.name': self.parameters.get('qtree')} quota_target = self.parameters.get('quota_target') if self.parameters.get('type') == 'user': body['users.name'] = quota_target.split(',') elif self.parameters.get('type') == 'group': body['group.name'] = quota_target if self.parameters.get('type') == 'tree': body['qtree.name'] = quota_target if 'file_limit' in self.parameters: body['files.hard_limit'] = self.parameters.get('file_limit') if 'soft_file_limit' in self.parameters: body['files.soft_limit'] = self.parameters.get('soft_file_limit') if 'disk_limit' in self.parameters: body['space.hard_limit'] = self.parameters.get('disk_limit') if 'soft_disk_limit' in self.parameters: body['space.soft_limit'] = self.parameters.get('soft_disk_limit') if 'perform_user_mapping' in self.parameters: body['user_mapping'] = self.parameters.get('perform_user_mapping') query = {'return_records': 'true'} # in order to capture UUID api = 'storage/quota/rules' response, error = rest_generic.post_async(self.rest_api, api, body, query) if error: if "job reported error:" in error and "entry doesn't exist" in error: # ignore RBAC issue with FSx - BURT1525998 self.module.warn('Ignoring job status, assuming success.') elif '5308568' in error: # code: 5308568 requires quota to be disabled/enabled to take effect. # code: 5308571 - rule created, but to make it active reinitialize quota. # reinitialize will disable/enable quota. self.form_warn_msg_rest('create', '5308568') elif '5308571' in error: self.form_warn_msg_rest('create', '5308571') else: self.module.fail_json(msg="Error on creating quotas rule: %s" % error) # fetch volume uuid as response will be None if above code error occurs. self.volume_uuid = self.get_quota_status_or_volume_id_rest(get_volume=True) # skip fetching volume uuid from response if volume_uuid already populated. if not self.volume_uuid and response: record, error = rrh.check_for_0_or_1_records(api, response, error, query) if not error and record and not record['volume']['uuid']: error = 'volume uuid key not present in %s:' % record if error: self.module.fail_json(msg='Error on getting volume uuid: %s' % error) if record: self.volume_uuid = record['volume']['uuid'] def quota_entry_delete_rest(self): """ quota_entry_delete with rest API. """ if not self.use_rest: return self.quota_entry_delete() api = 'storage/quota/rules' dummy, error = rest_generic.delete_async(self.rest_api, api, self.quota_uuid) if error is not None: # delete operation succeeded, but reinitialize is required. # code: 5308569 requires quota to be disabled/enabled to take effect. # code: 5308572 error occurs when trying to delete last rule. if '5308569' in error: self.form_warn_msg_rest('delete', '5308569') elif '5308572' in error: self.form_warn_msg_rest('delete', '5308572') else: self.module.fail_json(msg="Error on deleting quotas rule: %s" % error) def quota_entry_modify_rest(self, modify_quota): """ quota_entry_modify with rest API. User mapping cannot be turned on for multiuser quota rules. """ if not self.use_rest: return self.quota_entry_modify(modify_quota) body = {} if 'disk_limit' in modify_quota: body['space.hard_limit'] = modify_quota['disk_limit'] if 'file_limit' in modify_quota: body['files.hard_limit'] = modify_quota['file_limit'] if 'soft_disk_limit' in modify_quota: body['space.soft_limit'] = modify_quota['soft_disk_limit'] if 'soft_file_limit' in modify_quota: body['files.soft_limit'] = modify_quota['soft_file_limit'] if 'perform_user_mapping' in modify_quota: body['user_mapping'] = modify_quota['perform_user_mapping'] api = 'storage/quota/rules' dummy, error = rest_generic.patch_async(self.rest_api, api, self.quota_uuid, body) if error is not None: # limits are modified but internal error, require reinitialize quota. if '5308567' in error: self.form_warn_msg_rest('modify', '5308567') else: self.module.fail_json(msg="Error on modifying quotas rule: %s" % error) def get_quota_status_or_volume_id_rest(self, get_volume=None): """ Get the status info on or off """ if not self.use_rest: return self.get_quota_status() api = 'storage/volumes' params = {'name': self.parameters['volume'], 'svm.name': self.parameters['vserver'], 'fields': 'quota.state,uuid'} record, error = rest_generic.get_one_record(self.rest_api, api, params) if error: msg = "volume uuid" if get_volume else "quota status info" self.module.fail_json(msg="Error on getting %s: %s" % (msg, error)) if record: return record['uuid'] if get_volume else record['quota']['state'] self.module.fail_json(msg="Error: Volume %s in SVM %s does not exist" % (self.parameters['volume'], self.parameters['vserver'])) def on_or_off_quota_rest(self, status, cd_action=None): """ quota_entry_modify quota status with rest API. """ if not self.use_rest: return self.on_or_off_quota(status, cd_action) body = {} body['quota.enabled'] = status == 'quota-on' api = 'storage/volumes' if not self.volume_uuid: self.volume_uuid = self.get_quota_status_or_volume_id_rest(get_volume=True) dummy, error = rest_generic.patch_async(self.rest_api, api, self.volume_uuid, body) if error is not None: self.module.fail_json(msg='Error setting %s for %s: %s' % (status, self.parameters['volume'], to_native(error))) def form_warn_msg_rest(self, action, code): start_msg = "Quota policy rule %s opertation succeeded. " % action end_msg = "reinitialize(disable and enable again) the quota for volume %s " \ "in SVM %s." % (self.parameters['volume'], self.parameters['vserver']) msg = 'unexpected code: %s' % code if code == '5308572': msg = "However the rule is still being enforced. To stop enforcing, " if code in ['5308568', '5308569', '5308567']: msg = "However quota resize failed due to an internal error. To make quotas active, " if code == '5308571': msg = "but quota resize is skipped. To make quotas active, " self.warn_msg = start_msg + msg + end_msg def apply(self): """ Apply action to quotas """ cd_action = None modify_quota_status = None modify_quota = None current = self.get_quotas_rest() 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) quota_status = self.get_quota_status_or_volume_id_rest() if 'set_quota_status' in self.parameters and quota_status is not None: # if 'set_quota_status' == True in create, sometimes there is delay in status update from 'initializing' -> 'on'. # if quota_status == 'on' and options(set_quota_status == True and activate_quota_on_change == 'resize'), # sometimes there is delay in status update from 'resizing' -> 'on' set_quota_status = True if quota_status in ('on', 'resizing', 'initializing') else False quota_status_action = self.na_helper.get_modified_attributes({'set_quota_status': set_quota_status}, self.parameters) if quota_status_action: modify_quota_status = 'quota-on' if quota_status_action['set_quota_status'] else 'quota-off' if (self.parameters.get('activate_quota_on_change') in ['resize', 'reinitialize'] and (cd_action is not None or modify_quota is not None) and modify_quota_status is None and quota_status in ('on', None)): modify_quota_status = self.parameters['activate_quota_on_change'] if self.na_helper.changed and not self.module.check_mode: if cd_action == 'create': self.quota_entry_set_rest() elif cd_action == 'delete': self.quota_entry_delete_rest() elif modify_quota: self.quota_entry_modify_rest(modify_quota) if modify_quota_status in ['quota-off', 'quota-on']: self.on_or_off_quota_rest(modify_quota_status) elif modify_quota_status == 'resize': if not self.use_rest: self.resize_quota(cd_action) elif modify_quota_status == 'reinitialize': self.on_or_off_quota_rest('quota-off') time.sleep(10) # status switch interval self.on_or_off_quota_rest('quota-on', cd_action) # if warn message and quota not reinitialize, throw warnings to reinitialize in REST. if self.warn_msg and modify_quota_status != 'reinitialize': self.module.warn(self.warn_msg) result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify_quota, extra_responses={'modify_quota_status': modify_quota_status}) self.module.exit_json(**result) def convert_to_kb_or_bytes(self, option): """ convert input to kb, and set to self.parameters. :param option: disk_limit or soft_disk_limit. :return: boolean if it can be converted. """ self.parameters[option].replace(' ', '') slices = re.findall(r"\d+|\D+", self.parameters[option]) if len(slices) < 1 or len(slices) > 2: return False if not slices[0].isdigit(): return False if len(slices) > 1 and slices[1].lower() not in ['b', 'kb', 'mb', 'gb', 'tb']: return False # force kb as the default unit for REST if len(slices) == 1 and self.use_rest: slices = (slices[0], 'kb') if len(slices) > 1: if not self.use_rest: # conversion to KB self.parameters[option] = str(int(slices[0]) * netapp_utils.POW2_BYTE_MAP[slices[1].lower()] // 1024) else: # conversion to Bytes self.parameters[option] = str(int(slices[0]) * netapp_utils.POW2_BYTE_MAP[slices[1].lower()]) if self.use_rest: # Rounding off the converted bytes self.parameters[option] = str(((int(self.parameters[option]) + 1023) // 1024) * 1024) return True def main(): '''Execute action''' quota_obj = NetAppONTAPQuotas() quota_obj.apply() if __name__ == '__main__': main()