Merge "[SVf] As part of Flashcopy 2.0 adding support for volumegroup snapshots"

This commit is contained in:
Zuul 2023-01-20 15:49:19 +00:00 committed by Gerrit Code Review
commit 344ec1da64
4 changed files with 451 additions and 29 deletions

View File

@ -82,6 +82,7 @@ class StorwizeSVCManagementSimulator(object):
self._partnershipcandidate_list = {}
self._rcconsistgrp_list = {}
self._volumegroup_list = {}
self._volumegroup_snapshot_list = {}
self._system_list = {'storwize-svc-sim': {'id': '0123456789ABCDEF',
'name': 'storwize-svc-sim'},
'aux-svc-sim': {'id': 'ABCDEF0123456789',
@ -377,7 +378,8 @@ class StorwizeSVCManagementSimulator(object):
'removehostmappings',
'removefcmaps',
'removercrelationships',
'novolumegroup'
'novolumegroup',
'ignorelegacy'
]
one_param_args = [
'chapsecret',
@ -418,7 +420,8 @@ class StorwizeSVCManagementSimulator(object):
'pool',
'site',
'buffersize',
'volumegroup'
'volumegroup',
'snapshot'
]
no_or_one_param_args = [
'autoexpand',
@ -2652,6 +2655,108 @@ port_speed!N/A
del self._volumegroup_list[volumegroup_name]
return ('', '')
def _cmd_addsnapshot(self, **kwargs):
# Create a Volumegroup snapshot
volumegroup_snapshot_info = {}
volumegroup_snapshot_info['id'] = self._find_unused_id(
self._volumegroup_snapshot_list)
volumegroup_name = kwargs['volumegroup']
if 'name' in kwargs:
volumegroup_snapshot_info['name'] = kwargs["name"].strip('\'\"')
else:
volumegroup_snapshot_info['name'] = (
'vg_snap-' + volumegroup_snapshot_info['id'])
volumegroup_snapshot_info['volume_group_id'] = (
self._volumegroup_list[volumegroup_name]['id'])
volumegroup_snapshot_info['volume_group_name'] = volumegroup_name
volumegroup_snapshot_info['time'] = ''
volumegroup_snapshot_info['state'] = 'active'
volumegroup_snapshot_info['matches_group'] = 'yes'
volumegroup_snapshot_info['parent_uid'] = (
self._volumegroup_list[volumegroup_name]['uid'])
volumegroup_snapshot_info['expiration_time'] = ''
volumegroup_snapshot_info['protection_provisioned_capacity'] = '1.00GB'
volumegroup_snapshot_info['protection_written_capacity'] = '0.75MB'
volumegroup_snapshot_info['operation_start_time'] = ''
volumegroup_snapshot_info['operation_completion_estimate'] = ''
volumegroup_snapshot_info['owner_id'] = ''
volumegroup_snapshot_info['owner_name'] = ''
volumegroup_snapshot_info['auto_snapshot'] = 'no'
self._volumegroup_snapshot_list[volumegroup_snapshot_info['name']] = (
volumegroup_snapshot_info)
return ('Snapshot, id [' + volumegroup_snapshot_info['id'] +
'], successfully created or triggered', '')
def _cmd_lsvolumegroupsnapshot(self, **kwargs):
# List the volume group snapshot
rows = []
rows.append(['id', 'name', 'volume_group_id', 'volume_group_name',
'time', 'state', 'matches_group', 'parent_uid',
'expiration_time', 'protection_provisioned_capacity',
'protection_written_capacity', 'operation_start_time',
'operation_completion_estimate', 'owner_id',
'owner_name', 'auto_snapshot'])
if 'snapshot' and 'volumegroup' not in kwargs:
found = False
for volumegroup_snapshot in sorted(
self._volumegroup_snapshot_list.keys()):
volumegroup_snapshot_info = self._volumegroup_snapshot_list[
volumegroup_snapshot]
if 'filtervalue' not in kwargs:
rows.append(
[volumegroup_snapshot_info['id'],
volumegroup_snapshot_info['name'],
volumegroup_snapshot_info['volume_group_id'],
volumegroup_snapshot_info['volume_group_name'],
'', 'active', 'yes',
volumegroup_snapshot_info['parent_uid'], '', '1.00GB',
'0.75MB', '', '', '', '', 'no'])
found = True
if found:
return self._print_info_cmd(rows=rows, **kwargs)
else:
return ('', '')
else:
volumegroup_snapshot_info = kwargs['snapshot'].strip('\'\"')
if volumegroup_snapshot_info not in (
self._volumegroup_snapshot_list):
return self._errors['CMMVC5804E']
volumegroup_snapshot_info = self._volumegroup_snapshot_list[
volumegroup_snapshot_info]
rows.append(
[volumegroup_snapshot_info['id'],
volumegroup_snapshot_info['name'],
volumegroup_snapshot_info['volume_group_id'],
volumegroup_snapshot_info['volume_group_name'],
volumegroup_snapshot_info['time'],
volumegroup_snapshot_info['state'],
volumegroup_snapshot_info['matches_group'],
volumegroup_snapshot_info['parent_uid'],
volumegroup_snapshot_info['expiration_time'],
volumegroup_snapshot_info['protection_provisioned_capacity'],
volumegroup_snapshot_info['protection_written_capacity'],
volumegroup_snapshot_info['operation_start_time'],
volumegroup_snapshot_info['operation_completion_estimate'],
volumegroup_snapshot_info['owner_id'],
volumegroup_snapshot_info['owner_name'],
volumegroup_snapshot_info['auto_snapshot']])
if 'delim' in kwargs:
for index in range(len(rows)):
rows[index] = kwargs['delim'].join(rows[index])
return ('%s' % '\n'.join(rows), '')
def _cmd_rmsnapshot(self, **kwargs):
# Delete a Volume Group snapshot
if 'snapshot' and 'volumegroup' not in kwargs:
return self._errors['CMMVC5701E']
volumegroup_snapshot_name = kwargs['snapshot'].strip('\'\"')
if volumegroup_snapshot_name not in self._volumegroup_snapshot_list:
return self._errors['CMMVC9755E']
del self._volumegroup_snapshot_list[volumegroup_snapshot_name]
return ('', '')
def _cmd_mkrcconsistgrp(self, **kwargs):
master_sys = self._system_list['storwize-svc-sim']
aux_sys = self._system_list['aux-svc-sim']
@ -5563,6 +5668,31 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
"CG created failed")
return grp
def _create_volumegroup_type_and_volumegroup(self, vol_type_ref,
is_pool=None,
is_io_grp=None):
# Create volumegroup type
volumegroup_spec = {'volume_group_enabled': '<is> True'}
if is_pool:
volumegroup_spec.update({'volume_group_pool': is_pool})
if is_io_grp:
volumegroup_spec.update({'volume_group_iogrp': is_io_grp})
volumegroup_type_ref = group_types.create(self.ctxt,
'volumegroup_type',
volumegroup_spec)
volumegroup_type = objects.GroupType.get_by_id(
self.ctxt, volumegroup_type_ref['id'])
# Create volumegroup
volumegroup = testutils.create_group(
self.ctxt, group_type_id=volumegroup_type.id,
volume_type_ids=[vol_type_ref['id']])
model_update = self.driver.create_group(self.ctxt, volumegroup)
return (volumegroup_type_ref, volumegroup_type,
volumegroup, model_update)
def _create_group_snapshot_in_db(self, group_id, **kwargs):
group_snapshot = testutils.create_group_snapshot(self.ctxt,
group_id=group_id,
@ -7206,6 +7336,139 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.assertEqual(fields.GroupStatus.DELETED,
model_update[0]['status'])
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_system_info')
def test_storwize_create_and_delete_volumegroup_snapshot(
self, get_system_info):
"""Test creation and deletion of volumegroup snapshot"""
fake_system_info = {'code_level': (8, 5, 1, 0),
'system_name': 'storwize-svc-sim',
'system_id': '0123456789ABCDEF'}
get_system_info.return_value = fake_system_info
self.driver.do_setup(None)
# Create volume
vol_type_ref = volume_types.create(self.ctxt, 'non_rep_type', {})
vol_type = objects.VolumeType.get_by_id(self.ctxt,
vol_type_ref['id'])
volume = self._generate_vol_info(vol_type)
self.driver.create_volume(volume)
# Create volumegroup type and volumegroup
(volumegroup_type_ref, volumegroup_type, volumegroup,
model_update) = self._create_volumegroup_type_and_volumegroup(
vol_type_ref)
self.assertEqual(fields.GroupStatus.AVAILABLE,
model_update['status'])
# Add volumes to volumegroup
add_vols = [volume]
remove_vols = [volume]
(model_update, add_volumes_update, remove_volumes_update) = (
self.driver.update_group(self.ctxt, volumegroup, add_vols, []))
self.assertEqual(fields.GroupStatus.AVAILABLE,
model_update['status'])
# Create group-snapshot
group_snapshot, snapshots = self._create_group_snapshot_in_db(
volumegroup.id, group_type_id=volumegroup_type_ref.id)
model_update, snapshots_model = (
self.driver.create_group_snapshot(self.ctxt, group_snapshot,
snapshots))
self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE,
model_update['status'])
for snapshot in snapshots_model:
self.assertEqual(fields.SnapshotStatus.AVAILABLE,
snapshot['status'])
# Validating the snapshot_name property value
# from metadata of the snapshot
groupsnapshot_name = self.driver._get_volumegroup_snapshot_name(
group_snapshot)
for snapshot in snapshots:
self.assertEqual(groupsnapshot_name,
snapshot.metadata['snapshot_name'])
# Delete group-snapshot
model_update, snapshots_model = self.driver.delete_group_snapshot(
self.ctxt, group_snapshot, snapshots)
self.assertEqual(fields.GroupSnapshotStatus.DELETED,
model_update['status'])
for snapshot in snapshots_model:
self.assertEqual(fields.SnapshotStatus.DELETED,
snapshot['status'])
# Remove the volumes from volumegroup
(model_update, add_volumes_update,
remove_volumes_update) = self.driver.update_group(
self.ctxt, volumegroup, [], remove_vols)
self.assertEqual(fields.GroupStatus.AVAILABLE,
model_update['status'])
# Delete Volume Group
model_update = self.driver.delete_group(self.ctxt, volumegroup,
[])
self.assertEqual(fields.GroupStatus.DELETED,
model_update[0]['status'])
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_system_info')
@mock.patch.object(cinder.volume.volume_utils,
'is_group_a_type')
@mock.patch('cinder.volume.volume_utils.is_group_a_cg_snapshot_type')
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
'create_volumegroup_snapshot')
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
'delete_volumegroup_snapshot')
def test_storwize_create_and_delete_volumegroup_snapshot_calls(
self, delete_volumegroup_snapshot,
create_volumegroup_snapshot, is_grp_a_cg_snapshot_type,
vg_type, get_system_info):
"""Test creation and deletion of volumegroup snapshot"""
fake_system_info = {'code_level': (8, 5, 1, 0),
'system_name': 'storwize-svc-sim',
'system_id': '0123456789ABCDEF'}
get_system_info.return_value = fake_system_info
self.driver.do_setup(None)
# Mocking volume-group-enabled spec as true
is_grp_a_cg_snapshot_type.side_effect = [False, False, False]
vg_type.side_effect = [False, False, True, False, True,
False, False, True, False, False, True]
# Create volume group
type_ref = volume_types.create(self.ctxt, 'testtype', None)
group = testutils.create_group(self.ctxt,
group_type_id=fake.GROUP_TYPE_ID,
volume_type_ids=[type_ref['id']])
# Create volume group snapshot
group_snapshot, snapshots = self._create_group_snapshot_in_db(
group.id)
model_update, snapshots_model = (
self.driver.create_group_snapshot(self.ctxt, group_snapshot,
snapshots))
self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE,
model_update['status'])
self.assertTrue(create_volumegroup_snapshot.called)
# Delete volume group snapshot
model_update, snapshots_model = (
self.driver.delete_group_snapshot(self.ctxt, group_snapshot,
snapshots))
self.assertEqual(fields.GroupSnapshotStatus.DELETED,
model_update['status'])
self.assertTrue(delete_volumegroup_snapshot.called)
# Delete volume group
self.driver.delete_group(self.ctxt, group, [])
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
'create_rccg')
def test_storwize_group_create(self, create_rccg):

View File

@ -50,6 +50,7 @@ RCCG_PREFIX = 'rccg-'
HYPERCG_PREFIX = 'hycg-'
VG_PREFIX = 'vg-'
VG_SNAPSHOT_PREFIX = 'vg_snap-'
# remote mirror copy status
REP_CONSIS_SYNC = 'consistent_synchronized'

View File

@ -586,6 +586,64 @@ class StorwizeSSH(object):
with excutils.save_and_reraise_exception():
LOG.exception('Failed to delete volumegroup.')
def lsvolumegroupsnapshot(self, params):
"""Return volumegroup-snapshot attributes.
Return None if it doesn't exists
"""
ssh_cmd = ['svcinfo', 'lsvolumegroupsnapshot']
if "id" in params:
ssh_cmd.append(params["id"])
elif "name" and "volumegroup" in params:
ssh_cmd.extend(['-snapshot', params["name"], '-volumegroup',
params["volumegroup"]])
# Add delimiter to parse the output
ssh_cmd.extend(['-delim', ':'])
out, err = self._ssh(ssh_cmd, check_exit_code=False)
if not err:
if not out:
return None
# Parse the lsvolumegroupsnapshot output
output = out.split('\n')
attributes = output[0].split(":")
attribute_values = output[1].split(":")
attrs = {key: val for key, val in zip(attributes,
attribute_values)}
return attrs
# CMMVC5804E implies volumegroup-snapshot or volumegroup specified
# does not exist in the SVC storage.
if 'CMMVC5804E' in err:
return None
msg = (_('CLI Exception output:\n command: %(cmd)s\n '
'stdout: %(out)s\n stderr: %(err)s.') %
{'cmd': ssh_cmd,
'out': out,
'err': err})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def addsnapshot(self, params):
ssh_cmd = ['svctask', 'addsnapshot', '-ignorelegacy']
if "volumegroup" in params:
ssh_cmd.extend(['-volumegroup', params["volumegroup"]])
if "name" in params:
ssh_cmd.extend(['-name', params["name"]])
try:
return self.run_ssh_check_created(ssh_cmd)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Failed to create volumegroup snapshot.')
def rmsnapshot(self, params):
ssh_cmd = ['svctask', 'rmsnapshot']
if "id" in params:
ssh_cmd.extend(['-snapshotid', params["id"]])
elif "name" and "volumegroup" in params:
ssh_cmd.extend(['-snapshot', params["name"], '-volumegroup',
params["volumegroup"]])
self.run_ssh_assert_no_output(ssh_cmd)
def mkvdisk(self, name, size, units, pool, opts, params):
ssh_cmd = ['svctask', 'mkvdisk', '-name', '"%s"' % name, '-mdiskgrp',
'"%s"' % pool, '-iogrp', six.text_type(opts['iogrp']),
@ -2844,6 +2902,21 @@ class StorwizeHelpers(object):
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
def create_volumegroup_snapshot(self, params):
self.ssh.addsnapshot(params)
def is_volumegroup_snapshot_exists(self, params):
"""Check if volumegroup snapshot exists."""
attrs = self.ssh.lsvolumegroupsnapshot(params)
return attrs is not None
def delete_volumegroup_snapshot(self, params):
"""Delete volumegroup snapshot"""
if not self.is_volumegroup_snapshot_exists(params):
LOG.info('Tried to delete non-existent volumegroup snapshot.')
return
self.ssh.rmsnapshot(params)
def get_partnership_info(self, system_name):
partnership = self.ssh.lspartnership(system_name)
return partnership[0] if len(partnership) > 0 else None
@ -3795,6 +3868,16 @@ class StorwizeSVCCommonDriver(san.SanDriver,
volume.metadata['Volume Group Name'] = volumegroup_name
volume.save()
def _update_volumegroup_snapshot_properties(self, ctxt, snapshot,
group_snapshot=None):
volumegroup_snapshot_name = (
self._get_volumegroup_snapshot_name(group_snapshot)
if group_snapshot else "")
if not snapshot.metadata:
snapshot.metadata = dict()
snapshot.metadata['snapshot_name'] = volumegroup_snapshot_name
snapshot.save()
def create_volume(self, volume):
LOG.debug('enter: create_volume: volume %s', volume['name'])
# Create a replication or hyperswap volume with group_id is not
@ -6080,6 +6163,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
vg = storwize_const.VG_PREFIX
return vg + group_id[0:4] + '-' + group_id[-5:]
@staticmethod
def _get_volumegroup_snapshot_name(group_snapshot, grp_snapshot_id=None):
group_snapshot_id = (
group_snapshot.id if group_snapshot else grp_snapshot_id)
vg_snapshot = storwize_const.VG_SNAPSHOT_PREFIX
return vg_snapshot + group_snapshot_id
# Add CG capability to generic volume groups
def create_group(self, context, group):
"""Creates a group.
@ -6443,27 +6533,57 @@ class StorwizeSVCCommonDriver(san.SanDriver,
:param snapshots: a list of Snapshot objects in the group_snapshot.
:returns: model_update, snapshots_model_update
"""
if (not volume_utils.is_group_a_cg_snapshot_type(group_snapshot) and
not volume_utils.is_group_a_type
if (volume_utils.is_group_a_cg_snapshot_type(group_snapshot) or
volume_utils.is_group_a_type
(group_snapshot, "consistent_group_replication_enabled")
and not volume_utils.is_group_a_type(
or volume_utils.is_group_a_type(
group_snapshot, "hyperswap_group_enabled")):
# Use group_snapshot id as cg name
cg_name = 'cg_snap-' + group_snapshot.id
# Create new cg as cg_snapshot
self._helpers.create_fc_consistgrp(cg_name)
timeout = self.configuration.storwize_svc_flashcopy_timeout
model_update, snapshots_model = (
self._helpers.run_consistgrp_snapshots(cg_name,
snapshots,
self._state,
self.configuration,
timeout))
elif volume_utils.is_group_a_type(
group_snapshot, "volume_group_enabled"):
try:
self._helpers.check_codelevel_for_volumegroup(
self._state['code_level'])
params = dict()
# Use group_snapshot id as volumegroup name
volumegroup_snapshot_name = (
self._get_volumegroup_snapshot_name(group_snapshot))
params["name"] = volumegroup_snapshot_name
volumegroup_name = self._get_volumegroup_name(
None, grp_id=group_snapshot.group_id)
params["volumegroup"] = volumegroup_name
model_update = {'status': fields.GroupSnapshotStatus.AVAILABLE}
snapshots_model = []
self._helpers.create_volumegroup_snapshot(params)
except exception.VolumeBackendAPIException as err:
model_update['status'] = fields.GroupSnapshotStatus.ERROR
LOG.error("Failed to create VolumeGroup Snapshot. "
"Exception: %s.", err)
for snapshot in snapshots:
self._update_volumegroup_snapshot_properties(
context, snapshot, group_snapshot)
snapshots_model.append(
{'id': snapshot['id'],
'status': model_update['status'],
'replication_status': fields.ReplicationStatus.NOT_CAPABLE
})
else:
# we'll rely on the generic group implementation if it is not a
# consistency group request.
# consistency group/volumegroup request.
raise NotImplementedError()
# Use group_snapshot id as cg name
cg_name = 'cg_snap-' + group_snapshot.id
# Create new cg as cg_snapshot
self._helpers.create_fc_consistgrp(cg_name)
timeout = self.configuration.storwize_svc_flashcopy_timeout
model_update, snapshots_model = (
self._helpers.run_consistgrp_snapshots(cg_name,
snapshots,
self._state,
self.configuration,
timeout))
return model_update, snapshots_model
@ -6476,20 +6596,53 @@ class StorwizeSVCCommonDriver(san.SanDriver,
:returns: model_update, snapshots_model_update
"""
if (not volume_utils.is_group_a_cg_snapshot_type(group_snapshot) and
not volume_utils.is_group_a_type(
if (volume_utils.is_group_a_cg_snapshot_type(group_snapshot) or
volume_utils.is_group_a_type(
group_snapshot, "hyperswap_group_enabled")):
cgsnapshot_id = group_snapshot.id
cg_name = 'cg_snap-' + cgsnapshot_id
model_update, snapshots_model = (
self._helpers.delete_consistgrp_snapshots(cg_name,
snapshots))
elif volume_utils.is_group_a_type(
group_snapshot, "volume_group_enabled"):
try:
self._helpers.check_codelevel_for_volumegroup(
self._state['code_level'])
params = dict()
volumegroup_snapshot_name = (
self._get_volumegroup_snapshot_name(group_snapshot))
params["name"] = volumegroup_snapshot_name
volumegroup_name = self._get_volumegroup_name(
None, grp_id=group_snapshot.group_id)
params["volumegroup"] = volumegroup_name
model_update = {'status': fields.GroupSnapshotStatus.DELETED}
snapshots_model = []
self._helpers.delete_volumegroup_snapshot(params)
for snapshot in snapshots:
self._update_volumegroup_snapshot_properties(
context, snapshot)
snapshots_model.append(
{'id': snapshot['id'],
'status': fields.GroupSnapshotStatus.DELETED})
except exception.VolumeBackendAPIException as err:
model_update['status'] = (
fields.GroupSnapshotStatus.ERROR_DELETING)
for snapshot in snapshots:
snapshots_model.append(
{'id': snapshot['id'],
'status': fields.GroupSnapshotStatus.ERROR_DELETING})
LOG.error("Failed to delete the volume_group_snapshot %(snap) "
"with Exception: %(exception)s.",
{'snap': group_snapshot.group_id, 'exception': err})
else:
# we'll rely on the generic group implementation if it is not a
# consistency group request.
# consistency group/volumegroup request.
raise NotImplementedError()
cgsnapshot_id = group_snapshot.id
cg_name = 'cg_snap-' + cgsnapshot_id
model_update, snapshots_model = (
self._helpers.delete_consistgrp_snapshots(cg_name,
snapshots))
return model_update, snapshots_model
@volume_utils.trace

View File

@ -0,0 +1,5 @@
---
features:
- |
IBM Spectrum Virtualize Family driver: Added support for creation
and deletion of volumegroup snapshots.