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
This commit is contained in:
Eric Young 2017-04-02 09:54:00 -04:00
parent 641864b662
commit fcbd762d9d
4 changed files with 394 additions and 248 deletions

View File

@ -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))

View File

@ -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)

View File

@ -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")

View File

@ -0,0 +1,3 @@
---
features:
- Added consistency group support to generic volume groups in ScaleIO Driver.