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 51c7d01343a..eaa30749e8e 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py @@ -6852,9 +6852,29 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): for each_vol in volumes_model_update: self.assertEqual('available', each_vol['status']) + # Delete the Group model_update = self.driver.delete_group(self.ctxt, group, volumes) self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) + + for each_vol in model_update[1]: + self.assertEqual('deleted', each_vol['status']) + + with (mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'create_rccg')) as create_rccg: + # Create cg from source cg + model_update, volumes_model_update = ( + self.driver.create_group_from_src(self.ctxt, group, volumes, + None, None, source_cg, + source_vols)) + create_rccg.assert_not_called() + + # Delete the Group + model_update = self.driver.delete_group(self.ctxt, group, volumes) + + self.assertEqual(fields.GroupStatus.DELETED, + model_update[0]['status']) + for each_vol in model_update[1]: self.assertEqual('deleted', each_vol['status']) @@ -6875,6 +6895,15 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): for each_vol in model_update[1]: self.assertEqual('deleted', each_vol['status']) + with (mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'create_rccg')) as create_rccg: + # Create cg from cg snapshot + model_update, volumes_model_update = ( + self.driver.create_group_from_src(self.ctxt, group, volumes, + group_snapshot, snapshots, + None, None)) + create_rccg.assert_not_called() + model_update = self.driver.delete_group_snapshot(self.ctxt, group_snapshot, snapshots) @@ -9420,6 +9449,43 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self._create_test_volume_types() self.rccg_type = self._create_consistent_rep_grp_type() + def _create_group_snapshot_in_db(self, group_id, **kwargs): + group_snapshot = testutils.create_group_snapshot(self.ctxt, + group_id=group_id, + **kwargs) + snapshots = [] + volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), group_id) + + if not volumes: + msg = _("Group is empty. No cgsnapshot will be created.") + raise exception.InvalidGroup(reason=msg) + + for volume in volumes: + snapshots.append(testutils.create_snapshot( + self.ctxt, volume['id'], + group_snapshot.id, + group_snapshot.name, + group_snapshot.id, + fields.SnapshotStatus.CREATING)) + return group_snapshot, snapshots + + def _create_group_snapshot(self, cg_id, **kwargs): + group_snapshot, snapshots = self._create_group_snapshot_in_db( + cg_id, **kwargs) + + model_update, snapshots_model = ( + self.driver.create_group_snapshot(self.ctxt, group_snapshot, + snapshots)) + self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE, + model_update['status'], + "CGSnapshot created failed") + + for snapshot in snapshots_model: + self.assertEqual(fields.SnapshotStatus.AVAILABLE, + snapshot['status']) + return group_snapshot, snapshots + def _set_flag(self, flag, value): group = self.driver.configuration.config_group self.driver.configuration.set_override(flag, value, group) @@ -12146,4 +12212,513 @@ class StorwizeSVCReplicationTestCase(test.TestCase): group_type_id=gr_type.id, volume_type_ids=[vol_type_ref['id']]) model_update = self.driver.create_group(self.ctxt, group) - self.assertEqual(fields.GroupStatus.ERROR, model_update['status']) + if vol_spec['replication_enabled'] == ' True': + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) + elif vol_spec['replication_enabled'] == ' False': + self.assertEqual(fields.GroupStatus.ERROR, model_update['status']) + + @ddt.data(({'volume_type': 'mm'}), + ({'volume_type': 'gm'}), + ({'volume_type': 'gmcv'}) + ) + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', + new=testutils.ZeroIntervalLoopingCall) + def test_create_group_from_src_grp(self, vol_spec): + self.driver.configuration.set_override('replication_device', + [self.rep_target]) + + pool = _get_test_pool() + + if vol_spec['volume_type'] == 'mm': + vol_type_id = self.mm_type.id + if vol_spec['volume_type'] == 'gm': + vol_type_id = self.gm_type.id + if vol_spec['volume_type'] == 'gmcv': + vol_type_id = self.gmcv_default_type.id + + # create group in db + src_group = testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id) + + model_update = self.driver.create_group(self.ctxt, src_group) + + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + # Create volumes in db + src_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + src_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + + self.driver.create_volume(src_vol1) + self.driver.create_volume(src_vol2) + src_volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), src_group.id) + + add_volumes = [src_vol1, src_vol2] + del_volumes = [] + + # add volumes to group + (model_update, add_volumes_update, + remove_volumes_update) = self.driver.update_group(self.ctxt, + src_group, + add_volumes, + del_volumes) + + # Clone group for source group + clone_group = ( + testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id)) + + # Create volumes in db + clone_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), clone_group.id) + + # Create group from source group + model_update, volumes_model_update = ( + self.driver.create_group_from_src(self.ctxt, clone_group, + clone_volumes, None, None, + src_group, + src_volumes)) + self.assertEqual('available', model_update['status']) + for each_vol in volumes_model_update: + self.assertEqual('available', each_vol['status']) + model_update = self.driver.delete_group(self.ctxt, clone_group, + [clone_vol1, clone_vol2]) + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) + + with (mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'create_rccg')) as create_rccg: + with ((mock.patch.object( + storwize_svc_common.StorwizeSVCCommonDriver, + 'update_group'))) as update_group: + # Create group from source group + model_update, volumes_model_update = ( + self.driver.create_group_from_src(self.ctxt, + clone_group, + clone_volumes, None, + None, src_group, + src_volumes)) + create_rccg.assert_called() + self.assertEqual(1, create_rccg.call_count) + update_group.assert_called() + self.assertEqual(1, update_group.call_count) + model_update = self.driver.delete_group( + self.ctxt, clone_group, [clone_vol1, clone_vol2]) + self.assertEqual(fields.GroupStatus.DELETED, + model_update[0]['status']) + model_update = self.driver.delete_group(self.ctxt, src_group, + [src_vol1, src_vol2]) + self.assertEqual(fields.GroupStatus.DELETED, + model_update[0]['status']) + + @ddt.data(({'volume_type': 'mm'}), + ({'volume_type': 'gm'}), + ({'volume_type': 'gmcv'}) + ) + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', + new=testutils.ZeroIntervalLoopingCall) + @mock.patch('cinder.volume.volume_utils.is_group_a_cg_snapshot_type') + def test_create_group_from_grp_snapshot(self, vol_spec, + is_group_a_cg_snap_type): + self.driver.configuration.set_override('replication_device', + [self.rep_target]) + is_group_a_cg_snap_type.return_value = False + + pool = _get_test_pool() + + if vol_spec['volume_type'] == 'mm': + vol_type_id = self.mm_type.id + if vol_spec['volume_type'] == 'gm': + vol_type_id = self.gm_type.id + if vol_spec['volume_type'] == 'gmcv': + vol_type_id = self.gmcv_default_type.id + + # create group in db + src_group = testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id) + + model_update = self.driver.create_group(self.ctxt, src_group) + + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + # Create volumes in db + src_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + src_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + + self.driver.create_volume(src_vol1) + self.driver.create_volume(src_vol2) + + add_volumes = [src_vol1, src_vol2] + del_volumes = [] + + # add volumes to group + (model_update, add_volumes_update, + remove_volumes_update) = self.driver.update_group(self.ctxt, + src_group, + add_volumes, + del_volumes) + + # Clone group for source group + clone_group = ( + testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id)) + + # Create volumes in db + clone_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), clone_group.id) + + # Create group snapshot + group_snapshot, snapshots = self._create_group_snapshot( + src_group.id, group_type_id=self.rccg_type.id) + + # Create group from source as group snapshot + model_update, volumes_model_update = ( + self.driver.create_group_from_src(self.ctxt, clone_group, + clone_volumes, + group_snapshot, + snapshots, None, None)) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status'], + "CG create from src created passed") + + for each_vol in volumes_model_update: + self.assertEqual('available', each_vol['status']) + + model_update = self.driver.delete_group(self.ctxt, clone_group, + [clone_vol1, clone_vol2]) + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) + + with (mock.patch.object(storwize_svc_common.StorwizeHelpers, + 'create_rccg')) as create_rccg: + with ((mock.patch.object( + storwize_svc_common.StorwizeSVCCommonDriver, + 'update_group'))) as update_group: + + # Create group from source as group snapshot + model_update, volumes_model_update = ( + self.driver.create_group_from_src(self.ctxt, + clone_group, + clone_volumes, + group_snapshot, + snapshots, None, + None)) + create_rccg.assert_called() + self.assertEqual(1, create_rccg.call_count) + update_group.assert_called() + self.assertEqual(1, update_group.call_count) + + model_update = ( + self.driver.delete_group(self.ctxt, clone_group, + [clone_vol1, clone_vol2])) + self.assertEqual(fields.GroupStatus.DELETED, + model_update[0]['status']) + is_group_a_cg_snap_type.return_value = True + model_update = ( + self.driver.delete_group_snapshot(self.ctxt, + group_snapshot, + snapshots)) + self.assertEqual(fields.GroupSnapshotStatus.DELETED, + model_update[0]['status']) + for volume in model_update[1]: + self.assertEqual(fields.SnapshotStatus.DELETED, + volume['status']) + + @ddt.data(({'volume_type': 'mm'}), + ({'volume_type': 'gm'}), + ({'volume_type': 'gmcv'}) + ) + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', + new=testutils.ZeroIntervalLoopingCall) + def test_create_group_from_src_grp_with_invalid_vol(self, vol_spec): + self.driver.configuration.set_override('replication_device', + [self.rep_target]) + + pool = _get_test_pool() + + if vol_spec['volume_type'] == 'mm': + vol_type_id = self.mm_type.id + if vol_spec['volume_type'] == 'gm': + vol_type_id = self.gm_type.id + if vol_spec['volume_type'] == 'gmcv': + vol_type_id = self.gmcv_default_type.id + + # create group in db + src_group = testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id) + + model_update = self.driver.create_group(self.ctxt, src_group) + + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + # Create volumes in db + src_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + src_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + + src_volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), src_group.id) + + add_volumes = [src_vol1, src_vol2] + del_volumes = [] + + # add volumes to group + (model_update, add_volumes_update, + remove_volumes_update) = self.driver.update_group(self.ctxt, + src_group, + add_volumes, + del_volumes) + + # Clone group for source group + clone_group = ( + testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id)) + + # Create volumes in db + clone_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), clone_group.id) + + self.assertRaises(exception.VolumeDriverException, + self.driver.create_group_from_src, self.ctxt, + clone_group, clone_volumes, None, None, + src_group, src_volumes) + + # Delete group + model_update = self.driver.delete_group(self.ctxt, clone_group, + [clone_vol1, clone_vol2]) + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) + + @ddt.data(({'volume_type': 'mm'}), + ({'volume_type': 'gm'}), + ({'volume_type': 'gmcv'}) + ) + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', + new=testutils.ZeroIntervalLoopingCall) + @mock.patch('cinder.volume.volume_utils.is_group_a_cg_snapshot_type') + def test_create_grp_from_grp_snapshot_invalid(self, vol_spec, + is_group_a_cg_snap_type): + self.driver.configuration.set_override('replication_device', + [self.rep_target]) + is_group_a_cg_snap_type.return_value = False + + pool = _get_test_pool() + + if vol_spec['volume_type'] == 'mm': + vol_type_id = self.mm_type.id + if vol_spec['volume_type'] == 'gm': + vol_type_id = self.gm_type.id + if vol_spec['volume_type'] == 'gmcv': + vol_type_id = self.gmcv_default_type.id + + # create group in db + src_group = testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id) + + model_update = self.driver.create_group(self.ctxt, src_group) + + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + # Create volumes in db + src_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + src_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + + add_volumes = [src_vol1, src_vol2] + del_volumes = [] + + # add volumes to group + (model_update, add_volumes_update, + remove_volumes_update) = self.driver.update_group(self.ctxt, + src_group, + add_volumes, + del_volumes) + + # Clone group for source group + clone_group = ( + testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id)) + + # Create volumes in db + clone_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + + # Exception raised while creating group snapshot + self.assertRaises(exception.VolumeDriverException, + self._create_group_snapshot, src_group.id, + group_type_id=self.rccg_type.id) + + # Delete group + model_update = self.driver.delete_group(self.ctxt, clone_group, + [clone_vol1, clone_vol2]) + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) + + @ddt.data(({'volume_type': 'mm'}), + ({'volume_type': 'gm'}), + ({'volume_type': 'gmcv'}) + ) + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', + new=testutils.ZeroIntervalLoopingCall) + @mock.patch('cinder.volume.volume_utils.is_group_a_cg_snapshot_type') + def test_create_grp_from_empty_grp_snapshot_inv(self, vol_spec, + is_group_a_cg_snap_type): + self.driver.configuration.set_override('replication_device', + [self.rep_target]) + is_group_a_cg_snap_type.return_value = False + + pool = _get_test_pool() + + if vol_spec['volume_type'] == 'mm': + vol_type_id = self.mm_type.id + if vol_spec['volume_type'] == 'gm': + vol_type_id = self.gm_type.id + if vol_spec['volume_type'] == 'gmcv': + vol_type_id = self.gmcv_default_type.id + + # create group in db + src_group = testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id) + + model_update = self.driver.create_group(self.ctxt, src_group) + + self.assertEqual(fields.ReplicationStatus.ENABLED, + model_update['replication_status']) + + # Create volumes in db + src_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + src_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=src_group.id, + host='openstack@svc#%s' % pool)) + + self.driver.create_volume(src_vol1) + self.driver.create_volume(src_vol2) + + add_volumes = [src_vol1, src_vol2] + del_volumes = [] + + # add volumes to group + (model_update, add_volumes_update, + remove_volumes_update) = self.driver.update_group(self.ctxt, + src_group, + add_volumes, + del_volumes) + + # Clone group for source group + clone_group = ( + testutils.create_group( + self.ctxt, volume_type_ids=[vol_type_id], + group_type_id=self.rccg_type.id)) + + # Create volumes in db + clone_vol1 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_vol2 = ( + testutils.create_volume(self.ctxt, + volume_type_id=vol_type_id, + group_id=clone_group.id, + host='openstack@svc#%s' % pool)) + clone_volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), clone_group.id) + + # Create group snapshot + group_snapshot, snapshots = self._create_group_snapshot_in_db( + src_group.id) + + # Create group from source as group snapshot + self.assertRaises(exception.VolumeDriverException, + self.driver.create_group_from_src, self.ctxt, + clone_group, clone_volumes, group_snapshot, + snapshots, None, None) + + # Delete group + model_update = self.driver.delete_group(self.ctxt, clone_group, + [clone_vol1, clone_vol2]) + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) 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 246a990cee8..9709ea4e835 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py @@ -5603,13 +5603,6 @@ class StorwizeSVCCommonDriver(san.SanDriver, 'types is not supported.') model_update = {'status': fields.GroupStatus.ERROR} return model_update - if rccg_type == storwize_const.GMCV: - LOG.error('Unable to create group: create consistent ' - 'replication group with GMCV replication ' - 'volume type is not supported.') - model_update = {'status': fields.GroupStatus.ERROR} - return model_update - rccg_name = self._get_rccg_name(group) try: tgt_sys = self._aux_backend_helpers.get_system_info() @@ -5796,6 +5789,21 @@ class StorwizeSVCCommonDriver(san.SanDriver, self._state, self.configuration, timeout)) + if volume_utils.is_group_a_type( + group, "consistent_group_replication_enabled"): + self._validate_replication_enabled() + rccg_name = self._get_rccg_name(group) + try: + tgt_sys = self._aux_backend_helpers.get_system_info() + self._helpers.create_rccg(rccg_name, + tgt_sys.get('system_name')) + model_update.update({'replication_status': + fields.ReplicationStatus.ENABLED}) + except exception.VolumeBackendAPIException as err: + LOG.error("Failed to create rccg %(rccg)s. " + "Exception: %(exception)s.", + {'rccg': rccg_name, 'exception': err}) + model_update = {'status': fields.GroupStatus.ERROR} for vol in volumes: rep_type = self._get_volume_replicated_type(context, @@ -5821,7 +5829,10 @@ class StorwizeSVCCommonDriver(san.SanDriver, volumes_model[volumes.index(vol)] = ( self._qos_model_update( volumes_model[volumes.index(vol)], vol)) - + if volume_utils.is_group_a_type( + group, "consistent_group_replication_enabled"): + self.update_group(context, group, add_volumes=volumes, + remove_volumes=[]) LOG.debug("Leave: create_group_from_src.") return model_update, volumes_model diff --git a/releasenotes/notes/bug-1920912-add_volumes_to_clone_group_fix-1cc9668ea077831e.yaml b/releasenotes/notes/bug-1920912-add_volumes_to_clone_group_fix-1cc9668ea077831e.yaml new file mode 100644 index 00000000000..7d9a3054de6 --- /dev/null +++ b/releasenotes/notes/bug-1920912-add_volumes_to_clone_group_fix-1cc9668ea077831e.yaml @@ -0,0 +1,6 @@ +fixes: + - | + IBM Spectrum Virtualize Family driver: `Bug #1920912 + `_: + Fixed rccg create issue while adding volumes to a group where + the group is cloned from group snapshot or other source group.