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