1# Copyright (c) 2017 Dell Inc. or its subsidiaries. 2# All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15 16import time 17 18from oslo_log import log as logging 19from oslo_service import loopingcall 20 21from cinder import coordination 22from cinder import exception 23from cinder.i18n import _ 24from cinder.volume.drivers.dell_emc.vmax import utils 25 26LOG = logging.getLogger(__name__) 27 28WRITE_DISABLED = "Write Disabled" 29UNLINK_INTERVAL = 15 30UNLINK_RETRIES = 30 31 32 33class VMAXProvision(object): 34 """Provisioning Class for Dell EMC VMAX volume drivers. 35 36 It supports VMAX arrays. 37 """ 38 def __init__(self, rest): 39 self.utils = utils.VMAXUtils() 40 self.rest = rest 41 42 def create_storage_group( 43 self, array, storagegroup_name, srp, slo, workload, 44 extra_specs, do_disable_compression=False): 45 """Create a new storage group. 46 47 :param array: the array serial number 48 :param storagegroup_name: the group name (String) 49 :param srp: the SRP (String) 50 :param slo: the SLO (String) 51 :param workload: the workload (String) 52 :param extra_specs: additional info 53 :param do_disable_compression: disable compression flag 54 :returns: storagegroup - storage group object 55 """ 56 start_time = time.time() 57 58 @coordination.synchronized("emc-sg-{storage_group}") 59 def do_create_storage_group(storage_group): 60 # Check if storage group has been recently created 61 storagegroup = self.rest.get_storage_group( 62 array, storagegroup_name) 63 if storagegroup is None: 64 storagegroup = self.rest.create_storage_group( 65 array, storage_group, srp, slo, workload, extra_specs, 66 do_disable_compression) 67 68 LOG.debug("Create storage group took: %(delta)s H:MM:SS.", 69 {'delta': self.utils.get_time_delta(start_time, 70 time.time())}) 71 LOG.info("Storage group %(sg)s created successfully.", 72 {'sg': storagegroup_name}) 73 else: 74 LOG.info("Storage group %(sg)s already exists.", 75 {'sg': storagegroup_name}) 76 return storagegroup 77 78 return do_create_storage_group(storagegroup_name) 79 80 def create_volume_from_sg(self, array, volume_name, storagegroup_name, 81 volume_size, extra_specs): 82 """Create a new volume in the given storage group. 83 84 :param array: the array serial number 85 :param volume_name: the volume name (String) 86 :param storagegroup_name: the storage group name 87 :param volume_size: volume size (String) 88 :param extra_specs: the extra specifications 89 :returns: dict -- volume_dict - the volume dict 90 """ 91 @coordination.synchronized("emc-sg-{storage_group}") 92 def do_create_volume_from_sg(storage_group): 93 start_time = time.time() 94 95 volume_dict = self.rest.create_volume_from_sg( 96 array, volume_name, storage_group, 97 volume_size, extra_specs) 98 99 LOG.debug("Create volume from storage group " 100 "took: %(delta)s H:MM:SS.", 101 {'delta': self.utils.get_time_delta(start_time, 102 time.time())}) 103 return volume_dict 104 return do_create_volume_from_sg(storagegroup_name) 105 106 def delete_volume_from_srp(self, array, device_id, volume_name): 107 """Delete a volume from the srp. 108 109 :param array: the array serial number 110 :param device_id: the volume device id 111 :param volume_name: the volume name 112 """ 113 start_time = time.time() 114 LOG.debug("Delete volume %(volume_name)s from srp.", 115 {'volume_name': volume_name}) 116 self.rest.delete_volume(array, device_id) 117 LOG.debug("Delete volume took: %(delta)s H:MM:SS.", 118 {'delta': self.utils.get_time_delta( 119 start_time, time.time())}) 120 121 def create_volume_snapvx(self, array, source_device_id, 122 snap_name, extra_specs): 123 """Create a snapVx of a volume. 124 125 :param array: the array serial number 126 :param source_device_id: source volume device id 127 :param snap_name: the snapshot name 128 :param extra_specs: the extra specifications 129 """ 130 start_time = time.time() 131 LOG.debug("Create Snap Vx snapshot of: %(source)s.", 132 {'source': source_device_id}) 133 self.rest.create_volume_snap( 134 array, snap_name, source_device_id, extra_specs) 135 LOG.debug("Create volume snapVx took: %(delta)s H:MM:SS.", 136 {'delta': self.utils.get_time_delta(start_time, 137 time.time())}) 138 139 def create_volume_replica( 140 self, array, source_device_id, target_device_id, 141 snap_name, extra_specs, create_snap=False): 142 """Create a snap vx of a source and copy to a target. 143 144 :param array: the array serial number 145 :param source_device_id: source volume device id 146 :param target_device_id: target volume device id 147 :param snap_name: the name for the snap shot 148 :param extra_specs: extra specifications 149 :param create_snap: Flag for create snapvx 150 """ 151 start_time = time.time() 152 if create_snap: 153 self.create_volume_snapvx(array, source_device_id, 154 snap_name, extra_specs) 155 # Link source to target 156 self.rest.modify_volume_snap( 157 array, source_device_id, target_device_id, snap_name, 158 extra_specs, link=True) 159 160 LOG.debug("Create element replica took: %(delta)s H:MM:SS.", 161 {'delta': self.utils.get_time_delta(start_time, 162 time.time())}) 163 164 def break_replication_relationship( 165 self, array, target_device_id, source_device_id, snap_name, 166 extra_specs): 167 """Unlink a snapshot from its target volume. 168 169 :param array: the array serial number 170 :param source_device_id: source volume device id 171 :param target_device_id: target volume device id 172 :param snap_name: the name for the snap shot 173 :param extra_specs: extra specifications 174 """ 175 LOG.debug("Break snap vx link relationship between: %(src)s " 176 "and: %(tgt)s.", 177 {'src': source_device_id, 'tgt': target_device_id}) 178 179 self._unlink_volume(array, source_device_id, target_device_id, 180 snap_name, extra_specs) 181 182 def _unlink_volume( 183 self, array, source_device_id, target_device_id, snap_name, 184 extra_specs, list_volume_pairs=None): 185 """Unlink a target volume from its source volume. 186 187 :param array: the array serial number 188 :param source_device_id: the source device id 189 :param target_device_id: the target device id 190 :param snap_name: the snap name 191 :param extra_specs: extra specifications 192 :param list_volume_pairs: list of volume pairs, optional 193 :return: return code 194 """ 195 196 def _unlink_vol(): 197 """Called at an interval until the synchronization is finished. 198 199 :raises: loopingcall.LoopingCallDone 200 """ 201 retries = kwargs['retries'] 202 try: 203 kwargs['retries'] = retries + 1 204 if not kwargs['modify_vol_success']: 205 self.rest.modify_volume_snap( 206 array, source_device_id, target_device_id, snap_name, 207 extra_specs, unlink=True, 208 list_volume_pairs=list_volume_pairs) 209 kwargs['modify_vol_success'] = True 210 except exception.VolumeBackendAPIException: 211 pass 212 213 if kwargs['retries'] > UNLINK_RETRIES: 214 LOG.error("_unlink_volume failed after %(retries)d " 215 "tries.", {'retries': retries}) 216 raise loopingcall.LoopingCallDone(retvalue=30) 217 if kwargs['modify_vol_success']: 218 raise loopingcall.LoopingCallDone() 219 220 kwargs = {'retries': 0, 221 'modify_vol_success': False} 222 timer = loopingcall.FixedIntervalLoopingCall(_unlink_vol) 223 rc = timer.start(interval=UNLINK_INTERVAL).wait() 224 return rc 225 226 def delete_volume_snap(self, array, snap_name, 227 source_device_id, restored=False): 228 """Delete a snapVx snapshot of a volume. 229 230 :param array: the array serial number 231 :param snap_name: the snapshot name 232 :param source_device_id: the source device id 233 :param restored: Flag to indicate if restored session is being deleted 234 """ 235 LOG.debug("Delete SnapVx: %(snap_name)s for volume %(vol)s.", 236 {'vol': source_device_id, 'snap_name': snap_name}) 237 self.rest.delete_volume_snap( 238 array, snap_name, source_device_id, restored) 239 240 def is_restore_complete(self, array, source_device_id, 241 snap_name, extra_specs): 242 """Check and wait for a restore to complete 243 244 :param array: the array serial number 245 :param source_device_id: source device id 246 :param snap_name: snapshot name 247 :param extra_specs: extra specification 248 :returns: bool 249 """ 250 251 def _wait_for_restore(): 252 """Called at an interval until the restore is finished. 253 254 :raises: loopingcall.LoopingCallDone 255 :raises: VolumeBackendAPIException 256 """ 257 retries = kwargs['retries'] 258 try: 259 kwargs['retries'] = retries + 1 260 if not kwargs['wait_for_restore_called']: 261 if self._is_restore_complete( 262 array, source_device_id, snap_name): 263 kwargs['wait_for_restore_called'] = True 264 except Exception: 265 exception_message = (_("Issue encountered waiting for " 266 "restore.")) 267 LOG.exception(exception_message) 268 raise exception.VolumeBackendAPIException( 269 data=exception_message) 270 271 if kwargs['wait_for_restore_called']: 272 raise loopingcall.LoopingCallDone() 273 if kwargs['retries'] > int(extra_specs[utils.RETRIES]): 274 LOG.error("_wait_for_restore failed after %(retries)d " 275 "tries.", {'retries': retries}) 276 raise loopingcall.LoopingCallDone( 277 retvalue=int(extra_specs[utils.RETRIES])) 278 279 kwargs = {'retries': 0, 280 'wait_for_restore_called': False} 281 timer = loopingcall.FixedIntervalLoopingCall(_wait_for_restore) 282 rc = timer.start(interval=int(extra_specs[utils.INTERVAL])).wait() 283 return rc 284 285 def _is_restore_complete(self, array, source_device_id, snap_name): 286 """Helper function to check if restore is complete. 287 288 :param array: the array serial number 289 :param source_device_id: source device id 290 :param snap_name: the snapshot name 291 :returns: restored -- bool 292 """ 293 restored = False 294 snap_details = self.rest.get_volume_snap( 295 array, source_device_id, snap_name) 296 if snap_details: 297 linked_devices = snap_details.get("linkedDevices", []) 298 for linked_device in linked_devices: 299 if ('targetDevice' in linked_device and 300 source_device_id == linked_device['targetDevice']): 301 if ('state' in linked_device and 302 linked_device['state'] == "Restored"): 303 restored = True 304 return restored 305 306 def delete_temp_volume_snap(self, array, snap_name, source_device_id): 307 """Delete the temporary snapshot created for clone operations. 308 309 There can be instances where the source and target both attempt to 310 delete a temp snapshot simultaneously, so we must lock the snap and 311 then double check it is on the array. 312 :param array: the array serial number 313 :param snap_name: the snapshot name 314 :param source_device_id: the source device id 315 """ 316 317 @coordination.synchronized("emc-snapvx-{snapvx_name}") 318 def do_delete_temp_snap(snapvx_name): 319 # Ensure snap has not been recently deleted 320 if self.rest.get_volume_snap( 321 array, source_device_id, snapvx_name): 322 self.delete_volume_snap(array, snapvx_name, source_device_id) 323 324 do_delete_temp_snap(snap_name) 325 326 def delete_volume_snap_check_for_links(self, array, snap_name, 327 source_devices, extra_specs): 328 """Check if a snap has any links before deletion. 329 330 If a snapshot has any links, break the replication relationship 331 before deletion. 332 :param array: the array serial number 333 :param snap_name: the snapshot name 334 :param source_devices: the source device ids 335 :param extra_specs: the extra specifications 336 """ 337 list_device_pairs = [] 338 if not isinstance(source_devices, list): 339 source_devices = [source_devices] 340 for source_device in source_devices: 341 LOG.debug("Check for linked devices to SnapVx: %(snap_name)s " 342 "for volume %(vol)s.", 343 {'vol': source_device, 'snap_name': snap_name}) 344 linked_list = self.rest.get_snap_linked_device_list( 345 array, source_device, snap_name) 346 if len(linked_list) == 1: 347 target_device = linked_list[0]['targetDevice'] 348 list_device_pairs.append((source_device, target_device)) 349 else: 350 for link in linked_list: 351 # If a single source volume has multiple targets, 352 # we must unlink each target individually 353 target_device = link['targetDevice'] 354 self._unlink_volume(array, source_device, target_device, 355 snap_name, extra_specs) 356 if list_device_pairs: 357 self._unlink_volume(array, "", "", snap_name, extra_specs, 358 list_volume_pairs=list_device_pairs) 359 self.delete_volume_snap(array, snap_name, source_devices) 360 361 def extend_volume(self, array, device_id, new_size, extra_specs, 362 rdf_group=None): 363 """Extend a volume. 364 365 :param array: the array serial number 366 :param device_id: the volume device id 367 :param new_size: the new size (GB) 368 :param extra_specs: the extra specifications 369 :param rdf_group: the rdf group number, if required 370 :returns: status_code 371 """ 372 start_time = time.time() 373 if rdf_group: 374 @coordination.synchronized('emc-rg-{rdf_group}') 375 def _extend_replicated_volume(rdf_group): 376 self.rest.extend_volume(array, device_id, 377 new_size, extra_specs) 378 _extend_replicated_volume(rdf_group) 379 else: 380 self.rest.extend_volume(array, device_id, new_size, extra_specs) 381 LOG.debug("Extend VMAX volume took: %(delta)s H:MM:SS.", 382 {'delta': self.utils.get_time_delta(start_time, 383 time.time())}) 384 385 def get_srp_pool_stats(self, array, array_info): 386 """Get the srp capacity stats. 387 388 :param array: the array serial number 389 :param array_info: the array dict 390 :returns: total_capacity_gb 391 :returns: remaining_capacity_gb 392 :returns: subscribed_capacity_gb 393 :returns: array_reserve_percent 394 """ 395 total_capacity_gb = 0 396 remaining_capacity_gb = 0 397 subscribed_capacity_gb = 0 398 array_reserve_percent = 0 399 srp = array_info['srpName'] 400 LOG.debug( 401 "Retrieving capacity for srp %(srpName)s on array %(array)s.", 402 {'srpName': srp, 'array': array}) 403 404 srp_details = self.rest.get_srp_by_name(array, srp) 405 if not srp_details: 406 LOG.error("Unable to retrieve srp instance of %(srpName)s on " 407 "array %(array)s.", 408 {'srpName': srp, 'array': array}) 409 return 0, 0, 0, 0, False 410 try: 411 total_capacity_gb = srp_details['total_usable_cap_gb'] 412 try: 413 used_capacity_gb = srp_details['total_used_cap_gb'] 414 remaining_capacity_gb = float( 415 total_capacity_gb - used_capacity_gb) 416 except KeyError: 417 remaining_capacity_gb = srp_details['fba_free_capacity'] 418 subscribed_capacity_gb = srp_details['total_subscribed_cap_gb'] 419 array_reserve_percent = srp_details['reserved_cap_percent'] 420 except KeyError: 421 pass 422 423 return (total_capacity_gb, remaining_capacity_gb, 424 subscribed_capacity_gb, array_reserve_percent) 425 426 def verify_slo_workload(self, array, slo, workload, srp): 427 """Check if SLO and workload values are valid. 428 429 :param array: the array serial number 430 :param slo: Service Level Object e.g bronze 431 :param workload: workload e.g DSS 432 :param srp: the storage resource pool name 433 :returns: boolean 434 """ 435 is_valid_slo, is_valid_workload = False, False 436 437 if workload and workload.lower() == 'none': 438 workload = None 439 440 if not workload: 441 is_valid_workload = True 442 443 if slo and slo.lower() == 'none': 444 slo = None 445 446 valid_slos = self.rest.get_slo_list(array) 447 valid_workloads = self.rest.get_workload_settings(array) 448 for valid_slo in valid_slos: 449 if slo == valid_slo: 450 is_valid_slo = True 451 break 452 453 for valid_workload in valid_workloads: 454 if workload == valid_workload: 455 is_valid_workload = True 456 break 457 458 if not slo: 459 is_valid_slo = True 460 if workload: 461 is_valid_workload = False 462 463 if not is_valid_slo: 464 LOG.error( 465 "SLO: %(slo)s is not valid. Valid values are: " 466 "%(valid_slos)s.", {'slo': slo, 'valid_slos': valid_slos}) 467 468 if not is_valid_workload: 469 LOG.error( 470 "Workload: %(workload)s is not valid. Valid values are " 471 "%(valid_workloads)s. Note you cannot " 472 "set a workload without an SLO.", 473 {'workload': workload, 'valid_workloads': valid_workloads}) 474 475 return is_valid_slo, is_valid_workload 476 477 def get_slo_workload_settings_from_storage_group( 478 self, array, sg_name): 479 """Get slo and workload settings from a storage group. 480 481 :param array: the array serial number 482 :param sg_name: the storage group name 483 :returns: storage group slo settings 484 """ 485 slo = 'NONE' 486 workload = 'NONE' 487 storage_group = self.rest.get_storage_group(array, sg_name) 488 if storage_group: 489 try: 490 slo = storage_group['slo'] 491 workload = 'NONE' if self.rest.is_next_gen_array(array) else ( 492 storage_group['workload']) 493 except KeyError: 494 pass 495 else: 496 exception_message = (_( 497 "Could not retrieve storage group %(sg_name)s. ") % 498 {'sg_name': sg_name}) 499 LOG.error(exception_message) 500 raise exception.VolumeBackendAPIException(data=exception_message) 501 return '%(slo)s+%(workload)s' % {'slo': slo, 'workload': workload} 502 503 @coordination.synchronized('emc-rg-{rdf_group}') 504 def break_rdf_relationship(self, array, device_id, target_device, 505 rdf_group, rep_extra_specs, state): 506 """Break the rdf relationship between a pair of devices. 507 508 :param array: the array serial number 509 :param device_id: the source device id 510 :param target_device: target device id 511 :param rdf_group: the rdf group number 512 :param rep_extra_specs: replication extra specs 513 :param state: the state of the rdf pair 514 """ 515 LOG.info("Suspending rdf pair: source device: %(src)s " 516 "target device: %(tgt)s.", 517 {'src': device_id, 'tgt': target_device}) 518 if state.lower() == utils.RDF_SYNCINPROG_STATE: 519 self.rest.wait_for_rdf_consistent_state( 520 array, device_id, target_device, 521 rep_extra_specs, state) 522 if state.lower() == utils.RDF_SUSPENDED_STATE: 523 LOG.info("RDF pair is already suspended") 524 else: 525 self.rest.modify_rdf_device_pair( 526 array, device_id, rdf_group, rep_extra_specs, suspend=True) 527 self.delete_rdf_pair(array, device_id, rdf_group, 528 target_device, rep_extra_specs) 529 530 def break_metro_rdf_pair(self, array, device_id, target_device, 531 rdf_group, rep_extra_specs, metro_grp): 532 """Delete replication for a Metro device pair. 533 534 Need to suspend the entire group before we can delete a single pair. 535 :param array: the array serial number 536 :param device_id: the device id 537 :param target_device: the target device id 538 :param rdf_group: the rdf group number 539 :param rep_extra_specs: the replication extra specifications 540 :param metro_grp: the metro storage group name 541 """ 542 # Suspend I/O on the RDF links... 543 LOG.info("Suspending I/O for all volumes in the RDF group: %(rdfg)s", 544 {'rdfg': rdf_group}) 545 self.disable_group_replication( 546 array, metro_grp, rdf_group, rep_extra_specs) 547 self.delete_rdf_pair(array, device_id, rdf_group, 548 target_device, rep_extra_specs) 549 550 def delete_rdf_pair( 551 self, array, device_id, rdf_group, target_device, extra_specs): 552 """Delete an rdf pairing. 553 554 If the replication mode is synchronous, only one attempt is required 555 to delete the pair. Otherwise, we need to wait until all the tracks 556 are cleared before the delete will be successful. As there is 557 currently no way to track this information, we keep attempting the 558 operation until it is successful. 559 560 :param array: the array serial number 561 :param device_id: source volume device id 562 :param rdf_group: the rdf group number 563 :param target_device: the target device 564 :param extra_specs: extra specifications 565 """ 566 LOG.info("Deleting rdf pair: source device: %(src)s " 567 "target device: %(tgt)s.", 568 {'src': device_id, 'tgt': target_device}) 569 if (extra_specs.get(utils.REP_MODE) and 570 extra_specs.get(utils.REP_MODE) == utils.REP_SYNC): 571 return self.rest.delete_rdf_pair(array, device_id, rdf_group) 572 573 def _delete_pair(): 574 """Delete a rdf volume pair. 575 576 Called at an interval until all the tracks are cleared 577 and the operation is successful. 578 579 :raises: loopingcall.LoopingCallDone 580 """ 581 retries = kwargs['retries'] 582 try: 583 kwargs['retries'] = retries + 1 584 if not kwargs['delete_pair_success']: 585 self.rest.delete_rdf_pair( 586 array, device_id, rdf_group) 587 kwargs['delete_pair_success'] = True 588 except exception.VolumeBackendAPIException: 589 pass 590 591 if kwargs['retries'] > UNLINK_RETRIES: 592 LOG.error("Delete volume pair failed after %(retries)d " 593 "tries.", {'retries': retries}) 594 raise loopingcall.LoopingCallDone(retvalue=30) 595 if kwargs['delete_pair_success']: 596 raise loopingcall.LoopingCallDone() 597 598 kwargs = {'retries': 0, 599 'delete_pair_success': False} 600 timer = loopingcall.FixedIntervalLoopingCall(_delete_pair) 601 rc = timer.start(interval=UNLINK_INTERVAL).wait() 602 return rc 603 604 def failover_volume(self, array, device_id, rdf_group, 605 extra_specs, local_vol_state, failover): 606 """Failover or back a volume pair. 607 608 :param array: the array serial number 609 :param device_id: the source device id 610 :param rdf_group: the rdf group number 611 :param extra_specs: extra specs 612 :param local_vol_state: the local volume state 613 :param failover: flag to indicate failover or failback -- bool 614 """ 615 if local_vol_state == WRITE_DISABLED: 616 LOG.info("Volume %(dev)s is already failed over.", 617 {'dev': device_id}) 618 return 619 if failover: 620 action = "Failing over" 621 else: 622 action = "Failing back" 623 LOG.info("%(action)s rdf pair: source device: %(src)s ", 624 {'action': action, 'src': device_id}) 625 626 @coordination.synchronized('emc-rg-{rdfg_no}') 627 def _failover_volume(rdfg_no): 628 self.rest.modify_rdf_device_pair( 629 array, device_id, rdfg_no, extra_specs) 630 631 _failover_volume(rdf_group) 632 633 def get_or_create_volume_group(self, array, group, extra_specs): 634 """Get or create a volume group. 635 636 Sometimes it may be necessary to recreate a volume group on the 637 backend - for example, when the last member volume has been removed 638 from the group, but the cinder group object has not been deleted. 639 :param array: the array serial number 640 :param group: the group object 641 :param extra_specs: the extra specifications 642 :return: group name 643 """ 644 vol_grp_name = self.utils.update_volume_group_name(group) 645 return self.get_or_create_group(array, vol_grp_name, extra_specs) 646 647 def get_or_create_group(self, array, group_name, extra_specs): 648 """Get or create a generic volume group. 649 650 :param array: the array serial number 651 :param group_name: the group name 652 :param extra_specs: the extra specifications 653 :return: group name 654 """ 655 storage_group = self.rest.get_storage_group(array, group_name) 656 if not storage_group: 657 self.create_volume_group(array, group_name, extra_specs) 658 return group_name 659 660 def create_volume_group(self, array, group_name, extra_specs): 661 """Create a generic volume group. 662 663 :param array: the array serial number 664 :param group_name: the name of the group 665 :param extra_specs: the extra specifications 666 :returns: volume_group 667 """ 668 return self.create_storage_group(array, group_name, 669 None, None, None, extra_specs) 670 671 def create_group_replica( 672 self, array, source_group, snap_name, extra_specs): 673 """Create a replica (snapVx) of a volume group. 674 675 :param array: the array serial number 676 :param source_group: the source group name 677 :param snap_name: the name for the snap shot 678 :param extra_specs: extra specifications 679 """ 680 LOG.debug("Creating Snap Vx snapshot of storage group: %(srcGroup)s.", 681 {'srcGroup': source_group}) 682 683 # Create snapshot 684 self.rest.create_storagegroup_snap( 685 array, source_group, snap_name, extra_specs) 686 687 def delete_group_replica(self, array, snap_name, source_group_name, 688 src_dev_ids, extra_specs): 689 """Delete the snapshot. 690 691 :param array: the array serial number 692 :param snap_name: the name for the snap shot 693 :param source_group_name: the source group name 694 :param src_dev_ids: the list of source device ids 695 :param extra_specs: extra specifications 696 """ 697 # Delete snapvx snapshot 698 LOG.debug("Deleting Snap Vx snapshot: source group: %(srcGroup)s " 699 "snapshot: %(snap_name)s.", 700 {'srcGroup': source_group_name, 'snap_name': snap_name}) 701 self.delete_volume_snap_check_for_links( 702 array, snap_name, src_dev_ids, extra_specs) 703 704 def link_and_break_replica(self, array, source_group_name, 705 target_group_name, snap_name, extra_specs, 706 list_volume_pairs, delete_snapshot=False): 707 """Links a group snap and breaks the relationship. 708 709 :param array: the array serial 710 :param source_group_name: the source group name 711 :param target_group_name: the target group name 712 :param snap_name: the snapshot name 713 :param extra_specs: extra specifications 714 :param list_volume_pairs: the list of volume pairs 715 :param delete_snapshot: delete snapshot flag 716 """ 717 LOG.debug("Linking Snap Vx snapshot: source group: %(srcGroup)s " 718 "targetGroup: %(tgtGroup)s.", 719 {'srcGroup': source_group_name, 720 'tgtGroup': target_group_name}) 721 # Link the snapshot 722 self.rest.modify_volume_snap( 723 array, None, None, snap_name, extra_specs, link=True, 724 list_volume_pairs=list_volume_pairs) 725 # Unlink the snapshot 726 LOG.debug("Unlinking Snap Vx snapshot: source group: %(srcGroup)s " 727 "targetGroup: %(tgtGroup)s.", 728 {'srcGroup': source_group_name, 729 'tgtGroup': target_group_name}) 730 self._unlink_volume(array, None, None, snap_name, extra_specs, 731 list_volume_pairs=list_volume_pairs) 732 # Delete the snapshot if necessary 733 if delete_snapshot: 734 LOG.debug("Deleting Snap Vx snapshot: source group: %(srcGroup)s " 735 "snapshot: %(snap_name)s.", 736 {'srcGroup': source_group_name, 737 'snap_name': snap_name}) 738 source_devices = [a for a, b in list_volume_pairs] 739 self.delete_volume_snap(array, snap_name, source_devices) 740 741 def enable_group_replication(self, array, storagegroup_name, 742 rdf_group_num, extra_specs, establish=False): 743 """Resume rdf replication on a storage group. 744 745 Replication is enabled by default. This allows resuming 746 replication on a suspended group. 747 :param array: the array serial number 748 :param storagegroup_name: the storagegroup name 749 :param rdf_group_num: the rdf group number 750 :param extra_specs: the extra specifications 751 :param establish: flag to indicate 'establish' instead of 'resume' 752 """ 753 action = "Establish" if establish is True else "Resume" 754 self.rest.modify_storagegroup_rdf( 755 array, storagegroup_name, rdf_group_num, action, extra_specs) 756 757 def disable_group_replication(self, array, storagegroup_name, 758 rdf_group_num, extra_specs): 759 """Suspend rdf replication on a storage group. 760 761 This does not delete the rdf pairs, that can only be done 762 by deleting the group. This method suspends all i/o activity 763 on the rdf links. 764 :param array: the array serial number 765 :param storagegroup_name: the storagegroup name 766 :param rdf_group_num: the rdf group number 767 :param extra_specs: the extra specifications 768 """ 769 action = "Suspend" 770 self.rest.modify_storagegroup_rdf( 771 array, storagegroup_name, rdf_group_num, action, extra_specs) 772 773 def failover_group(self, array, storagegroup_name, 774 rdf_group_num, extra_specs, failover=True): 775 """Failover or failback replication on a storage group. 776 777 :param array: the array serial number 778 :param storagegroup_name: the storagegroup name 779 :param rdf_group_num: the rdf group number 780 :param extra_specs: the extra specifications 781 :param failover: flag to indicate failover/ failback 782 """ 783 action = "Failover" if failover else "Failback" 784 self.rest.modify_storagegroup_rdf( 785 array, storagegroup_name, rdf_group_num, action, extra_specs) 786 787 def delete_group_replication(self, array, storagegroup_name, 788 rdf_group_num, extra_specs): 789 """Split replication for a group and delete the pairs. 790 791 :param array: the array serial number 792 :param storagegroup_name: the storage group name 793 :param rdf_group_num: the rdf group number 794 :param extra_specs: the extra specifications 795 """ 796 group_details = self.rest.get_storage_group_rep( 797 array, storagegroup_name) 798 if (group_details and group_details.get('rdf') 799 and group_details['rdf'] is True): 800 action = "Split" 801 LOG.debug("Splitting remote replication for group %(sg)s", 802 {'sg': storagegroup_name}) 803 self.rest.modify_storagegroup_rdf( 804 array, storagegroup_name, rdf_group_num, action, extra_specs) 805 LOG.debug("Deleting remote replication for group %(sg)s", 806 {'sg': storagegroup_name}) 807 self.rest.delete_storagegroup_rdf( 808 array, storagegroup_name, rdf_group_num) 809 810 def revert_volume_snapshot(self, array, source_device_id, 811 snap_name, extra_specs): 812 """Revert a volume snapshot 813 814 :param array: the array serial number 815 :param source_device_id: device id of the source 816 :param snap_name: snapvx snapshot name 817 :param extra_specs: the extra specifications 818 """ 819 start_time = time.time() 820 self.rest.modify_volume_snap( 821 array, source_device_id, "", snap_name, extra_specs, restore=True) 822 LOG.debug("Restore volume snapshot took: %(delta)s H:MM:SS.", 823 {'delta': self.utils.get_time_delta(start_time, 824 time.time())}) 825