From fcbd762d9d7923ac403324c8aafa6731cb52632a Mon Sep 17 00:00:00 2001 From: Eric Young Date: Sun, 2 Apr 2017 09:54:00 -0400 Subject: [PATCH] ScaleIO: Adding CG support to groups Adding consistency group support capability to generic volume groups in the ScaleIO driver. Change-Id: Iae4dc241137a78db74b8f29b3df540fb3eb89008 Implements: blueprint scaleio-generic-volume-group --- .../scaleio/test_consistencygroups.py | 211 ------------- .../drivers/dell_emc/scaleio/test_groups.py | 279 ++++++++++++++++++ .../volume/drivers/dell_emc/scaleio/driver.py | 149 +++++++--- ...generic-volume-group-ee36e4dba8893422.yaml | 3 + 4 files changed, 394 insertions(+), 248 deletions(-) delete mode 100644 cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_consistencygroups.py create mode 100644 cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_groups.py create mode 100644 releasenotes/notes/scaleio-generic-volume-group-ee36e4dba8893422.yaml diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_consistencygroups.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_consistencygroups.py deleted file mode 100644 index fe0e7ecc1..000000000 --- a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_consistencygroups.py +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright (c) 2013 - 2016 EMC Corporation. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import json - -import mock - -from cinder import context -from cinder.tests.unit.consistencygroup import fake_consistencygroup -from cinder.tests.unit import fake_constants as fake -from cinder.tests.unit import fake_snapshot -from cinder.tests.unit import fake_volume -from cinder.tests.unit.volume.drivers.dell_emc import scaleio -from cinder.tests.unit.volume.drivers.dell_emc.scaleio import mocks - - -class TestConsistencyGroups(scaleio.TestScaleIODriver): - """Test cases for ``ScaleIODriver consistency groups support``""" - - def setUp(self): - """Setup a test case environment. - - Creates a fake volume object and sets up the required API responses. - """ - super(TestConsistencyGroups, self).setUp() - self.ctx = context.RequestContext('fake', 'fake', auth_token=True) - self.consistency_group = ( - fake_consistencygroup.fake_consistencyobject_obj( - self.ctx, **{'id': fake.CONSISTENCY_GROUP_ID})) - fake_volume1 = fake_volume.fake_volume_obj( - self.ctx, - **{'id': fake.VOLUME_ID, 'provider_id': fake.PROVIDER_ID}) - fake_volume2 = fake_volume.fake_volume_obj( - self.ctx, - **{'id': fake.VOLUME2_ID, 'provider_id': fake.PROVIDER2_ID}) - fake_volume3 = fake_volume.fake_volume_obj( - self.ctx, - **{'id': fake.VOLUME3_ID, 'provider_id': fake.PROVIDER3_ID}) - fake_volume4 = fake_volume.fake_volume_obj( - self.ctx, - **{'id': fake.VOLUME4_ID, 'provider_id': fake.PROVIDER4_ID}) - self.volumes = [fake_volume1, fake_volume2] - self.volumes2 = [fake_volume3, fake_volume4] - fake_snapshot1 = fake_snapshot.fake_snapshot_obj( - self.ctx, - **{'id': fake.SNAPSHOT_ID, 'volume_id': fake.VOLUME_ID, - 'volume': fake_volume1}) - fake_snapshot2 = fake_snapshot.fake_snapshot_obj( - self.ctx, - **{'id': fake.SNAPSHOT2_ID, 'volume_id': fake.VOLUME2_ID, 'volume': - fake_volume2}) - self.snapshots = [fake_snapshot1, fake_snapshot2] - self.snapshot_reply = json.dumps({ - 'volumeIdList': ['sid1', 'sid2'], - 'snapshotGroupId': 'sgid1'}) - self.HTTPS_MOCK_RESPONSES = { - self.RESPONSE_MODE.Valid: { - 'instances/Volume::{}/action/removeVolume'.format( - fake_volume1['provider_id'] - ): fake_volume1['provider_id'], - 'instances/Volume::{}/action/removeVolume'.format( - fake_volume2['provider_id'] - ): fake_volume2['provider_id'], - 'instances/Volume::{}/action/removeMappedSdc'.format( - fake_volume1['provider_id'] - ): fake_volume1['provider_id'], - 'instances/Volume::{}/action/removeMappedSdc'.format( - fake_volume2['provider_id'] - ): fake_volume2['provider_id'], - 'instances/System/action/snapshotVolumes': - self.snapshot_reply, - }, - self.RESPONSE_MODE.BadStatus: { - 'instances/Volume::{}/action/removeVolume'.format( - fake_volume1['provider_id'] - ): mocks.MockHTTPSResponse( - { - 'errorCode': 401, - 'message': 'BadStatus Volume Test', - }, 401 - ), - 'instances/Volume::{}/action/removeVolume'.format( - fake_volume2['provider_id'] - ): mocks.MockHTTPSResponse( - { - 'errorCode': 401, - 'message': 'BadStatus Volume Test', - }, 401 - ), - 'instances/System/action/snapshotVolumes': - self.BAD_STATUS_RESPONSE - }, - } - - def _fake_cgsnapshot(self): - cgsnap = {'id': 'cgsid', 'name': 'testsnap', - 'consistencygroup_id': fake.CONSISTENCY_GROUP_ID, - 'status': 'available'} - return cgsnap - - def test_create_consistencygroup(self): - result = self.driver.create_consistencygroup(self.ctx, - self.consistency_group) - self.assertEqual('available', result['status']) - - def test_delete_consistencygroup_valid(self): - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - self.driver.configuration.set_override( - 'sio_unmap_volume_before_deletion', - override=True) - result_model_update, result_volumes_update = ( - self.driver.delete_consistencygroup(self.ctx, - self.consistency_group, - self.volumes)) - self.assertTrue(all(volume['status'] == 'deleted' for volume in - result_volumes_update)) - self.assertEqual('deleted', result_model_update['status']) - - def test_delete_consistency_group_fail(self): - self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) - result_model_update, result_volumes_update = ( - self.driver.delete_consistencygroup(self.ctx, - self.consistency_group, - self.volumes)) - self.assertTrue(any(volume['status'] == 'error_deleting' for volume in - result_volumes_update)) - self.assertIn(result_model_update['status'], - ['error_deleting', 'error']) - - def test_create_consistencygroup_from_cg(self): - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result_model_update, result_volumes_model_update = ( - self.driver.create_consistencygroup_from_src( - self.ctx, self.consistency_group, self.volumes2, - source_cg=self.consistency_group, source_vols=self.volumes)) - self.assertEqual('available', result_model_update['status']) - get_pid = lambda snapshot: snapshot['provider_id'] - volume_provider_list = list(map(get_pid, result_volumes_model_update)) - self.assertListEqual(volume_provider_list, ['sid1', 'sid2']) - - def test_create_consistencygroup_from_cgs(self): - self.snapshots[0]['provider_id'] = fake.PROVIDER_ID - self.snapshots[1]['provider_id'] = fake.PROVIDER2_ID - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result_model_update, result_volumes_model_update = ( - self.driver.create_consistencygroup_from_src( - self.ctx, self.consistency_group, self.volumes2, - cgsnapshot=self._fake_cgsnapshot(), - snapshots=self.snapshots)) - self.assertEqual('available', result_model_update['status']) - get_pid = lambda snapshot: snapshot['provider_id'] - volume_provider_list = list(map(get_pid, result_volumes_model_update)) - self.assertListEqual(['sid1', 'sid2'], volume_provider_list) - - @mock.patch('cinder.objects.snapshot') - @mock.patch('cinder.objects.snapshot') - def test_create_cgsnapshots(self, snapshot1, snapshot2): - type(snapshot1).volume = mock.PropertyMock( - return_value=self.volumes[0]) - type(snapshot2).volume = mock.PropertyMock( - return_value=self.volumes[1]) - snapshots = [snapshot1, snapshot2] - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result_model_update, result_snapshot_model_update = ( - self.driver.create_cgsnapshot( - self.ctx, - self._fake_cgsnapshot(), - snapshots - )) - self.assertEqual('available', result_model_update['status']) - self.assertTrue(all(snapshot['status'] == 'available' for snapshot in - result_snapshot_model_update)) - get_pid = lambda snapshot: snapshot['provider_id'] - snapshot_provider_list = list(map(get_pid, - result_snapshot_model_update)) - self.assertListEqual(['sid1', 'sid2'], snapshot_provider_list) - - @mock.patch('cinder.objects.snapshot') - @mock.patch('cinder.objects.snapshot') - def test_delete_cgsnapshots(self, snapshot1, snapshot2): - type(snapshot1).volume = mock.PropertyMock( - return_value=self.volumes[0]) - type(snapshot2).volume = mock.PropertyMock( - return_value=self.volumes[1]) - type(snapshot1).provider_id = mock.PropertyMock( - return_value=fake.PROVIDER_ID) - type(snapshot2).provider_id = mock.PropertyMock( - return_value=fake.PROVIDER2_ID) - snapshots = [snapshot1, snapshot2] - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result_model_update, result_snapshot_model_update = ( - self.driver.delete_cgsnapshot( - self.ctx, - self._fake_cgsnapshot(), - snapshots - )) - self.assertEqual('deleted', result_model_update['status']) - self.assertTrue(all(snapshot['status'] == 'deleted' for snapshot in - result_snapshot_model_update)) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_groups.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_groups.py new file mode 100644 index 000000000..2bdb666d3 --- /dev/null +++ b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_groups.py @@ -0,0 +1,279 @@ +# Copyright (C) 2017 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import mock + +from cinder import context +from cinder.objects import fields + +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_group +from cinder.tests.unit import fake_snapshot +from cinder.tests.unit import fake_volume +from cinder.tests.unit.volume.drivers.dell_emc import scaleio +from cinder.tests.unit.volume.drivers.dell_emc.scaleio import mocks + + +class TestGroups(scaleio.TestScaleIODriver): + """Test cases for ``ScaleIODriver groups support``""" + + def setUp(self): + """Setup a test case environment. + + Creates a fake volume object and sets up the required API responses. + """ + super(TestGroups, self).setUp() + self.ctx = context.RequestContext('fake', 'fake', auth_token=True) + self.fake_grp_snap = {'id': 'group_snap_id', + 'name': 'test_group_snapshot', + 'group_id': fake.GROUP_ID, + 'status': fields.GroupSnapshotStatus.AVAILABLE + } + self.group = ( + fake_group.fake_group_obj( + self.ctx, **{'id': fake.GROUP_ID})) + fake_volume1 = fake_volume.fake_volume_obj( + self.ctx, + **{'id': fake.VOLUME_ID, 'provider_id': fake.PROVIDER_ID}) + fake_volume2 = fake_volume.fake_volume_obj( + self.ctx, + **{'id': fake.VOLUME2_ID, 'provider_id': fake.PROVIDER2_ID}) + fake_volume3 = fake_volume.fake_volume_obj( + self.ctx, + **{'id': fake.VOLUME3_ID, 'provider_id': fake.PROVIDER3_ID}) + fake_volume4 = fake_volume.fake_volume_obj( + self.ctx, + **{'id': fake.VOLUME4_ID, 'provider_id': fake.PROVIDER4_ID}) + self.volumes = [fake_volume1, fake_volume2] + self.volumes2 = [fake_volume3, fake_volume4] + fake_snapshot1 = fake_snapshot.fake_snapshot_obj( + self.ctx, + **{'id': fake.SNAPSHOT_ID, 'volume_id': fake.VOLUME_ID, + 'volume': fake_volume1}) + fake_snapshot2 = fake_snapshot.fake_snapshot_obj( + self.ctx, + **{'id': fake.SNAPSHOT2_ID, 'volume_id': fake.VOLUME2_ID, 'volume': + fake_volume2}) + self.snapshots = [fake_snapshot1, fake_snapshot2] + self.snapshot_reply = json.dumps({ + 'volumeIdList': ['sid1', 'sid2'], + 'snapshotGroupId': 'sgid1'}) + self.HTTPS_MOCK_RESPONSES = { + self.RESPONSE_MODE.Valid: { + 'instances/Volume::{}/action/removeVolume'.format( + fake_volume1['provider_id'] + ): fake_volume1['provider_id'], + 'instances/Volume::{}/action/removeVolume'.format( + fake_volume2['provider_id'] + ): fake_volume2['provider_id'], + 'instances/Volume::{}/action/removeMappedSdc'.format( + fake_volume1['provider_id'] + ): fake_volume1['provider_id'], + 'instances/Volume::{}/action/removeMappedSdc'.format( + fake_volume2['provider_id'] + ): fake_volume2['provider_id'], + 'instances/System/action/snapshotVolumes': + self.snapshot_reply, + }, + self.RESPONSE_MODE.BadStatus: { + 'instances/Volume::{}/action/removeVolume'.format( + fake_volume1['provider_id'] + ): mocks.MockHTTPSResponse( + { + 'errorCode': 401, + 'message': 'BadStatus Volume Test', + }, 401 + ), + 'instances/Volume::{}/action/removeVolume'.format( + fake_volume2['provider_id'] + ): mocks.MockHTTPSResponse( + { + 'errorCode': 401, + 'message': 'BadStatus Volume Test', + }, 401 + ), + 'instances/System/action/snapshotVolumes': + self.BAD_STATUS_RESPONSE + }, + } + + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_group(self, is_group_a_cg_snapshot_type): + """Test group create. + + should throw NotImplementedError, is_group_a_cg_snapshot_type=False + otherwise returns status of 'available' + """ + is_group_a_cg_snapshot_type.side_effect = [False, True] + + self.assertRaises(NotImplementedError, + self.driver.create_group, self.ctx, self.group) + + model_update = self.driver.create_group(self.ctx, self.group) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) + + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_delete_group(self, is_group_a_cg_snapshot_type): + """Test group deletion. + + should throw NotImplementedError, is_group_a_cg_snapshot_type=False + otherwise returns status of 'deleted' + """ + is_group_a_cg_snapshot_type.side_effect = [False, True] + + self.assertRaises(NotImplementedError, + self.driver.delete_group, + self.ctx, self.group, self.volumes) + + model_update = self.driver.delete_group(self.ctx, + self.group, + self.volumes) + self.assertEqual(fields.GroupStatus.DELETED, + model_update[0]['status']) + + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_update_group(self, is_group_a_cg_snapshot_type): + """Test updating a group + + should throw NotImplementedError, is_group_a_cg_snapshot_type=False + otherwise returns 'None' for each of the updates + """ + is_group_a_cg_snapshot_type.side_effect = [False, True] + + self.assertRaises(NotImplementedError, + self.driver.update_group, self.ctx, self.group) + + mod_up, add_up, remove_up = self.driver.update_group(self.ctx, + self.group) + self.assertIsNone(mod_up) + self.assertIsNone(add_up) + self.assertIsNone(remove_up) + + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_group_from_src_group(self, is_group_a_cg_snapshot_type): + """Test creating group from source group + + should throw NotImplementedError, is_group_a_cg_snapshot_type=False + otherwise returns list of volumes in 'available' state + """ + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + + is_group_a_cg_snapshot_type.side_effect = [False, True] + + self.assertRaises(NotImplementedError, + self.driver.create_group_from_src, + self.ctx, self.group, self.volumes, + source_group=self.group, source_vols=self.volumes) + + result_model_update, result_volumes_model_update = ( + self.driver.create_group_from_src( + self.ctx, self.group, self.volumes, + source_group=self.group, source_vols=self.volumes)) + self.assertEqual(fields.GroupStatus.AVAILABLE, + result_model_update['status']) + get_pid = lambda snapshot: snapshot['provider_id'] + volume_provider_list = list(map(get_pid, result_volumes_model_update)) + self.assertListEqual(volume_provider_list, ['sid1', 'sid2']) + + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_group_from_src_snapshot(self, is_group_a_cg_snapshot_type): + """Test creating group from snapshot + + """ + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + is_group_a_cg_snapshot_type.side_effect = [False, True] + + self.assertRaises(NotImplementedError, + self.driver.create_group_from_src, + self.ctx, self.group, self.volumes, + group_snapshot=self.fake_grp_snap, + snapshots=self.snapshots) + + result_model_update, result_volumes_model_update = ( + self.driver.create_group_from_src( + self.ctx, self.group, self.volumes, + group_snapshot=self.fake_grp_snap, + snapshots=self.snapshots)) + self.assertEqual(fields.GroupStatus.AVAILABLE, + result_model_update['status']) + get_pid = lambda snapshot: snapshot['provider_id'] + volume_provider_list = list(map(get_pid, result_volumes_model_update)) + self.assertListEqual(volume_provider_list, ['sid1', 'sid2']) + + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_delete_group_snapshot(self, is_group_a_cg_snapshot_type): + """Test deleting group snapshot + + should throw NotImplementedError, is_group_a_cg_snapshot_type=False + otherwise returns model updates + """ + is_group_a_cg_snapshot_type.side_effect = [False, True] + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + + self.snapshots[0].volume = self.volumes[0] + self.snapshots[1].volume = self.volumes[1] + self.snapshots[0].provider_id = fake.PROVIDER_ID + self.snapshots[1].provider_id = fake.PROVIDER2_ID + + self.assertRaises(NotImplementedError, + self.driver.delete_group_snapshot, + self.ctx, + self.group, + self.snapshots) + + result_model_update, result_snapshot_model_update = ( + self.driver.delete_group_snapshot( + self.ctx, + self.group, + self.snapshots + )) + self.assertEqual(fields.GroupSnapshotStatus.DELETED, + result_model_update['status']) + self.assertTrue(all(snapshot['status'] == 'deleted' for snapshot in + result_snapshot_model_update)) + + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_group_snapshot(self, is_group_a_cg_snapshot_type): + """Test creating group snapshot + + should throw NotImplementedError, is_group_a_cg_snapshot_type=False + otherwise returns model updates + """ + is_group_a_cg_snapshot_type.side_effect = [False, True] + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + + self.assertRaises(NotImplementedError, + self.driver.create_group_snapshot, + self.ctx, + self.group, + self.snapshots) + + result_model_update, result_snapshot_model_update = ( + self.driver.create_group_snapshot( + self.ctx, + self.group, + self.snapshots + )) + self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE, + result_model_update['status']) + self.assertTrue(all(snapshot['status'] == 'available' for snapshot in + result_snapshot_model_update)) + get_pid = lambda snapshot: snapshot['provider_id'] + snapshot_provider_list = list(map(get_pid, + result_snapshot_model_update)) + + self.assertListEqual(['sid1', 'sid2'], snapshot_provider_list) diff --git a/cinder/volume/drivers/dell_emc/scaleio/driver.py b/cinder/volume/drivers/dell_emc/scaleio/driver.py index d24eaa1ec..89fe4482b 100644 --- a/cinder/volume/drivers/dell_emc/scaleio/driver.py +++ b/cinder/volume/drivers/dell_emc/scaleio/driver.py @@ -36,9 +36,12 @@ from cinder.i18n import _ from cinder.image import image_utils from cinder import interface from cinder import utils + +from cinder.objects import fields from cinder.volume import driver from cinder.volume.drivers.san import san from cinder.volume import qos_specs +from cinder.volume import utils as volume_utils from cinder.volume import volume_types CONF = cfg.CONF @@ -114,9 +117,10 @@ SIO_MAX_OVERSUBSCRIPTION_RATIO = 10.0 class ScaleIODriver(driver.VolumeDriver): """Dell EMC ScaleIO Driver.""" - VERSION = "2.0.1" + VERSION = "2.0.2" # Major changes # 2.0.1: Added support for SIO 1.3x in addition to 2.0.x + # 2.0.2: Added consistency group support to generic volume groups # ThirdPartySystems wiki CI_WIKI_NAME = "EMC_ScaleIO_CI" @@ -826,7 +830,7 @@ class ScaleIODriver(driver.VolumeDriver): stats['storage_protocol'] = 'scaleio' stats['reserved_percentage'] = 0 stats['QoS_support'] = True - stats['consistencygroup_support'] = True + stats['consistent_group_snapshot_enabled'] = True stats['thick_provisioning_support'] = True stats['thin_provisioning_support'] = True pools = [] @@ -951,7 +955,7 @@ class ScaleIODriver(driver.VolumeDriver): 'total_capacity_gb': total_capacity_gb, 'free_capacity_gb': free_capacity_gb, 'QoS_support': True, - 'consistencygroup_support': True, + 'consistent_group_snapshot_enabled': True, 'reserved_percentage': 0, 'thin_provisioning_support': True, 'thick_provisioning_support': True, @@ -1251,24 +1255,44 @@ class ScaleIODriver(driver.VolumeDriver): reason=reason ) - def create_consistencygroup(self, context, group): - """Creates a consistency group. + def create_group(self, context, group): + """Creates a group. + + :param context: the context of the caller. + :param group: the group object. + :returns: model_update ScaleIO won't create CG until cg-snapshot creation, db will maintain the volumes and CG relationship. """ - LOG.info("Creating Consistency Group") - model_update = {'status': 'available'} + + # let generic volume group support handle non-cgsnapshots + if not volume_utils.is_group_a_cg_snapshot_type(group): + raise NotImplementedError() + + LOG.info("Creating Group") + model_update = {'status': fields.GroupStatus.AVAILABLE} return model_update - def delete_consistencygroup(self, context, group, volumes): - """Deletes a consistency group. + 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 ScaleIO will delete the volumes of the CG. """ - LOG.info("Deleting Consistency Group") - model_update = {'status': 'deleted'} - error_statuses = ['error', 'error_deleting'] + + # let generic volume group support handle non-cgsnapshots + if not volume_utils.is_group_a_cg_snapshot_type(group): + raise NotImplementedError() + + LOG.info("Deleting Group") + model_update = {'status': fields.GroupStatus.DELETED} + error_statuses = [fields.GroupStatus.ERROR, + fields.GroupStatus.ERROR_DELETING] volumes_model_update = [] for volume in volumes: try: @@ -1282,13 +1306,24 @@ class ScaleIODriver(driver.VolumeDriver): volumes_model_update.append(update_item) if model_update['status'] not in error_statuses: model_update['status'] = 'error_deleting' - LOG.error("Failed to delete the volume %(vol)s of CG. " + LOG.error("Failed to delete the volume %(vol)s of group. " "Exception: %(exception)s.", {'vol': volume['name'], 'exception': err}) return model_update, volumes_model_update - def create_cgsnapshot(self, context, cgsnapshot, snapshots): - """Creates a cgsnapshot.""" + 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 + """ + + # let generic volume group support handle non-cgsnapshots + if not volume_utils.is_group_a_cg_snapshot_type(group_snapshot): + raise NotImplementedError() + get_scaleio_snapshot_params = lambda snapshot: { 'volumeId': snapshot.volume['provider_id'], 'snapshotName': self._id_to_base64(snapshot['id'])} @@ -1304,46 +1339,74 @@ class ScaleIODriver(driver.VolumeDriver): snapshot_model_update = [] for snapshot, scaleio_id in zip(snapshots, response['volumeIdList']): update_item = {'id': snapshot['id'], - 'status': 'available', + 'status': fields.SnapshotStatus.AVAILABLE, 'provider_id': scaleio_id} snapshot_model_update.append(update_item) - model_update = {'status': 'available'} + model_update = {'status': fields.GroupStatus.AVAILABLE} return model_update, snapshot_model_update - def delete_cgsnapshot(self, context, cgsnapshot, snapshots): - """Deletes a cgsnapshot.""" - error_statuses = ['error', 'error_deleting'] - model_update = {'status': cgsnapshot['status']} + def delete_group_snapshot(self, context, group_snapshot, snapshots): + """Deletes a 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 + """ + + # let generic volume group support handle non-cgsnapshots + if not volume_utils.is_group_a_cg_snapshot_type(group_snapshot): + raise NotImplementedError() + + error_statuses = [fields.SnapshotStatus.ERROR, + fields.SnapshotStatus.ERROR_DELETING] + model_update = {'status': group_snapshot['status']} snapshot_model_update = [] for snapshot in snapshots: try: self._delete_volume(snapshot.provider_id) update_item = {'id': snapshot['id'], - 'status': 'deleted'} + 'status': fields.SnapshotStatus.DELETED} snapshot_model_update.append(update_item) except exception.VolumeBackendAPIException as err: update_item = {'id': snapshot['id'], - 'status': 'error_deleting'} + 'status': fields.SnapshotStatus.ERROR_DELETING} snapshot_model_update.append(update_item) if model_update['status'] not in error_statuses: - model_update['status'] = 'error_deleting' + model_update['status'] = ( + fields.SnapshotStatus.ERROR_DELETING) LOG.error("Failed to delete the snapshot %(snap)s " - "of cgsnapshot: %(cgsnapshot_id)s. " + "of snapshot: %(snapshot_id)s. " "Exception: %(exception)s.", {'snap': snapshot['name'], 'exception': err, - 'cgsnapshot_id': cgsnapshot.id}) - model_update['status'] = 'deleted' + 'snapshot_id': group_snapshot.id}) + model_update['status'] = fields.GroupSnapshotStatus.DELETED return model_update, snapshot_model_update - def create_consistencygroup_from_src(self, context, group, volumes, - cgsnapshot=None, snapshots=None, - source_cg=None, source_vols=None): - """Creates a consistency group from a source.""" + 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 + """ + + # let generic volume group support handle non-cgsnapshots + if not volume_utils.is_group_a_cg_snapshot_type(group): + raise NotImplementedError() + get_scaleio_snapshot_params = lambda src_volume, trg_volume: { 'volumeId': src_volume['provider_id'], 'snapshotName': self._id_to_base64(trg_volume['id'])} - if cgsnapshot and snapshots: + if group_snapshot and snapshots: snapshot_defs = map(get_scaleio_snapshot_params, snapshots, volumes) @@ -1365,17 +1428,29 @@ class ScaleIODriver(driver.VolumeDriver): 'status': 'available', 'provider_id': scaleio_id} volumes_model_update.append(update_item) - model_update = {'status': 'available'} + model_update = {'status': fields.GroupStatus.AVAILABLE} return model_update, volumes_model_update - def update_consistencygroup(self, context, group, - add_volumes=None, remove_volumes=None): - """Update a consistency group. + def update_group(self, context, group, + add_volumes=None, remove_volumes=None): + """Update a group. + + :param context: the context of the caller. + :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 ScaleIO does not handle volume grouping. Cinder maintains volumes and CG relationship. """ - return None, None, None + + if volume_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 _snapshot_volume_group(self, snapshot_defs): LOG.info("ScaleIO snapshot group of volumes") diff --git a/releasenotes/notes/scaleio-generic-volume-group-ee36e4dba8893422.yaml b/releasenotes/notes/scaleio-generic-volume-group-ee36e4dba8893422.yaml new file mode 100644 index 000000000..ab0d9e6c2 --- /dev/null +++ b/releasenotes/notes/scaleio-generic-volume-group-ee36e4dba8893422.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added consistency group support to generic volume groups in ScaleIO Driver. \ No newline at end of file