From 103870f40d8a65892dab1edc69413c3e16321edd Mon Sep 17 00:00:00 2001 From: Xiaoqin Li Date: Mon, 13 Mar 2017 18:00:11 +0800 Subject: [PATCH] Storwize: add CG capability to generic groups This patch adds consistency group capability to generic volume groups in Storwize driver. Re-use the CG implementations if it is a CG type group. And if the group being created isn't a CG then we bail out and the generic volume groups will take care of it. Implements: blueprint storwize-generic-group Change-Id: I64a7a29fd6620c3c8ea848c84bd564c9a632d307 --- .../volume/drivers/ibm/test_storwize_svc.py | 459 ++++++++++-------- .../ibm/storwize_svc/storwize_svc_common.py | 184 ++++--- .../ibm/storwize_svc/storwize_svc_fc.py | 3 +- .../ibm/storwize_svc/storwize_svc_iscsi.py | 3 +- ...generic-volume-group-74495fa23e059bf9.yaml | 4 + 5 files changed, 387 insertions(+), 266 deletions(-) create mode 100644 releasenotes/notes/storwize-generic-volume-group-74495fa23e059bf9.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 fba24e16b10..cffc14c1d13 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py @@ -48,6 +48,7 @@ from cinder.volume.drivers.ibm.storwize_svc import storwize_const from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_common from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_fc from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_iscsi +from cinder.volume import group_types from cinder.volume import qos_specs from cinder.volume import utils as volume_utils from cinder.volume import volume_types @@ -3685,54 +3686,55 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver.delete_volume(volume) self.db.volume_destroy(self.ctxt, volume['id']) - def _create_consistencygroup_in_db(self, **kwargs): - cg = testutils.create_consistencygroup(self.ctxt, **kwargs) + def _create_group_in_db(self, **kwargs): + cg = testutils.create_group(self.ctxt, **kwargs) return cg - def _create_consistencegroup(self, **kwargs): - cg = self._create_consistencygroup_in_db(**kwargs) + def _create_group(self, **kwargs): + grp = self._create_group_in_db(**kwargs) - model_update = self.driver.create_consistencygroup(self.ctxt, cg) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, + model_update = self.driver.create_group(self.ctxt, grp) + self.assertEqual(fields.GroupStatus.AVAILABLE, model_update['status'], "CG created failed") - return cg + return grp - def _create_cgsnapshot_in_db(self, cg_id, **kwargs): - cg_snapshot = testutils.create_cgsnapshot(self.ctxt, - consistencygroup_id= cg_id, - **kwargs) + def _create_group_snapshot_in_db(self, group_id, **kwargs): + group_snapshot = testutils.create_group_snapshot(self.ctxt, + group_id=group_id, + **kwargs) snapshots = [] - cg_id = cg_snapshot['consistencygroup_id'] - volumes = self.db.volume_get_all_by_group(self.ctxt.elevated(), cg_id) + volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), group_id) if not volumes: - msg = _("Consistency group is empty. No cgsnapshot " - "will be created.") - raise exception.InvalidConsistencyGroup(reason=msg) + 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'], - cg_snapshot.id, - cg_snapshot.name, - cg_snapshot.id, + group_snapshot.id, + group_snapshot.name, + group_snapshot.id, fields.SnapshotStatus.CREATING)) - return cg_snapshot, snapshots + return group_snapshot, snapshots - def _create_cgsnapshot(self, cg_id, **kwargs): - cg_snapshot, snapshots = self._create_cgsnapshot_in_db(cg_id, **kwargs) + 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_cgsnapshot(self.ctxt, cg_snapshot, snapshots)) - self.assertEqual('available', + 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 cg_snapshot, snapshots + return group_snapshot, snapshots def _create_test_vol(self, opts): ctxt = testutils.get_test_admin_context() @@ -4065,8 +4067,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): volume) # Try to delete a volume that doesn't exist (should not fail) - vol_no_exist = {'name': 'i_dont_exist', - 'id': '111111'} + vol_no_exist = self._generate_vol_info(None, None) self.driver.delete_volume(vol_no_exist) # Ensure export for volume that doesn't exist (should not fail) self.driver.ensure_export(None, vol_no_exist) @@ -4075,15 +4076,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): self.driver.delete_volume(volume) def test_storwize_svc_volume_name(self): - # Create a volume with space in name - pool = _get_test_pool() - rand_id = six.text_type(random.randint(10000, 99999)) - volume = {'name': 'volume_ space', - 'size': 10, - 'id': '%s' % rand_id, - 'volume_type_id': None, - 'mdisk_grp_name': pool, - 'host': 'openstack@svc#%s' % pool} + volume = self._generate_vol_info(None, None) self.driver.create_volume(volume) self.driver.ensure_export(None, volume) @@ -4295,6 +4288,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): each_pool['thin_provisioning_support']) self.assertEqual(not is_thin_provisioning_enabled, each_pool['thick_provisioning_support']) + self.assertTrue(each_pool['consistent_group_snapshot_enabled']) if self.USESIM: expected = 'storwize-svc-sim' self.assertEqual(expected, stats['volume_backend_name']) @@ -4951,232 +4945,300 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase): attrs = self.driver._helpers.get_vdisk_attributes(vol2['name']) self.assertIn('openstack2', attrs['mdisk_grp_name']) + # Test groups operation #### + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_storwize_group_create_with_replication( + self, is_grp_a_cg_snapshot_type): + """Test group create.""" + is_grp_a_cg_snapshot_type.side_effect = True + spec = {'replication_enabled': ' True', + 'replication_type': ' metro'} + rep_type_ref = volume_types.create(self.ctxt, 'rep_type', spec) + rep_group = testutils.create_group( + self.ctxt, group_type_id=fake.GROUP_TYPE_ID, + volume_type_ids=[rep_type_ref['id']]) + + model_update = self.driver.create_group(self.ctxt, rep_group) + self.assertEqual(fields.GroupStatus.ERROR, + model_update['status']) + + self.assertFalse(is_grp_a_cg_snapshot_type.called) + + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_storwize_group_create(self, is_grp_a_cg_snapshot_type): + """Test group create.""" + is_grp_a_cg_snapshot_type.side_effect = [False, True] + group = mock.MagicMock() + + self.assertRaises(NotImplementedError, + self.driver.create_group, self.ctxt, group) + + model_update = self.driver.create_group(self.ctxt, group) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=testutils.ZeroIntervalLoopingCall) - def test_storwize_consistency_group_snapshot(self): - cg_type = self._create_consistency_group_volume_type() - self.ctxt.user_id = fake.USER_ID - self.ctxt.project_id = fake.PROJECT_ID - cg = self._create_consistencygroup_in_db(volume_type_id=cg_type['id']) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_storwize_delete_group(self, is_grp_a_cg_snapshot_type): + is_grp_a_cg_snapshot_type.side_effect = [False, True] + type_ref = volume_types.create(self.ctxt, 'testtype', None) + group = testutils.create_group(self.ctxt, + group_type_id=fake.GROUP_TYPE_ID, + volume_type_id=type_ref['id']) - model_update = self.driver.create_consistencygroup(self.ctxt, cg) + self._create_volume(volume_type_id=type_ref['id'], group_id=group.id) + self._create_volume(volume_type_id=type_ref['id'], group_id=group.id) + volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), group.id) + self.assertRaises(NotImplementedError, + self.driver.delete_group, + self.ctxt, group, volumes) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, - model_update['status'], - "CG created failed") - - volumes = [ - self._create_volume(volume_type_id=cg_type['id'], - consistencygroup_id=cg['id']), - self._create_volume(volume_type_id=cg_type['id'], - consistencygroup_id=cg['id']), - self._create_volume(volume_type_id=cg_type['id'], - consistencygroup_id=cg['id']) - ] - - cg_snapshot, snapshots = self._create_cgsnapshot_in_db(cg['id']) - - snapshots = objects.SnapshotList.get_all_for_cgsnapshot( - self.ctxt, cg_snapshot.id) - model_update = self.driver.create_cgsnapshot(self.ctxt, cg_snapshot, - snapshots) - self.assertEqual('available', - model_update[0]['status'], - "CGSnapshot created failed") - - for snapshot in model_update[1]: - self.assertEqual(fields.SnapshotStatus.AVAILABLE, - snapshot['status']) - - model_update = self.driver.delete_consistencygroup(self.ctxt, - cg, volumes) - - self.assertEqual(fields.ConsistencyGroupStatus.DELETED, + model_update = self.driver.delete_group(self.ctxt, group, volumes) + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) for volume in model_update[1]: self.assertEqual('deleted', volume['status']) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_storwize_group_update(self, is_grp_a_cg_snapshot_type): + """Test group update.""" + is_grp_a_cg_snapshot_type.side_effect = [False, True] + group = mock.MagicMock() + self.assertRaises(NotImplementedError, self.driver.update_group, + self.ctxt, group, None, None) + + (model_update, add_volumes_update, + remove_volumes_update) = self.driver.update_group(self.ctxt, group) + self.assertIsNone(model_update) + self.assertIsNone(add_volumes_update) + self.assertIsNone(remove_volumes_update) + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=testutils.ZeroIntervalLoopingCall) - def test_storwize_consistency_group_from_src_invalid(self): - # Invalid input case for create cg from src - cg_type = self._create_consistency_group_volume_type() - self.ctxt.user_id = fake.USER_ID - self.ctxt.project_id = fake.PROJECT_ID - # create cg in db - cg = self._create_consistencygroup_in_db(volume_type_id=cg_type['id']) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_storwize_create_group_snapshot(self, is_grp_a_cg_snapshot_type): + is_grp_a_cg_snapshot_type.side_effect = [False, True] + type_ref = volume_types.create(self.ctxt, 'testtype', None) + group = testutils.create_group(self.ctxt, + group_type_id=fake.GROUP_TYPE_ID, + volume_type_id=type_ref['id']) + + self._create_volume(volume_type_id=type_ref['id'], group_id=group.id) + self._create_volume(volume_type_id=type_ref['id'], group_id=group.id) + group_snapshot, snapshots = self._create_group_snapshot_in_db( + group.id) + self.assertRaises(NotImplementedError, + self.driver.create_group_snapshot, + self.ctxt, group_snapshot, snapshots) + + (model_update, + snapshots_model_update) = 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_update: + self.assertEqual(fields.SnapshotStatus.AVAILABLE, + snapshot['status']) + + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', + new=testutils.ZeroIntervalLoopingCall) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_storwize_delete_group_snapshot(self, is_grp_a_cg_snapshot_type): + is_grp_a_cg_snapshot_type.side_effect = [True, False, True] + type_ref = volume_types.create(self.ctxt, 'testtype', None) + group = testutils.create_group(self.ctxt, + group_type_id=fake.GROUP_TYPE_ID, + volume_type_id=type_ref['id']) + + self._create_volume(volume_type_id=type_ref['id'], group_id=group.id) + self._create_volume(volume_type_id=type_ref['id'], group_id=group.id) + + group_snapshot, snapshots = self._create_group_snapshot(group.id) + self.assertRaises(NotImplementedError, + self.driver.delete_group_snapshot, + self.ctxt, group_snapshot, snapshots) + + 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']) + + @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', + new=testutils.ZeroIntervalLoopingCall) + def test_storwize_create_group_from_src_invalid(self): + # Invalid input case for create group from src + type_ref = volume_types.create(self.ctxt, 'testtype', None) + spec = {'consistent_group_snapshot_enabled': ' True'} + cg_type_ref = group_types.create(self.ctxt, 'cg_type', spec) + vg_type_ref = group_types.create(self.ctxt, 'vg_type', None) + + # create group in db + group = self._create_group_in_db(volume_type_id=type_ref.id, + group_type_id=vg_type_ref.id) + self.assertRaises(NotImplementedError, + self.driver.create_group_from_src, + self.ctxt, group, None, None, None, + None, None) + + group = self._create_group_in_db(volume_type_id=type_ref.id, + group_type_id=cg_type_ref.id) # create volumes in db - vol1 = testutils.create_volume(self.ctxt, volume_type_id=cg_type['id'], - consistencygroup_id=cg['id']) - vol2 = testutils.create_volume(self.ctxt, volume_type_id=cg_type['id'], - consistencygroup_id=cg['id']) + vol1 = testutils.create_volume(self.ctxt, volume_type_id=type_ref.id, + group_id=group.id) + vol2 = testutils.create_volume(self.ctxt, volume_type_id=type_ref.id, + group_id=group.id) volumes = [vol1, vol2] - source_cg = self._create_consistencegroup(volume_type_id=cg_type['id']) + source_cg = self._create_group_in_db(volume_type_id=type_ref.id, + group_type_id=cg_type_ref.id) # Add volumes to source CG - src_vol1 = self._create_volume(volume_type_id=cg_type['id'], - consistencygroup_id=source_cg['id']) - src_vol2 = self._create_volume(volume_type_id=cg_type['id'], - consistencygroup_id=source_cg['id']) + src_vol1 = self._create_volume(volume_type_id=type_ref.id, + group_id=source_cg['id']) + src_vol2 = self._create_volume(volume_type_id=type_ref.id, + group_id=source_cg['id']) source_vols = [src_vol1, src_vol2] - cgsnapshot, snapshots = self._create_cgsnapshot(source_cg['id']) + group_snapshot, snapshots = self._create_group_snapshot( + source_cg['id'], group_type_id=cg_type_ref.id) - # Create cg from src with null input + # Create group from src with null input self.assertRaises(exception.InvalidInput, - self.driver.create_consistencygroup_from_src, - self.ctxt, cg, volumes, None, None, + self.driver.create_group_from_src, + self.ctxt, group, volumes, None, None, None, None) # Create cg from src with source_cg and empty source_vols self.assertRaises(exception.InvalidInput, - self.driver.create_consistencygroup_from_src, - self.ctxt, cg, volumes, None, None, + self.driver.create_group_from_src, + self.ctxt, group, volumes, None, None, source_cg, None) # Create cg from src with source_vols and empty source_cg self.assertRaises(exception.InvalidInput, - self.driver.create_consistencygroup_from_src, - self.ctxt, cg, volumes, None, None, + self.driver.create_group_from_src, + self.ctxt, group, volumes, None, None, None, source_vols) # Create cg from src with cgsnapshot and empty snapshots self.assertRaises(exception.InvalidInput, - self.driver.create_consistencygroup_from_src, - self.ctxt, cg, volumes, cgsnapshot, None, + self.driver.create_group_from_src, + self.ctxt, group, volumes, group_snapshot, None, None, None) # Create cg from src with snapshots and empty cgsnapshot self.assertRaises(exception.InvalidInput, - self.driver.create_consistencygroup_from_src, - self.ctxt, cg, volumes, None, snapshots, + self.driver.create_group_from_src, + self.ctxt, group, volumes, None, snapshots, None, None) - model_update = self.driver.delete_consistencygroup(self.ctxt, - cg, volumes) + model_update = self.driver.delete_group(self.ctxt, group, volumes) - self.assertEqual(fields.ConsistencyGroupStatus.DELETED, + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) for volume in model_update[1]: self.assertEqual('deleted', volume['status']) - model_update = ( - self.driver.delete_consistencygroup(self.ctxt, - source_cg, source_vols)) + model_update = self.driver.delete_group(self.ctxt, + source_cg, source_vols) - self.assertEqual(fields.ConsistencyGroupStatus.DELETED, + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) for volume in model_update[1]: self.assertEqual('deleted', volume['status']) - model_update = ( - self.driver.delete_consistencygroup(self.ctxt, - cgsnapshot, snapshots)) + model_update = self.driver.delete_group(self.ctxt, + group_snapshot, snapshots) - self.assertEqual(fields.ConsistencyGroupStatus.DELETED, + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) for volume in model_update[1]: self.assertEqual('deleted', volume['status']) @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=testutils.ZeroIntervalLoopingCall) - def test_storwize_consistency_group_from_src(self): + def test_storwize_group_from_src(self): # Valid case for create cg from src - cg_type = self._create_consistency_group_volume_type() - self.ctxt.user_id = fake.USER_ID - self.ctxt.project_id = fake.PROJECT_ID + type_ref = volume_types.create(self.ctxt, 'testtype', None) + spec = {'consistent_group_snapshot_enabled': ' True'} + cg_type_ref = group_types.create(self.ctxt, 'cg_type', spec) pool = _get_test_pool() # Create cg in db - cg = self._create_consistencygroup_in_db(volume_type_id=cg_type['id']) + group = self._create_group_in_db(volume_type_id=type_ref.id, + group_type_id=cg_type_ref.id) # Create volumes in db - testutils.create_volume(self.ctxt, volume_type_id=cg_type['id'], - consistencygroup_id=cg['id'], + testutils.create_volume(self.ctxt, volume_type_id=type_ref.id, + group_id=group.id, host='openstack@svc#%s' % pool) - testutils.create_volume(self.ctxt, volume_type_id=cg_type['id'], - consistencygroup_id=cg['id'], + testutils.create_volume(self.ctxt, volume_type_id=type_ref.id, + consistencygroup_id=group.id, host='openstack@svc#%s' % pool) - volumes = ( - self.db.volume_get_all_by_group(self.ctxt.elevated(), cg['id'])) + volumes = self.db.volume_get_all_by_generic_group( + self.ctxt.elevated(), group.id) # Create source CG - source_cg = self._create_consistencegroup(volume_type_id=cg_type['id']) + source_cg = self._create_group_in_db(volume_type_id=type_ref.id, + group_type_id=cg_type_ref.id) # Add volumes to source CG - self._create_volume(volume_type_id=cg_type['id'], - consistencygroup_id=source_cg['id']) - self._create_volume(volume_type_id=cg_type['id'], - consistencygroup_id=source_cg['id']) - source_vols = self.db.volume_get_all_by_group( + self._create_volume(volume_type_id=type_ref.id, + group_id=source_cg['id']) + self._create_volume(volume_type_id=type_ref.id, + group_id=source_cg['id']) + source_vols = self.db.volume_get_all_by_generic_group( self.ctxt.elevated(), source_cg['id']) # Create cgsnapshot - cgsnapshot, snapshots = self._create_cgsnapshot(source_cg['id']) + group_snapshot, snapshots = self._create_group_snapshot( + source_cg['id'], group_type_id=cg_type_ref.id) # Create cg from source cg - model_update, volumes_model_update = ( - self.driver.create_consistencygroup_from_src(self.ctxt, - cg, - volumes, - None, None, - source_cg, - source_vols)) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, + self.driver.create_group_from_src(self.ctxt, group, volumes, None, + None, source_cg, source_vols)) + self.assertEqual(fields.GroupStatus.AVAILABLE, model_update['status'], "CG create from src created failed") - for each_vol in volumes_model_update: self.assertEqual('available', each_vol['status']) - model_update = self.driver.delete_consistencygroup(self.ctxt, - cg, - volumes) - self.assertEqual(fields.ConsistencyGroupStatus.DELETED, + 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']) # Create cg from cg snapshot model_update, volumes_model_update = ( - self.driver.create_consistencygroup_from_src(self.ctxt, - cg, - volumes, - cgsnapshot, - snapshots, - None, None)) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, + self.driver.create_group_from_src(self.ctxt, group, volumes, + group_snapshot, snapshots, + None, None)) + self.assertEqual(fields.GroupStatus.AVAILABLE, model_update['status'], "CG create from src created failed") - for each_vol in volumes_model_update: self.assertEqual('available', each_vol['status']) - model_update = self.driver.delete_consistencygroup(self.ctxt, - cg, volumes) - - self.assertEqual(fields.ConsistencyGroupStatus.DELETED, + 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']) - model_update = self.driver.delete_consistencygroup(self.ctxt, - cgsnapshot, - snapshots) - - self.assertEqual(fields.ConsistencyGroupStatus.DELETED, + model_update = self.driver.delete_group_snapshot(self.ctxt, + group_snapshot, + snapshots) + self.assertEqual(fields.GroupStatus.DELETED, model_update[0]['status']) for volume in model_update[1]: self.assertEqual('deleted', volume['status']) - model_update = self.driver.delete_consistencygroup(self.ctxt, - source_cg, - source_vols) - - self.assertEqual(fields.ConsistencyGroupStatus.DELETED, - model_update[0]['status']) - for each_vol in model_update[1]: - self.assertEqual('deleted', each_vol['status']) - def _create_volume_type_qos(self, extra_specs, fake_qos): # Generate a QoS volume type for volume. if extra_specs: @@ -5871,27 +5933,20 @@ class StorwizeSVCReplicationTestCase(test.TestCase): is_vol_defined = self.driver._helpers.is_vdisk_defined(name) self.assertEqual(exists, is_vol_defined) - def _generate_vol_info(self, vol_name, vol_id, vol_type=None): + def _generate_vol_info(self, vol_type=None, size=1): pool = _get_test_pool() - rand_id = six.text_type(random.randint(10000, 99999)) - volume_type = self.non_replica_type - if vol_type: - volume_type = vol_type - if vol_name: - return {'name': 'snap_volume%s' % rand_id, - 'volume_name': vol_name, - 'id': rand_id, - 'volume_id': vol_id, - 'volume_size': 10, - 'mdisk_grp_name': pool} - else: - return {'name': 'test_volume%s' % rand_id, - 'size': 10, - 'id': '%s' % rand_id, - 'mdisk_grp_name': pool, - 'host': 'openstack@svc#%s' % pool, - 'volume_type_id': volume_type['id'], - 'volume_type': volume_type} + volume_type = vol_type if vol_type else self.non_replica_type + prop = {'size': size, + 'volume_type_id': volume_type.id, + 'host': 'openstack@svc#%s' % pool + } + vol = testutils.create_volume(self.ctxt, **prop) + return vol + + def _generate_snap_info(self, vol_id): + prop = {'volume_id': vol_id} + snap = testutils.create_snapshot(self.ctxt, **prop) + return snap def _create_replica_volume_type(self, enable, rep_type=storwize_const.METRO): @@ -5910,8 +5965,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): type_name = "non_rep" type_ref = volume_types.create(self.ctxt, type_name, spec) - - replication_type = volume_types.get_volume_type(self.ctxt, + replication_type = objects.VolumeType.get_by_id(self.ctxt, type_ref['id']) return replication_type @@ -5923,14 +5977,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.non_replica_type = self._create_replica_volume_type(False) def _create_test_volume(self, rep_type): - volume = self._generate_vol_info(None, None, rep_type) - opts = {} - for p in volume.keys(): - if p not in volume: - opts[p] = volume[p] - vol = testutils.create_volume(self.ctxt, **opts) - volume['id'] = vol['id'] - volume['name'] = vol['name'] + volume = self._generate_vol_info(rep_type) model_update = self.driver.create_volume(volume) volume['status'] = 'available' return volume, model_update @@ -6054,10 +6101,10 @@ class StorwizeSVCReplicationTestCase(test.TestCase): vol1, model_update = self._create_test_volume(self.mm_type) self.assertEqual('enabled', model_update['replication_status']) - snap = self._generate_vol_info(vol1['name'], vol1['id']) + snap = testutils.create_snapshot(self.ctxt, vol1.id) self.driver.create_snapshot(snap) - vol2 = self._generate_vol_info(None, None, self.mm_type) + vol2 = self._generate_vol_info(self.mm_type) model_update = self.driver.create_volume_from_snapshot(vol2, snap) self.assertEqual('enabled', model_update['replication_status']) self._validate_replic_vol_creation(vol2) @@ -6076,7 +6123,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): src_volume, model_update = self._create_test_volume(self.mm_type) self.assertEqual('enabled', model_update['replication_status']) - volume = self._generate_vol_info(None, None, self.mm_type) + volume = self._generate_vol_info(self.mm_type) # Create a cloned volume from source volume. model_update = self.driver.create_cloned_volume(volume, src_volume) @@ -6177,7 +6224,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): non_rep_volume, model_update = self._create_test_volume( self.non_replica_type) - new_volume = self._generate_vol_info(None, None) + new_volume = self._generate_vol_info() ref = {'source-name': rep_volume['name']} new_volume['volume_type_id'] = self.non_replica_type['id'] @@ -6213,7 +6260,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): uid_of_aux = self._get_vdisk_uid( storwize_const.REPLICA_AUX_VOL_PREFIX + rep_volume['name']) - new_volume = self._generate_vol_info(None, None) + new_volume = self._generate_vol_info() ref = {'source-name': rep_volume['name']} new_volume['volume_type_id'] = self.mm_type['id'] new_volume['volume_type'] = self.mm_type @@ -6692,7 +6739,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): 'start_relationship') def test_sync_replica_volumes_with_aux(self, start_relationship): # Create metro mirror replication. - mm_vol = self._generate_vol_info(None, None, self.mm_type) + mm_vol = self._generate_vol_info(self.mm_type) tgt_volume = storwize_const.REPLICA_AUX_VOL_PREFIX + mm_vol['name'] volumes = [mm_vol] @@ -6741,7 +6788,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): new=testutils.ZeroIntervalLoopingCall) def test_wait_replica_vol_ready(self, get_relationship_info): # Create metro mirror replication. - mm_vol = self._generate_vol_info(None, None, self.mm_type) + mm_vol = self._generate_vol_info(self.mm_type) fake_info = {'volume': 'fake', 'master_vdisk_name': 'fake', 'aux_vdisk_name': 'fake', 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 9573d1944ac..2da247b1414 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py @@ -1343,7 +1343,7 @@ class StorwizeHelpers(object): def run_consistgrp_snapshots(self, fc_consistgrp, snapshots, state, config, timeout): - model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} + model_update = {'status': fields.GroupSnapshotStatus.AVAILABLE} snapshots_model_update = [] try: for snapshot in snapshots: @@ -1362,7 +1362,7 @@ class StorwizeHelpers(object): # Cinder general will maintain the CG and snapshots relationship. self.delete_fc_consistgrp(fc_consistgrp) except exception.VolumeBackendAPIException as err: - model_update['status'] = fields.ConsistencyGroupStatus.ERROR + model_update['status'] = fields.GroupSnapshotStatus.ERROR # Release cg self.delete_fc_consistgrp(fc_consistgrp) LOG.error("Failed to create CGSnapshot. " @@ -1377,7 +1377,7 @@ class StorwizeHelpers(object): def delete_consistgrp_snapshots(self, fc_consistgrp, snapshots): """Delete flashcopy maps and consistent group.""" - model_update = {'status': fields.ConsistencyGroupStatus.DELETED} + model_update = {'status': fields.GroupSnapshotStatus.DELETED} snapshots_model_update = [] try: @@ -1385,7 +1385,7 @@ class StorwizeHelpers(object): self.ssh.rmvdisk(snapshot['name'], True) except exception.VolumeBackendAPIException as err: model_update['status'] = ( - fields.ConsistencyGroupStatus.ERROR_DELETING) + fields.GroupSnapshotStatus.ERROR_DELETING) LOG.error("Failed to delete the snapshot %(snap)s of " "CGSnapshot. Exception: %(exception)s.", {'snap': snapshot['name'], 'exception': err}) @@ -1429,7 +1429,7 @@ class StorwizeHelpers(object): LOG.debug('Enter: create_cg_from_source: cg %(cg)s' ' source %(source)s, target %(target)s', {'cg': fc_consistgrp, 'source': sources, 'target': targets}) - model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} + model_update = {'status': fields.GroupStatus.AVAILABLE} ctxt = context.get_admin_context() try: for source, target in zip(sources, targets): @@ -1447,7 +1447,7 @@ class StorwizeHelpers(object): volumes_model_update = self._get_volume_model_updates( ctxt, targets, group['id'], model_update['status']) except exception.VolumeBackendAPIException as err: - model_update['status'] = fields.ConsistencyGroupStatus.ERROR + model_update['status'] = fields.GroupStatus.ERROR volumes_model_update = self._get_volume_model_updates( ctxt, targets, group['id'], model_update['status']) with excutils.save_and_reraise_exception(): @@ -3065,13 +3065,13 @@ class StorwizeSVCCommonDriver(san.SanDriver, raise exception.InvalidInput(reason=msg) return replication_type - def _get_volume_replicated_type(self, ctxt, volume): + def _get_volume_replicated_type(self, ctxt, volume, vol_type_id=None): replication_type = None - if volume.get("volume_type_id"): - volume_type = volume_types.get_volume_type( - ctxt, volume["volume_type_id"]) + volume_type = (volume.volume_type if volume else + objects.VolumeType.get_by_name_or_id(ctxt, + vol_type_id)) + if volume_type: replication_type = self._get_specs_replicated_type(volume_type) - return replication_type def _get_storwize_config(self): @@ -3479,23 +3479,49 @@ class StorwizeSVCCommonDriver(san.SanDriver, return self._stats - def create_consistencygroup(self, context, group): - """Create a consistency group. + # Add CG capability to generic volume groups + def create_group(self, context, group): + """Creates a group. - IBM Storwize will create CG until cg-snapshot creation, - db will maintain the volumes and CG relationship. + :param context: the context of the caller. + :param group: the group object. + :returns: model_update """ - LOG.debug("Creating consistency group.") - model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} - return model_update - def delete_consistencygroup(self, context, group, volumes): - """Deletes a consistency group. + LOG.debug("Creating group.") + model_update = {'status': fields.GroupStatus.AVAILABLE} - IBM Storwize will delete the volumes of the CG. + for vol_type_id in group.volume_type_ids: + replication_type = self._get_volume_replicated_type( + context, None, vol_type_id) + if replication_type: + # An unsupported configuration + LOG.error('Unable to create group: create group with ' + 'replication volume type is not supported.') + model_update = {'status': fields.GroupStatus.ERROR} + return model_update + + if utils.is_group_a_cg_snapshot_type(group): + return {'status': fields.GroupStatus.AVAILABLE} + # we'll rely on the generic group implementation if it is not a + # consistency group request. + raise NotImplementedError() + + def delete_group(self, context, group, volumes): + """Deletes a group. + + :param context: the context of the caller. + :param group: the group object. + :param volumes: a list of volume objects in the group. + :returns: model_update, volumes_model_update """ - LOG.debug("Deleting consistency group.") - model_update = {'status': fields.ConsistencyGroupStatus.DELETED} + LOG.debug("Deleting group.") + if not utils.is_group_a_cg_snapshot_type(group): + # we'll rely on the generic group implementation if it is + # not a consistency group request. + raise NotImplementedError() + + model_update = {'status': fields.GroupStatus.DELETED} volumes_model_update = [] for volume in volumes: @@ -3505,51 +3531,69 @@ class StorwizeSVCCommonDriver(san.SanDriver, {'id': volume['id'], 'status': 'deleted'}) except exception.VolumeBackendAPIException as err: model_update['status'] = ( - fields.ConsistencyGroupStatus.ERROR_DELETING) + fields.GroupStatus.ERROR_DELETING) LOG.error("Failed to delete the volume %(vol)s of CG. " "Exception: %(exception)s.", {'vol': volume['name'], 'exception': err}) volumes_model_update.append( - {'id': volume['id'], 'status': 'error_deleting'}) + {'id': volume['id'], + 'status': fields.GroupStatus.ERROR_DELETING}) return model_update, volumes_model_update - def update_consistencygroup(self, ctxt, group, add_volumes, - remove_volumes): - """Adds or removes volume(s) to/from an existing consistency group.""" - - LOG.debug("Updating consistency group.") - return None, None, None - - def create_consistencygroup_from_src(self, context, group, volumes, - cgsnapshot=None, snapshots=None, - source_cg=None, source_vols=None): - """Creates a consistencygroup from source. + def update_group(self, context, group, add_volumes=None, + remove_volumes=None): + """Updates a group. :param context: the context of the caller. - :param group: the dictionary of the consistency group to be created. - :param volumes: a list of volume dictionaries in the group. - :param cgsnapshot: the dictionary of the cgsnapshot as source. - :param snapshots: a list of snapshot dictionaries in the cgsnapshot. - :param source_cg: the dictionary of a consistency group as source. - :param source_vols: a list of volume dictionaries in the source_cg. + :param group: the group object. + :param add_volumes: a list of volume objects to be added. + :param remove_volumes: a list of volume objects to be removed. + :returns: model_update, add_volumes_update, remove_volumes_update + """ + + LOG.debug("Updating group.") + if utils.is_group_a_cg_snapshot_type(group): + return None, None, None + + # we'll rely on the generic group implementation if it is not a + # consistency group request. + raise NotImplementedError() + + def create_group_from_src(self, context, group, volumes, + group_snapshot=None, snapshots=None, + source_group=None, source_vols=None): + """Creates a group from source. + + :param context: the context of the caller. + :param group: the Group object to be created. + :param volumes: a list of Volume objects in the group. + :param group_snapshot: the GroupSnapshot object as source. + :param snapshots: a list of snapshot objects in group_snapshot. + :param source_group: the Group object as source. + :param source_vols: a list of volume objects in the source_group. :returns: model_update, volumes_model_update """ - LOG.debug('Enter: create_consistencygroup_from_src.') - if cgsnapshot and snapshots: - cg_name = 'cg-' + cgsnapshot.id + LOG.debug('Enter: create_group_from_src.') + if not utils.is_group_a_cg_snapshot_type(group): + # we'll rely on the generic volume groups implementation if it is + # not a consistency group request. + raise NotImplementedError() + + if group_snapshot and snapshots: + cg_name = 'cg-' + group_snapshot.id sources = snapshots - elif source_cg and source_vols: - cg_name = 'cg-' + source_cg.id + elif source_group and source_vols: + cg_name = 'cg-' + source_group.id sources = source_vols else: - error_msg = _("create_consistencygroup_from_src must be " - "creating from a CG snapshot, or a source CG.") + error_msg = _("create_group_from_src must be creating from a " + "group snapshot, or a source group.") raise exception.InvalidInput(reason=error_msg) - LOG.debug('create_consistencygroup_from_src: cg_name %(cg_name)s' + LOG.debug('create_group_from_src: cg_name %(cg_name)s' ' %(sources)s', {'cg_name': cg_name, 'sources': sources}) self._helpers.create_fc_consistgrp(cg_name) timeout = self.configuration.storwize_svc_flashcopy_timeout @@ -3561,13 +3605,24 @@ class StorwizeSVCCommonDriver(san.SanDriver, self._state, self.configuration, timeout)) - LOG.debug("Leave: create_consistencygroup_from_src.") + LOG.debug("Leave: create_group_from_src.") return model_update, snapshots_model - def create_cgsnapshot(self, ctxt, cgsnapshot, snapshots): - """Creates a cgsnapshot.""" - # Use cgsnapshot id as cg name - cg_name = 'cg_snap-' + cgsnapshot.id + def create_group_snapshot(self, context, group_snapshot, snapshots): + """Creates a group_snapshot. + + :param context: the context of the caller. + :param group_snapshot: the GroupSnapshot object to be created. + :param snapshots: a list of Snapshot objects in the group_snapshot. + :returns: model_update, snapshots_model_update + """ + if not utils.is_group_a_cg_snapshot_type(group_snapshot): + # we'll rely on the generic group implementation if it is not a + # consistency group 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) @@ -3582,9 +3637,21 @@ class StorwizeSVCCommonDriver(san.SanDriver, return model_update, snapshots_model - def delete_cgsnapshot(self, context, cgsnapshot, snapshots): - """Deletes a cgsnapshot.""" - cgsnapshot_id = cgsnapshot['id'] + def delete_group_snapshot(self, context, group_snapshot, snapshots): + """Deletes a group_snapshot. + + :param context: the context of the caller. + :param group_snapshot: the GroupSnapshot object to be deleted. + :param snapshots: a list of snapshot objects in the group_snapshot. + :returns: model_update, snapshots_model_update + """ + + if not utils.is_group_a_cg_snapshot_type(group_snapshot): + # we'll rely on the generic group implementation if it is not a + # consistency group request. + raise NotImplementedError() + + cgsnapshot_id = group_snapshot.id cg_name = 'cg_snap-' + cgsnapshot_id model_update, snapshots_model = ( @@ -3671,6 +3738,7 @@ class StorwizeSVCCommonDriver(san.SanDriver, 'thin_provisioning_support': not use_thick_provisioning, 'thick_provisioning_support': use_thick_provisioning, 'max_over_subscription_ratio': over_sub_ratio, + 'consistent_group_snapshot_enabled': True, } if self._replica_enabled: pool_stats.update({ diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_fc.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_fc.py index b0f683aba0a..b49aaba1b5c 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_fc.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_fc.py @@ -88,9 +88,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver): 2.1 - Added replication V2 support to the global/metro mirror mode 2.1.1 - Update replication to version 2.1 + 2.2 - Add CG capability to generic volume groups """ - VERSION = "2.1.1" + VERSION = "2.2" # ThirdPartySystems wiki page CI_WIKI_NAME = "IBM_STORAGE_CI" diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_iscsi.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_iscsi.py index d70ccd5c6ee..62ccbbb2229 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_iscsi.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_iscsi.py @@ -88,9 +88,10 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver): 2.1 - Added replication V2 support to the global/metro mirror mode 2.1.1 - Update replication to version 2.1 + 2.2 - Add CG capability to generic volume groups """ - VERSION = "2.1.1" + VERSION = "2.2" # ThirdPartySystems wiki page CI_WIKI_NAME = "IBM_STORAGE_CI" diff --git a/releasenotes/notes/storwize-generic-volume-group-74495fa23e059bf9.yaml b/releasenotes/notes/storwize-generic-volume-group-74495fa23e059bf9.yaml new file mode 100644 index 00000000000..00103f3a0c7 --- /dev/null +++ b/releasenotes/notes/storwize-generic-volume-group-74495fa23e059bf9.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add consistency group capability to generic volume groups in Storwize + drivers.