From b248aad12a223095b22b312b16b18c108df81fd4 Mon Sep 17 00:00:00 2001 From: Ravi Edpuganti Date: Tue, 25 Apr 2017 14:22:49 +0530 Subject: [PATCH] Added CG capability to volume group in CoprHD CoprHD has been supporting consistency group starting from kilo release. This code change makes sure that cg support is added for generic volume groups for PIKE. Volume group maps to consistency group in CoprHD when the consistency group snapshot flag is enabled for the generic volume group. Change-Id: I3ec946ca594e7c18a84f0b030607f0cc6f5864fb Closes-Bug: #1682239 --- .../tests/unit/volume/drivers/test_coprhd.py | 447 ++++++++++-------- cinder/volume/drivers/coprhd/common.py | 221 ++++++--- cinder/volume/drivers/coprhd/fc.py | 94 +++- cinder/volume/drivers/coprhd/iscsi.py | 88 +++- cinder/volume/drivers/coprhd/scaleio.py | 89 +++- ...generic-volume-group-a1d41d439f94ae19.yaml | 3 + 6 files changed, 597 insertions(+), 345 deletions(-) create mode 100644 releasenotes/notes/coprhd-generic-volume-group-a1d41d439f94ae19.yaml diff --git a/cinder/tests/unit/volume/drivers/test_coprhd.py b/cinder/tests/unit/volume/drivers/test_coprhd.py index d2eaa16f861..83df077a95f 100644 --- a/cinder/tests/unit/volume/drivers/test_coprhd.py +++ b/cinder/tests/unit/volume/drivers/test_coprhd.py @@ -13,13 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -from mock import Mock +import mock from cinder import context -from cinder import exception from cinder.objects import fields from cinder import test -from cinder.tests.unit import fake_constants +from cinder.tests.unit import fake_constants as fake from cinder.volume.drivers.coprhd import common as coprhd_common from cinder.volume.drivers.coprhd import fc as coprhd_fc from cinder.volume.drivers.coprhd import iscsi as coprhd_iscsi @@ -185,60 +184,68 @@ scaleio_itl_list = {"itl": [{"hlu": -1, "target": {}}]} -def get_test_volume_data(volume_type_id): - test_volume = {'name': 'test-vol1', - 'size': 1, - 'volume_name': 'test-vol1', - 'id': '1', - 'consistencygroup_id': None, - 'provider_auth': None, - 'project_id': 'project', - 'display_name': 'test-vol1', - 'display_description': 'test volume', - 'volume_type_id': volume_type_id, - 'provider_id': '1', - } - return test_volume +class test_volume_data(object): + name = 'test-vol1' + size = 1 + volume_name = 'test-vol1' + id = fake.VOLUME_ID + group_id = None + provider_auth = None + project_id = fake.PROJECT_ID + display_name = 'test-vol1' + display_description = 'test volume', + volume_type_id = None + provider_id = fake.PROVIDER_ID + + def __init__(self, volume_type_id): + self.volume_type_id = volume_type_id -def get_source_test_volume_data(volume_type_id): - test_volume = {'name': 'source_test-vol1', - 'size': 1, - 'volume_name': 'source_test-vol1', - 'id': '1234', - 'consistencygroup_id': None, - 'provider_auth': None, - 'project_id': 'project', - 'display_name': 'source_test-vol1', - 'display_description': 'test volume', - 'volume_type_id': volume_type_id} - return test_volume +class source_test_volume_data(object): + name = 'source_test-vol1' + size = 1 + volume_name = 'source_test-vol1' + id = fake.VOLUME2_ID + group_id = None + provider_auth = None + project_id = fake.PROJECT_ID + display_name = 'source_test-vol1' + display_description = 'test volume' + volume_type_id = None + + def __init__(self, volume_type_id): + self.volume_type_id = volume_type_id -def get_clone_volume_data(volume_type_id): - clone_test_volume = {'name': 'clone-test-vol1', - 'size': 1, - 'volume_name': 'clone-test-vol1', - 'id': '2', - 'provider_auth': None, - 'project_id': 'project', - 'display_name': 'clone-test-vol1', - 'display_description': 'clone test volume', - 'volume_type_id': volume_type_id} - return clone_test_volume +class test_clone_volume_data(object): + name = 'clone-test-vol1' + size = 1 + volume_name = 'clone-test-vol1' + id = fake.VOLUME3_ID + provider_auth = None + project_id = fake.PROJECT_ID + display_name = 'clone-test-vol1' + display_description = 'clone test volume' + volume_type_id = None + + def __init__(self, volume_type_id): + self.volume_type_id = volume_type_id -def get_test_snapshot_data(src_volume): - test_snapshot = {'name': 'snapshot1', - 'display_name': 'snapshot1', - 'size': 1, - 'id': '1111', - 'volume_name': 'test-vol1', - 'volume_id': '1234', - 'volume': src_volume, - 'volume_size': 1, - 'project_id': 'project'} - return test_snapshot +class test_snapshot_data(object): + name = 'snapshot1' + display_name = 'snapshot1' + size = 1 + id = fake.SNAPSHOT_ID + volume_name = 'test-vol1' + volume_id = fake.VOLUME_ID + volume = None + volume_size = 1 + project_id = fake.PROJECT_ID + status = fields.SnapshotStatus.AVAILABLE + + def __init__(self, src_volume): + self.volume = src_volume def get_connector_data(): @@ -250,26 +257,41 @@ def get_connector_data(): return connector -def get_test_CG_data(volume_type_id): - test_CG = {'name': 'consistency_group_name', - 'id': fake_constants.CONSISTENCY_GROUP_ID, - 'volume_type_id': volume_type_id, - 'status': fields.ConsistencyGroupStatus.AVAILABLE - } - return test_CG +class test_group_data(object): + name = 'group_name' + display_name = 'group_name' + id = fake.GROUP_ID + volume_type_ids = None + volume_types = None + group_type_id = None + status = fields.GroupStatus.AVAILABLE + + def __init__(self, volume_types, group_type_id): + self.group_type_id = group_type_id + self.volume_types = volume_types -def get_test_CG_snap_data(volume_type_id): - test_CG_snapshot = {'name': 'cg_snap_name', - 'id': fake_constants.SNAPSHOT_ID, - 'consistencygroup_id': - fake_constants.CONSISTENCY_GROUP_ID, - 'status': fields.ConsistencyGroupStatus.AVAILABLE, - 'snapshots': [], - 'consistencygroup': get_test_CG_data(volume_type_id), - 'cgsnapshot_id': fake_constants.CGSNAPSHOT_ID, - } - return test_CG_snapshot +class test_group_type_data(object): + name = 'group_name' + display_name = 'group_name' + groupsnapshot_id = None + id = fake.GROUP_TYPE_ID + description = 'group' + + +class test_group_snap_data(object): + name = 'cg_snap_name' + display_name = 'cg_snap_name' + id = fake.GROUP_SNAPSHOT_ID + group_id = fake.GROUP_ID + status = fields.GroupStatus.AVAILABLE + snapshots = [] + group = None + group_type_id = None + + def __init__(self, volume_types, group_type_id): + self.group_type_id = group_type_id + self.group = test_group_data(volume_types, group_type_id) class MockedEMCCoprHDDriverCommon(coprhd_common.EMCCoprHDDriverCommon): @@ -300,22 +322,21 @@ class MockedEMCCoprHDDriverCommon(coprhd_common.EMCCoprHDDriverCommon): return "cg_uri" def init_volume_api(self): - self.volume_api = Mock() + self.volume_api = mock.Mock() self.volume_api.get.return_value = { 'name': 'source_test-vol1', 'size': 1, 'volume_name': 'source_test-vol1', - 'id': fake_constants.VOLUME_ID, - 'consistencygroup_id': fake_constants.CONSISTENCYGROUP_ID, + 'id': fake.VOLUME_ID, + 'group_id': fake.GROUP_ID, 'provider_auth': None, - 'project_id': fake_constants.PROJECT_ID, + 'project_id': fake.PROJECT_ID, 'display_name': 'source_test-vol1', 'display_description': 'test volume', - 'volume_type_id': fake_constants.VOLUME_TYPE_ID, - } + 'volume_type_id': fake.VOLUME_TYPE_ID} def init_coprhd_api_components(self): - self.volume_obj = Mock() + self.volume_obj = mock.Mock() self.volume_obj.create.return_value = "volume_created" self.volume_obj.volume_query.return_value = "volume_uri" self.volume_obj.get_storageAttributes.return_value = ( @@ -342,12 +363,12 @@ class MockedEMCCoprHDDriverCommon(coprhd_common.EMCCoprHDDriverCommon): self.volume_obj.show.return_value = {"id": "vol_id"} self.volume_obj.expand.return_value = "expanded" - self.tag_obj = Mock() + self.tag_obj = mock.Mock() self.tag_obj.list_tags.return_value = [ "Openstack-vol", "Openstack-vol1"] self.tag_obj.tag_resource.return_value = "Tagged" - self.exportgroup_obj = Mock() + self.exportgroup_obj = mock.Mock() self.exportgroup_obj.exportgroup_list.return_value = ( export_group_list) self.exportgroup_obj.exportgroup_show.return_value = ( @@ -356,7 +377,7 @@ class MockedEMCCoprHDDriverCommon(coprhd_common.EMCCoprHDDriverCommon): self.exportgroup_obj.exportgroup_add_volumes.return_value = ( "volume-added") - self.host_obj = Mock() + self.host_obj = mock.Mock() self.host_obj.list_by_tenant.return_value = [] self.host_obj.list_all.return_value = [{'id': "host1_id", 'name': "host1"}] @@ -365,11 +386,11 @@ class MockedEMCCoprHDDriverCommon(coprhd_common.EMCCoprHDDriverCommon): {'name': "12:34:56:78:90:54:32:11"}, {'name': "bfdf432500000004"}] - self.hostinitiator_obj = Mock() - self.varray_obj = Mock() + self.hostinitiator_obj = mock.Mock() + self.varray_obj = mock.Mock() self.varray_obj.varray_show.return_value = varray_detail_data - self.snapshot_obj = Mock() + self.snapshot_obj = mock.Mock() mocked_snap_obj = self.snapshot_obj.return_value mocked_snap_obj.storageResource_query.return_value = ( "resourceUri") @@ -377,10 +398,10 @@ class MockedEMCCoprHDDriverCommon(coprhd_common.EMCCoprHDDriverCommon): "snapshot_created") mocked_snap_obj.snapshot_query.return_value = "snapshot_uri" - self.consistencygroup_obj = Mock() - mocked_cg_object = self.consistencygroup_obj.return_value - mocked_cg_object.create.return_value = "CG-Created" - mocked_cg_object.consistencygroup_query.return_value = "CG-uri" + self.consistencygroup_obj = mock.Mock() + mocked_group_object = self.consistencygroup_obj.return_value + mocked_group_object.create.return_value = "CG-Created" + mocked_group_object.consistencygroup_query.return_value = "CG-uri" class EMCCoprHDISCSIDriverTest(test.TestCase): @@ -391,7 +412,7 @@ class EMCCoprHDISCSIDriverTest(test.TestCase): def create_coprhd_setup(self): - self.configuration = Mock() + self.configuration = mock.Mock() self.configuration.coprhd_hostname = "10.10.10.10" self.configuration.coprhd_port = "4443" self.configuration.volume_backend_name = "EMCCoprHDISCSIDriver" @@ -402,7 +423,10 @@ class EMCCoprHDISCSIDriverTest(test.TestCase): self.configuration.coprhd_varray = "varray" self.configuration.coprhd_emulate_snapshot = False - self.volume_type_id = self.create_coprhd_volume_type() + self.volume_type = self.create_coprhd_volume_type() + self.volume_type_id = self.volume_type.id + self.group_type = test_group_type_data() + self.group_type_id = self.group_type.id self.mock_object(coprhd_iscsi.EMCCoprHDISCSIDriver, '_get_common_driver', @@ -423,8 +447,7 @@ class EMCCoprHDISCSIDriverTest(test.TestCase): "coprhd-volume-type", {'CoprHD:VPOOL': 'vpool_coprhd'}) - volume_id = vipr_volume_type['id'] - return volume_id + return vipr_volume_type def _get_mocked_common_driver(self): return MockedEMCCoprHDDriverCommon( @@ -437,7 +460,7 @@ class EMCCoprHDISCSIDriverTest(test.TestCase): volume_types.destroy(ctx, self.volume_type_id) def test_create_destroy(self): - volume = get_test_volume_data(self.volume_type_id) + volume = test_volume_data(self.volume_type_id) self.driver.create_volume(volume) self.driver.delete_volume(volume) @@ -447,17 +470,17 @@ class EMCCoprHDISCSIDriverTest(test.TestCase): self.assertEqual('unknown', vol_stats['free_capacity_gb']) def test_create_volume_clone(self): - src_volume_data = get_test_volume_data(self.volume_type_id) - clone_volume_data = get_clone_volume_data(self.volume_type_id) + src_volume_data = test_volume_data(self.volume_type_id) + clone_volume_data = test_clone_volume_data(self.volume_type_id) self.driver.create_volume(src_volume_data) self.driver.create_cloned_volume(clone_volume_data, src_volume_data) self.driver.delete_volume(src_volume_data) self.driver.delete_volume(clone_volume_data) def test_create_destroy_snapshot(self): - volume_data = get_test_volume_data(self.volume_type_id) - snapshot_data = get_test_snapshot_data( - get_source_test_volume_data(self.volume_type_id)) + volume_data = test_volume_data(self.volume_type_id) + snapshot_data = test_snapshot_data( + source_test_volume_data(self.volume_type_id)) self.driver.create_volume(volume_data) self.driver.create_snapshot(snapshot_data) @@ -466,11 +489,11 @@ class EMCCoprHDISCSIDriverTest(test.TestCase): def test_create_volume_from_snapshot(self): - src_vol_data = get_source_test_volume_data(self.volume_type_id) + src_vol_data = source_test_volume_data(self.volume_type_id) self.driver.create_volume(src_vol_data) - volume_data = get_test_volume_data(self.volume_type_id) - snapshot_data = get_test_snapshot_data(src_vol_data) + volume_data = test_volume_data(self.volume_type_id) + snapshot_data = test_snapshot_data(src_vol_data) self.driver.create_snapshot(snapshot_data) self.driver.create_volume_from_snapshot(volume_data, snapshot_data) @@ -480,14 +503,14 @@ class EMCCoprHDISCSIDriverTest(test.TestCase): self.driver.delete_volume(volume_data) def test_extend_volume(self): - volume_data = get_test_volume_data(self.volume_type_id) + volume_data = test_volume_data(self.volume_type_id) self.driver.create_volume(volume_data) self.driver.extend_volume(volume_data, 2) self.driver.delete_volume(volume_data) def test_initialize_and_terminate_connection(self): connector_data = get_connector_data() - volume_data = get_test_volume_data(self.volume_type_id) + volume_data = test_volume_data(self.volume_type_id) self.driver.create_volume(volume_data) res_initialize = self.driver.initialize_connection( @@ -498,54 +521,63 @@ class EMCCoprHDISCSIDriverTest(test.TestCase): 'target_iqn': '50:00:09:73:00:18:95:19', 'target_discovered': False, - 'volume_id': '1'}} + 'volume_id': fake.VOLUME_ID}} self.assertEqual( expected_initialize, res_initialize, 'Unexpected return data') self.driver.terminate_connection(volume_data, connector_data) self.driver.delete_volume(volume_data) - def test_create_delete_empty_CG(self): - cg_data = get_test_CG_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_delete_empty_group(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True] + group_data = test_group_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() - self.driver.create_consistencygroup(ctx, cg_data) + self.driver.create_group(ctx, group_data) model_update, volumes_model_update = ( - self.driver.delete_consistencygroup(ctx, cg_data, [])) + self.driver.delete_group(ctx, group_data, [])) self.assertEqual([], volumes_model_update, 'Unexpected return data') - def test_create_update_delete_CG(self): - cg_data = get_test_CG_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_update_delete_group(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True, True, True] + group_data = test_group_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() - self.driver.create_consistencygroup(ctx, cg_data) + self.driver.create_group(ctx, group_data) - volume = get_test_volume_data(self.volume_type_id) + volume = test_volume_data(self.volume_type_id) self.driver.create_volume(volume) model_update, ret1, ret2 = ( - self.driver.update_consistencygroup(ctx, cg_data, [volume], [])) + self.driver.update_group(ctx, group_data, [volume], [])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) model_update, volumes_model_update = ( - self.driver.delete_consistencygroup(ctx, cg_data, [volume])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.driver.delete_group(ctx, group_data, [volume])) + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) - self.assertEqual([{'status': 'deleted', 'id': '1'}], + self.assertEqual([{'status': 'deleted', 'id': fake.VOLUME_ID}], volumes_model_update) - def test_create_delete_CG_snap(self): - cg_snap_data = get_test_CG_snap_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_delete_group_snap(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True] + group_snap_data = test_group_snap_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() model_update, snapshots_model_update = ( - self.driver.create_cgsnapshot(ctx, cg_snap_data, [])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.driver.create_group_snapshot(ctx, group_snap_data, [])) + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) self.assertEqual([], snapshots_model_update, 'Unexpected return data') model_update, snapshots_model_update = ( - self.driver.delete_cgsnapshot(ctx, cg_snap_data, [])) + self.driver.delete_group_snapshot(ctx, group_snap_data, [])) self.assertEqual({}, model_update, 'Unexpected return data') self.assertEqual([], snapshots_model_update, 'Unexpected return data') @@ -558,7 +590,7 @@ class EMCCoprHDFCDriverTest(test.TestCase): def create_coprhd_setup(self): - self.configuration = Mock() + self.configuration = mock.Mock() self.configuration.coprhd_hostname = "10.10.10.10" self.configuration.coprhd_port = "4443" self.configuration.volume_backend_name = "EMCCoprHDFCDriver" @@ -569,7 +601,10 @@ class EMCCoprHDFCDriverTest(test.TestCase): self.configuration.coprhd_varray = "varray" self.configuration.coprhd_emulate_snapshot = False - self.volume_type_id = self.create_coprhd_volume_type() + self.volume_type = self.create_coprhd_volume_type() + self.volume_type_id = self.volume_type.id + self.group_type = test_group_type_data() + self.group_type_id = self.group_type.id self.mock_object(coprhd_fc.EMCCoprHDFCDriver, '_get_common_driver', @@ -589,8 +624,7 @@ class EMCCoprHDFCDriverTest(test.TestCase): vipr_volume_type = volume_types.create(ctx, "coprhd-volume-type", {'CoprHD:VPOOL': 'vpool_vipr'}) - volume_id = vipr_volume_type['id'] - return volume_id + return vipr_volume_type def _get_mocked_common_driver(self): return MockedEMCCoprHDDriverCommon( @@ -603,7 +637,7 @@ class EMCCoprHDFCDriverTest(test.TestCase): volume_types.destroy(ctx, self.volume_type_id) def test_create_destroy(self): - volume = get_test_volume_data(self.volume_type_id) + volume = test_volume_data(self.volume_type_id) self.driver.create_volume(volume) self.driver.delete_volume(volume) @@ -614,8 +648,8 @@ class EMCCoprHDFCDriverTest(test.TestCase): def test_create_volume_clone(self): - src_volume_data = get_test_volume_data(self.volume_type_id) - clone_volume_data = get_clone_volume_data(self.volume_type_id) + src_volume_data = test_volume_data(self.volume_type_id) + clone_volume_data = test_clone_volume_data(self.volume_type_id) self.driver.create_volume(src_volume_data) self.driver.create_cloned_volume(clone_volume_data, src_volume_data) self.driver.delete_volume(src_volume_data) @@ -623,9 +657,9 @@ class EMCCoprHDFCDriverTest(test.TestCase): def test_create_destroy_snapshot(self): - volume_data = get_test_volume_data(self.volume_type_id) - snapshot_data = get_test_snapshot_data( - get_source_test_volume_data(self.volume_type_id)) + volume_data = test_volume_data(self.volume_type_id) + snapshot_data = test_snapshot_data( + source_test_volume_data(self.volume_type_id)) self.driver.create_volume(volume_data) self.driver.create_snapshot(snapshot_data) @@ -633,11 +667,11 @@ class EMCCoprHDFCDriverTest(test.TestCase): self.driver.delete_volume(volume_data) def test_create_volume_from_snapshot(self): - src_vol_data = get_source_test_volume_data(self.volume_type_id) + src_vol_data = source_test_volume_data(self.volume_type_id) self.driver.create_volume(src_vol_data) - volume_data = get_test_volume_data(self.volume_type_id) - snapshot_data = get_test_snapshot_data(src_vol_data) + volume_data = test_volume_data(self.volume_type_id) + snapshot_data = test_snapshot_data(src_vol_data) self.driver.create_snapshot(snapshot_data) self.driver.create_volume_from_snapshot(volume_data, snapshot_data) @@ -646,22 +680,8 @@ class EMCCoprHDFCDriverTest(test.TestCase): self.driver.delete_volume(src_vol_data) self.driver.delete_volume(volume_data) - def test_create_volume_from_cg_snapshot(self): - ctx = context.get_admin_context() - - volume_data = get_test_volume_data(self.volume_type_id) - cg_snap_data = get_test_CG_snap_data(self.volume_type_id) - - self.driver.create_cgsnapshot(ctx, cg_snap_data, []) - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_volume_from_snapshot, - volume_data, cg_snap_data) - - self.driver.delete_cgsnapshot(ctx, cg_snap_data, []) - self.driver.delete_volume(volume_data) - def test_extend_volume(self): - volume_data = get_test_volume_data(self.volume_type_id) + volume_data = test_volume_data(self.volume_type_id) self.driver.create_volume(volume_data) self.driver.extend_volume(volume_data, 2) self.driver.delete_volume(volume_data) @@ -669,7 +689,7 @@ class EMCCoprHDFCDriverTest(test.TestCase): def test_initialize_and_terminate_connection(self): connector_data = get_connector_data() - volume_data = get_test_volume_data(self.volume_type_id) + volume_data = test_volume_data(self.volume_type_id) self.driver.create_volume(volume_data) res_initiatlize = self.driver.initialize_connection( @@ -686,7 +706,7 @@ class EMCCoprHDFCDriverTest(test.TestCase): 'target_wwn': ['1234567890123456', '1234567890123456'], 'target_discovered': False, - 'volume_id': '1'}} + 'volume_id': fake.VOLUME_ID}} self.assertEqual( expected_initialize, res_initiatlize, 'Unexpected return data') @@ -707,47 +727,56 @@ class EMCCoprHDFCDriverTest(test.TestCase): self.driver.delete_volume(volume_data) - def test_create_delete_empty_CG(self): - cg_data = get_test_CG_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_delete_empty_group(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True] + group_data = test_group_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() - self.driver.create_consistencygroup(ctx, cg_data) + self.driver.create_group(ctx, group_data) model_update, volumes_model_update = ( - self.driver.delete_consistencygroup(ctx, cg_data, [])) + self.driver.delete_group(ctx, group_data, [])) self.assertEqual([], volumes_model_update, 'Unexpected return data') - def test_create_update_delete_CG(self): - cg_data = get_test_CG_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_update_delete_group(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True, True] + group_data = test_group_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() - self.driver.create_consistencygroup(ctx, cg_data) + self.driver.create_group(ctx, group_data) - volume = get_test_volume_data(self.volume_type_id) + volume = test_volume_data(self.volume_type_id) self.driver.create_volume(volume) model_update, ret1, ret2 = ( - self.driver.update_consistencygroup(ctx, cg_data, [volume], [])) + self.driver.update_group(ctx, group_data, [volume], [])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) model_update, volumes_model_update = ( - self.driver.delete_consistencygroup(ctx, cg_data, [volume])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.driver.delete_group(ctx, group_data, [volume])) + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) - self.assertEqual([{'status': 'deleted', 'id': '1'}], + self.assertEqual([{'status': 'deleted', 'id': fake.VOLUME_ID}], volumes_model_update) - def test_create_delete_CG_snap(self): - cg_snap_data = get_test_CG_snap_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_delete_group_snap(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True] + group_snap_data = test_group_snap_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() model_update, snapshots_model_update = ( - self.driver.create_cgsnapshot(ctx, cg_snap_data, [])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.driver.create_group_snapshot(ctx, group_snap_data, [])) + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) self.assertEqual([], snapshots_model_update, 'Unexpected return data') model_update, snapshots_model_update = ( - self.driver.delete_cgsnapshot(ctx, cg_snap_data, [])) + self.driver.delete_group_snapshot(ctx, group_snap_data, [])) self.assertEqual({}, model_update, 'Unexpected return data') self.assertEqual([], snapshots_model_update, 'Unexpected return data') @@ -760,7 +789,7 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): def create_coprhd_setup(self): - self.configuration = Mock() + self.configuration = mock.Mock() self.configuration.coprhd_hostname = "10.10.10.10" self.configuration.coprhd_port = "4443" self.configuration.volume_backend_name = "EMCCoprHDFCDriver" @@ -779,7 +808,10 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): self.configuration.scaleio_server_certificate_path = ( "/etc/scaleio/certs") - self.volume_type_id = self.create_coprhd_volume_type() + self.volume_type = self.create_coprhd_volume_type() + self.volume_type_id = self.volume_type.id + self.group_type = test_group_type_data() + self.group_type_id = self.group_type.id self.mock_object(coprhd_scaleio.EMCCoprHDScaleIODriver, '_get_common_driver', @@ -802,8 +834,7 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): vipr_volume_type = volume_types.create(ctx, "coprhd-volume-type", {'CoprHD:VPOOL': 'vpool_vipr'}) - volume_id = vipr_volume_type['id'] - return volume_id + return vipr_volume_type def _get_mocked_common_driver(self): return MockedEMCCoprHDDriverCommon( @@ -820,7 +851,7 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): volume_types.destroy(ctx, self.volume_type_id) def test_create_destroy(self): - volume = get_test_volume_data(self.volume_type_id) + volume = test_volume_data(self.volume_type_id) self.driver.create_volume(volume) self.driver.delete_volume(volume) @@ -831,8 +862,8 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): def test_create_volume_clone(self): - src_volume_data = get_test_volume_data(self.volume_type_id) - clone_volume_data = get_clone_volume_data(self.volume_type_id) + src_volume_data = test_volume_data(self.volume_type_id) + clone_volume_data = test_clone_volume_data(self.volume_type_id) self.driver.create_volume(src_volume_data) self.driver.create_cloned_volume(clone_volume_data, src_volume_data) self.driver.delete_volume(src_volume_data) @@ -840,9 +871,9 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): def test_create_destroy_snapshot(self): - volume_data = get_test_volume_data(self.volume_type_id) - snapshot_data = get_test_snapshot_data( - get_source_test_volume_data(self.volume_type_id)) + volume_data = test_volume_data(self.volume_type_id) + snapshot_data = test_snapshot_data( + source_test_volume_data(self.volume_type_id)) self.driver.create_volume(volume_data) self.driver.create_snapshot(snapshot_data) @@ -850,11 +881,11 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): self.driver.delete_volume(volume_data) def test_create_volume_from_snapshot(self): - src_vol_data = get_source_test_volume_data(self.volume_type_id) + src_vol_data = source_test_volume_data(self.volume_type_id) self.driver.create_volume(src_vol_data) - volume_data = get_test_volume_data(self.volume_type_id) - snapshot_data = get_test_snapshot_data(src_vol_data) + volume_data = test_volume_data(self.volume_type_id) + snapshot_data = test_snapshot_data(src_vol_data) self.driver.create_snapshot(snapshot_data) self.driver.create_volume_from_snapshot(volume_data, snapshot_data) @@ -864,7 +895,7 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): self.driver.delete_volume(volume_data) def test_extend_volume(self): - volume_data = get_test_volume_data(self.volume_type_id) + volume_data = test_volume_data(self.volume_type_id) self.driver.create_volume(volume_data) self.driver.extend_volume(volume_data, 2) self.driver.delete_volume(volume_data) @@ -872,16 +903,17 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): def test_initialize_and_terminate_connection(self): connector_data = get_connector_data() - volume_data = get_test_volume_data(self.volume_type_id) + volume_data = test_volume_data(self.volume_type_id) self.driver.create_volume(volume_data) res_initiatlize = self.driver.initialize_connection( volume_data, connector_data) + exp_name = res_initiatlize['data']['scaleIO_volname'] expected_initialize = {'data': {'bandwidthLimit': None, 'hostIP': '10.0.0.2', 'iopsLimit': None, - 'scaleIO_volname': 'test-vol1', - 'scaleIO_volume_id': '1', + 'scaleIO_volname': exp_name, + 'scaleIO_volume_id': fake.PROVIDER_ID, 'serverIP': '10.10.10.11', 'serverPassword': 'scaleio_password', 'serverPort': 443, @@ -895,46 +927,55 @@ class EMCCoprHDScaleIODriverTest(test.TestCase): volume_data, connector_data) self.driver.delete_volume(volume_data) - def test_create_delete_empty_CG(self): - cg_data = get_test_CG_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_delete_empty_group(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True] + group_data = test_group_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() - self.driver.create_consistencygroup(ctx, cg_data) + self.driver.create_group(ctx, group_data) model_update, volumes_model_update = ( - self.driver.delete_consistencygroup(ctx, cg_data, [])) + self.driver.delete_group(ctx, group_data, [])) self.assertEqual([], volumes_model_update, 'Unexpected return data') - def test_create_update_delete_CG(self): - cg_data = get_test_CG_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_update_delete_group(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True, True, True] + group_data = test_group_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() - self.driver.create_consistencygroup(ctx, cg_data) + self.driver.create_group(ctx, group_data) - volume = get_test_volume_data(self.volume_type_id) + volume = test_volume_data(self.volume_type_id) self.driver.create_volume(volume) model_update, ret1, ret2 = ( - self.driver.update_consistencygroup(ctx, cg_data, [volume], [])) + self.driver.update_group(ctx, group_data, [volume], [])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) model_update, volumes_model_update = ( - self.driver.delete_consistencygroup(ctx, cg_data, [volume])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.driver.delete_group(ctx, group_data, [volume])) + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) - self.assertEqual([{'status': 'deleted', 'id': '1'}], + self.assertEqual([{'status': 'deleted', 'id': fake.VOLUME_ID}], volumes_model_update) - def test_create_delete_CG_snap(self): - cg_snap_data = get_test_CG_snap_data(self.volume_type_id) + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_delete_group_snap(self, cg_ss_enabled): + cg_ss_enabled.side_effect = [True, True] + group_snap_data = test_group_snap_data([self.volume_type], + self.group_type_id) ctx = context.get_admin_context() model_update, snapshots_model_update = ( - self.driver.create_cgsnapshot(ctx, cg_snap_data, [])) - self.assertEqual({'status': fields.ConsistencyGroupStatus.AVAILABLE}, + self.driver.create_group_snapshot(ctx, group_snap_data, [])) + self.assertEqual({'status': fields.GroupStatus.AVAILABLE}, model_update) self.assertEqual([], snapshots_model_update, 'Unexpected return data') model_update, snapshots_model_update = ( - self.driver.delete_cgsnapshot(ctx, cg_snap_data, [])) + self.driver.delete_group_snapshot(ctx, group_snap_data, [])) self.assertEqual({}, model_update, 'Unexpected return data') self.assertEqual([], snapshots_model_update, 'Unexpected return data') diff --git a/cinder/volume/drivers/coprhd/common.py b/cinder/volume/drivers/coprhd/common.py index 7c474e9936b..1c807276422 100644 --- a/cinder/volume/drivers/coprhd/common.py +++ b/cinder/volume/drivers/coprhd/common.py @@ -45,9 +45,9 @@ from cinder.volume.drivers.coprhd.helpers import tag as coprhd_tag from cinder.volume.drivers.coprhd.helpers import ( virtualarray as coprhd_varray) from cinder.volume.drivers.coprhd.helpers import volume as coprhd_vol +from cinder.volume import utils as volume_utils from cinder.volume import volume_types - LOG = logging.getLogger(__name__) MAX_RETRIES = 10 @@ -88,6 +88,10 @@ CONF.register_opts(volume_opts, group=configuration.SHARED_CONF_GROUP) URI_VPOOL_VARRAY_CAPACITY = '/block/vpools/{0}/varrays/{1}/capacity' URI_BLOCK_EXPORTS_FOR_INITIATORS = '/block/exports?initiators={0}' EXPORT_RETRY_COUNT = 5 +MAX_DEFAULT_NAME_LENGTH = 128 +MAX_SNAPSHOT_NAME_LENGTH = 63 +MAX_CONSISTENCY_GROUP_NAME_LENGTH = 64 +MAX_SIO_LEN = 31 def retry_wrapper(func): @@ -225,8 +229,9 @@ class EMCCoprHDDriverCommon(object): def create_volume(self, vol, driver, truncate_name=False): self.authenticate_user() - name = self._get_resource_name(vol, truncate_name) - size = int(vol['size']) * units.Gi + name = self._get_resource_name(vol, MAX_DEFAULT_NAME_LENGTH, + truncate_name) + size = int(vol.size) * units.Gi vpool = self._get_vpool(vol) self.vpool = vpool['CoprHD:VPOOL'] @@ -234,14 +239,17 @@ class EMCCoprHDDriverCommon(object): try: coprhd_cgid = None try: - cgid = vol['consistencygroup_id'] - if cgid: - coprhd_cgid = self._get_coprhd_cgid(cgid) + if vol.group_id: + if volume_utils.is_group_a_cg_snapshot_type(vol.group): + coprhd_cgid = self._get_coprhd_cgid(vol.group_id) except KeyError: coprhd_cgid = None + except AttributeError: + coprhd_cgid = None full_project_name = ("%s/%s" % (self.configuration.coprhd_tenant, - self.configuration.coprhd_project)) + self.configuration.coprhd_project) + ) self.volume_obj.create(full_project_name, name, size, self.configuration.coprhd_varray, self.vpool, @@ -249,6 +257,7 @@ class EMCCoprHDDriverCommon(object): sync=True, # no longer specified in volume creation consistencygroup=coprhd_cgid) + except coprhd_utils.CoprHdError as e: coprhd_err_msg = (_("Volume %(name)s: create failed\n%(err)s") % {'name': name, 'err': six.text_type(e.msg)}) @@ -260,7 +269,9 @@ class EMCCoprHDDriverCommon(object): @retry_wrapper def create_consistencygroup(self, context, group, truncate_name=False): self.authenticate_user() - name = self._get_resource_name(group, truncate_name) + name = self._get_resource_name(group, + MAX_CONSISTENCY_GROUP_NAME_LENGTH, + truncate_name) try: self.consistencygroup_obj.create( @@ -291,8 +302,8 @@ class EMCCoprHDDriverCommon(object): def update_consistencygroup(self, group, add_volumes, remove_volumes): self.authenticate_user() - model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} - cg_uri = self._get_coprhd_cgid(group['id']) + model_update = {'status': fields.GroupStatus.AVAILABLE} + cg_uri = self._get_coprhd_cgid(group.id) add_volnames = [] remove_volnames = [] @@ -329,7 +340,9 @@ class EMCCoprHDDriverCommon(object): def delete_consistencygroup(self, context, group, volumes, truncate_name=False): self.authenticate_user() - name = self._get_resource_name(group, truncate_name) + name = self._get_resource_name(group, + MAX_CONSISTENCY_GROUP_NAME_LENGTH, + truncate_name) volumes_model_update = [] try: @@ -344,20 +357,20 @@ class EMCCoprHDDriverCommon(object): sync=True, force_delete=True) - update_item = {'id': vol['id'], + update_item = {'id': vol.id, 'status': - fields.ConsistencyGroupStatus.DELETED} + fields.GroupStatus.DELETED} volumes_model_update.append(update_item) except exception.VolumeBackendAPIException: - update_item = {'id': vol['id'], + update_item = {'id': vol.id, 'status': fields.ConsistencyGroupStatus. ERROR_DELETING} volumes_model_update.append(update_item) LOG.exception("Failed to delete the volume %s of CG.", - vol['name']) + vol.name) self.consistencygroup_obj.delete( name, @@ -365,7 +378,7 @@ class EMCCoprHDDriverCommon(object): self.configuration.coprhd_tenant) model_update = {} - model_update['status'] = group['status'] + model_update['status'] = group.status return model_update, volumes_model_update @@ -384,9 +397,19 @@ class EMCCoprHDDriverCommon(object): self.authenticate_user() snapshots_model_update = [] - cgsnapshot_name = self._get_resource_name(cgsnapshot, truncate_name) - cg_id = cgsnapshot['consistencygroup_id'] - cg_group = cgsnapshot.get('consistencygroup') + cgsnapshot_name = self._get_resource_name(cgsnapshot, + MAX_SNAPSHOT_NAME_LENGTH, + truncate_name) + + cg_id = None + cg_group = None + + try: + cg_id = cgsnapshot.group_id + cg_group = cgsnapshot.group + except AttributeError: + pass + cg_name = None coprhd_cgid = None @@ -408,7 +431,7 @@ class EMCCoprHDDriverCommon(object): True) for snapshot in snapshots: - vol_id_of_snap = snapshot['volume_id'] + vol_id_of_snap = snapshot.volume_id # Finding the volume in CoprHD for this volume id tagname = "OpenStack:id:" + vol_id_of_snap @@ -470,7 +493,7 @@ class EMCCoprHDDriverCommon(object): snapshot['status'] = fields.SnapshotStatus.AVAILABLE snapshots_model_update.append( - {'id': snapshot['id'], 'status': + {'id': snapshot.id, 'status': fields.SnapshotStatus.AVAILABLE}) model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} @@ -493,19 +516,28 @@ class EMCCoprHDDriverCommon(object): @retry_wrapper def delete_cgsnapshot(self, cgsnapshot, snapshots, truncate_name=False): self.authenticate_user() - cgsnapshot_id = cgsnapshot['id'] - cgsnapshot_name = self._get_resource_name(cgsnapshot, truncate_name) + cgsnapshot_id = cgsnapshot.id + cgsnapshot_name = self._get_resource_name(cgsnapshot, + MAX_SNAPSHOT_NAME_LENGTH, + truncate_name) snapshots_model_update = [] - cg_id = cgsnapshot['consistencygroup_id'] - cg_group = cgsnapshot.get('consistencygroup') + + cg_id = None + cg_group = None + + try: + cg_id = cgsnapshot.group_id + cg_group = cgsnapshot.group + except AttributeError: + pass coprhd_cgid = self._get_coprhd_cgid(cg_id) cg_name = self._get_consistencygroup_name(cg_group) model_update = {} LOG.info('Delete cgsnapshot %(snap_name)s for consistency group: ' - '%(group_name)s', {'snap_name': cgsnapshot['name'], + '%(group_name)s', {'snap_name': cgsnapshot.name, 'group_name': cg_name}) try: @@ -531,7 +563,7 @@ class EMCCoprHDDriverCommon(object): for snapshot in snapshots: snapshots_model_update.append( - {'id': snapshot['id'], + {'id': snapshot.id, 'status': fields.SnapshotStatus.DELETED}) return model_update, snapshots_model_update @@ -557,7 +589,9 @@ class EMCCoprHDDriverCommon(object): exempt_tags = [] self.authenticate_user() - name = self._get_resource_name(vol, truncate_name) + name = self._get_resource_name(vol, + MAX_DEFAULT_NAME_LENGTH, + truncate_name) full_project_name = ("%s/%s" % ( self.configuration.coprhd_tenant, self.configuration.coprhd_project)) @@ -613,11 +647,16 @@ class EMCCoprHDDriverCommon(object): if ((not prop.startswith("status") and not prop.startswith("obj_status") and prop != "obj_volume") and value): - add_tags.append( - "%s:%s:%s" % (self.OPENSTACK_TAG, prop, - six.text_type(value))) + tag = ("%s:%s:%s" % + (self.OPENSTACK_TAG, prop, + six.text_type(value))) + + if len(tag) > 128: + tag = tag[0:128] + add_tags.append(tag) except TypeError: - LOG.error("Error tagging the resource property %s", prop) + LOG.error( + "Error tagging the resource property %s", prop) except TypeError: LOG.error("Error tagging the resource properties") @@ -638,17 +677,21 @@ class EMCCoprHDDriverCommon(object): def create_cloned_volume(self, vol, src_vref, truncate_name=False): """Creates a clone of the specified volume.""" self.authenticate_user() - name = self._get_resource_name(vol, truncate_name) + name = self._get_resource_name(vol, + MAX_DEFAULT_NAME_LENGTH, + truncate_name) srcname = self._get_coprhd_volume_name(src_vref) try: - if src_vref['consistencygroup_id']: + if src_vref.group_id: raise coprhd_utils.CoprHdError( coprhd_utils.CoprHdError.SOS_FAILURE_ERR, _("Clone can't be taken individually on a volume" " that is part of a Consistency Group")) except KeyError as e: pass + except AttributeError: + pass try: (storageres_type, storageres_typename) = self.volume_obj.get_storageAttributes( @@ -691,13 +734,21 @@ class EMCCoprHDDriverCommon(object): self._raise_or_log_exception(e.err_code, coprhd_err_msg, log_err_msg) - try: - src_vol_size = src_vref['size'] - except KeyError: - src_vol_size = src_vref['volume_size'] + src_vol_size = 0 + dest_vol_size = 0 - if vol['size'] > src_vol_size: - size_in_bytes = coprhd_utils.to_bytes("%sG" % vol['size']) + try: + src_vol_size = src_vref.size + except AttributeError: + src_vol_size = src_vref.volume_size + + try: + dest_vol_size = vol.size + except AttributeError: + dest_vol_size = vol.volume_size + + if dest_vol_size > src_vol_size: + size_in_bytes = coprhd_utils.to_bytes("%sG" % dest_vol_size) try: self.volume_obj.expand( ("%s/%s" % (self.configuration.coprhd_tenant, @@ -733,7 +784,8 @@ class EMCCoprHDDriverCommon(object): {'volume_name': volume_name, 'err': six.text_type(e.msg)}) - log_err_msg = "Volume : %s expand failed" % volume_name + log_err_msg = ("Volume : %s expand failed" % + volume_name) self._raise_or_log_exception(e.err_code, coprhd_err_msg, log_err_msg) @@ -747,15 +799,20 @@ class EMCCoprHDDriverCommon(object): self.create_cloned_volume(volume, snapshot, truncate_name) return - if snapshot.get('cgsnapshot_id'): - raise coprhd_utils.CoprHdError( - coprhd_utils.CoprHdError.SOS_FAILURE_ERR, - _("Volume cannot be created individually from a snapshot " - "that is part of a Consistency Group")) + try: + if snapshot.group_snapshot_id: + raise coprhd_utils.CoprHdError( + coprhd_utils.CoprHdError.SOS_FAILURE_ERR, + _("Volume cannot be created individually from a snapshot " + "that is part of a Consistency Group")) + except AttributeError: + pass src_snapshot_name = None - src_vol_ref = snapshot['volume'] - new_volume_name = self._get_resource_name(volume, truncate_name) + src_vol_ref = snapshot.volume + new_volume_name = self._get_resource_name(volume, + MAX_DEFAULT_NAME_LENGTH, + truncate_name) try: coprhd_vol_info = self._get_coprhd_volume_name( @@ -786,12 +843,13 @@ class EMCCoprHDDriverCommon(object): {'src_snapshot_name': src_snapshot_name, 'err': six.text_type(e.msg)}) - log_err_msg = "Snapshot : %s clone failed" % src_snapshot_name + log_err_msg = ("Snapshot : %s clone failed" % + src_snapshot_name) self._raise_or_log_exception(e.err_code, coprhd_err_msg, log_err_msg) - if volume['size'] > snapshot['volume_size']: - size_in_bytes = coprhd_utils.to_bytes("%sG" % volume['size']) + if volume.size > snapshot.volume_size: + size_in_bytes = coprhd_utils.to_bytes("%sG" % volume.size) try: self.volume_obj.expand( @@ -805,7 +863,8 @@ class EMCCoprHDDriverCommon(object): {'volume_name': new_volume_name, 'err': six.text_type(e.msg)}) - log_err_msg = "Volume : %s expand failed" % new_volume_name + log_err_msg = ("Volume : %s expand failed" % + new_volume_name) self._raise_or_log_exception(e.err_code, coprhd_err_msg, log_err_msg) @@ -829,7 +888,7 @@ class EMCCoprHDDriverCommon(object): "\n%(err)s") % {'name': name, 'err': six.text_type(e.msg)}) - log_err_msg = "Volume : %s delete failed" % name + log_err_msg = ("Volume : %s delete failed" % name) self._raise_or_log_exception(e.err_code, coprhd_err_msg, log_err_msg) @@ -837,10 +896,10 @@ class EMCCoprHDDriverCommon(object): def create_snapshot(self, snapshot, truncate_name=False): self.authenticate_user() - volume = snapshot['volume'] + volume = snapshot.volume try: - if volume['consistencygroup_id']: + if volume.group_id: raise coprhd_utils.CoprHdError( coprhd_utils.CoprHdError.SOS_FAILURE_ERR, _("Snapshot can't be taken individually on a volume" @@ -855,8 +914,10 @@ class EMCCoprHDDriverCommon(object): return try: - snapshotname = self._get_resource_name(snapshot, truncate_name) - vol = snapshot['volume'] + snapshotname = self._get_resource_name(snapshot, + MAX_SNAPSHOT_NAME_LENGTH, + truncate_name) + vol = snapshot.volume volumename = self._get_coprhd_volume_name(vol) projectname = self.configuration.coprhd_project @@ -894,7 +955,7 @@ class EMCCoprHDDriverCommon(object): "\n%(err)s") % {'snapshotname': snapshotname, 'err': six.text_type(e.msg)}) - log_err_msg = "Snapshot : %s create failed" % snapshotname + log_err_msg = ("Snapshot : %s create failed" % snapshotname) self._raise_or_log_exception(e.err_code, coprhd_err_msg, log_err_msg) @@ -902,10 +963,10 @@ class EMCCoprHDDriverCommon(object): def delete_snapshot(self, snapshot): self.authenticate_user() - vol = snapshot['volume'] + vol = snapshot.volume try: - if vol['consistencygroup_id']: + if vol.group_id: raise coprhd_utils.CoprHdError( coprhd_utils.CoprHdError.SOS_FAILURE_ERR, _("Snapshot delete can't be done individually on a volume" @@ -949,7 +1010,7 @@ class EMCCoprHDDriverCommon(object): coprhd_err_msg = (_("Snapshot %s : Delete Failed\n") % snapshotname) - log_err_msg = "Snapshot : %s delete failed" % snapshotname + log_err_msg = ("Snapshot : %s delete failed" % snapshotname) self._raise_or_log_exception(e.err_code, coprhd_err_msg, log_err_msg) @@ -1170,7 +1231,7 @@ class EMCCoprHDDriverCommon(object): (_("Consistency Group %s not found") % cgid)) def _get_consistencygroup_name(self, consisgrp): - return consisgrp['name'] + return consisgrp.name def _get_coprhd_snapshot_name(self, snapshot, resUri): tagname = self.OPENSTACK_TAG + ":id:" + snapshot['id'] @@ -1200,7 +1261,7 @@ class EMCCoprHDDriverCommon(object): return rslt_snap['name'] def _get_coprhd_volume_name(self, vol, verbose=False): - tagname = self.OPENSTACK_TAG + ":id:" + vol['id'] + tagname = self.OPENSTACK_TAG + ":id:" + vol.id rslt = coprhd_utils.search_by_tag( coprhd_vol.Volume.URI_SEARCH_VOLUMES_BY_TAG.format(tagname), self.configuration.coprhd_hostname, @@ -1210,7 +1271,7 @@ class EMCCoprHDDriverCommon(object): # as "OpenStack:obj_id" # as snapshots will be having the obj_id instead of just id. if len(rslt) == 0: - tagname = self.OPENSTACK_TAG + ":obj_id:" + vol['id'] + tagname = self.OPENSTACK_TAG + ":obj_id:" + vol.id rslt = coprhd_utils.search_by_tag( coprhd_vol.Volume.URI_SEARCH_VOLUMES_BY_TAG.format(tagname), self.configuration.coprhd_hostname, @@ -1228,21 +1289,36 @@ class EMCCoprHDDriverCommon(object): coprhd_utils.CoprHdError.NOT_FOUND_ERR, (_("Volume %s not found") % vol['display_name'])) - def _get_resource_name(self, resource, truncate_name=False): - name = resource.get('display_name', None) - + def _get_resource_name(self, resource, + max_name_cap=MAX_DEFAULT_NAME_LENGTH, + truncate_name=False): + # 36 refers to the length of UUID and +1 for '-' + permitted_name_length = max_name_cap - (36 + 1) + name = resource.display_name if not name: - name = resource['name'] + name = resource.name - if truncate_name and len(name) > 31: + ''' + for scaleio, truncate_name will be true. We make sure the + total name is less than or equal to 31 characters. + _id_to_base64 will return a 24 character name''' + if truncate_name: name = self._id_to_base64(resource.id) + return name - return name + elif len(name) > permitted_name_length: + ''' + The maximum length of resource name in CoprHD is 128. Hence we use + only first 91 characters of the resource name''' + return name[0:permitted_name_length] + "-" + resource.id + + else: + return name + "-" + resource.id def _get_vpool(self, volume): vpool = {} ctxt = context.get_admin_context() - type_id = volume['volume_type_id'] + type_id = volume.volume_type_id if type_id is not None: volume_type = volume_types.get_volume_type(ctxt, type_id) specs = volume_type.get('extra_specs') @@ -1363,7 +1439,8 @@ class EMCCoprHDDriverCommon(object): self.authenticate_user() try: - self.stats['consistencygroup_support'] = 'True' + self.stats['consistencygroup_support'] = True + self.stats['consistent_group_snapshot_enabled'] = True vols = self.volume_obj.list_volumes( self.configuration.coprhd_tenant + "/" + diff --git a/cinder/volume/drivers/coprhd/fc.py b/cinder/volume/drivers/coprhd/fc.py index 031f2517ad5..3018e068566 100644 --- a/cinder/volume/drivers/coprhd/fc.py +++ b/cinder/volume/drivers/coprhd/fc.py @@ -20,9 +20,13 @@ import re from oslo_log import log as logging +from cinder import exception +from cinder.i18n import _ from cinder import interface from cinder.volume import driver from cinder.volume.drivers.coprhd import common as coprhd_common +from cinder.volume import utils as volume_utils + from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) @@ -89,30 +93,67 @@ class EMCCoprHDFCDriver(driver.FibreChannelDriver): pass def remove_export(self, context, volume): - """Driver exntry point to remove an export for a volume.""" + """Driver entry point to remove an export for a volume.""" pass - def create_consistencygroup(self, context, group): - """Creates a consistencygroup.""" - return self.common.create_consistencygroup(context, group) + def create_group(self, context, group): + """Creates a group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.create_consistencygroup(context, group) - def update_consistencygroup(self, context, group, add_volumes=None, - remove_volumes=None): - """Updates volumes in consistency group.""" - return self.common.update_consistencygroup(group, add_volumes, - remove_volumes) + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() - def delete_consistencygroup(self, context, group, volumes): - """Deletes a consistency group.""" - return self.common.delete_consistencygroup(context, group, volumes) + def update_group(self, context, group, add_volumes=None, + remove_volumes=None): + """Updates volumes in group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.update_consistencygroup(group, add_volumes, + remove_volumes) - def create_cgsnapshot(self, context, cgsnapshot, snapshots): - """Creates a cgsnapshot.""" - return self.common.create_cgsnapshot(cgsnapshot, snapshots) + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() - def delete_cgsnapshot(self, context, cgsnapshot, snapshots): - """Deletes a cgsnapshot.""" - return self.common.delete_cgsnapshot(cgsnapshot, snapshots) + def create_group_from_src(self, ctxt, group, volumes, + group_snapshot=None, snapshots=None, + source_group=None, source_vols=None): + """Creates a group from source.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + message = _("create group from source is not supported " + "for CoprHD if the group type supports " + "consistent group snapshot.") + raise exception.VolumeBackendAPIException(data=message) + else: + raise NotImplementedError() + + def delete_group(self, context, group, volumes): + """Deletes a group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.delete_consistencygroup(context, group, volumes) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() + + def create_group_snapshot(self, context, group_snapshot, snapshots): + """Creates a group snapshot.""" + if volume_utils.is_group_a_cg_snapshot_type(group_snapshot): + return self.common.create_cgsnapshot(group_snapshot, snapshots) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() + + def delete_group_snapshot(self, context, group_snapshot, snapshots): + """Deletes a group snapshot.""" + if volume_utils.is_group_a_cg_snapshot_type(group_snapshot): + return self.common.delete_cgsnapshot(group_snapshot, snapshots) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() def check_for_export(self, context, volume_id): """Make sure volume is exported.""" @@ -123,14 +164,12 @@ class EMCCoprHDFCDriver(driver.FibreChannelDriver): """Initializes the connection and returns connection info.""" properties = {} - properties['volume_id'] = volume['id'] + properties['volume_id'] = volume.id properties['target_discovered'] = False properties['target_wwn'] = [] init_ports = self._build_initport_list(connector) - itls = self.common.initialize_connection(volume, - 'FC', - init_ports, + itls = self.common.initialize_connection(volume, 'FC', init_ports, connector['host']) target_wwns = None @@ -144,7 +183,12 @@ class EMCCoprHDFCDriver(driver.FibreChannelDriver): properties['target_wwn'] = target_wwns properties['initiator_target_map'] = initiator_target_map - auth = volume['provider_auth'] + auth = None + try: + auth = volume.provider_auth + except AttributeError: + pass + if auth: (auth_method, auth_username, auth_secret) = auth.split() properties['auth_method'] = auth_method @@ -162,9 +206,7 @@ class EMCCoprHDFCDriver(driver.FibreChannelDriver): """Driver entry point to detach a volume from an instance.""" init_ports = self._build_initport_list(connector) - itls = self.common.terminate_connection(volume, - 'FC', - init_ports, + itls = self.common.terminate_connection(volume, 'FC', init_ports, connector['host']) volumes_count = self.common.get_exports_count_by_initiators(init_ports) diff --git a/cinder/volume/drivers/coprhd/iscsi.py b/cinder/volume/drivers/coprhd/iscsi.py index 0575b4da0f6..a113f536a08 100644 --- a/cinder/volume/drivers/coprhd/iscsi.py +++ b/cinder/volume/drivers/coprhd/iscsi.py @@ -18,10 +18,12 @@ from oslo_log import log as logging +from cinder import exception +from cinder.i18n import _ from cinder import interface from cinder.volume import driver from cinder.volume.drivers.coprhd import common as coprhd_common - +from cinder.volume import utils as volume_utils LOG = logging.getLogger(__name__) @@ -67,7 +69,7 @@ class EMCCoprHDISCSIDriver(driver.ISCSIDriver): self.common.expand_volume(volume, new_size) def delete_volume(self, volume): - """Deletes an volume.""" + """Deletes a volume.""" self.common.delete_volume(volume) def create_snapshot(self, snapshot): @@ -90,27 +92,65 @@ class EMCCoprHDISCSIDriver(driver.ISCSIDriver): """Driver entry point to remove an export for a volume.""" pass - def create_consistencygroup(self, context, group): - """Creates a consistencygroup.""" - return self.common.create_consistencygroup(context, group) + def create_group(self, context, group): + """Creates a group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.create_consistencygroup(context, group) - def delete_consistencygroup(self, context, group, volumes): - """Deletes a consistency group.""" - return self.common.delete_consistencygroup(context, group, volumes) + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() - def update_consistencygroup(self, context, group, - add_volumes=None, remove_volumes=None): - """Updates volumes in consistency group.""" - return self.common.update_consistencygroup(group, add_volumes, - remove_volumes) + def create_group_from_src(self, ctxt, group, volumes, + group_snapshot=None, snapshots=None, + source_group=None, source_vols=None): + """Creates a group from source.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + message = _("create group from source is not supported " + "for CoprHD if the group type supports " + "consistent group snapshot.") + raise exception.VolumeBackendAPIException(data=message) + else: + raise NotImplementedError() - def create_cgsnapshot(self, context, cgsnapshot, snapshots): - """Creates a cgsnapshot.""" - return self.common.create_cgsnapshot(cgsnapshot, snapshots) + def update_group(self, context, group, add_volumes=None, + remove_volumes=None): + """Updates volumes in group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.update_consistencygroup(group, add_volumes, + remove_volumes) - def delete_cgsnapshot(self, context, cgsnapshot, snapshots): - """Deletes a cgsnapshot.""" - return self.common.delete_cgsnapshot(cgsnapshot, snapshots) + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() + + def delete_group(self, context, group, volumes): + """Deletes a group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.delete_consistencygroup(context, group, volumes) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() + + def create_group_snapshot(self, context, group_snapshot, snapshots): + """Creates a group snapshot.""" + if volume_utils.is_group_a_cg_snapshot_type(group_snapshot): + LOG.debug("creating a group snapshot") + return self.common.create_cgsnapshot(group_snapshot, snapshots) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() + + def delete_group_snapshot(self, context, group_snapshot, snapshots): + """Deletes a group snapshot.""" + if volume_utils.is_group_a_cg_snapshot_type(group_snapshot): + return self.common.delete_cgsnapshot(group_snapshot, snapshots) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() def check_for_export(self, context, volume_id): """Make sure volume is exported.""" @@ -127,14 +167,20 @@ class EMCCoprHDISCSIDriver(driver.ISCSIDriver): connector['host']) properties = {} properties['target_discovered'] = False - properties['volume_id'] = volume['id'] + properties['volume_id'] = volume.id if itls: properties['target_iqn'] = itls[0]['target']['port'] properties['target_portal'] = '%s:%s' % ( itls[0]['target']['ip_address'], itls[0]['target']['tcp_port']) properties['target_lun'] = itls[0]['hlu'] - auth = volume['provider_auth'] + + auth = None + try: + auth = volume.provider_auth + except AttributeError: + pass + if auth: (auth_method, auth_username, auth_secret) = auth.split() properties['auth_method'] = auth_method diff --git a/cinder/volume/drivers/coprhd/scaleio.py b/cinder/volume/drivers/coprhd/scaleio.py index 23c56e168a9..c4ddc3b3171 100644 --- a/cinder/volume/drivers/coprhd/scaleio.py +++ b/cinder/volume/drivers/coprhd/scaleio.py @@ -29,6 +29,7 @@ from cinder import interface from cinder.volume import configuration from cinder.volume import driver from cinder.volume.drivers.coprhd import common as coprhd_common +from cinder.volume import utils as volume_utils LOG = logging.getLogger(__name__) @@ -92,7 +93,7 @@ class EMCCoprHDScaleIODriver(driver.VolumeDriver): """Creates a Volume.""" self.common.create_volume(volume, self, True) self.common.set_volume_tags(volume, ['_obj_volume_type'], True) - vol_size = self._update_volume_size(int(volume['size'])) + vol_size = self._update_volume_size(int(volume.size)) return {'size': vol_size} def _update_volume_size(self, vol_size): @@ -141,28 +142,68 @@ class EMCCoprHDScaleIODriver(driver.VolumeDriver): """Driver exntry point to remove an export for a volume.""" pass - def create_consistencygroup(self, context, group): - """Creates a consistencygroup.""" - return self.common.create_consistencygroup(context, group, True) + def create_group(self, context, group): + """Creates a group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.create_consistencygroup(context, group, True) - def update_consistencygroup(self, context, group, - add_volumes=None, remove_volumes=None): - """Updates volumes in consistency group.""" - return self.common.update_consistencygroup(group, add_volumes, - remove_volumes) + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() - def delete_consistencygroup(self, context, group, volumes): - """Deletes a consistency group.""" - return self.common.delete_consistencygroup(context, group, - volumes, True) + def update_group(self, context, group, add_volumes=None, + remove_volumes=None): + """Updates volumes in group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.update_consistencygroup(group, add_volumes, + remove_volumes) - def create_cgsnapshot(self, context, cgsnapshot, snapshots): - """Creates a cgsnapshot.""" - return self.common.create_cgsnapshot(cgsnapshot, snapshots, True) + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() - def delete_cgsnapshot(self, context, cgsnapshot, snapshots): - """Deletes a cgsnapshot.""" - return self.common.delete_cgsnapshot(cgsnapshot, snapshots, True) + def create_group_from_src(self, ctxt, group, volumes, + group_snapshot=None, snapshots=None, + source_group=None, source_vols=None): + """Creates a group from source.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + message = _("create group from source is not supported " + "for CoprHD if the group type supports " + "consistent group snapshot.") + raise exception.VolumeBackendAPIException(data=message) + else: + raise NotImplementedError() + + def delete_group(self, context, group, volumes): + """Deletes a group.""" + if volume_utils.is_group_a_cg_snapshot_type(group): + return self.common.delete_consistencygroup(context, group, + volumes, True) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() + + def create_group_snapshot(self, context, group_snapshot, snapshots): + """Creates a group snapshot.""" + if volume_utils.is_group_a_cg_snapshot_type(group_snapshot): + LOG.debug("creating a group snapshot") + return self.common.create_cgsnapshot(group_snapshot, snapshots, + True) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() + + def delete_group_snapshot(self, context, group_snapshot, snapshots): + """Deletes a group snapshot.""" + if volume_utils.is_group_a_cg_snapshot_type(group_snapshot): + return self.common.delete_cgsnapshot(group_snapshot, snapshots, + True) + + # If the group is not consistency group snapshot enabled, then + # we shall rely on generic volume group implementation + raise NotImplementedError() def check_for_export(self, context, volume_id): """Make sure volume is exported.""" @@ -171,11 +212,13 @@ class EMCCoprHDScaleIODriver(driver.VolumeDriver): def initialize_connection(self, volume, connector): """Initializes the connection and returns connection info.""" - volname = self.common._get_resource_name(volume, True) + volname = self.common._get_resource_name(volume, + coprhd_common.MAX_SIO_LEN, + True) properties = {} properties['scaleIO_volname'] = volname - properties['scaleIO_volume_id'] = volume['provider_id'] + properties['scaleIO_volume_id'] = volume.provider_id properties['hostIP'] = connector['ip'] properties[ 'serverIP'] = self.configuration.coprhd_scaleio_rest_gateway_host @@ -215,10 +258,10 @@ class EMCCoprHDScaleIODriver(driver.VolumeDriver): def terminate_connection(self, volume, connector, **kwargs): """Disallow connection from connector.""" - volname = volume['display_name'] + volname = volume.display_name properties = {} properties['scaleIO_volname'] = volname - properties['scaleIO_volume_id'] = volume['provider_id'] + properties['scaleIO_volume_id'] = volume.provider_id properties['hostIP'] = connector['ip'] properties[ 'serverIP'] = self.configuration.coprhd_scaleio_rest_gateway_host diff --git a/releasenotes/notes/coprhd-generic-volume-group-a1d41d439f94ae19.yaml b/releasenotes/notes/coprhd-generic-volume-group-a1d41d439f94ae19.yaml new file mode 100644 index 00000000000..8ba22715585 --- /dev/null +++ b/releasenotes/notes/coprhd-generic-volume-group-a1d41d439f94ae19.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add consistent group capability to generic volume groups in CoprHD driver.