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 50f9055e9dc..16cb05a8d83 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py @@ -96,7 +96,8 @@ class StorwizeSVCManagementSimulator(object): 'lslicense': '', 'lsguicapabilities': '', 'lshost': '', - 'lsrcrelationship': '' + 'lsrcrelationship': '', + 'expandvdisksize': '' } self._errors = { 'CMMVC5701E': ('', 'CMMVC5701E No object ID was specified.'), @@ -167,6 +168,14 @@ class StorwizeSVCManagementSimulator(object): 'is not in a group.'), 'CMMVC9012E': ('', 'CMMVC9012E The copy type differs from other ' 'copies already in the consistency group.'), + 'CMMVC9201E': ('', 'CMMVC9201E Task failed because volume has a ' + 'copy that is fully allocated and is part of a ' + 'Metro Mirror or Global Mirror relationship.'), + 'CMMVC8587E': ('', 'CMMVC8587E The command failed because the ' + 'volume is fast formatting.'), + 'CMMVC8783E': ('', 'CMMVC8783E The volume copy was not deleted ' + 'because the volume is part of a consistency ' + 'group.'), } self._fc_transitions = {'begin': {'make': 'idle_or_copied'}, 'idle_or_copied': {'prepare': 'preparing', @@ -1037,6 +1046,14 @@ port_speed!N/A if vol_name not in self._volumes_list: return self._errors['CMMVC5753E'] + vol = self._volumes_list[kwargs['obj']] + if self._next_cmd_error['expandvdisksize'] == 'fast_formatting': + if vol['RC_name']: + rcrel = self._rcrelationship_list[vol['RC_name']] + if rcrel.get('copy_type', None): + return self._errors['CMMVC9201E'] + return self._errors['CMMVC8587E'] + curr_size = int(self._volumes_list[vol_name]['capacity']) addition = size * units.Gi self._volumes_list[vol_name]['capacity'] = ( @@ -2964,6 +2981,11 @@ port_speed!N/A return self._errors['CMMVC5701E'] vol_name = kwargs['obj'].strip('\'\"') site1_volume_info = self._volumes_list[vol_name] + if site1_volume_info['RC_name']: + rcrel = self._rcrelationship_list[site1_volume_info['RC_name']] + if rcrel.get('consistency_group_name', None): + return self._errors['CMMVC8783E'] + site2_volume_info = self._volumes_list['site2' + vol_name] del self._rcrelationship_list[self._volumes_list[vol_name]['RC_name']] @@ -2985,6 +3007,9 @@ port_speed!N/A del self._fcmappings_list[site2fcmap['id']] del site2_volume_info + del self._volumes_list['site2' + vol_name] + del self._volumes_list['fcsite1' + vol_name] + del self._volumes_list['fcsite2' + vol_name] site1_volume_info['RC_name'] = '' site1_volume_info['RC_id'] = '' return ('', '') @@ -7643,12 +7668,78 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): 'system_id': '0123456789ABCDEF'} get_system_info.return_value = fake_system_info self.driver.do_setup(None) - - hyper_type = self._create_hyperswap_type('test_hyperswap_type') - vol = self._create_hyperswap_volume(hyper_type) + spec = {'drivers:volume_topology': 'hyperswap', + 'peer_pool': 'hyperswap2'} + vol_type_ref = volume_types.create(self.ctxt, 'test_hyperswap_type', + spec) + vol = self._create_hyperswap_volume(vol_type_ref) self._assert_vol_exists(vol.name, True) - self.assertRaises(exception.InvalidInput, - self.driver.extend_volume, vol, '16') + + self.driver.extend_volume(vol, '13') + attrs = self.driver._helpers.get_vdisk_attributes(vol['name']) + vol_size = int(attrs['capacity']) / units.Gi + self.assertAlmostEqual(vol_size, 13) + self.driver.delete_volume(vol) + + # Extend hyperswap volume with thick_provisioning_support. + spec = {'drivers:volume_topology': 'hyperswap', + 'peer_pool': 'hyperswap2', + 'drivers:rsize': -1} + hs_thick_type = volume_types.create( + self.ctxt, 'test_hyperswap_thick_type', spec) + hs_vol = self._create_hyperswap_volume(hs_thick_type) + self._assert_vol_exists(hs_vol.name, True) + + if self.USESIM: + # tell expandvdisksize to fail while called extend_volume + # because volume is fast formatting + self.sim.error_injection('expandvdisksize', 'fast_formatting') + self.assertRaises(exception.VolumeDriverException, + self.driver.extend_volume, hs_vol, 15) + attrs = self.driver._helpers.get_vdisk_attributes(hs_vol['name']) + vol_size = int(attrs['capacity']) / units.Gi + self.assertAlmostEqual(vol_size, 1) + self.driver.delete_volume(hs_vol) + + # Extend hyperswap volume that added to group. + with mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'extend_vdisk') as extend_vdisk: + group_specs = {'hyperswap_group_enabled': ' True'} + group_type_ref = group_types.create(self.ctxt, 'testgroup', + group_specs) + hyper_group = testutils.create_group( + self.ctxt, name='hypergroup', + group_type_id=group_type_ref['id'], + volume_type_ids=[vol_type_ref['id']]) + model_update = self.driver.create_group(self.ctxt, hyper_group) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) + + vol = self._create_hyperswap_volume(vol_type_ref) + self.db.volume_update(context.get_admin_context(), vol['id'], + {'group_id': hyper_group.id}) + add_volumes = [vol] + del_volumes = [] + + (model_update, add_volumes_update, + remove_volumes_update) = self.driver.update_group(self.ctxt, + hyper_group, + add_volumes, + del_volumes) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) + self.assertEqual([{'id': vol.id, 'group_id': hyper_group.id}], + add_volumes_update) + self.assertEqual([], remove_volumes_update) + + self.assertRaises(exception.VolumeDriverException, + self.driver.extend_volume, vol, 15) + + self.assertFalse(extend_vdisk.called) + attrs = self.driver._helpers.get_vdisk_attributes(vol['name']) + vol_size = int(attrs['capacity']) / units.Gi + self.assertAlmostEqual(vol_size, 1) + self.driver.delete_volume(vol) def test_migrate_hyperswap_volume(self): with mock.patch.object(storwize_svc_common.StorwizeHelpers, 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 b10df882bb8..a4dcde57f7a 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py @@ -1680,7 +1680,8 @@ class StorwizeHelpers(object): params.append('-compressed') else: params.append('-thin') - params.extend(['-grainsize', six.text_type(opts['grainsize'])]) + params.extend(['-grainsize', + six.text_type(opts['grainsize'])]) return params def create_hyperswap_volume(self, vol_name, size, units, pool, opts): @@ -1712,11 +1713,32 @@ class StorwizeHelpers(object): pool = attr['mdisk_grp_name'] self.check_hyperswap_pool(pool, opts['peer_pool']) hyper_pool = '%s' % opts['peer_pool'] - is_dr_pool = self.is_volume_type_dr_pools(pool, opts) - if is_dr_pool and opts['rsize'] != -1: + params = [] + if opts['rsize'] != -1: + is_dr_pool = self.is_volume_type_dr_pools(pool, opts) + if is_dr_pool: + self.check_data_reduction_pool_params(opts) + params = self._get_hyperswap_volume_create_params(opts, + is_dr_pool) + self.ssh.addvolumecopy(vol_name, hyper_pool, params) + + def convert_extended_volume_to_hyperswap(self, vol_name, opts, state): + vol_name = '%s' % vol_name + attr = self.get_vdisk_attributes(vol_name) + if attr is None: + msg = (_('convert_volume_to_hyperswap: Failed to get ' + 'attributes for volume %s.') % vol_name) + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + hyper_pool = '%s' % opts['peer_pool'] + params = [] + if opts['rsize'] != -1: + is_dr_pool = self.is_volume_type_dr_pools(attr['mdisk_grp_name'], + opts) + if is_dr_pool: self.check_data_reduction_pool_params(opts) params = self._get_hyperswap_volume_create_params(opts, is_dr_pool) - self.ssh.addvolumecopy(vol_name, hyper_pool, params) + self.ssh.addvolumecopy(vol_name, hyper_pool, params) def convert_hyperswap_volume_to_normal(self, vol_name, peer_pool): vol_name = '%s' % vol_name @@ -3519,11 +3541,6 @@ class StorwizeSVCCommonDriver(san.SanDriver, def _extend_volume_op(self, volume, new_size, old_size=None): LOG.debug('enter: _extend_volume_op: volume %s', volume['id']) volume_name = self._get_target_vol(volume) - if self.is_volume_hyperswap(volume): - msg = _('_extend_volume_op: Extending a hyperswap volume is ' - 'not supported.') - LOG.error(msg) - raise exception.InvalidInput(message=msg) ret = self._helpers.ensure_vdisk_no_fc_mappings(volume_name, allow_snaps=False) @@ -3540,51 +3557,84 @@ class StorwizeSVCCommonDriver(san.SanDriver, rel_info = self._helpers.get_relationship_info(volume_name) if rel_info: LOG.warning('_extend_volume_op: Extending a volume with ' - 'remote copy is not recommended.') - try: - rep_type = rel_info['copy_type'] - cyclingmode = rel_info['cycling_mode'] - self._master_backend_helpers.delete_relationship( - volume.name) - tgt_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX + - volume.name) - self._master_backend_helpers.extend_vdisk(volume.name, - extend_amt) - self._aux_backend_helpers.extend_vdisk(tgt_vol, extend_amt) - tgt_sys = self._aux_backend_helpers.get_system_info() - if storwize_const.GMCV_MULTI == cyclingmode: - tgt_change_vol = ( - storwize_const.REPLICA_CHG_VOL_PREFIX + - tgt_vol) - source_change_vol = ( - storwize_const.REPLICA_CHG_VOL_PREFIX + - volume.name) - self._master_backend_helpers.extend_vdisk( - source_change_vol, extend_amt) - self._aux_backend_helpers.extend_vdisk( - tgt_change_vol, extend_amt) - src_change_opts = self._get_vdisk_params( - volume.volume_type_id) - cycle_period_seconds = src_change_opts.get( - 'cycle_period_seconds') - self._master_backend_helpers.create_relationship( - volume.name, tgt_vol, tgt_sys.get('system_name'), - True, True, source_change_vol, cycle_period_seconds) - self._aux_backend_helpers.change_relationship_changevolume( - tgt_vol, tgt_change_vol, False) - self._master_backend_helpers.start_relationship( - volume.name) - else: - self._master_backend_helpers.create_relationship( - volume.name, tgt_vol, tgt_sys.get('system_name'), - True if storwize_const.GLOBAL == rep_type else False) - except Exception as e: - msg = (_('Failed to extend a volume with remote copy ' - '%(volume)s. Exception: ' - '%(err)s.') % {'volume': volume.id, - 'err': e}) - LOG.error(msg) - raise exception.VolumeDriverException(message=msg) + 'remote copy or with "active-active" relationship is ' + 'not recommended.') + rep_type = rel_info['copy_type'] + cyclingmode = rel_info['cycling_mode'] + master_helper = self._master_backend_helpers + target_helper = self._aux_backend_helpers + if rep_type == 'activeactive': + hs_opts = self._get_vdisk_params(volume['volume_type_id'], + volume_metadata= + volume.get( + 'volume_matadata')) + try: + master_helper.convert_hyperswap_volume_to_normal( + volume_name, hs_opts['peer_pool']) + except Exception as e: + msg = (_('_extend_volume_op: Failed to convert hyperswap ' + 'volume to normal volume %(volume)s. Exception: ' + '%(err)s.') % {'volume': volume.id, 'err': e}) + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + + try: + master_helper.extend_vdisk(volume_name, extend_amt) + except Exception as e: + msg = (_('_extend_volume_op: Failed to extend a hyperswap ' + 'volume %(volume)s. Exception: ' + '%(err)s.') % {'volume': volume.id, 'err': e}) + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + finally: + try: + master_helper.convert_extended_volume_to_hyperswap( + volume_name, hs_opts, self._state) + except Exception as e: + msg = (_('_extend_volume_op: Failed to convert volume ' + 'to hyperswap volume %(volume)s. Exception: ' + '%(err)s.') % {'volume': volume.id, 'err': e}) + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + else: + try: + master_helper.delete_relationship(volume.name) + tgt_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX + + volume.name) + master_helper.extend_vdisk(volume.name, extend_amt) + target_helper.extend_vdisk(tgt_vol, extend_amt) + tgt_sys = target_helper.get_system_info() + if storwize_const.GMCV_MULTI == cyclingmode: + tgt_change_vol = ( + storwize_const.REPLICA_CHG_VOL_PREFIX + + tgt_vol) + source_change_vol = ( + storwize_const.REPLICA_CHG_VOL_PREFIX + + volume.name) + master_helper.extend_vdisk(source_change_vol, + extend_amt) + target_helper.extend_vdisk(tgt_change_vol, extend_amt) + src_change_opts = self._get_vdisk_params( + volume.volume_type_id) + cycle_period_seconds = src_change_opts.get( + 'cycle_period_seconds') + master_helper.create_relationship( + volume.name, tgt_vol, tgt_sys.get('system_name'), + True, True, source_change_vol, + cycle_period_seconds) + target_helper.change_relationship_changevolume( + tgt_vol, tgt_change_vol, False) + master_helper.start_relationship(volume.name) + else: + master_helper.create_relationship( + volume.name, tgt_vol, tgt_sys.get('system_name'), + True if cyclingmode == 'none' else False) + except Exception as e: + msg = (_('Failed to extend a volume with remote copy ' + '%(volume)s. Exception: ' + '%(err)s.') % {'volume': volume.id, 'err': e}) + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) else: self._helpers.extend_vdisk(volume_name, extend_amt) LOG.debug('leave: _extend_volume_op: volume %s', volume.id) diff --git a/releasenotes/notes/ibm-svf-support-hyperswap-volume-extend-f578efa02314faff.yaml b/releasenotes/notes/ibm-svf-support-hyperswap-volume-extend-f578efa02314faff.yaml new file mode 100644 index 00000000000..7acf8292f5a --- /dev/null +++ b/releasenotes/notes/ibm-svf-support-hyperswap-volume-extend-f578efa02314faff.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + IBM Spectrum Virtualize Family driver: Added volume-extend support for + volumes created using a HyperSwap volume-type template.