VMAX driver - Fix error handling and checks for generic volume groups
There are several issues with the implementation of generic volume groups in the VMAX driver. There is no rollback implemented in case of errors for create group from source or for deleting groups. There can be issues if source groups have been modified since a snapshot was taken. The provider_location is also not being updated in the individual volume snapshot objects of a group snapshot. This patch rectifies these issues, and also does some general refactoring of the generic volume group code. Change-Id: I3e6ac90b145015ccd820af3449b20e377c9f4651 Closes-bug: 1732488
This commit is contained in:
parent
cf40a001da
commit
826d48e986
@ -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,
|
||||
|
@ -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'])
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
@ -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, '
|
||||
|
Loading…
x
Reference in New Issue
Block a user