Merge "VMAX driver - detach volume shouldn't remove from volume groups"

This commit is contained in:
Zuul 2017-10-31 12:09:25 +00:00 committed by Gerrit Code Review
commit a11dcdb0f7
4 changed files with 165 additions and 107 deletions

View File

@ -2877,6 +2877,16 @@ class VMAXProvisionTest(test.TestCase):
self.data.group_snapshot_name, self.data.extra_specs, self.data.group_snapshot_name, self.data.extra_specs,
unlink=True) 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): class VMAXCommonTest(test.TestCase):
def setUp(self): def setUp(self):
@ -2999,7 +3009,7 @@ class VMAXCommonTest(test.TestCase):
self.common._remove_members(array, volume, device_id, self.common._remove_members(array, volume, device_id,
extra_specs, self.data.connector) extra_specs, self.data.connector)
mock_rm.assert_called_once_with( mock_rm.assert_called_once_with(
array, device_id, volume_name, array, volume, device_id, volume_name,
extra_specs, True, self.data.connector) extra_specs, True, self.data.connector)
def test_unmap_lun(self): def test_unmap_lun(self):
@ -3567,9 +3577,9 @@ class VMAXCommonTest(test.TestCase):
with mock.patch.object( with mock.patch.object(
self.common, 'cleanup_lun_replication') as mock_clean: self.common, 'cleanup_lun_replication') as mock_clean:
self.common._remove_vol_and_cleanup_replication( 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( 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() mock_clean.assert_not_called()
self.common._remove_vol_and_cleanup_replication( self.common._remove_vol_and_cleanup_replication(
array, device_id, volume_name, extra_specs, volume) array, device_id, volume_name, extra_specs, volume)
@ -3929,7 +3939,7 @@ class VMAXCommonTest(test.TestCase):
self.common._slo_workload_migration( self.common._slo_workload_migration(
device_id, volume, host, volume_name, new_type, extra_specs) device_id, volume, host, volume_name, new_type, extra_specs)
self.common._migrate_volume.assert_called_once_with( 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', extra_specs[utils.SRP], 'Silver',
'OLTP', volume_name, new_type, extra_specs) 'OLTP', volume_name, new_type, extra_specs)
@ -3974,7 +3984,7 @@ class VMAXCommonTest(test.TestCase):
extra_specs) extra_specs)
self.assertTrue(bool(migrate_status)) self.assertTrue(bool(migrate_status))
self.common._migrate_volume.assert_called_once_with( 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, extra_specs[utils.SRP], self.data.slo,
self.data.workload, volume_name, new_type, extra_specs) self.data.workload, volume_name, new_type, extra_specs)
@ -3985,25 +3995,28 @@ class VMAXCommonTest(test.TestCase):
device_id = self.data.device_id device_id = self.data.device_id
volume_name = self.data.test_volume.name volume_name = self.data.test_volume.name
extra_specs = self.data.extra_specs extra_specs = self.data.extra_specs
volume = self.data.test_volume
new_type = {'extra_specs': {}} new_type = {'extra_specs': {}}
migrate_status = self.common._migrate_volume( migrate_status = self.common._migrate_volume(
self.data.array, device_id, self.data.srp, self.data.slo, self.data.array, volume, device_id, self.data.srp,
self.data.workload, volume_name, new_type, extra_specs) self.data.slo, self.data.workload, volume_name,
new_type, extra_specs)
self.assertTrue(migrate_status) self.assertTrue(migrate_status)
target_extra_specs = { target_extra_specs = {
'array': self.data.array, 'interval': 3, 'array': self.data.array, 'interval': 3,
'retries': 120, 'slo': self.data.slo, 'retries': 120, 'slo': self.data.slo,
'srp': self.data.srp, 'workload': self.data.workload} 'srp': self.data.srp, 'workload': self.data.workload}
mock_remove.assert_called_once_with( 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) target_extra_specs, reset=True)
mock_remove.reset_mock() mock_remove.reset_mock()
with mock.patch.object( with mock.patch.object(
self.rest, 'get_storage_groups_from_volume', self.rest, 'get_storage_groups_from_volume',
return_value=[]): return_value=[]):
migrate_status = self.common._migrate_volume( migrate_status = self.common._migrate_volume(
self.data.array, device_id, self.data.srp, self.data.slo, self.data.array, volume, device_id, self.data.srp,
self.data.workload, volume_name, new_type, extra_specs) self.data.slo, self.data.workload, volume_name,
new_type, extra_specs)
self.assertTrue(migrate_status) self.assertTrue(migrate_status)
mock_remove.assert_not_called() mock_remove.assert_not_called()
@ -4017,7 +4030,8 @@ class VMAXCommonTest(test.TestCase):
self.masking, 'get_or_create_default_storage_group', self.masking, 'get_or_create_default_storage_group',
side_effect=exception.VolumeBackendAPIException): side_effect=exception.VolumeBackendAPIException):
migrate_status = self.common._migrate_volume( 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.data.workload, volume_name, new_type, extra_specs)
self.assertFalse(migrate_status) self.assertFalse(migrate_status)
@ -4030,7 +4044,8 @@ class VMAXCommonTest(test.TestCase):
self.rest, 'is_volume_in_storagegroup', self.rest, 'is_volume_in_storagegroup',
return_value=False): return_value=False):
migrate_status = self.common._migrate_volume( 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.data.workload, volume_name, new_type, extra_specs)
self.assertFalse(migrate_status) self.assertFalse(migrate_status)
@ -4080,26 +4095,6 @@ class VMAXCommonTest(test.TestCase):
self.data.srp, volume_name, False) self.data.srp, volume_name, False)
self.assertEqual(ref_return, return_val) 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): def test_find_volume_group(self):
group = self.data.test_group_1 group = self.data.test_group_1
array = self.data.array array = self.data.array
@ -4845,7 +4840,8 @@ class VMAXMaskingTest(test.TestCase):
'get_or_create_masking_view_and_map_lun') 'get_or_create_masking_view_and_map_lun')
def test_setup_masking_view(self, mock_get_or_create_mv): def test_setup_masking_view(self, mock_get_or_create_mv):
self.driver.masking.setup_masking_view( 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_get_or_create_mv.assert_called_once()
@mock.patch.object( @mock.patch.object(
@ -4869,19 +4865,22 @@ class VMAXMaskingTest(test.TestCase):
mock_add_volume): mock_add_volume):
rollback_dict = ( rollback_dict = (
self.driver.masking.get_or_create_masking_view_and_map_lun( 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, self.extra_specs))
self.assertEqual(self.maskingviewdict, rollback_dict) self.assertEqual(self.maskingviewdict, rollback_dict)
self.assertRaises( self.assertRaises(
exception.VolumeBackendAPIException, exception.VolumeBackendAPIException,
self.driver.masking.get_or_create_masking_view_and_map_lun, 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, self.extra_specs)
self.maskingviewdict['slo'] = None self.maskingviewdict['slo'] = None
self.assertRaises( self.assertRaises(
exception.VolumeBackendAPIException, exception.VolumeBackendAPIException,
self.driver.masking.get_or_create_masking_view_and_map_lun, 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, self.extra_specs)
@mock.patch.object( @mock.patch.object(
@ -5246,14 +5245,16 @@ class VMAXMaskingTest(test.TestCase):
self.assertRaises( self.assertRaises(
exception.VolumeBackendAPIException, exception.VolumeBackendAPIException,
self.mask.check_if_rollback_action_for_masking_required, 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, with mock.patch.object(masking.VMAXMasking,
'remove_and_reset_members'): 'remove_and_reset_members'):
self.maskingviewdict[ self.maskingviewdict[
'default_sg_name'] = self.data.defaultstoragegroup_name 'default_sg_name'] = self.data.defaultstoragegroup_name
error_message = ( error_message = (
self.mask.check_if_rollback_action_for_masking_required( 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) self.assertIsNone(error_message)
@mock.patch.object(rest.VMAXRest, 'delete_masking_view') @mock.patch.object(rest.VMAXRest, 'delete_masking_view')
@ -5328,13 +5329,14 @@ class VMAXMaskingTest(test.TestCase):
@mock.patch.object(masking.VMAXMasking, '_cleanup_deletion') @mock.patch.object(masking.VMAXMasking, '_cleanup_deletion')
def test_remove_and_reset_members(self, mock_cleanup): def test_remove_and_reset_members(self, mock_cleanup):
self.mask.remove_and_reset_members(self.data.array, self.device_id, self.mask.remove_and_reset_members(
self.volume_name, self.extra_specs, self.data.array, self.device_id, self.data.test_volume,
reset=False) self.volume_name, self.extra_specs, reset=False)
mock_cleanup.assert_called_once() mock_cleanup.assert_called_once()
@mock.patch.object(rest.VMAXRest, 'get_storage_groups_from_volume', @mock.patch.object(rest.VMAXRest, 'get_storage_groups_from_volume',
side_effect=[[VMAXCommonData.storagegroup_name_i], side_effect=[[VMAXCommonData.storagegroup_name_i],
[VMAXCommonData.storagegroup_name_i],
[VMAXCommonData.storagegroup_name_i, [VMAXCommonData.storagegroup_name_i,
VMAXCommonData.storagegroup_name_f]]) VMAXCommonData.storagegroup_name_f]])
@mock.patch.object(masking.VMAXMasking, 'remove_volume_from_sg') @mock.patch.object(masking.VMAXMasking, 'remove_volume_from_sg')
@ -5342,14 +5344,19 @@ class VMAXMaskingTest(test.TestCase):
'add_volume_to_default_storage_group') 'add_volume_to_default_storage_group')
def test_cleanup_deletion(self, mock_add, mock_remove_vol, mock_get_sg): def test_cleanup_deletion(self, mock_add, mock_remove_vol, mock_get_sg):
self.mask._cleanup_deletion( self.mask._cleanup_deletion(
self.data.array, self.device_id, self.volume_name, self.data.array, self.data.test_volume, self.device_id,
self.extra_specs, None, True) self.volume_name, self.extra_specs, None, True)
mock_add.assert_not_called() mock_add.assert_not_called()
self.mask._cleanup_deletion( self.mask._cleanup_deletion(
self.data.array, self.device_id, self.volume_name, self.data.array, self.data.test_volume, self.device_id,
self.extra_specs, None, True) self.volume_name, self.extra_specs, self.data.connector, True)
mock_add.assert_called_once_with(self.data.array, self.device_id, mock_add.assert_not_called()
self.volume_name, self.extra_specs) 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, '_last_vol_in_sg')
@mock.patch.object(masking.VMAXMasking, '_multiple_vols_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.data.array, self.device_id, self.volume_name,
self.extra_specs, src_sg=self.data.storagegroup_name_i) self.extra_specs, src_sg=self.data.storagegroup_name_i)
mock_move.assert_called_once() 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') @mock.patch.object(provision.VMAXProvision, 'create_storage_group')
def test_get_or_create_default_storage_group(self, mock_create_sg): 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", self.data.device_id2, self.data.rdf_group_no, "1",
rep_extra_specs) rep_extra_specs)
mock_rm.assert_called_once_with( mock_rm.assert_called_once_with(
self.data.remote_array, self.data.device_id2, "1", self.data.remote_array, self.data.test_volume,
rep_extra_specs, False) self.data.device_id2, "1", rep_extra_specs, False)
# Cleanup legacy replication # Cleanup legacy replication
self.common.cleanup_lun_replication( self.common.cleanup_lun_replication(
self.data.test_legacy_vol, "1", self.data.device_id, 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( rep_config = self.utils.get_replication_config(
[self.replication_device]) [self.replication_device])
self.common.enable_rdf( self.common.enable_rdf(
self.data.array, self.data.device_id, self.data.rdf_group_no, self.data.array, self.data.test_volume, self.data.device_id,
rep_config, 'OS-1', self.data.remote_array, self.data.device_id2, self.data.rdf_group_no, rep_config, 'OS-1',
self.extra_specs) self.data.remote_array, self.data.device_id2, self.extra_specs)
self.assertEqual(2, mock_remove.call_count) self.assertEqual(2, mock_remove.call_count)
self.assertEqual(2, mock_add.call_count) self.assertEqual(2, mock_add.call_count)
@ -6135,7 +6150,7 @@ class VMAXCommonReplicationTest(test.TestCase):
[self.replication_device]) [self.replication_device])
self.assertRaises( self.assertRaises(
exception.VolumeBackendAPIException, self.common.enable_rdf, 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.failed_resource, rep_config, 'OS-1',
self.data.remote_array, self.data.device_id2, self.extra_specs) self.data.remote_array, self.data.device_id2, self.extra_specs)
self.assertEqual(1, mock_cleanup.call_count) self.assertEqual(1, mock_cleanup.call_count)

View File

@ -254,9 +254,8 @@ class VMAXCommon(object):
volume_name, volume_size, extra_specs)) volume_name, volume_size, extra_specs))
if volume.group_id is not None: if volume.group_id is not None:
group_name = self._find_volume_group_name_from_id( group_name = self.provision.get_or_create_volume_group(
extra_specs[utils.ARRAY], volume.group_id) extra_specs[utils.ARRAY], volume.group, extra_specs)
if group_name is not None:
self.masking.add_volume_to_storage_group( self.masking.add_volume_to_storage_group(
extra_specs[utils.ARRAY], volume_dict['device_id'], extra_specs[utils.ARRAY], volume_dict['device_id'],
group_name, volume_name, extra_specs) group_name, volume_name, extra_specs)
@ -266,7 +265,6 @@ class VMAXCommon(object):
rep_update = self._replicate_volume(volume, volume_name, rep_update = self._replicate_volume(volume, volume_name,
volume_dict, extra_specs) volume_dict, extra_specs)
model_update.update(rep_update) model_update.update(rep_update)
LOG.info("Leaving create_volume: %(name)s. Volume dict: %(dict)s.", LOG.info("Leaving create_volume: %(name)s. Volume dict: %(dict)s.",
{'name': volume_name, 'dict': volume_dict}) {'name': volume_name, 'dict': volume_dict})
model_update.update( model_update.update(
@ -424,7 +422,8 @@ class VMAXCommon(object):
volume_name = volume.name volume_name = volume.name
LOG.debug("Detaching volume %s.", volume_name) LOG.debug("Detaching volume %s.", volume_name)
return self.masking.remove_and_reset_members( 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): def _unmap_lun(self, volume, connector):
"""Unmaps a volume from the host. """Unmaps a volume from the host.
@ -587,7 +586,7 @@ class VMAXCommon(object):
else: else:
masking_view_dict['isLiveMigration'] = False masking_view_dict['isLiveMigration'] = False
rollback_dict = self.masking.setup_masking_view( rollback_dict = self.masking.setup_masking_view(
masking_view_dict[utils.ARRAY], masking_view_dict[utils.ARRAY], volume,
masking_view_dict, extra_specs) masking_view_dict, extra_specs)
# Find host lun id again after the volume is exported to the host. # 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) raise exception.VolumeBackendAPIException(data=error_message)
def _remove_vol_and_cleanup_replication( 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. """Remove a volume from its storage groups and cleanup replication.
:param array: the array serial number :param array: the array serial number
@ -1506,7 +1505,7 @@ class VMAXCommon(object):
""" """
# Remove from any storage groups # Remove from any storage groups
self.masking.remove_and_reset_members( 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 # Cleanup remote replication
if self.utils.is_replication_enabled(extra_specs): if self.utils.is_replication_enabled(extra_specs):
self.cleanup_lun_replication(volume, volume_name, self.cleanup_lun_replication(volume, volume_name,
@ -1953,20 +1952,21 @@ class VMAXCommon(object):
'targetHost': host['host'], 'targetHost': host['host'],
'cc': do_change_compression}) 'cc': do_change_compression})
return self._migrate_volume( return self._migrate_volume(
extra_specs[utils.ARRAY], device_id, extra_specs[utils.ARRAY], volume, device_id,
extra_specs[utils.SRP], target_slo, extra_specs[utils.SRP], target_slo,
target_workload, volume_name, new_type, extra_specs) target_workload, volume_name, new_type, extra_specs)
return False return False
def _migrate_volume( 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): target_workload, volume_name, new_type, extra_specs):
"""Migrate from one slo/workload combination to another. """Migrate from one slo/workload combination to another.
This requires moving the volume from its current SG to a This requires moving the volume from its current SG to a
new or existing SG that has the target attributes. new or existing SG that has the target attributes.
:param array: the array serial number :param array: the array serial number
:param volume: the volume object
:param device_id: the device number :param device_id: the device number
:param srp: the storage resource pool :param srp: the storage resource pool
:param target_slo: the target service level :param target_slo: the target service level
@ -2005,7 +2005,7 @@ class VMAXCommon(object):
array, device_id, target_sg_name, volume_name, extra_specs) array, device_id, target_sg_name, volume_name, extra_specs)
else: else:
self.masking.remove_and_reset_members( 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) reset=True)
# Check that it has been added. # Check that it has been added.
@ -2145,7 +2145,7 @@ class VMAXCommon(object):
# Enable rdf replication and establish the link # Enable rdf replication and establish the link
rdf_dict = self.enable_rdf( 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) target_name, remote_array, target_device_id, extra_specs)
LOG.info('Successfully setup replication for %s.', LOG.info('Successfully setup replication for %s.',
@ -2189,7 +2189,7 @@ class VMAXCommon(object):
if target_device is not None: if target_device is not None:
# Clean-up target # Clean-up target
self.masking.remove_and_reset_members( self.masking.remove_and_reset_members(
remote_array, target_device, volume_name, remote_array, volume, target_device, volume_name,
rep_extra_specs, False) rep_extra_specs, False)
self._cleanup_remote_target( self._cleanup_remote_target(
array, remote_array, device_id, target_device, array, remote_array, device_id, target_device,
@ -2494,13 +2494,13 @@ class VMAXCommon(object):
# have a mix of replicated and non-replicated volumes as # have a mix of replicated and non-replicated volumes as
# the SRDF groups become unmanageable). # the SRDF groups become unmanageable).
self.masking.remove_and_reset_members( 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 # Repeat on target side
rep_extra_specs = self._get_replication_extra_specs( rep_extra_specs = self._get_replication_extra_specs(
extra_specs, self.rep_config) extra_specs, self.rep_config)
self.masking.remove_and_reset_members( self.masking.remove_and_reset_members(
remote_array, target_device, volume_name, remote_array, volume, target_device, volume_name,
rep_extra_specs, False) rep_extra_specs, False)
LOG.info("Breaking replication relationship...") LOG.info("Breaking replication relationship...")
@ -2539,11 +2539,12 @@ class VMAXCommon(object):
LOG.error(exception_message) LOG.error(exception_message)
raise exception.VolumeBackendAPIException(data=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): target_name, remote_array, target_device, extra_specs):
"""Create a replication relationship with a target volume. """Create a replication relationship with a target volume.
:param array: the array serial number :param array: the array serial number
:param volume: the volume object
:param device_id: the device id :param device_id: the device id
:param rdf_group_no: the rdf group number :param rdf_group_no: the rdf group number
:param rep_config: the replication config :param rep_config: the replication config
@ -2559,10 +2560,10 @@ class VMAXCommon(object):
# Remove source and target instances from their # Remove source and target instances from their
# default storage groups # default storage groups
self.masking.remove_and_reset_members( 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( self.masking.remove_and_reset_members(
remote_array, target_device, target_name, remote_array, volume, target_device, target_name,
rep_extra_specs, False) rep_extra_specs, False)
# Establish replication relationship # Establish replication relationship
@ -2585,7 +2586,7 @@ class VMAXCommon(object):
"group. Volume name: %(name)s "), "group. Volume name: %(name)s "),
{'name': target_name}) {'name': target_name})
self.masking.remove_and_reset_members( self.masking.remove_and_reset_members(
remote_array, target_device, target_name, remote_array, volume, target_device, target_name,
rep_extra_specs, False) rep_extra_specs, False)
self._cleanup_remote_target( self._cleanup_remote_target(
array, remote_array, device_id, target_device, array, remote_array, device_id, target_device,
@ -3009,21 +3010,6 @@ class VMAXCommon(object):
return model_update, snapshots_model_update 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): def _find_volume_group(self, array, group):
"""Finds a volume group given the group. """Finds a volume group given the group.

View File

@ -40,24 +40,25 @@ class VMAXMasking(object):
self.provision = provision.VMAXProvision(self.rest) self.provision = provision.VMAXProvision(self.rest)
def setup_masking_view( 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}") @coordination.synchronized("emc-mv-{maskingview_name}")
def do_get_or_create_masking_view_and_map_lun(maskingview_name): def do_get_or_create_masking_view_and_map_lun(maskingview_name):
return self.get_or_create_masking_view_and_map_lun( 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) extra_specs)
return do_get_or_create_masking_view_and_map_lun( return do_get_or_create_masking_view_and_map_lun(
masking_view_dict[utils.MV_NAME]) masking_view_dict[utils.MV_NAME])
def get_or_create_masking_view_and_map_lun( 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): extra_specs):
"""Get or Create a masking view and add a volume to the storage group. """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 Given a masking view dict either get or create a masking view and add
the volume to the associated storage group. the volume to the associated storage group.
:param serial_number: the array serial number :param serial_number: the array serial number
:param volume: the volume object
:param maskingview_name: the masking view name :param maskingview_name: the masking view name
:param masking_view_dict: the masking view dict :param masking_view_dict: the masking view dict
:param extra_specs: the extra specifications :param extra_specs: the extra specifications
@ -112,7 +113,7 @@ class VMAXMasking(object):
if rollback_dict['slo'] is not None: if rollback_dict['slo'] is not None:
self.check_if_rollback_action_for_masking_required( self.check_if_rollback_action_for_masking_required(
serial_number, device_id, masking_view_dict) serial_number, volume, device_id, masking_view_dict)
else: else:
self._check_adding_volume_to_storage_group( self._check_adding_volume_to_storage_group(
@ -832,7 +833,7 @@ class VMAXMasking(object):
return error_message return error_message
def check_if_rollback_action_for_masking_required( 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. """Rollback action for volumes with an associated service level.
We need to be able to return the volume to the default storage group 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 the exception occurred. We also may need to clean up any unused
initiator groups. initiator groups.
:param serial_number: the array serial number :param serial_number: the array serial number
:param volume: the volume object
:param device_id: the device id :param device_id: the device id
:param rollback_dict: the rollback dict :param rollback_dict: the rollback dict
:returns: error message -- string, or None :returns: error message -- string, or None
@ -883,9 +885,10 @@ class VMAXMasking(object):
# Remove it from its current storage group and return it # Remove it from its current storage group and return it
# to its default masking view if slo is defined. # to its default masking view if slo is defined.
self.remove_and_reset_members( self.remove_and_reset_members(
serial_number, device_id, serial_number, volume, device_id,
rollback_dict['volume_name'], rollback_dict['volume_name'],
rollback_dict['extra_specs']) rollback_dict['extra_specs'], True,
rollback_dict['connector'])
message = (_("Rollback - Volume in another storage " message = (_("Rollback - Volume in another storage "
"group besides default storage group.")) "group besides default storage group."))
except Exception as e: except Exception as e:
@ -1019,11 +1022,12 @@ class VMAXMasking(object):
@coordination.synchronized("emc-vol-{device_id}") @coordination.synchronized("emc-vol-{device_id}")
def remove_and_reset_members( def remove_and_reset_members(
self, serial_number, device_id, volume_name, extra_specs, self, serial_number, volume, device_id, volume_name,
reset=True, connector=None): extra_specs, reset=True, connector=None):
"""This is called on a delete, unmap device or rollback. """This is called on a delete, unmap device or rollback.
:param serial_number: the array serial number :param serial_number: the array serial number
:param volume: the volume object
:param device_id: the volume device id :param device_id: the volume device id
:param volume_name: the volume name :param volume_name: the volume name
:param extra_specs: additional info :param extra_specs: additional info
@ -1031,33 +1035,48 @@ class VMAXMasking(object):
:param connector: the connector object (optional) :param connector: the connector object (optional)
""" """
self._cleanup_deletion( self._cleanup_deletion(
serial_number, device_id, volume_name, serial_number, volume, device_id, volume_name,
extra_specs, connector, reset) extra_specs, connector, reset)
def _cleanup_deletion( def _cleanup_deletion(
self, serial_number, device_id, volume_name, self, serial_number, volume, device_id, volume_name,
extra_specs, connector, reset): extra_specs, connector, reset):
"""Prepare a volume for a delete operation. """Prepare a volume for a delete operation.
:param serial_number: the array serial number :param serial_number: the array serial number
:param volume: the volume object
:param device_id: the volume device id :param device_id: the volume device id
:param volume_name: the volume name :param volume_name: the volume name
:param extra_specs: the extra specifications :param extra_specs: the extra specifications
:param connector: the connector object :param connector: the connector object
""" """
move = False move = False
short_host_name = None
storagegroup_names = (self.rest.get_storage_groups_from_volume( storagegroup_names = (self.rest.get_storage_groups_from_volume(
serial_number, device_id)) serial_number, device_id))
if storagegroup_names: if storagegroup_names:
if len(storagegroup_names) == 1 and reset is True: if len(storagegroup_names) == 1 and reset is True:
move = True move = True
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: for sg_name in storagegroup_names:
self.remove_volume_from_sg( self.remove_volume_from_sg(
serial_number, device_id, volume_name, sg_name, serial_number, device_id, volume_name, sg_name,
extra_specs, connector, move) extra_specs, connector, move)
if reset is True and move is False: if reset is True and move is False:
self.add_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, volume=volume)
def remove_volume_from_sg( def remove_volume_from_sg(
self, serial_number, device_id, vol_name, storagegroup_name, self, serial_number, device_id, vol_name, storagegroup_name,
@ -1386,7 +1405,7 @@ class VMAXMasking(object):
def add_volume_to_default_storage_group( def add_volume_to_default_storage_group(
self, serial_number, device_id, volume_name, 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. """Return volume to its default storage group.
:param serial_number: the array serial number :param serial_number: the array serial number
@ -1394,6 +1413,7 @@ class VMAXMasking(object):
:param volume_name: the volume name :param volume_name: the volume name
:param extra_specs: the extra specifications :param extra_specs: the extra specifications
:param src_sg: the source storage group, if any :param src_sg: the source storage group, if any
:param volume: the volume object
""" """
do_disable_compression = self.utils.is_compression_disabled( do_disable_compression = self.utils.is_compression_disabled(
extra_specs) extra_specs)
@ -1414,6 +1434,16 @@ class VMAXMasking(object):
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,
extra_specs) 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( def get_or_create_default_storage_group(
self, serial_number, srp, slo, workload, extra_specs, self, serial_number, srp, slo, workload, extra_specs,

View File

@ -448,6 +448,33 @@ class VMAXProvision(object):
self.rest.modify_rdf_device_pair( self.rest.modify_rdf_device_pair(
array, device_id, rdf_group, extra_specs, split=False) 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): def create_volume_group(self, array, group_name, extra_specs):
"""Create a generic volume group. """Create a generic volume group.