1#!/usr/local/bin/python3.8 2 3# (c) 2019-2020, 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_snapmirror_policy 8''' 9 10from __future__ import absolute_import, division, print_function 11 12__metaclass__ = type 13 14ANSIBLE_METADATA = {'metadata_version': '1.1', 15 'status': ['preview'], 16 'supported_by': 'certified'} 17 18DOCUMENTATION = """ 19module: na_ontap_snapmirror_policy 20short_description: NetApp ONTAP create, delete or modify SnapMirror policies 21extends_documentation_fragment: 22 - netapp.ontap.netapp.na_ontap 23version_added: '20.3.0' 24author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> 25description: 26- NetApp ONTAP create, modify, or destroy the SnapMirror policy 27- Add, modify and remove SnapMirror policy rules 28- Following parameters are not supported in REST; 'owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime', 'common_snapshot_schedule' 29options: 30 state: 31 description: 32 - Whether the specified SnapMirror policy should exist or not. 33 choices: ['present', 'absent'] 34 default: present 35 type: str 36 vserver: 37 description: 38 - Specifies the vserver for the SnapMirror policy. 39 required: true 40 type: str 41 policy_name: 42 description: 43 - Specifies the SnapMirror policy name. 44 required: true 45 type: str 46 policy_type: 47 description: 48 - Specifies the SnapMirror policy type. Modifying the type of an existing SnapMirror policy is not supported 49 choices: ['vault', 'async_mirror', 'mirror_vault', 'strict_sync_mirror', 'sync_mirror'] 50 type: str 51 comment: 52 description: 53 - Specifies the SnapMirror policy comment. 54 type: str 55 tries: 56 description: 57 - Specifies the number of tries. 58 type: str 59 transfer_priority: 60 description: 61 - Specifies the priority at which a SnapMirror transfer runs. 62 choices: ['low', 'normal'] 63 type: str 64 common_snapshot_schedule: 65 description: 66 - Specifies the common Snapshot copy schedule associated with the policy, only required for strict_sync_mirror and sync_mirror. 67 type: str 68 owner: 69 description: 70 - Specifies the owner of the SnapMirror policy. 71 choices: ['cluster_admin', 'vserver_admin'] 72 type: str 73 is_network_compression_enabled: 74 description: 75 - Specifies whether network compression is enabled for transfers. 76 type: bool 77 ignore_atime: 78 description: 79 - Specifies whether incremental transfers will ignore files which have only their access time changed. Applies to SnapMirror vault relationships only. 80 type: bool 81 restart: 82 description: 83 - Defines the behavior of SnapMirror if an interrupted transfer exists, applies to data protection only. 84 choices: ['always', 'never', 'default'] 85 type: str 86 snapmirror_label: 87 description: 88 - SnapMirror policy rule label. 89 - Required when defining policy rules. 90 - Use an empty list to remove all user-defined rules. 91 type: list 92 elements: str 93 version_added: '20.7.0' 94 keep: 95 description: 96 - SnapMirror policy rule retention count for snapshots created. 97 - Required when defining policy rules. 98 type: list 99 elements: int 100 version_added: '20.7.0' 101 prefix: 102 description: 103 - SnapMirror policy rule prefix. 104 - Optional when defining policy rules. 105 - Set to '' to not set or remove an existing custom prefix. 106 - Prefix name should be unique within the policy. 107 - When specifying a custom prefix, schedule must also be specified. 108 type: list 109 elements: str 110 version_added: '20.7.0' 111 schedule: 112 description: 113 - SnapMirror policy rule schedule. 114 - Optional when defining policy rules. 115 - Set to '' to not set or remove a schedule. 116 - When specifying a schedule a custom prefix can be set otherwise the prefix will be set to snapmirror_label. 117 type: list 118 elements: str 119 version_added: '20.7.0' 120 121""" 122 123EXAMPLES = """ 124 - name: Create SnapMirror policy 125 na_ontap_snapmirror_policy: 126 state: present 127 vserver: "SVM1" 128 policy_name: "ansible_policy" 129 policy_type: "mirror_vault" 130 comment: "created by ansible" 131 hostname: "{{ hostname }}" 132 username: "{{ username }}" 133 password: "{{ password }}" 134 https: true 135 validate_certs: false 136 137 - name: Modify SnapMirror policy 138 na_ontap_snapmirror_policy: 139 state: present 140 vserver: "SVM1" 141 policy_name: "ansible_policy" 142 policy_type: "async_mirror" 143 transfer_priority: "low" 144 hostname: "{{ hostname }}" 145 username: "{{ username }}" 146 password: "{{ password }}" 147 https: true 148 validate_certs: false 149 150 - name: Create SnapMirror policy with basic rules 151 na_ontap_snapmirror_policy: 152 state: present 153 vserver: "SVM1" 154 policy_name: "ansible_policy" 155 policy_type: "async_mirror" 156 snapmirror_label: ['daily', 'weekly', 'monthly'] 157 keep: [7, 5, 12] 158 hostname: "{{ hostname }}" 159 username: "{{ username }}" 160 password: "{{ password }}" 161 https: true 162 validate_certs: false 163 164 - name: Create SnapMirror policy with rules and schedules (no schedule for daily rule) 165 na_ontap_snapmirror_policy: 166 state: present 167 vserver: "SVM1" 168 policy_name: "ansible_policy" 169 policy_type: "mirror_vault" 170 snapmirror_label: ['daily', 'weekly', 'monthly'] 171 keep: [7, 5, 12] 172 schedule: ['','weekly','monthly'] 173 prefix: ['','','monthly_mv'] 174 hostname: "{{ hostname }}" 175 username: "{{ username }}" 176 password: "{{ password }}" 177 https: true 178 validate_certs: false 179 180 - name: Modify SnapMirror policy with rules, remove existing schedules and prefixes 181 na_ontap_snapmirror_policy: 182 state: present 183 vserver: "SVM1" 184 policy_name: "ansible_policy" 185 policy_type: "mirror_vault" 186 snapmirror_label: ['daily', 'weekly', 'monthly'] 187 keep: [7, 5, 12] 188 schedule: ['','',''] 189 prefix: ['','',''] 190 hostname: "{{ hostname }}" 191 username: "{{ username }}" 192 password: "{{ password }}" 193 https: true 194 validate_certs: false 195 196 - name: Modify SnapMirror policy, delete all rules (excludes builtin rules) 197 na_ontap_snapmirror_policy: 198 state: present 199 vserver: "SVM1" 200 policy_name: "ansible_policy" 201 policy_type: "mirror_vault" 202 snapmirror_label: [] 203 hostname: "{{ hostname }}" 204 username: "{{ username }}" 205 password: "{{ password }}" 206 https: true 207 validate_certs: false 208 209 - name: Delete SnapMirror policy 210 na_ontap_snapmirror_policy: 211 state: absent 212 vserver: "SVM1" 213 policy_type: "async_mirror" 214 policy_name: "ansible_policy" 215 hostname: "{{ hostname }}" 216 username: "{{ username }}" 217 password: "{{ password }}" 218 https: true 219 validate_certs: false 220""" 221 222RETURN = """ 223 224""" 225 226import traceback 227 228from ansible.module_utils.basic import AnsibleModule 229from ansible.module_utils._text import to_native 230import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils 231from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule 232from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI 233 234HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() 235 236 237class NetAppOntapSnapMirrorPolicy(object): 238 """ 239 Create, Modifies and Destroys a SnapMirror policy 240 """ 241 def __init__(self): 242 """ 243 Initialize the Ontap SnapMirror policy class 244 """ 245 246 self.use_rest = False 247 self.argument_spec = netapp_utils.na_ontap_host_argument_spec() 248 self.argument_spec.update(dict( 249 state=dict(required=False, choices=['present', 'absent'], default='present'), 250 vserver=dict(required=True, type='str'), 251 policy_name=dict(required=True, type='str'), 252 comment=dict(required=False, type='str'), 253 policy_type=dict(required=False, type='str', 254 choices=['vault', 'async_mirror', 'mirror_vault', 'strict_sync_mirror', 'sync_mirror']), 255 tries=dict(required=False, type='str'), 256 transfer_priority=dict(required=False, type='str', choices=['low', 'normal']), 257 common_snapshot_schedule=dict(required=False, type='str'), 258 ignore_atime=dict(required=False, type='bool'), 259 is_network_compression_enabled=dict(required=False, type='bool'), 260 owner=dict(required=False, type='str', choices=['cluster_admin', 'vserver_admin']), 261 restart=dict(required=False, type='str', choices=['always', 'never', 'default']), 262 snapmirror_label=dict(required=False, type="list", elements="str"), 263 keep=dict(required=False, type="list", elements="int"), 264 prefix=dict(required=False, type="list", elements="str"), 265 schedule=dict(required=False, type="list", elements="str"), 266 )) 267 268 self.module = AnsibleModule( 269 argument_spec=self.argument_spec, 270 supports_check_mode=True 271 ) 272 273 # set up variables 274 self.na_helper = NetAppModule() 275 self.parameters = self.na_helper.set_parameters(self.module.params) 276 277 # API should be used for ONTAP 9.6 or higher, Zapi for lower version 278 self.rest_api = OntapRestAPI(self.module) 279 # some attributes are not supported in earlier REST implementation 280 unsupported_rest_properties = ['owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime', 281 'common_snapshot_schedule'] 282 used_unsupported_rest_properties = [x for x in unsupported_rest_properties if x in self.parameters] 283 self.use_rest, error = self.rest_api.is_rest(used_unsupported_rest_properties) 284 285 if error: 286 self.module.fail_json(msg=error) 287 if not self.use_rest: 288 if HAS_NETAPP_LIB is False: 289 self.module.fail_json(msg='The python NetApp-Lib module is required') 290 else: 291 self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver']) 292 293 def get_snapmirror_policy(self): 294 295 if self.use_rest: 296 data = {'fields': 'uuid,name,svm.name,comment,network_compression_enabled,type,retention', 297 'name': self.parameters['policy_name'], 298 'svm.name': self.parameters['vserver']} 299 api = "snapmirror/policies" 300 message, error = self.rest_api.get(api, data) 301 if error: 302 self.module.fail_json(msg=error) 303 if len(message['records']) != 0: 304 return_value = { 305 'uuid': message['records'][0]['uuid'], 306 'vserver': message['records'][0]['svm']['name'], 307 'policy_name': message['records'][0]['name'], 308 'comment': '', 309 'is_network_compression_enabled': message['records'][0]['network_compression_enabled'], 310 'snapmirror_label': list(), 311 'keep': list(), 312 'prefix': list(), 313 'schedule': list() 314 } 315 if 'type' in message['records'][0]: 316 policy_type = message['records'][0]['type'] 317 if policy_type == 'async': 318 policy_type = 'async_mirror' 319 elif policy_type == 'sync': 320 policy_type = 'sync_mirror' 321 return_value['policy_type'] = policy_type 322 if 'comment' in message['records'][0]: 323 return_value['comment'] = message['records'][0]['comment'] 324 if 'retention' in message['records'][0]: 325 for rule in message['records'][0]['retention']: 326 return_value['snapmirror_label'].append(rule['label']) 327 return_value['keep'].append(int(rule['count'])) 328 if rule['prefix'] == '-': 329 return_value['prefix'].append('') 330 else: 331 return_value['prefix'].append(rule['prefix']) 332 if rule['creation_schedule']['name'] == '-': 333 return_value['schedule'].append('') 334 else: 335 return_value['schedule'].append(rule['creation_schedule']['name']) 336 return return_value 337 return None 338 else: 339 return_value = None 340 341 snapmirror_policy_get_iter = netapp_utils.zapi.NaElement('snapmirror-policy-get-iter') 342 snapmirror_policy_info = netapp_utils.zapi.NaElement('snapmirror-policy-info') 343 snapmirror_policy_info.add_new_child('policy-name', self.parameters['policy_name']) 344 snapmirror_policy_info.add_new_child('vserver', self.parameters['vserver']) 345 query = netapp_utils.zapi.NaElement('query') 346 query.add_child_elem(snapmirror_policy_info) 347 snapmirror_policy_get_iter.add_child_elem(query) 348 349 try: 350 result = self.server.invoke_successfully(snapmirror_policy_get_iter, True) 351 if result.get_child_by_name('attributes-list'): 352 snapmirror_policy_attributes = result['attributes-list']['snapmirror-policy-info'] 353 354 return_value = { 355 'policy_name': snapmirror_policy_attributes['policy-name'], 356 'tries': snapmirror_policy_attributes['tries'], 357 'transfer_priority': snapmirror_policy_attributes['transfer-priority'], 358 'is_network_compression_enabled': self.na_helper.get_value_for_bool(True, 359 snapmirror_policy_attributes['is-network-compression-enabled']), 360 'restart': snapmirror_policy_attributes['restart'], 361 'ignore_atime': self.na_helper.get_value_for_bool(True, snapmirror_policy_attributes['ignore-atime']), 362 'vserver': snapmirror_policy_attributes['vserver-name'], 363 'comment': '', 364 'snapmirror_label': list(), 365 'keep': list(), 366 'prefix': list(), 367 'schedule': list() 368 } 369 if snapmirror_policy_attributes.get_child_content('comment') is not None: 370 return_value['comment'] = snapmirror_policy_attributes['comment'] 371 372 if snapmirror_policy_attributes.get_child_content('type') is not None: 373 return_value['policy_type'] = snapmirror_policy_attributes['type'] 374 375 if snapmirror_policy_attributes.get_child_by_name('snapmirror-policy-rules'): 376 for rule in snapmirror_policy_attributes['snapmirror-policy-rules'].get_children(): 377 # Ignore builtin rules 378 if rule.get_child_content('snapmirror-label') == "sm_created" or \ 379 rule.get_child_content('snapmirror-label') == "all_source_snapshots": 380 continue 381 382 return_value['snapmirror_label'].append(rule.get_child_content('snapmirror-label')) 383 return_value['keep'].append(int(rule.get_child_content('keep'))) 384 385 prefix = rule.get_child_content('prefix') 386 if prefix is None or prefix == '-': 387 prefix = '' 388 return_value['prefix'].append(prefix) 389 390 schedule = rule.get_child_content('schedule') 391 if schedule is None or schedule == '-': 392 schedule = '' 393 return_value['schedule'].append(schedule) 394 395 except netapp_utils.zapi.NaApiError as error: 396 if 'NetApp API failed. Reason - 13001:' in to_native(error): 397 # Policy does not exist 398 pass 399 else: 400 self.module.fail_json(msg='Error getting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), 401 exception=traceback.format_exc()) 402 return return_value 403 404 def validate_parameters(self): 405 """ 406 Validate snapmirror policy rules 407 :return: None 408 """ 409 410 # For snapmirror policy rules, 'snapmirror_label' is required. 411 if 'snapmirror_label' in self.parameters: 412 413 # Check size of 'snapmirror_label' list is 0-10. Can have zero rules. 414 # Take builtin 'sm_created' rule into account for 'mirror_vault'. 415 if (('policy_type' in self.parameters and 416 self.parameters['policy_type'] == 'mirror_vault' and 417 len(self.parameters['snapmirror_label']) > 9) or 418 len(self.parameters['snapmirror_label']) > 10): 419 self.module.fail_json(msg="Error: A SnapMirror Policy can have up to a maximum of " 420 "10 rules (including builtin rules), with a 'keep' value " 421 "representing the maximum number of Snapshot copies for each rule") 422 423 # 'keep' must be supplied as long as there is at least one snapmirror_label 424 if len(self.parameters['snapmirror_label']) > 0 and 'keep' not in self.parameters: 425 self.module.fail_json(msg="Error: Missing 'keep' parameter. When specifying the " 426 "'snapmirror_label' parameter, the 'keep' parameter must " 427 "also be supplied") 428 429 # Make sure other rule values match same number of 'snapmirror_label' values. 430 for rule_parameter in ['keep', 'prefix', 'schedule']: 431 if rule_parameter in self.parameters: 432 if len(self.parameters['snapmirror_label']) > len(self.parameters[rule_parameter]): 433 self.module.fail_json(msg="Error: Each 'snapmirror_label' value must have " 434 "an accompanying '%s' value" % rule_parameter) 435 if len(self.parameters[rule_parameter]) > len(self.parameters['snapmirror_label']): 436 self.module.fail_json(msg="Error: Each '%s' value must have an accompanying " 437 "'snapmirror_label' value" % rule_parameter) 438 else: 439 # 'snapmirror_label' not supplied. 440 # Bail out if other rule parameters have been supplied. 441 for rule_parameter in ['keep', 'prefix', 'schedule']: 442 if rule_parameter in self.parameters: 443 self.module.fail_json(msg="Error: Missing 'snapmirror_label' parameter. When " 444 "specifying the '%s' parameter, the 'snapmirror_label' " 445 "parameter must also be supplied" % rule_parameter) 446 447 # Schedule must be supplied if prefix is supplied. 448 if 'prefix' in self.parameters and 'schedule' not in self.parameters: 449 self.module.fail_json(msg="Error: Missing 'schedule' parameter. When " 450 "specifying the 'prefix' parameter, the 'schedule' " 451 "parameter must also be supplied") 452 453 def create_snapmirror_policy(self): 454 """ 455 Creates a new storage efficiency policy 456 """ 457 self.validate_parameters() 458 if self.use_rest: 459 data = {'name': self.parameters['policy_name'], 460 'svm': {'name': self.parameters['vserver']}} 461 if 'policy_type' in self.parameters.keys(): 462 if 'async_mirror' in self.parameters['policy_type']: 463 data['type'] = 'async' 464 elif 'sync_mirror' in self.parameters['policy_type']: 465 data['type'] = 'sync' 466 data['sync_type'] = 'sync' 467 else: 468 self.module.fail_json(msg='policy type in REST only supports options async_mirror or sync_mirror, given %s' 469 % (self.parameters['policy_type'])) 470 data = self.create_snapmirror_policy_obj_for_rest(data, data['type']) 471 else: 472 data = self.create_snapmirror_policy_obj_for_rest(data) 473 api = "snapmirror/policies" 474 response, error = self.rest_api.post(api, data) 475 if error: 476 self.module.fail_json(msg=error) 477 if 'job' in response: 478 message, error = self.rest_api.wait_on_job(response['job'], increment=5) 479 if error: 480 self.module.fail_json(msg="%s" % error) 481 else: 482 snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-create") 483 snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) 484 if 'policy_type' in self.parameters.keys(): 485 snapmirror_policy_obj.add_new_child("type", self.parameters['policy_type']) 486 snapmirror_policy_obj = self.create_snapmirror_policy_obj(snapmirror_policy_obj) 487 488 try: 489 self.server.invoke_successfully(snapmirror_policy_obj, True) 490 except netapp_utils.zapi.NaApiError as error: 491 self.module.fail_json(msg='Error creating snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), 492 exception=traceback.format_exc()) 493 494 def create_snapmirror_policy_obj(self, snapmirror_policy_obj): 495 if 'comment' in self.parameters.keys(): 496 snapmirror_policy_obj.add_new_child("comment", self.parameters['comment']) 497 if 'common_snapshot_schedule' in self.parameters.keys() and 'sync_mirror' in self.parameters['policy_type']: 498 snapmirror_policy_obj.add_new_child("common-snapshot-schedule", self.parameters['common_snapshot_schedule']) 499 if 'ignore_atime' in self.parameters.keys(): 500 snapmirror_policy_obj.add_new_child("ignore-atime", self.na_helper.get_value_for_bool(False, self.parameters['ignore_atime'])) 501 if 'is_network_compression_enabled' in self.parameters.keys(): 502 snapmirror_policy_obj.add_new_child("is-network-compression-enabled", 503 self.na_helper.get_value_for_bool(False, self.parameters['is_network_compression_enabled'])) 504 if 'owner' in self.parameters.keys(): 505 snapmirror_policy_obj.add_new_child("owner", self.parameters['owner']) 506 if 'restart' in self.parameters.keys(): 507 snapmirror_policy_obj.add_new_child("restart", self.parameters['restart']) 508 if 'transfer_priority' in self.parameters.keys(): 509 snapmirror_policy_obj.add_new_child("transfer-priority", self.parameters['transfer_priority']) 510 if 'tries' in self.parameters.keys(): 511 snapmirror_policy_obj.add_new_child("tries", self.parameters['tries']) 512 return snapmirror_policy_obj 513 514 def create_snapmirror_policy_obj_for_rest(self, snapmirror_policy_obj, policy_type=None): 515 if 'comment' in self.parameters.keys(): 516 snapmirror_policy_obj["comment"] = self.parameters['comment'] 517 if 'is_network_compression_enabled' in self.parameters: 518 if policy_type == 'async': 519 snapmirror_policy_obj["network_compression_enabled"] = self.parameters['is_network_compression_enabled'] 520 elif policy_type == 'sync': 521 self.module.fail_json(msg="Input parameter network_compression_enabled is not valid for SnapMirror policy type sync") 522 return snapmirror_policy_obj 523 524 def create_snapmirror_policy_retention_obj_for_rest(self, rules=None): 525 """ 526 Create SnapMirror policy retention REST object. 527 :param list rules: e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ] 528 :return: List of retention REST objects. 529 e.g. [{'label': 'daily', 'count': 7, 'prefix': 'daily', 'creation_schedule': {'name': 'daily'}}, ... ] 530 """ 531 snapmirror_policy_retention_objs = list() 532 if rules is not None: 533 for rule in rules: 534 retention = {'label': rule['snapmirror_label'], 'count': str(rule['keep'])} 535 if 'prefix' in rule and rule['prefix'] != '': 536 retention['prefix'] = rule['prefix'] 537 if 'schedule' in rule and rule['schedule'] != '': 538 retention['creation_schedule'] = {'name': rule['schedule']} 539 snapmirror_policy_retention_objs.append(retention) 540 return snapmirror_policy_retention_objs 541 542 def delete_snapmirror_policy(self, uuid=None): 543 """ 544 Deletes a snapmirror policy 545 """ 546 if self.use_rest: 547 api = "snapmirror/policies/%s" % uuid 548 dummy, error = self.rest_api.delete(api) 549 if error: 550 self.module.fail_json(msg=error) 551 else: 552 snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-delete") 553 snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) 554 555 try: 556 self.server.invoke_successfully(snapmirror_policy_obj, True) 557 except netapp_utils.zapi.NaApiError as error: 558 self.module.fail_json(msg='Error deleting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), 559 exception=traceback.format_exc()) 560 561 def modify_snapmirror_policy(self, uuid=None, policy_type=None): 562 """ 563 Modifies a snapmirror policy 564 """ 565 if self.use_rest: 566 api = "snapmirror/policies/" + uuid 567 data = self.create_snapmirror_policy_obj_for_rest(dict(), policy_type) 568 dummy, error = self.rest_api.patch(api, data) 569 if error: 570 self.module.fail_json(msg=error) 571 else: 572 snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-modify") 573 snapmirror_policy_obj = self.create_snapmirror_policy_obj(snapmirror_policy_obj) 574 # Only modify snapmirror policy if a specific snapmirror policy attribute needs 575 # modifying. It may be that only snapmirror policy rules are being modified. 576 if snapmirror_policy_obj.get_children(): 577 snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name']) 578 579 try: 580 self.server.invoke_successfully(snapmirror_policy_obj, True) 581 except netapp_utils.zapi.NaApiError as error: 582 self.module.fail_json(msg='Error modifying snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)), 583 exception=traceback.format_exc()) 584 585 def identify_new_snapmirror_policy_rules(self, current=None): 586 """ 587 Identify new rules that should be added. 588 :return: List of new rules to be added 589 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] 590 """ 591 new_rules = list() 592 if 'snapmirror_label' in self.parameters: 593 for snapmirror_label in self.parameters['snapmirror_label']: 594 snapmirror_label = snapmirror_label.strip() 595 596 # Construct new rule. prefix and schedule are optional. 597 snapmirror_label_index = self.parameters['snapmirror_label'].index(snapmirror_label) 598 rule = dict({ 599 'snapmirror_label': snapmirror_label, 600 'keep': self.parameters['keep'][snapmirror_label_index] 601 }) 602 if 'prefix' in self.parameters: 603 rule['prefix'] = self.parameters['prefix'][snapmirror_label_index] 604 else: 605 rule['prefix'] = '' 606 if 'schedule' in self.parameters: 607 rule['schedule'] = self.parameters['schedule'][snapmirror_label_index] 608 else: 609 rule['schedule'] = '' 610 611 if current is not None and 'snapmirror_label' in current: 612 if snapmirror_label not in current['snapmirror_label']: 613 # Rule doesn't exist. Add new rule. 614 new_rules.append(rule) 615 else: 616 # No current or any rules. Add new rule. 617 new_rules.append(rule) 618 return new_rules 619 620 def identify_obsolete_snapmirror_policy_rules(self, current=None): 621 """ 622 Identify existing rules that should be deleted 623 :return: List of rules to be deleted 624 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] 625 """ 626 obsolete_rules = list() 627 if 'snapmirror_label' in self.parameters: 628 if current is not None and 'snapmirror_label' in current: 629 # Iterate existing rules. 630 for snapmirror_label in current['snapmirror_label']: 631 snapmirror_label = snapmirror_label.strip() 632 if snapmirror_label not in [item.strip() for item in self.parameters['snapmirror_label']]: 633 # Existing rule isn't in parameters. Delete existing rule. 634 current_snapmirror_label_index = current['snapmirror_label'].index(snapmirror_label) 635 rule = dict({ 636 'snapmirror_label': snapmirror_label, 637 'keep': current['keep'][current_snapmirror_label_index], 638 'prefix': current['prefix'][current_snapmirror_label_index], 639 'schedule': current['schedule'][current_snapmirror_label_index] 640 }) 641 obsolete_rules.append(rule) 642 return obsolete_rules 643 644 def identify_modified_snapmirror_policy_rules(self, current=None): 645 """ 646 Identify self.parameters rules that will be modified or not. 647 :return: List of 'modified' rules and a list of 'unmodified' rules 648 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ] 649 """ 650 modified_rules = list() 651 unmodified_rules = list() 652 if 'snapmirror_label' in self.parameters: 653 for snapmirror_label in self.parameters['snapmirror_label']: 654 snapmirror_label = snapmirror_label.strip() 655 if current is not None and 'snapmirror_label' in current: 656 if snapmirror_label in current['snapmirror_label']: 657 # Rule exists. Identify whether it requires modification or not. 658 modified = False 659 rule = dict() 660 rule['snapmirror_label'] = snapmirror_label 661 662 # Get indexes of current and supplied rule. 663 current_snapmirror_label_index = current['snapmirror_label'].index(snapmirror_label) 664 snapmirror_label_index = self.parameters['snapmirror_label'].index(snapmirror_label) 665 666 # Check if keep modified 667 if self.parameters['keep'][snapmirror_label_index] != current['keep'][current_snapmirror_label_index]: 668 modified = True 669 rule['keep'] = self.parameters['keep'][snapmirror_label_index] 670 else: 671 rule['keep'] = current['keep'][current_snapmirror_label_index] 672 673 # Check if prefix modified 674 if 'prefix' in self.parameters: 675 if self.parameters['prefix'][snapmirror_label_index] != current['prefix'][current_snapmirror_label_index]: 676 modified = True 677 rule['prefix'] = self.parameters['prefix'][snapmirror_label_index] 678 else: 679 rule['prefix'] = current['prefix'][current_snapmirror_label_index] 680 else: 681 rule['prefix'] = current['prefix'][current_snapmirror_label_index] 682 683 # Check if schedule modified 684 if 'schedule' in self.parameters: 685 if self.parameters['schedule'][snapmirror_label_index] != current['schedule'][current_snapmirror_label_index]: 686 modified = True 687 rule['schedule'] = self.parameters['schedule'][snapmirror_label_index] 688 else: 689 rule['schedule'] = current['schedule'][current_snapmirror_label_index] 690 else: 691 rule['schedule'] = current['schedule'][current_snapmirror_label_index] 692 693 if modified: 694 modified_rules.append(rule) 695 else: 696 unmodified_rules.append(rule) 697 return modified_rules, unmodified_rules 698 699 def identify_snapmirror_policy_rules_with_schedule(self, rules=None): 700 """ 701 Identify rules that are using a schedule or not. At least one 702 non-schedule rule must be added to a policy before schedule rules 703 are added. 704 :return: List of rules with schedules and a list of rules without schedules 705 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ], 706 [{'snapmirror_label': 'weekly', 'keep': 5, 'prefix': '', 'schedule': ''}, ... ] 707 """ 708 schedule_rules = list() 709 non_schedule_rules = list() 710 if rules is not None: 711 for rule in rules: 712 if 'schedule' in rule: 713 schedule_rules.append(rule) 714 else: 715 non_schedule_rules.append(rule) 716 return schedule_rules, non_schedule_rules 717 718 def modify_snapmirror_policy_rules(self, current=None, uuid=None): 719 """ 720 Modify existing rules in snapmirror policy 721 :return: None 722 """ 723 self.validate_parameters() 724 725 # Need 'snapmirror_label' to add/modify/delete rules 726 if 'snapmirror_label' not in self.parameters: 727 return 728 729 obsolete_rules = self.identify_obsolete_snapmirror_policy_rules(current) 730 new_rules = self.identify_new_snapmirror_policy_rules(current) 731 modified_rules, unmodified_rules = self.identify_modified_snapmirror_policy_rules(current) 732 733 if self.use_rest: 734 api = "snapmirror/policies/" + uuid 735 data = {'retention': list()} 736 737 # As rule 'prefix' can't be unset, have to delete existing rules first. 738 # Builtin rules remain. 739 dummy, error = self.rest_api.patch(api, data) 740 if error: 741 self.module.fail_json(msg=error) 742 743 # Re-add desired rules. 744 rules = unmodified_rules + modified_rules + new_rules 745 data['retention'] = self.create_snapmirror_policy_retention_obj_for_rest(rules) 746 747 if len(data['retention']) > 0: 748 dummy, error = self.rest_api.patch(api, data) 749 if error: 750 self.module.fail_json(msg=error) 751 else: 752 delete_rules = obsolete_rules + modified_rules 753 add_schedule_rules, add_non_schedule_rules = self.identify_snapmirror_policy_rules_with_schedule(new_rules + modified_rules) 754 # Delete rules no longer required or modified rules that will be re-added. 755 for rule in delete_rules: 756 options = {'policy-name': self.parameters['policy_name'], 757 'snapmirror-label': rule['snapmirror_label']} 758 self.modify_snapmirror_policy_rule(options, 'snapmirror-policy-remove-rule') 759 760 # Add rules. At least one non-schedule rule must exist before 761 # a rule with a schedule can be added, otherwise zapi will complain. 762 for rule in add_non_schedule_rules + add_schedule_rules: 763 options = {'policy-name': self.parameters['policy_name'], 764 'snapmirror-label': rule['snapmirror_label'], 765 'keep': str(rule['keep'])} 766 if 'prefix' in rule and rule['prefix'] != '': 767 options['prefix'] = rule['prefix'] 768 if 'schedule' in rule and rule['schedule'] != '': 769 options['schedule'] = rule['schedule'] 770 self.modify_snapmirror_policy_rule(options, 'snapmirror-policy-add-rule') 771 772 def modify_snapmirror_policy_rule(self, options, zapi): 773 """ 774 Add, modify or remove a rule to/from a snapmirror policy 775 """ 776 snapmirror_obj = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options) 777 try: 778 self.server.invoke_successfully(snapmirror_obj, enable_tunneling=True) 779 except netapp_utils.zapi.NaApiError as error: 780 self.module.fail_json(msg='Error modifying snapmirror policy rule %s: %s' % 781 (self.parameters['policy_name'], to_native(error)), 782 exception=traceback.format_exc()) 783 784 def asup_log_for_cserver(self): 785 results = netapp_utils.get_cserver(self.server) 786 cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) 787 netapp_utils.ems_log_event("na_ontap_snapmirror_policy", cserver) 788 789 def apply(self): 790 uuid = None 791 if not self.use_rest: 792 self.asup_log_for_cserver() 793 current, modify = self.get_snapmirror_policy(), None 794 cd_action = self.na_helper.get_cd_action(current, self.parameters) 795 if current and cd_action is None and self.parameters['state'] == 'present': 796 modify = self.na_helper.get_modified_attributes(current, self.parameters) 797 if 'policy_type' in modify: 798 self.module.fail_json(msg='Error: policy type cannot be changed: current=%s, expected=%s' % 799 (current.get('policy_type'), modify['policy_type'])) 800 801 if self.na_helper.changed: 802 if self.module.check_mode: 803 pass 804 else: 805 if cd_action == 'create': 806 self.create_snapmirror_policy() 807 if self.use_rest: 808 current = self.get_snapmirror_policy() 809 uuid = current['uuid'] 810 self.modify_snapmirror_policy_rules(current, uuid) 811 else: 812 self.modify_snapmirror_policy_rules(current) 813 elif cd_action == 'delete': 814 if self.use_rest: 815 uuid = current['uuid'] 816 self.delete_snapmirror_policy(uuid) 817 elif modify: 818 if self.use_rest: 819 uuid = current['uuid'] 820 self.modify_snapmirror_policy(uuid, current['policy_type']) 821 self.modify_snapmirror_policy_rules(current, uuid) 822 else: 823 self.modify_snapmirror_policy() 824 self.modify_snapmirror_policy_rules(current) 825 self.module.exit_json(changed=self.na_helper.changed) 826 827 828def main(): 829 """ 830 Creates the NetApp Ontap SnapMirror policy object and runs the correct play task 831 """ 832 obj = NetAppOntapSnapMirrorPolicy() 833 obj.apply() 834 835 836if __name__ == '__main__': 837 main() 838