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
19import six
20
21from cinder import coordination
22from cinder import exception
23from cinder.i18n import _
24from cinder.volume.drivers.dell_emc.vmax import provision
25from cinder.volume.drivers.dell_emc.vmax import utils
26
27LOG = logging.getLogger(__name__)
28
29
30class VMAXMasking(object):
31    """Masking class for Dell EMC VMAX.
32
33    Masking code to dynamically create a masking view.
34    It supports VMAX arrays.
35    """
36    def __init__(self, prtcl, rest):
37        self.protocol = prtcl
38        self.utils = utils.VMAXUtils()
39        self.rest = rest
40        self.provision = provision.VMAXProvision(self.rest)
41
42    def setup_masking_view(
43            self, serial_number, volume, masking_view_dict, extra_specs):
44
45        @coordination.synchronized("emc-mv-{maskingview_name}")
46        def do_get_or_create_masking_view_and_map_lun(maskingview_name):
47            return self.get_or_create_masking_view_and_map_lun(
48                serial_number, volume, maskingview_name, masking_view_dict,
49                extra_specs)
50        return do_get_or_create_masking_view_and_map_lun(
51            masking_view_dict[utils.MV_NAME])
52
53    def get_or_create_masking_view_and_map_lun(
54            self, serial_number, volume, maskingview_name, masking_view_dict,
55            extra_specs):
56        """Get or Create a masking view and add a volume to the storage group.
57
58        Given a masking view dict either get or create a masking view and add
59        the volume to the associated storage group.
60        :param serial_number: the array serial number
61        :param volume: the volume object
62        :param maskingview_name: the masking view name
63        :param masking_view_dict: the masking view dict
64        :param extra_specs: the extra specifications
65        :returns: rollback_dict
66        :raises: VolumeBackendAPIException
67        """
68        storagegroup_name = masking_view_dict[utils.SG_NAME]
69        volume_name = masking_view_dict[utils.VOL_NAME]
70        masking_view_dict[utils.EXTRA_SPECS] = extra_specs
71        device_id = masking_view_dict[utils.DEVICE_ID]
72        rep_mode = extra_specs.get(utils.REP_MODE, None)
73        default_sg_name = self.utils.get_default_storage_group_name(
74            masking_view_dict[utils.SRP],
75            masking_view_dict[utils.SLO],
76            masking_view_dict[utils.WORKLOAD],
77            masking_view_dict[utils.DISABLECOMPRESSION],
78            masking_view_dict[utils.IS_RE], rep_mode)
79        rollback_dict = masking_view_dict
80
81        try:
82            error_message = self._get_or_create_masking_view(
83                serial_number, masking_view_dict,
84                default_sg_name, extra_specs)
85            LOG.debug(
86                "The masking view in the attach operation is "
87                "%(masking_name)s. The storage group "
88                "in the masking view is %(storage_name)s.",
89                {'masking_name': maskingview_name,
90                 'storage_name': storagegroup_name})
91            rollback_dict['portgroup_name'] = (
92                self.rest.get_element_from_masking_view(
93                    serial_number, maskingview_name, portgroup=True))
94
95        except Exception as e:
96            LOG.exception(
97                "Masking View creation or retrieval was not successful "
98                "for masking view %(maskingview_name)s. "
99                "Attempting rollback.",
100                {'maskingview_name': masking_view_dict[utils.MV_NAME]})
101            error_message = six.text_type(e)
102
103        if 'source_nf_sg' in masking_view_dict:
104            default_sg_name = masking_view_dict['source_nf_sg']
105        rollback_dict['default_sg_name'] = default_sg_name
106
107        if error_message:
108            # Rollback code if we cannot complete any of the steps above
109            # successfully then we must roll back by adding the volume back to
110            # the default storage group for that slo/workload combination.
111
112            if rollback_dict['slo'] is not None:
113                self.check_if_rollback_action_for_masking_required(
114                    serial_number, volume, device_id, masking_view_dict)
115
116            else:
117                self._check_adding_volume_to_storage_group(
118                    serial_number, device_id, rollback_dict['default_sg_name'],
119                    masking_view_dict[utils.VOL_NAME],
120                    masking_view_dict[utils.EXTRA_SPECS])
121
122            exception_message = (_(
123                "Failed to get, create or add volume %(volumeName)s "
124                "to masking view %(maskingview_name)s. "
125                "The error message received was %(errorMessage)s.")
126                % {'maskingview_name': maskingview_name,
127                   'volumeName': volume_name,
128                   'errorMessage': error_message})
129            LOG.error(exception_message)
130            raise exception.VolumeBackendAPIException(data=exception_message)
131
132        return rollback_dict
133
134    def _move_vol_from_default_sg(
135            self, serial_number, device_id, volume_name,
136            default_sg_name, dest_storagegroup, extra_specs):
137        """Get the default storage group and move the volume.
138
139        :param serial_number: the array serial number
140        :param device_id: the device id
141        :param volume_name: the volume name
142        :param default_sg_name: the name of the default sg
143        :param dest_storagegroup: the destination storage group
144        :param extra_specs: the extra specifications
145        :returns: msg
146        """
147        msg = None
148        check_vol = self.rest.is_volume_in_storagegroup(
149            serial_number, device_id, default_sg_name)
150        if check_vol:
151            @coordination.synchronized("emc-sg-{sg_name}")
152            def do_move_vol_from_def_sg(sg_name):
153                num_vol_in_sg = self.rest.get_num_vols_in_sg(
154                    serial_number, default_sg_name)
155                LOG.debug("There are %(num_vol)d volumes in the "
156                          "storage group %(sg_name)s.",
157                          {'num_vol': num_vol_in_sg,
158                           'sg_name': default_sg_name})
159                self.rest.move_volume_between_storage_groups(
160                    serial_number, device_id, default_sg_name,
161                    dest_storagegroup, extra_specs)
162                if num_vol_in_sg == 1:
163                    # Last volume in the storage group - delete sg.
164                    self.rest.delete_storage_group(
165                        serial_number, default_sg_name)
166
167            try:
168                do_move_vol_from_def_sg(default_sg_name)
169            except Exception as e:
170                msg = ("Exception while moving volume from the default "
171                       "storage group to %(sg)s. Exception received was "
172                       "%(e)s")
173                LOG.error(msg, {'sg': dest_storagegroup, 'e': e})
174        else:
175            LOG.warning(
176                "Volume: %(volume_name)s does not belong "
177                "to default storage group %(default_sg_name)s.",
178                {'volume_name': volume_name,
179                 'default_sg_name': default_sg_name})
180            msg = self._check_adding_volume_to_storage_group(
181                serial_number, device_id, dest_storagegroup,
182                volume_name, extra_specs)
183
184        return msg
185
186    def _get_or_create_masking_view(self, serial_number, masking_view_dict,
187                                    default_sg_name, extra_specs):
188        """Retrieve an existing masking view or create a new one.
189
190        :param serial_number: the array serial number
191        :param masking_view_dict: the masking view dict
192        :param default_sg_name: the name of the default sg
193        :param extra_specs: the extra specifications
194        :returns: error message
195        """
196        maskingview_name = masking_view_dict[utils.MV_NAME]
197
198        masking_view_details = self.rest.get_masking_view(
199            serial_number, masking_view_name=maskingview_name)
200        if not masking_view_details:
201            error_message = self._create_new_masking_view(
202                serial_number, masking_view_dict, maskingview_name,
203                default_sg_name, extra_specs)
204
205        else:
206            storagegroup_name, error_message = (
207                self._validate_existing_masking_view(
208                    serial_number, masking_view_dict, maskingview_name,
209                    default_sg_name, extra_specs))
210
211        return error_message
212
213    def _create_new_masking_view(
214            self, serial_number, masking_view_dict,
215            maskingview_name, default_sg_name, extra_specs):
216        """Create a new masking view.
217
218        :param serial_number: the array serial number
219        :param masking_view_dict: the masking view dict
220        :param maskingview_name: the masking view name
221        :param default_sg_name: the name of the default sg
222        :param extra_specs: the extra specifications
223        :returns: error_message
224        """
225        init_group_name = masking_view_dict[utils.IG_NAME]
226        parent_sg_name = masking_view_dict[utils.PARENT_SG_NAME]
227        storagegroup_name = masking_view_dict[utils.SG_NAME]
228        connector = masking_view_dict[utils.CONNECTOR]
229        port_group_name = masking_view_dict[utils.PORTGROUPNAME]
230        LOG.info("Port Group in masking view operation: %(port_group_name)s.",
231                 {'port_group_name': port_group_name})
232
233        # get or create parent sg
234        error_message = self._get_or_create_storage_group(
235            serial_number, masking_view_dict, parent_sg_name, extra_specs,
236            parent=True)
237        if error_message:
238            return error_message
239
240        # get or create child sg
241        error_message = self._get_or_create_storage_group(
242            serial_number, masking_view_dict, storagegroup_name, extra_specs)
243        if error_message:
244            return error_message
245
246        __, error_message = self._check_port_group(
247            serial_number, port_group_name)
248        if error_message:
249            return error_message
250
251        init_group_name, error_message = (self._get_or_create_initiator_group(
252            serial_number, init_group_name, connector, extra_specs))
253        if error_message:
254            return error_message
255
256        # Only after the components of the MV have been validated,
257        # move the volume from the default storage group to the
258        # masking view storage group. This is necessary before
259        # creating a new masking view.
260        error_message = self._move_vol_from_default_sg(
261            serial_number, masking_view_dict[utils.DEVICE_ID],
262            masking_view_dict[utils.VOL_NAME], default_sg_name,
263            storagegroup_name, extra_specs)
264        if error_message:
265            return error_message
266
267        error_message = self._check_add_child_sg_to_parent_sg(
268            serial_number, storagegroup_name, parent_sg_name,
269            masking_view_dict[utils.EXTRA_SPECS])
270        if error_message:
271            return error_message
272
273        error_message = (self.create_masking_view(
274            serial_number, maskingview_name, parent_sg_name,
275            port_group_name, init_group_name, extra_specs))
276
277        return error_message
278
279    def _validate_existing_masking_view(
280            self, serial_number, masking_view_dict,
281            maskingview_name, default_sg_name, extra_specs):
282        """Validate the components of an existing masking view.
283
284        :param serial_number: the array serial number
285        :param masking_view_dict: the masking view dict
286        :param maskingview_name: the amsking view name
287        :param default_sg_name: the default sg name
288        :param extra_specs: the extra specifications
289        :returns: storage_group_name -- string, msg -- string
290        """
291        storage_group_name, msg = self._check_existing_storage_group(
292            serial_number, maskingview_name, default_sg_name,
293            masking_view_dict)
294        if not msg:
295            portgroup_name = self.rest.get_element_from_masking_view(
296                serial_number, maskingview_name, portgroup=True)
297            __, msg = self._check_port_group(
298                serial_number, portgroup_name)
299            if not msg:
300                initiator_group, msg = self._check_existing_initiator_group(
301                    serial_number, maskingview_name, masking_view_dict,
302                    storage_group_name, portgroup_name, extra_specs)
303
304        return storage_group_name, msg
305
306    def _check_add_child_sg_to_parent_sg(
307            self, serial_number, child_sg_name, parent_sg_name, extra_specs):
308        """Check adding a child storage group to a parent storage group.
309
310        :param serial_number: the array serial number
311        :param child_sg_name: the name of the child storage group
312        :param parent_sg_name: the name of the aprent storage group
313        :param extra_specs: the extra specifications
314        :returns: error_message or None
315        """
316        msg = None
317        if self.rest.is_child_sg_in_parent_sg(
318                serial_number, child_sg_name, parent_sg_name):
319            LOG.info("Child sg: %(child_sg)s is already part "
320                     "of parent storage group %(parent_sg)s.",
321                     {'child_sg': child_sg_name,
322                      'parent_sg': parent_sg_name})
323        else:
324            try:
325                self.add_child_sg_to_parent_sg(
326                    serial_number, child_sg_name, parent_sg_name, extra_specs)
327            except Exception as e:
328                msg = ("Exception adding child sg %(child_sg)s to "
329                       "%(parent_sg)s. Exception received was %(e)s"
330                       % {'child_sg': child_sg_name,
331                          'parent_sg': parent_sg_name,
332                          'e': six.text_type(e)})
333                LOG.error(msg)
334        return msg
335
336    def add_child_sg_to_parent_sg(
337            self, serial_number, child_sg_name, parent_sg_name, extra_specs,
338            default_version=True
339    ):
340        """Add a child storage group to a parent storage group.
341
342        :param default_version: the default uv4 version
343        :param serial_number: the array serial number
344        :param child_sg_name: the name of the child storage group
345        :param parent_sg_name: the name of the aprent storage group
346        :param extra_specs: the extra specifications
347        """
348        start_time = time.time()
349
350        @coordination.synchronized("emc-sg-{child_sg}")
351        @coordination.synchronized("emc-sg-{parent_sg}")
352        def do_add_sg_to_sg(child_sg, parent_sg):
353            # Check if another process has added the child to the
354            # parent sg while this process was waiting for the lock
355            if self.rest.is_child_sg_in_parent_sg(
356                    serial_number, child_sg_name, parent_sg_name):
357                pass
358            else:
359                if default_version:
360                    self.rest.add_child_sg_to_parent_sg(
361                        serial_number, child_sg, parent_sg, extra_specs)
362                else:
363                    self.rest.add_empty_child_sg_to_parent_sg(
364                        serial_number, child_sg, parent_sg, extra_specs)
365
366        do_add_sg_to_sg(child_sg_name, parent_sg_name)
367
368        LOG.debug("Add child to storagegroup took: %(delta)s H:MM:SS.",
369                  {'delta': self.utils.get_time_delta(start_time,
370                                                      time.time())})
371        LOG.info("Added child sg: %(child_name)s to parent storage "
372                 "group %(parent_name)s.",
373                 {'child_name': child_sg_name, 'parent_name': parent_sg_name})
374
375    def _get_or_create_storage_group(
376            self, serial_number, masking_view_dict, storagegroup_name,
377            extra_specs, parent=False):
378        """Get or create a storage group for a masking view.
379
380        :param serial_number: the array serial number
381        :param masking_view_dict: the masking view dict
382        :param storagegroup_name: the storage group name
383        :param extra_specs: the extra specifications
384        :param parent: flag to indicate if this a parent storage group
385        :returns: msg -- string or None
386        """
387        msg = None
388        srp = extra_specs[utils.SRP]
389        workload = extra_specs[utils.WORKLOAD]
390        if parent:
391            slo = None
392        else:
393            slo = extra_specs[utils.SLO]
394        do_disable_compression = (
395            masking_view_dict[utils.DISABLECOMPRESSION])
396        storagegroup = self.rest.get_storage_group(
397            serial_number, storagegroup_name)
398        if storagegroup is None:
399            storagegroup = self.provision.create_storage_group(
400                serial_number, storagegroup_name, srp, slo, workload,
401                extra_specs, do_disable_compression)
402
403        if storagegroup is None:
404            msg = ("Cannot get or create a storage group: "
405                   "%(storagegroup_name)s for volume %(volume_name)s."
406                   % {'storagegroup_name': storagegroup_name,
407                      'volume_name': masking_view_dict[utils.VOL_NAME]})
408            LOG.error(msg)
409
410        # If qos exists, update storage group to reflect qos parameters
411        if 'qos' in extra_specs:
412            self.rest.update_storagegroup_qos(
413                serial_number, storagegroup_name, extra_specs)
414
415        return msg
416
417    def _check_existing_storage_group(
418            self, serial_number, maskingview_name,
419            default_sg_name, masking_view_dict):
420        """Check if the masking view has the child storage group.
421
422        Get the parent storage group associated with a masking view and check
423        if the required child storage group is already a member. If not, get
424        or create the child storage group.
425        :param serial_number: the array serial number
426        :param maskingview_name: the masking view name
427        :param default_sg_name: the default sg name
428        :param masking_view_dict: the masking view dict
429        :returns: storage group name, msg
430        """
431        msg = None
432        child_sg_name = masking_view_dict[utils.SG_NAME]
433
434        sg_from_mv = self.rest.get_element_from_masking_view(
435            serial_number, maskingview_name, storagegroup=True)
436
437        storagegroup = self.rest.get_storage_group(serial_number, sg_from_mv)
438
439        if not storagegroup:
440            msg = ("Cannot get storage group: %(sg_from_mv)s "
441                   "from masking view %(masking_view)s."
442                   % {'sg_from_mv': sg_from_mv,
443                      'masking_view': maskingview_name})
444            LOG.error(msg)
445        else:
446            check_child = self.rest.is_child_sg_in_parent_sg(
447                serial_number, child_sg_name, sg_from_mv)
448            child_sg = self.rest.get_storage_group(
449                serial_number, child_sg_name)
450            # Ensure the child sg can be retrieved
451            if check_child and not child_sg:
452                msg = ("Cannot get child storage group: %(sg_name)s "
453                       "but it is listed as child of %(parent_sg)s"
454                       % {'sg_name': child_sg_name, 'parent_sg': sg_from_mv})
455                LOG.error(msg)
456            elif check_child and child_sg:
457                LOG.info("Retrieved child sg %(sg_name)s from %(mv_name)s",
458                         {'sg_name': child_sg_name,
459                          'mv_name': maskingview_name})
460            else:
461                msg = self._get_or_create_storage_group(
462                    serial_number, masking_view_dict, child_sg_name,
463                    masking_view_dict[utils.EXTRA_SPECS])
464            if not msg:
465                msg = self._move_vol_from_default_sg(
466                    serial_number, masking_view_dict[utils.DEVICE_ID],
467                    masking_view_dict[utils.VOL_NAME], default_sg_name,
468                    child_sg_name, masking_view_dict[utils.EXTRA_SPECS])
469            if not msg and not check_child:
470                msg = self._check_add_child_sg_to_parent_sg(
471                    serial_number, child_sg_name, sg_from_mv,
472                    masking_view_dict[utils.EXTRA_SPECS])
473
474        return child_sg_name, msg
475
476    @coordination.synchronized("emc-sg-{source_storagegroup_name}")
477    @coordination.synchronized("emc-sg-{target_storagegroup_name}")
478    def move_volume_between_storage_groups(
479            self, serial_number, device_id, source_storagegroup_name,
480            target_storagegroup_name, extra_specs):
481        """Move a volume between storage groups.
482
483        :param serial_number: the array serial number
484        :param device_id: the device id
485        :param source_storagegroup_name: the source sg
486        :param target_storagegroup_name: the target sg
487        :param extra_specs: the extra specifications
488        """
489        self.rest.move_volume_between_storage_groups(
490            serial_number, device_id, source_storagegroup_name,
491            target_storagegroup_name, extra_specs)
492
493    def _check_port_group(self, serial_number, portgroup_name):
494        """Check that you can get a port group.
495
496        :param serial_number: the array serial number
497        :param portgroup_name: the port group name
498        :returns: string -- msg, the error message
499        """
500        msg = None
501        portgroup = self.rest.get_portgroup(serial_number, portgroup_name)
502        if portgroup is None:
503            msg = ("Cannot get port group: %(portgroup)s from the array "
504                   "%(array)s. Portgroups must be pre-configured - please "
505                   "check the array."
506                   % {'portgroup': portgroup_name, 'array': serial_number})
507            LOG.error(msg)
508        return portgroup_name, msg
509
510    def _get_or_create_initiator_group(
511            self, serial_number, init_group_name, connector, extra_specs):
512        """Retrieve or create an initiator group.
513
514        :param serial_number: the array serial number
515        :param init_group_name: the name of the initiator group
516        :param connector: the connector object
517        :param extra_specs: the extra specifications
518        :returns: name of the initiator group -- string, msg
519        """
520        msg = None
521        initiator_names = self.find_initiator_names(connector)
522        LOG.debug("The initiator name(s) are: %(initiatorNames)s.",
523                  {'initiatorNames': initiator_names})
524
525        found_init_group = self._find_initiator_group(
526            serial_number, initiator_names)
527
528        # If you cannot find an initiator group that matches the connector
529        # info, create a new initiator group.
530        if found_init_group is None:
531            found_init_group = self._create_initiator_group(
532                serial_number, init_group_name, initiator_names, extra_specs)
533            LOG.info("Created new initiator group name: %(init_group_name)s.",
534                     {'init_group_name': init_group_name})
535        else:
536            LOG.info("Using existing initiator group name: "
537                     "%(init_group_name)s.",
538                     {'init_group_name': found_init_group})
539
540        if found_init_group is None:
541            msg = ("Cannot get or create initiator group: "
542                   "%(init_group_name)s. "
543                   % {'init_group_name': init_group_name})
544            LOG.error(msg)
545
546        return found_init_group, msg
547
548    def _check_existing_initiator_group(
549            self, serial_number, maskingview_name, masking_view_dict,
550            storagegroup_name, portgroup_name, extra_specs):
551        """Checks an existing initiator group in the masking view.
552
553        Check if the initiators in the initiator group match those in the
554        system.
555        :param serial_number: the array serial number
556        :param maskingview_name: name of the masking view
557        :param masking_view_dict: masking view dict
558        :param storagegroup_name: the storage group name
559        :param portgroup_name: the port group name
560        :param extra_specs: the extra specifications
561        :returns: ig_from_mv, msg
562        """
563        msg = None
564        ig_from_mv = self.rest.get_element_from_masking_view(
565            serial_number, maskingview_name, host=True)
566        check_ig = masking_view_dict[utils.INITIATOR_CHECK]
567
568        if check_ig:
569            # First verify that the initiator group matches the initiators.
570            check, found_ig = self._verify_initiator_group_from_masking_view(
571                serial_number, maskingview_name, masking_view_dict, ig_from_mv,
572                storagegroup_name, portgroup_name, extra_specs)
573            if not check:
574                msg = ("Unable to verify initiator group: %(ig_name)s "
575                       "in masking view %(maskingview_name)s."
576                       % {'ig_name': ig_from_mv,
577                          'maskingview_name': maskingview_name})
578                LOG.error(msg)
579        return ig_from_mv, msg
580
581    def _check_adding_volume_to_storage_group(
582            self, serial_number, device_id, storagegroup_name,
583            volume_name, extra_specs):
584        """Check if a volume is part of an sg and add it if not.
585
586        :param serial_number: the array serial number
587        :param device_id: the device id
588        :param storagegroup_name: the storage group name
589        :param volume_name: volume name
590        :param extra_specs: extra specifications
591        :returns: msg
592        """
593        msg = None
594        if self.rest.is_volume_in_storagegroup(
595                serial_number, device_id, storagegroup_name):
596            LOG.info("Volume: %(volume_name)s is already part "
597                     "of storage group %(sg_name)s.",
598                     {'volume_name': volume_name,
599                      'sg_name': storagegroup_name})
600        else:
601            try:
602                self.add_volume_to_storage_group(
603                    serial_number, device_id, storagegroup_name,
604                    volume_name, extra_specs)
605            except Exception as e:
606                msg = ("Exception adding volume %(vol)s to %(sg)s. "
607                       "Exception received was %(e)s."
608                       % {'vol': volume_name, 'sg': storagegroup_name,
609                          'e': six.text_type(e)})
610                LOG.error(msg)
611        return msg
612
613    def add_volume_to_storage_group(
614            self, serial_number, device_id, storagegroup_name,
615            volume_name, extra_specs):
616        """Add a volume to a storage group.
617
618        :param serial_number: array serial number
619        :param device_id: volume device id
620        :param storagegroup_name: storage group name
621        :param volume_name: volume name
622        :param extra_specs: extra specifications
623        """
624        start_time = time.time()
625
626        @coordination.synchronized("emc-sg-{sg_name}")
627        def do_add_volume_to_sg(sg_name):
628            # Check if another process has added the volume to the
629            # sg while this process was waiting for the lock
630            if self.rest.is_volume_in_storagegroup(
631                    serial_number, device_id, storagegroup_name):
632                LOG.info("Volume: %(volume_name)s is already part "
633                         "of storage group %(sg_name)s.",
634                         {'volume_name': volume_name,
635                          'sg_name': storagegroup_name})
636            else:
637                self.rest.add_vol_to_sg(serial_number, sg_name,
638                                        device_id, extra_specs)
639        do_add_volume_to_sg(storagegroup_name)
640
641        LOG.debug("Add volume to storagegroup took: %(delta)s H:MM:SS.",
642                  {'delta': self.utils.get_time_delta(start_time,
643                                                      time.time())})
644        LOG.info("Added volume: %(vol_name)s to storage group %(sg_name)s.",
645                 {'vol_name': volume_name, 'sg_name': storagegroup_name})
646
647    def add_volumes_to_storage_group(
648            self, serial_number, list_device_id, storagegroup_name,
649            extra_specs):
650        """Add a volume to a storage group.
651
652        :param serial_number: array serial number
653        :param list_device_id: list of volume device id
654        :param storagegroup_name: storage group name
655        :param extra_specs: extra specifications
656        """
657        if not list_device_id:
658            LOG.info("add_volumes_to_storage_group: No volumes to add")
659            return
660        start_time = time.time()
661        temp_device_id_list = list_device_id
662
663        @coordination.synchronized("emc-sg-{sg_name}")
664        def do_add_volume_to_sg(sg_name):
665            # Check if another process has added any volume to the
666            # sg while this process was waiting for the lock
667            volume_list = self.rest.get_volumes_in_storage_group(
668                serial_number, storagegroup_name)
669            for volume in volume_list:
670                if volume in temp_device_id_list:
671                    LOG.info("Volume: %(volume_name)s is already part "
672                             "of storage group %(sg_name)s.",
673                             {'volume_name': volume,
674                              'sg_name': storagegroup_name})
675                    # Remove this device id from the list
676                    temp_device_id_list.remove(volume)
677            self.rest.add_vol_to_sg(serial_number, storagegroup_name,
678                                    temp_device_id_list, extra_specs)
679        do_add_volume_to_sg(storagegroup_name)
680
681        LOG.debug("Add volumes to storagegroup took: %(delta)s H:MM:SS.",
682                  {'delta': self.utils.get_time_delta(start_time,
683                                                      time.time())})
684        LOG.info("Added volumes to storage group %(sg_name)s.",
685                 {'sg_name': storagegroup_name})
686
687    def remove_vol_from_storage_group(
688            self, serial_number, device_id, storagegroup_name,
689            volume_name, extra_specs):
690        """Remove a volume from a storage group.
691
692        :param serial_number: the array serial number
693        :param device_id: the volume device id
694        :param storagegroup_name: the name of the storage group
695        :param volume_name: the volume name
696        :param extra_specs: the extra specifications
697        :raises: VolumeBackendAPIException
698        """
699        start_time = time.time()
700
701        self.rest.remove_vol_from_sg(
702            serial_number, storagegroup_name, device_id, extra_specs)
703
704        LOG.debug("Remove volume from storagegroup took: %(delta)s H:MM:SS.",
705                  {'delta': self.utils.get_time_delta(start_time,
706                                                      time.time())})
707
708        check_vol = (self.rest.is_volume_in_storagegroup(
709            serial_number, device_id, storagegroup_name))
710        if check_vol:
711            exception_message = (_(
712                "Failed to remove volume %(vol)s from SG: %(sg_name)s.")
713                % {'vol': volume_name, 'sg_name': storagegroup_name})
714            LOG.error(exception_message)
715            raise exception.VolumeBackendAPIException(
716                data=exception_message)
717
718    def remove_volumes_from_storage_group(
719            self, serial_number, list_of_device_ids,
720            storagegroup_name, extra_specs):
721        """Remove multiple volumes from a storage group.
722
723        :param serial_number: the array serial number
724        :param list_of_device_ids: list of device ids
725        :param storagegroup_name: the name of the storage group
726        :param extra_specs: the extra specifications
727        :raises: VolumeBackendAPIException
728        """
729        start_time = time.time()
730
731        @coordination.synchronized("emc-sg-{sg_name}")
732        def do_remove_volumes_from_storage_group(sg_name):
733            self.rest.remove_vol_from_sg(
734                serial_number, storagegroup_name,
735                list_of_device_ids, extra_specs)
736
737            LOG.debug("Remove volumes from storagegroup "
738                      "took: %(delta)s H:MM:SS.",
739                      {'delta': self.utils.get_time_delta(start_time,
740                                                          time.time())})
741            volume_list = self.rest.get_volumes_in_storage_group(
742                serial_number, storagegroup_name)
743
744            for device_id in list_of_device_ids:
745                if device_id in volume_list:
746                    exception_message = (_(
747                        "Failed to remove device "
748                        "with id %(dev_id)s from SG: %(sg_name)s.")
749                        % {'dev_id': device_id, 'sg_name': storagegroup_name})
750                    LOG.error(exception_message)
751                    raise exception.VolumeBackendAPIException(
752                        data=exception_message)
753        return do_remove_volumes_from_storage_group(storagegroup_name)
754
755    def find_initiator_names(self, connector):
756        """Check the connector object for initiators(ISCSI) or wwpns(FC).
757
758        :param connector: the connector object
759        :returns: list -- list of found initiator names
760        :raises: VolumeBackendAPIException
761        """
762        foundinitiatornames = []
763        name = 'initiator name'
764        if self.protocol.lower() == utils.ISCSI and connector['initiator']:
765            foundinitiatornames.append(connector['initiator'])
766        elif self.protocol.lower() == utils.FC:
767            if 'wwpns' in connector and connector['wwpns']:
768                for wwn in connector['wwpns']:
769                    foundinitiatornames.append(wwn)
770                name = 'world wide port names'
771            else:
772                msg = (_("FC is the protocol but wwpns are "
773                         "not supplied by OpenStack."))
774                LOG.error(msg)
775                raise exception.VolumeBackendAPIException(data=msg)
776
777        if not foundinitiatornames:
778            msg = (_("Error finding %(name)s.") % {'name': name})
779            LOG.error(msg)
780            raise exception.VolumeBackendAPIException(data=msg)
781
782        LOG.debug("Found %(name)s: %(initiator)s.",
783                  {'name': name,
784                   'initiator': foundinitiatornames})
785
786        return foundinitiatornames
787
788    def _find_initiator_group(self, serial_number, initiator_names):
789        """Check to see if an initiator group already exists.
790
791        NOTE:  An initiator/wwn can only belong to one initiator group.
792        If we were to attempt to create one with an initiator/wwn that is
793        already belonging to another initiator group, it would fail.
794        :param serial_number: the array serial number
795        :param initiator_names: the list of initiator names
796        :returns: initiator group name -- string or None
797        """
798        ig_name = None
799        for initiator in initiator_names:
800            params = {'initiator_hba': initiator.lower()}
801            found_init = self.rest.get_initiator_list(serial_number, params)
802            if found_init:
803                ig_name = self.rest.get_initiator_group_from_initiator(
804                    serial_number, found_init[0])
805                break
806        return ig_name
807
808    def create_masking_view(
809            self, serial_number, maskingview_name, storagegroup_name,
810            port_group_name, init_group_name, extra_specs):
811        """Create a new masking view.
812
813        :param serial_number: the array serial number
814        :param maskingview_name: the masking view name
815        :param storagegroup_name: the storage group name
816        :param port_group_name: the port group
817        :param init_group_name: the initiator group
818        :param extra_specs: extra specifications
819        :returns: error_message -- string or None
820        """
821        error_message = None
822        try:
823            self.rest.create_masking_view(
824                serial_number, maskingview_name, storagegroup_name,
825                port_group_name, init_group_name, extra_specs)
826
827        except Exception as e:
828            error_message = ("Error creating new masking view. Exception "
829                             "received: %(e)s" % {'e': six.text_type(e)})
830        return error_message
831
832    def check_if_rollback_action_for_masking_required(
833            self, serial_number, volume, device_id, rollback_dict):
834        """Rollback action for volumes with an associated service level.
835
836        We need to be able to return the volume to the default storage group
837        if anything has gone wrong. The volume can also potentially belong to
838        a storage group that is not the default depending on where
839        the exception occurred. We also may need to clean up any unused
840        initiator groups.
841        :param serial_number: the array serial number
842        :param volume: the volume object
843        :param device_id: the device id
844        :param rollback_dict: the rollback dict
845        :returns: error message -- string, or None
846        :raises: VolumeBackendAPIException
847        """
848        message = None
849        # Check if ig has been created. If so, check for other
850        # masking views associated with the ig. If none, delete the ig.
851        self._check_ig_rollback(
852            serial_number, rollback_dict['init_group_name'],
853            rollback_dict['connector'])
854        try:
855            found_sg_name_list = (
856                self.rest.get_storage_groups_from_volume(
857                    serial_number, rollback_dict['device_id']))
858            # Volume is not associated with any storage group so add
859            # it back to the default.
860            if not found_sg_name_list:
861                error_message = self._check_adding_volume_to_storage_group(
862                    serial_number, device_id,
863                    rollback_dict['default_sg_name'],
864                    rollback_dict[utils.VOL_NAME],
865                    rollback_dict[utils.EXTRA_SPECS])
866                if error_message:
867                    LOG.error(error_message)
868                message = (_("Rollback"))
869            elif 'isLiveMigration' in rollback_dict and (
870                    rollback_dict['isLiveMigration'] is True):
871                # Live migration case.
872                # Remove from nonfast storage group to fast sg
873                self.failed_live_migration(rollback_dict, found_sg_name_list,
874                                           rollback_dict[utils.EXTRA_SPECS])
875            else:
876                LOG.info("Volume %(vol_id)s is in %(list_size)d storage"
877                         "groups. The storage groups are %(found_sg_list)s.",
878                         {'vol_id': volume.id,
879                          'list_size': len(found_sg_name_list),
880                          'found_sg_list': found_sg_name_list})
881
882                # Check the name, see if it is the default storage group
883                # or another.
884                sg_found = False
885                for found_sg_name in found_sg_name_list:
886                    if found_sg_name == rollback_dict['default_sg_name']:
887                        sg_found = True
888                if not sg_found:
889                    # Remove it from its current storage group and return it
890                    # to its default storage group if slo is defined.
891                    self.remove_and_reset_members(
892                        serial_number, volume, device_id,
893                        rollback_dict['volume_name'],
894                        rollback_dict['extra_specs'], True,
895                        rollback_dict['connector'])
896                    message = (_("Rollback - Volume in another storage "
897                                 "group besides default storage group."))
898        except Exception as e:
899            error_message = (_(
900                "Rollback for Volume: %(volume_name)s has failed. "
901                "Please contact your system administrator to manually return "
902                "your volume to the default storage group for its slo. "
903                "Exception received: %(e)s")
904                % {'volume_name': rollback_dict['volume_name'],
905                   'e': six.text_type(e)})
906            LOG.exception(error_message)
907            raise exception.VolumeBackendAPIException(data=error_message)
908        return message
909
910    def _verify_initiator_group_from_masking_view(
911            self, serial_number, maskingview_name, maskingview_dict,
912            ig_from_mv, storagegroup_name, portgroup_name, extra_specs):
913        """Check that the initiator group contains the correct initiators.
914
915        If using an existing masking view check that the initiator group
916        contains the correct initiators.  If it does not contain the correct
917        initiators then we delete the initiator group from the masking view,
918        re-create it with the correct initiators and add it to the masking view
919        NOTE:  VMAX does not support ModifyMaskingView so we must first
920               delete the masking view and recreate it.
921        :param serial_number: the array serial number
922        :param maskingview_name: name of the masking view
923        :param maskingview_dict: the masking view dict
924        :param ig_from_mv: the initiator group name
925        :param storagegroup_name: the storage group
926        :param portgroup_name: the port group
927        :param extra_specs: extra specifications
928        :returns: bool, found_ig_from_connector
929        """
930        connector = maskingview_dict['connector']
931        initiator_names = self.find_initiator_names(connector)
932        found_ig_from_connector = self._find_initiator_group(
933            serial_number, initiator_names)
934
935        if found_ig_from_connector != ig_from_mv:
936            check_ig = self.rest.get_initiator_group(
937                serial_number, initiator_group=ig_from_mv)
938            if check_ig:
939                if found_ig_from_connector is None:
940                    # If the name of the current initiator group from the
941                    # masking view matches the igGroupName supplied for the
942                    # new group, the existing ig needs to be deleted before
943                    # the new one with the correct initiators can be created.
944                    if maskingview_dict['init_group_name'] == ig_from_mv:
945                        # Masking view needs to be deleted before IG
946                        # can be deleted.
947                        self.rest.delete_masking_view(
948                            serial_number, maskingview_name)
949                        self.rest.delete_initiator_group(
950                            serial_number, ig_from_mv)
951                        found_ig_from_connector = (
952                            self._create_initiator_group(
953                                serial_number, ig_from_mv, initiator_names,
954                                extra_specs))
955                if (found_ig_from_connector is not None and
956                        storagegroup_name is not None and
957                        portgroup_name is not None):
958                    # Existing masking view (if still on the array) needs
959                    # to be deleted before a new one can be created.
960                    try:
961                        self.rest.delete_masking_view(
962                            serial_number, maskingview_name)
963                    except Exception:
964                        pass
965                    error_message = (
966                        self.create_masking_view(
967                            serial_number, maskingview_name, storagegroup_name,
968                            portgroup_name,
969                            maskingview_dict['init_group_name'],
970                            extra_specs))
971                    if not error_message:
972                        LOG.debug(
973                            "The old masking view has been replaced: "
974                            "%(maskingview_name)s.",
975                            {'maskingview_name': maskingview_name})
976                else:
977                    LOG.error(
978                        "One of the components of the original masking view "
979                        "%(maskingview_name)s cannot be retrieved so "
980                        "please contact your system administrator to check "
981                        "that the correct initiator(s) are part of masking.",
982                        {'maskingview_name': maskingview_name})
983                    return False
984        return True, found_ig_from_connector
985
986    def _create_initiator_group(
987            self, serial_number, init_group_name, initiator_names,
988            extra_specs):
989        """Create a new initiator group.
990
991        Given a list of initiators, create a new initiator group.
992        :param serial_number: array serial number
993        :param init_group_name: the name for the initiator group
994        :param initiator_names: initaitor names
995        :param extra_specs: the extra specifications
996        :returns: the initiator group name
997        """
998        self.rest.create_initiator_group(
999            serial_number, init_group_name, initiator_names, extra_specs)
1000        return init_group_name
1001
1002    def _check_ig_rollback(
1003            self, serial_number, init_group_name, connector, force=False):
1004        """Check if rollback action is required on an initiator group.
1005
1006        If anything goes wrong on a masking view creation, we need to check if
1007        the process created a now-stale initiator group before failing, i.e.
1008        an initiator group a) matching the name used in the mv process and
1009        b) not associated with any other masking views.
1010        If a stale ig exists, delete the ig.
1011        :param serial_number: the array serial number
1012        :param init_group_name: the initiator group name
1013        :param connector: the connector object
1014        :param force: force a delete even if no entry in login table
1015        """
1016        initiator_names = self.find_initiator_names(connector)
1017        found_ig_name = self._find_initiator_group(
1018            serial_number, initiator_names)
1019        if found_ig_name:
1020            if found_ig_name == init_group_name:
1021                force = True
1022        if force:
1023            found_ig_name = init_group_name
1024            host = init_group_name.split("-")[1]
1025            LOG.debug("Searching for masking views associated with "
1026                      "%(init_group_name)s",
1027                      {'init_group_name': init_group_name})
1028            self._last_volume_delete_initiator_group(
1029                serial_number, found_ig_name, host)
1030
1031    @coordination.synchronized("emc-vol-{device_id}")
1032    def remove_and_reset_members(
1033            self, serial_number, volume, device_id, volume_name,
1034            extra_specs, reset=True, connector=None, async_grp=None):
1035        """This is called on a delete, unmap device or rollback.
1036
1037        :param serial_number: the array serial number
1038        :param volume: the volume object
1039        :param device_id: the volume device id
1040        :param volume_name: the volume name
1041        :param extra_specs: additional info
1042        :param reset: reset, return to original SG (optional)
1043        :param connector: the connector object (optional)
1044        :param async_grp: the async rep group (optional)
1045        """
1046        self._cleanup_deletion(
1047            serial_number, volume, device_id, volume_name,
1048            extra_specs, connector, reset, async_grp)
1049
1050    def _cleanup_deletion(
1051            self, serial_number, volume, device_id, volume_name,
1052            extra_specs, connector, reset, async_grp):
1053        """Prepare a volume for a delete operation.
1054
1055        :param serial_number: the array serial number
1056        :param volume: the volume object
1057        :param device_id: the volume device id
1058        :param volume_name: the volume name
1059        :param extra_specs: the extra specifications
1060        :param connector: the connector object
1061        :param async_grp: the async rep group
1062        """
1063        move = False
1064        short_host_name = None
1065        storagegroup_names = (self.rest.get_storage_groups_from_volume(
1066            serial_number, device_id))
1067        if storagegroup_names:
1068            if async_grp is not None:
1069                for index, sg in enumerate(storagegroup_names):
1070                    if sg == async_grp:
1071                        storagegroup_names.pop(index)
1072            if len(storagegroup_names) == 1 and reset is True:
1073                move = True
1074            elif connector is not None and reset is True:
1075                short_host_name = self.utils.get_host_short_name(
1076                    connector['host'])
1077                move = True
1078            if short_host_name:
1079                for sg_name in storagegroup_names:
1080                    if short_host_name in sg_name:
1081                        self.remove_volume_from_sg(
1082                            serial_number, device_id, volume_name, sg_name,
1083                            extra_specs, connector, move)
1084                        break
1085            else:
1086                for sg_name in storagegroup_names:
1087                    self.remove_volume_from_sg(
1088                        serial_number, device_id, volume_name, sg_name,
1089                        extra_specs, connector, move)
1090        if reset is True and move is False:
1091            self.add_volume_to_default_storage_group(
1092                serial_number, device_id, volume_name,
1093                extra_specs, volume=volume)
1094
1095    def remove_volume_from_sg(
1096            self, serial_number, device_id, vol_name, storagegroup_name,
1097            extra_specs, connector=None, move=False):
1098        """Remove a volume from a storage group.
1099
1100        :param serial_number: the array serial number
1101        :param device_id: the volume device id
1102        :param vol_name: the volume name
1103        :param storagegroup_name: the storage group name
1104        :param extra_specs: the extra specifications
1105        :param connector: the connector object
1106        :param move: flag to indicate if move should be used instead of remove
1107        """
1108        masking_list = self.rest.get_masking_views_from_storage_group(
1109            serial_number, storagegroup_name)
1110        if not masking_list:
1111            LOG.debug("No masking views associated with storage group "
1112                      "%(sg_name)s", {'sg_name': storagegroup_name})
1113
1114            @coordination.synchronized("emc-sg-{sg_name}")
1115            def do_remove_volume_from_sg(sg_name):
1116                # Make sure volume hasn't been recently removed from the sg
1117                if self.rest.is_volume_in_storagegroup(
1118                        serial_number, device_id, sg_name):
1119                    num_vol_in_sg = self.rest.get_num_vols_in_sg(
1120                        serial_number, sg_name)
1121                    LOG.debug("There are %(num_vol)d volumes in the "
1122                              "storage group %(sg_name)s.",
1123                              {'num_vol': num_vol_in_sg,
1124                               'sg_name': sg_name})
1125
1126                    if num_vol_in_sg == 1:
1127                        # Last volume in the storage group - delete sg.
1128                        self._last_vol_in_sg(
1129                            serial_number, device_id, vol_name, sg_name,
1130                            extra_specs, move)
1131                    else:
1132                        # Not the last volume so remove it from storage group
1133                        self._multiple_vols_in_sg(
1134                            serial_number, device_id, sg_name, vol_name,
1135                            extra_specs, move)
1136                else:
1137                    LOG.info("Volume with device_id %(dev)s is no longer a "
1138                             "member of %(sg)s.",
1139                             {'dev': device_id, 'sg': sg_name})
1140
1141            return do_remove_volume_from_sg(storagegroup_name)
1142        else:
1143            # Need to lock masking view when we are locking the storage
1144            # group to avoid possible deadlock situations from concurrent
1145            # processes
1146            masking_name = masking_list[0]
1147            parent_sg_name = self.rest.get_element_from_masking_view(
1148                serial_number, masking_name, storagegroup=True)
1149
1150            @coordination.synchronized("emc-mv-{parent_name}")
1151            @coordination.synchronized("emc-mv-{mv_name}")
1152            @coordination.synchronized("emc-sg-{sg_name}")
1153            def do_remove_volume_from_sg(mv_name, sg_name, parent_name):
1154                # Make sure volume hasn't been recently removed from the sg
1155                is_vol = self.rest.is_volume_in_storagegroup(
1156                    serial_number, device_id, sg_name)
1157                if is_vol:
1158                    num_vol_in_sg = self.rest.get_num_vols_in_sg(
1159                        serial_number, sg_name)
1160                    LOG.debug(
1161                        "There are %(num_vol)d volumes in the storage group "
1162                        "%(sg_name)s associated with %(mv_name)s. Parent "
1163                        "storagegroup is %(parent)s.",
1164                        {'num_vol': num_vol_in_sg, 'sg_name': sg_name,
1165                         'mv_name': mv_name, 'parent': parent_name})
1166
1167                    if num_vol_in_sg == 1:
1168                        # Last volume in the storage group - delete sg.
1169                        self._last_vol_in_sg(
1170                            serial_number, device_id, vol_name, sg_name,
1171                            extra_specs, move, connector)
1172                    else:
1173                        # Not the last volume so remove it from storage group
1174                        self._multiple_vols_in_sg(
1175                            serial_number, device_id, sg_name, vol_name,
1176                            extra_specs, move)
1177                else:
1178                    LOG.info("Volume with device_id %(dev)s is no longer a "
1179                             "member of %(sg)s",
1180                             {'dev': device_id, 'sg': sg_name})
1181
1182            return do_remove_volume_from_sg(masking_name, storagegroup_name,
1183                                            parent_sg_name)
1184
1185    def _last_vol_in_sg(self, serial_number, device_id, volume_name,
1186                        storagegroup_name, extra_specs, move, connector=None):
1187        """Steps if the volume is the last in a storage group.
1188
1189        1. Check if the volume is in a masking view.
1190        2. If it is in a masking view, check if it is the last volume in the
1191           masking view or just this child storage group.
1192        3. If it is last in the masking view, delete the masking view,
1193           delete the initiator group if there are no other masking views
1194           associated with it, and delete the both the current storage group
1195           and its parent group.
1196        4. Otherwise, remove the volume and delete the child storage group.
1197        5. If it is not in a masking view, delete the storage group.
1198        :param serial_number: array serial number
1199        :param device_id: volume device id
1200        :param volume_name: volume name
1201        :param storagegroup_name: storage group name
1202        :param extra_specs: extra specifications
1203        :param move: flag to indicate a move instead of remove
1204        :param connector: the connector object
1205        :returns: status -- bool
1206        """
1207        LOG.debug("Only one volume remains in storage group "
1208                  "%(sgname)s. Driver will attempt cleanup.",
1209                  {'sgname': storagegroup_name})
1210        maskingview_list = self.rest.get_masking_views_from_storage_group(
1211            serial_number, storagegroup_name)
1212        if not bool(maskingview_list):
1213            status = self._last_vol_no_masking_views(
1214                serial_number, storagegroup_name, device_id, volume_name,
1215                extra_specs, move)
1216        else:
1217            status = self._last_vol_masking_views(
1218                serial_number, storagegroup_name, maskingview_list,
1219                device_id, volume_name, extra_specs, connector, move)
1220        return status
1221
1222    def _last_vol_no_masking_views(self, serial_number, storagegroup_name,
1223                                   device_id, volume_name, extra_specs, move):
1224        """Remove the last vol from an sg not associated with an mv.
1225
1226        Helper function for removing the last vol from a storage group
1227        which is not associated with a masking view.
1228        :param serial_number: the array serial number
1229        :param storagegroup_name: the storage group name
1230        :param device_id: the device id
1231        :param volume_name: the volume name
1232        :param extra_specs: the extra specifications
1233        :param move: flag to indicate a move instead of remove
1234        :returns: status -- bool
1235        """
1236        # Check if storage group is a child sg:
1237        parent_sg = self.get_parent_sg_from_child(
1238            serial_number, storagegroup_name)
1239        if parent_sg is None:
1240            # Move the volume back to the default storage group, if required
1241            if move:
1242                self.add_volume_to_default_storage_group(
1243                    serial_number, device_id, volume_name,
1244                    extra_specs, src_sg=storagegroup_name)
1245            # Delete the storage group.
1246            self.rest.delete_storage_group(serial_number, storagegroup_name)
1247            status = True
1248        else:
1249            num_vols_parent = self.rest.get_num_vols_in_sg(
1250                serial_number, parent_sg)
1251            if num_vols_parent == 1:
1252                self._delete_cascaded_storage_groups(
1253                    serial_number, storagegroup_name, parent_sg,
1254                    extra_specs, device_id, move)
1255            else:
1256                self._remove_last_vol_and_delete_sg(
1257                    serial_number, device_id, volume_name,
1258                    storagegroup_name, extra_specs, parent_sg, move)
1259            status = True
1260        return status
1261
1262    def _last_vol_masking_views(
1263            self, serial_number, storagegroup_name, maskingview_list,
1264            device_id, volume_name, extra_specs, connector, move):
1265        """Remove the last vol from an sg associated with masking views.
1266
1267        Helper function for removing the last vol from a storage group
1268        which is associated with one or more masking views.
1269        :param serial_number: the array serial number
1270        :param storagegroup_name: the storage group name
1271        :param maskingview_list: the liast of masking views
1272        :param device_id: the device id
1273        :param volume_name: the volume name
1274        :param extra_specs: the extra specifications
1275        :param move: flag to indicate a move instead of remove
1276        :returns: status -- bool
1277        """
1278        status = False
1279        for mv in maskingview_list:
1280            num_vols_in_mv, parent_sg_name = (
1281                self._get_num_vols_from_mv(serial_number, mv))
1282            # If the volume is the last in the masking view, full cleanup
1283            if num_vols_in_mv == 1:
1284                self._delete_mv_ig_and_sg(
1285                    serial_number, device_id, mv, storagegroup_name,
1286                    parent_sg_name, connector, move, extra_specs)
1287            else:
1288                self._remove_last_vol_and_delete_sg(
1289                    serial_number, device_id, volume_name,
1290                    storagegroup_name, extra_specs, parent_sg_name, move)
1291            status = True
1292        return status
1293
1294    def get_parent_sg_from_child(self, serial_number, storagegroup_name):
1295        """Given a storage group name, get its parent storage group, if any.
1296
1297        :param serial_number: the array serial number
1298        :param storagegroup_name: the name of the storage group
1299        :returns: the parent storage group name, or None
1300        """
1301        parent_sg_name = None
1302        storagegroup = self.rest.get_storage_group(
1303            serial_number, storagegroup_name)
1304        if storagegroup and storagegroup.get('parent_storage_group'):
1305            parent_sg_name = storagegroup['parent_storage_group'][0]
1306        return parent_sg_name
1307
1308    def _get_num_vols_from_mv(self, serial_number, maskingview_name):
1309        """Get the total number of volumes associated with a masking view.
1310
1311        :param serial_number: the array serial number
1312        :param maskingview_name: the name of the masking view
1313        :returns: num_vols, parent_sg_name
1314        """
1315        parent_sg_name = self.rest.get_element_from_masking_view(
1316            serial_number, maskingview_name, storagegroup=True)
1317        num_vols = self.rest.get_num_vols_in_sg(serial_number, parent_sg_name)
1318        return num_vols, parent_sg_name
1319
1320    def _multiple_vols_in_sg(self, serial_number, device_id, storagegroup_name,
1321                             volume_name, extra_specs, move):
1322        """Remove the volume from the SG.
1323
1324        If the volume is not the last in the storage group,
1325        remove the volume from the SG and leave the sg on the array.
1326        :param serial_number: array serial number
1327        :param device_id: volume device id
1328        :param volume_name: volume name
1329        :param storagegroup_name: storage group name
1330        :param move: flag to indicate a move instead of remove
1331        :param extra_specs: extra specifications
1332        """
1333        if move:
1334            self.add_volume_to_default_storage_group(
1335                serial_number, device_id, volume_name,
1336                extra_specs, src_sg=storagegroup_name)
1337        else:
1338            self.remove_vol_from_storage_group(
1339                serial_number, device_id, storagegroup_name,
1340                volume_name, extra_specs)
1341
1342        LOG.debug(
1343            "Volume %(volume_name)s successfully moved/ removed from "
1344            "storage group %(sg)s.",
1345            {'volume_name': volume_name, 'sg': storagegroup_name})
1346
1347        num_vol_in_sg = self.rest.get_num_vols_in_sg(
1348            serial_number, storagegroup_name)
1349        LOG.debug("There are %(num_vol)d volumes remaining in the storage "
1350                  "group %(sg_name)s.",
1351                  {'num_vol': num_vol_in_sg,
1352                   'sg_name': storagegroup_name})
1353
1354    def _delete_cascaded_storage_groups(self, serial_number, child_sg_name,
1355                                        parent_sg_name, extra_specs,
1356                                        device_id, move):
1357        """Delete a child and parent storage groups.
1358
1359        :param serial_number: the array serial number
1360        :param child_sg_name: the child storage group name
1361        :param parent_sg_name: the parent storage group name
1362        :param extra_specs: the extra specifications
1363        :param device_id: the volume device id
1364        :param move: flag to indicate if volume should be moved to default sg
1365        """
1366        if move:
1367            self.add_volume_to_default_storage_group(
1368                serial_number, device_id, "",
1369                extra_specs, src_sg=child_sg_name)
1370        if child_sg_name != parent_sg_name:
1371            self.rest.delete_storage_group(serial_number, parent_sg_name)
1372            LOG.debug("Storage Group %(storagegroup_name)s "
1373                      "successfully deleted.",
1374                      {'storagegroup_name': parent_sg_name})
1375        self.rest.delete_storage_group(serial_number, child_sg_name)
1376
1377        LOG.debug("Storage Group %(storagegroup_name)s successfully deleted.",
1378                  {'storagegroup_name': child_sg_name})
1379
1380    def _delete_mv_ig_and_sg(
1381            self, serial_number, device_id, masking_view, storagegroup_name,
1382            parent_sg_name, connector, move, extra_specs):
1383        """Delete the masking view, storage groups and initiator group.
1384
1385        :param serial_number: array serial number
1386        :param device_id: the device id
1387        :param masking_view: masking view name
1388        :param storagegroup_name: storage group name
1389        :param parent_sg_name: the parent storage group name
1390        :param connector: the connector object
1391        :param move: flag to indicate if the volume should be moved
1392        :param extra_specs: the extra specifications
1393        """
1394        host = (self.utils.get_host_short_name(connector['host'])
1395                if connector else None)
1396
1397        initiatorgroup = self.rest.get_element_from_masking_view(
1398            serial_number, masking_view, host=True)
1399        self._last_volume_delete_masking_view(serial_number, masking_view)
1400        self._last_volume_delete_initiator_group(
1401            serial_number, initiatorgroup, host)
1402        self._delete_cascaded_storage_groups(
1403            serial_number, storagegroup_name, parent_sg_name,
1404            extra_specs, device_id, move)
1405
1406    def _last_volume_delete_masking_view(self, serial_number, masking_view):
1407        """Delete the masking view.
1408
1409        Delete the masking view if the volume is the last one in the
1410        storage group.
1411        :param serial_number: the array serial number
1412        :param masking_view: masking view name
1413        """
1414        LOG.debug("Last volume in the storage group, deleting masking view "
1415                  "%(maskingview_name)s.", {'maskingview_name': masking_view})
1416        self.rest.delete_masking_view(serial_number, masking_view)
1417        LOG.info("Masking view %(maskingview)s successfully deleted.",
1418                 {'maskingview': masking_view})
1419
1420    def add_volume_to_default_storage_group(
1421            self, serial_number, device_id, volume_name,
1422            extra_specs, src_sg=None, volume=None):
1423        """Return volume to its default storage group.
1424
1425        :param serial_number: the array serial number
1426        :param device_id: the volume device id
1427        :param volume_name: the volume name
1428        :param extra_specs: the extra specifications
1429        :param src_sg: the source storage group, if any
1430        :param volume: the volume object
1431        """
1432        do_disable_compression = self.utils.is_compression_disabled(
1433            extra_specs)
1434        rep_enabled = self.utils.is_replication_enabled(extra_specs)
1435        rep_mode = extra_specs.get(utils.REP_MODE, None)
1436        if self.rest.is_next_gen_array(serial_number):
1437            extra_specs[utils.WORKLOAD] = 'NONE'
1438        storagegroup_name = self.get_or_create_default_storage_group(
1439            serial_number, extra_specs[utils.SRP], extra_specs[utils.SLO],
1440            extra_specs[utils.WORKLOAD], extra_specs, do_disable_compression,
1441            rep_enabled, rep_mode)
1442        if src_sg is not None:
1443            # Need to lock the default storage group
1444            @coordination.synchronized("emc-sg-{default_sg_name}")
1445            def _move_vol_to_default_sg(default_sg_name):
1446                self.rest.move_volume_between_storage_groups(
1447                    serial_number, device_id, src_sg,
1448                    default_sg_name, extra_specs, force=True)
1449            _move_vol_to_default_sg(storagegroup_name)
1450        else:
1451            self._check_adding_volume_to_storage_group(
1452                serial_number, device_id, storagegroup_name, volume_name,
1453                extra_specs)
1454        if volume:
1455            # Need to check if the volume needs to be returned to a
1456            # generic volume group. This may be necessary in a force-detach
1457            # situation.
1458            if volume.group_id is not None:
1459                vol_grp_name = self.provision.get_or_create_volume_group(
1460                    serial_number, volume.group, extra_specs)
1461                self._check_adding_volume_to_storage_group(
1462                    serial_number, device_id,
1463                    vol_grp_name, volume_name, extra_specs)
1464
1465    def get_or_create_default_storage_group(
1466            self, serial_number, srp, slo, workload, extra_specs,
1467            do_disable_compression=False, is_re=False, rep_mode=None):
1468        """Get or create a default storage group.
1469
1470        :param serial_number: the array serial number
1471        :param srp: the SRP name
1472        :param slo: the SLO
1473        :param workload: the workload
1474        :param extra_specs: extra specifications
1475        :param do_disable_compression: flag for compression
1476        :param is_re: is replication enabled
1477        :param rep_mode: flag to indicate replication mode
1478        :returns: storagegroup_name
1479        :raises: VolumeBackendAPIException
1480        """
1481        storagegroup, storagegroup_name = (
1482            self.rest.get_vmax_default_storage_group(
1483                serial_number, srp, slo, workload, do_disable_compression,
1484                is_re, rep_mode))
1485        if storagegroup is None:
1486            self.provision.create_storage_group(
1487                serial_number, storagegroup_name, srp, slo, workload,
1488                extra_specs, do_disable_compression)
1489        else:
1490            # Check that SG is not part of a masking view
1491            LOG.info("Using existing default storage group")
1492            masking_views = self.rest.get_masking_views_from_storage_group(
1493                serial_number, storagegroup_name)
1494            if masking_views:
1495                exception_message = (_(
1496                    "Default storage group %(sg_name)s is part of masking "
1497                    "views %(mvs)s. Please remove it from all masking views")
1498                    % {'sg_name': storagegroup_name, 'mvs': masking_views})
1499                LOG.error(exception_message)
1500                raise exception.VolumeBackendAPIException(
1501                    data=exception_message)
1502        # If qos exists, update storage group to reflect qos parameters
1503        if 'qos' in extra_specs:
1504            self.rest.update_storagegroup_qos(
1505                serial_number, storagegroup_name, extra_specs)
1506
1507        return storagegroup_name
1508
1509    def _remove_last_vol_and_delete_sg(
1510            self, serial_number, device_id, volume_name,
1511            storagegroup_name, extra_specs, parent_sg_name=None, move=False):
1512        """Remove the last volume and delete the storage group.
1513
1514        If the storage group is a child of another storage group,
1515        it must be removed from the parent before deletion.
1516        :param serial_number: the array serial number
1517        :param device_id: the volume device id
1518        :param volume_name: the volume name
1519        :param storagegroup_name: the sg name
1520        :param extra_specs: extra specifications
1521        :param parent_sg_name: the parent sg name
1522        """
1523        if move:
1524            self.add_volume_to_default_storage_group(
1525                serial_number, device_id, volume_name,
1526                extra_specs, src_sg=storagegroup_name)
1527        else:
1528            self.remove_vol_from_storage_group(
1529                serial_number, device_id, storagegroup_name, volume_name,
1530                extra_specs)
1531
1532        LOG.debug("Remove the last volume %(volumeName)s completed "
1533                  "successfully.", {'volumeName': volume_name})
1534        if parent_sg_name:
1535            self.rest.remove_child_sg_from_parent_sg(
1536                serial_number, storagegroup_name, parent_sg_name,
1537                extra_specs)
1538
1539        self.rest.delete_storage_group(serial_number, storagegroup_name)
1540
1541    def _last_volume_delete_initiator_group(
1542            self, serial_number, initiatorgroup_name, host):
1543        """Delete the initiator group.
1544
1545        Delete the Initiator group if it has been created by the VMAX driver,
1546        and if there are no masking views associated with it.
1547        :param serial_number: the array serial number
1548        :param initiatorgroup_name: initiator group name
1549        :param host: the short name of the host
1550        """
1551        if host is not None:
1552            protocol = self.utils.get_short_protocol_type(self.protocol)
1553            default_ig_name = ("OS-%(shortHostName)s-%(protocol)s-IG"
1554                               % {'shortHostName': host,
1555                                  'protocol': protocol})
1556
1557            if initiatorgroup_name == default_ig_name:
1558                maskingview_names = (
1559                    self.rest.get_masking_views_by_initiator_group(
1560                        serial_number, initiatorgroup_name))
1561                if not maskingview_names:
1562                    @coordination.synchronized("emc-ig-{ig_name}")
1563                    def _delete_ig(ig_name):
1564                        # Check initiator group hasn't been recently deleted
1565                        ig_details = self.rest.get_initiator_group(
1566                            serial_number, ig_name)
1567                        if ig_details:
1568                            LOG.debug(
1569                                "Last volume associated with the initiator "
1570                                "group - deleting the associated initiator "
1571                                "group %(initiatorgroup_name)s.",
1572                                {'initiatorgroup_name': initiatorgroup_name})
1573                            self.rest.delete_initiator_group(
1574                                serial_number, initiatorgroup_name)
1575                    _delete_ig(initiatorgroup_name)
1576                else:
1577                    LOG.warning("Initiator group %(ig_name)s is associated "
1578                                "with masking views and can't be deleted. "
1579                                "Number of associated masking view is: "
1580                                "%(nmv)d.",
1581                                {'ig_name': initiatorgroup_name,
1582                                 'nmv': len(maskingview_names)})
1583            else:
1584                LOG.warning("Initiator group %(ig_name)s was "
1585                            "not created by the VMAX driver so will "
1586                            "not be deleted by the VMAX driver.",
1587                            {'ig_name': initiatorgroup_name})
1588        else:
1589            LOG.warning("Cannot get host name from connector object - "
1590                        "initiator group %(ig_name)s will not be deleted.",
1591                        {'ig_name': initiatorgroup_name})
1592
1593    def pre_live_migration(self, source_nf_sg, source_sg, source_parent_sg,
1594                           is_source_nf_sg, device_info_dict, extra_specs):
1595        """Run before any live migration operation.
1596
1597        :param source_nf_sg: The non fast storage group
1598        :param source_sg: The source storage group
1599        :param source_parent_sg: The parent storage group
1600        :param is_source_nf_sg: if the non fast storage group already exists
1601        :param device_info_dict: the data dict
1602        :param extra_specs: extra specifications
1603        """
1604        if is_source_nf_sg is False:
1605            storage_group = self.rest.get_storage_group(
1606                device_info_dict['array'], source_nf_sg)
1607            if storage_group is None:
1608                self.provision.create_storage_group(
1609                    device_info_dict['array'], source_nf_sg, None, None, None,
1610                    extra_specs)
1611            self.add_child_sg_to_parent_sg(
1612                device_info_dict['array'], source_nf_sg, source_parent_sg,
1613                extra_specs, default_version=False)
1614        self.move_volume_between_storage_groups(
1615            device_info_dict['array'], device_info_dict['device_id'],
1616            source_sg, source_nf_sg, extra_specs)
1617
1618    def post_live_migration(self, device_info_dict, extra_specs):
1619        """Run after every live migration operation.
1620
1621        :param device_info_dict: : the data dict
1622        :param extra_specs: extra specifications
1623        """
1624        array = device_info_dict['array']
1625        source_sg = device_info_dict['source_sg']
1626        # Delete fast storage group
1627        num_vol_in_sg = self.rest.get_num_vols_in_sg(
1628            array, source_sg)
1629        if num_vol_in_sg == 0:
1630            self.rest.remove_child_sg_from_parent_sg(
1631                array, source_sg, device_info_dict['source_parent_sg'],
1632                extra_specs)
1633            self.rest.delete_storage_group(array, source_sg)
1634
1635    def failed_live_migration(self, device_info_dict,
1636                              source_storage_group_list, extra_specs):
1637        """This is run in the event of a failed live migration operation.
1638
1639        :param device_info_dict: the data dict
1640        :param source_storage_group_list: list of storage groups associated
1641                                          with the device
1642        :param extra_specs: extra specifications
1643        """
1644        array = device_info_dict['array']
1645        source_nf_sg = device_info_dict['source_nf_sg']
1646        source_sg = device_info_dict['source_sg']
1647        source_parent_sg = device_info_dict['source_parent_sg']
1648        device_id = device_info_dict['device_id']
1649        for sg in source_storage_group_list:
1650            if sg not in [source_sg, source_nf_sg]:
1651                self.remove_volume_from_sg(
1652                    array, device_id, device_info_dict['volume_name'], sg,
1653                    extra_specs)
1654        if source_nf_sg in source_storage_group_list:
1655            self.move_volume_between_storage_groups(
1656                array, device_id, source_nf_sg,
1657                source_sg, extra_specs)
1658            is_descendant = self.rest.is_child_sg_in_parent_sg(
1659                array, source_nf_sg, source_parent_sg)
1660            if is_descendant:
1661                self.rest.remove_child_sg_from_parent_sg(
1662                    array, source_nf_sg, source_parent_sg, extra_specs)
1663            # Delete non fast storage group
1664            self.rest.delete_storage_group(array, source_nf_sg)
1665
1666    def attempt_ig_cleanup(self, connector, protocol, serial_number, force):
1667        """Attempt to cleanup an orphan initiator group
1668
1669        :param connector: connector object
1670        :param protocol: iscsi or fc
1671        :param serial_number: extra the array serial number
1672        """
1673        protocol = self.utils.get_short_protocol_type(protocol)
1674        host_name = connector['host']
1675        short_host_name = self.utils.get_host_short_name(host_name)
1676        init_group = (
1677            ("OS-%(shortHostName)s-%(protocol)s-IG"
1678             % {'shortHostName': short_host_name,
1679                'protocol': protocol}))
1680        self._check_ig_rollback(
1681            serial_number, init_group, connector, force)
1682