Merge "VMAX driver - Fix error handling and checks for generic volume groups"

This commit is contained in:
Zuul 2018-01-17 10:28:32 +00:00 committed by Gerrit Code Review
commit bd016106a1
5 changed files with 380 additions and 374 deletions

View File

@ -199,10 +199,13 @@ class VMAXCommonData(object):
replication_driver_data=six.text_type(legacy_provider_location2),
host=fake_host, volume_type=test_volume_type)
snapshot_id = '390eeb4d-0f56-4a02-ba14-167167967014'
test_clone_volume = fake_volume.fake_volume_obj(
context=ctx, name='vol1', size=2, provider_auth=None,
provider_location=six.text_type(provider_location2),
host=fake_host)
host=fake_host, source_volid=test_volume.id,
snapshot_id=snapshot_id)
test_volume_snap_manage = fake_volume.fake_volume_obj(
context=ctx, name='vol1', size=2, provider_auth=None,
@ -212,7 +215,6 @@ class VMAXCommonData(object):
replication_driver_data=six.text_type(provider_location4))
snapshot_display_id = 'my_snap'
snapshot_id = '390eeb4d-0f56-4a02-ba14-167167967014'
managed_snap_id = 'OS-390eeb4d-0f56-4a02-ba14-167167967014'
test_snapshot_snap_name = 'OS-' + snapshot_id[:6] + snapshot_id[-9:]
@ -482,10 +484,10 @@ class VMAXCommonData(object):
sg_details_rep = [{"childNames": [],
"numDevicesNonGk": 2,
"isLinkTarget": False,
"rdf": False,
"rdf": True,
"capacityGB": 2.0,
"name": storagegroup_name_source,
"snapVXSnapshots": ['12345'],
"snapVXSnapshots": ['6560405d-752f5a139'],
"symmetrixId": array,
"numSnapVXSnapshots": 1}]
@ -1365,32 +1367,11 @@ class VMAXUtilsTest(test.TestCase):
self.assertEqual(ref_group_name, vol_grp_name)
def test_get_volume_group_utils(self):
group = self.data.test_group_1
array, extraspecs_dict = self.utils.get_volume_group_utils(
group, interval=1, retries=1)
array, intervals_retries = self.utils.get_volume_group_utils(
self.data.test_group_1, interval=1, retries=1)
ref_array = self.data.array
self.assertEqual(ref_array, array)
def test_update_extra_specs_list(self):
extra_specs = self.data.extra_specs
volume_type_id = 'abc'
extraspecs_dict = self.utils._update_extra_specs_list(
extra_specs, volume_type_id, interval=1, retries=1)
self.assertEqual(extra_specs, extraspecs_dict['extra_specs'])
def test_update_intervals_and_retries(self):
extra_specs = self.data.extra_specs
ref_interval = 1
extraspecs = self.utils._update_intervals_and_retries(
extra_specs, interval=1, retries=1)
self.assertEqual(ref_interval, extraspecs['interval'])
def test_get_intervals_retries_dict(self):
ref_value = {'interval': 1, 'retries': 1}
ret_dict = self.utils.get_intervals_retries_dict(
interval=1, retries=1)
self.assertEqual(ref_value, ret_dict)
def test_update_volume_model_updates(self):
volume_model_updates = [{'id': '1', 'status': 'available'}]
volumes = [self.data.test_volume]
@ -2452,6 +2433,16 @@ class VMAXRestTest(test.TestCase):
self.rest.modify_resource.assert_called_once_with(
array, 'replication', 'snapshot', payload_restore,
resource_name=snap_name, private='/private')
# link or unlink, list of volumes
mock_modify.reset_mock()
payload["action"] = "Link"
self.rest.modify_volume_snap(
array, "", "", snap_name,
extra_specs, unlink=False, link=True,
list_volume_pairs=[(source_id, target_id)])
self.rest.modify_resource.assert_called_once_with(
array, 'replication', 'snapshot', payload,
resource_name=snap_name, private='/private')
# none selected
mock_modify.reset_mock()
self.rest.modify_volume_snap(
@ -2976,7 +2967,8 @@ class VMAXProvisionTest(test.TestCase):
(self.provision.rest.modify_volume_snap.
assert_called_once_with(
array, source_device_id, target_device_id,
snap_name, extra_specs, unlink=True))
snap_name, extra_specs,
list_volume_pairs=None, unlink=True))
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall)
@ -2988,7 +2980,7 @@ class VMAXProvisionTest(test.TestCase):
mock_mod.assert_called_once_with(
self.data.array, self.data.device_id, self.data.device_id2,
self.data.snap_location['snap_name'], self.data.extra_specs,
unlink=True)
list_volume_pairs=None, unlink=True)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=test_utils.ZeroIntervalLoopingCall)
@ -3281,19 +3273,6 @@ class VMAXProvisionTest(test.TestCase):
target_group_name, snap_name,
extra_specs, deleteSnapshot)
def test_unlink_group(self):
with mock.patch.object(self.rest,
'modify_storagegroup_snap') as mock_mod:
self.provision._unlink_group(
self.data.array, self.data.storagegroup_name_source,
self.data.target_group_name,
self.data.group_snapshot_name, self.data.extra_specs)
mock_mod.assert_called_once_with(
self.data.array, self.data.storagegroup_name_source,
self.data.target_group_name,
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')
@ -3452,7 +3431,7 @@ class VMAXCommonTest(test.TestCase):
self.common.delete_snapshot(self.data.test_snapshot,
self.data.test_volume)
self.provision.delete_volume_snap.assert_called_once_with(
self.data.array, snap_name, sourcedevice_id)
self.data.array, snap_name, [sourcedevice_id])
def test_delete_snapshot_not_found(self):
with mock.patch.object(self.common, '_parse_snap_info',
@ -4838,26 +4817,61 @@ class VMAXCommonTest(test.TestCase):
group, volumes)
self.assertEqual(ref_model_update, model_update)
@mock.patch.object(
common.VMAXCommon, '_get_clone_vol_info',
return_value=(VMAXCommonData.device_id,
VMAXCommonData.extra_specs, 1, 'tgt_vol'))
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type',
return_value=True)
@mock.patch.object(volume_utils, 'is_group_a_type',
return_value=False)
def test_create_group_from_src_success(self, mock_type, mock_cg_type):
context = None
group = self.data.test_group_1
group_snapshot = self.data.test_group_snapshot_1
snapshots = []
volumes = [self.data.test_volume]
source_group = None
source_vols = []
def test_create_group_from_src_success(self, mock_type,
mock_cg_type, mock_info):
ref_model_update = {'status': fields.GroupStatus.AVAILABLE}
model_update, volumes_model_update = (
self.common.create_group_from_src(
context, group, volumes,
group_snapshot, snapshots,
source_group, source_vols))
None, self.data.test_group_1, [self.data.test_volume],
self.data.test_group_snapshot_1, [], None, []))
self.assertEqual(ref_model_update, model_update)
@mock.patch.object(
common.VMAXCommon, '_remove_vol_and_cleanup_replication')
@mock.patch.object(
masking.VMAXMasking, 'remove_volumes_from_storage_group')
def test_rollback_create_group_from_src(
self, mock_rm, mock_clean):
rollback_dict = {
'target_group_name': self.data.target_group_name,
'snap_name': 'snap1', 'source_group_name': 'src_grp',
'volumes': (self.data.device_id, self.data.extra_specs,
self.data.test_volume),
'device_ids': [self.data.device_id],
'interval_retries_dict': self.data.extra_specs}
for x in range(0, 2):
self.common._rollback_create_group_from_src(
self.data.array, rollback_dict)
self.assertEqual(2, mock_rm.call_count)
def test_get_snap_src_dev_list(self):
src_dev_ids = self.common._get_snap_src_dev_list(
self.data.array, [self.data.test_snapshot])
ref_dev_ids = [self.data.device_id]
self.assertEqual(ref_dev_ids, src_dev_ids)
def test_get_clone_vol_info(self):
ref_dev_id = self.data.device_id
source_vols = [self.data.test_volume,
self.data.test_attached_volume]
src_snapshots = [self.data.test_snapshot]
src_dev_id1, extra_specs1, vol_size1, tgt_vol_name1 = (
self.common._get_clone_vol_info(
self.data.test_clone_volume, source_vols, []))
src_dev_id2, extra_specs2, vol_size2, tgt_vol_name2 = (
self.common._get_clone_vol_info(
self.data.test_clone_volume, [], src_snapshots))
self.assertEqual(ref_dev_id, src_dev_id1)
self.assertEqual(ref_dev_id, src_dev_id2)
def test_get_attributes_from_cinder_config(self):
kwargs_expected = (
{'RestServerIp': '1.1.1.1',
@ -7249,7 +7263,7 @@ class VMAXCommonReplicationTest(test.TestCase):
model_update['replication_status'])
@mock.patch.object(utils.VMAXUtils, 'get_volume_group_utils',
return_value=(VMAXCommonData.array, []))
return_value=(VMAXCommonData.array, {}))
@mock.patch.object(common.VMAXCommon, '_cleanup_group_replication')
@mock.patch.object(volume_utils, 'is_group_a_type', return_value=True)
def test_delete_replication_group(self, mock_check,

View File

@ -274,6 +274,8 @@ class VMAXCommon(object):
if volume.group_id is not None:
if (volume_utils.is_group_a_cg_snapshot_type(volume.group)
or volume.group.is_replicated):
LOG.debug("Adding volume %(vol_id)s to group %(grp_id)s",
{'vol_id': volume.id, 'grp_id': volume.group_id})
self._add_new_volume_to_volume_group(
volume, volume_dict['device_id'], volume_name,
extra_specs, rep_driver_data)
@ -1007,8 +1009,11 @@ class VMAXCommon(object):
device_id = name['keybindings']['DeviceID']
else:
device_id = None
founddevice_id = self.rest.check_volume_device_id(
array, device_id, volume_name)
try:
founddevice_id = self.rest.check_volume_device_id(
array, device_id, volume_name)
except exception.VolumeBackendAPIException:
pass
if founddevice_id is None:
LOG.debug("Volume %(volume_name)s not found on the array.",
@ -3237,10 +3242,8 @@ class VMAXCommon(object):
vol_grp_name = self.utils.update_volume_group_name(group)
try:
array, __ = self.utils.get_volume_group_utils(
array, interval_retries_dict = self.utils.get_volume_group_utils(
group, self.interval, self.retries)
interval_retries_dict = self.utils.get_intervals_retries_dict(
self.interval, self.retries)
self.provision.create_volume_group(
array, vol_grp_name, interval_retries_dict)
if group.is_replicated:
@ -3288,7 +3291,7 @@ class VMAXCommon(object):
:returns: model_update, volumes_model_update
"""
volumes_model_update = []
array, extraspecs_dict_list = self.utils.get_volume_group_utils(
array, interval_retries_dict = self.utils.get_volume_group_utils(
group, self.interval, self.retries)
vol_grp_name = None
@ -3308,39 +3311,33 @@ class VMAXCommon(object):
vol_grp_name = volume_group['name']
volume_device_ids = self._get_members_of_volume_group(
array, vol_grp_name)
intervals_retries_dict = self.utils.get_intervals_retries_dict(
self.interval, self.retries)
deleted_volume_device_ids = []
# Remove replication for group, if applicable
if group.is_replicated:
self._cleanup_group_replication(
array, vol_grp_name, volume_device_ids,
intervals_retries_dict)
interval_retries_dict)
try:
if volume_device_ids:
# First remove all the volumes from the SG
self.masking.remove_volumes_from_storage_group(
array, volume_device_ids, vol_grp_name,
intervals_retries_dict)
interval_retries_dict)
for vol in volumes:
for extraspecs_dict in extraspecs_dict_list:
if (vol.volume_type_id in
extraspecs_dict['volumeTypeId']):
extraspecs = extraspecs_dict.get(
utils.EXTRA_SPECS)
device_id = self._find_device_on_array(
vol, extraspecs)
if device_id in volume_device_ids:
self.masking.remove_and_reset_members(
array, vol, device_id, vol.name,
extraspecs, False)
self._delete_from_srp(
array, device_id, "group vol", extraspecs)
else:
LOG.debug("Volume not found on the array.")
# Add the device id to the deleted list
deleted_volume_device_ids.append(device_id)
extra_specs = self._initial_setup(vol)
device_id = self._find_device_on_array(
vol, extra_specs)
if device_id in volume_device_ids:
self.masking.remove_and_reset_members(
array, vol, device_id, vol.name,
extra_specs, False)
self._delete_from_srp(
array, device_id, "group vol", extra_specs)
else:
LOG.debug("Volume not found on the array.")
# Add the device id to the deleted list
deleted_volume_device_ids.append(device_id)
# Once all volumes are deleted then delete the SG
self.rest.delete_storage_group(array, vol_grp_name)
model_update = {'status': fields.GroupStatus.DELETED}
@ -3351,27 +3348,28 @@ class VMAXCommon(object):
"Error received: %(e)s", {'e': e})
model_update = {'status': fields.GroupStatus.ERROR_DELETING}
# Update the volumes_model_update
if len(deleted_volume_device_ids) is not 0:
LOG.debug("Device ids: %(dev)s are deleted.",
{'dev': deleted_volume_device_ids})
volumes_not_deleted = []
for vol in volume_device_ids:
if vol not in deleted_volume_device_ids:
volumes_not_deleted.append(vol)
if not deleted_volume_device_ids:
volumes_model_update = self.utils.update_volume_model_updates(
volumes_model_update,
deleted_volume_device_ids,
volumes_model_update, deleted_volume_device_ids,
group.id, status='deleted')
if not volumes_not_deleted:
volumes_model_update = self.utils.update_volume_model_updates(
volumes_model_update,
volumes_not_deleted,
group.id, status='deleted')
volumes_model_update, volumes_not_deleted,
group.id, status='error_deleting')
# As a best effort try to add back the undeleted volumes to sg
# Dont throw any exception in case of failure
# Don't throw any exception in case of failure
try:
if not volumes_not_deleted:
self.masking.add_volumes_to_storage_group(
array, volumes_not_deleted,
vol_grp_name, intervals_retries_dict)
vol_grp_name, interval_retries_dict)
except Exception as ex:
LOG.error("Error in rollback - %(ex)s. "
"Failed to add back volumes to sg %(sg_name)s",
@ -3434,8 +3432,7 @@ class VMAXCommon(object):
try:
snap_name = self.utils.truncate_string(group_snapshot.id, 19)
self._create_group_replica(source_group,
snap_name)
self._create_group_replica(source_group, snap_name)
except Exception as e:
exception_message = (_("Failed to create snapshot for group: "
@ -3446,13 +3443,26 @@ class VMAXCommon(object):
raise exception.VolumeBackendAPIException(data=exception_message)
for snapshot in snapshots:
src_dev_id = self._get_src_device_id_for_group_snap(snapshot)
snapshots_model_update.append(
{'id': snapshot.id,
'provider_location': six.text_type(
{'source_id': src_dev_id, 'snap_name': snap_name}),
'status': fields.SnapshotStatus.AVAILABLE})
model_update = {'status': fields.GroupStatus.AVAILABLE}
return model_update, snapshots_model_update
def _get_src_device_id_for_group_snap(self, snapshot):
"""Get the source device id for the provider_location.
:param snapshot: the snapshot object
:return: src_device_id
"""
volume = snapshot.volume
extra_specs = self._initial_setup(volume)
return self._find_device_on_array(volume, extra_specs)
def _create_group_replica(
self, source_group, snap_name):
"""Create a group replica.
@ -3461,9 +3471,8 @@ class VMAXCommon(object):
:param source_group: the group object
:param snap_name: the name of the snapshot
"""
array, __ = (
self.utils.get_volume_group_utils(
source_group, self.interval, self.retries))
array, interval_retries_dict = self.utils.get_volume_group_utils(
source_group, self.interval, self.retries)
vol_grp_name = None
volume_group = (
self._find_volume_group(array, source_group))
@ -3476,8 +3485,6 @@ class VMAXCommon(object):
{'group_id': source_group.id})
raise exception.VolumeBackendAPIException(
data=exception_message)
interval_retries_dict = self.utils.get_intervals_retries_dict(
self.interval, self.retries)
self.provision.create_group_replica(
array, vol_grp_name,
snap_name, interval_retries_dict)
@ -3503,7 +3510,6 @@ class VMAXCommon(object):
:raises: VolumeBackendApiException, NotImplementedError
"""
snapshots_model_update = []
model_update = {}
source_group = group_snapshot.get('group')
grp_id = group_snapshot.group_id
if not volume_utils.is_group_a_cg_snapshot_type(source_group):
@ -3518,15 +3524,12 @@ class VMAXCommon(object):
vol_grp_name = None
try:
# Get the array serial
array, __ = (
self.utils.get_volume_group_utils(
source_group, self.interval, self.retries))
array, extra_specs = self.utils.get_volume_group_utils(
source_group, self.interval, self.retries)
# Get the volume group dict for getting the group name
volume_group = (
self._find_volume_group(array, source_group))
if volume_group:
if 'name' in volume_group:
vol_grp_name = volume_group['name']
volume_group = (self._find_volume_group(array, source_group))
if volume_group and volume_group.get('name'):
vol_grp_name = volume_group['name']
if vol_grp_name is None:
exception_message = (
_("Cannot find generic volume group %(grp_id)s.") %
@ -3536,9 +3539,9 @@ class VMAXCommon(object):
# Check if the snapshot exists
if 'snapVXSnapshots' in volume_group:
if snap_name in volume_group['snapVXSnapshots']:
self.provision.delete_group_replica(array,
snap_name,
vol_grp_name)
src_devs = self._get_snap_src_dev_list(array, snapshots)
self.provision.delete_group_replica(
array, snap_name, vol_grp_name, src_devs, extra_specs)
else:
# Snapshot has been already deleted, return successfully
LOG.error("Cannot find group snapshot %(snapId)s.",
@ -3556,6 +3559,20 @@ class VMAXCommon(object):
return model_update, snapshots_model_update
def _get_snap_src_dev_list(self, array, snapshots):
"""Get the list of source devices for a list of snapshots.
:param array: the array serial number
:param snapshots: the list of snapshot objects
:return: src_dev_ids
"""
src_dev_ids = []
for snap in snapshots:
src_dev_id, snap_name = self._parse_snap_info(array, snap)
if snap_name:
src_dev_ids.append(src_dev_id)
return src_dev_ids
def _find_volume_group(self, array, group):
"""Finds a volume group given the group.
@ -3603,7 +3620,7 @@ class VMAXCommon(object):
and not group.is_replicated):
raise NotImplementedError()
array, __ = self.utils.get_volume_group_utils(
array, interval_retries_dict = self.utils.get_volume_group_utils(
group, self.interval, self.retries)
model_update = {'status': fields.GroupStatus.AVAILABLE}
add_vols = [vol for vol in add_volumes] if add_volumes else []
@ -3618,8 +3635,6 @@ class VMAXCommon(object):
vol_grp_name = volume_group['name']
if vol_grp_name is None:
raise exception.GroupNotFound(group_id=group.id)
interval_retries_dict = self.utils.get_intervals_retries_dict(
self.interval, self.retries)
# Add volume(s) to the group
if add_device_ids:
self.utils.check_rep_status_enabled(group)
@ -3741,15 +3756,12 @@ class VMAXCommon(object):
"""
if not volume_utils.is_group_a_cg_snapshot_type(group):
raise NotImplementedError()
# Check if we need to create a snapshot
create_snapshot = False
volumes_model_update = []
if group_snapshot:
source_vols_or_snapshots = snapshots
source_id = group_snapshot.id
actual_source_grp = group_snapshot
actual_source_grp = group_snapshot.get('group')
elif source_group:
source_vols_or_snapshots = source_vols
source_id = source_group.id
actual_source_grp = source_group
create_snapshot = True
@ -3759,88 +3771,169 @@ class VMAXCommon(object):
raise exception.VolumeBackendAPIException(
data=exception_message)
tgt_name = self.utils.update_volume_group_name(group)
rollback_dict = {}
array, interval_retries_dict = self.utils.get_volume_group_utils(
group, self.interval, self.retries)
source_sg = self._find_volume_group(array, actual_source_grp)
if source_sg is not None:
src_grp_name = (source_sg['name']
if 'name' in source_sg else None)
rollback_dict['source_group_name'] = src_grp_name
else:
error_msg = (_("Cannot retrieve source volume group %(grp_id)s "
"from the array.")
% {'grp_id': actual_source_grp.id})
LOG.error(error_msg)
raise exception.VolumeBackendAPIException(data=error_msg)
LOG.debug("Enter VMAX create_volume group_from_src. Group to be "
"created: %(grpId)s, Source : %(SourceGrpId)s.",
{'grpId': group.id,
'SourceGrpId': source_id})
{'grpId': group.id, 'SourceGrpId': source_id})
tgt_name = self.utils.update_volume_group_name(group)
self.create_group(context, group)
model_update = {'status': fields.GroupStatus.AVAILABLE}
try:
array, extraspecs_dict_list = (
self.utils.get_volume_group_utils(
group, self.interval, self.retries))
vol_grp_name = ""
self.provision.create_volume_group(
array, tgt_name, interval_retries_dict)
rollback_dict.update({
'target_group_name': tgt_name, 'volumes': [],
'device_ids': [], 'list_volume_pairs': [],
'interval_retries_dict': interval_retries_dict})
model_update = {'status': fields.GroupStatus.AVAILABLE}
# Create the target devices
dict_volume_dicts = {}
target_volume_names = {}
for volume, source_vol_or_snapshot in zip(
volumes, source_vols_or_snapshots):
if 'size' in source_vol_or_snapshot:
volume_size = source_vol_or_snapshot['size']
else:
volume_size = source_vol_or_snapshot['volume_size']
for extraspecs_dict in extraspecs_dict_list:
if volume.volume_type_id in (
extraspecs_dict['volumeTypeId']):
extraspecs = extraspecs_dict.get(utils.EXTRA_SPECS)
target_volume_name = (
self.utils.get_volume_element_name(volume.id))
volume_dict = self.provision.create_volume_from_sg(
array, target_volume_name,
tgt_name, volume_size, extraspecs)
dict_volume_dicts[volume.id] = volume_dict
target_volume_names[volume.id] = target_volume_name
list_volume_pairs = []
for volume in volumes:
src_dev_id, extra_specs, vol_size, tgt_vol_name = (
self._get_clone_vol_info(
volume, source_vols, snapshots))
volume_dict = self._create_volume(
tgt_vol_name, vol_size, extra_specs)
device_id = volume_dict['device_id']
# Add the volume to the volume group SG
self.masking.add_volume_to_storage_group(
extra_specs[utils.ARRAY], device_id, tgt_name,
tgt_vol_name, extra_specs)
# Record relevant information
list_volume_pairs.append((src_dev_id, device_id))
# Add details to rollback dict
rollback_dict['device_ids'].append(device_id)
rollback_dict['list_volume_pairs'].append(
(src_dev_id, device_id))
rollback_dict['volumes'].append(
(device_id, extra_specs, volume))
volumes_model_update.append(
self.utils.get_grp_volume_model_update(
volume, volume_dict, group.id))
if create_snapshot is True:
# We have to create a snapshot of the source group
snap_name = self.utils.truncate_string(group.id, 19)
self._create_group_replica(actual_source_grp, snap_name)
vol_grp_name = self.utils.update_volume_group_name(
source_group)
rollback_dict['snap_name'] = snap_name
else:
# We need to check if the snapshot exists
snap_name = self.utils.truncate_string(source_id, 19)
source_group = actual_source_grp.get('group')
volume_group = self._find_volume_group(array, source_group)
if volume_group is not None:
if 'snapVXSnapshots' in volume_group:
if snap_name in volume_group['snapVXSnapshots']:
LOG.info("Snapshot is present on the array")
if 'name' in volume_group:
vol_grp_name = volume_group['name']
if ('snapVXSnapshots' in source_sg and
snap_name in source_sg['snapVXSnapshots']):
LOG.info("Snapshot is present on the array")
else:
error_msg = (
_("Cannot retrieve source snapshot %(snap_id)s "
"from the array.") % {'snap_id': source_id})
LOG.error(error_msg)
raise exception.VolumeBackendAPIException(data=error_msg)
# Link and break the snapshot to the source group
interval_retries_dict = self.utils.get_intervals_retries_dict(
self.interval, self.retries)
self.provision.link_and_break_replica(
array, vol_grp_name, tgt_name, snap_name,
interval_retries_dict, delete_snapshot=create_snapshot)
except Exception:
exception_message = (_("Failed to create vol grp %(volGrpName)s"
" from source %(grpSnapshot)s.")
% {'volGrpName': group.id,
'grpSnapshot': source_id})
LOG.exception(exception_message)
raise exception.VolumeBackendAPIException(data=exception_message)
volumes_model_update = self.utils.update_volume_model_updates(
volumes_model_update, volumes, group.id, model_update['status'])
# Update the provider_location & replication status
for volume_model_update in volumes_model_update:
if volume_model_update['id'] in dict_volume_dicts:
volume_model_update.update(
{'provider_location': six.text_type(
dict_volume_dicts[volume_model_update['id']])})
array, src_grp_name, tgt_name, snap_name,
interval_retries_dict, list_volume_pairs,
delete_snapshot=create_snapshot)
# Update the replication status
if group.is_replicated:
volumes_model_update = self._replicate_group(
array, volumes_model_update,
tgt_name, interval_retries_dict)
model_update.update({
'replication_status': fields.ReplicationStatus.ENABLED})
except Exception:
exception_message = (_("Failed to create vol grp %(volGrpName)s"
" from source %(grpSnapshot)s.")
% {'volGrpName': group.id,
'grpSnapshot': source_id})
LOG.error(exception_message)
if array is not None:
LOG.info("Attempting rollback for the create group from src.")
self._rollback_create_group_from_src(array, rollback_dict)
raise exception.VolumeBackendAPIException(data=exception_message)
return model_update, volumes_model_update
def _get_clone_vol_info(self, volume, source_vols, snapshots):
"""Get the clone volume info.
:param volume: the new volume object
:param source_vols: the source volume list
:param snapshots: the source snapshot list
:returns: src_dev_id, extra_specs, vol_size, tgt_vol_name
"""
src_dev_id, vol_size = None, None
extra_specs = self._initial_setup(volume)
if not source_vols:
for snap in snapshots:
if snap.id == volume.snapshot_id:
src_dev_id, __ = self._parse_snap_info(
extra_specs[utils.ARRAY], snap)
vol_size = snap.volume_size
else:
for src_vol in source_vols:
if src_vol.id == volume.source_volid:
src_extra_specs = self._initial_setup(src_vol)
src_dev_id = self._find_device_on_array(
src_vol, src_extra_specs)
vol_size = src_vol.size
tgt_vol_name = self.utils.get_volume_element_name(volume.id)
return src_dev_id, extra_specs, vol_size, tgt_vol_name
def _rollback_create_group_from_src(self, array, rollback_dict):
"""Performs rollback for create group from src in case of failure.
:param array: the array serial number
:param rollback_dict: dict containing rollback details
"""
try:
# Delete the snapshot if required
if rollback_dict.get("snap_name"):
try:
src_dev_ids = [
a for a, b in rollback_dict['list_volume_pairs']]
self.provision.delete_group_replica(
array, rollback_dict["snap_name"],
rollback_dict["source_group_name"],
src_dev_ids, rollback_dict['interval_retries_dict'])
except Exception as e:
LOG.debug("Failed to delete group snapshot. Attempting "
"further rollback. Exception received: %(e)s.",
{'e': e})
if rollback_dict.get('volumes'):
# Remove any devices which were added to the target SG
if rollback_dict['device_ids']:
self.masking.remove_volumes_from_storage_group(
array, rollback_dict['device_ids'],
rollback_dict['target_group_name'],
rollback_dict['interval_retries_dict'])
# Delete all the volumes
for dev_id, extra_specs, volume in rollback_dict['volumes']:
self._remove_vol_and_cleanup_replication(
array, dev_id, "group vol", extra_specs, volume)
self._delete_from_srp(
array, dev_id, "group vol", extra_specs)
# Delete the target SG
if rollback_dict.get("target_group_name"):
self.rest.delete_storage_group(
array, rollback_dict['target_group_name'])
LOG.info("Rollback completed for create group from src.")
except Exception as e:
LOG.error("Rollback failed for the create group from src. "
"Exception received: %(e)s.", {'e': e})
def _replicate_group(self, array, volumes_model_update,
group_name, extra_specs):
"""Replicate a cloned volume group.
@ -3854,10 +3947,11 @@ class VMAXCommon(object):
rdf_group_no, remote_array = self.get_rdf_details(array)
self.rest.replicate_group(
array, group_name, rdf_group_no, remote_array, extra_specs)
# Need to set SRP to None for generic volume group - Not set
# Need to set SRP to None for remote generic volume group - Not set
# automatically, and a volume can only be in one storage group
# managed by FAST
self.rest.set_storagegroup_srp(array, group_name, "None", extra_specs)
self.rest.set_storagegroup_srp(
remote_array, group_name, "None", extra_specs)
for volume_model_update in volumes_model_update:
vol_id = volume_model_update['id']
loc = ast.literal_eval(volume_model_update['provider_location'])

View File

@ -174,7 +174,7 @@ class VMAXProvision(object):
def _unlink_volume(
self, array, source_device_id, target_device_id, snap_name,
extra_specs):
extra_specs, list_volume_pairs=None):
"""Unlink a target volume from its source volume.
:param array: the array serial number
@ -182,6 +182,7 @@ class VMAXProvision(object):
:param target_device_id: the target device id
:param snap_name: the snap name
:param extra_specs: extra specifications
:param list_volume_pairs: list of volume pairs, optional
:return: return code
"""
@ -196,7 +197,8 @@ class VMAXProvision(object):
if not kwargs['modify_vol_success']:
self.rest.modify_volume_snap(
array, source_device_id, target_device_id, snap_name,
extra_specs, unlink=True)
extra_specs, unlink=True,
list_volume_pairs=list_volume_pairs)
kwargs['modify_vol_success'] = True
except exception.VolumeBackendAPIException:
pass
@ -315,26 +317,32 @@ class VMAXProvision(object):
do_delete_temp_snap(snap_name)
def delete_volume_snap_check_for_links(self, array, snap_name,
source_device, extra_specs):
source_devices, extra_specs):
"""Check if a snap has any links before deletion.
If a snapshot has any links, break the replication relationship
before deletion.
:param array: the array serial number
:param snap_name: the snapshot name
:param source_device: the source device id
:param source_devices: the source device ids
:param extra_specs: the extra specifications
"""
LOG.debug("Check for linked devices to SnapVx: %(snap_name)s "
"for volume %(vol)s.",
{'vol': source_device, 'snap_name': snap_name})
linked_list = self.rest.get_snap_linked_device_list(
array, source_device, snap_name)
for link in linked_list:
target_device = link['targetDevice']
self.break_replication_relationship(
array, target_device, source_device, snap_name, extra_specs)
self.delete_volume_snap(array, snap_name, source_device)
list_device_pairs = []
if not isinstance(source_devices, list):
source_devices = [source_devices]
for source_device in source_devices:
LOG.debug("Check for linked devices to SnapVx: %(snap_name)s "
"for volume %(vol)s.",
{'vol': source_device, 'snap_name': snap_name})
linked_list = self.rest.get_snap_linked_device_list(
array, source_device, snap_name)
for link in linked_list:
target_device = link['targetDevice']
list_device_pairs.append((source_device, target_device))
if list_device_pairs:
self._unlink_volume(array, "", "", snap_name, extra_specs,
list_volume_pairs=list_device_pairs)
self.delete_volume_snap(array, snap_name, source_devices)
def extend_volume(self, array, device_id, new_size, extra_specs):
"""Extend a volume.
@ -649,26 +657,26 @@ class VMAXProvision(object):
self.rest.create_storagegroup_snap(
array, source_group, snap_name, extra_specs)
def delete_group_replica(self, array, snap_name,
source_group_name):
def delete_group_replica(self, array, snap_name, source_group_name,
src_dev_ids, extra_specs):
"""Delete the snapshot.
:param array: the array serial number
:param snap_name: the name for the snap shot
:param source_group_name: the source group name
:param src_dev_ids: the list of source device ids
:param extra_specs: extra specifications
"""
# Delete snapvx snapshot
LOG.debug("Deleting Snap Vx snapshot: source group: %(srcGroup)s "
"snapshot: %(snap_name)s.",
{'srcGroup': source_group_name,
'snap_name': snap_name})
# The check for existence of snapshot has already happened
# So we just need to delete the snapshot
self.rest.delete_storagegroup_snap(array, snap_name, source_group_name)
{'srcGroup': source_group_name, 'snap_name': snap_name})
self.delete_volume_snap_check_for_links(
array, snap_name, src_dev_ids, extra_specs)
def link_and_break_replica(self, array, source_group_name,
target_group_name, snap_name, extra_specs,
delete_snapshot=False):
list_volume_pairs, delete_snapshot=False):
"""Links a group snap and breaks the relationship.
:param array: the array serial
@ -676,6 +684,7 @@ class VMAXProvision(object):
:param target_group_name: the target group name
:param snap_name: the snapshot name
:param extra_specs: extra specifications
:param list_volume_pairs: the list of volume pairs
:param delete_snapshot: delete snapshot flag
"""
LOG.debug("Linking Snap Vx snapshot: source group: %(srcGroup)s "
@ -683,66 +692,24 @@ class VMAXProvision(object):
{'srcGroup': source_group_name,
'tgtGroup': target_group_name})
# Link the snapshot
self.rest.modify_storagegroup_snap(
array, source_group_name, target_group_name, snap_name,
extra_specs, link=True)
self.rest.modify_volume_snap(
array, None, None, snap_name, extra_specs, link=True,
list_volume_pairs=list_volume_pairs)
# Unlink the snapshot
LOG.debug("Unlinking Snap Vx snapshot: source group: %(srcGroup)s "
"targetGroup: %(tgtGroup)s.",
{'srcGroup': source_group_name,
'tgtGroup': target_group_name})
self._unlink_group(array, source_group_name,
target_group_name, snap_name, extra_specs)
self._unlink_volume(array, None, None, snap_name, extra_specs,
list_volume_pairs=list_volume_pairs)
# Delete the snapshot if necessary
if delete_snapshot:
LOG.debug("Deleting Snap Vx snapshot: source group: %(srcGroup)s "
"snapshot: %(snap_name)s.",
{'srcGroup': source_group_name,
'snap_name': snap_name})
self.rest.delete_storagegroup_snap(array, snap_name,
source_group_name)
def _unlink_group(
self, array, source_group_name, target_group_name, snap_name,
extra_specs):
"""Unlink a target group from it's source group.
:param array: the array serial number
:param source_group_name: the source group name
:param target_group_name: the target device name
:param snap_name: the snap name
:param extra_specs: extra specifications
:returns: return code
"""
def _unlink_grp():
"""Called at an interval until the synchronization is finished.
:raises: loopingcall.LoopingCallDone
"""
retries = kwargs['retries']
try:
kwargs['retries'] = retries + 1
if not kwargs['modify_grp_snap_success']:
self.rest.modify_storagegroup_snap(
array, source_group_name, target_group_name,
snap_name, extra_specs, unlink=True)
kwargs['modify_grp_snap_success'] = True
except exception.VolumeBackendAPIException:
pass
if kwargs['retries'] > UNLINK_RETRIES:
LOG.error("_unlink_grp failed after %(retries)d "
"tries.", {'retries': retries})
raise loopingcall.LoopingCallDone(retvalue=30)
if kwargs['modify_grp_snap_success']:
raise loopingcall.LoopingCallDone()
kwargs = {'retries': 0,
'modify_grp_snap_success': False}
timer = loopingcall.FixedIntervalLoopingCall(_unlink_grp)
rc = timer.start(interval=UNLINK_INTERVAL).wait()
return rc
source_devices = [a for a, b in list_volume_pairs]
self.delete_volume_snap(array, snap_name, source_devices)
def enable_group_replication(self, array, storagegroup_name,
rdf_group_num, extra_specs, establish=False):
@ -799,15 +766,19 @@ class VMAXProvision(object):
:param rdf_group_num: the rdf group number
:param extra_specs: the extra specifications
"""
action = "Split"
LOG.debug("Splitting remote replication for group %(sg)s",
{'sg': storagegroup_name})
self.rest.modify_storagegroup_rdf(
array, storagegroup_name, rdf_group_num, action, extra_specs)
LOG.debug("Deleting remote replication for group %(sg)s",
{'sg': storagegroup_name})
self.rest.delete_storagegroup_rdf(
array, storagegroup_name, rdf_group_num)
group_details = self.rest.get_storage_group_rep(
array, storagegroup_name)
if (group_details and group_details.get('rdf')
and group_details['rdf'] is True):
action = "Split"
LOG.debug("Splitting remote replication for group %(sg)s",
{'sg': storagegroup_name})
self.rest.modify_storagegroup_rdf(
array, storagegroup_name, rdf_group_num, action, extra_specs)
LOG.debug("Deleting remote replication for group %(sg)s",
{'sg': storagegroup_name})
self.rest.delete_storagegroup_rdf(
array, storagegroup_name, rdf_group_num)
def revert_volume_snapshot(self, array, source_device_id,
snap_name, extra_specs):

View File

@ -1541,7 +1541,8 @@ class VMAXRest(object):
def modify_volume_snap(self, array, source_id, target_id, snap_name,
extra_specs, link=False, unlink=False,
rename=False, new_snap_name=None, restore=False):
rename=False, new_snap_name=None, restore=False,
list_volume_pairs=None):
"""Modify a snapvx snapshot
:param array: the array serial number
@ -1554,9 +1555,9 @@ class VMAXRest(object):
:param rename: Flag to indicate action = Rename
:param new_snap_name: Optional new snapshot name
:param restore: Flag to indicate action = Restore
:param list_volume_pairs: list of volume pairs to link, optional
"""
action = None
operation = ''
action, operation, payload = '', '', {}
if link:
action = "Link"
elif unlink:
@ -1575,8 +1576,16 @@ class VMAXRest(object):
"star": 'false', "force": 'false'}
elif action in ('Link', 'Unlink'):
operation = 'Modify snapVx relationship to target'
payload = {"deviceNameListSource": [{"name": source_id}],
"deviceNameListTarget": [{"name": target_id}],
src_list, tgt_list = [], []
if list_volume_pairs:
for a, b in list_volume_pairs:
src_list.append({'name': a})
tgt_list.append({'name': b})
else:
src_list.append({'name': source_id})
tgt_list.append({'name': target_id})
payload = {"deviceNameListSource": src_list,
"deviceNameListTarget": tgt_list,
"copy": 'true', "action": action,
"star": 'false', "force": 'false',
"exact": 'false', "remote": 'false',
@ -1595,19 +1604,22 @@ class VMAXRest(object):
self.wait_for_job(operation, status_code, job, extra_specs)
def delete_volume_snap(self, array, snap_name,
source_device_id, restored=False):
"""Delete the snapshot of a volume.
source_device_ids, restored=False):
"""Delete the snapshot of a volume or volumes.
:param array: the array serial number
:param snap_name: the name of the snapshot
:param source_device_id: the source device id
:param source_device_ids: the source device ids
:param restored: Flag to indicate terminate restore session
"""
device_list = []
if not isinstance(source_device_ids, list):
source_device_ids = [source_device_ids]
for dev in source_device_ids:
device_list.append({"name": dev})
payload = {"deviceNameListSource": device_list}
if restored:
payload = {"deviceNameListSource": [{"name": source_device_id}],
"restore": True}
else:
payload = {"deviceNameListSource": [{"name": source_device_id}]}
payload.update({"restore": True})
return self.delete_resource(
array, REPLICATION, 'snapshot', snap_name, payload=payload,
private='/private')
@ -2108,7 +2120,6 @@ class VMAXRest(object):
:param storagegroup_name: the storage group name
:returns: volume_list
"""
volume_list = None
params = {"storageGroupId": storagegroup_name}
volume_list = self.get_volume_list(array, params)
@ -2134,50 +2145,6 @@ class VMAXRest(object):
self.wait_for_job('Create storage group snapVx', status_code,
job, extra_specs)
def modify_storagegroup_snap(
self, array, source_sg_id, target_sg_id, snap_name,
extra_specs, link=False, unlink=False):
"""Link or unlink a snapVx to or from a target storagegroup.
:param array: the array serial number
:param source_sg_id: the source device id
:param target_sg_id: the target device id
:param snap_name: the snapshot name
:param extra_specs: extra specifications
:param link: Flag to indicate action = Link
:param unlink: Flag to indicate action = Unlink
"""
payload = ''
if link:
payload = {"link": {"linkStorageGroupName": target_sg_id,
"copy": "true"},
"action": "Link"}
elif unlink:
payload = {"unlink": {"unlinkStorageGroupName": target_sg_id},
"action": "Unlink"}
resource_name = ('%(sg_name)s/snapshot/%(snap_id)s/generation/0'
% {'sg_name': source_sg_id, 'snap_id': snap_name})
status_code, job = self.modify_resource(
array, REPLICATION, 'storagegroup', payload,
resource_name=resource_name)
self.wait_for_job('Modify storagegroup snapVx relationship to target',
status_code, job, extra_specs)
def delete_storagegroup_snap(self, array, snap_name, source_sg_id):
"""Delete the snapshot of a storagegroup.
:param array: the array serial number
:param snap_name: the name of the snapshot
:param source_sg_id: the source device id
"""
resource_name = ('%(sg_name)s/snapshot/%(snap_id)s/generation/0'
% {'sg_name': source_sg_id, 'snap_id': snap_name})
return self.delete_resource(
array, REPLICATION, 'storagegroup', resource_name)
def get_storagegroup_rdf_details(self, array, storagegroup_name,
rdf_group_num):
"""Get the remote replication details of a storage group.

View File

@ -583,18 +583,29 @@ class VMAXUtils(object):
:param status: string value reflects the status of the member volume
:returns: volume_model_updates - updated volumes
"""
LOG.info(
"Updating status for group: %(id)s.",
{'id': group_id})
LOG.info("Updating status for group: %(id)s.", {'id': group_id})
if volumes:
for volume in volumes:
volume_model_updates.append({'id': volume.id,
'status': status})
else:
LOG.info("No volume found for group: %(cg)s.",
{'cg': group_id})
LOG.info("No volume found for group: %(cg)s.", {'cg': group_id})
return volume_model_updates
@staticmethod
def get_grp_volume_model_update(volume, volume_dict, group_id):
"""Create and return the volume model update on creation.
:param volume: volume object
:param volume_dict: the volume dict
:param group_id: consistency group id
:returns: model_update
"""
LOG.info("Updating status for group: %(id)s.", {'id': group_id})
model_update = ({'id': volume.id, 'status': 'available',
'provider_location': six.text_type(volume_dict)})
return model_update
@staticmethod
def update_extra_specs(extraspecs):
"""Update extra specs.
@ -619,39 +630,21 @@ class VMAXUtils(object):
" the provided extra_specs.")
return extraspecs
@staticmethod
def get_intervals_retries_dict(interval, retries):
"""Get the default intervals and retries.
:param interval: Interval in seconds between retries
:param retries: Retry count
:returns: default_dict
"""
default_dict = {}
default_dict[INTERVAL] = interval
default_dict[RETRIES] = retries
return default_dict
def get_volume_group_utils(self, group, interval, retries):
"""Standard utility for generic volume groups.
:param group: the generic volume group object to be created
:param interval: Interval in seconds between retries
:param retries: Retry count
:returns: array, extra specs dict list
:returns: array, intervals_retries_dict
:raises: VolumeBackendAPIException
"""
arrays = set()
extraspecs_dict_list = []
# Check if it is a generic volume group instance
if isinstance(group, Group):
for volume_type in group.volume_types:
extraspecs_dict = (
self._update_extra_specs_list(
volume_type.extra_specs,
volume_type.id, interval, retries))
extraspecs_dict_list.append(extraspecs_dict)
arrays.add(extraspecs_dict[EXTRA_SPECS][ARRAY])
extra_specs = self.update_extra_specs(volume_type.extra_specs)
arrays.add(extra_specs[ARRAY])
else:
msg = (_("Unable to get volume type ids."))
LOG.error(msg)
@ -669,25 +662,8 @@ class VMAXUtils(object):
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
array = arrays.pop()
return array, extraspecs_dict_list
def _update_extra_specs_list(self, extraspecs, volumetype_id,
interval, retries):
"""Update the extra specs list.
:param extraspecs: extraspecs
:param volumetype_Id: volume type identifier
:param interval: Interval in seconds between retries
:param retries: Retry count
:returns: extraspecs_dict_list
"""
extraspecs_dict = {}
extraspecs = self.update_extra_specs(extraspecs)
extraspecs = self._update_intervals_and_retries(
extraspecs, interval, retries)
extraspecs_dict["volumeTypeId"] = volumetype_id
extraspecs_dict[EXTRA_SPECS] = extraspecs
return extraspecs_dict
intervals_retries_dict = {INTERVAL: interval, RETRIES: retries}
return array, intervals_retries_dict
def update_volume_group_name(self, group):
"""Format id and name consistency group.
@ -704,23 +680,6 @@ class VMAXUtils(object):
group_name += group.id
return group_name
@staticmethod
def _update_intervals_and_retries(extra_specs, interval, retries):
"""Updates the extraSpecs with intervals and retries values.
:param extra_specs:
:param interval: Interval in seconds between retries
:param retries: Retry count
:returns: Updated extra_specs
"""
extra_specs[INTERVAL] = interval
LOG.debug("The interval is set at: %(intervalInSecs)s.",
{'intervalInSecs': interval})
extra_specs[RETRIES] = retries
LOG.debug("Retries are set at: %(retries)s.",
{'retries': retries})
return extra_specs
@staticmethod
def add_legacy_pools(pools):
"""Add legacy pools to allow extending a volume after upgrade.
@ -781,6 +740,7 @@ class VMAXUtils(object):
msg = (_('Replication status should be %s for '
'replication-enabled group.')
% fields.ReplicationStatus.ENABLED)
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
else:
LOG.debug('Replication is not enabled on group %s, '