VMAX - Live Migration, replacing SMI-S with REST
In VMAX driver version 3.0, SMI-S has been replaced with unisphere REST. This is porting Live Migration from SMIS to REST. See original https://review.openstack.org/#/c/450430/ for more details. Change-Id: I7e0d9cc382a75148ecd53c48f8b2e4e69a68163c Partially-Implements: blueprint vmax-rest
This commit is contained in:
parent
22eb9b69c1
commit
dd065f8e19
@ -74,6 +74,7 @@ class VMAXCommonData(object):
|
||||
device_id2 = '00002'
|
||||
rdf_group_name = '23_24_007'
|
||||
rdf_group_no = '70'
|
||||
u4v_version = '84'
|
||||
|
||||
# connector info
|
||||
wwpn1 = "123456789012345"
|
||||
@ -1344,11 +1345,12 @@ class VMAXRestTest(test.TestCase):
|
||||
array = self.data.array
|
||||
storagegroup = self.data.defaultstoragegroup_name
|
||||
payload = {'someKey': 'someValue'}
|
||||
version = self.data.u4v_version
|
||||
with mock.patch.object(self.rest, 'modify_resource'):
|
||||
self.rest.modify_storage_group(array, storagegroup, payload)
|
||||
self.rest.modify_resource.assert_called_once_with(
|
||||
self.data.array, 'sloprovisioning', 'storagegroup',
|
||||
payload, resource_name=storagegroup)
|
||||
payload, version, resource_name=storagegroup)
|
||||
|
||||
def test_create_volume_from_sg_success(self):
|
||||
volume_name = self.data.volume_details[0]['volume_identifier']
|
||||
@ -2699,7 +2701,7 @@ class VMAXCommonTest(test.TestCase):
|
||||
volume = self.data.test_volume
|
||||
connector = self.data.connector
|
||||
with mock.patch.object(self.common, 'find_host_lun_id',
|
||||
return_value={}):
|
||||
return_value=({}, False, [])):
|
||||
with mock.patch.object(self.common, '_remove_members'):
|
||||
self.common._unmap_lun(volume, connector)
|
||||
self.common._remove_members.assert_not_called()
|
||||
@ -2711,7 +2713,8 @@ class VMAXCommonTest(test.TestCase):
|
||||
['host_lun_address'])
|
||||
ref_dict = {'hostlunid': int(host_lun, 16),
|
||||
'maskingview': self.data.masking_view_name_f,
|
||||
'array': self.data.array}
|
||||
'array': self.data.array,
|
||||
'device_id': self.data.device_id}
|
||||
device_info_dict = self.common.initialize_connection(volume, connector)
|
||||
self.assertEqual(ref_dict, device_info_dict)
|
||||
|
||||
@ -2723,7 +2726,7 @@ class VMAXCommonTest(test.TestCase):
|
||||
masking_view_dict = self.common._populate_masking_dict(
|
||||
volume, connector, extra_specs)
|
||||
with mock.patch.object(self.common, 'find_host_lun_id',
|
||||
return_value={}):
|
||||
return_value=({}, False, [])):
|
||||
with mock.patch.object(
|
||||
self.common, '_attach_volume', return_value=(
|
||||
{}, self.data.port_group_name_f)):
|
||||
@ -2731,7 +2734,7 @@ class VMAXCommonTest(test.TestCase):
|
||||
connector)
|
||||
self.assertEqual({}, device_info_dict)
|
||||
self.common._attach_volume.assert_called_once_with(
|
||||
volume, connector, extra_specs, masking_view_dict)
|
||||
volume, connector, extra_specs, masking_view_dict, False)
|
||||
|
||||
def test_attach_volume_success(self):
|
||||
volume = self.data.test_volume
|
||||
@ -2744,7 +2747,8 @@ class VMAXCommonTest(test.TestCase):
|
||||
['host_lun_address'])
|
||||
ref_dict = {'hostlunid': int(host_lun, 16),
|
||||
'maskingview': self.data.masking_view_name_f,
|
||||
'array': self.data.array}
|
||||
'array': self.data.array,
|
||||
'device_id': self.data.device_id}
|
||||
with mock.patch.object(self.masking, 'setup_masking_view',
|
||||
return_value={
|
||||
'port_group_name':
|
||||
@ -2763,7 +2767,7 @@ class VMAXCommonTest(test.TestCase):
|
||||
with mock.patch.object(self.masking, 'setup_masking_view',
|
||||
return_value={}):
|
||||
with mock.patch.object(self.common, 'find_host_lun_id',
|
||||
return_value={}):
|
||||
return_value=({}, False, [])):
|
||||
with mock.patch.object(
|
||||
self.masking,
|
||||
'check_if_rollback_action_for_masking_required'):
|
||||
@ -2880,8 +2884,9 @@ class VMAXCommonTest(test.TestCase):
|
||||
['host_lun_address'])
|
||||
ref_masked = {'hostlunid': int(host_lun, 16),
|
||||
'maskingview': self.data.masking_view_name_f,
|
||||
'array': self.data.array}
|
||||
maskedvols = self.common.find_host_lun_id(
|
||||
'array': self.data.array,
|
||||
'device_id': self.data.device_id}
|
||||
maskedvols, __, __ = self.common.find_host_lun_id(
|
||||
volume, host, extra_specs)
|
||||
self.assertEqual(ref_masked, maskedvols)
|
||||
|
||||
@ -2891,7 +2896,7 @@ class VMAXCommonTest(test.TestCase):
|
||||
host = 'HostX'
|
||||
with mock.patch.object(self.rest, 'find_mv_connections_for_vol',
|
||||
return_value=None):
|
||||
maskedvols = self.common.find_host_lun_id(
|
||||
maskedvols, __, __ = self.common.find_host_lun_id(
|
||||
volume, host, extra_specs)
|
||||
self.assertEqual({}, maskedvols)
|
||||
|
||||
@ -3994,6 +3999,7 @@ class VMAXISCSITest(test.TestCase):
|
||||
ref_dict = {'maskingview': self.data.masking_view_name_f,
|
||||
'array': self.data.array,
|
||||
'hostlunid': 3,
|
||||
'device_id': self.data.device_id,
|
||||
'ip_and_iqn': [{'ip': self.data.ip,
|
||||
'iqn': self.data.initiator}],
|
||||
'is_multipath': False}
|
||||
@ -4951,6 +4957,60 @@ class VMAXMaskingTest(test.TestCase):
|
||||
self.data.parent_sg_f)
|
||||
self.assertEqual(2, mock_delete.call_count)
|
||||
|
||||
@mock.patch.object(masking.VMAXMasking, 'add_child_sg_to_parent_sg')
|
||||
@mock.patch.object(masking.VMAXMasking,
|
||||
'move_volume_between_storage_groups')
|
||||
@mock.patch.object(provision.VMAXProvision, 'create_storage_group')
|
||||
def test_pre_live_migration(self, mock_create_sg, mock_move, mock_add):
|
||||
with mock.patch.object(
|
||||
rest.VMAXRest, 'get_storage_group',
|
||||
side_effect=[None, self.data.sg_details[1]["storageGroupId"]]
|
||||
):
|
||||
source_sg = self.data.sg_details[2]["storageGroupId"]
|
||||
source_parent_sg = self.data.sg_details[4]["storageGroupId"]
|
||||
source_nf_sg = source_parent_sg[:-2] + 'NONFAST'
|
||||
self.data.iscsi_device_info['device_id'] = self.data.device_id
|
||||
self.mask.pre_live_migration(
|
||||
source_nf_sg, source_sg, source_parent_sg, False,
|
||||
self.data.iscsi_device_info, None)
|
||||
mock_create_sg.assert_called_once()
|
||||
|
||||
@mock.patch.object(rest.VMAXRest, 'delete_storage_group')
|
||||
@mock.patch.object(rest.VMAXRest, 'remove_child_sg_from_parent_sg')
|
||||
def test_post_live_migration(self, mock_remove_child_sg, mock_delete_sg):
|
||||
self.data.iscsi_device_info['source_sg'] = self.data.sg_details[2][
|
||||
"storageGroupId"]
|
||||
self.data.iscsi_device_info['source_parent_sg'] = self.data.sg_details[
|
||||
4]["storageGroupId"]
|
||||
with mock.patch.object(
|
||||
rest.VMAXRest, 'get_num_vols_in_sg', side_effect=[0, 1]):
|
||||
self.mask.post_live_migration(self.data.iscsi_device_info, None)
|
||||
mock_remove_child_sg.assert_called_once()
|
||||
mock_delete_sg.assert_called_once()
|
||||
|
||||
@mock.patch.object(masking.VMAXMasking,
|
||||
'move_volume_between_storage_groups')
|
||||
@mock.patch.object(rest.VMAXRest, 'delete_storage_group')
|
||||
@mock.patch.object(rest.VMAXRest, 'remove_child_sg_from_parent_sg')
|
||||
@mock.patch.object(masking.VMAXMasking, 'remove_volume_from_sg')
|
||||
def test_failed_live_migration(
|
||||
self, mock_remove_volume, mock_remove_child_sg, mock_delete_sg,
|
||||
mock_move):
|
||||
device_dict = self.data.iscsi_device_info
|
||||
device_dict['device_id'] = self.data.device_id
|
||||
device_dict['source_sg'] = self.data.sg_details[2]["storageGroupId"]
|
||||
device_dict['source_parent_sg'] = self.data.sg_details[4][
|
||||
"storageGroupId"]
|
||||
device_dict['source_nf_sg'] = (
|
||||
self.data.sg_details[4]["storageGroupId"][:-2] + 'NONFAST')
|
||||
sg_list = [device_dict['source_nf_sg']]
|
||||
with mock.patch.object(
|
||||
rest.VMAXRest, 'is_child_sg_in_parent_sg',
|
||||
side_effect=[True, False]):
|
||||
self.mask.failed_live_migration(device_dict, sg_list, None)
|
||||
mock_remove_volume.assert_not_called()
|
||||
mock_remove_child_sg.assert_called_once()
|
||||
|
||||
|
||||
class VMAXCommonReplicationTest(test.TestCase):
|
||||
def setUp(self):
|
||||
|
@ -415,16 +415,31 @@ class VMAXCommon(object):
|
||||
LOG.exception(exception_message)
|
||||
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||
|
||||
device_info = self.find_host_lun_id(
|
||||
volume, connector['host'], extra_specs)
|
||||
device_info, is_live_migration, source_storage_group_list = (
|
||||
self.find_host_lun_id(volume, connector['host'], extra_specs))
|
||||
if 'hostlunid' not in device_info:
|
||||
LOG.info("Volume %s is not mapped. No volume to unmap.",
|
||||
volume_name)
|
||||
return
|
||||
|
||||
device_id = self._find_device_on_array(volume, extra_specs)
|
||||
if is_live_migration and len(source_storage_group_list) == 1:
|
||||
LOG.info("Volume %s is mapped. Failed live migration case",
|
||||
volume_name)
|
||||
return
|
||||
source_nf_sg = None
|
||||
array = extra_specs[utils.ARRAY]
|
||||
self._remove_members(array, volume, device_id, extra_specs)
|
||||
if len(source_storage_group_list) > 1:
|
||||
for storage_group in source_storage_group_list:
|
||||
if 'NONFAST' in storage_group:
|
||||
source_nf_sg = storage_group
|
||||
break
|
||||
if source_nf_sg:
|
||||
# Remove volume from non fast storage group
|
||||
self.masking.remove_volume_from_sg(
|
||||
array, device_info['device_id'], volume_name, storage_group,
|
||||
extra_specs)
|
||||
else:
|
||||
self._remove_members(array, volume, device_info['device_id'],
|
||||
extra_specs)
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Initializes the connection and returns device and connection info.
|
||||
@ -463,13 +478,15 @@ class VMAXCommon(object):
|
||||
if self.utils.is_volume_failed_over(volume):
|
||||
extra_specs = self._get_replication_extra_specs(
|
||||
extra_specs, self.rep_config)
|
||||
device_info_dict = self.find_host_lun_id(
|
||||
volume, connector['host'], extra_specs)
|
||||
device_info_dict, is_live_migration, source_storage_group_list = (
|
||||
self.find_host_lun_id(volume, connector['host'], extra_specs))
|
||||
masking_view_dict = self._populate_masking_dict(
|
||||
volume, connector, extra_specs)
|
||||
|
||||
if ('hostlunid' in device_info_dict and
|
||||
device_info_dict['hostlunid'] is not None):
|
||||
device_info_dict['hostlunid'] is not None and
|
||||
is_live_migration is False) or (
|
||||
is_live_migration and len(source_storage_group_list) > 1):
|
||||
hostlunid = device_info_dict['hostlunid']
|
||||
LOG.info("Volume %(volume)s is already mapped. "
|
||||
"The hostlunid is %(hostlunid)s.",
|
||||
@ -481,9 +498,39 @@ class VMAXCommon(object):
|
||||
device_info_dict['maskingview']))
|
||||
|
||||
else:
|
||||
if is_live_migration:
|
||||
source_nf_sg, source_sg, source_parent_sg, is_source_nf_sg = (
|
||||
self._setup_for_live_migration(
|
||||
device_info_dict, source_storage_group_list))
|
||||
masking_view_dict['source_nf_sg'] = source_nf_sg
|
||||
masking_view_dict['source_sg'] = source_sg
|
||||
masking_view_dict['source_parent_sg'] = source_parent_sg
|
||||
try:
|
||||
self.masking.pre_live_migration(
|
||||
source_nf_sg, source_sg, source_parent_sg,
|
||||
is_source_nf_sg, device_info_dict, extra_specs)
|
||||
except Exception:
|
||||
# Move it back to original storage group
|
||||
source_storage_group_list = (
|
||||
self.rest.get_storage_groups_from_volume(
|
||||
device_info_dict['array'],
|
||||
device_info_dict['device_id']))
|
||||
self.masking.failed_live_migration(
|
||||
masking_view_dict, source_storage_group_list,
|
||||
extra_specs)
|
||||
exception_message = (_(
|
||||
"Unable to setup live migration because of the "
|
||||
"following error: %(errorMessage)s.")
|
||||
% {'errorMessage': sys.exc_info()[1]})
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exception_message)
|
||||
device_info_dict, port_group_name = (
|
||||
self._attach_volume(
|
||||
volume, connector, extra_specs, masking_view_dict))
|
||||
volume, connector, extra_specs, masking_view_dict,
|
||||
is_live_migration))
|
||||
if is_live_migration:
|
||||
self.masking.post_live_migration(
|
||||
masking_view_dict, extra_specs)
|
||||
if self.protocol.lower() == 'iscsi':
|
||||
device_info_dict['ip_and_iqn'] = (
|
||||
self._find_ip_and_iqns(
|
||||
@ -492,7 +539,7 @@ class VMAXCommon(object):
|
||||
return device_info_dict
|
||||
|
||||
def _attach_volume(self, volume, connector, extra_specs,
|
||||
masking_view_dict):
|
||||
masking_view_dict, is_live_migration=False):
|
||||
"""Attach a volume to a host.
|
||||
|
||||
:param volume: the volume object
|
||||
@ -504,14 +551,18 @@ class VMAXCommon(object):
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
volume_name = volume.name
|
||||
|
||||
if is_live_migration:
|
||||
masking_view_dict['isLiveMigration'] = True
|
||||
else:
|
||||
masking_view_dict['isLiveMigration'] = False
|
||||
rollback_dict = self.masking.setup_masking_view(
|
||||
masking_view_dict[utils.ARRAY],
|
||||
masking_view_dict, extra_specs)
|
||||
|
||||
# Find host lun id again after the volume is exported to the host.
|
||||
device_info_dict = self.find_host_lun_id(volume, connector['host'],
|
||||
extra_specs)
|
||||
|
||||
device_info_dict, __, __ = self.find_host_lun_id(
|
||||
volume, connector['host'], extra_specs)
|
||||
if 'hostlunid' not in device_info_dict:
|
||||
# Did not successfully attach to host,
|
||||
# so a rollback for FAST is required.
|
||||
@ -830,14 +881,17 @@ class VMAXCommon(object):
|
||||
:returns: dict -- the data dict
|
||||
"""
|
||||
maskedvols = {}
|
||||
is_live_migration = False
|
||||
volume_name = volume.name
|
||||
device_id = self._find_device_on_array(volume, extra_specs)
|
||||
if device_id:
|
||||
array = extra_specs[utils.ARRAY]
|
||||
host = self.utils.get_host_short_name(host)
|
||||
source_storage_group_list = (
|
||||
self.rest.get_storage_groups_from_volume(array, device_id))
|
||||
# return only masking views for this host
|
||||
maskingviews = self.get_masking_views_from_volume(
|
||||
array, device_id, host)
|
||||
array, device_id, host, source_storage_group_list)
|
||||
|
||||
for maskingview in maskingviews:
|
||||
host_lun_id = self.rest.find_mv_connections_for_vol(
|
||||
@ -845,7 +899,8 @@ class VMAXCommon(object):
|
||||
if host_lun_id is not None:
|
||||
devicedict = {'hostlunid': host_lun_id,
|
||||
'maskingview': maskingview,
|
||||
'array': array}
|
||||
'array': array,
|
||||
'device_id': device_id}
|
||||
maskedvols = devicedict
|
||||
if not maskedvols:
|
||||
LOG.debug(
|
||||
@ -856,32 +911,55 @@ class VMAXCommon(object):
|
||||
else:
|
||||
LOG.debug("Device info: %(maskedvols)s.",
|
||||
{'maskedvols': maskedvols})
|
||||
host = self.utils.get_host_short_name(host)
|
||||
hoststr = ("-%(host)s-"
|
||||
% {'host': host})
|
||||
|
||||
if hoststr.lower() not in maskedvols['maskingview'].lower():
|
||||
LOG.debug(
|
||||
"Volume is masked but not to host %(host)s as is "
|
||||
"expected. Assuming live migration.",
|
||||
{'host': host})
|
||||
is_live_migration = True
|
||||
else:
|
||||
for storage_group in source_storage_group_list:
|
||||
if 'NONFAST' in storage_group:
|
||||
is_live_migration = True
|
||||
break
|
||||
else:
|
||||
exception_message = (_("Cannot retrieve volume %(vol)s "
|
||||
"from the array.") % {'vol': volume_name})
|
||||
LOG.exception(exception_message)
|
||||
raise exception.VolumeBackendAPIException(exception_message)
|
||||
|
||||
return maskedvols
|
||||
return maskedvols, is_live_migration, source_storage_group_list
|
||||
|
||||
def get_masking_views_from_volume(self, array, device_id, host):
|
||||
def get_masking_views_from_volume(self, array, device_id, host,
|
||||
storage_group_list=None):
|
||||
"""Retrieve masking view list for a volume.
|
||||
|
||||
:param array: array serial number
|
||||
:param device_id: the volume device id
|
||||
:param host: the host
|
||||
:param storage_group_list: the storage group list to use
|
||||
:returns: masking view list
|
||||
"""
|
||||
LOG.debug("Getting masking views from volume")
|
||||
maskingview_list = []
|
||||
short_host = self.utils.get_host_short_name(host)
|
||||
storagegrouplist = self.rest.get_storage_groups_from_volume(
|
||||
array, device_id)
|
||||
for sg in storagegrouplist:
|
||||
host_compare = False
|
||||
if not storage_group_list:
|
||||
storage_group_list = self.rest.get_storage_groups_from_volume(
|
||||
array, device_id)
|
||||
host_compare = True
|
||||
for sg in storage_group_list:
|
||||
mvs = self.rest.get_masking_views_from_storage_group(
|
||||
array, sg)
|
||||
for mv in mvs:
|
||||
if short_host.lower() in mv.lower():
|
||||
if host_compare:
|
||||
if short_host.lower() in mv.lower():
|
||||
maskingview_list.append(mv)
|
||||
else:
|
||||
maskingview_list.append(mv)
|
||||
return maskingview_list
|
||||
|
||||
@ -2523,3 +2601,32 @@ class VMAXCommon(object):
|
||||
secondary_info['SerialNumber'] = six.text_type(rep_config['array'])
|
||||
secondary_info['srpName'] = rep_config['srp']
|
||||
return secondary_info
|
||||
|
||||
def _setup_for_live_migration(self, device_info_dict,
|
||||
source_storage_group_list):
|
||||
"""Function to set attributes for live migration.
|
||||
|
||||
:param device_info_dict: the data dict
|
||||
:param source_storage_group_list:
|
||||
:returns: source_nf_sg: The non fast storage group
|
||||
:returns: source_sg: The source storage group
|
||||
:returns: source_parent_sg: The parent storage group
|
||||
:returns: is_source_nf_sg:if the non fast storage group already exists
|
||||
"""
|
||||
array = device_info_dict['array']
|
||||
source_sg = None
|
||||
is_source_nf_sg = False
|
||||
# Get parent storage group
|
||||
source_parent_sg = self.rest.get_element_from_masking_view(
|
||||
array, device_info_dict['maskingview'], storagegroup=True)
|
||||
source_nf_sg = source_parent_sg[:-2] + 'NONFAST'
|
||||
for sg in source_storage_group_list:
|
||||
is_descendant = self.rest.is_child_sg_in_parent_sg(
|
||||
array, sg, source_parent_sg)
|
||||
if is_descendant:
|
||||
source_sg = sg
|
||||
is_descendant = self.rest.is_child_sg_in_parent_sg(
|
||||
array, source_nf_sg, source_parent_sg)
|
||||
if is_descendant:
|
||||
is_source_nf_sg = True
|
||||
return source_nf_sg, source_sg, source_parent_sg, is_source_nf_sg
|
||||
|
@ -80,6 +80,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
|
||||
- QoS support
|
||||
- Support for compression on All Flash
|
||||
- Support for volume replication
|
||||
- Support for live migration
|
||||
"""
|
||||
|
||||
VERSION = "3.0.0"
|
||||
|
@ -85,6 +85,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
|
||||
- QoS support
|
||||
- Support for compression on All Flash
|
||||
- Support for volume replication
|
||||
- Support for live migration
|
||||
"""
|
||||
|
||||
VERSION = "3.0.0"
|
||||
|
@ -68,9 +68,12 @@ class VMAXMasking(object):
|
||||
volume_name = masking_view_dict[utils.VOL_NAME]
|
||||
masking_view_dict[utils.EXTRA_SPECS] = extra_specs
|
||||
device_id = masking_view_dict[utils.DEVICE_ID]
|
||||
default_sg_name = self._get_default_storagegroup_and_remove_vol(
|
||||
serial_number, device_id, masking_view_dict, volume_name,
|
||||
extra_specs)
|
||||
if 'source_nf_sg' in masking_view_dict:
|
||||
default_sg_name = masking_view_dict['source_nf_sg']
|
||||
else:
|
||||
default_sg_name = self._get_default_storagegroup_and_remove_vol(
|
||||
serial_number, device_id, masking_view_dict, volume_name,
|
||||
extra_specs)
|
||||
|
||||
try:
|
||||
error_message = self._get_or_create_masking_view(
|
||||
@ -304,9 +307,12 @@ class VMAXMasking(object):
|
||||
return msg
|
||||
|
||||
def add_child_sg_to_parent_sg(
|
||||
self, serial_number, child_sg_name, parent_sg_name, extra_specs):
|
||||
self, serial_number, child_sg_name, parent_sg_name, extra_specs,
|
||||
default_version=True
|
||||
):
|
||||
"""Add a child storage group to a parent storage group.
|
||||
|
||||
:param default_version: the default uv4 version
|
||||
:param serial_number: the array serial number
|
||||
:param child_sg_name: the name of the child storage group
|
||||
:param parent_sg_name: the name of the aprent storage group
|
||||
@ -323,8 +329,12 @@ class VMAXMasking(object):
|
||||
serial_number, child_sg_name, parent_sg_name):
|
||||
pass
|
||||
else:
|
||||
self.rest.add_child_sg_to_parent_sg(
|
||||
serial_number, child_sg, parent_sg, extra_specs)
|
||||
if default_version:
|
||||
self.rest.add_child_sg_to_parent_sg(
|
||||
serial_number, child_sg, parent_sg, extra_specs)
|
||||
else:
|
||||
self.rest.add_empty_child_sg_to_parent_sg(
|
||||
serial_number, child_sg, parent_sg, extra_specs)
|
||||
|
||||
do_add_sg_to_sg(child_sg_name, parent_sg_name)
|
||||
|
||||
@ -434,6 +444,20 @@ class VMAXMasking(object):
|
||||
|
||||
return child_sg_name, msg
|
||||
|
||||
def move_volume_between_storage_groups(
|
||||
self, array, device_id, source_storagegroup_name,
|
||||
target_storagegroup_name, extra_specs):
|
||||
@coordination.synchronized("emc-sg-{source_storage_group}")
|
||||
@coordination.synchronized("emc-sg-{target_storage_group}")
|
||||
def do_move_volume_between_storage_groups(source_storage_group,
|
||||
target_storage_group):
|
||||
self.rest.move_volume_between_storage_groups(
|
||||
array, device_id, source_storage_group, target_storage_group,
|
||||
extra_specs)
|
||||
|
||||
do_move_volume_between_storage_groups(
|
||||
source_storagegroup_name, target_storagegroup_name)
|
||||
|
||||
def _check_port_group(self, serial_number, portgroup_name):
|
||||
"""Check that you can get a port group.
|
||||
|
||||
@ -733,6 +757,12 @@ class VMAXMasking(object):
|
||||
if error_message:
|
||||
LOG.error(error_message)
|
||||
message = (_("Rollback"))
|
||||
elif 'isLiveMigration' in rollback_dict and (
|
||||
rollback_dict['isLiveMigration'] is True):
|
||||
# Live migration case.
|
||||
# Remove from nonfast storage group to fast sg
|
||||
self.failed_live_migration(rollback_dict, found_sg_name,
|
||||
rollback_dict[utils.EXTRA_SPECS])
|
||||
else:
|
||||
LOG.info("The storage group found is %(found_sg_name)s.",
|
||||
{'found_sg_name': found_sg_name})
|
||||
@ -1334,3 +1364,76 @@ class VMAXMasking(object):
|
||||
"not created by the VMAX driver so will "
|
||||
"not be deleted by the VMAX driver.",
|
||||
{'ig_name': initiatorgroup_name})
|
||||
|
||||
def pre_live_migration(self, source_nf_sg, source_sg, source_parent_sg,
|
||||
is_source_nf_sg, device_info_dict, extra_specs):
|
||||
"""Run before any live migration operation.
|
||||
|
||||
:param source_nf_sg: The non fast storage group
|
||||
:param source_sg: The source storage group
|
||||
:param source_parent_sg: The parent storage group
|
||||
:param is_source_nf_sg: if the non fast storage group already exists
|
||||
:param device_info_dict: the data dict
|
||||
:param extra_specs: extra specifications
|
||||
"""
|
||||
if is_source_nf_sg is False:
|
||||
storage_group = self.rest.get_storage_group(
|
||||
device_info_dict['array'], source_nf_sg)
|
||||
if storage_group is None:
|
||||
self.provision.create_storage_group(
|
||||
device_info_dict['array'], source_nf_sg, None, None, None,
|
||||
extra_specs)
|
||||
self.add_child_sg_to_parent_sg(
|
||||
device_info_dict['array'], source_nf_sg, source_parent_sg,
|
||||
extra_specs, default_version=False)
|
||||
self.move_volume_between_storage_groups(
|
||||
device_info_dict['array'], device_info_dict['device_id'],
|
||||
source_sg, source_nf_sg, extra_specs)
|
||||
|
||||
def post_live_migration(self, device_info_dict, extra_specs):
|
||||
"""Run after every live migration operation.
|
||||
|
||||
:param device_info_dict: : the data dict
|
||||
:param extra_specs: extra specifications
|
||||
"""
|
||||
array = device_info_dict['array']
|
||||
source_sg = device_info_dict['source_sg']
|
||||
# Delete fast storage group
|
||||
num_vol_in_sg = self.rest.get_num_vols_in_sg(
|
||||
array, source_sg)
|
||||
if num_vol_in_sg == 0:
|
||||
self.rest.remove_child_sg_from_parent_sg(
|
||||
array, source_sg, device_info_dict['source_parent_sg'],
|
||||
extra_specs)
|
||||
self.rest.delete_storage_group(array, source_sg)
|
||||
|
||||
def failed_live_migration(self, device_info_dict,
|
||||
source_storage_group_list, extra_specs):
|
||||
"""This is run in the event of a failed live migration operation.
|
||||
|
||||
:param device_info_dict: the data dict
|
||||
:param source_storage_group_list: list of storage groups associated
|
||||
with the device
|
||||
:param extra_specs: extra specifications
|
||||
"""
|
||||
array = device_info_dict['array']
|
||||
source_nf_sg = device_info_dict['source_nf_sg']
|
||||
source_sg = device_info_dict['source_sg']
|
||||
source_parent_sg = device_info_dict['source_parent_sg']
|
||||
device_id = device_info_dict['device_id']
|
||||
for sg in source_storage_group_list:
|
||||
if sg not in [source_sg, source_nf_sg]:
|
||||
self.remove_volume_from_sg(
|
||||
array, device_id, device_info_dict['volume_name'], sg,
|
||||
extra_specs)
|
||||
if source_nf_sg in source_storage_group_list:
|
||||
self.move_volume_between_storage_groups(
|
||||
array, device_id, source_nf_sg,
|
||||
source_sg, extra_specs)
|
||||
is_descendant = self.rest.is_child_sg_in_parent_sg(
|
||||
array, source_nf_sg, source_parent_sg)
|
||||
if is_descendant:
|
||||
self.rest.remove_child_sg_from_parent_sg(
|
||||
array, source_nf_sg, source_parent_sg, extra_specs)
|
||||
# Delete non fast storage group
|
||||
self.rest.delete_storage_group(array, source_nf_sg)
|
||||
|
@ -280,7 +280,7 @@ class VMAXRest(object):
|
||||
|
||||
@staticmethod
|
||||
def _build_uri(array, category, resource_type,
|
||||
resource_name=None, private=''):
|
||||
resource_name=None, private='', version=U4V_VERSION):
|
||||
"""Build the target url.
|
||||
|
||||
:param array: the array serial number
|
||||
@ -292,7 +292,7 @@ class VMAXRest(object):
|
||||
"""
|
||||
target_uri = ('%(private)s/%(version)s/%(category)s/symmetrix/'
|
||||
'%(array)s/%(resource_type)s'
|
||||
% {'private': private, 'version': U4V_VERSION,
|
||||
% {'private': private, 'version': version,
|
||||
'category': category, 'array': array,
|
||||
'resource_type': resource_type})
|
||||
if resource_name:
|
||||
@ -357,9 +357,10 @@ class VMAXRest(object):
|
||||
return status_code, message
|
||||
|
||||
def modify_resource(self, array, category, resource_type, payload,
|
||||
resource_name=None, private=''):
|
||||
version=U4V_VERSION, resource_name=None, private=''):
|
||||
"""Modify a resource.
|
||||
|
||||
:param version: the uv4 version
|
||||
:param array: the array serial number
|
||||
:param category: the category
|
||||
:param resource_type: the resource type
|
||||
@ -369,7 +370,7 @@ class VMAXRest(object):
|
||||
:returns: status_code -- int, message -- string (server response)
|
||||
"""
|
||||
target_uri = self._build_uri(array, category, resource_type,
|
||||
resource_name, private)
|
||||
resource_name, private, version)
|
||||
status_code, message = self.request(target_uri, PUT,
|
||||
request_object=payload)
|
||||
operation = 'modify %(res)s resource' % {'res': resource_type}
|
||||
@ -554,6 +555,24 @@ class VMAXRest(object):
|
||||
sc, job = self.modify_storage_group(array, parent_sg, payload)
|
||||
self.wait_for_job('Add child sg to parent sg', sc, job, extra_specs)
|
||||
|
||||
def add_empty_child_sg_to_parent_sg(
|
||||
self, array, child_sg, parent_sg, extra_specs):
|
||||
"""Add an empty storage group to a parent storage group.
|
||||
|
||||
This method adds an existing storage group to another storage
|
||||
group, i.e. cascaded storage groups.
|
||||
:param array: the array serial number
|
||||
:param child_sg: the name of the child sg
|
||||
:param parent_sg: the name of the parent sg
|
||||
:param extra_specs: the extra specifications
|
||||
"""
|
||||
payload = {"editStorageGroupActionParam": {
|
||||
"addExistingStorageGroupParam": {
|
||||
"storageGroupId": [child_sg]}}}
|
||||
sc, job = self.modify_storage_group(array, parent_sg, payload,
|
||||
version="83")
|
||||
self.wait_for_job('Add child sg to parent sg', sc, job, extra_specs)
|
||||
|
||||
def remove_child_sg_from_parent_sg(
|
||||
self, array, child_sg, parent_sg, extra_specs):
|
||||
"""Remove a storage group from its parent storage group.
|
||||
@ -621,16 +640,18 @@ class VMAXRest(object):
|
||||
job, extra_specs)
|
||||
return storagegroup_name
|
||||
|
||||
def modify_storage_group(self, array, storagegroup, payload):
|
||||
def modify_storage_group(self, array, storagegroup, payload,
|
||||
version=U4V_VERSION):
|
||||
"""Modify a storage group (PUT operation).
|
||||
|
||||
:param version: the uv4 version
|
||||
:param array: the array serial number
|
||||
:param storagegroup: storage group name
|
||||
:param payload: the request payload
|
||||
:returns: status_code -- int, message -- string, server response
|
||||
"""
|
||||
return self.modify_resource(
|
||||
array, SLOPROVISIONING, 'storagegroup', payload,
|
||||
array, SLOPROVISIONING, 'storagegroup', payload, version,
|
||||
resource_name=storagegroup)
|
||||
|
||||
def create_volume_from_sg(self, array, volume_name, storagegroup_name,
|
||||
@ -836,6 +857,28 @@ class VMAXRest(object):
|
||||
array, SLOPROVISIONING, 'storagegroup', storagegroup_name)
|
||||
LOG.debug("Storage Group successfully deleted.")
|
||||
|
||||
def move_volume_between_storage_groups(
|
||||
self, array, device_id, source_storagegroup_name,
|
||||
target_storagegroup_name, extra_specs):
|
||||
"""Move a volume to a different storage group.
|
||||
|
||||
:param array: the array serial number
|
||||
:param source_storagegroup_name: the originating storage group name
|
||||
:param target_storagegroup_name: the destination storage group name
|
||||
:param device_id: the device id
|
||||
:param extra_specs: extra specifications
|
||||
"""
|
||||
payload = ({"executionOption": "ASYNCHRONOUS",
|
||||
"editStorageGroupActionParam": {
|
||||
"moveVolumeToStorageGroupParam": {
|
||||
"volumeId": [device_id],
|
||||
"storageGroupId": target_storagegroup_name,
|
||||
"useForceFlag": "false"}}})
|
||||
status_code, job = self.modify_storage_group(
|
||||
array, source_storagegroup_name, payload)
|
||||
self.wait_for_job('move volume between storage groups', status_code,
|
||||
job, extra_specs)
|
||||
|
||||
def get_volume(self, array, device_id):
|
||||
"""Get a VMAX volume from array.
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adding Live Migration functionality to VMAX driver version 3.0.
|
Loading…
Reference in New Issue
Block a user