1#!/usr/bin/python 2 3# (c) 2018-2019, NetApp, Inc 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5from __future__ import absolute_import, division, print_function 6__metaclass__ = type 7 8ANSIBLE_METADATA = {'metadata_version': '1.1', 9 'status': ['preview'], 10 'supported_by': 'certified'} 11 12 13DOCUMENTATION = ''' 14author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> 15description: 16 - Create/Delete/Initialize SnapMirror volume/vserver relationships for ONTAP/ONTAP 17 - Create/Delete/Initialize SnapMirror volume relationship between ElementSW and ONTAP 18 - Modify schedule for a SnapMirror relationship for ONTAP/ONTAP and ElementSW/ONTAP 19 - Pre-requisite for ElementSW to ONTAP relationship or vice-versa is an established SnapMirror endpoint for ONTAP cluster with ElementSW UI 20 - Pre-requisite for ElementSW to ONTAP relationship or vice-versa is to have SnapMirror enabled in the ElementSW volume 21 - For creating a SnapMirror ElementSW/ONTAP relationship, an existing ONTAP/ElementSW relationship should be present 22extends_documentation_fragment: 23 - netapp.na_ontap 24module: na_ontap_snapmirror 25options: 26 state: 27 choices: ['present', 'absent'] 28 description: 29 - Whether the specified relationship should exist or not. 30 default: present 31 source_volume: 32 description: 33 - Specifies the name of the source volume for the SnapMirror. 34 destination_volume: 35 description: 36 - Specifies the name of the destination volume for the SnapMirror. 37 source_vserver: 38 description: 39 - Name of the source vserver for the SnapMirror. 40 destination_vserver: 41 description: 42 - Name of the destination vserver for the SnapMirror. 43 source_path: 44 description: 45 - Specifies the source endpoint of the SnapMirror relationship. 46 - If the source is an ONTAP volume, format should be <[vserver:][volume]> or <[[cluster:]//vserver/]volume> 47 - If the source is an ElementSW volume, format should be <[Element_SVIP]:/lun/[Element_VOLUME_ID]> 48 - If the source is an ElementSW volume, the volume should have SnapMirror enabled. 49 destination_path: 50 description: 51 - Specifies the destination endpoint of the SnapMirror relationship. 52 relationship_type: 53 choices: ['data_protection', 'load_sharing', 'vault', 'restore', 'transition_data_protection', 54 'extended_data_protection'] 55 description: 56 - Specify the type of SnapMirror relationship. 57 schedule: 58 description: 59 - Specify the name of the current schedule, which is used to update the SnapMirror relationship. 60 - Optional for create, modifiable. 61 policy: 62 description: 63 - Specify the name of the SnapMirror policy that applies to this relationship. 64 version_added: "2.8" 65 source_hostname: 66 description: 67 - Source hostname or management IP address for ONTAP or ElementSW cluster. 68 - Required for SnapMirror delete 69 source_username: 70 description: 71 - Source username for ONTAP or ElementSW cluster. 72 - Optional if this is same as destination username. 73 source_password: 74 description: 75 - Source password for ONTAP or ElementSW cluster. 76 - Optional if this is same as destination password. 77 connection_type: 78 description: 79 - Type of SnapMirror relationship. 80 - Pre-requisite for either elementsw_ontap or ontap_elementsw the ElementSW volume should have enableSnapmirror option set to true. 81 - For using ontap_elementsw, elementsw_ontap snapmirror relationship should exist. 82 choices: ['ontap_ontap', 'elementsw_ontap', 'ontap_elementsw'] 83 default: ontap_ontap 84 version_added: '2.9' 85 max_transfer_rate: 86 description: 87 - Specifies the upper bound, in kilobytes per second, at which data is transferred. 88 - Default is unlimited, it can be explicitly set to 0 as unlimited. 89 type: int 90 version_added: '2.9' 91 identity_preserve: 92 description: 93 - Specifies whether or not the identity of the source Vserver is replicated to the destination Vserver. 94 - If this parameter is set to true, the source Vserver's configuration will additionally be replicated to the destination. 95 - If the parameter is set to false, then only the source Vserver's volumes and RBAC configuration are replicated to the destination. 96 type: bool 97 version_added: '2.9' 98short_description: "NetApp ONTAP or ElementSW Manage SnapMirror" 99version_added: "2.7" 100''' 101 102EXAMPLES = """ 103 104 # creates and initializes the snapmirror 105 - name: Create ONTAP/ONTAP SnapMirror 106 na_ontap_snapmirror: 107 state: present 108 source_volume: test_src 109 destination_volume: test_dest 110 source_vserver: ansible_src 111 destination_vserver: ansible_dest 112 schedule: hourly 113 policy: MirrorAllSnapshots 114 max_transfer_rate: 1000 115 hostname: "{{ destination_cluster_hostname }}" 116 username: "{{ destination_cluster_username }}" 117 password: "{{ destination_cluster_password }}" 118 119 # creates and initializes the snapmirror between vservers 120 - name: Create ONTAP/ONTAP vserver SnapMirror 121 na_ontap_snapmirror: 122 state: present 123 source_vserver: ansible_src 124 destination_vserver: ansible_dest 125 identity_preserve: true 126 hostname: "{{ destination_cluster_hostname }}" 127 username: "{{ destination_cluster_username }}" 128 password: "{{ destination_cluster_password }}" 129 130 # existing snapmirror relation with status 'snapmirrored' will be initialized 131 - name: Initialize ONTAP/ONTAP SnapMirror 132 na_ontap_snapmirror: 133 state: present 134 source_path: 'ansible:test' 135 destination_path: 'ansible:dest' 136 hostname: "{{ destination_cluster_hostname }}" 137 username: "{{ destination_cluster_username }}" 138 password: "{{ destination_cluster_password }}" 139 140 - name: Delete SnapMirror 141 na_ontap_snapmirror: 142 state: absent 143 destination_path: <path> 144 source_hostname: "{{ source_hostname }}" 145 hostname: "{{ destination_cluster_hostname }}" 146 username: "{{ destination_cluster_username }}" 147 password: "{{ destination_cluster_password }}" 148 149 - name: Set schedule to NULL 150 na_ontap_snapmirror: 151 state: present 152 destination_path: <path> 153 schedule: "" 154 hostname: "{{ destination_cluster_hostname }}" 155 username: "{{ destination_cluster_username }}" 156 password: "{{ destination_cluster_password }}" 157 158 - name: Create SnapMirror from ElementSW to ONTAP 159 na_ontap_snapmirror: 160 state: present 161 connection_type: elementsw_ontap 162 source_path: '10.10.10.10:/lun/300' 163 destination_path: 'ansible_test:ansible_dest_vol' 164 schedule: hourly 165 policy: MirrorLatest 166 hostname: "{{ netapp_hostname }}" 167 username: "{{ netapp_username }}" 168 password: "{{ netapp_password }}" 169 source_hostname: " {{ Element_cluster_mvip }}" 170 source_username: "{{ Element_cluster_username }}" 171 source_password: "{{ Element_cluster_password }}" 172 173 - name: Create SnapMirror from ONTAP to ElementSW 174 na_ontap_snapmirror: 175 state: present 176 connection_type: ontap_elementsw 177 destination_path: '10.10.10.10:/lun/300' 178 source_path: 'ansible_test:ansible_dest_vol' 179 policy: MirrorLatest 180 hostname: "{{ Element_cluster_mvip }}" 181 username: "{{ Element_cluster_username }}" 182 password: "{{ Element_cluster_password }}" 183 source_hostname: " {{ netapp_hostname }}" 184 source_username: "{{ netapp_username }}" 185 source_password: "{{ netapp_password }}" 186""" 187 188RETURN = """ 189""" 190 191import re 192import traceback 193from ansible.module_utils.basic import AnsibleModule 194from ansible.module_utils._text import to_native 195import ansible.module_utils.netapp as netapp_utils 196from ansible.module_utils.netapp_elementsw_module import NaElementSWModule 197from ansible.module_utils.netapp_module import NetAppModule 198 199HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() 200 201HAS_SF_SDK = netapp_utils.has_sf_sdk() 202try: 203 import solidfire.common 204except ImportError: 205 HAS_SF_SDK = False 206 207 208class NetAppONTAPSnapmirror(object): 209 """ 210 Class with Snapmirror methods 211 """ 212 213 def __init__(self): 214 215 self.argument_spec = netapp_utils.na_ontap_host_argument_spec() 216 self.argument_spec.update(dict( 217 state=dict(required=False, type='str', choices=['present', 'absent'], default='present'), 218 source_vserver=dict(required=False, type='str'), 219 destination_vserver=dict(required=False, type='str'), 220 source_volume=dict(required=False, type='str'), 221 destination_volume=dict(required=False, type='str'), 222 source_path=dict(required=False, type='str'), 223 destination_path=dict(required=False, type='str'), 224 schedule=dict(required=False, type='str'), 225 policy=dict(required=False, type='str'), 226 relationship_type=dict(required=False, type='str', 227 choices=['data_protection', 'load_sharing', 228 'vault', 'restore', 229 'transition_data_protection', 230 'extended_data_protection'] 231 ), 232 source_hostname=dict(required=False, type='str'), 233 connection_type=dict(required=False, type='str', 234 choices=['ontap_ontap', 'elementsw_ontap', 'ontap_elementsw'], 235 default='ontap_ontap'), 236 source_username=dict(required=False, type='str'), 237 source_password=dict(required=False, type='str', no_log=True), 238 max_transfer_rate=dict(required=False, type='int'), 239 identity_preserve=dict(required=False, type='bool') 240 )) 241 242 self.module = AnsibleModule( 243 argument_spec=self.argument_spec, 244 required_together=(['source_volume', 'destination_volume'], 245 ['source_vserver', 'destination_vserver']), 246 supports_check_mode=True 247 ) 248 249 self.na_helper = NetAppModule() 250 self.parameters = self.na_helper.set_parameters(self.module.params) 251 # setup later if required 252 self.source_server = None 253 # only for ElementSW -> ONTAP snapmirroring, validate if ElementSW SDK is available 254 if self.parameters.get('connection_type') in ['elementsw_ontap', 'ontap_elementsw']: 255 if HAS_SF_SDK is False: 256 self.module.fail_json(msg="Unable to import the SolidFire Python SDK") 257 if HAS_NETAPP_LIB is False: 258 self.module.fail_json(msg="the python NetApp-Lib module is required") 259 if self.parameters.get('connection_type') != 'ontap_elementsw': 260 self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) 261 else: 262 if self.parameters.get('source_username'): 263 self.module.params['username'] = self.parameters['source_username'] 264 if self.parameters.get('source_password'): 265 self.module.params['password'] = self.parameters['source_password'] 266 self.module.params['hostname'] = self.parameters['source_hostname'] 267 self.server = netapp_utils.setup_na_ontap_zapi(module=self.module) 268 269 def set_element_connection(self, kind): 270 if kind == 'source': 271 self.module.params['hostname'] = self.parameters['source_hostname'] 272 self.module.params['username'] = self.parameters['source_username'] 273 self.module.params['password'] = self.parameters['source_password'] 274 elif kind == 'destination': 275 self.module.params['hostname'] = self.parameters['hostname'] 276 self.module.params['username'] = self.parameters['username'] 277 self.module.params['password'] = self.parameters['password'] 278 elem = netapp_utils.create_sf_connection(module=self.module) 279 elementsw_helper = NaElementSWModule(elem) 280 return elementsw_helper, elem 281 282 def snapmirror_get_iter(self, destination=None): 283 """ 284 Compose NaElement object to query current SnapMirror relations using destination-path 285 SnapMirror relation for a destination path is unique 286 :return: NaElement object for SnapMirror-get-iter 287 """ 288 snapmirror_get_iter = netapp_utils.zapi.NaElement('snapmirror-get-iter') 289 query = netapp_utils.zapi.NaElement('query') 290 snapmirror_info = netapp_utils.zapi.NaElement('snapmirror-info') 291 if destination is None: 292 destination = self.parameters['destination_path'] 293 snapmirror_info.add_new_child('destination-location', destination) 294 query.add_child_elem(snapmirror_info) 295 snapmirror_get_iter.add_child_elem(query) 296 return snapmirror_get_iter 297 298 def snapmirror_get(self, destination=None): 299 """ 300 Get current SnapMirror relations 301 :return: Dictionary of current SnapMirror details if query successful, else None 302 """ 303 snapmirror_get_iter = self.snapmirror_get_iter(destination) 304 snap_info = dict() 305 try: 306 result = self.server.invoke_successfully(snapmirror_get_iter, enable_tunneling=True) 307 except netapp_utils.zapi.NaApiError as error: 308 self.module.fail_json(msg='Error fetching snapmirror info: %s' % to_native(error), 309 exception=traceback.format_exc()) 310 if result.get_child_by_name('num-records') and \ 311 int(result.get_child_content('num-records')) > 0: 312 snapmirror_info = result.get_child_by_name('attributes-list').get_child_by_name( 313 'snapmirror-info') 314 snap_info['mirror_state'] = snapmirror_info.get_child_content('mirror-state') 315 snap_info['status'] = snapmirror_info.get_child_content('relationship-status') 316 snap_info['schedule'] = snapmirror_info.get_child_content('schedule') 317 snap_info['policy'] = snapmirror_info.get_child_content('policy') 318 snap_info['relationship'] = snapmirror_info.get_child_content('relationship-type') 319 if snapmirror_info.get_child_by_name('max-transfer-rate'): 320 snap_info['max_transfer_rate'] = int(snapmirror_info.get_child_content('max-transfer-rate')) 321 if snap_info['schedule'] is None: 322 snap_info['schedule'] = "" 323 return snap_info 324 return None 325 326 def check_if_remote_volume_exists(self): 327 """ 328 Validate existence of source volume 329 :return: True if volume exists, False otherwise 330 """ 331 self.set_source_cluster_connection() 332 # do a get volume to check if volume exists or not 333 volume_info = netapp_utils.zapi.NaElement('volume-get-iter') 334 volume_attributes = netapp_utils.zapi.NaElement('volume-attributes') 335 volume_id_attributes = netapp_utils.zapi.NaElement('volume-id-attributes') 336 volume_id_attributes.add_new_child('name', self.parameters['source_volume']) 337 # if source_volume is present, then source_vserver is also guaranteed to be present 338 volume_id_attributes.add_new_child('vserver-name', self.parameters['source_vserver']) 339 volume_attributes.add_child_elem(volume_id_attributes) 340 query = netapp_utils.zapi.NaElement('query') 341 query.add_child_elem(volume_attributes) 342 volume_info.add_child_elem(query) 343 try: 344 result = self.source_server.invoke_successfully(volume_info, True) 345 except netapp_utils.zapi.NaApiError as error: 346 self.module.fail_json(msg='Error fetching source volume details %s : %s' 347 % (self.parameters['source_volume'], to_native(error)), 348 exception=traceback.format_exc()) 349 if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0: 350 return True 351 return False 352 353 def snapmirror_create(self): 354 """ 355 Create a SnapMirror relationship 356 """ 357 if self.parameters.get('source_hostname') and self.parameters.get('source_volume'): 358 if not self.check_if_remote_volume_exists(): 359 self.module.fail_json(msg='Source volume does not exist. Please specify a volume that exists') 360 options = {'source-location': self.parameters['source_path'], 361 'destination-location': self.parameters['destination_path']} 362 snapmirror_create = netapp_utils.zapi.NaElement.create_node_with_children('snapmirror-create', **options) 363 if self.parameters.get('relationship_type'): 364 snapmirror_create.add_new_child('relationship-type', self.parameters['relationship_type']) 365 if self.parameters.get('schedule'): 366 snapmirror_create.add_new_child('schedule', self.parameters['schedule']) 367 if self.parameters.get('policy'): 368 snapmirror_create.add_new_child('policy', self.parameters['policy']) 369 if self.parameters.get('max_transfer_rate'): 370 snapmirror_create.add_new_child('max-transfer-rate', str(self.parameters['max_transfer_rate'])) 371 if self.parameters.get('identity_preserve'): 372 snapmirror_create.add_new_child('identity-preserve', str(self.parameters['identity_preserve'])) 373 try: 374 self.server.invoke_successfully(snapmirror_create, enable_tunneling=True) 375 self.snapmirror_initialize() 376 except netapp_utils.zapi.NaApiError as error: 377 self.module.fail_json(msg='Error creating SnapMirror %s' % to_native(error), 378 exception=traceback.format_exc()) 379 380 def set_source_cluster_connection(self): 381 """ 382 Setup ontap ZAPI server connection for source hostname 383 :return: None 384 """ 385 if self.parameters.get('source_username'): 386 self.module.params['username'] = self.parameters['source_username'] 387 if self.parameters.get('source_password'): 388 self.module.params['password'] = self.parameters['source_password'] 389 self.module.params['hostname'] = self.parameters['source_hostname'] 390 self.source_server = netapp_utils.setup_na_ontap_zapi(module=self.module) 391 392 def delete_snapmirror(self, is_hci, relationship_type): 393 """ 394 Delete a SnapMirror relationship 395 #1. Quiesce the SnapMirror relationship at destination 396 #2. Break the SnapMirror relationship at the destination 397 #3. Release the SnapMirror at source 398 #4. Delete SnapMirror at destination 399 """ 400 if not is_hci: 401 if not self.parameters.get('source_hostname'): 402 self.module.fail_json(msg='Missing parameters for delete: Please specify the ' 403 'source cluster hostname to release the SnapMirror relation') 404 # Quiesce at destination 405 self.snapmirror_quiesce() 406 # Break at destination 407 if relationship_type not in ['load_sharing', 'vault']: 408 self.snapmirror_break() 409 # if source is ONTAP, release the destination at source cluster 410 if not is_hci: 411 self.set_source_cluster_connection() 412 if self.get_destination(): 413 # Release at source 414 self.snapmirror_release() 415 # Delete at destination 416 self.snapmirror_delete() 417 418 def snapmirror_quiesce(self): 419 """ 420 Quiesce SnapMirror relationship - disable all future transfers to this destination 421 """ 422 options = {'destination-location': self.parameters['destination_path']} 423 424 snapmirror_quiesce = netapp_utils.zapi.NaElement.create_node_with_children( 425 'snapmirror-quiesce', **options) 426 try: 427 self.server.invoke_successfully(snapmirror_quiesce, 428 enable_tunneling=True) 429 except netapp_utils.zapi.NaApiError as error: 430 self.module.fail_json(msg='Error Quiescing SnapMirror : %s' 431 % (to_native(error)), 432 exception=traceback.format_exc()) 433 434 def snapmirror_delete(self): 435 """ 436 Delete SnapMirror relationship at destination cluster 437 """ 438 options = {'destination-location': self.parameters['destination_path']} 439 440 snapmirror_delete = netapp_utils.zapi.NaElement.create_node_with_children( 441 'snapmirror-destroy', **options) 442 try: 443 self.server.invoke_successfully(snapmirror_delete, 444 enable_tunneling=True) 445 except netapp_utils.zapi.NaApiError as error: 446 self.module.fail_json(msg='Error deleting SnapMirror : %s' 447 % (to_native(error)), 448 exception=traceback.format_exc()) 449 450 def snapmirror_break(self, destination=None): 451 """ 452 Break SnapMirror relationship at destination cluster 453 """ 454 if destination is None: 455 destination = self.parameters['destination_path'] 456 options = {'destination-location': destination} 457 snapmirror_break = netapp_utils.zapi.NaElement.create_node_with_children( 458 'snapmirror-break', **options) 459 try: 460 self.server.invoke_successfully(snapmirror_break, 461 enable_tunneling=True) 462 except netapp_utils.zapi.NaApiError as error: 463 self.module.fail_json(msg='Error breaking SnapMirror relationship : %s' 464 % (to_native(error)), 465 exception=traceback.format_exc()) 466 467 def snapmirror_release(self): 468 """ 469 Release SnapMirror relationship from source cluster 470 """ 471 options = {'destination-location': self.parameters['destination_path']} 472 snapmirror_release = netapp_utils.zapi.NaElement.create_node_with_children( 473 'snapmirror-release', **options) 474 try: 475 self.source_server.invoke_successfully(snapmirror_release, 476 enable_tunneling=True) 477 except netapp_utils.zapi.NaApiError as error: 478 self.module.fail_json(msg='Error releasing SnapMirror relationship : %s' 479 % (to_native(error)), 480 exception=traceback.format_exc()) 481 482 def snapmirror_abort(self): 483 """ 484 Abort a SnapMirror relationship in progress 485 """ 486 options = {'destination-location': self.parameters['destination_path']} 487 snapmirror_abort = netapp_utils.zapi.NaElement.create_node_with_children( 488 'snapmirror-abort', **options) 489 try: 490 self.server.invoke_successfully(snapmirror_abort, 491 enable_tunneling=True) 492 except netapp_utils.zapi.NaApiError as error: 493 self.module.fail_json(msg='Error aborting SnapMirror relationship : %s' 494 % (to_native(error)), 495 exception=traceback.format_exc()) 496 497 def snapmirror_initialize(self): 498 """ 499 Initialize SnapMirror based on relationship type 500 """ 501 current = self.snapmirror_get() 502 if current['mirror_state'] != 'snapmirrored': 503 initialize_zapi = 'snapmirror-initialize' 504 if self.parameters.get('relationship_type') and self.parameters['relationship_type'] == 'load_sharing': 505 initialize_zapi = 'snapmirror-initialize-ls-set' 506 options = {'source-location': self.parameters['source_path']} 507 else: 508 options = {'destination-location': self.parameters['destination_path']} 509 snapmirror_init = netapp_utils.zapi.NaElement.create_node_with_children( 510 initialize_zapi, **options) 511 try: 512 self.server.invoke_successfully(snapmirror_init, 513 enable_tunneling=True) 514 except netapp_utils.zapi.NaApiError as error: 515 self.module.fail_json(msg='Error initializing SnapMirror : %s' 516 % (to_native(error)), 517 exception=traceback.format_exc()) 518 519 def snapmirror_modify(self, modify): 520 """ 521 Modify SnapMirror schedule or policy 522 """ 523 options = {'destination-location': self.parameters['destination_path']} 524 snapmirror_modify = netapp_utils.zapi.NaElement.create_node_with_children( 525 'snapmirror-modify', **options) 526 if modify.get('schedule') is not None: 527 snapmirror_modify.add_new_child('schedule', modify.get('schedule')) 528 if modify.get('policy'): 529 snapmirror_modify.add_new_child('policy', modify.get('policy')) 530 if modify.get('max_transfer_rate'): 531 snapmirror_modify.add_new_child('max-transfer-rate', str(modify.get('max_transfer_rate'))) 532 try: 533 self.server.invoke_successfully(snapmirror_modify, 534 enable_tunneling=True) 535 except netapp_utils.zapi.NaApiError as error: 536 self.module.fail_json(msg='Error modifying SnapMirror schedule or policy : %s' 537 % (to_native(error)), 538 exception=traceback.format_exc()) 539 540 def snapmirror_update(self): 541 """ 542 Update data in destination endpoint 543 """ 544 options = {'destination-location': self.parameters['destination_path']} 545 snapmirror_update = netapp_utils.zapi.NaElement.create_node_with_children( 546 'snapmirror-update', **options) 547 try: 548 result = self.server.invoke_successfully(snapmirror_update, 549 enable_tunneling=True) 550 except netapp_utils.zapi.NaApiError as error: 551 self.module.fail_json(msg='Error updating SnapMirror : %s' 552 % (to_native(error)), 553 exception=traceback.format_exc()) 554 555 def check_parameters(self): 556 """ 557 Validate parameters and fail if one or more required params are missing 558 Update source and destination path from vserver and volume parameters 559 """ 560 if self.parameters['state'] == 'present'\ 561 and (self.parameters.get('source_path') or self.parameters.get('destination_path')): 562 if not self.parameters.get('destination_path') or not self.parameters.get('source_path'): 563 self.module.fail_json(msg='Missing parameters: Source path or Destination path') 564 elif self.parameters.get('source_volume'): 565 if not self.parameters.get('source_vserver') or not self.parameters.get('destination_vserver'): 566 self.module.fail_json(msg='Missing parameters: source vserver or destination vserver or both') 567 self.parameters['source_path'] = self.parameters['source_vserver'] + ":" + self.parameters['source_volume'] 568 self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" +\ 569 self.parameters['destination_volume'] 570 elif self.parameters.get('source_vserver'): 571 self.parameters['source_path'] = self.parameters['source_vserver'] + ":" 572 self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" 573 574 def get_destination(self): 575 result = None 576 release_get = netapp_utils.zapi.NaElement('snapmirror-get-destination-iter') 577 query = netapp_utils.zapi.NaElement('query') 578 snapmirror_dest_info = netapp_utils.zapi.NaElement('snapmirror-destination-info') 579 snapmirror_dest_info.add_new_child('destination-location', self.parameters['destination_path']) 580 query.add_child_elem(snapmirror_dest_info) 581 release_get.add_child_elem(query) 582 try: 583 result = self.source_server.invoke_successfully(release_get, enable_tunneling=True) 584 except netapp_utils.zapi.NaApiError as error: 585 self.module.fail_json(msg='Error fetching snapmirror destinations info: %s' % to_native(error), 586 exception=traceback.format_exc()) 587 if result.get_child_by_name('num-records') and \ 588 int(result.get_child_content('num-records')) > 0: 589 return True 590 return None 591 592 @staticmethod 593 def element_source_path_format_matches(value): 594 return re.match(pattern=r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\/lun\/[0-9]+", 595 string=value) 596 597 def check_elementsw_parameters(self, kind='source'): 598 """ 599 Validate all ElementSW cluster parameters required for managing the SnapMirror relationship 600 Validate if both source and destination paths are present 601 Validate if source_path follows the required format 602 Validate SVIP 603 Validate if ElementSW volume exists 604 :return: None 605 """ 606 path = None 607 if kind == 'destination': 608 path = self.parameters.get('destination_path') 609 elif kind == 'source': 610 path = self.parameters.get('source_path') 611 if path is None: 612 self.module.fail_json(msg="Error: Missing required parameter %s_path for " 613 "connection_type %s" % (kind, self.parameters['connection_type'])) 614 else: 615 if NetAppONTAPSnapmirror.element_source_path_format_matches(path) is None: 616 self.module.fail_json(msg="Error: invalid %s_path %s. " 617 "If the path is a ElementSW cluster, the value should be of the format" 618 " <Element_SVIP>:/lun/<Element_VOLUME_ID>" % (kind, path)) 619 # validate source_path 620 elementsw_helper, elem = self.set_element_connection(kind) 621 self.validate_elementsw_svip(path, elem) 622 self.check_if_elementsw_volume_exists(path, elementsw_helper) 623 624 def validate_elementsw_svip(self, path, elem): 625 """ 626 Validate ElementSW cluster SVIP 627 :return: None 628 """ 629 result = None 630 try: 631 result = elem.get_cluster_info() 632 except solidfire.common.ApiServerError as err: 633 self.module.fail_json(msg="Error fetching SVIP", exception=to_native(err)) 634 if result and result.cluster_info.svip: 635 cluster_svip = result.cluster_info.svip 636 svip = path.split(':')[0] # split IP address from source_path 637 if svip != cluster_svip: 638 self.module.fail_json(msg="Error: Invalid SVIP") 639 640 def check_if_elementsw_volume_exists(self, path, elementsw_helper): 641 """ 642 Check if remote ElementSW volume exists 643 :return: None 644 """ 645 volume_id, vol_id = None, path.split('/')[-1] 646 try: 647 volume_id = elementsw_helper.volume_id_exists(int(vol_id)) 648 except solidfire.common.ApiServerError as err: 649 self.module.fail_json(msg="Error fetching Volume details", exception=to_native(err)) 650 651 if volume_id is None: 652 self.module.fail_json(msg="Error: Source volume does not exist in the ElementSW cluster") 653 654 def asup_log_for_cserver(self, event_name): 655 """ 656 Fetch admin vserver for the given cluster 657 Create and Autosupport log event with the given module name 658 :param event_name: Name of the event log 659 :return: None 660 """ 661 results = netapp_utils.get_cserver(self.server) 662 cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) 663 netapp_utils.ems_log_event(event_name, cserver) 664 665 def apply(self): 666 """ 667 Apply action to SnapMirror 668 """ 669 self.asup_log_for_cserver("na_ontap_snapmirror") 670 # source is ElementSW 671 if self.parameters['state'] == 'present' and self.parameters.get('connection_type') == 'elementsw_ontap': 672 self.check_elementsw_parameters() 673 elif self.parameters.get('connection_type') == 'ontap_elementsw': 674 self.check_elementsw_parameters('destination') 675 else: 676 self.check_parameters() 677 if self.parameters['state'] == 'present' and self.parameters.get('connection_type') == 'ontap_elementsw': 678 current_elementsw_ontap = self.snapmirror_get(self.parameters['source_path']) 679 if current_elementsw_ontap is None: 680 self.module.fail_json(msg='Error: creating an ONTAP to ElementSW snapmirror relationship requires an ' 681 'established SnapMirror relation from ElementSW to ONTAP cluster') 682 current = self.snapmirror_get() 683 cd_action = self.na_helper.get_cd_action(current, self.parameters) 684 modify = self.na_helper.get_modified_attributes(current, self.parameters) 685 element_snapmirror = False 686 if cd_action == 'create': 687 self.snapmirror_create() 688 elif cd_action == 'delete': 689 if current['status'] == 'transferring': 690 self.snapmirror_abort() 691 else: 692 if self.parameters.get('connection_type') == 'elementsw_ontap': 693 element_snapmirror = True 694 self.delete_snapmirror(element_snapmirror, current['relationship']) 695 else: 696 if modify: 697 self.snapmirror_modify(modify) 698 # check for initialize 699 if current and current['mirror_state'] != 'snapmirrored': 700 self.snapmirror_initialize() 701 # set changed explicitly for initialize 702 self.na_helper.changed = True 703 # Update when create is called again, or modify is being called 704 if self.parameters['state'] == 'present': 705 self.snapmirror_update() 706 self.module.exit_json(changed=self.na_helper.changed) 707 708 709def main(): 710 """Execute action""" 711 community_obj = NetAppONTAPSnapmirror() 712 community_obj.apply() 713 714 715if __name__ == '__main__': 716 main() 717