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