1#!/usr/local/bin/python3.8
2
3# (c) 2019, NetApp, Inc
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6"""
7na_ontap_flexcache
8"""
9
10from __future__ import absolute_import, division, print_function
11__metaclass__ = type
12
13ANSIBLE_METADATA = {'metadata_version': '1.1',
14                    'status': ['preview'],
15                    'supported_by': 'certified'}
16
17
18DOCUMENTATION = '''
19short_description: NetApp ONTAP FlexCache - create/delete relationship
20author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
21description:
22  - Create/Delete FlexCache volume relationships.
23  - This module does not modify an existing FlexCache volume with two exceptions.
24  - When using REST, a prepopulate can be started on an exising FlexCache volume.
25  - When using REST, the volume can be mounted or unmounted.  Set path to '' to unmount it.
26  - It is required the volume is mounted to prepopulate it.
27  - Some actions are also available through the na_ontap_volume.
28extends_documentation_fragment:
29  - netapp.ontap.netapp.na_ontap
30module: na_ontap_flexcache
31version_added: 2.8.0
32options:
33  state:
34    choices: ['present', 'absent']
35    description:
36      - Whether the specified relationship should exist or not.
37    default: present
38    type: str
39  origin_volume:
40    description:
41      - Name of the origin volume for the FlexCache.
42      - Required for creation.
43    type: str
44  origin_vserver:
45    description:
46      - Name of the origin vserver for the FlexCache.
47      - Required for creation.
48    type: str
49  origin_cluster:
50    description:
51      - Name of the origin cluster for the FlexCache.
52      - Defaults to cluster associated with target vserver if absent.
53      - Not used for creation.
54    type: str
55  name:
56    description:
57      - Name of the target volume for the FlexCache.
58    required: true
59    type: str
60    aliases: ['volume']
61    version_added: 21.3.0
62  junction_path:
63    description:
64      - Junction path of the cache volume.
65    type: str
66    aliases: ['path']
67  auto_provision_as:
68    description:
69      - Use this parameter to automatically select existing aggregates for volume provisioning.  Eg flexgroup
70      - Note that the fastest aggregate type with at least one aggregate on each node of the cluster will be selected.
71      - Ignored when using REST - omit aggr_list for automatic selection.
72    type: str
73  size:
74    description:
75      - Size of cache volume.
76    type: int
77  size_unit:
78    description:
79    - The unit used to interpret the size parameter.
80    choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
81    type: str
82    default: gb
83  vserver:
84    description:
85      - Name of the target vserver for the FlexCache.
86      - Note that hostname, username, password are intended for the target vserver.
87    required: true
88    type: str
89  aggr_list:
90    description:
91      - List of aggregates to host target FlexCache volume.
92    type: list
93    elements: str
94    aliases: ['aggregates']
95  aggr_list_multiplier:
96    description:
97      - Aggregate list repeat count.
98      - REST - Number of FlexCache constituents per aggregate when the C(aggregates) field is mentioned.
99    type: int
100    aliases: ['constituents_per_aggregate']
101  force_unmount:
102    description:
103      - Unmount FlexCache volume. Delete the junction path at which the volume is mounted before deleting the FlexCache relationship.
104    type: bool
105    default: false
106  force_offline:
107    description:
108      - Offline FlexCache volume before deleting the FlexCache relationship.
109      - The volume will be destroyed and data can be lost.
110    type: bool
111    default: false
112  time_out:
113    description:
114      - time to wait for flexcache creation or deletion in seconds
115      - if 0, the request is asynchronous
116      - default is set to 3 minutes
117    type: int
118    default: 180
119  prepopulate:
120    version_added: 21.3.0
121    description:
122      - prepopulate FlexCache with data from origin volume.
123      - requires ONTAP 9.8 or later, and REST support.
124      - dir_paths must be set for this option to be effective.
125    type: dict
126    suboptions:
127      dir_paths:
128        description:
129          - List of directory paths in the owning SVM's namespace at which the FlexCache volume is mounted.
130          - Path must begin with '/'.
131        type: list
132        elements: str
133        required: true
134      exclude_dir_paths:
135        description:
136          - Directory path which needs to be excluded from prepopulation.
137          - Path must begin with '/'.
138          - Requires ONTAP 9.9 or later.
139        type: list
140        elements: str
141      recurse:
142        description:
143          - Specifies whether or not the prepopulate action should search through the directory-path recursively.
144          - If not set, the default value 'true' is used.
145        type: bool
146      force_prepopulate_if_already_created:
147        description:
148          - by default, this module will start a prepopulate task each time it is called, and is not idempotent.
149          - if set to false, the prepopulate task is not started if the FlexCache already exists.
150        type: bool
151        default: true
152'''
153
154EXAMPLES = """
155
156    - name: Create FlexCache
157      netapp.ontap.na_ontap_flexcache:
158        state: present
159        origin_volume: test_src
160        name: test_dest
161        origin_vserver: ansible_src
162        vserver: ansible_dest
163        hostname: "{{ netapp_hostname }}"
164        username: "{{ netapp_username }}"
165        password: "{{ netapp_password }}"
166
167    - name: Delete FlexCache
168      netapp.ontap.na_ontap_flexcache:
169        state: absent
170        name: test_dest
171        vserver: ansible_dest
172        hostname: "{{ netapp_hostname }}"
173        username: "{{ netapp_username }}"
174        password: "{{ netapp_password }}"
175
176"""
177
178RETURN = """
179"""
180
181import time
182import traceback
183from ansible.module_utils.basic import AnsibleModule
184from ansible.module_utils._text import to_native
185import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
186from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
187from ansible_collections.netapp.ontap.plugins.module_utils import rest_flexcache
188from ansible_collections.netapp.ontap.plugins.module_utils import rest_volume
189
190
191class NetAppONTAPFlexCache(object):
192    """
193    Class with FlexCache methods
194    """
195
196    def __init__(self):
197
198        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
199        self.argument_spec.update(dict(
200            state=dict(required=False, type='str', choices=['present', 'absent'],
201                       default='present'),
202            origin_volume=dict(required=False, type='str'),                     # origins[0]
203            origin_vserver=dict(required=False, type='str'),                    # origins[0]
204            origin_cluster=dict(required=False, type='str'),                    # origins[0]
205            auto_provision_as=dict(required=False, type='str'),                 # ignored with REST
206            name=dict(required=True, type='str', aliases=['volume']),
207            junction_path=dict(required=False, type='str', aliases=['path']),
208            size=dict(required=False, type='int'),
209            size_unit=dict(default='gb',
210                           choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
211                                    'pb', 'eb', 'zb', 'yb'], type='str'),
212            vserver=dict(required=True, type='str'),
213            aggr_list=dict(required=False, type='list', elements='str', aliases=['aggregates']),
214            aggr_list_multiplier=dict(required=False, type='int', aliases=['constituents_per_aggregate']),
215            force_offline=dict(required=False, type='bool', default=False),
216            force_unmount=dict(required=False, type='bool', default=False),
217            time_out=dict(required=False, type='int', default=180),
218            prepopulate=dict(required=False, type='dict', options=dict(
219                dir_paths=dict(required=True, type='list', elements='str'),
220                exclude_dir_paths=dict(required=False, type='list', elements='str'),
221                recurse=dict(required=False, type='bool'),
222                force_prepopulate_if_already_created=dict(required=False, type='bool', default=True),
223            ))
224        ))
225
226        self.module = AnsibleModule(
227            argument_spec=self.argument_spec,
228            mutually_exclusive=[
229                ('aggr_list', 'auto_provision_as'),
230            ],
231            supports_check_mode=True
232        )
233
234        self.na_helper = NetAppModule(self.module)
235        self.parameters = self.na_helper.set_parameters(self.module.params)
236        if self.parameters.get('size'):
237            self.parameters['size'] = self.parameters['size'] * \
238                netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']]
239        # setup later if required
240        self.origin_server = None
241
242        self.rest_api = netapp_utils.OntapRestAPI(self.module)
243        self.use_rest = self.rest_api.is_rest()
244
245        ontap_98_options = ['prepopulate']
246        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):
247            self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version(ontap_98_options, version='9.8'))
248
249        if 'prepopulate' in self.parameters:
250            # sanitize the dictionary, as Ansible fills everything with None values
251            self.parameters['prepopulate'] = self.na_helper.filter_out_none_entries(self.parameters['prepopulate'])
252            ontap_99_options = ['exclude_dir_paths']
253            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):
254                options = ['prepopulate: ' + x for x in ontap_99_options]
255                self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version(options, version='9.9'))
256            if not self.parameters['prepopulate']:
257                # remove entry if the dict is empty
258                del self.parameters['prepopulate']
259
260        if not self.use_rest:
261            if not netapp_utils.has_netapp_lib():
262                self.module.fail_json(msg=netapp_utils.netapp_lib_is_required())
263            else:
264                self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
265
266    def add_parameter_to_dict(self, adict, name, key=None, tostr=False):
267        ''' add defined parameter (not None) to adict using key '''
268        if key is None:
269            key = name
270        if self.parameters.get(name) is not None:
271            if tostr:
272                adict[key] = str(self.parameters.get(name))
273            else:
274                adict[key] = self.parameters.get(name)
275
276    def get_job(self, jobid, server):
277        """
278        Get job details by id
279        """
280        job_get = netapp_utils.zapi.NaElement('job-get')
281        job_get.add_new_child('job-id', jobid)
282        try:
283            result = server.invoke_successfully(job_get, enable_tunneling=True)
284        except netapp_utils.zapi.NaApiError as error:
285            if to_native(error.code) == "15661":
286                # Not found
287                return None
288            self.module.fail_json(msg='Error fetching job info: %s' % to_native(error),
289                                  exception=traceback.format_exc())
290        results = dict()
291        job_info = result.get_child_by_name('attributes').get_child_by_name('job-info')
292        results = {
293            'job-progress': job_info['job-progress'],
294            'job-state': job_info['job-state']
295        }
296        if job_info.get_child_by_name('job-completion') is not None:
297            results['job-completion'] = job_info['job-completion']
298        else:
299            results['job-completion'] = None
300        return results
301
302    def check_job_status(self, jobid):
303        """
304        Loop until job is complete
305        """
306        server = self.server
307        sleep_time = 5
308        time_out = self.parameters['time_out']
309        while time_out > 0:
310            results = self.get_job(jobid, server)
311            # If running as cluster admin, the job is owned by cluster vserver
312            # rather than the target vserver.
313            if results is None and server == self.server:
314                results = netapp_utils.get_cserver(self.server)
315                server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
316                continue
317            if results is None:
318                error = 'cannot locate job with id: %d' % jobid
319                break
320            if results['job-state'] in ('queued', 'running'):
321                time.sleep(sleep_time)
322                time_out -= sleep_time
323                continue
324            if results['job-state'] in ('success', 'failure'):
325                break
326            else:
327                self.module.fail_json(msg='Unexpected job status in: %s' % repr(results))
328
329        if results is not None:
330            if results['job-state'] == 'success':
331                error = None
332            elif results['job-state'] in ('queued', 'running'):
333                error = 'job completion exceeded expected timer of: %s seconds' % \
334                        self.parameters['time_out']
335            else:
336                if results['job-completion'] is not None:
337                    error = results['job-completion']
338                else:
339                    error = results['job-progress']
340        return error
341
342    def flexcache_get_iter(self):
343        """
344        Compose NaElement object to query current FlexCache relation
345        """
346        options = {'volume': self.parameters['name']}
347        self.add_parameter_to_dict(options, 'origin_volume', 'origin-volume')
348        self.add_parameter_to_dict(options, 'origin_vserver', 'origin-vserver')
349        self.add_parameter_to_dict(options, 'origin_cluster', 'origin-cluster')
350        flexcache_info = netapp_utils.zapi.NaElement.create_node_with_children(
351            'flexcache-info', **options)
352        query = netapp_utils.zapi.NaElement('query')
353        query.add_child_elem(flexcache_info)
354        flexcache_get_iter = netapp_utils.zapi.NaElement('flexcache-get-iter')
355        flexcache_get_iter.add_child_elem(query)
356        return flexcache_get_iter
357
358    def flexcache_get(self):
359        """
360        Get current FlexCache relations
361        :return: Dictionary of current FlexCache details if query successful, else None
362        """
363        fields = 'svm,name,uuid,path'
364        if self.use_rest:
365            flexcache, error = rest_flexcache.get_flexcache(self.rest_api, self.parameters['vserver'], self.parameters['name'], fields=fields)
366            self.na_helper.fail_on_error(error)
367            if flexcache is None:
368                return None
369            return dict(
370                vserver=flexcache['svm']['name'],
371                name=flexcache['name'],
372                uuid=flexcache['uuid'],
373                junction_path=flexcache.get('path')
374            )
375
376        flexcache_get_iter = self.flexcache_get_iter()
377        flex_info = dict()
378        try:
379            result = self.server.invoke_successfully(flexcache_get_iter, enable_tunneling=True)
380        except netapp_utils.zapi.NaApiError as error:
381            self.module.fail_json(msg='Error fetching FlexCache info: %s' % to_native(error),
382                                  exception=traceback.format_exc())
383        if result.get_child_by_name('num-records') and \
384                int(result.get_child_content('num-records')) == 1:
385            flexcache_info = result.get_child_by_name('attributes-list') \
386                                   .get_child_by_name('flexcache-info')
387            flex_info['origin_cluster'] = flexcache_info.get_child_content('origin-cluster')
388            flex_info['origin_volume'] = flexcache_info.get_child_content('origin-volume')
389            flex_info['origin_vserver'] = flexcache_info.get_child_content('origin-vserver')
390            flex_info['size'] = flexcache_info.get_child_content('size')
391            flex_info['name'] = flexcache_info.get_child_content('volume')
392            flex_info['vserver'] = flexcache_info.get_child_content('vserver')
393
394            return flex_info
395        if result.get_child_by_name('num-records') and \
396                int(result.get_child_content('num-records')) > 1:
397            msg = 'Multiple records found for %s:' % self.parameters['name']
398            self.module.fail_json(msg='Error fetching FlexCache info: %s' % msg)
399        return None
400
401    def flexcache_rest_create_body(self, mappings):
402        ''' maps self.parameters to REST API body attributes, using mappings to identify fields to add '''
403        body = dict()
404        for key, value in mappings.items():
405            if key in self.parameters:
406                if key == 'aggr_list':
407                    body[value] = [dict(name=aggr) for aggr in self.parameters[key]]
408                else:
409                    body[value] = self.parameters[key]
410            elif key == 'origins':
411                # this is an artificial key, to match the REST list of dict structure
412                origin = dict(
413                    volume=dict(name=self.parameters['origin_volume']),
414                    svm=dict(name=self.parameters['origin_vserver'])
415                )
416                if 'origin_cluster' in self.parameters:
417                    origin['cluster'] = dict(name=self.parameters['origin_cluster'])
418                body[value] = [origin]
419        return body
420
421    def flexcache_rest_create(self):
422        ''' use POST to create a FlexCache '''
423        mappings = dict(
424            name='name',
425            vserver='svm.name',
426            junction_path='path',
427            size='size',
428            aggr_list='aggregates',
429            aggr_list_multiplier='constituents_per_aggregate',
430            origins='origins',
431            prepopulate='prepopulate'
432        )
433        body = self.flexcache_rest_create_body(mappings)
434        response, error = rest_flexcache.post_flexcache(self.rest_api, body, timeout=self.parameters['time_out'])
435        self.na_helper.fail_on_error(error)
436        return response
437
438    def flexcache_rest_modify(self, uuid):
439        ''' use PATCH to start prepopulating a FlexCache '''
440        mappings = dict(                # name cannot be set, though swagger example shows it
441            prepopulate='prepopulate'
442        )
443        body = self.flexcache_rest_create_body(mappings)
444        response, error = rest_flexcache.patch_flexcache(self.rest_api, uuid, body)
445        self.na_helper.fail_on_error(error)
446        return response
447
448    def flexcache_create_async(self):
449        """
450        Create a FlexCache relationship
451        """
452        options = {'origin-volume': self.parameters['origin_volume'],
453                   'origin-vserver': self.parameters['origin_vserver'],
454                   'volume': self.parameters['name']}
455        self.add_parameter_to_dict(options, 'junction_path', 'junction-path')
456        self.add_parameter_to_dict(options, 'auto_provision_as', 'auto-provision-as')
457        self.add_parameter_to_dict(options, 'size', 'size', tostr=True)
458        if self.parameters.get('aggr_list') and self.parameters.get('aggr_list_multiplier'):
459            self.add_parameter_to_dict(options, 'aggr_list_multiplier', 'aggr-list-multiplier', tostr=True)
460        flexcache_create = netapp_utils.zapi.NaElement.create_node_with_children(
461            'flexcache-create-async', **options)
462        if self.parameters.get('aggr_list'):
463            aggregates = netapp_utils.zapi.NaElement('aggr-list')
464            for aggregate in self.parameters['aggr_list']:
465                aggregates.add_new_child('aggr-name', aggregate)
466            flexcache_create.add_child_elem(aggregates)
467        try:
468            result = self.server.invoke_successfully(flexcache_create, enable_tunneling=True)
469        except netapp_utils.zapi.NaApiError as error:
470            self.module.fail_json(msg='Error creating FlexCache %s' % to_native(error),
471                                  exception=traceback.format_exc())
472        results = dict()
473        for key in ('result-status', 'result-jobid'):
474            if result.get_child_by_name(key):
475                results[key] = result[key]
476        return results
477
478    def flexcache_create(self):
479        """
480        Create a FlexCache relationship
481        Check job status
482        """
483        if self.use_rest:
484            return self.flexcache_rest_create()
485
486        results = self.flexcache_create_async()
487        status = results.get('result-status')
488        if status == 'in_progress' and 'result-jobid' in results:
489            if self.parameters['time_out'] == 0:
490                # asynchronous call, assuming success!
491                return
492            error = self.check_job_status(results['result-jobid'])
493            if error is None:
494                return
495            else:
496                self.module.fail_json(msg='Error when creating flexcache: %s' % error)
497        self.module.fail_json(msg='Unexpected error when creating flexcache: results is: %s' % repr(results))
498
499    def flexcache_delete_async(self):
500        """
501        Delete FlexCache relationship at destination cluster
502        """
503        options = {'volume': self.parameters['name']}
504        flexcache_delete = netapp_utils.zapi.NaElement.create_node_with_children(
505            'flexcache-destroy-async', **options)
506        try:
507            result = self.server.invoke_successfully(flexcache_delete, enable_tunneling=True)
508        except netapp_utils.zapi.NaApiError as error:
509            self.module.fail_json(msg='Error deleting FlexCache : %s'
510                                  % (to_native(error)),
511                                  exception=traceback.format_exc())
512        results = dict()
513        for key in ('result-status', 'result-jobid'):
514            if result.get_child_by_name(key):
515                results[key] = result[key]
516        return results
517
518    def rest_offline_volume(self, current):
519        """
520        Offline the volume using REST PATCH method.
521        """
522        response = None
523        uuid = current.get('uuid')
524        if uuid is None:
525            error = 'Error, no uuid in current: %s' % str(current)
526            self.na_helper.fail_on_error(error)
527        body = dict(state='offline')
528        response, error = rest_volume.patch_volume(self.rest_api, uuid, body)
529        self.na_helper.fail_on_error(error)
530        return response
531
532    def volume_offline(self, current):
533        """
534        Offline FlexCache volume at destination cluster
535        """
536        if self.use_rest:
537            self.rest_offline_volume(current)
538        else:
539            options = {'name': self.parameters['name']}
540            xml = netapp_utils.zapi.NaElement.create_node_with_children(
541                'volume-offline', **options)
542            try:
543                self.server.invoke_successfully(xml, enable_tunneling=True)
544            except netapp_utils.zapi.NaApiError as error:
545                self.module.fail_json(msg='Error offlining FlexCache volume: %s'
546                                      % (to_native(error)),
547                                      exception=traceback.format_exc())
548
549    def rest_mount_volume(self, current, path):
550        """
551        Mount the volume using REST PATCH method.
552        If path is empty string, unmount the volume.
553        """
554        response = None
555        uuid = current.get('uuid')
556        if uuid is None:
557            error = 'Error, no uuid in current: %s' % str(current)
558            self.na_helper.fail_on_error(error)
559        body = dict(nas=dict(path=path))
560        response, error = rest_volume.patch_volume(self.rest_api, uuid, body)
561        self.na_helper.fail_on_error(error)
562        return response
563
564    def rest_unmount_volume(self, current):
565        """
566        Unmount the volume using REST PATCH method.
567        """
568        response = None
569        if current.get('junction_path'):
570            response = self.rest_mount_volume(current, '')
571        return response
572
573    def volume_unmount(self, current):
574        """
575        Unmount FlexCache volume at destination cluster
576        """
577        if self.use_rest:
578            self.rest_unmount_volume(current)
579        else:
580            options = {'volume-name': self.parameters['name']}
581            xml = netapp_utils.zapi.NaElement.create_node_with_children(
582                'volume-unmount', **options)
583            try:
584                self.server.invoke_successfully(xml, enable_tunneling=True)
585            except netapp_utils.zapi.NaApiError as error:
586                self.module.fail_json(msg='Error unmounting FlexCache volume: %s'
587                                      % (to_native(error)),
588                                      exception=traceback.format_exc())
589
590    def flexcache_rest_delete(self, current):
591        """
592        Delete the flexcache using REST DELETE method.
593        """
594        response = None
595        uuid = current.get('uuid')
596        if uuid is None:
597            error = 'Error, no uuid in current: %s' % str(current)
598            self.na_helper.fail_on_error(error)
599        rto = netapp_utils.get_feature(self.module, 'flexcache_delete_return_timeout')
600        response, error = rest_flexcache.delete_flexcache(self.rest_api, uuid, timeout=self.parameters['time_out'], return_timeout=rto)
601        self.na_helper.fail_on_error(error)
602        return response
603
604    def flexcache_delete(self, current):
605        """
606        Delete FlexCache relationship at destination cluster
607        Check job status
608        """
609        if self.parameters['force_unmount']:
610            self.volume_unmount(current)
611        if self.parameters['force_offline']:
612            self.volume_offline(current)
613        if self.use_rest:
614            return self.flexcache_rest_delete(current)
615        results = self.flexcache_delete_async()
616        status = results.get('result-status')
617        if status == 'in_progress' and 'result-jobid' in results:
618            if self.parameters['time_out'] == 0:
619                # asynchronous call, assuming success!
620                return None
621            error = self.check_job_status(results['result-jobid'])
622            if error is not None:
623                self.module.fail_json(msg='Error when deleting flexcache: %s' % error)
624            return None
625        self.module.fail_json(msg='Unexpected error when deleting flexcache: results is: %s' % repr(results))
626
627    def check_parameters(self, cd_action):
628        """
629        Validate parameters and fail if one or more required params are missing
630        """
631        if cd_action != 'create':
632            return
633        missings = list()
634        expected = ('origin_volume', 'origin_vserver')
635        if self.parameters['state'] == 'present':
636            for param in expected:
637                if not self.parameters.get(param):
638                    missings.append(param)
639        if missings:
640            plural = 's' if len(missings) > 1 else ''
641            msg = 'Missing parameter%s: %s' % (plural, ', '.join(missings))
642            self.module.fail_json(msg=msg)
643
644    def apply(self):
645        """
646        Apply action to FlexCache
647        """
648        if not self.use_rest:
649            netapp_utils.ems_log_event("na_ontap_flexcache", self.server)
650        current = self.flexcache_get()
651        cd_action = self.na_helper.get_cd_action(current, self.parameters)
652        modify, mount_unmount = None, None
653        prepopulate_if_already_created = None
654
655        if self.parameters['state'] == 'present' and 'prepopulate' in self.parameters:
656            prepopulate_if_already_created = self.parameters['prepopulate'].pop('force_prepopulate_if_already_created')
657
658        if cd_action is None:
659            modify = self.na_helper.get_modified_attributes(current, self.parameters)
660            if modify:
661                if self.use_rest:
662                    mount_unmount = modify.pop('junction_path', None)
663                if modify:
664                    self.module.fail_json(msg='FlexCache properties cannot be modified by this module.  modify: %s' % str(modify))
665            if current and prepopulate_if_already_created:
666                # force a prepopulate action
667                modify = dict(prepopulate=self.parameters['prepopulate'])
668                self.na_helper.changed = True
669                self.module.warn('na_ontap_flexcache is not idempotent when prepopulate is present and force_prepopulate_if_already_created=true')
670                if mount_unmount == '' or current['junction_path'] == '':
671                    self.module.warn('prepopulate requires the FlexCache volume to be mounted')
672        self.check_parameters(cd_action)
673        response = None
674        if self.na_helper.changed and not self.module.check_mode:
675            if cd_action == 'create':
676                response = self.flexcache_create()
677            elif cd_action == 'delete':
678                response = self.flexcache_delete(current)
679            else:
680                if mount_unmount is not None:
681                    # mount first, as this is required for prepopulate to succeed (or fail for unmount)
682                    self.rest_mount_volume(current, mount_unmount)
683                if modify:
684                    response = self.flexcache_rest_modify(current['uuid'])
685        self.module.exit_json(changed=self.na_helper.changed, response=response, modify=modify)
686
687
688def main():
689    """Execute action"""
690    community_obj = NetAppONTAPFlexCache()
691    community_obj.apply()
692
693
694if __name__ == '__main__':
695    main()
696