From ac9debdf3590b12b1699e38ba48bb310bb0fed1f Mon Sep 17 00:00:00 2001 From: haailani Date: Mon, 8 Aug 2022 14:08:42 +0000 Subject: [PATCH] [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 --- .../volume/drivers/ibm/test_storwize_svc.py | 244 +++++++++++++++++- .../ibm/storwize_svc/storwize_const.py | 2 + .../ibm/storwize_svc/storwize_svc_common.py | 195 +++++++++++++- ...-volumegroup-support-134fc2194ad092bd.yaml | 6 + 4 files changed, 439 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/ibm-svf-volumegroup-support-134fc2194ad092bd.yaml diff --git a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py index 9184b04eaf1..8b0cbdeb67a 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py @@ -81,6 +81,7 @@ class StorwizeSVCManagementSimulator(object): self._partnership_list = {} self._partnershipcandidate_list = {} self._rcconsistgrp_list = {} + self._volumegroup_list = {} self._system_list = {'storwize-svc-sim': {'id': '0123456789ABCDEF', 'name': 'storwize-svc-sim'}, 'aux-svc-sim': {'id': 'ABCDEF0123456789', @@ -375,7 +376,8 @@ class StorwizeSVCManagementSimulator(object): 'thin', 'removehostmappings', 'removefcmaps', - 'removercrelationships' + 'removercrelationships', + 'novolumegroup' ] one_param_args = [ 'chapsecret', @@ -416,6 +418,7 @@ class StorwizeSVCManagementSimulator(object): 'pool', 'site', 'buffersize', + 'volumegroup' ] no_or_one_param_args = [ 'autoexpand', @@ -2039,7 +2042,8 @@ port_speed!N/A kwargs.pop('obj') params = ['name', 'warning', 'udid', - 'autoexpand', 'easytier', 'primary'] + 'autoexpand', 'easytier', 'primary', + 'volumegroup', 'novolumegroup'] for key, value in kwargs.items(): if key == 'easytier': vol['easy_tier'] = value @@ -2067,6 +2071,8 @@ port_speed!N/A else: err = self._errors['CMMVC6353E'][1] % {'VALUE': key} return ('', err) + if key == 'volumegroup': + self._volumes_list[vol_name]['volume_group_id'] = value if key in params: vol[key] = value if key == 'autoexpand': @@ -2531,6 +2537,121 @@ port_speed!N/A except Exception: 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): master_sys = self._system_list['storwize-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) 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': ' 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': ' 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, '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, is_grp_a_cg_snapshot_type): 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) group = testutils.create_group(self.ctxt, group_type_id=fake.GROUP_TYPE_ID, @@ -6969,7 +7205,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): is_grp_a_cg_snapshot_type): """Test group update.""" 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] group = mock.MagicMock() self.assertRaises(NotImplementedError, self.driver.update_group, diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_const.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_const.py index 593c4b00aae..ef841732c81 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_const.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_const.py @@ -49,6 +49,8 @@ REPLICA_CHG_VOL_PREFIX = 'chg_' RCCG_PREFIX = 'rccg-' HYPERCG_PREFIX = 'hycg-' +VG_PREFIX = 'vg-' + # remote mirror copy status REP_CONSIS_SYNC = 'consistent_synchronized' REP_CONSIS_COPYING = 'consistent_copying' diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py index e4153521308..9b3d2eb8e5d 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py @@ -540,6 +540,52 @@ class StorwizeSSH(object): ssh_cmd = ['svctask', 'rmhost', '"%s"' % host] 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): ssh_cmd = ['svctask', 'mkvdisk', '-name', '"%s"' % name, '-mdiskgrp', '"%s"' % pool, '-iogrp', six.text_type(opts['iogrp']), @@ -2769,6 +2815,34 @@ class StorwizeHelpers(object): else: 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): partnership = self.ssh.lspartnership(system_name) 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.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): LOG.debug('enter: create_volume: volume %s', volume['name']) # 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) 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 def create_group(self, context, group): """Creates a group. @@ -6011,7 +6098,8 @@ class StorwizeSVCCommonDriver(san.SanDriver, support_grps = ['group_snapshot_enabled', 'consistent_group_snapshot_enabled', 'consistent_group_replication_enabled', - 'hyperswap_group_enabled'] + 'hyperswap_group_enabled', + 'volume_group_enabled'] supported_grp = False for grp_spec in support_grps: if volume_utils.is_group_a_type(group, grp_spec): @@ -6102,6 +6190,19 @@ class StorwizeSVCCommonDriver(san.SanDriver, model_update = {'status': fields.GroupStatus.ERROR} 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 def delete_group(self, context, group, volumes): @@ -6123,7 +6224,10 @@ class StorwizeSVCCommonDriver(san.SanDriver, "consistent_group_replication_enabled") and not volume_utils.is_group_a_type( group, - "hyperswap_group_enabled")): + "hyperswap_group_enabled") + and not volume_utils.is_group_a_type( + group, + "volume_group_enabled")): raise NotImplementedError() model_update = {'status': fields.GroupStatus.DELETED} @@ -6133,10 +6237,15 @@ class StorwizeSVCCommonDriver(san.SanDriver, model_update, volumes_model_update = self._delete_replication_grp( 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( 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: for volume in volumes: try: @@ -6180,7 +6289,10 @@ class StorwizeSVCCommonDriver(san.SanDriver, "consistent_group_replication_enabled") and not volume_utils.is_group_a_type( group, - "hyperswap_group_enabled")): + "hyperswap_group_enabled") + and not volume_utils.is_group_a_type( + group, + "volume_group_enabled")): raise NotImplementedError() 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): 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, group_snapshot=None, snapshots=None, source_group=None, source_vols=None): @@ -6778,6 +6896,75 @@ class StorwizeSVCCommonDriver(san.SanDriver, {'vol': volume.name, 'exception': err}) 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): model_update = {'status': fields.GroupStatus.DELETED} volumes_model_update = [] diff --git a/releasenotes/notes/ibm-svf-volumegroup-support-134fc2194ad092bd.yaml b/releasenotes/notes/ibm-svf-volumegroup-support-134fc2194ad092bd.yaml new file mode 100644 index 00000000000..ad36c97e95e --- /dev/null +++ b/releasenotes/notes/ibm-svf-volumegroup-support-134fc2194ad092bd.yaml @@ -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.