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
16from copy import deepcopy
17import datetime
18import hashlib
19import random
20import re
21from xml.dom import minidom
22
23from cinder.objects.group import Group
24from oslo_log import log as logging
25from oslo_utils import strutils
26import six
27
28from cinder import exception
29from cinder.i18n import _
30from cinder.objects import fields
31from cinder.volume import volume_types
32
33
34LOG = logging.getLogger(__name__)
35# SHARED CONSTANTS
36ISCSI = 'iscsi'
37FC = 'fc'
38INTERVAL = 'interval'
39RETRIES = 'retries'
40VOLUME_ELEMENT_NAME_PREFIX = 'OS-'
41VMAX_AFA_MODELS = ['VMAX250F', 'VMAX450F', 'VMAX850F', 'VMAX950F']
42MAX_SRP_LENGTH = 16
43TRUNCATE_5 = 5
44TRUNCATE_27 = 27
45
46ARRAY = 'array'
47SLO = 'slo'
48WORKLOAD = 'workload'
49SRP = 'srp'
50PORTGROUPNAME = 'storagetype:portgroupname'
51DEVICE_ID = 'device_id'
52INITIATOR_CHECK = 'initiator_check'
53SG_NAME = 'storagegroup_name'
54MV_NAME = 'maskingview_name'
55IG_NAME = 'init_group_name'
56PARENT_SG_NAME = 'parent_sg_name'
57CONNECTOR = 'connector'
58VOL_NAME = 'volume_name'
59EXTRA_SPECS = 'extra_specs'
60IS_RE = 'replication_enabled'
61DISABLECOMPRESSION = 'storagetype:disablecompression'
62REP_SYNC = 'Synchronous'
63REP_ASYNC = 'Asynchronous'
64REP_METRO = 'Metro'
65REP_MODE = 'rep_mode'
66RDF_SYNC_STATE = 'synchronized'
67RDF_SYNCINPROG_STATE = 'syncinprog'
68RDF_CONSISTENT_STATE = 'consistent'
69RDF_SUSPENDED_STATE = 'suspended'
70RDF_FAILEDOVER_STATE = 'failed over'
71RDF_ACTIVE = 'active'
72RDF_ACTIVEACTIVE = 'activeactive'
73RDF_ACTIVEBIAS = 'activebias'
74METROBIAS = 'metro_bias'
75
76# Cinder.conf vmax configuration
77VMAX_SERVER_IP = 'san_ip'
78VMAX_USER_NAME = 'san_login'
79VMAX_PASSWORD = 'san_password'
80VMAX_SERVER_PORT = 'san_rest_port'
81VMAX_ARRAY = 'vmax_array'
82VMAX_WORKLOAD = 'vmax_workload'
83VMAX_SRP = 'vmax_srp'
84VMAX_SERVICE_LEVEL = 'vmax_service_level'
85VMAX_PORT_GROUPS = 'vmax_port_groups'
86
87
88class VMAXUtils(object):
89    """Utility class for Rest based VMAX volume drivers.
90
91    This Utility class is for VMAX volume drivers based on Unisphere Rest API.
92    """
93
94    def __init__(self):
95        """Utility class for Rest based VMAX volume drivers."""
96
97    def get_host_short_name(self, host_name):
98        """Returns the short name for a given qualified host name.
99
100        Checks the host name to see if it is the fully qualified host name
101        and returns part before the dot. If there is no dot in the host name
102        the full host name is returned.
103        :param host_name: the fully qualified host name
104        :returns: string -- the short host_name
105        """
106        host_array = host_name.split('.')
107        if len(host_array) > 1:
108            short_host_name = host_array[0]
109        else:
110            short_host_name = host_name
111
112        return self.generate_unique_trunc_host(short_host_name)
113
114    @staticmethod
115    def get_volumetype_extra_specs(volume, volume_type_id=None):
116        """Gets the extra specs associated with a volume type.
117
118        :param volume: the volume dictionary
119        :param volume_type_id: Optional override for volume.volume_type_id
120        :returns: dict -- extra_specs - the extra specs
121        :raises: VolumeBackendAPIException
122        """
123        extra_specs = {}
124
125        try:
126            if volume_type_id:
127                type_id = volume_type_id
128            else:
129                type_id = volume.volume_type_id
130            if type_id is not None:
131                extra_specs = volume_types.get_volume_type_extra_specs(type_id)
132        except Exception as e:
133            LOG.debug('Exception getting volume type extra specs: %(e)s',
134                      {'e': six.text_type(e)})
135        return extra_specs
136
137    @staticmethod
138    def get_short_protocol_type(protocol):
139        """Given the protocol type, return I for iscsi and F for fc.
140
141        :param protocol: iscsi or fc
142        :returns: string -- 'I' for iscsi or 'F' for fc
143        """
144        if protocol.lower() == ISCSI.lower():
145            return 'I'
146        elif protocol.lower() == FC.lower():
147            return 'F'
148        else:
149            return protocol
150
151    @staticmethod
152    def truncate_string(str_to_truncate, max_num):
153        """Truncate a string by taking first and last characters.
154
155        :param str_to_truncate: the string to be truncated
156        :param max_num: the maximum number of characters
157        :returns: string -- truncated string or original string
158        """
159        if len(str_to_truncate) > max_num:
160            new_num = len(str_to_truncate) - max_num // 2
161            first_chars = str_to_truncate[:max_num // 2]
162            last_chars = str_to_truncate[new_num:]
163            str_to_truncate = first_chars + last_chars
164        return str_to_truncate
165
166    @staticmethod
167    def get_time_delta(start_time, end_time):
168        """Get the delta between start and end time.
169
170        :param start_time: the start time
171        :param end_time: the end time
172        :returns: string -- delta in string H:MM:SS
173        """
174        delta = end_time - start_time
175        return six.text_type(datetime.timedelta(seconds=int(delta)))
176
177    def get_default_storage_group_name(
178            self, srp_name, slo, workload, is_compression_disabled=False,
179            is_re=False, rep_mode=None):
180        """Determine default storage group from extra_specs.
181
182        :param srp_name: the name of the srp on the array
183        :param slo: the service level string e.g Bronze
184        :param workload: the workload string e.g DSS
185        :param is_compression_disabled:  flag for disabling compression
186        :param is_re: flag for replication
187        :param rep_mode: flag to indicate replication mode
188        :returns: storage_group_name
189        """
190        if slo and workload:
191            prefix = ("OS-%(srpName)s-%(slo)s-%(workload)s"
192                      % {'srpName': srp_name, 'slo': slo,
193                         'workload': workload})
194
195            if is_compression_disabled:
196                prefix += "-CD"
197
198        else:
199            prefix = "OS-no_SLO"
200        if is_re:
201            prefix += self.get_replication_prefix(rep_mode)
202
203        storage_group_name = ("%(prefix)s-SG" % {'prefix': prefix})
204        return storage_group_name
205
206    @staticmethod
207    def get_volume_element_name(volume_id):
208        """Get volume element name follows naming convention, i.e. 'OS-UUID'.
209
210        :param volume_id: Openstack volume ID containing uuid
211        :returns: volume element name in format of OS-UUID
212        """
213        element_name = volume_id
214        uuid_regex = (re.compile(
215            '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}',
216            re.I))
217        match = uuid_regex.search(volume_id)
218        if match:
219            volume_uuid = match.group()
220            element_name = ("%(prefix)s%(volumeUUID)s"
221                            % {'prefix': VOLUME_ELEMENT_NAME_PREFIX,
222                               'volumeUUID': volume_uuid})
223            LOG.debug(
224                "get_volume_element_name elementName:  %(elementName)s.",
225                {'elementName': element_name})
226        return element_name
227
228    @staticmethod
229    def modify_snapshot_prefix(snapshot_name, manage=False, unmanage=False):
230        """Modify a Snapshot prefix on VMAX backend.
231
232        Prepare a snapshot name for manage/unmanage snapshot process either
233        by adding or removing 'OS-' prefix.
234
235        :param snapshot_name: the old snapshot backend display name
236        :param manage: (bool) if the operation is managing a snapshot
237        :param unmanage: (bool) if the operation is unmanaging a snapshot
238        :return: snapshot name ready for backend VMAX assignment
239        """
240        new_snap_name = None
241        if manage:
242            new_snap_name = ("%(prefix)s%(snapshot_name)s"
243                             % {'prefix': 'OS-',
244                                'snapshot_name': snapshot_name})
245
246        if unmanage:
247            snap_split = snapshot_name.split("-", 1)
248            if snap_split[0] == 'OS':
249                new_snap_name = snap_split[1]
250
251        return new_snap_name
252
253    def generate_unique_trunc_host(self, host_name):
254        """Create a unique short host name under 16 characters.
255
256        :param host_name: long host name
257        :returns: truncated host name
258        """
259        if host_name and len(host_name) > 16:
260            host_name = host_name.lower()
261            m = hashlib.md5()
262            m.update(host_name.encode('utf-8'))
263            uuid = m.hexdigest()
264            new_name = ("%(host)s%(uuid)s"
265                        % {'host': host_name[-6:],
266                           'uuid': uuid})
267            host_name = self.truncate_string(new_name, 16)
268        return host_name
269
270    def get_pg_short_name(self, portgroup_name):
271        """Create a unique port group name under 12 characters.
272
273        :param portgroup_name: long portgroup_name
274        :returns: truncated portgroup_name
275        """
276        if portgroup_name and len(portgroup_name) > 12:
277            portgroup_name = portgroup_name.lower()
278            m = hashlib.md5()
279            m.update(portgroup_name.encode('utf-8'))
280            uuid = m.hexdigest()
281            new_name = ("%(pg)s%(uuid)s"
282                        % {'pg': portgroup_name[-6:],
283                           'uuid': uuid})
284            portgroup_name = self.truncate_string(new_name, 12)
285        return portgroup_name
286
287    @staticmethod
288    def get_default_oversubscription_ratio(max_over_sub_ratio):
289        """Override ratio if necessary.
290
291        The over subscription ratio will be overridden if the user supplied
292        max oversubscription ratio is less than 1.
293        :param max_over_sub_ratio: user supplied over subscription ratio
294        :returns: max_over_sub_ratio
295        """
296        if max_over_sub_ratio < 1.0:
297            LOG.info("The user supplied value for max_over_subscription "
298                     "ratio is less than 1.0. Using the default value of "
299                     "20.0 instead...")
300            max_over_sub_ratio = 20.0
301        return max_over_sub_ratio
302
303    @staticmethod
304    def _process_tag(element, tag_name):
305        """Process the tag to get the value.
306
307        :param element: the parent element
308        :param tag_name: the tag name
309        :returns: nodeValue(can be None)
310        """
311        node_value = None
312        try:
313            processed_element = element.getElementsByTagName(tag_name)[0]
314            node_value = processed_element.childNodes[0].nodeValue
315            if node_value:
316                node_value = node_value.strip()
317        except IndexError:
318            pass
319        return node_value
320
321    def _get_connection_info(self, rest_element):
322        """Given the filename get the rest server connection details.
323
324        :param rest_element: the rest element
325        :returns: dict -- connargs - the connection info dictionary
326        :raises: VolumeBackendAPIException
327        """
328        connargs = {
329            'RestServerIp': (
330                self._process_tag(rest_element, 'RestServerIp')),
331            'RestServerPort': (
332                self._process_tag(rest_element, 'RestServerPort')),
333            'RestUserName': (
334                self._process_tag(rest_element, 'RestUserName')),
335            'RestPassword': (
336                self._process_tag(rest_element, 'RestPassword'))}
337
338        for k, __ in connargs.items():
339            if connargs[k] is None:
340                exception_message = (_(
341                    "RestServerIp, RestServerPort, RestUserName, "
342                    "RestPassword must have valid values."))
343                LOG.error(exception_message)
344                raise exception.VolumeBackendAPIException(
345                    data=exception_message)
346
347        # These can be None
348        connargs['SSLCert'] = self._process_tag(rest_element, 'SSLCert')
349        connargs['SSLVerify'] = (
350            self._process_tag(rest_element, 'SSLVerify'))
351
352        return connargs
353
354    def parse_file_to_get_array_map(self, file_name):
355        """Parses a file and gets array map.
356
357        Given a file, parse it to get array and pool(srp).
358
359        .. code:: ini
360
361          <EMC>
362          <RestServerIp>10.108.246.202</RestServerIp>
363          <RestServerPort>8443</RestServerPort>
364          <RestUserName>smc</RestUserName>
365          <RestPassword>smc</RestPassword>
366          <SSLCert>/path/client.cert</SSLCert>
367          <SSLVerify>/path/to/certfile.pem</SSLVerify>
368          <PortGroups>
369              <PortGroup>OS-PORTGROUP1-PG</PortGroup>
370          </PortGroups>
371          <Array>000198700439</Array>
372          <SRP>SRP_1</SRP>
373          </EMC>
374
375        :param file_name: the configuration file
376        :returns: list
377        """
378        LOG.warning("Use of xml file in backend configuration is deprecated "
379                    "in Queens and will not be supported in future releases.")
380        kwargs = {}
381        my_file = open(file_name, 'r')
382        data = my_file.read()
383        my_file.close()
384        dom = minidom.parseString(data)
385        try:
386            connargs = self._get_connection_info(dom)
387            portgroup = self._get_random_portgroup(dom)
388            serialnumber = self._process_tag(dom, 'Array')
389            if serialnumber is None:
390                LOG.error("Array Serial Number must be in the file %(file)s.",
391                          {'file': file_name})
392            srp_name = self._process_tag(dom, 'SRP')
393            if srp_name is None:
394                LOG.error("SRP Name must be in the file %(file)s.",
395                          {'file': file_name})
396            slo = self._process_tag(dom, 'ServiceLevel')
397            workload = self._process_tag(dom, 'Workload')
398            kwargs = (
399                {'RestServerIp': connargs['RestServerIp'],
400                 'RestServerPort': connargs['RestServerPort'],
401                 'RestUserName': connargs['RestUserName'],
402                 'RestPassword': connargs['RestPassword'],
403                 'SSLCert': connargs['SSLCert'],
404                 'SSLVerify': connargs['SSLVerify'],
405                 'SerialNumber': serialnumber,
406                 'srpName': srp_name,
407                 'PortGroup': portgroup})
408            if slo is not None:
409                kwargs.update({'ServiceLevel': slo, 'Workload': workload})
410
411        except IndexError:
412            pass
413        return kwargs
414
415    @staticmethod
416    def _get_random_portgroup(element):
417        """Randomly choose a portgroup from list of portgroups.
418
419        :param element: the parent element
420        :returns: the randomly chosen port group
421        """
422        portgroupelements = element.getElementsByTagName('PortGroup')
423        if portgroupelements and len(portgroupelements) > 0:
424            portgroupnames = [portgroupelement.childNodes[0].nodeValue.strip()
425                              for portgroupelement in portgroupelements
426                              if portgroupelement.childNodes]
427            portgroupnames = list(set(filter(None, portgroupnames)))
428            pg_len = len(portgroupnames)
429            if pg_len > 0:
430                return portgroupnames[random.randint(0, pg_len - 1)]
431        return None
432
433    def get_temp_snap_name(self, clone_name, source_device_id):
434        """Construct a temporary snapshot name for clone operation.
435
436        :param clone_name: the name of the clone
437        :param source_device_id: the source device id
438        :returns: snap_name
439        """
440        trunc_clone = self.truncate_string(clone_name, 10)
441        snap_name = ("temp-%(device)s-%(clone)s"
442                     % {'device': source_device_id, 'clone': trunc_clone})
443        return snap_name
444
445    @staticmethod
446    def get_array_and_device_id(volume, external_ref):
447        """Helper function for manage volume to get array name and device ID.
448
449        :param volume: volume object from API
450        :param external_ref: the existing volume object to be manged
451        :returns: string value of the array name and device ID
452        """
453        device_id = external_ref.get(u'source-name', None)
454        LOG.debug("External_ref: %(er)s", {'er': external_ref})
455        if not device_id:
456            device_id = external_ref.get(u'source-id', None)
457        host = volume.host
458        host_list = host.split('+')
459        array = host_list[(len(host_list) - 1)]
460
461        if device_id:
462            LOG.debug("Get device ID of existing volume - device ID: "
463                      "%(device_id)s, Array: %(array)s.",
464                      {'device_id': device_id,
465                       'array': array})
466        else:
467            exception_message = (_("Source volume device ID is required."))
468            raise exception.VolumeBackendAPIException(
469                data=exception_message)
470        return array, device_id
471
472    @staticmethod
473    def is_compression_disabled(extra_specs):
474        """Check is compression is to be disabled.
475
476        :param extra_specs: extra specifications
477        :returns: boolean
478        """
479        do_disable_compression = False
480        if DISABLECOMPRESSION in extra_specs:
481            if strutils.bool_from_string(extra_specs[DISABLECOMPRESSION]):
482                do_disable_compression = True
483        return do_disable_compression
484
485    def change_compression_type(self, is_source_compr_disabled, new_type):
486        """Check if volume type have different compression types
487
488        :param is_source_compr_disabled: from source
489        :param new_type: from target
490        :returns: boolean
491        """
492        extra_specs = new_type['extra_specs']
493        is_target_compr_disabled = self.is_compression_disabled(extra_specs)
494        if is_target_compr_disabled == is_source_compr_disabled:
495            return False
496        else:
497            return True
498
499    @staticmethod
500    def is_replication_enabled(extra_specs):
501        """Check if replication is to be enabled.
502
503        :param extra_specs: extra specifications
504        :returns: bool - true if enabled, else false
505        """
506        replication_enabled = False
507        if IS_RE in extra_specs:
508            replication_enabled = True
509        return replication_enabled
510
511    @staticmethod
512    def get_replication_config(rep_device_list):
513        """Gather necessary replication configuration info.
514
515        :param rep_device_list: the replication device list from cinder.conf
516        :returns: rep_config, replication configuration dict
517        """
518        rep_config = {}
519        if not rep_device_list:
520            return None
521        else:
522            target = rep_device_list[0]
523            try:
524                rep_config['array'] = target['target_device_id']
525                rep_config['srp'] = target['remote_pool']
526                rep_config['rdf_group_label'] = target['rdf_group_label']
527                rep_config['portgroup'] = target['remote_port_group']
528
529            except KeyError as ke:
530                error_message = (_("Failed to retrieve all necessary SRDF "
531                                   "information. Error received: %(ke)s.") %
532                                 {'ke': six.text_type(ke)})
533                LOG.exception(error_message)
534                raise exception.VolumeBackendAPIException(data=error_message)
535
536            allow_extend = target.get('allow_extend', 'false')
537            if strutils.bool_from_string(allow_extend):
538                rep_config['allow_extend'] = True
539            else:
540                rep_config['allow_extend'] = False
541
542            rep_mode = target.get('mode', '')
543            if rep_mode.lower() in ['async', 'asynchronous']:
544                rep_config['mode'] = REP_ASYNC
545            elif rep_mode.lower() == 'metro':
546                rep_config['mode'] = REP_METRO
547                metro_bias = target.get('metro_use_bias', 'false')
548                if strutils.bool_from_string(metro_bias):
549                    rep_config[METROBIAS] = True
550                else:
551                    rep_config[METROBIAS] = False
552                allow_delete_metro = target.get('allow_delete_metro', 'false')
553                if strutils.bool_from_string(allow_delete_metro):
554                    rep_config['allow_delete_metro'] = True
555                else:
556                    rep_config['allow_delete_metro'] = False
557            else:
558                rep_config['mode'] = REP_SYNC
559
560        return rep_config
561
562    @staticmethod
563    def is_volume_failed_over(volume):
564        """Check if a volume has been failed over.
565
566        :param volume: the volume object
567        :returns: bool
568        """
569        if volume is not None:
570            if volume.get('replication_status') and (
571                volume.replication_status ==
572                    fields.ReplicationStatus.FAILED_OVER):
573                    return True
574        return False
575
576    @staticmethod
577    def update_volume_model_updates(volume_model_updates,
578                                    volumes, group_id, status='available'):
579        """Update the volume model's status and return it.
580
581        :param volume_model_updates: list of volume model update dicts
582        :param volumes: volumes object api
583        :param group_id: consistency group id
584        :param status: string value reflects the status of the member volume
585        :returns: volume_model_updates - updated volumes
586        """
587        LOG.info("Updating status for group: %(id)s.", {'id': group_id})
588        if volumes:
589            for volume in volumes:
590                volume_model_updates.append({'id': volume.id,
591                                             'status': status})
592        else:
593            LOG.info("No volume found for group: %(cg)s.", {'cg': group_id})
594        return volume_model_updates
595
596    @staticmethod
597    def get_grp_volume_model_update(volume, volume_dict, group_id):
598        """Create and return the volume model update on creation.
599
600        :param volume: volume object
601        :param volume_dict: the volume dict
602        :param group_id: consistency group id
603        :returns: model_update
604        """
605        LOG.info("Updating status for group: %(id)s.", {'id': group_id})
606        model_update = ({'id': volume.id, 'status': 'available',
607                         'provider_location': six.text_type(volume_dict)})
608        return model_update
609
610    @staticmethod
611    def update_extra_specs(extraspecs):
612        """Update extra specs.
613
614        :param extraspecs: the additional info
615        :returns: extraspecs
616        """
617        try:
618            pool_details = extraspecs['pool_name'].split('+')
619            extraspecs[SLO] = pool_details[0]
620            if len(pool_details) == 4:
621                extraspecs[WORKLOAD] = pool_details[1]
622                extraspecs[SRP] = pool_details[2]
623                extraspecs[ARRAY] = pool_details[3]
624            else:
625                # Assume no workload given in pool name
626                extraspecs[SRP] = pool_details[1]
627                extraspecs[ARRAY] = pool_details[2]
628                extraspecs[WORKLOAD] = 'NONE'
629        except KeyError:
630            LOG.error("Error parsing SLO, workload from"
631                      " the provided extra_specs.")
632        return extraspecs
633
634    def get_volume_group_utils(self, group, interval, retries):
635        """Standard utility for generic volume groups.
636
637        :param group: the generic volume group object to be created
638        :param interval: Interval in seconds between retries
639        :param retries: Retry count
640        :returns: array, intervals_retries_dict
641        :raises: VolumeBackendAPIException
642        """
643        arrays = set()
644        # Check if it is a generic volume group instance
645        if isinstance(group, Group):
646            for volume_type in group.volume_types:
647                extra_specs = self.update_extra_specs(volume_type.extra_specs)
648                arrays.add(extra_specs[ARRAY])
649        else:
650            msg = (_("Unable to get volume type ids."))
651            LOG.error(msg)
652            raise exception.VolumeBackendAPIException(data=msg)
653
654        if len(arrays) != 1:
655            if not arrays:
656                msg = (_("Failed to get an array associated with "
657                         "volume group: %(groupid)s.")
658                       % {'groupid': group.id})
659            else:
660                msg = (_("There are multiple arrays "
661                         "associated with volume group: %(groupid)s.")
662                       % {'groupid': group.id})
663            LOG.error(msg)
664            raise exception.VolumeBackendAPIException(data=msg)
665        array = arrays.pop()
666        intervals_retries_dict = {INTERVAL: interval, RETRIES: retries}
667        return array, intervals_retries_dict
668
669    def update_volume_group_name(self, group):
670        """Format id and name consistency group.
671
672        :param group: the generic volume group object
673        :returns: group_name -- formatted name + id
674        """
675        group_name = ""
676        if group.name is not None and group.name != group.id:
677            group_name = (
678                self.truncate_string(
679                    group.name, TRUNCATE_27) + "_")
680
681        group_name += group.id
682        return group_name
683
684    @staticmethod
685    def add_legacy_pools(pools):
686        """Add legacy pools to allow extending a volume after upgrade.
687
688        :param pools: the pool list
689        :return: pools - the updated pool list
690        """
691        extra_pools = []
692        for pool in pools:
693            if 'none' in pool['pool_name'].lower():
694                extra_pools.append(pool)
695        for pool in extra_pools:
696            try:
697                slo = pool['pool_name'].split('+')[0]
698                srp = pool['pool_name'].split('+')[2]
699                array = pool['pool_name'].split('+')[3]
700            except IndexError:
701                slo = pool['pool_name'].split('+')[0]
702                srp = pool['pool_name'].split('+')[1]
703                array = pool['pool_name'].split('+')[2]
704            new_pool_name = ('%(slo)s+%(srp)s+%(array)s'
705                             % {'slo': slo, 'srp': srp, 'array': array})
706            new_pool = deepcopy(pool)
707            new_pool['pool_name'] = new_pool_name
708            pools.append(new_pool)
709        return pools
710
711    def check_replication_matched(self, volume, extra_specs):
712        """Check volume type and group type.
713
714        This will make sure they do not conflict with each other.
715
716        :param volume: volume to be checked
717        :param extra_specs: the extra specifications
718        :raises: InvalidInput
719        """
720        # If volume is not a member of group, skip this check anyway.
721        if not volume.group:
722            return
723        vol_is_re = self.is_replication_enabled(extra_specs)
724        group_is_re = volume.group.is_replicated
725
726        if not (vol_is_re == group_is_re):
727            msg = _('Replication should be enabled or disabled for both '
728                    'volume or group. Volume replication status: '
729                    '%(vol_status)s, group replication status: '
730                    '%(group_status)s') % {
731                        'vol_status': vol_is_re, 'group_status': group_is_re}
732            raise exception.InvalidInput(reason=msg)
733
734    @staticmethod
735    def check_rep_status_enabled(group):
736        """Check replication status for group.
737
738        Group status must be enabled before proceeding with certain
739        operations.
740
741        :param group: the group object
742        :raises: InvalidInput
743        """
744        if group.is_replicated:
745            if group.replication_status != fields.ReplicationStatus.ENABLED:
746                msg = (_('Replication status should be %s for '
747                         'replication-enabled group.')
748                       % fields.ReplicationStatus.ENABLED)
749                LOG.error(msg)
750                raise exception.InvalidInput(reason=msg)
751        else:
752            LOG.debug('Replication is not enabled on group %s, '
753                      'skip status check.', group.id)
754
755    @staticmethod
756    def get_replication_prefix(rep_mode):
757        """Get the replication prefix.
758
759        Replication prefix for storage group naming is based on whether it is
760        synchronous, asynchronous, or metro replication mode.
761
762        :param rep_mode: flag to indicate if replication is async
763        :return: prefix
764        """
765        if rep_mode == REP_ASYNC:
766            prefix = "-RA"
767        elif rep_mode == REP_METRO:
768            prefix = "-RM"
769        else:
770            prefix = "-RE"
771        return prefix
772
773    @staticmethod
774    def get_async_rdf_managed_grp_name(rep_config):
775        """Get the name of the group used for async replication management.
776
777        :param rep_config: the replication configuration
778        :return: group name
779        """
780        async_grp_name = ("OS-%(rdf)s-%(mode)s-rdf-sg"
781                          % {'rdf': rep_config['rdf_group_label'],
782                             'mode': rep_config['mode']})
783        LOG.debug("The async/ metro rdf managed group name is %(name)s",
784                  {'name': async_grp_name})
785        return async_grp_name
786
787    def is_metro_device(self, rep_config, extra_specs):
788        """Determine if a volume is a Metro enabled device.
789
790        :param rep_config: the replication configuration
791        :param extra_specs: the extra specifications
792        :return: bool
793        """
794        is_metro = (True if self.is_replication_enabled(extra_specs)
795                    and rep_config is not None
796                    and rep_config['mode'] == REP_METRO else False)
797        return is_metro
798
799    def does_vol_need_rdf_management_group(self, extra_specs):
800        """Determine if a volume is a Metro or Async.
801
802        :param extra_specs: the extra specifications
803        :return: bool
804        """
805        if (self.is_replication_enabled(extra_specs) and
806                extra_specs.get(REP_MODE, None) in
807                [REP_ASYNC, REP_METRO]):
808            return True
809        return False
810