Server IP : 85.214.239.14 / Your IP : 18.116.27.225 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /lib/python3/dist-packages/ansible_collections/netapp/ontap/plugins/modules/ |
Upload File : |
#!/usr/bin/python # (c) 2019-2022, NetApp, Inc # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ na_ontap_flexcache """ from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = ''' short_description: NetApp ONTAP FlexCache - create/delete relationship author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> description: - Create/Delete FlexCache volume relationships. - This module does not modify an existing FlexCache volume with two exceptions. - When using REST, a prepopulate can be started on an exising FlexCache volume. - When using REST, the volume can be mounted or unmounted. Set path to '' to unmount it. - It is required the volume is mounted to prepopulate it. - Some actions are also available through the na_ontap_volume. extends_documentation_fragment: - netapp.ontap.netapp.na_ontap module: na_ontap_flexcache version_added: 2.8.0 options: state: choices: ['present', 'absent'] description: - Whether the specified relationship should exist or not. default: present type: str origin_volume: description: - Name of the origin volume for the FlexCache. - Required for creation. type: str origin_vserver: description: - Name of the origin vserver for the FlexCache. - Required for creation. type: str origin_cluster: description: - Name of the origin cluster for the FlexCache. - Defaults to cluster associated with target vserver if absent. - Not used for creation. type: str name: description: - Name of the target volume for the FlexCache. required: true type: str aliases: ['volume'] version_added: 21.3.0 junction_path: description: - Junction path of the cache volume. type: str aliases: ['path'] auto_provision_as: description: - Use this parameter to automatically select existing aggregates for volume provisioning. Eg flexgroup - Note that the fastest aggregate type with at least one aggregate on each node of the cluster will be selected. - Ignored when using REST - omit aggr_list for automatic selection. type: str size: description: - Size of cache volume. type: int size_unit: description: - The unit used to interpret the size parameter. choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'] type: str default: gb vserver: description: - Name of the target vserver for the FlexCache. - Note that hostname, username, password are intended for the target vserver. required: true type: str aggr_list: description: - List of aggregates to host target FlexCache volume. type: list elements: str aliases: ['aggregates'] aggr_list_multiplier: description: - Aggregate list repeat count. - REST - Number of FlexCache constituents per aggregate when the C(aggregates) field is mentioned. type: int aliases: ['constituents_per_aggregate'] force_unmount: description: - Unmount FlexCache volume. Delete the junction path at which the volume is mounted before deleting the FlexCache relationship. type: bool default: false force_offline: description: - Offline FlexCache volume before deleting the FlexCache relationship. - The volume will be destroyed and data can be lost. type: bool default: false time_out: description: - time to wait for flexcache creation or deletion in seconds - if 0, the request is asynchronous - default is set to 3 minutes type: int default: 180 prepopulate: version_added: 21.3.0 description: - prepopulate FlexCache with data from origin volume. - requires ONTAP 9.8 or later, and REST support. - dir_paths must be set for this option to be effective. type: dict suboptions: dir_paths: description: - List of directory paths in the owning SVM's namespace at which the FlexCache volume is mounted. - Path must begin with '/'. type: list elements: str required: true exclude_dir_paths: description: - Directory path which needs to be excluded from prepopulation. - Path must begin with '/'. - Requires ONTAP 9.9 or later. type: list elements: str recurse: description: - Specifies whether or not the prepopulate action should search through the directory-path recursively. - If not set, the default value 'true' is used. type: bool force_prepopulate_if_already_created: description: - by default, this module will start a prepopulate task each time it is called, and is not idempotent. - if set to false, the prepopulate task is not started if the FlexCache already exists. type: bool default: true ''' EXAMPLES = """ - name: Create FlexCache netapp.ontap.na_ontap_flexcache: state: present origin_volume: test_src name: test_dest origin_vserver: ansible_src vserver: ansible_dest hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" - name: Delete FlexCache netapp.ontap.na_ontap_flexcache: state: absent name: test_dest vserver: ansible_dest hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" """ RETURN = """ """ import time import traceback from ansible.module_utils.basic import AnsibleModule from ansible.module_utils._text import to_native import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic, rest_volume class NetAppONTAPFlexCache: """ Class with FlexCache methods """ 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'), origin_volume=dict(required=False, type='str'), # origins[0] origin_vserver=dict(required=False, type='str'), # origins[0] origin_cluster=dict(required=False, type='str'), # origins[0] auto_provision_as=dict(required=False, type='str'), # ignored with REST name=dict(required=True, type='str', aliases=['volume']), junction_path=dict(required=False, type='str', aliases=['path']), size=dict(required=False, type='int'), size_unit=dict(default='gb', choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'], type='str'), vserver=dict(required=True, type='str'), aggr_list=dict(required=False, type='list', elements='str', aliases=['aggregates']), aggr_list_multiplier=dict(required=False, type='int', aliases=['constituents_per_aggregate']), force_offline=dict(required=False, type='bool', default=False), force_unmount=dict(required=False, type='bool', default=False), time_out=dict(required=False, type='int', default=180), prepopulate=dict(required=False, type='dict', options=dict( dir_paths=dict(required=True, type='list', elements='str'), exclude_dir_paths=dict(required=False, type='list', elements='str'), recurse=dict(required=False, type='bool'), force_prepopulate_if_already_created=dict(required=False, type='bool', default=True), )) )) self.module = AnsibleModule( argument_spec=self.argument_spec, mutually_exclusive=[ ('aggr_list', 'auto_provision_as'), ], supports_check_mode=True ) self.na_helper = NetAppModule(self.module) self.parameters = self.na_helper.set_parameters(self.module.params) if self.parameters.get('size'): self.parameters['size'] = self.parameters['size'] * netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']] # setup later if required self.origin_server = None self.rest_api = netapp_utils.OntapRestAPI(self.module) self.use_rest = self.rest_api.is_rest() ontap_98_options = ['prepopulate'] if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 8) and any(x in self.parameters for x in ontap_98_options): self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version(ontap_98_options, version='9.8')) if 'prepopulate' in self.parameters: # sanitize the dictionary, as Ansible fills everything with None values self.parameters['prepopulate'] = self.na_helper.filter_out_none_entries(self.parameters['prepopulate']) ontap_99_options = ['exclude_dir_paths'] if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9) and any(x in self.parameters['prepopulate'] for x in ontap_99_options): options = ['prepopulate: ' + x for x in ontap_99_options] self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version(options, version='9.9')) 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 add_parameter_to_dict(self, adict, name, key, tostr=False): ''' add defined parameter (not None) to adict using key ''' value = self.parameters.get(name) if value is not None: adict[key] = str(value) if tostr else value def get_job(self, jobid, server): """ Get job details by id """ job_get = netapp_utils.zapi.NaElement('job-get') job_get.add_new_child('job-id', jobid) try: result = server.invoke_successfully(job_get, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: if to_native(error.code) == "15661": # Not found return None self.module.fail_json(msg='Error fetching job info: %s' % to_native(error), exception=traceback.format_exc()) job_info = result.get_child_by_name('attributes').get_child_by_name('job-info') return { 'job-progress': job_info['job-progress'], 'job-state': job_info['job-state'], 'job-completion': job_info['job-completion'] if job_info.get_child_by_name('job-completion') is not None else None } def check_job_status(self, jobid): """ Loop until job is complete """ server = self.server sleep_time = 5 time_out = self.parameters['time_out'] while time_out > 0: results = self.get_job(jobid, server) # If running as cluster admin, the job is owned by cluster vserver # rather than the target vserver. if results is None and server == self.server: results = netapp_utils.get_cserver(self.server) server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) continue if results is None: error = 'cannot locate job with id: %s' % jobid break if results['job-state'] in ('queued', 'running'): time.sleep(sleep_time) time_out -= sleep_time continue if results['job-state'] in ('success', 'failure'): break else: self.module.fail_json(msg='Unexpected job status in: %s' % repr(results)) if results is not None: if results['job-state'] == 'success': error = None elif results['job-state'] in ('queued', 'running'): error = 'job completion exceeded expected timer of: %s seconds' % self.parameters['time_out'] elif results['job-completion'] is not None: error = results['job-completion'] else: error = results['job-progress'] return error def flexcache_get_iter(self): """ Compose NaElement object to query current FlexCache relation """ options = {'volume': self.parameters['name']} self.add_parameter_to_dict(options, 'origin_volume', 'origin-volume') self.add_parameter_to_dict(options, 'origin_vserver', 'origin-vserver') self.add_parameter_to_dict(options, 'origin_cluster', 'origin-cluster') flexcache_info = netapp_utils.zapi.NaElement.create_node_with_children( 'flexcache-info', **options) query = netapp_utils.zapi.NaElement('query') query.add_child_elem(flexcache_info) flexcache_get_iter = netapp_utils.zapi.NaElement('flexcache-get-iter') flexcache_get_iter.add_child_elem(query) return flexcache_get_iter def flexcache_get(self): """ Get current FlexCache relations :return: Dictionary of current FlexCache details if query successful, else None """ if self.use_rest: api = 'storage/flexcache/flexcaches' query = { 'name': self.parameters['name'], 'svm.name': self.parameters['vserver'] } if 'origin_cluster' in self.parameters: query['origin.cluster.name'] = self.parameters['origin_cluster'] fields = 'svm,name,uuid,path' flexcache, error = rest_generic.get_one_record(self.rest_api, api, query, fields) self.na_helper.fail_on_error(error) if flexcache is None: return None return dict( vserver=flexcache['svm']['name'], name=flexcache['name'], uuid=flexcache['uuid'], junction_path=flexcache.get('path') ) flexcache_get_iter = self.flexcache_get_iter() flex_info = {} try: result = self.server.invoke_successfully(flexcache_get_iter, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error fetching FlexCache 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: flexcache_info = result.get_child_by_name('attributes-list') \ .get_child_by_name('flexcache-info') flex_info['origin_cluster'] = flexcache_info.get_child_content('origin-cluster') flex_info['origin_volume'] = flexcache_info.get_child_content('origin-volume') flex_info['origin_vserver'] = flexcache_info.get_child_content('origin-vserver') flex_info['size'] = flexcache_info.get_child_content('size') flex_info['name'] = flexcache_info.get_child_content('volume') flex_info['vserver'] = flexcache_info.get_child_content('vserver') return flex_info if result.get_child_by_name('num-records') and \ int(result.get_child_content('num-records')) > 1: msg = 'Multiple records found for %s:' % self.parameters['name'] self.module.fail_json(msg='Error fetching FlexCache info: %s' % msg) return None def flexcache_rest_create_body(self, mappings): ''' maps self.parameters to REST API body attributes, using mappings to identify fields to add ''' body = {} for key, value in mappings.items(): if key in self.parameters: if key == 'aggr_list': body[value] = [dict(name=aggr) for aggr in self.parameters[key]] else: body[value] = self.parameters[key] elif key == 'origins': # this is an artificial key, to match the REST list of dict structure origin = dict( volume=dict(name=self.parameters['origin_volume']), svm=dict(name=self.parameters['origin_vserver']) ) body[value] = [origin] return body def flexcache_rest_create(self): ''' use POST to create a FlexCache ''' mappings = dict( name='name', vserver='svm.name', junction_path='path', size='size', aggr_list='aggregates', aggr_list_multiplier='constituents_per_aggregate', origins='origins', prepopulate='prepopulate' ) body = self.flexcache_rest_create_body(mappings) api = 'storage/flexcache/flexcaches' response, error = rest_generic.post_async(self.rest_api, api, body, job_timeout=self.parameters['time_out']) self.na_helper.fail_on_error(error) return response def flexcache_rest_modify(self, uuid): ''' use PATCH to start prepopulating a FlexCache ''' mappings = dict( # name cannot be set, though swagger example shows it prepopulate='prepopulate' ) body = self.flexcache_rest_create_body(mappings) api = 'storage/flexcache/flexcaches' response, error = rest_generic.patch_async(self.rest_api, api, uuid, body, job_timeout=self.parameters['time_out']) self.na_helper.fail_on_error(error) return response def flexcache_create_async(self): """ Create a FlexCache relationship """ options = {'origin-volume': self.parameters['origin_volume'], 'origin-vserver': self.parameters['origin_vserver'], 'volume': self.parameters['name']} self.add_parameter_to_dict(options, 'junction_path', 'junction-path') self.add_parameter_to_dict(options, 'auto_provision_as', 'auto-provision-as') self.add_parameter_to_dict(options, 'size', 'size', tostr=True) if self.parameters.get('aggr_list') and self.parameters.get('aggr_list_multiplier'): self.add_parameter_to_dict(options, 'aggr_list_multiplier', 'aggr-list-multiplier', tostr=True) flexcache_create = netapp_utils.zapi.NaElement.create_node_with_children('flexcache-create-async', **options) if self.parameters.get('aggr_list'): aggregates = netapp_utils.zapi.NaElement('aggr-list') for aggregate in self.parameters['aggr_list']: aggregates.add_new_child('aggr-name', aggregate) flexcache_create.add_child_elem(aggregates) try: result = self.server.invoke_successfully(flexcache_create, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error creating FlexCache: %s' % to_native(error), exception=traceback.format_exc()) results = {} for key in ('result-status', 'result-jobid'): if result.get_child_by_name(key): results[key] = result[key] return results def flexcache_create(self): """ Create a FlexCache relationship Check job status """ if self.use_rest: return self.flexcache_rest_create() results = self.flexcache_create_async() status = results.get('result-status') if status == 'in_progress' and 'result-jobid' in results: if self.parameters['time_out'] == 0: # asynchronous call, assuming success! return error = self.check_job_status(results['result-jobid']) if error is None: return else: self.module.fail_json(msg='Error when creating flexcache: %s' % error) self.module.fail_json(msg='Unexpected error when creating flexcache: results is: %s' % repr(results)) def flexcache_delete_async(self): """ Delete FlexCache relationship at destination cluster """ options = {'volume': self.parameters['name']} flexcache_delete = netapp_utils.zapi.NaElement.create_node_with_children('flexcache-destroy-async', **options) try: result = self.server.invoke_successfully(flexcache_delete, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error deleting FlexCache: %s' % (to_native(error)), exception=traceback.format_exc()) results = {} for key in ('result-status', 'result-jobid'): if result.get_child_by_name(key): results[key] = result[key] return results def rest_offline_volume(self, current): """ Offline the volume using REST PATCH method. """ uuid = current.get('uuid') if uuid is None: error = 'Error, no uuid in current: %s' % str(current) self.na_helper.fail_on_error(error) body = dict(state='offline') return self.patch_volume_rest(uuid, body) def volume_offline(self, current): """ Offline FlexCache volume at destination cluster """ if self.use_rest: self.rest_offline_volume(current) else: options = {'name': self.parameters['name']} xml = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-offline', **options) try: self.server.invoke_successfully(xml, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error offlining FlexCache volume: %s' % (to_native(error)), exception=traceback.format_exc()) def rest_mount_volume(self, current, path): """ Mount the volume using REST PATCH method. If path is empty string, unmount the volume. """ uuid = current.get('uuid') if uuid is None: error = 'Error, no uuid in current: %s' % str(current) self.na_helper.fail_on_error(error) body = dict(nas=dict(path=path)) return self.patch_volume_rest(uuid, body) def rest_unmount_volume(self, current): """ Unmount the volume using REST PATCH method. """ self.rest_mount_volume(current, '') if current.get('junction_path') else None def volume_unmount(self, current): """ Unmount FlexCache volume at destination cluster """ if self.use_rest: self.rest_unmount_volume(current) else: options = {'volume-name': self.parameters['name']} xml = netapp_utils.zapi.NaElement.create_node_with_children( 'volume-unmount', **options) try: self.server.invoke_successfully(xml, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: self.module.fail_json(msg='Error unmounting FlexCache volume: %s' % (to_native(error)), exception=traceback.format_exc()) def patch_volume_rest(self, uuid, body): response, error = rest_volume.patch_volume(self.rest_api, uuid, body) self.na_helper.fail_on_error(error) return response def flexcache_rest_delete(self, current): """ Delete the flexcache using REST DELETE method. """ response = None uuid = current.get('uuid') if uuid is None: error = 'Error, no uuid in current: %s' % str(current) self.na_helper.fail_on_error(error) api = 'storage/flexcache/flexcaches' # There may be a bug in ONTAP. If return_timeout is >= 15, the call fails with uuid not found! # With 5, a job is queued, and completes with success. With a big enough value, no job is # queued, and the API returns in around 15 seconds with a not found error. rto = netapp_utils.get_feature(self.module, 'flexcache_delete_return_timeout') response, error = rest_generic.delete_async(self.rest_api, api, uuid, timeout=rto, job_timeout=self.parameters['time_out']) self.na_helper.fail_on_error(error) return response def flexcache_delete(self, current): """ Delete FlexCache relationship at destination cluster Check job status """ if self.parameters['force_unmount']: self.volume_unmount(current) if self.parameters['force_offline']: self.volume_offline(current) if self.use_rest: return self.flexcache_rest_delete(current) results = self.flexcache_delete_async() status = results.get('result-status') if status == 'in_progress' and 'result-jobid' in results: if self.parameters['time_out'] == 0: # asynchronous call, assuming success! return None error = self.check_job_status(results['result-jobid']) if error is not None: self.module.fail_json(msg='Error when deleting flexcache: %s' % error) return None self.module.fail_json(msg='Unexpected error when deleting flexcache: results is: %s' % repr(results)) def check_parameters(self, cd_action): """ Validate parameters and fail if one or more required params are missing """ if cd_action != 'create': return if self.parameters['state'] == 'present': expected = 'origin_volume', 'origin_vserver' missings = [param for param in expected if not self.parameters.get(param)] if missings: plural = 's' if len(missings) > 1 else '' msg = 'Missing parameter%s: %s' % (plural, ', '.join(missings)) self.module.fail_json(msg=msg) def apply(self): """ Apply action to FlexCache """ current = self.flexcache_get() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify, mount_unmount = None, None prepopulate_if_already_created = None if self.parameters['state'] == 'present' and 'prepopulate' in self.parameters: prepopulate_if_already_created = self.parameters['prepopulate'].pop('force_prepopulate_if_already_created') if cd_action is None: modify = self.na_helper.get_modified_attributes(current, self.parameters) if modify and self.use_rest: mount_unmount = modify.pop('junction_path', None) if modify: self.module.fail_json(msg='FlexCache properties cannot be modified by this module. modify: %s' % str(modify)) if current and prepopulate_if_already_created: # force a prepopulate action modify = dict(prepopulate=self.parameters['prepopulate']) self.na_helper.changed = True self.module.warn('na_ontap_flexcache is not idempotent when prepopulate is present and force_prepopulate_if_already_created=true') if mount_unmount == '' or current['junction_path'] == '': self.module.warn('prepopulate requires the FlexCache volume to be mounted') self.check_parameters(cd_action) response = None if self.na_helper.changed and not self.module.check_mode: if cd_action == 'create': response = self.flexcache_create() elif cd_action == 'delete': response = self.flexcache_delete(current) else: if mount_unmount is not None: # mount first, as this is required for prepopulate to succeed (or fail for unmount) self.rest_mount_volume(current, mount_unmount) if modify: response = self.flexcache_rest_modify(current['uuid']) result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify, response) self.module.exit_json(**result) def main(): """Execute action""" my_obj = NetAppONTAPFlexCache() my_obj.apply() if __name__ == '__main__': main()