VMAX driver - Replication, replacing SMI-S with REST

In VMAX driver version 3.0, SMI-S has been replaced with unisphere
REST. This is porting Replication V2.1 from SMIS to REST.
See original https://review.openstack.org/#/c/409079/ for more
details

Change-Id: I9cb8d931bd40cb34429f228f1723bb162a75443f
Partially-Implements: blueprint vmax-rest
This commit is contained in:
Helen Walsh 2017-05-18 16:45:46 +01:00
parent 431d356c8a
commit 22eb9b69c1
9 changed files with 2136 additions and 334 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -79,6 +79,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
- Retype (storage-assisted migration) - Retype (storage-assisted migration)
- QoS support - QoS support
- Support for compression on All Flash - Support for compression on All Flash
- Support for volume replication
""" """
VERSION = "3.0.0" VERSION = "3.0.0"
@ -89,10 +90,12 @@ class VMAXFCDriver(driver.FibreChannelDriver):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(VMAXFCDriver, self).__init__(*args, **kwargs) super(VMAXFCDriver, self).__init__(*args, **kwargs)
self.active_backend_id = kwargs.get('active_backend_id', None)
self.common = common.VMAXCommon( self.common = common.VMAXCommon(
'FC', 'FC',
self.VERSION, self.VERSION,
configuration=self.configuration) configuration=self.configuration,
active_backend_id=self.active_backend_id)
self.zonemanager_lookup_service = fczm_utils.create_lookup_service() self.zonemanager_lookup_service = fczm_utils.create_lookup_service()
def check_for_setup_error(self): def check_for_setup_error(self):
@ -102,7 +105,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
"""Creates a VMAX volume. """Creates a VMAX volume.
:param volume: the cinder volume object :param volume: the cinder volume object
:return: provider location dict :returns: provider location dict
""" """
return self.common.create_volume(volume) return self.common.create_volume(volume)
@ -111,7 +114,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
:param volume: the cinder volume object :param volume: the cinder volume object
:param snapshot: the cinder snapshot object :param snapshot: the cinder snapshot object
:return: provider location dict :returns: provider location dict
""" """
return self.common.create_volume_from_snapshot( return self.common.create_volume_from_snapshot(
volume, snapshot) volume, snapshot)
@ -121,7 +124,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
:param volume: the cinder volume object :param volume: the cinder volume object
:param src_vref: the source volume reference :param src_vref: the source volume reference
:return: provider location dict :returns: provider location dict
""" """
return self.common.create_cloned_volume(volume, src_vref) return self.common.create_cloned_volume(volume, src_vref)
@ -136,7 +139,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
"""Creates a snapshot. """Creates a snapshot.
:param snapshot: the cinder snapshot object :param snapshot: the cinder snapshot object
:return: provider location dict :returns: provider location dict
""" """
src_volume = snapshot.volume src_volume = snapshot.volume
return self.common.create_snapshot(snapshot, src_volume) return self.common.create_snapshot(snapshot, src_volume)
@ -215,7 +218,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
} }
:param volume: the cinder volume object :param volume: the cinder volume object
:param connector: the connector object :param connector: the connector object
:return: dict -- the target_wwns and initiator_target_map :returns: dict -- the target_wwns and initiator_target_map
""" """
device_info = self.common.initialize_connection( device_info = self.common.initialize_connection(
volume, connector) volume, connector)
@ -349,7 +352,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
:param volume: the cinder volume object :param volume: the cinder volume object
:param connector: the connector object :param connector: the connector object
:return: target_wwns -- list, init_targ_map -- dict :returns: target_wwns -- list, init_targ_map -- dict
""" """
target_wwns, init_targ_map = [], {} target_wwns, init_targ_map = [], {}
initiator_wwns = connector['wwpns'] initiator_wwns = connector['wwpns']
@ -406,7 +409,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
Also need to consider things like QoS, Emulation, account/tenant. Also need to consider things like QoS, Emulation, account/tenant.
:param volume: the volume object :param volume: the volume object
:param external_ref: the reference for the VMAX volume :param external_ref: the reference for the VMAX volume
:return: model_update :returns: model_update
""" """
return self.common.manage_existing(volume, external_ref) return self.common.manage_existing(volume, external_ref)
@ -440,3 +443,15 @@ class VMAXFCDriver(driver.FibreChannelDriver):
:returns: boolean -- True if retype succeeded, False if error :returns: boolean -- True if retype succeeded, False if error
""" """
return self.common.retype(volume, new_type, host) return self.common.retype(volume, new_type, host)
def failover_host(self, context, volumes, secondary_id=None, groups=None):
"""Failover volumes to a secondary host/ backend.
:param context: the context
:param volumes: the list of volumes to be failed over
:param secondary_id: the backend to be failed over to, is 'default'
if fail back
:param groups: replication groups
:returns: secondary_id, volume_update_list, group_update_list
"""
return self.common.failover_host(volumes, secondary_id, groups)

View File

@ -84,6 +84,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
- Retype (storage-assisted migration) - Retype (storage-assisted migration)
- QoS support - QoS support
- Support for compression on All Flash - Support for compression on All Flash
- Support for volume replication
""" """
VERSION = "3.0.0" VERSION = "3.0.0"
@ -94,11 +95,13 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(VMAXISCSIDriver, self).__init__(*args, **kwargs) super(VMAXISCSIDriver, self).__init__(*args, **kwargs)
self.active_backend_id = kwargs.get('active_backend_id', None)
self.common = ( self.common = (
common.VMAXCommon( common.VMAXCommon(
'iSCSI', 'iSCSI',
self.VERSION, self.VERSION,
configuration=self.configuration)) configuration=self.configuration,
active_backend_id=self.active_backend_id))
def check_for_setup_error(self): def check_for_setup_error(self):
pass pass
@ -107,7 +110,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
"""Creates a VMAX volume. """Creates a VMAX volume.
:param volume: the cinder volume object :param volume: the cinder volume object
:return: provider location dict :returns: provider location dict
""" """
return self.common.create_volume(volume) return self.common.create_volume(volume)
@ -116,7 +119,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
:param volume: the cinder volume object :param volume: the cinder volume object
:param snapshot: the cinder snapshot object :param snapshot: the cinder snapshot object
:return: provider location dict :returns: provider location dict
""" """
return self.common.create_volume_from_snapshot( return self.common.create_volume_from_snapshot(
volume, snapshot) volume, snapshot)
@ -126,7 +129,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
:param volume: the cinder volume object :param volume: the cinder volume object
:param src_vref: the source volume reference :param src_vref: the source volume reference
:return: provider location dict :returns: provider location dict
""" """
return self.common.create_cloned_volume(volume, src_vref) return self.common.create_cloned_volume(volume, src_vref)
@ -141,7 +144,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
"""Creates a snapshot. """Creates a snapshot.
:param snapshot: the cinder snapshot object :param snapshot: the cinder snapshot object
:return: provider location dict :returns: provider location dict
""" """
src_volume = snapshot.volume src_volume = snapshot.volume
return self.common.create_snapshot(snapshot, src_volume) return self.common.create_snapshot(snapshot, src_volume)
@ -220,7 +223,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
} }
:param volume: the cinder volume object :param volume: the cinder volume object
:param connector: the connector object :param connector: the connector object
:return: dict -- the iscsi dict :returns: dict -- the iscsi dict
""" """
device_info = self.common.initialize_connection( device_info = self.common.initialize_connection(
volume, connector) volume, connector)
@ -231,7 +234,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
:param device_info: device info dict :param device_info: device info dict
:param volume: volume object :param volume: volume object
:return: iscsi dict :returns: iscsi dict
""" """
try: try:
ip_and_iqn = device_info['ip_and_iqn'] ip_and_iqn = device_info['ip_and_iqn']
@ -273,7 +276,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
:param ip_and_iqn: list of ip and iqn dicts :param ip_and_iqn: list of ip and iqn dicts
:param is_multipath: flag for multipath :param is_multipath: flag for multipath
:param host_lun_id: the host lun id of the device :param host_lun_id: the host lun id of the device
:return: properties :returns: properties
""" """
properties = {} properties = {}
if len(ip_and_iqn) > 1 and is_multipath: if len(ip_and_iqn) > 1 and is_multipath:
@ -384,3 +387,15 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
:returns: boolean -- True if retype succeeded, False if error :returns: boolean -- True if retype succeeded, False if error
""" """
return self.common.retype(volume, new_type, host) return self.common.retype(volume, new_type, host)
def failover_host(self, context, volumes, secondary_id=None, groups=None):
"""Failover volumes to a secondary host/ backend.
:param context: the context
:param volumes: the list of volumes to be failed over
:param secondary_id: the backend to be failed over to, is 'default'
if fail back
:param groups: replication groups
:returns: secondary_id, volume_update_list, group_update_list
"""
return self.common.failover_host(volumes, secondary_id, groups)

View File

@ -142,14 +142,15 @@ class VMAXMasking(object):
masking_view_dict[utils.SRP], masking_view_dict[utils.SRP],
masking_view_dict[utils.SLO], masking_view_dict[utils.SLO],
masking_view_dict[utils.WORKLOAD], masking_view_dict[utils.WORKLOAD],
masking_view_dict[utils.DISABLECOMPRESSION]) masking_view_dict[utils.DISABLECOMPRESSION],
masking_view_dict[utils.IS_RE])
check_vol = self.rest.is_volume_in_storagegroup( check_vol = self.rest.is_volume_in_storagegroup(
serial_number, device_id, default_sg_name) serial_number, device_id, default_sg_name)
if check_vol: if check_vol:
self.remove_volume_from_sg( self.remove_vol_from_storage_group(
serial_number, device_id, volume_name, default_sg_name, serial_number, device_id, default_sg_name,
extra_specs) volume_name, extra_specs)
else: else:
LOG.warning( LOG.warning(
"Volume: %(volume_name)s does not belong " "Volume: %(volume_name)s does not belong "
@ -587,7 +588,7 @@ class VMAXMasking(object):
LOG.info("Added volume: %(vol_name)s to storage group %(sg_name)s.", LOG.info("Added volume: %(vol_name)s to storage group %(sg_name)s.",
{'vol_name': volume_name, 'sg_name': storagegroup_name}) {'vol_name': volume_name, 'sg_name': storagegroup_name})
def _remove_vol_from_storage_group( def remove_vol_from_storage_group(
self, serial_number, device_id, storagegroup_name, self, serial_number, device_id, storagegroup_name,
volume_name, extra_specs): volume_name, extra_specs):
"""Remove a volume from a storage group. """Remove a volume from a storage group.
@ -890,7 +891,7 @@ class VMAXMasking(object):
self._cleanup_deletion( self._cleanup_deletion(
serial_number, device_id, volume_name, extra_specs) serial_number, device_id, volume_name, extra_specs)
if reset: if reset:
self.return_volume_to_default_storage_group( self.add_volume_to_default_storage_group(
serial_number, device_id, volume_name, extra_specs) serial_number, device_id, volume_name, extra_specs)
def _cleanup_deletion( def _cleanup_deletion(
@ -1139,7 +1140,7 @@ class VMAXMasking(object):
:param storagegroup_name: storage group name :param storagegroup_name: storage group name
:param extra_specs: extra specifications :param extra_specs: extra specifications
""" """
self._remove_vol_from_storage_group( self.remove_vol_from_storage_group(
serial_number, device_id, storagegroup_name, serial_number, device_id, storagegroup_name,
volume_name, extra_specs) volume_name, extra_specs)
@ -1204,7 +1205,7 @@ class VMAXMasking(object):
LOG.info("Masking view %(maskingview)s successfully deleted.", LOG.info("Masking view %(maskingview)s successfully deleted.",
{'maskingview': masking_view}) {'maskingview': masking_view})
def return_volume_to_default_storage_group( def add_volume_to_default_storage_group(
self, serial_number, device_id, volume_name, extra_specs): self, serial_number, device_id, volume_name, extra_specs):
"""Return volume to its default storage group. """Return volume to its default storage group.
@ -1215,9 +1216,11 @@ class VMAXMasking(object):
""" """
do_disable_compression = self.utils.is_compression_disabled( do_disable_compression = self.utils.is_compression_disabled(
extra_specs) extra_specs)
rep_enabled = self.utils.is_replication_enabled(extra_specs)
storagegroup_name = self.get_or_create_default_storage_group( storagegroup_name = self.get_or_create_default_storage_group(
serial_number, extra_specs[utils.SRP], extra_specs[utils.SLO], serial_number, extra_specs[utils.SRP], extra_specs[utils.SLO],
extra_specs[utils.WORKLOAD], extra_specs, do_disable_compression) extra_specs[utils.WORKLOAD], extra_specs, do_disable_compression,
rep_enabled)
self._check_adding_volume_to_storage_group( self._check_adding_volume_to_storage_group(
serial_number, device_id, storagegroup_name, volume_name, serial_number, device_id, storagegroup_name, volume_name,
@ -1225,7 +1228,7 @@ class VMAXMasking(object):
def get_or_create_default_storage_group( def get_or_create_default_storage_group(
self, serial_number, srp, slo, workload, extra_specs, self, serial_number, srp, slo, workload, extra_specs,
do_disable_compression=False): do_disable_compression=False, is_re=False):
"""Get or create a default storage group. """Get or create a default storage group.
:param serial_number: the array serial number :param serial_number: the array serial number
@ -1234,12 +1237,14 @@ class VMAXMasking(object):
:param workload: the workload :param workload: the workload
:param extra_specs: extra specifications :param extra_specs: extra specifications
:param do_disable_compression: flag for compression :param do_disable_compression: flag for compression
:param is_re: is replication enabled
:returns: storagegroup_name :returns: storagegroup_name
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
storagegroup, storagegroup_name = ( storagegroup, storagegroup_name = (
self.rest.get_vmax_default_storage_group( self.rest.get_vmax_default_storage_group(
serial_number, srp, slo, workload, do_disable_compression)) serial_number, srp, slo, workload, do_disable_compression,
is_re))
if storagegroup is None: if storagegroup is None:
self.provision.create_storage_group( self.provision.create_storage_group(
serial_number, storagegroup_name, srp, slo, workload, serial_number, storagegroup_name, srp, slo, workload,
@ -1278,7 +1283,7 @@ class VMAXMasking(object):
:param extra_specs: extra specifications :param extra_specs: extra specifications
:param parent_sg_name: the parent sg name :param parent_sg_name: the parent sg name
""" """
self._remove_vol_from_storage_group( self.remove_vol_from_storage_group(
serial_number, device_id, storagegroup_name, volume_name, serial_number, device_id, storagegroup_name, volume_name,
extra_specs) extra_specs)

View File

@ -24,6 +24,8 @@ from cinder.volume.drivers.dell_emc.vmax import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
WRITE_DISABLED = "Write Disabled"
class VMAXProvision(object): class VMAXProvision(object):
"""Provisioning Class for Dell EMC VMAX volume drivers. """Provisioning Class for Dell EMC VMAX volume drivers.
@ -238,7 +240,7 @@ class VMAXProvision(object):
:param device_id: the volume device id :param device_id: the volume device id
:param new_size: the new size (GB) :param new_size: the new size (GB)
:param extra_specs: the extra specifications :param extra_specs: the extra specifications
:return: status_code :returns: status_code
""" """
start_time = time.time() start_time = time.time()
self.rest.extend_volume(array, device_id, new_size, extra_specs) self.rest.extend_volume(array, device_id, new_size, extra_specs)
@ -310,7 +312,7 @@ class VMAXProvision(object):
:param array: the array serial number :param array: the array serial number
:param srp: the srp name :param srp: the srp name
:param array_info: array info dict :param array_info: array info dict
:return: remaining_capacity :returns: remaining_capacity
""" """
remaining_capacity = -1 remaining_capacity = -1
if array_info['SLO']: if array_info['SLO']:
@ -336,13 +338,15 @@ class VMAXProvision(object):
""" """
is_valid_slo, is_valid_workload = False, False is_valid_slo, is_valid_workload = False, False
if workload: if workload and workload.lower() == 'none':
if workload.lower() == 'none': workload = None
workload = None
if not workload: if not workload:
is_valid_workload = True is_valid_workload = True
if slo and slo.lower() == 'none':
slo = None
valid_slos = self.rest.get_slo_list(array) valid_slos = self.rest.get_slo_list(array)
valid_workloads = self.rest.get_workload_settings(array) valid_workloads = self.rest.get_workload_settings(array)
for valid_slo in valid_slos: for valid_slo in valid_slos:
@ -380,7 +384,7 @@ class VMAXProvision(object):
:param array: the array serial number :param array: the array serial number
:param sg_name: the storage group name :param sg_name: the storage group name
:return: storage group slo settings :returns: storage group slo settings
""" """
slo = 'NONE' slo = 'NONE'
workload = 'NONE' workload = 'NONE'
@ -398,3 +402,49 @@ class VMAXProvision(object):
LOG.error(exception_message) LOG.error(exception_message)
raise exception.VolumeBackendAPIException(data=exception_message) raise exception.VolumeBackendAPIException(data=exception_message)
return '%(slo)s+%(workload)s' % {'slo': slo, 'workload': workload} return '%(slo)s+%(workload)s' % {'slo': slo, 'workload': workload}
def break_rdf_relationship(self, array, device_id, target_device,
rdf_group, rep_extra_specs, state):
"""Break the rdf relationship between a pair of devices.
:param array: the array serial number
:param device_id: the source device id
:param target_device: target device id
:param rdf_group: the rdf group number
:param rep_extra_specs: replication extra specs
:param state: the state of the rdf pair
"""
LOG.info("Splitting rdf pair: source device: %(src)s "
"target device: %(tgt)s.",
{'src': device_id, 'tgt': target_device})
if state == 'Synchronized':
self.rest.modify_rdf_device_pair(
array, device_id, rdf_group, rep_extra_specs, split=True)
LOG.info("Deleting rdf pair: source device: %(src)s "
"target device: %(tgt)s.",
{'src': device_id, 'tgt': target_device})
self.rest.delete_rdf_pair(array, device_id, rdf_group)
def failover_volume(self, array, device_id, rdf_group,
extra_specs, local_vol_state, failover):
"""Failover or back a volume pair.
:param array: the array serial number
:param device_id: the source device id
:param rdf_group: the rdf group number
:param extra_specs: extra specs
:param local_vol_state: the local volume state
:param failover: flag to indicate failover or failback -- bool
"""
if local_vol_state == WRITE_DISABLED:
LOG.info("Volume %(dev)s is already failed over.",
{'dev': device_id})
return
if failover:
action = "Failing over"
else:
action = "Failing back"
LOG.info("%(action)s rdf pair: source device: %(src)s ",
{'action': action, 'src': device_id})
self.rest.modify_rdf_device_pair(
array, device_id, rdf_group, extra_specs, split=False)

View File

@ -106,7 +106,7 @@ class VMAXRest(object):
:param method: The method (GET, POST, PUT, or DELETE) :param method: The method (GET, POST, PUT, or DELETE)
:param params: Additional URL parameters :param params: Additional URL parameters
:param request_object: request payload (dict) :param request_object: request payload (dict)
:return: server response object (dict) :returns: server response object (dict)
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
message, status_code = None, None message, status_code = None, None
@ -158,8 +158,8 @@ class VMAXRest(object):
:param job: the job dict :param job: the job dict
:param extra_specs: the extra_specs dict. :param extra_specs: the extra_specs dict.
:return rc -- int, result -- string, status -- string, :returns: rc -- int, result -- string, status -- string,
task -- list of dicts detailing tasks in the job task -- list of dicts detailing tasks in the job
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
res, tasks = None, None res, tasks = None, None
@ -259,7 +259,7 @@ class VMAXRest(object):
:param status_code: the status code :param status_code: the status code
:param job: the job :param job: the job
:param extra_specs: the extra specifications :param extra_specs: the extra specifications
:return: task -- list of dicts detailing tasks in the job :returns: task -- list of dicts detailing tasks in the job
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
task = None task = None
@ -288,7 +288,7 @@ class VMAXRest(object):
:param resource_type: the resource type e.g. maskingview :param resource_type: the resource type e.g. maskingview
:param resource_name: the name of a specific resource :param resource_name: the name of a specific resource
:param private: empty string or '/private' if private url :param private: empty string or '/private' if private url
:return: target url, string :returns: target url, string
""" """
target_uri = ('%(private)s/%(version)s/%(category)s/symmetrix/' target_uri = ('%(private)s/%(version)s/%(category)s/symmetrix/'
'%(array)s/%(resource_type)s' '%(array)s/%(resource_type)s'
@ -306,7 +306,7 @@ class VMAXRest(object):
:param target_uri: the target uri :param target_uri: the target uri
:param resource_type: the resource type, e.g. maskingview :param resource_type: the resource type, e.g. maskingview
:param params: optional dict of filter params :param params: optional dict of filter params
:return: resource_object -- dict or None :returns: resource_object -- dict or None
""" """
resource_object = None resource_object = None
sc, message = self.request(target_uri, GET, params=params) sc, message = self.request(target_uri, GET, params=params)
@ -330,7 +330,7 @@ class VMAXRest(object):
:param resource_name: the name of a specific resource :param resource_name: the name of a specific resource
:param params: query parameters :param params: query parameters
:param private: empty string or '/private' if private url :param private: empty string or '/private' if private url
:return: resource object -- dict or None :returns: resource object -- dict or None
""" """
target_uri = self._build_uri(array, category, resource_type, target_uri = self._build_uri(array, category, resource_type,
resource_name, private) resource_name, private)
@ -345,7 +345,7 @@ class VMAXRest(object):
:param resource_type: the resource type :param resource_type: the resource type
:param payload: the payload :param payload: the payload
:param private: empty string or '/private' if private url :param private: empty string or '/private' if private url
:return: status_code -- int, message -- string, server response :returns: status_code -- int, message -- string, server response
""" """
target_uri = self._build_uri(array, category, resource_type, target_uri = self._build_uri(array, category, resource_type,
None, private) None, private)
@ -366,7 +366,7 @@ class VMAXRest(object):
:param payload: the payload :param payload: the payload
:param resource_name: the resource name :param resource_name: the resource name
:param private: empty string or '/private' if private url :param private: empty string or '/private' if private url
:return: status_code -- int, message -- string (server response) :returns: status_code -- int, message -- string (server response)
""" """
target_uri = self._build_uri(array, category, resource_type, target_uri = self._build_uri(array, category, resource_type,
resource_name, private) resource_name, private)
@ -378,7 +378,7 @@ class VMAXRest(object):
def delete_resource( def delete_resource(
self, array, category, resource_type, resource_name, self, array, category, resource_type, resource_name,
payload=None, private=''): payload=None, private='', params=None):
"""Delete a provisioning resource. """Delete a provisioning resource.
:param array: the array serial number :param array: the array serial number
@ -387,11 +387,13 @@ class VMAXRest(object):
:param resource_name: the name of the resource to be deleted :param resource_name: the name of the resource to be deleted
:param payload: the payload, optional :param payload: the payload, optional
:param private: empty string or '/private' if private url :param private: empty string or '/private' if private url
:param params: dict of optional query params
""" """
target_uri = self._build_uri(array, category, resource_type, target_uri = self._build_uri(array, category, resource_type,
resource_name, private) resource_name, private)
status_code, message = self.request(target_uri, DELETE, status_code, message = self.request(target_uri, DELETE,
request_object=payload) request_object=payload,
params=params)
operation = 'delete %(res)s resource' % {'res': resource_type} operation = 'delete %(res)s resource' % {'res': resource_type}
self.check_status_code_success(operation, status_code, message) self.check_status_code_success(operation, status_code, message)
@ -399,7 +401,7 @@ class VMAXRest(object):
"""Get an array from its serial number. """Get an array from its serial number.
:param array: the array serial number :param array: the array serial number
:return: array_details -- dict or None :returns: array_details -- dict or None
""" """
target_uri = '/%s/system/symmetrix/%s' % (U4V_VERSION, array) target_uri = '/%s/system/symmetrix/%s' % (U4V_VERSION, array)
array_details = self._get_request(target_uri, 'system') array_details = self._get_request(target_uri, 'system')
@ -422,14 +424,14 @@ class VMAXRest(object):
return srp_details return srp_details
def get_slo_list(self, array): def get_slo_list(self, array):
"""Returns the list of service levels associated with an srp. """Retrieve the list of slo's from the array
:param array: the array serial number :param array: the array serial number
:return slo_list -- list of service level names :returns: slo_list -- list of service level names
""" """
slo_list = [] slo_list = []
slo_dict = self.get_resource(array, SLOPROVISIONING, 'slo') slo_dict = self.get_resource(array, SLOPROVISIONING, 'slo')
if slo_dict: if slo_dict and slo_dict.get('sloId'):
slo_list = slo_dict['sloId'] slo_list = slo_dict['sloId']
return slo_list return slo_list
@ -437,7 +439,7 @@ class VMAXRest(object):
"""Get valid workload options from array. """Get valid workload options from array.
:param array: the array serial number :param array: the array serial number
:return: workload_setting -- list of workload names :returns: workload_setting -- list of workload names
""" """
workload_setting = [] workload_setting = []
wl_details = self.get_resource(array, SLOPROVISIONING, 'workloadtype') wl_details = self.get_resource(array, SLOPROVISIONING, 'workloadtype')
@ -452,7 +454,7 @@ class VMAXRest(object):
:param srp: the storage resource srp :param srp: the storage resource srp
:param slo: the service level :param slo: the service level
:param workload: the workload :param workload: the workload
:return remaining_capacity -- string, or None :returns: remaining_capacity -- string, or None
""" """
params = {'srp': srp, 'slo': slo, 'workloadtype': workload} params = {'srp': srp, 'slo': slo, 'workloadtype': workload}
try: try:
@ -484,7 +486,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param storage_group_name: the name of the storage group :param storage_group_name: the name of the storage group
:return: storage group dict or None :returns: storage group dict or None
""" """
return self.get_resource( return self.get_resource(
array, SLOPROVISIONING, 'storagegroup', array, SLOPROVISIONING, 'storagegroup',
@ -495,7 +497,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param params: optional filter parameters :param params: optional filter parameters
:return: storage group list :returns: storage group list
""" """
sg_list = [] sg_list = []
sg_details = self.get_resource(array, SLOPROVISIONING, sg_details = self.get_resource(array, SLOPROVISIONING,
@ -509,7 +511,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param storage_group_name: the storage group name :param storage_group_name: the storage group name
:return: num_vols -- int :returns: num_vols -- int
""" """
num_vols = 0 num_vols = 0
storagegroup = self.get_storage_group(array, storage_group_name) storagegroup = self.get_storage_group(array, storage_group_name)
@ -525,7 +527,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param child_name: the child sg name :param child_name: the child sg name
:param parent_name: the parent sg name :param parent_name: the parent sg name
:return: bool :returns: bool
""" """
parent_sg = self.get_storage_group(array, parent_name) parent_sg = self.get_storage_group(array, parent_name)
if parent_sg and parent_sg.get('child_storage_group'): if parent_sg and parent_sg.get('child_storage_group'):
@ -575,7 +577,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param payload: the payload -- dict :param payload: the payload -- dict
:return: status_code -- int, message -- string, server response :returns: status_code -- int, message -- string, server response
""" """
return self.create_resource( return self.create_resource(
array, SLOPROVISIONING, 'storagegroup', payload) array, SLOPROVISIONING, 'storagegroup', payload)
@ -625,7 +627,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param storagegroup: storage group name :param storagegroup: storage group name
:param payload: the request payload :param payload: the request payload
:return: status_code -- int, message -- string, server response :returns: status_code -- int, message -- string, server response
""" """
return self.modify_resource( return self.modify_resource(
array, SLOPROVISIONING, 'storagegroup', payload, array, SLOPROVISIONING, 'storagegroup', payload,
@ -806,8 +808,9 @@ class VMAXRest(object):
return_value = False return_value = False
return return_value return return_value
def get_vmax_default_storage_group(self, array, srp, slo, workload, def get_vmax_default_storage_group(
do_disable_compression=False): self, array, srp, slo, workload,
do_disable_compression=False, is_re=False):
"""Get the default storage group. """Get the default storage group.
:param array: the array serial number :param array: the array serial number
@ -815,10 +818,11 @@ class VMAXRest(object):
:param slo: the SLO :param slo: the SLO
:param workload: the workload :param workload: the workload
:param do_disable_compression: flag for disabling compression :param do_disable_compression: flag for disabling compression
:param is_re: flag for replication
:returns: the storage group dict (or None), the storage group name :returns: the storage group dict (or None), the storage group name
""" """
storagegroup_name = self.utils.get_default_storage_group_name( storagegroup_name = self.utils.get_default_storage_group_name(
srp, slo, workload) srp, slo, workload, do_disable_compression, is_re)
storagegroup = self.get_storage_group(array, storagegroup_name) storagegroup = self.get_storage_group(array, storagegroup_name)
return storagegroup, storagegroup_name return storagegroup, storagegroup_name
@ -837,7 +841,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param device_id: the volume device id :param device_id: the volume device id
:return: volume dict :returns: volume dict
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
volume_dict = self.get_resource( volume_dict = self.get_resource(
@ -854,7 +858,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param device_id: the volume device id :param device_id: the volume device id
:return: volume dict :returns: volume dict
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
try: try:
@ -864,7 +868,7 @@ class VMAXRest(object):
array, SLOPROVISIONING, 'volume', params=params, array, SLOPROVISIONING, 'volume', params=params,
private='/private') private='/private')
volume_dict = volume_info['resultList']['result'][0] volume_dict = volume_info['resultList']['result'][0]
except KeyError: except (KeyError, TypeError):
exception_message = (_("Volume %(deviceID)s not found.") exception_message = (_("Volume %(deviceID)s not found.")
% {'deviceID': device_id}) % {'deviceID': device_id})
LOG.error(exception_message) LOG.error(exception_message)
@ -878,7 +882,7 @@ class VMAXRest(object):
very large and could affect performance if called often. very large and could affect performance if called often.
:param array: the array serial number :param array: the array serial number
:param params: filter parameters :param params: filter parameters
:return: device_ids -- list :returns: device_ids -- list
""" """
device_ids = [] device_ids = []
volumes = self.get_resource( volumes = self.get_resource(
@ -960,7 +964,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param maskingview: the masking view name :param maskingview: the masking view name
:param device_id: the device ID :param device_id: the device ID
:return: host_lun_id -- int :returns: host_lun_id -- int
""" """
host_lun_id = None host_lun_id = None
resource_name = ('%(maskingview)s/connections' resource_name = ('%(maskingview)s/connections'
@ -990,7 +994,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param device_id: the volume device id :param device_id: the volume device id
:return: storagegroup_list :returns: storagegroup_list
""" """
sg_list = [] sg_list = []
vol = self.get_volume(array, device_id) vol = self.get_volume(array, device_id)
@ -1008,7 +1012,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param device_id: the device id :param device_id: the device id
:param storagegroup: the storage group name :param storagegroup: the storage group name
:return: bool :returns: bool
""" """
is_vol_in_sg = False is_vol_in_sg = False
sg_list = self.get_storage_groups_from_volume(array, device_id) sg_list = self.get_storage_groups_from_volume(array, device_id)
@ -1021,7 +1025,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param volume_name: the volume name (OS-<UUID>) :param volume_name: the volume name (OS-<UUID>)
:return: device_id :returns: device_id
""" """
device_id = None device_id = None
params = {"volume_identifier": volume_name} params = {"volume_identifier": volume_name}
@ -1039,7 +1043,7 @@ class VMAXRest(object):
:param array: array serial number :param array: array serial number
:param device_id: the device id :param device_id: the device id
:return: the volume identifier -- string :returns: the volume identifier -- string
""" """
vol = self.get_volume(array, device_id) vol = self.get_volume(array, device_id)
return vol['volume_identifier'] return vol['volume_identifier']
@ -1049,7 +1053,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param device_id: the volume device id :param device_id: the volume device id
:return: size -- or None :returns: size -- or None
""" """
cap = None cap = None
try: try:
@ -1066,7 +1070,7 @@ class VMAXRest(object):
:param array: array serial number :param array: array serial number
:param portgroup: the portgroup name :param portgroup: the portgroup name
:return: portgroup dict or None :returns: portgroup dict or None
""" """
return self.get_resource( return self.get_resource(
array, SLOPROVISIONING, 'portgroup', resource_name=portgroup) array, SLOPROVISIONING, 'portgroup', resource_name=portgroup)
@ -1076,7 +1080,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param portgroup: the name of the portgroup :param portgroup: the name of the portgroup
:return: list of port ids, e.g. ['FA-3D:35', 'FA-4D:32'] :returns: list of port ids, e.g. ['FA-3D:35', 'FA-4D:32']
""" """
portlist = [] portlist = []
portgroup_info = self.get_portgroup(array, portgroup) portgroup_info = self.get_portgroup(array, portgroup)
@ -1092,7 +1096,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param port_id: the port id :param port_id: the port id
:return: port dict, or None :returns: port dict, or None
""" """
dir_id = port_id.split(':')[0] dir_id = port_id.split(':')[0]
port_no = port_id.split(':')[1] port_no = port_id.split(':')[1]
@ -1107,7 +1111,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param port_id: the director port identifier :param port_id: the director port identifier
:return: (list of ip_addresses, iqn) :returns: (list of ip_addresses, iqn)
""" """
ip_addresses, iqn = None, None ip_addresses, iqn = None, None
port_details = self.get_port(array, port_id) port_details = self.get_port(array, port_id)
@ -1142,7 +1146,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param initiator_group: the initaitor group name :param initiator_group: the initaitor group name
:param params: optional filter parameters :param params: optional filter parameters
:return: initiator group dict, or None :returns: initiator group dict, or None
""" """
return self.get_resource( return self.get_resource(
array, SLOPROVISIONING, 'host', array, SLOPROVISIONING, 'host',
@ -1153,7 +1157,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param initiator_id: the initiator id :param initiator_id: the initiator id
:return: initiator dict, or None :returns: initiator dict, or None
""" """
return self.get_resource( return self.get_resource(
array, SLOPROVISIONING, 'initiator', array, SLOPROVISIONING, 'initiator',
@ -1164,7 +1168,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param params: dict of optional params :param params: dict of optional params
:return: list of initiators :returns: list of initiators
""" """
init_dict = self.get_resource( init_dict = self.get_resource(
array, SLOPROVISIONING, 'initiator', params=params) array, SLOPROVISIONING, 'initiator', params=params)
@ -1180,7 +1184,7 @@ class VMAXRest(object):
Gets the list of initiators from the array which are in Gets the list of initiators from the array which are in
hosts/ initiator groups. hosts/ initiator groups.
:param array: the array serial number :param array: the array serial number
:return: init_list :returns: init_list
""" """
params = {'in_a_host': 'true'} params = {'in_a_host': 'true'}
return self.get_initiator_list(array, params) return self.get_initiator_list(array, params)
@ -1190,7 +1194,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param initiator: the initiator id :param initiator: the initiator id
:return: found_init_group_name -- string :returns: found_init_group_name -- string
""" """
found_init_group_name = None found_init_group_name = None
init_details = self.get_initiator(array, initiator) init_details = self.get_initiator(array, initiator)
@ -1231,7 +1235,7 @@ class VMAXRest(object):
:param array: array serial number :param array: array serial number
:param masking_view_name: the masking view name :param masking_view_name: the masking view name
:return: masking view dict :returns: masking view dict
""" """
return self.get_resource( return self.get_resource(
array, SLOPROVISIONING, 'maskingview', masking_view_name) array, SLOPROVISIONING, 'maskingview', masking_view_name)
@ -1241,7 +1245,7 @@ class VMAXRest(object):
:param array: array serial number :param array: array serial number
:param params: optional GET parameters :param params: optional GET parameters
:return: masking view list :returns: masking view list
""" """
masking_view_list = [] masking_view_list = []
masking_view_details = self.get_resource( masking_view_details = self.get_resource(
@ -1257,7 +1261,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param storagegroup: the storage group name :param storagegroup: the storage group name
:return: masking view list :returns: masking view list
""" """
maskingviewlist = [] maskingviewlist = []
storagegroup = self.get_storage_group(array, storagegroup) storagegroup = self.get_storage_group(array, storagegroup)
@ -1296,7 +1300,7 @@ class VMAXRest(object):
:param portgroup: the port group name - optional :param portgroup: the port group name - optional
:param host: the host name - optional :param host: the host name - optional
:param storagegroup: the storage group name - optional :param storagegroup: the storage group name - optional
:return: name of the specified element -- string :returns: name of the specified element -- string
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
element = None element = None
@ -1320,7 +1324,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param portgroup_name: the port group name :param portgroup_name: the port group name
:param ig_name: the initiator group name :param ig_name: the initiator group name
:return: masking view list :returns: masking view list
""" """
params = {'port_group_name': portgroup_name, params = {'port_group_name': portgroup_name,
'host_or_host_group_name': ig_name} 'host_or_host_group_name': ig_name}
@ -1473,7 +1477,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param source_device_id: the source volume device ID :param source_device_id: the source volume device ID
:return: message -- dict, or None :returns: message -- dict, or None
""" """
resource_name = ("%(device_id)s/snapshot" resource_name = ("%(device_id)s/snapshot"
% {'device_id': source_device_id}) % {'device_id': source_device_id})
@ -1486,7 +1490,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param device_id: the source volume device id :param device_id: the source volume device id
:param snap_name: the name of the snapshot :param snap_name: the name of the snapshot
:return: snapshot dict, or None :returns: snapshot dict, or None
""" """
snapshot = None snapshot = None
snap_info = self.get_volume_snap_info(array, device_id) snap_info = self.get_volume_snap_info(array, device_id)
@ -1503,7 +1507,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param source_device_id: the osurce device id :param source_device_id: the osurce device id
:return: snapshot list or None :returns: snapshot list or None
""" """
snapshot_list = [] snapshot_list = []
snap_info = self.get_volume_snap_info(array, source_device_id) snap_info = self.get_volume_snap_info(array, source_device_id)
@ -1517,7 +1521,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param device_id: the device id :param device_id: the device id
:return: snapvx_tgt -- bool, snapvx_src -- bool, :returns: snapvx_tgt -- bool, snapvx_src -- bool,
rdf_grp -- list or None rdf_grp -- list or None
""" """
snapvx_src = False snapvx_src = False
@ -1544,7 +1548,7 @@ class VMAXRest(object):
:param target_device_id: target device id :param target_device_id: target device id
:param snap_name: snapshot name :param snap_name: snapshot name
:param extra_specs: extra specifications :param extra_specs: extra specifications
:return: bool :returns: bool
""" """
def _wait_for_sync(): def _wait_for_sync():
@ -1579,8 +1583,7 @@ class VMAXRest(object):
kwargs = {'retries': 0, kwargs = {'retries': 0,
'wait_for_sync_called': False} 'wait_for_sync_called': False}
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_sync) timer = loopingcall.FixedIntervalLoopingCall(_wait_for_sync)
rc = timer.start(interval=int( rc = timer.start(interval=extra_specs[utils.INTERVAL]).wait()
extra_specs[utils.INTERVAL])).wait()
return rc return rc
def _is_sync_complete(self, array, source_device_id, snap_name, def _is_sync_complete(self, array, source_device_id, snap_name,
@ -1591,24 +1594,24 @@ class VMAXRest(object):
:param source_device_id: source device id :param source_device_id: source device id
:param snap_name: the snapshot name :param snap_name: the snapshot name
:param target_device_id: the target device id :param target_device_id: the target device id
:return: defined -- bool :returns: defined -- bool
""" """
defined = True defined = True
session = self._get_sync_session( session = self.get_sync_session(
array, source_device_id, snap_name, target_device_id) array, source_device_id, snap_name, target_device_id)
if session: if session:
defined = session['defined'] defined = session['defined']
return defined return defined
def _get_sync_session(self, array, source_device_id, snap_name, def get_sync_session(self, array, source_device_id, snap_name,
target_device_id): target_device_id):
"""Get a particular sync session. """Get a particular sync session.
:param array: the array serial number :param array: the array serial number
:param source_device_id: source device id :param source_device_id: source device id
:param snap_name: the snapshot name :param snap_name: the snapshot name
:param target_device_id: the target device id :param target_device_id: the target device id
:return: sync session -- dict, or None :returns: sync session -- dict, or None
""" """
session = None session = None
linked_device_list = self.get_snap_linked_device_list( linked_device_list = self.get_snap_linked_device_list(
@ -1623,7 +1626,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param source_device_id: the source device id :param source_device_id: the source device id
:return: list of snapshot dicts :returns: list of snapshot dicts
""" """
snap_dict_list = [] snap_dict_list = []
snapshots = self.get_volume_snapshot_list(array, source_device_id) snapshots = self.get_volume_snapshot_list(array, source_device_id)
@ -1640,7 +1643,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param source_device_id: source device id :param source_device_id: source device id
:param snap_name: the snapshot name :param snap_name: the snapshot name
:return: linked_device_list :returns: linked_device_list
""" """
linked_device_list = [] linked_device_list = []
snap_list = self._find_snap_vx_source_sessions(array, source_device_id) snap_list = self._find_snap_vx_source_sessions(array, source_device_id)
@ -1655,7 +1658,7 @@ class VMAXRest(object):
:param array: the array serial number :param array: the array serial number
:param device_id: the device id :param device_id: the device id
:param tgt_only: Flag - return only sessions where device is target :param tgt_only: Flag - return only sessions where device is target
:return: list of snapshot dicts :returns: list of snapshot dicts
""" """
snap_dict_list, sessions = [], [] snap_dict_list, sessions = [], []
vol_details = self._get_private_volume(array, device_id) vol_details = self._get_private_volume(array, device_id)
@ -1691,3 +1694,156 @@ class VMAXRest(object):
'source_vol': source_vol} 'source_vol': source_vol}
snap_dict_list.append(link_info) snap_dict_list.append(link_info)
return snap_dict_list return snap_dict_list
def get_rdf_group(self, array, rdf_number):
"""Get specific rdf group details.
:param array: the array serial number
:param rdf_number: the rdf number
"""
return self.get_resource(array, REPLICATION, 'rdf_group',
rdf_number)
def get_rdf_group_list(self, array):
"""Get rdf group list from array.
:param array: the array serial number
"""
return self.get_resource(array, REPLICATION, 'rdf_group')
def get_rdf_group_volume(self, array, rdf_number, device_id):
"""Get specific volume details, from an RDF group.
:param array: the array serial number
:param rdf_number: the rdf group number
:param device_id: the device id
"""
resource_name = "%(rdf)s/volume/%(dev)s" % {
'rdf': rdf_number, 'dev': device_id}
return self.get_resource(array, REPLICATION, 'rdf_group',
resource_name)
def are_vols_rdf_paired(self, array, remote_array, device_id,
target_device, rdf_group):
"""Check if a pair of volumes are RDF paired.
:param array: the array serial number
:param remote_array: the remote array serial number
:param device_id: the device id
:param target_device: the target device id
:param rdf_group: the rdf group
:returns: paired -- bool, state -- string
"""
paired, local_vol_state, rdf_pair_state = False, '', ''
volume = self.get_rdf_group_volume(array, rdf_group, device_id)
if volume:
remote_volume = volume['remoteVolumeName']
remote_symm = volume['remoteSymmetrixId']
if (remote_volume == target_device
and remote_array == remote_symm):
paired = True
local_vol_state = volume['localVolumeState']
rdf_pair_state = volume['rdfpairState']
else:
LOG.warning("Cannot locate source RDF volume %s", device_id)
return paired, local_vol_state, rdf_pair_state
def get_rdf_group_number(self, array, rdf_group_label):
"""Given an rdf_group_label, return the associated group number.
:param array: the array serial number
:param rdf_group_label: the group label
:returns: rdf_group_number
"""
number = None
rdf_list = self.get_rdf_group_list(array)
if rdf_list and rdf_list.get('rdfGroupID'):
number = [rdf['rdfgNumber'] for rdf in rdf_list['rdfGroupID']
if rdf['label'] == rdf_group_label][0]
if number:
rdf_group = self.get_rdf_group(array, number)
if not rdf_group:
number = None
return number
def create_rdf_device_pair(self, array, device_id, rdf_group_no,
target_device, remote_array,
target_vol_name, extra_specs):
"""Create an RDF pairing.
Create a remote replication relationship between source and target
devices.
:param array: the array serial number
:param device_id: the device id
:param rdf_group_no: the rdf group number
:param target_device: the target device id
:param remote_array: the remote array serial
:param target_vol_name: the name of the target volume
:param extra_specs: the extra specs
:returns: rdf_dict
"""
payload = ({"deviceNameListSource": [{"name": device_id}],
"deviceNameListTarget": [{"name": target_device}],
"replicationMode": "Synchronous",
"establish": 'true',
"rdfType": 'RDF1'})
resource_type = ("rdf_group/%(rdf_num)s/volume"
% {'rdf_num': rdf_group_no})
status_code, job = self.create_resource(array, REPLICATION,
resource_type, payload,
private="/private")
self.wait_for_job('Create rdf pair', status_code,
job, extra_specs)
rdf_dict = {'array': remote_array, 'device_id': target_device}
return rdf_dict
def modify_rdf_device_pair(
self, array, device_id, rdf_group, extra_specs, split=False):
"""Modify an rdf device pair.
:param array: the array serial number
:param device_id: the device id
:param rdf_group: the rdf group
:param extra_specs: the extra specs
:param split: flag to indicate "split" action
"""
common_opts = {"force": 'false',
"symForce": 'false',
"star": 'false',
"hop2": 'false',
"bypass": 'false'}
if split:
common_opts.update({"immediate": 'false'})
payload = {"action": "Split",
"executionOption": "ASYNCHRONOUS",
"split": common_opts}
else:
common_opts.update({"establish": 'true',
"restore": 'false',
"remote": 'false',
"immediate": 'false'})
payload = {"action": "Failover",
"executionOption": "ASYNCHRONOUS",
"failover": common_opts}
resource_name = ("%(rdf_num)s/volume/%(device_id)s"
% {'rdf_num': rdf_group, 'device_id': device_id})
sc, job = self.modify_resource(
array, REPLICATION, 'rdf_group',
payload, resource_name=resource_name, private="/private")
self.wait_for_job('Modify device pair', sc,
job, extra_specs)
def delete_rdf_pair(self, array, device_id, rdf_group):
"""Delete an rdf pair.
:param array: the array serial number
:param device_id: the device id
:param rdf_group: the rdf group
"""
params = {'half': 'false', 'force': 'true', 'symforce': 'false',
'star': 'false', 'bypass': 'false'}
resource_name = ("%(rdf_num)s/volume/%(device_id)s"
% {'rdf_num': rdf_group, 'device_id': device_id})
self.delete_resource(array, REPLICATION, 'rdf_group', resource_name,
private="/private", params=params)

View File

@ -25,6 +25,7 @@ import six
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder.objects import fields
from cinder.volume import volume_types from cinder.volume import volume_types
@ -53,6 +54,7 @@ PARENT_SG_NAME = 'parent_sg_name'
CONNECTOR = 'connector' CONNECTOR = 'connector'
VOL_NAME = 'volume_name' VOL_NAME = 'volume_name'
EXTRA_SPECS = 'extra_specs' EXTRA_SPECS = 'extra_specs'
IS_RE = 'replication_enabled'
DISABLECOMPRESSION = 'storagetype:disablecompression' DISABLECOMPRESSION = 'storagetype:disablecompression'
@ -147,13 +149,15 @@ class VMAXUtils(object):
@staticmethod @staticmethod
def get_default_storage_group_name( def get_default_storage_group_name(
srp_name, slo, workload, is_compression_disabled=False): srp_name, slo, workload, is_compression_disabled=False,
is_re=False):
"""Determine default storage group from extra_specs. """Determine default storage group from extra_specs.
:param srp_name: the name of the srp on the array :param srp_name: the name of the srp on the array
:param slo: the service level string e.g Bronze :param slo: the service level string e.g Bronze
:param workload: the workload string e.g DSS :param workload: the workload string e.g DSS
:param is_compression_disabled: flag for disabling compression :param is_compression_disabled: flag for disabling compression
:param is_re: flag for replication
:returns: storage_group_name :returns: storage_group_name
""" """
if slo and workload: if slo and workload:
@ -166,6 +170,8 @@ class VMAXUtils(object):
else: else:
prefix = "OS-no_SLO" prefix = "OS-no_SLO"
if is_re:
prefix += "-RE"
storage_group_name = ("%(prefix)s-SG" % {'prefix': prefix}) storage_group_name = ("%(prefix)s-SG" % {'prefix': prefix})
return storage_group_name return storage_group_name
@ -374,7 +380,7 @@ class VMAXUtils(object):
:param clone_name: the name of the clone :param clone_name: the name of the clone
:param source_device_id: the source device id :param source_device_id: the source device id
:return: snap_name :returns: snap_name
""" """
trunc_clone = self.truncate_string(clone_name, 10) trunc_clone = self.truncate_string(clone_name, 10)
snap_name = ("temp-%(device)s-%(clone)s" snap_name = ("temp-%(device)s-%(clone)s"
@ -434,3 +440,64 @@ class VMAXUtils(object):
return False return False
else: else:
return True return True
@staticmethod
def is_replication_enabled(extra_specs):
"""Check if replication is to be enabled.
:param extra_specs: extra specifications
:returns: bool - true if enabled, else false
"""
replication_enabled = False
if IS_RE in extra_specs:
replication_enabled = True
return replication_enabled
def get_replication_config(self, rep_device_list):
"""Gather necessary replication configuration info.
:param rep_device_list: the replication device list from cinder.conf
:returns: rep_config, replication configuration dict
"""
rep_config = {}
if not rep_device_list:
return None
else:
target = rep_device_list[0]
try:
rep_config['array'] = target['target_device_id']
rep_config['srp'] = target['remote_pool']
rep_config['rdf_group_label'] = target['rdf_group_label']
rep_config['portgroup'] = target['remote_port_group']
except KeyError as ke:
error_message = (_("Failed to retrieve all necessary SRDF "
"information. Error received: %(ke)s.") %
{'ke': six.text_type(ke)})
LOG.exception(error_message)
raise exception.VolumeBackendAPIException(data=error_message)
try:
allow_extend = target['allow_extend']
if strutils.bool_from_string(allow_extend):
rep_config['allow_extend'] = True
else:
rep_config['allow_extend'] = False
except KeyError:
rep_config['allow_extend'] = False
return rep_config
@staticmethod
def is_volume_failed_over(volume):
"""Check if a volume has been failed over.
:param volume: the volume object
:returns: bool
"""
if volume is not None:
if volume.get('replication_status') and (
volume.replication_status ==
fields.ReplicationStatus.FAILED_OVER):
return True
return False

View File

@ -0,0 +1,4 @@
---
features:
- |
Adding Replication V2.1 functionality to VMAX driver version 3.0.