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.image import image_utils
from cinder import interface from cinder import interface
from cinder import utils from cinder import utils
from cinder.objects import fields
from cinder.volume import driver from cinder.volume import driver
from cinder.volume.drivers.san import san from cinder.volume.drivers.san import san
from cinder.volume import qos_specs from cinder.volume import qos_specs
from cinder.volume import utils as volume_utils
from cinder.volume import volume_types from cinder.volume import volume_types
CONF = cfg.CONF CONF = cfg.CONF
@ -114,9 +117,10 @@ SIO_MAX_OVERSUBSCRIPTION_RATIO = 10.0
class ScaleIODriver(driver.VolumeDriver): class ScaleIODriver(driver.VolumeDriver):
"""Dell EMC ScaleIO Driver.""" """Dell EMC ScaleIO Driver."""
VERSION = "2.0.1" VERSION = "2.0.2"
# Major changes # Major changes
# 2.0.1: Added support for SIO 1.3x in addition to 2.0.x # 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 # ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_ScaleIO_CI" CI_WIKI_NAME = "EMC_ScaleIO_CI"
@ -826,7 +830,7 @@ class ScaleIODriver(driver.VolumeDriver):
stats['storage_protocol'] = 'scaleio' stats['storage_protocol'] = 'scaleio'
stats['reserved_percentage'] = 0 stats['reserved_percentage'] = 0
stats['QoS_support'] = True stats['QoS_support'] = True
stats['consistencygroup_support'] = True stats['consistent_group_snapshot_enabled'] = True
stats['thick_provisioning_support'] = True stats['thick_provisioning_support'] = True
stats['thin_provisioning_support'] = True stats['thin_provisioning_support'] = True
pools = [] pools = []
@ -951,7 +955,7 @@ class ScaleIODriver(driver.VolumeDriver):
'total_capacity_gb': total_capacity_gb, 'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb, 'free_capacity_gb': free_capacity_gb,
'QoS_support': True, 'QoS_support': True,
'consistencygroup_support': True, 'consistent_group_snapshot_enabled': True,
'reserved_percentage': 0, 'reserved_percentage': 0,
'thin_provisioning_support': True, 'thin_provisioning_support': True,
'thick_provisioning_support': True, 'thick_provisioning_support': True,
@ -1251,24 +1255,44 @@ class ScaleIODriver(driver.VolumeDriver):
reason=reason reason=reason
) )
def create_consistencygroup(self, context, group): def create_group(self, context, group):
"""Creates a consistency 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, ScaleIO won't create CG until cg-snapshot creation,
db will maintain the volumes and CG relationship. 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 return model_update
def delete_consistencygroup(self, context, group, volumes): def delete_group(self, context, group, volumes):
"""Deletes a consistency group. """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. ScaleIO will delete the volumes of the CG.
""" """
LOG.info("Deleting Consistency Group")
model_update = {'status': 'deleted'} # let generic volume group support handle non-cgsnapshots
error_statuses = ['error', 'error_deleting'] 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 = [] volumes_model_update = []
for volume in volumes: for volume in volumes:
try: try:
@ -1282,13 +1306,24 @@ class ScaleIODriver(driver.VolumeDriver):
volumes_model_update.append(update_item) volumes_model_update.append(update_item)
if model_update['status'] not in error_statuses: if model_update['status'] not in error_statuses:
model_update['status'] = 'error_deleting' 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.", "Exception: %(exception)s.",
{'vol': volume['name'], 'exception': err}) {'vol': volume['name'], 'exception': err})
return model_update, volumes_model_update return model_update, volumes_model_update
def create_cgsnapshot(self, context, cgsnapshot, snapshots): def create_group_snapshot(self, context, group_snapshot, snapshots):
"""Creates a cgsnapshot.""" """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: { get_scaleio_snapshot_params = lambda snapshot: {
'volumeId': snapshot.volume['provider_id'], 'volumeId': snapshot.volume['provider_id'],
'snapshotName': self._id_to_base64(snapshot['id'])} 'snapshotName': self._id_to_base64(snapshot['id'])}
@ -1304,46 +1339,74 @@ class ScaleIODriver(driver.VolumeDriver):
snapshot_model_update = [] snapshot_model_update = []
for snapshot, scaleio_id in zip(snapshots, response['volumeIdList']): for snapshot, scaleio_id in zip(snapshots, response['volumeIdList']):
update_item = {'id': snapshot['id'], update_item = {'id': snapshot['id'],
'status': 'available', 'status': fields.SnapshotStatus.AVAILABLE,
'provider_id': scaleio_id} 'provider_id': scaleio_id}
snapshot_model_update.append(update_item) snapshot_model_update.append(update_item)
model_update = {'status': 'available'} model_update = {'status': fields.GroupStatus.AVAILABLE}
return model_update, snapshot_model_update return model_update, snapshot_model_update
def delete_cgsnapshot(self, context, cgsnapshot, snapshots): def delete_group_snapshot(self, context, group_snapshot, snapshots):
"""Deletes a cgsnapshot.""" """Deletes a snapshot.
error_statuses = ['error', 'error_deleting']
model_update = {'status': cgsnapshot['status']} :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 = [] snapshot_model_update = []
for snapshot in snapshots: for snapshot in snapshots:
try: try:
self._delete_volume(snapshot.provider_id) self._delete_volume(snapshot.provider_id)
update_item = {'id': snapshot['id'], update_item = {'id': snapshot['id'],
'status': 'deleted'} 'status': fields.SnapshotStatus.DELETED}
snapshot_model_update.append(update_item) snapshot_model_update.append(update_item)
except exception.VolumeBackendAPIException as err: except exception.VolumeBackendAPIException as err:
update_item = {'id': snapshot['id'], update_item = {'id': snapshot['id'],
'status': 'error_deleting'} 'status': fields.SnapshotStatus.ERROR_DELETING}
snapshot_model_update.append(update_item) snapshot_model_update.append(update_item)
if model_update['status'] not in error_statuses: 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 " LOG.error("Failed to delete the snapshot %(snap)s "
"of cgsnapshot: %(cgsnapshot_id)s. " "of snapshot: %(snapshot_id)s. "
"Exception: %(exception)s.", "Exception: %(exception)s.",
{'snap': snapshot['name'], {'snap': snapshot['name'],
'exception': err, 'exception': err,
'cgsnapshot_id': cgsnapshot.id}) 'snapshot_id': group_snapshot.id})
model_update['status'] = 'deleted' model_update['status'] = fields.GroupSnapshotStatus.DELETED
return model_update, snapshot_model_update return model_update, snapshot_model_update
def create_consistencygroup_from_src(self, context, group, volumes, def create_group_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None, group_snapshot=None, snapshots=None,
source_cg=None, source_vols=None): source_group=None, source_vols=None):
"""Creates a consistency group from a source.""" """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: { get_scaleio_snapshot_params = lambda src_volume, trg_volume: {
'volumeId': src_volume['provider_id'], 'volumeId': src_volume['provider_id'],
'snapshotName': self._id_to_base64(trg_volume['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, snapshot_defs = map(get_scaleio_snapshot_params,
snapshots, snapshots,
volumes) volumes)
@ -1365,17 +1428,29 @@ class ScaleIODriver(driver.VolumeDriver):
'status': 'available', 'status': 'available',
'provider_id': scaleio_id} 'provider_id': scaleio_id}
volumes_model_update.append(update_item) volumes_model_update.append(update_item)
model_update = {'status': 'available'} model_update = {'status': fields.GroupStatus.AVAILABLE}
return model_update, volumes_model_update return model_update, volumes_model_update
def update_consistencygroup(self, context, group, def update_group(self, context, group,
add_volumes=None, remove_volumes=None): add_volumes=None, remove_volumes=None):
"""Update a consistency group. """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. ScaleIO does not handle volume grouping.
Cinder maintains volumes and CG relationship. 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): def _snapshot_volume_group(self, snapshot_defs):
LOG.info("ScaleIO snapshot group of volumes") 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.