[SVf] As part of Flashcopy 2.0 adding support for volumegroup
[Spectrum Virtualize family] As part of Flashcopy 2.0 implementation, added support for VolumeGroup. Volumegroup creation, modification and deletion is now supported using the existing Cinder CLI for group operations. Implements: blueprint ibm-svf-volumegroup Change-Id: I72041e0c3c530a732b6024c036a2a8f7083259d4
This commit is contained in:
parent
6dd5e20c2e
commit
ac9debdf35
@ -81,6 +81,7 @@ class StorwizeSVCManagementSimulator(object):
|
|||||||
self._partnership_list = {}
|
self._partnership_list = {}
|
||||||
self._partnershipcandidate_list = {}
|
self._partnershipcandidate_list = {}
|
||||||
self._rcconsistgrp_list = {}
|
self._rcconsistgrp_list = {}
|
||||||
|
self._volumegroup_list = {}
|
||||||
self._system_list = {'storwize-svc-sim': {'id': '0123456789ABCDEF',
|
self._system_list = {'storwize-svc-sim': {'id': '0123456789ABCDEF',
|
||||||
'name': 'storwize-svc-sim'},
|
'name': 'storwize-svc-sim'},
|
||||||
'aux-svc-sim': {'id': 'ABCDEF0123456789',
|
'aux-svc-sim': {'id': 'ABCDEF0123456789',
|
||||||
@ -375,7 +376,8 @@ class StorwizeSVCManagementSimulator(object):
|
|||||||
'thin',
|
'thin',
|
||||||
'removehostmappings',
|
'removehostmappings',
|
||||||
'removefcmaps',
|
'removefcmaps',
|
||||||
'removercrelationships'
|
'removercrelationships',
|
||||||
|
'novolumegroup'
|
||||||
]
|
]
|
||||||
one_param_args = [
|
one_param_args = [
|
||||||
'chapsecret',
|
'chapsecret',
|
||||||
@ -416,6 +418,7 @@ class StorwizeSVCManagementSimulator(object):
|
|||||||
'pool',
|
'pool',
|
||||||
'site',
|
'site',
|
||||||
'buffersize',
|
'buffersize',
|
||||||
|
'volumegroup'
|
||||||
]
|
]
|
||||||
no_or_one_param_args = [
|
no_or_one_param_args = [
|
||||||
'autoexpand',
|
'autoexpand',
|
||||||
@ -2039,7 +2042,8 @@ port_speed!N/A
|
|||||||
kwargs.pop('obj')
|
kwargs.pop('obj')
|
||||||
|
|
||||||
params = ['name', 'warning', 'udid',
|
params = ['name', 'warning', 'udid',
|
||||||
'autoexpand', 'easytier', 'primary']
|
'autoexpand', 'easytier', 'primary',
|
||||||
|
'volumegroup', 'novolumegroup']
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key == 'easytier':
|
if key == 'easytier':
|
||||||
vol['easy_tier'] = value
|
vol['easy_tier'] = value
|
||||||
@ -2067,6 +2071,8 @@ port_speed!N/A
|
|||||||
else:
|
else:
|
||||||
err = self._errors['CMMVC6353E'][1] % {'VALUE': key}
|
err = self._errors['CMMVC6353E'][1] % {'VALUE': key}
|
||||||
return ('', err)
|
return ('', err)
|
||||||
|
if key == 'volumegroup':
|
||||||
|
self._volumes_list[vol_name]['volume_group_id'] = value
|
||||||
if key in params:
|
if key in params:
|
||||||
vol[key] = value
|
vol[key] = value
|
||||||
if key == 'autoexpand':
|
if key == 'autoexpand':
|
||||||
@ -2531,6 +2537,121 @@ port_speed!N/A
|
|||||||
except Exception:
|
except Exception:
|
||||||
return self._errors['CMMVC5982E']
|
return self._errors['CMMVC5982E']
|
||||||
|
|
||||||
|
def _cmd_mkvolumegroup(self, **kwargs):
|
||||||
|
# Create a Volume group
|
||||||
|
volumegroup_info = {}
|
||||||
|
volumegroup_info['id'] = self._find_unused_id(self._volumegroup_list)
|
||||||
|
if 'name' in kwargs:
|
||||||
|
volumegroup_info['name'] = kwargs["name"].strip('\'\"')
|
||||||
|
else:
|
||||||
|
volumegroup_info['name'] = self.driver._get_volumegroup_name(
|
||||||
|
None, volumegroup_info['id'])
|
||||||
|
volumegroup_info['volume_count'] = '0'
|
||||||
|
volumegroup_info['backup_status'] = 'empty'
|
||||||
|
volumegroup_info['last_backup_time'] = ''
|
||||||
|
volumegroup_info['owner_id'] = ''
|
||||||
|
volumegroup_info['owner_name'] = ''
|
||||||
|
volumegroup_info['safeguarded_policy_id'] = ''
|
||||||
|
volumegroup_info['safeguarded_policy_name'] = ''
|
||||||
|
volumegroup_info['safeguarded_policy_start_time'] = ''
|
||||||
|
volumegroup_info['volume_group_type'] = ''
|
||||||
|
volumegroup_info['uid'] = (('ABCDEF' * 3) + ('0' * 14) +
|
||||||
|
volumegroup_info['id'])
|
||||||
|
volumegroup_info['source_volume_group_id'] = ''
|
||||||
|
volumegroup_info['source_volume_group_name'] = ''
|
||||||
|
volumegroup_info['parent_uid'] = ''
|
||||||
|
volumegroup_info['source_snapshot_id'] = ''
|
||||||
|
volumegroup_info['source_snapshot'] = ''
|
||||||
|
volumegroup_info['snapshot_count'] = '0'
|
||||||
|
volumegroup_info['protection_provisioned_capacity'] = '0.00MB'
|
||||||
|
volumegroup_info['protection_written_capacity'] = '0.00MB'
|
||||||
|
volumegroup_info['snapshot_policy_id'] = ''
|
||||||
|
volumegroup_info['snapshot_policy_name'] = ''
|
||||||
|
self._volumegroup_list[volumegroup_info['name']] = volumegroup_info
|
||||||
|
return ('Volume Group, id [' + volumegroup_info['id'] +
|
||||||
|
'], successfully created', '')
|
||||||
|
|
||||||
|
def _cmd_lsvolumegroup(self, **kwargs):
|
||||||
|
# List the volume group
|
||||||
|
if 'obj' not in kwargs:
|
||||||
|
rows = []
|
||||||
|
rows.append(['id', 'name', 'volume_count', 'backup_status',
|
||||||
|
'last_backup_time', 'owner_id', 'owner_name',
|
||||||
|
'safeguarded_policy_id', 'safeguarded_policy_name',
|
||||||
|
'safeguarded_policy_start_time', 'volume_group_type',
|
||||||
|
'uid', 'source_volume_group_id',
|
||||||
|
'source_volume_group_name', 'parent_uid',
|
||||||
|
'source_snapshot_id', 'source_snapshot',
|
||||||
|
'snapshot_count', 'protection_provisioned_capacity',
|
||||||
|
'protection_written_capacity', 'snapshot_policy_id',
|
||||||
|
'snapshot_policy_name'])
|
||||||
|
found = False
|
||||||
|
for volumegroup_name in sorted(self._volumegroup_list.keys()):
|
||||||
|
volumegroup = self._volumegroup_list[volumegroup_name]
|
||||||
|
filterstr = 'name=' + volumegroup['name']
|
||||||
|
if (('filtervalue' not in kwargs) or
|
||||||
|
(kwargs['filtervalue'] == filterstr)):
|
||||||
|
rows.append(['0', 'empty', '', '', '', '', '', '', '',
|
||||||
|
'((\'ABCDEF\' * 3) + (\'0\' * 14) +\
|
||||||
|
vg_info[\'id\'])', '', '', '', '', '', '0',
|
||||||
|
'0.00MB', '0.00MB', '', ''])
|
||||||
|
found = True
|
||||||
|
if found:
|
||||||
|
return self._print_info_cmd(rows=rows, **kwargs)
|
||||||
|
else:
|
||||||
|
return ('', '')
|
||||||
|
else:
|
||||||
|
volumegroup_info = kwargs['obj'].strip('\'\"')
|
||||||
|
if volumegroup_info not in self._volumegroup_list:
|
||||||
|
return self._errors['CMMVC5804E']
|
||||||
|
volumegroup_info = self._volumegroup_list[volumegroup_info]
|
||||||
|
rows = []
|
||||||
|
rows.append(['id', volumegroup_info['id']])
|
||||||
|
rows.append(['name', volumegroup_info['name']])
|
||||||
|
rows.append(['volume_count', '1'])
|
||||||
|
rows.append(['backup_status', 'off'])
|
||||||
|
rows.append(['last_backup_time',
|
||||||
|
volumegroup_info['last_backup_time']])
|
||||||
|
rows.append(['owner_id', volumegroup_info['owner_id']])
|
||||||
|
rows.append(['owner_name', volumegroup_info['owner_name']])
|
||||||
|
rows.append(['safeguarded_policy_id',
|
||||||
|
volumegroup_info['safeguarded_policy_id']])
|
||||||
|
rows.append(['safeguarded_policy_name',
|
||||||
|
volumegroup_info['safeguarded_policy_name']])
|
||||||
|
rows.append(['safeguarded_policy_start_time',
|
||||||
|
volumegroup_info['safeguarded_policy_start_time']])
|
||||||
|
rows.append(['volume_group_type',
|
||||||
|
volumegroup_info['volume_group_type']])
|
||||||
|
rows.append(['source_volume_group_id',
|
||||||
|
volumegroup_info['source_volume_group_id']])
|
||||||
|
rows.append(['source_volume_group_name',
|
||||||
|
volumegroup_info['source_volume_group_name']])
|
||||||
|
rows.append(['parent_uid', volumegroup_info['parent_uid']])
|
||||||
|
rows.append(['source_snapshot_id',
|
||||||
|
volumegroup_info['source_snapshot_id']])
|
||||||
|
rows.append(['source_snapshot',
|
||||||
|
volumegroup_info['source_snapshot']])
|
||||||
|
rows.append(['snapshot_count', volumegroup_info['snapshot_count']])
|
||||||
|
rows.append(['protection_provisioned_capacity', '1.00GB'])
|
||||||
|
rows.append(['protection_written_capacity', '0.75MB'])
|
||||||
|
rows.append(['snapshot_policy_id',
|
||||||
|
volumegroup_info['snapshot_policy_id']])
|
||||||
|
rows.append(['snapshot_policy_name',
|
||||||
|
volumegroup_info['snapshot_policy_name']])
|
||||||
|
|
||||||
|
if 'delim' in kwargs:
|
||||||
|
for index in range(len(rows)):
|
||||||
|
rows[index] = kwargs['delim'].join(rows[index])
|
||||||
|
return ('%s' % '\n'.join(rows), '')
|
||||||
|
|
||||||
|
def _cmd_rmvolumegroup(self, **kwargs):
|
||||||
|
# Delete a Volume Group
|
||||||
|
if 'obj' not in kwargs:
|
||||||
|
return self._errors['CMMVC5701E']
|
||||||
|
volumegroup_name = kwargs['obj'].strip('\'\"')
|
||||||
|
del self._volumegroup_list[volumegroup_name]
|
||||||
|
return ('', '')
|
||||||
|
|
||||||
def _cmd_mkrcconsistgrp(self, **kwargs):
|
def _cmd_mkrcconsistgrp(self, **kwargs):
|
||||||
master_sys = self._system_list['storwize-svc-sim']
|
master_sys = self._system_list['storwize-svc-sim']
|
||||||
aux_sys = self._system_list['aux-svc-sim']
|
aux_sys = self._system_list['aux-svc-sim']
|
||||||
@ -6901,6 +7022,120 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
|
|||||||
model_update = self.driver.create_group(self.ctxt, group)
|
model_update = self.driver.create_group(self.ctxt, group)
|
||||||
self.assertEqual(fields.GroupStatus.ERROR, model_update['status'])
|
self.assertEqual(fields.GroupStatus.ERROR, model_update['status'])
|
||||||
|
|
||||||
|
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
|
||||||
|
'create_volumegroup')
|
||||||
|
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
|
||||||
|
'delete_volumegroup')
|
||||||
|
def test_storwize_create_and_delete_volumegroup(self, delete_volumegroup,
|
||||||
|
create_volumegroup):
|
||||||
|
"""Test volume group creation and deletion"""
|
||||||
|
with mock.patch.object(storwize_svc_common.StorwizeHelpers,
|
||||||
|
'get_system_info') as get_system_info:
|
||||||
|
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)
|
||||||
|
|
||||||
|
volumegroup_spec = {'volume_group_enabled': '<is> True'}
|
||||||
|
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'])
|
||||||
|
|
||||||
|
vol_type_ref = volume_types.create(self.ctxt, 'non_rep_type', {})
|
||||||
|
volumegroup = testutils.create_group(
|
||||||
|
self.ctxt, group_type_id=volumegroup_type.id,
|
||||||
|
volume_type_ids=[vol_type_ref['id']])
|
||||||
|
|
||||||
|
# Create Volume Group
|
||||||
|
model_update = self.driver.create_group(self.ctxt, volumegroup)
|
||||||
|
self.assertTrue(create_volumegroup.called)
|
||||||
|
self.assertEqual(fields.GroupStatus.AVAILABLE,
|
||||||
|
model_update['status'])
|
||||||
|
# Delete Volume Group
|
||||||
|
model_update = self.driver.delete_group(self.ctxt, volumegroup, None)
|
||||||
|
self.assertTrue(delete_volumegroup.called)
|
||||||
|
self.assertEqual(fields.GroupStatus.DELETED,
|
||||||
|
model_update[0]['status'])
|
||||||
|
|
||||||
|
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
|
||||||
|
'create_volumegroup')
|
||||||
|
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
|
||||||
|
'delete_volumegroup')
|
||||||
|
def test_storwize_update_volumegroup(self, delete_volumegroup,
|
||||||
|
create_volumegroup):
|
||||||
|
"""Test volume group updation"""
|
||||||
|
with mock.patch.object(storwize_svc_common.StorwizeHelpers,
|
||||||
|
'get_system_info') as get_system_info:
|
||||||
|
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 volumegroup type
|
||||||
|
volumegroup_spec = {'volume_group_enabled': '<is> True'}
|
||||||
|
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 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
|
||||||
|
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)
|
||||||
|
self.assertTrue(create_volumegroup.called)
|
||||||
|
self.assertEqual(fields.GroupStatus.AVAILABLE,
|
||||||
|
model_update['status'])
|
||||||
|
|
||||||
|
add_vols = [volume]
|
||||||
|
remove_vols = [volume]
|
||||||
|
with mock.patch.object(
|
||||||
|
storwize_svc_common.StorwizeSVCCommonDriver,
|
||||||
|
'_update_volumegroup') as _update_volumegroup:
|
||||||
|
model_update = {'status': 'available'}
|
||||||
|
fake_update_volumegroup_info = [model_update, add_vols, None]
|
||||||
|
_update_volumegroup.return_value = fake_update_volumegroup_info
|
||||||
|
(model_update, add_volumes_update,
|
||||||
|
remove_volumes_update) = self.driver.update_group(
|
||||||
|
self.ctxt, volumegroup, add_vols, [])
|
||||||
|
|
||||||
|
self.assertTrue(_update_volumegroup.called)
|
||||||
|
self.assertEqual(fields.GroupStatus.AVAILABLE,
|
||||||
|
model_update['status'])
|
||||||
|
|
||||||
|
model_update = {'status': 'available'}
|
||||||
|
fake_update_volumegroup_info = [model_update, None,
|
||||||
|
remove_vols]
|
||||||
|
_update_volumegroup.return_value = (
|
||||||
|
fake_update_volumegroup_info)
|
||||||
|
(model_update, add_volumes_update,
|
||||||
|
remove_volumes_update) = self.driver.update_group(
|
||||||
|
self.ctxt, volumegroup, [], remove_vols)
|
||||||
|
|
||||||
|
self.assertTrue(_update_volumegroup.called)
|
||||||
|
self.assertEqual(fields.GroupStatus.AVAILABLE,
|
||||||
|
model_update['status'])
|
||||||
|
|
||||||
|
# Delete Volume Group
|
||||||
|
model_update = self.driver.delete_group(self.ctxt, volumegroup,
|
||||||
|
None)
|
||||||
|
self.assertTrue(delete_volumegroup.called)
|
||||||
|
self.assertEqual(fields.GroupStatus.DELETED,
|
||||||
|
model_update[0]['status'])
|
||||||
|
|
||||||
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
|
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
|
||||||
'create_rccg')
|
'create_rccg')
|
||||||
def test_storwize_group_create(self, create_rccg):
|
def test_storwize_group_create(self, create_rccg):
|
||||||
@ -6940,7 +7175,8 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
|
|||||||
def test_storwize_delete_group(self, _del_rep_grp, is_grp_a_cg_rep_type,
|
def test_storwize_delete_group(self, _del_rep_grp, is_grp_a_cg_rep_type,
|
||||||
is_grp_a_cg_snapshot_type):
|
is_grp_a_cg_snapshot_type):
|
||||||
is_grp_a_cg_snapshot_type.side_effect = [True, True, False, True]
|
is_grp_a_cg_snapshot_type.side_effect = [True, True, False, True]
|
||||||
is_grp_a_cg_rep_type.side_effect = [False, False, False, False]
|
is_grp_a_cg_rep_type.side_effect = [False, False, False, False,
|
||||||
|
False, False]
|
||||||
type_ref = volume_types.create(self.ctxt, 'testtype', None)
|
type_ref = volume_types.create(self.ctxt, 'testtype', None)
|
||||||
group = testutils.create_group(self.ctxt,
|
group = testutils.create_group(self.ctxt,
|
||||||
group_type_id=fake.GROUP_TYPE_ID,
|
group_type_id=fake.GROUP_TYPE_ID,
|
||||||
@ -6969,7 +7205,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
|
|||||||
is_grp_a_cg_snapshot_type):
|
is_grp_a_cg_snapshot_type):
|
||||||
"""Test group update."""
|
"""Test group update."""
|
||||||
is_grp_a_cg_snapshot_type.side_effect = [False, True, True, False]
|
is_grp_a_cg_snapshot_type.side_effect = [False, True, True, False]
|
||||||
is_grp_a_cg_rep_type.side_effect = [False, False, False,
|
is_grp_a_cg_rep_type.side_effect = [False, False, False, False,
|
||||||
False, True, True]
|
False, True, True]
|
||||||
group = mock.MagicMock()
|
group = mock.MagicMock()
|
||||||
self.assertRaises(NotImplementedError, self.driver.update_group,
|
self.assertRaises(NotImplementedError, self.driver.update_group,
|
||||||
|
@ -49,6 +49,8 @@ REPLICA_CHG_VOL_PREFIX = 'chg_'
|
|||||||
RCCG_PREFIX = 'rccg-'
|
RCCG_PREFIX = 'rccg-'
|
||||||
HYPERCG_PREFIX = 'hycg-'
|
HYPERCG_PREFIX = 'hycg-'
|
||||||
|
|
||||||
|
VG_PREFIX = 'vg-'
|
||||||
|
|
||||||
# remote mirror copy status
|
# remote mirror copy status
|
||||||
REP_CONSIS_SYNC = 'consistent_synchronized'
|
REP_CONSIS_SYNC = 'consistent_synchronized'
|
||||||
REP_CONSIS_COPYING = 'consistent_copying'
|
REP_CONSIS_COPYING = 'consistent_copying'
|
||||||
|
@ -540,6 +540,52 @@ class StorwizeSSH(object):
|
|||||||
ssh_cmd = ['svctask', 'rmhost', '"%s"' % host]
|
ssh_cmd = ['svctask', 'rmhost', '"%s"' % host]
|
||||||
self.run_ssh_assert_no_output(ssh_cmd)
|
self.run_ssh_assert_no_output(ssh_cmd)
|
||||||
|
|
||||||
|
def mkvolumegroup(self, volumegroup_name):
|
||||||
|
"""Create a volume group(VG)."""
|
||||||
|
ssh_cmd = ['svctask', 'mkvolumegroup', '-name', '"%s"'
|
||||||
|
% volumegroup_name]
|
||||||
|
try:
|
||||||
|
return self.run_ssh_check_created(ssh_cmd)
|
||||||
|
except Exception as ex:
|
||||||
|
if hasattr(ex, 'msg') and 'CMMVC6035E' in ex.msg:
|
||||||
|
msg = (_('CMMVC6372W Action failed because volume group '
|
||||||
|
'with the name provided already exists.'))
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.VolumeBackendAPIException(data=msg)
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.exception('Failed to create volumegroup.')
|
||||||
|
|
||||||
|
def lsvolumegroup(self, volumegroup_id_or_name):
|
||||||
|
"""Return volume group attributes or None if it doesn't exist."""
|
||||||
|
ssh_cmd = ['svcinfo', 'lsvolumegroup', '-bytes', '-delim', '!',
|
||||||
|
'"%s"' % volumegroup_id_or_name]
|
||||||
|
out, err = self._ssh(ssh_cmd, check_exit_code=False)
|
||||||
|
if not err:
|
||||||
|
return CLIResponse((out, err), ssh_cmd=ssh_cmd, delim='!',
|
||||||
|
with_header=False)[0]
|
||||||
|
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 rmvolumegroup(self, volumegroup_name_or_id):
|
||||||
|
"""Delete a volume group"""
|
||||||
|
ssh_cmd = ['svctask', 'rmvolumegroup', '"%s"' % volumegroup_name_or_id]
|
||||||
|
try:
|
||||||
|
self.run_ssh_assert_no_output(ssh_cmd)
|
||||||
|
except Exception as ex:
|
||||||
|
if hasattr(ex, 'msg') and 'CMMVC8749E' in ex.msg:
|
||||||
|
msg = _('rmvolumegroup: specified volume group is not empty.')
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.VolumeDriverException(message=msg)
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.exception('Failed to delete volumegroup.')
|
||||||
|
|
||||||
def mkvdisk(self, name, size, units, pool, opts, params):
|
def mkvdisk(self, name, size, units, pool, opts, params):
|
||||||
ssh_cmd = ['svctask', 'mkvdisk', '-name', '"%s"' % name, '-mdiskgrp',
|
ssh_cmd = ['svctask', 'mkvdisk', '-name', '"%s"' % name, '-mdiskgrp',
|
||||||
'"%s"' % pool, '-iogrp', six.text_type(opts['iogrp']),
|
'"%s"' % pool, '-iogrp', six.text_type(opts['iogrp']),
|
||||||
@ -2769,6 +2815,34 @@ class StorwizeHelpers(object):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def create_volumegroup(self, volumegroup_name):
|
||||||
|
self.ssh.mkvolumegroup(volumegroup_name)
|
||||||
|
|
||||||
|
def get_volumegroup(self, volumegroup_id_or_name):
|
||||||
|
vg = self.ssh.lsvolumegroup(volumegroup_id_or_name)
|
||||||
|
return vg if len(vg) > 0 else None
|
||||||
|
|
||||||
|
def delete_volumegroup(self, volumegroup_id_or_name):
|
||||||
|
if self.ssh.lsvolumegroup(volumegroup_id_or_name):
|
||||||
|
self.ssh.rmvolumegroup(volumegroup_id_or_name)
|
||||||
|
|
||||||
|
def add_vdisk_to_volumegroup(self, vol_name, volumegroup_id):
|
||||||
|
self.ssh.chvdisk(vol_name, ['-volumegroup', volumegroup_id])
|
||||||
|
|
||||||
|
def remove_vdisk_from_volumegroup(self, vol_name):
|
||||||
|
self.ssh.chvdisk(vol_name, ['-novolumegroup'])
|
||||||
|
|
||||||
|
def check_codelevel_for_volumegroup(self, code_level):
|
||||||
|
if not (code_level >= (8, 5, 1, 0)):
|
||||||
|
msg = (_('The configured group type spec is '
|
||||||
|
'"volume_group_enabled". '
|
||||||
|
'The supported code level for this group type spec '
|
||||||
|
'is 8.5.1.0 '
|
||||||
|
'The current storage code level is %(code_level)s.')
|
||||||
|
% {'code_level': code_level})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.VolumeDriverException(message=msg)
|
||||||
|
|
||||||
def get_partnership_info(self, system_name):
|
def get_partnership_info(self, system_name):
|
||||||
partnership = self.ssh.lspartnership(system_name)
|
partnership = self.ssh.lspartnership(system_name)
|
||||||
return partnership[0] if len(partnership) > 0 else None
|
return partnership[0] if len(partnership) > 0 else None
|
||||||
@ -3711,6 +3785,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
volume.metadata['Consistency Group Name'] = rccg_name
|
volume.metadata['Consistency Group Name'] = rccg_name
|
||||||
volume.save()
|
volume.save()
|
||||||
|
|
||||||
|
def _update_volumegroup_properties(self, ctxt, volume, group=None):
|
||||||
|
volumegroup_name = self._get_volumegroup_name(group) if group else ""
|
||||||
|
if not volume.metadata:
|
||||||
|
volume.metadata = dict()
|
||||||
|
volume.metadata['Volume Group Name'] = volumegroup_name
|
||||||
|
volume.save()
|
||||||
|
|
||||||
def create_volume(self, volume):
|
def create_volume(self, volume):
|
||||||
LOG.debug('enter: create_volume: volume %s', volume['name'])
|
LOG.debug('enter: create_volume: volume %s', volume['name'])
|
||||||
# Create a replication or hyperswap volume with group_id is not
|
# Create a replication or hyperswap volume with group_id is not
|
||||||
@ -5990,6 +6071,12 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
if hyper_grp else storwize_const.RCCG_PREFIX)
|
if hyper_grp else storwize_const.RCCG_PREFIX)
|
||||||
return rccg + group_id[0:4] + '-' + group_id[-5:]
|
return rccg + group_id[0:4] + '-' + group_id[-5:]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_volumegroup_name(group, grp_id=None):
|
||||||
|
group_id = group.id if group else grp_id
|
||||||
|
vg = storwize_const.VG_PREFIX
|
||||||
|
return vg + group_id[0:4] + '-' + group_id[-5:]
|
||||||
|
|
||||||
# Add CG capability to generic volume groups
|
# Add CG capability to generic volume groups
|
||||||
def create_group(self, context, group):
|
def create_group(self, context, group):
|
||||||
"""Creates a group.
|
"""Creates a group.
|
||||||
@ -6011,7 +6098,8 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
support_grps = ['group_snapshot_enabled',
|
support_grps = ['group_snapshot_enabled',
|
||||||
'consistent_group_snapshot_enabled',
|
'consistent_group_snapshot_enabled',
|
||||||
'consistent_group_replication_enabled',
|
'consistent_group_replication_enabled',
|
||||||
'hyperswap_group_enabled']
|
'hyperswap_group_enabled',
|
||||||
|
'volume_group_enabled']
|
||||||
supported_grp = False
|
supported_grp = False
|
||||||
for grp_spec in support_grps:
|
for grp_spec in support_grps:
|
||||||
if volume_utils.is_group_a_type(group, grp_spec):
|
if volume_utils.is_group_a_type(group, grp_spec):
|
||||||
@ -6102,6 +6190,19 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
model_update = {'status': fields.GroupStatus.ERROR}
|
model_update = {'status': fields.GroupStatus.ERROR}
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
|
if volume_utils.is_group_a_type(group, "volume_group_enabled"):
|
||||||
|
try:
|
||||||
|
self._helpers.check_codelevel_for_volumegroup(
|
||||||
|
self._state['code_level'])
|
||||||
|
volumegroup_name = self._get_volumegroup_name(group)
|
||||||
|
self._helpers.create_volumegroup(volumegroup_name)
|
||||||
|
except exception.VolumeBackendAPIException as err:
|
||||||
|
LOG.error("Failed to create volume group %(volumegroup)s. "
|
||||||
|
"Exception: %(exception)s.",
|
||||||
|
{'volumegroup': volumegroup_name, 'exception': err})
|
||||||
|
model_update = {'status': fields.GroupStatus.ERROR}
|
||||||
|
return model_update
|
||||||
|
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
def delete_group(self, context, group, volumes):
|
def delete_group(self, context, group, volumes):
|
||||||
@ -6123,7 +6224,10 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
"consistent_group_replication_enabled")
|
"consistent_group_replication_enabled")
|
||||||
and not volume_utils.is_group_a_type(
|
and not volume_utils.is_group_a_type(
|
||||||
group,
|
group,
|
||||||
"hyperswap_group_enabled")):
|
"hyperswap_group_enabled")
|
||||||
|
and not volume_utils.is_group_a_type(
|
||||||
|
group,
|
||||||
|
"volume_group_enabled")):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
model_update = {'status': fields.GroupStatus.DELETED}
|
model_update = {'status': fields.GroupStatus.DELETED}
|
||||||
@ -6133,10 +6237,15 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
model_update, volumes_model_update = self._delete_replication_grp(
|
model_update, volumes_model_update = self._delete_replication_grp(
|
||||||
group, volumes)
|
group, volumes)
|
||||||
|
|
||||||
if volume_utils.is_group_a_type(group, "hyperswap_group_enabled"):
|
elif volume_utils.is_group_a_type(group, "hyperswap_group_enabled"):
|
||||||
model_update, volumes_model_update = self._delete_hyperswap_grp(
|
model_update, volumes_model_update = self._delete_hyperswap_grp(
|
||||||
group, volumes)
|
group, volumes)
|
||||||
|
|
||||||
|
elif volume_utils.is_group_a_type(group, "volume_group_enabled"):
|
||||||
|
self._helpers.check_codelevel_for_volumegroup(
|
||||||
|
self._state['code_level'])
|
||||||
|
model_update = self._delete_volumegroup(group)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for volume in volumes:
|
for volume in volumes:
|
||||||
try:
|
try:
|
||||||
@ -6180,7 +6289,10 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
"consistent_group_replication_enabled")
|
"consistent_group_replication_enabled")
|
||||||
and not volume_utils.is_group_a_type(
|
and not volume_utils.is_group_a_type(
|
||||||
group,
|
group,
|
||||||
"hyperswap_group_enabled")):
|
"hyperswap_group_enabled")
|
||||||
|
and not volume_utils.is_group_a_type(
|
||||||
|
group,
|
||||||
|
"volume_group_enabled")):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
if volume_utils.is_group_a_type(
|
if volume_utils.is_group_a_type(
|
||||||
@ -6195,6 +6307,12 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
if volume_utils.is_group_a_cg_snapshot_type(group):
|
if volume_utils.is_group_a_cg_snapshot_type(group):
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
|
if volume_utils.is_group_a_type(group, "volume_group_enabled"):
|
||||||
|
self._helpers.check_codelevel_for_volumegroup(
|
||||||
|
self._state['code_level'])
|
||||||
|
return self._update_volumegroup(context, group, add_volumes,
|
||||||
|
remove_volumes)
|
||||||
|
|
||||||
def create_group_from_src(self, context, group, volumes,
|
def create_group_from_src(self, context, group, volumes,
|
||||||
group_snapshot=None, snapshots=None,
|
group_snapshot=None, snapshots=None,
|
||||||
source_group=None, source_vols=None):
|
source_group=None, source_vols=None):
|
||||||
@ -6778,6 +6896,75 @@ class StorwizeSVCCommonDriver(san.SanDriver,
|
|||||||
{'vol': volume.name, 'exception': err})
|
{'vol': volume.name, 'exception': err})
|
||||||
return model_update, added_vols, removed_vols
|
return model_update, added_vols, removed_vols
|
||||||
|
|
||||||
|
def _delete_volumegroup(self, group):
|
||||||
|
model_update = {'status': fields.GroupStatus.DELETED}
|
||||||
|
volumegroup_name = self._get_volumegroup_name(group)
|
||||||
|
try:
|
||||||
|
self._helpers.delete_volumegroup(volumegroup_name)
|
||||||
|
except exception.VolumeBackendAPIException as err:
|
||||||
|
LOG.error("Failed to delete volume group %(volumegroup)s. "
|
||||||
|
"Exception: %(exception)s.",
|
||||||
|
{'volumegroup': volumegroup_name, 'exception': err})
|
||||||
|
model_update = {'status': fields.GroupStatus.ERROR_DELETING}
|
||||||
|
|
||||||
|
return model_update
|
||||||
|
|
||||||
|
def _update_volumegroup(self, context, group, add_volumes,
|
||||||
|
remove_volumes):
|
||||||
|
model_update = {'status': fields.GroupStatus.AVAILABLE}
|
||||||
|
LOG.info("Update volume group: %(volumegroup_id)s. ",
|
||||||
|
{'volumegroup_id': group.id})
|
||||||
|
|
||||||
|
volumegroup_name = self._get_volumegroup_name(group)
|
||||||
|
# This code block fails during remove of volumes from group
|
||||||
|
try:
|
||||||
|
volumegroup = self._helpers.get_volumegroup(volumegroup_name)
|
||||||
|
volumegroup_id = volumegroup["id"]
|
||||||
|
except Exception as ex:
|
||||||
|
if len(add_volumes) > 0:
|
||||||
|
LOG.exception("Unable to retrieve volume group "
|
||||||
|
"information. Failed with exception "
|
||||||
|
"%(ex)s", ex)
|
||||||
|
if not volumegroup and len(add_volumes) > 0:
|
||||||
|
LOG.error("Failed to update group: %(volumegroup)s does not "
|
||||||
|
"exist in backend.",
|
||||||
|
{'volumegroup': volumegroup_name})
|
||||||
|
model_update['status'] = fields.GroupStatus.ERROR
|
||||||
|
return model_update, None, None
|
||||||
|
|
||||||
|
# Add volume(s) to the volume group
|
||||||
|
added_vols = []
|
||||||
|
for volume in add_volumes:
|
||||||
|
vol_name = volume.name
|
||||||
|
try:
|
||||||
|
self._helpers.add_vdisk_to_volumegroup(vol_name,
|
||||||
|
volumegroup_id)
|
||||||
|
added_vols.append({'id': volume.id,
|
||||||
|
'group_id': group.id})
|
||||||
|
self._update_volumegroup_properties(context, volume, group)
|
||||||
|
except exception.VolumeBackendAPIException as err:
|
||||||
|
model_update['status'] = fields.GroupStatus.ERROR
|
||||||
|
LOG.error("Failed to add the volume %(vol)s to "
|
||||||
|
"group. Exception: %(exception)s.",
|
||||||
|
{'vol': volume.name, 'exception': err})
|
||||||
|
|
||||||
|
# Remove volume(s) from the volume group
|
||||||
|
removed_vols = []
|
||||||
|
for volume in remove_volumes:
|
||||||
|
vol_name = volume.name
|
||||||
|
try:
|
||||||
|
self._helpers.remove_vdisk_from_volumegroup(vol_name)
|
||||||
|
removed_vols.append({'id': volume.id,
|
||||||
|
'group_id': None})
|
||||||
|
self._update_volumegroup_properties(context, volume)
|
||||||
|
except exception.VolumeBackendAPIException as err:
|
||||||
|
model_update['status'] = fields.GroupStatus.ERROR
|
||||||
|
LOG.error("Failed to remove the volume %(vol)s from "
|
||||||
|
"group. Exception: %(exception)s.",
|
||||||
|
{'vol': volume.name, 'exception': err})
|
||||||
|
|
||||||
|
return model_update, added_vols, removed_vols
|
||||||
|
|
||||||
def _delete_hyperswap_grp(self, group, volumes):
|
def _delete_hyperswap_grp(self, group, volumes):
|
||||||
model_update = {'status': fields.GroupStatus.DELETED}
|
model_update = {'status': fields.GroupStatus.DELETED}
|
||||||
volumes_model_update = []
|
volumes_model_update = []
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
IBM Spectrum Virtualize Family driver: Added support for volumegroup
|
||||||
|
for SVC Code Level 8.5.1.0 and above. User can now create, modify
|
||||||
|
and delete volumegroup using the exising cinder CLI for group operations.
|
Loading…
Reference in New Issue
Block a user