Merge "VMAX driver - detach volume shouldn't remove from volume groups"
This commit is contained in:
commit
a11dcdb0f7
@ -2877,6 +2877,16 @@ class VMAXProvisionTest(test.TestCase):
|
||||
self.data.group_snapshot_name, self.data.extra_specs,
|
||||
unlink=True)
|
||||
|
||||
@mock.patch.object(rest.VMAXRest, 'get_storage_group',
|
||||
side_effect=[None, VMAXCommonData.sg_details[1]])
|
||||
@mock.patch.object(provision.VMAXProvision, 'create_volume_group')
|
||||
def test_get_or_create_volume_group(self, mock_create, mock_sg):
|
||||
for x in range(0, 2):
|
||||
self.provision.get_or_create_volume_group(
|
||||
self.data.array, self.data.test_group, self.data.extra_specs)
|
||||
self.assertEqual(2, mock_sg.call_count)
|
||||
self.assertEqual(1, mock_create.call_count)
|
||||
|
||||
|
||||
class VMAXCommonTest(test.TestCase):
|
||||
def setUp(self):
|
||||
@ -2999,7 +3009,7 @@ class VMAXCommonTest(test.TestCase):
|
||||
self.common._remove_members(array, volume, device_id,
|
||||
extra_specs, self.data.connector)
|
||||
mock_rm.assert_called_once_with(
|
||||
array, device_id, volume_name,
|
||||
array, volume, device_id, volume_name,
|
||||
extra_specs, True, self.data.connector)
|
||||
|
||||
def test_unmap_lun(self):
|
||||
@ -3567,9 +3577,9 @@ class VMAXCommonTest(test.TestCase):
|
||||
with mock.patch.object(
|
||||
self.common, 'cleanup_lun_replication') as mock_clean:
|
||||
self.common._remove_vol_and_cleanup_replication(
|
||||
array, device_id, volume_name, extra_specs)
|
||||
array, device_id, volume_name, extra_specs, volume)
|
||||
mock_rm.assert_called_once_with(
|
||||
array, device_id, volume_name, extra_specs, False)
|
||||
array, volume, device_id, volume_name, extra_specs, False)
|
||||
mock_clean.assert_not_called()
|
||||
self.common._remove_vol_and_cleanup_replication(
|
||||
array, device_id, volume_name, extra_specs, volume)
|
||||
@ -3929,7 +3939,7 @@ class VMAXCommonTest(test.TestCase):
|
||||
self.common._slo_workload_migration(
|
||||
device_id, volume, host, volume_name, new_type, extra_specs)
|
||||
self.common._migrate_volume.assert_called_once_with(
|
||||
extra_specs[utils.ARRAY], device_id,
|
||||
extra_specs[utils.ARRAY], volume, device_id,
|
||||
extra_specs[utils.SRP], 'Silver',
|
||||
'OLTP', volume_name, new_type, extra_specs)
|
||||
|
||||
@ -3974,7 +3984,7 @@ class VMAXCommonTest(test.TestCase):
|
||||
extra_specs)
|
||||
self.assertTrue(bool(migrate_status))
|
||||
self.common._migrate_volume.assert_called_once_with(
|
||||
extra_specs[utils.ARRAY], device_id,
|
||||
extra_specs[utils.ARRAY], volume, device_id,
|
||||
extra_specs[utils.SRP], self.data.slo,
|
||||
self.data.workload, volume_name, new_type, extra_specs)
|
||||
|
||||
@ -3985,25 +3995,28 @@ class VMAXCommonTest(test.TestCase):
|
||||
device_id = self.data.device_id
|
||||
volume_name = self.data.test_volume.name
|
||||
extra_specs = self.data.extra_specs
|
||||
volume = self.data.test_volume
|
||||
new_type = {'extra_specs': {}}
|
||||
migrate_status = self.common._migrate_volume(
|
||||
self.data.array, device_id, self.data.srp, self.data.slo,
|
||||
self.data.workload, volume_name, new_type, extra_specs)
|
||||
self.data.array, volume, device_id, self.data.srp,
|
||||
self.data.slo, self.data.workload, volume_name,
|
||||
new_type, extra_specs)
|
||||
self.assertTrue(migrate_status)
|
||||
target_extra_specs = {
|
||||
'array': self.data.array, 'interval': 3,
|
||||
'retries': 120, 'slo': self.data.slo,
|
||||
'srp': self.data.srp, 'workload': self.data.workload}
|
||||
mock_remove.assert_called_once_with(
|
||||
self.data.array, device_id, volume_name,
|
||||
self.data.array, volume, device_id, volume_name,
|
||||
target_extra_specs, reset=True)
|
||||
mock_remove.reset_mock()
|
||||
with mock.patch.object(
|
||||
self.rest, 'get_storage_groups_from_volume',
|
||||
return_value=[]):
|
||||
migrate_status = self.common._migrate_volume(
|
||||
self.data.array, device_id, self.data.srp, self.data.slo,
|
||||
self.data.workload, volume_name, new_type, extra_specs)
|
||||
self.data.array, volume, device_id, self.data.srp,
|
||||
self.data.slo, self.data.workload, volume_name,
|
||||
new_type, extra_specs)
|
||||
self.assertTrue(migrate_status)
|
||||
mock_remove.assert_not_called()
|
||||
|
||||
@ -4017,7 +4030,8 @@ class VMAXCommonTest(test.TestCase):
|
||||
self.masking, 'get_or_create_default_storage_group',
|
||||
side_effect=exception.VolumeBackendAPIException):
|
||||
migrate_status = self.common._migrate_volume(
|
||||
self.data.array, device_id, self.data.srp, self.data.slo,
|
||||
self.data.array, self.data.test_volume, device_id,
|
||||
self.data.srp, self.data.slo,
|
||||
self.data.workload, volume_name, new_type, extra_specs)
|
||||
self.assertFalse(migrate_status)
|
||||
|
||||
@ -4030,7 +4044,8 @@ class VMAXCommonTest(test.TestCase):
|
||||
self.rest, 'is_volume_in_storagegroup',
|
||||
return_value=False):
|
||||
migrate_status = self.common._migrate_volume(
|
||||
self.data.array, device_id, self.data.srp, self.data.slo,
|
||||
self.data.array, self.data.test_volume, device_id,
|
||||
self.data.srp, self.data.slo,
|
||||
self.data.workload, volume_name, new_type, extra_specs)
|
||||
self.assertFalse(migrate_status)
|
||||
|
||||
@ -4080,26 +4095,6 @@ class VMAXCommonTest(test.TestCase):
|
||||
self.data.srp, volume_name, False)
|
||||
self.assertEqual(ref_return, return_val)
|
||||
|
||||
def test_find_volume_group_name_from_id(self):
|
||||
array = self.data.array
|
||||
group_id = 'GrpId'
|
||||
group_name = None
|
||||
ref_group_name = self.data.storagegroup_name_with_id
|
||||
with mock.patch.object(
|
||||
self.rest, 'get_storage_group_list',
|
||||
return_value=self.data.sg_list_rep):
|
||||
group_name = self.common._find_volume_group_name_from_id(
|
||||
array, group_id)
|
||||
self.assertEqual(ref_group_name, group_name)
|
||||
|
||||
def test_find_volume_group_name_from_id_not_found(self):
|
||||
array = self.data.array
|
||||
group_id = 'GrpId'
|
||||
group_name = None
|
||||
group_name = self.common._find_volume_group_name_from_id(
|
||||
array, group_id)
|
||||
self.assertIsNone(group_name)
|
||||
|
||||
def test_find_volume_group(self):
|
||||
group = self.data.test_group_1
|
||||
array = self.data.array
|
||||
@ -4845,7 +4840,8 @@ class VMAXMaskingTest(test.TestCase):
|
||||
'get_or_create_masking_view_and_map_lun')
|
||||
def test_setup_masking_view(self, mock_get_or_create_mv):
|
||||
self.driver.masking.setup_masking_view(
|
||||
self.data.array, self.maskingviewdict, self.extra_specs)
|
||||
self.data.array, self.data.test_volume,
|
||||
self.maskingviewdict, self.extra_specs)
|
||||
mock_get_or_create_mv.assert_called_once()
|
||||
|
||||
@mock.patch.object(
|
||||
@ -4869,19 +4865,22 @@ class VMAXMaskingTest(test.TestCase):
|
||||
mock_add_volume):
|
||||
rollback_dict = (
|
||||
self.driver.masking.get_or_create_masking_view_and_map_lun(
|
||||
self.data.array, self.maskingviewdict['maskingview_name'],
|
||||
self.data.array, self.data.test_volume,
|
||||
self.maskingviewdict['maskingview_name'],
|
||||
self.maskingviewdict, self.extra_specs))
|
||||
self.assertEqual(self.maskingviewdict, rollback_dict)
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.masking.get_or_create_masking_view_and_map_lun,
|
||||
self.data.array, self.maskingviewdict['maskingview_name'],
|
||||
self.data.array, self.data.test_volume,
|
||||
self.maskingviewdict['maskingview_name'],
|
||||
self.maskingviewdict, self.extra_specs)
|
||||
self.maskingviewdict['slo'] = None
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.driver.masking.get_or_create_masking_view_and_map_lun,
|
||||
self.data.array, self.maskingviewdict['maskingview_name'],
|
||||
self.data.array, self.data.test_volume,
|
||||
self.maskingviewdict['maskingview_name'],
|
||||
self.maskingviewdict, self.extra_specs)
|
||||
|
||||
@mock.patch.object(
|
||||
@ -5246,14 +5245,16 @@ class VMAXMaskingTest(test.TestCase):
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.mask.check_if_rollback_action_for_masking_required,
|
||||
self.data.array, self.device_id, self.maskingviewdict)
|
||||
self.data.array, self.data.test_volume,
|
||||
self.device_id, self.maskingviewdict)
|
||||
with mock.patch.object(masking.VMAXMasking,
|
||||
'remove_and_reset_members'):
|
||||
self.maskingviewdict[
|
||||
'default_sg_name'] = self.data.defaultstoragegroup_name
|
||||
error_message = (
|
||||
self.mask.check_if_rollback_action_for_masking_required(
|
||||
self.data.array, self.device_id, self.maskingviewdict))
|
||||
self.data.array, self.data.test_volume,
|
||||
self.device_id, self.maskingviewdict))
|
||||
self.assertIsNone(error_message)
|
||||
|
||||
@mock.patch.object(rest.VMAXRest, 'delete_masking_view')
|
||||
@ -5328,13 +5329,14 @@ class VMAXMaskingTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(masking.VMAXMasking, '_cleanup_deletion')
|
||||
def test_remove_and_reset_members(self, mock_cleanup):
|
||||
self.mask.remove_and_reset_members(self.data.array, self.device_id,
|
||||
self.volume_name, self.extra_specs,
|
||||
reset=False)
|
||||
self.mask.remove_and_reset_members(
|
||||
self.data.array, self.device_id, self.data.test_volume,
|
||||
self.volume_name, self.extra_specs, reset=False)
|
||||
mock_cleanup.assert_called_once()
|
||||
|
||||
@mock.patch.object(rest.VMAXRest, 'get_storage_groups_from_volume',
|
||||
side_effect=[[VMAXCommonData.storagegroup_name_i],
|
||||
[VMAXCommonData.storagegroup_name_i],
|
||||
[VMAXCommonData.storagegroup_name_i,
|
||||
VMAXCommonData.storagegroup_name_f]])
|
||||
@mock.patch.object(masking.VMAXMasking, 'remove_volume_from_sg')
|
||||
@ -5342,14 +5344,19 @@ class VMAXMaskingTest(test.TestCase):
|
||||
'add_volume_to_default_storage_group')
|
||||
def test_cleanup_deletion(self, mock_add, mock_remove_vol, mock_get_sg):
|
||||
self.mask._cleanup_deletion(
|
||||
self.data.array, self.device_id, self.volume_name,
|
||||
self.extra_specs, None, True)
|
||||
self.data.array, self.data.test_volume, self.device_id,
|
||||
self.volume_name, self.extra_specs, None, True)
|
||||
mock_add.assert_not_called()
|
||||
self.mask._cleanup_deletion(
|
||||
self.data.array, self.device_id, self.volume_name,
|
||||
self.extra_specs, None, True)
|
||||
mock_add.assert_called_once_with(self.data.array, self.device_id,
|
||||
self.volume_name, self.extra_specs)
|
||||
self.data.array, self.data.test_volume, self.device_id,
|
||||
self.volume_name, self.extra_specs, self.data.connector, True)
|
||||
mock_add.assert_not_called()
|
||||
self.mask._cleanup_deletion(
|
||||
self.data.array, self.data.test_volume, self.device_id,
|
||||
self.volume_name, self.extra_specs, None, True)
|
||||
mock_add.assert_called_once_with(
|
||||
self.data.array, self.device_id,
|
||||
self.volume_name, self.extra_specs, volume=self.data.test_volume)
|
||||
|
||||
@mock.patch.object(masking.VMAXMasking, '_last_vol_in_sg')
|
||||
@mock.patch.object(masking.VMAXMasking, '_multiple_vols_in_sg')
|
||||
@ -5491,6 +5498,14 @@ class VMAXMaskingTest(test.TestCase):
|
||||
self.data.array, self.device_id, self.volume_name,
|
||||
self.extra_specs, src_sg=self.data.storagegroup_name_i)
|
||||
mock_move.assert_called_once()
|
||||
mock_add_sg.reset_mock()
|
||||
vol_grp_member = deepcopy(self.data.test_volume)
|
||||
vol_grp_member.group_id = self.data.test_vol_grp_name_id_only
|
||||
vol_grp_member.group = self.data.test_group
|
||||
self.mask.add_volume_to_default_storage_group(
|
||||
self.data.array, self.device_id, self.volume_name,
|
||||
self.extra_specs, volume=vol_grp_member)
|
||||
self.assertEqual(2, mock_add_sg.call_count)
|
||||
|
||||
@mock.patch.object(provision.VMAXProvision, 'create_storage_group')
|
||||
def test_get_or_create_default_storage_group(self, mock_create_sg):
|
||||
@ -5917,8 +5932,8 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
self.data.device_id2, self.data.rdf_group_no, "1",
|
||||
rep_extra_specs)
|
||||
mock_rm.assert_called_once_with(
|
||||
self.data.remote_array, self.data.device_id2, "1",
|
||||
rep_extra_specs, False)
|
||||
self.data.remote_array, self.data.test_volume,
|
||||
self.data.device_id2, "1", rep_extra_specs, False)
|
||||
# Cleanup legacy replication
|
||||
self.common.cleanup_lun_replication(
|
||||
self.data.test_legacy_vol, "1", self.data.device_id,
|
||||
@ -6122,9 +6137,9 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
rep_config = self.utils.get_replication_config(
|
||||
[self.replication_device])
|
||||
self.common.enable_rdf(
|
||||
self.data.array, self.data.device_id, self.data.rdf_group_no,
|
||||
rep_config, 'OS-1', self.data.remote_array, self.data.device_id2,
|
||||
self.extra_specs)
|
||||
self.data.array, self.data.test_volume, self.data.device_id,
|
||||
self.data.rdf_group_no, rep_config, 'OS-1',
|
||||
self.data.remote_array, self.data.device_id2, self.extra_specs)
|
||||
self.assertEqual(2, mock_remove.call_count)
|
||||
self.assertEqual(2, mock_add.call_count)
|
||||
|
||||
@ -6135,7 +6150,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
[self.replication_device])
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException, self.common.enable_rdf,
|
||||
self.data.array, self.data.device_id,
|
||||
self.data.array, self.data.test_volume, self.data.device_id,
|
||||
self.data.failed_resource, rep_config, 'OS-1',
|
||||
self.data.remote_array, self.data.device_id2, self.extra_specs)
|
||||
self.assertEqual(1, mock_cleanup.call_count)
|
||||
|
@ -254,19 +254,17 @@ class VMAXCommon(object):
|
||||
volume_name, volume_size, extra_specs))
|
||||
|
||||
if volume.group_id is not None:
|
||||
group_name = self._find_volume_group_name_from_id(
|
||||
extra_specs[utils.ARRAY], volume.group_id)
|
||||
if group_name is not None:
|
||||
self.masking.add_volume_to_storage_group(
|
||||
extra_specs[utils.ARRAY], volume_dict['device_id'],
|
||||
group_name, volume_name, extra_specs)
|
||||
group_name = self.provision.get_or_create_volume_group(
|
||||
extra_specs[utils.ARRAY], volume.group, extra_specs)
|
||||
self.masking.add_volume_to_storage_group(
|
||||
extra_specs[utils.ARRAY], volume_dict['device_id'],
|
||||
group_name, volume_name, extra_specs)
|
||||
|
||||
# Set-up volume replication, if enabled
|
||||
if self.utils.is_replication_enabled(extra_specs):
|
||||
rep_update = self._replicate_volume(volume, volume_name,
|
||||
volume_dict, extra_specs)
|
||||
model_update.update(rep_update)
|
||||
|
||||
LOG.info("Leaving create_volume: %(name)s. Volume dict: %(dict)s.",
|
||||
{'name': volume_name, 'dict': volume_dict})
|
||||
model_update.update(
|
||||
@ -424,7 +422,8 @@ class VMAXCommon(object):
|
||||
volume_name = volume.name
|
||||
LOG.debug("Detaching volume %s.", volume_name)
|
||||
return self.masking.remove_and_reset_members(
|
||||
array, device_id, volume_name, extra_specs, True, connector)
|
||||
array, volume, device_id, volume_name,
|
||||
extra_specs, True, connector)
|
||||
|
||||
def _unmap_lun(self, volume, connector):
|
||||
"""Unmaps a volume from the host.
|
||||
@ -587,7 +586,7 @@ class VMAXCommon(object):
|
||||
else:
|
||||
masking_view_dict['isLiveMigration'] = False
|
||||
rollback_dict = self.masking.setup_masking_view(
|
||||
masking_view_dict[utils.ARRAY],
|
||||
masking_view_dict[utils.ARRAY], volume,
|
||||
masking_view_dict, extra_specs)
|
||||
|
||||
# Find host lun id again after the volume is exported to the host.
|
||||
@ -1495,7 +1494,7 @@ class VMAXCommon(object):
|
||||
raise exception.VolumeBackendAPIException(data=error_message)
|
||||
|
||||
def _remove_vol_and_cleanup_replication(
|
||||
self, array, device_id, volume_name, extra_specs, volume=None):
|
||||
self, array, device_id, volume_name, extra_specs, volume):
|
||||
"""Remove a volume from its storage groups and cleanup replication.
|
||||
|
||||
:param array: the array serial number
|
||||
@ -1506,7 +1505,7 @@ class VMAXCommon(object):
|
||||
"""
|
||||
# Remove from any storage groups
|
||||
self.masking.remove_and_reset_members(
|
||||
array, device_id, volume_name, extra_specs, False)
|
||||
array, volume, device_id, volume_name, extra_specs, False)
|
||||
# Cleanup remote replication
|
||||
if self.utils.is_replication_enabled(extra_specs):
|
||||
self.cleanup_lun_replication(volume, volume_name,
|
||||
@ -1953,20 +1952,21 @@ class VMAXCommon(object):
|
||||
'targetHost': host['host'],
|
||||
'cc': do_change_compression})
|
||||
return self._migrate_volume(
|
||||
extra_specs[utils.ARRAY], device_id,
|
||||
extra_specs[utils.ARRAY], volume, device_id,
|
||||
extra_specs[utils.SRP], target_slo,
|
||||
target_workload, volume_name, new_type, extra_specs)
|
||||
|
||||
return False
|
||||
|
||||
def _migrate_volume(
|
||||
self, array, device_id, srp, target_slo,
|
||||
self, array, volume, device_id, srp, target_slo,
|
||||
target_workload, volume_name, new_type, extra_specs):
|
||||
"""Migrate from one slo/workload combination to another.
|
||||
|
||||
This requires moving the volume from its current SG to a
|
||||
new or existing SG that has the target attributes.
|
||||
:param array: the array serial number
|
||||
:param volume: the volume object
|
||||
:param device_id: the device number
|
||||
:param srp: the storage resource pool
|
||||
:param target_slo: the target service level
|
||||
@ -2005,7 +2005,7 @@ class VMAXCommon(object):
|
||||
array, device_id, target_sg_name, volume_name, extra_specs)
|
||||
else:
|
||||
self.masking.remove_and_reset_members(
|
||||
array, device_id, volume_name, target_extra_specs,
|
||||
array, volume, device_id, volume_name, target_extra_specs,
|
||||
reset=True)
|
||||
|
||||
# Check that it has been added.
|
||||
@ -2145,7 +2145,7 @@ class VMAXCommon(object):
|
||||
|
||||
# Enable rdf replication and establish the link
|
||||
rdf_dict = self.enable_rdf(
|
||||
array, device_id, rdf_group_no, self.rep_config,
|
||||
array, volume, device_id, rdf_group_no, self.rep_config,
|
||||
target_name, remote_array, target_device_id, extra_specs)
|
||||
|
||||
LOG.info('Successfully setup replication for %s.',
|
||||
@ -2189,7 +2189,7 @@ class VMAXCommon(object):
|
||||
if target_device is not None:
|
||||
# Clean-up target
|
||||
self.masking.remove_and_reset_members(
|
||||
remote_array, target_device, volume_name,
|
||||
remote_array, volume, target_device, volume_name,
|
||||
rep_extra_specs, False)
|
||||
self._cleanup_remote_target(
|
||||
array, remote_array, device_id, target_device,
|
||||
@ -2494,13 +2494,13 @@ class VMAXCommon(object):
|
||||
# have a mix of replicated and non-replicated volumes as
|
||||
# the SRDF groups become unmanageable).
|
||||
self.masking.remove_and_reset_members(
|
||||
array, device_id, volume_name, extra_specs, False)
|
||||
array, volume, device_id, volume_name, extra_specs, False)
|
||||
|
||||
# Repeat on target side
|
||||
rep_extra_specs = self._get_replication_extra_specs(
|
||||
extra_specs, self.rep_config)
|
||||
self.masking.remove_and_reset_members(
|
||||
remote_array, target_device, volume_name,
|
||||
remote_array, volume, target_device, volume_name,
|
||||
rep_extra_specs, False)
|
||||
|
||||
LOG.info("Breaking replication relationship...")
|
||||
@ -2539,11 +2539,12 @@ class VMAXCommon(object):
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||
|
||||
def enable_rdf(self, array, device_id, rdf_group_no, rep_config,
|
||||
def enable_rdf(self, array, volume, device_id, rdf_group_no, rep_config,
|
||||
target_name, remote_array, target_device, extra_specs):
|
||||
"""Create a replication relationship with a target volume.
|
||||
|
||||
:param array: the array serial number
|
||||
:param volume: the volume object
|
||||
:param device_id: the device id
|
||||
:param rdf_group_no: the rdf group number
|
||||
:param rep_config: the replication config
|
||||
@ -2559,10 +2560,10 @@ class VMAXCommon(object):
|
||||
# Remove source and target instances from their
|
||||
# default storage groups
|
||||
self.masking.remove_and_reset_members(
|
||||
array, device_id, target_name, extra_specs, False)
|
||||
array, volume, device_id, target_name, extra_specs, False)
|
||||
|
||||
self.masking.remove_and_reset_members(
|
||||
remote_array, target_device, target_name,
|
||||
remote_array, volume, target_device, target_name,
|
||||
rep_extra_specs, False)
|
||||
|
||||
# Establish replication relationship
|
||||
@ -2585,7 +2586,7 @@ class VMAXCommon(object):
|
||||
"group. Volume name: %(name)s "),
|
||||
{'name': target_name})
|
||||
self.masking.remove_and_reset_members(
|
||||
remote_array, target_device, target_name,
|
||||
remote_array, volume, target_device, target_name,
|
||||
rep_extra_specs, False)
|
||||
self._cleanup_remote_target(
|
||||
array, remote_array, device_id, target_device,
|
||||
@ -3009,21 +3010,6 @@ class VMAXCommon(object):
|
||||
|
||||
return model_update, snapshots_model_update
|
||||
|
||||
def _find_volume_group_name_from_id(self, array, group_id):
|
||||
"""Finds the volume group name given its id
|
||||
|
||||
:param array: the array serial number
|
||||
:param group_id: the group id
|
||||
:returns: group_name: Name of the group
|
||||
"""
|
||||
group_name = None
|
||||
sg_list = self.rest.get_storage_group_list(array)
|
||||
for sg in sg_list:
|
||||
if group_id in sg:
|
||||
group_name = sg
|
||||
return group_name
|
||||
return group_name
|
||||
|
||||
def _find_volume_group(self, array, group):
|
||||
"""Finds a volume group given the group.
|
||||
|
||||
|
@ -40,24 +40,25 @@ class VMAXMasking(object):
|
||||
self.provision = provision.VMAXProvision(self.rest)
|
||||
|
||||
def setup_masking_view(
|
||||
self, serial_number, masking_view_dict, extra_specs):
|
||||
self, serial_number, volume, masking_view_dict, extra_specs):
|
||||
|
||||
@coordination.synchronized("emc-mv-{maskingview_name}")
|
||||
def do_get_or_create_masking_view_and_map_lun(maskingview_name):
|
||||
return self.get_or_create_masking_view_and_map_lun(
|
||||
serial_number, maskingview_name, masking_view_dict,
|
||||
serial_number, volume, maskingview_name, masking_view_dict,
|
||||
extra_specs)
|
||||
return do_get_or_create_masking_view_and_map_lun(
|
||||
masking_view_dict[utils.MV_NAME])
|
||||
|
||||
def get_or_create_masking_view_and_map_lun(
|
||||
self, serial_number, maskingview_name, masking_view_dict,
|
||||
self, serial_number, volume, maskingview_name, masking_view_dict,
|
||||
extra_specs):
|
||||
"""Get or Create a masking view and add a volume to the storage group.
|
||||
|
||||
Given a masking view dict either get or create a masking view and add
|
||||
the volume to the associated storage group.
|
||||
:param serial_number: the array serial number
|
||||
:param volume: the volume object
|
||||
:param maskingview_name: the masking view name
|
||||
:param masking_view_dict: the masking view dict
|
||||
:param extra_specs: the extra specifications
|
||||
@ -112,7 +113,7 @@ class VMAXMasking(object):
|
||||
|
||||
if rollback_dict['slo'] is not None:
|
||||
self.check_if_rollback_action_for_masking_required(
|
||||
serial_number, device_id, masking_view_dict)
|
||||
serial_number, volume, device_id, masking_view_dict)
|
||||
|
||||
else:
|
||||
self._check_adding_volume_to_storage_group(
|
||||
@ -832,7 +833,7 @@ class VMAXMasking(object):
|
||||
return error_message
|
||||
|
||||
def check_if_rollback_action_for_masking_required(
|
||||
self, serial_number, device_id, rollback_dict):
|
||||
self, serial_number, volume, device_id, rollback_dict):
|
||||
"""Rollback action for volumes with an associated service level.
|
||||
|
||||
We need to be able to return the volume to the default storage group
|
||||
@ -841,6 +842,7 @@ class VMAXMasking(object):
|
||||
the exception occurred. We also may need to clean up any unused
|
||||
initiator groups.
|
||||
:param serial_number: the array serial number
|
||||
:param volume: the volume object
|
||||
:param device_id: the device id
|
||||
:param rollback_dict: the rollback dict
|
||||
:returns: error message -- string, or None
|
||||
@ -883,9 +885,10 @@ class VMAXMasking(object):
|
||||
# Remove it from its current storage group and return it
|
||||
# to its default masking view if slo is defined.
|
||||
self.remove_and_reset_members(
|
||||
serial_number, device_id,
|
||||
serial_number, volume, device_id,
|
||||
rollback_dict['volume_name'],
|
||||
rollback_dict['extra_specs'])
|
||||
rollback_dict['extra_specs'], True,
|
||||
rollback_dict['connector'])
|
||||
message = (_("Rollback - Volume in another storage "
|
||||
"group besides default storage group."))
|
||||
except Exception as e:
|
||||
@ -1019,11 +1022,12 @@ class VMAXMasking(object):
|
||||
|
||||
@coordination.synchronized("emc-vol-{device_id}")
|
||||
def remove_and_reset_members(
|
||||
self, serial_number, device_id, volume_name, extra_specs,
|
||||
reset=True, connector=None):
|
||||
self, serial_number, volume, device_id, volume_name,
|
||||
extra_specs, reset=True, connector=None):
|
||||
"""This is called on a delete, unmap device or rollback.
|
||||
|
||||
:param serial_number: the array serial number
|
||||
:param volume: the volume object
|
||||
:param device_id: the volume device id
|
||||
:param volume_name: the volume name
|
||||
:param extra_specs: additional info
|
||||
@ -1031,33 +1035,48 @@ class VMAXMasking(object):
|
||||
:param connector: the connector object (optional)
|
||||
"""
|
||||
self._cleanup_deletion(
|
||||
serial_number, device_id, volume_name,
|
||||
serial_number, volume, device_id, volume_name,
|
||||
extra_specs, connector, reset)
|
||||
|
||||
def _cleanup_deletion(
|
||||
self, serial_number, device_id, volume_name,
|
||||
self, serial_number, volume, device_id, volume_name,
|
||||
extra_specs, connector, reset):
|
||||
"""Prepare a volume for a delete operation.
|
||||
|
||||
:param serial_number: the array serial number
|
||||
:param volume: the volume object
|
||||
:param device_id: the volume device id
|
||||
:param volume_name: the volume name
|
||||
:param extra_specs: the extra specifications
|
||||
:param connector: the connector object
|
||||
"""
|
||||
move = False
|
||||
short_host_name = None
|
||||
storagegroup_names = (self.rest.get_storage_groups_from_volume(
|
||||
serial_number, device_id))
|
||||
if storagegroup_names:
|
||||
if len(storagegroup_names) == 1 and reset is True:
|
||||
move = True
|
||||
for sg_name in storagegroup_names:
|
||||
self.remove_volume_from_sg(
|
||||
serial_number, device_id, volume_name, sg_name,
|
||||
extra_specs, connector, move)
|
||||
elif connector is not None and reset is True:
|
||||
short_host_name = self.utils.get_host_short_name(
|
||||
connector['host'])
|
||||
move = True
|
||||
if short_host_name:
|
||||
for sg_name in storagegroup_names:
|
||||
if short_host_name in sg_name:
|
||||
self.remove_volume_from_sg(
|
||||
serial_number, device_id, volume_name, sg_name,
|
||||
extra_specs, connector, move)
|
||||
break
|
||||
else:
|
||||
for sg_name in storagegroup_names:
|
||||
self.remove_volume_from_sg(
|
||||
serial_number, device_id, volume_name, sg_name,
|
||||
extra_specs, connector, move)
|
||||
if reset is True and move is False:
|
||||
self.add_volume_to_default_storage_group(
|
||||
serial_number, device_id, volume_name, extra_specs)
|
||||
serial_number, device_id, volume_name,
|
||||
extra_specs, volume=volume)
|
||||
|
||||
def remove_volume_from_sg(
|
||||
self, serial_number, device_id, vol_name, storagegroup_name,
|
||||
@ -1386,7 +1405,7 @@ class VMAXMasking(object):
|
||||
|
||||
def add_volume_to_default_storage_group(
|
||||
self, serial_number, device_id, volume_name,
|
||||
extra_specs, src_sg=None):
|
||||
extra_specs, src_sg=None, volume=None):
|
||||
"""Return volume to its default storage group.
|
||||
|
||||
:param serial_number: the array serial number
|
||||
@ -1394,6 +1413,7 @@ class VMAXMasking(object):
|
||||
:param volume_name: the volume name
|
||||
:param extra_specs: the extra specifications
|
||||
:param src_sg: the source storage group, if any
|
||||
:param volume: the volume object
|
||||
"""
|
||||
do_disable_compression = self.utils.is_compression_disabled(
|
||||
extra_specs)
|
||||
@ -1414,6 +1434,16 @@ class VMAXMasking(object):
|
||||
self._check_adding_volume_to_storage_group(
|
||||
serial_number, device_id, storagegroup_name, volume_name,
|
||||
extra_specs)
|
||||
if volume:
|
||||
# Need to check if the volume needs to be returned to a
|
||||
# generic volume group. This may be necessary in a force-detach
|
||||
# situation.
|
||||
if volume.group_id is not None:
|
||||
vol_grp_name = self.provision.get_or_create_volume_group(
|
||||
serial_number, volume.group, extra_specs)
|
||||
self._check_adding_volume_to_storage_group(
|
||||
serial_number, device_id,
|
||||
vol_grp_name, volume_name, extra_specs)
|
||||
|
||||
def get_or_create_default_storage_group(
|
||||
self, serial_number, srp, slo, workload, extra_specs,
|
||||
|
@ -448,6 +448,33 @@ class VMAXProvision(object):
|
||||
self.rest.modify_rdf_device_pair(
|
||||
array, device_id, rdf_group, extra_specs, split=False)
|
||||
|
||||
def get_or_create_volume_group(self, array, group, extra_specs):
|
||||
"""Get or create a volume group.
|
||||
|
||||
Sometimes it may be necessary to recreate a volume group on the
|
||||
backend - for example, when the last member volume has been removed
|
||||
from the group, but the cinder group object has not been deleted.
|
||||
:param array: the array serial number
|
||||
:param group: the group object
|
||||
:param extra_specs: the extra specifications
|
||||
:return: group name
|
||||
"""
|
||||
vol_grp_name = self.utils.update_volume_group_name(group)
|
||||
return self.get_or_create_group(array, vol_grp_name, extra_specs)
|
||||
|
||||
def get_or_create_group(self, array, group_name, extra_specs):
|
||||
"""Get or create a generic volume group.
|
||||
|
||||
:param array: the array serial number
|
||||
:param group_name: the group name
|
||||
:param extra_specs: the extra specifications
|
||||
:return: group name
|
||||
"""
|
||||
storage_group = self.rest.get_storage_group(array, group_name)
|
||||
if not storage_group:
|
||||
self.create_volume_group(array, group_name, extra_specs)
|
||||
return group_name
|
||||
|
||||
def create_volume_group(self, array, group_name, extra_specs):
|
||||
"""Create a generic volume group.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user