From: Shay Halsband Date: Thu, 21 May 2015 18:34:18 +0000 (+0300) Subject: XtremIO volume driver consistency group support X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=60200a848b3537780e4c58d25e22013f45931e03;p=openstack-build%2Fcinder-build.git XtremIO volume driver consistency group support - Adding consistency group support using XtremIO REST API V2. - Implements all OpenStack CG volume driver interfaces using XtremIO CG capabilities. Partial-Implements: blueprint emc-xtremio-updates Change-Id: I2ddf629b0c97a849a2c95ca4a8b7a4dd4adccbec --- diff --git a/cinder/tests/unit/test_emc_xtremio.py b/cinder/tests/unit/test_emc_xtremio.py index 9c6a00c70..13f0b23a6 100644 --- a/cinder/tests/unit/test_emc_xtremio.py +++ b/cinder/tests/unit/test_emc_xtremio.py @@ -13,11 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. - import mock from cinder import exception from cinder import test +from cinder.tests.unit import fake_snapshot from cinder.volume.drivers.emc import xtremio @@ -25,55 +25,67 @@ typ2id = {'volumes': 'vol-id', 'snapshots': 'vol-id', 'initiators': 'initiator-id', 'initiator-groups': 'ig-id', - 'lun-maps': 'mapping-id'} + 'lun-maps': 'mapping-id', + 'consistency-groups': 'cg-id', + 'consistency-group-volumes': 'cg-vol-id', + } xms_data = {'xms': {1: {'version': '4.0.0'}}, - 'clusters': {'cluster1': - {'name': 'cluster1', - 'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73", - 'ud-ssd-space': '8146708710', - 'ud-ssd-space-in-use': '708710', - 'vol-size': '29884416', - 'chap-authentication-mode': 'disabled', - 'chap-discovery-mode': 'disabled', - "index": 1}, - 1: {'name': 'cluster1', - 'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73", + 'clusters': {1: {'name': 'brick1', + 'sys-sw-version': "4.0.0-devel_ba23ee5381eeab73", 'ud-ssd-space': '8146708710', 'ud-ssd-space-in-use': '708710', 'vol-size': '29884416', 'chap-authentication-mode': 'disabled', 'chap-discovery-mode': 'disabled', - "index": 1}}, - 'target-groups': {'Default': {"index": 1, }}, + "index": 1, + }, + }, + 'target-groups': {'Default': {"index": 1, }, + }, 'iscsi-portals': {'10.205.68.5/16': {"port-address": "iqn.2008-05.com.xtremio:001e67939c34", "ip-port": 3260, "ip-addr": "10.205.68.5/16", "name": "10.205.68.5/16", - "index": 1}}, + "index": 1, + }, + }, 'targets': {'X1-SC2-fc1': {'index': 1, "name": "X1-SC2-fc1", "port-address": "21:00:00:24:ff:57:b2:36", - 'port-state': 'up'}, + 'port-state': 'up', + }, 'X1-SC2-fc2': {'index': 2, "name": "X1-SC2-fc2", "port-address": "21:00:00:24:ff:57:b2:55", - 'port-state': 'up'} + 'port-state': 'up', + } }, 'volumes': {}, 'initiator-groups': {}, 'initiators': {}, 'lun-maps': {}, + 'consistency-groups': {}, + 'consistency-group-volumes': {}, } +def get_xms_obj_by_name(typ, name): + for item in xms_data[typ].values(): + if 'name' in item and item['name'] == name: + return item + raise exception.NotFound() + + def clean_xms_data(): xms_data['volumes'] = {} xms_data['initiator-groups'] = {} xms_data['initiators'] = {} xms_data['lun-maps'] = {} + xms_data['consistency-group-volumes'] = {} + xms_data['consistency-groups'] = {} def fix_data(data, object_type): @@ -88,7 +100,7 @@ def fix_data(data, object_type): d[typ2id[object_type]] = ["a91e8c81c2d14ae4865187ce4f866f8a", d.get('name'), - len(xms_data[object_type]) + 1] + len(xms_data.get(object_type, [])) + 1] d['index'] = len(xms_data[object_type]) + 1 return d @@ -99,24 +111,30 @@ def get_xms_obj_key(data): return key +def get_obj(typ, name, idx): + if name: + return {"content": get_xms_obj_by_name(typ, name)} + elif idx: + if idx not in xms_data.get(typ, {}): + raise exception.NotFound() + return {"content": xms_data[typ][idx]} + + def xms_request(object_type='volumes', request_typ='GET', data=None, - name=None, idx=None): + name=None, idx=None, ver='v1'): if object_type == 'snapshots': object_type = 'volumes' - obj_key = name if name else idx + try: + res = xms_data[object_type] + except KeyError: + raise exception.VolumeDriverException if request_typ == 'GET': - try: - res = xms_data[object_type] - except KeyError: - raise exception.VolumeDriverException if name or idx: - if obj_key not in res: - raise exception.NotFound() - return {"content": res[obj_key]} + return get_obj(object_type, name, idx) else: if data and data.get('full') == 1: - return {object_type: res.values()} + return {object_type: list(res.values())} else: return {object_type: [{"href": "/%s/%d" % (object_type, obj['index']), @@ -124,41 +142,45 @@ def xms_request(object_type='volumes', request_typ='GET', data=None, for obj in res.values()]} elif request_typ == 'POST': data = fix_data(data, object_type) + name_key = get_xms_obj_key(data) + try: + if name_key and get_xms_obj_by_name(object_type, data[name_key]): + raise (exception + .VolumeBackendAPIException + ('Volume by this name already exists')) + except exception.NotFound: + pass data['index'] = len(xms_data[object_type]) + 1 xms_data[object_type][data['index']] = data # find the name key - name_key = get_xms_obj_key(data) + if name_key: + data['name'] = data[name_key] if object_type == 'lun-maps': data['ig-name'] = data['ig-id'] - if name_key: - if data[name_key] in xms_data[object_type]: - raise (exception - .VolumeBackendAPIException - ('Volume by this name already exists')) - xms_data[object_type][data[name_key]] = data return {"links": [{"href": "/%s/%d" % (object_type, data[typ2id[object_type]][2])}]} elif request_typ == 'DELETE': - if obj_key in xms_data[object_type]: - data = xms_data[object_type][obj_key] + if object_type == 'consistency-group-volumes': + data = [cgv for cgv in + xms_data['consistency-group-volumes'].values() + if cgv['vol-id'] == data['vol-id'] + and cgv['cg-id'] == data['cg-id']][0] + else: + data = get_obj(object_type, name, idx)['content'] + if data: del xms_data[object_type][data['index']] - del xms_data[object_type][data[typ2id[object_type]][1]] else: raise exception.NotFound() elif request_typ == 'PUT': - if obj_key in xms_data[object_type]: - obj = xms_data[object_type][obj_key] - obj.update(data) - key = get_xms_obj_key(data) - if key: - xms_data[object_type][data[key]] = obj - else: - raise exception.NotFound() + obj = get_obj(object_type, name, idx)['content'] + data = fix_data(data, object_type) + del data['index'] + obj.update(data) def xms_bad_request(object_type='volumes', request_typ='GET', data=None, - name=None, idx=None): + name=None, idx=None, ver='v1'): if request_typ == 'GET': raise exception.NotFound() elif request_typ == 'POST': @@ -167,7 +189,7 @@ def xms_bad_request(object_type='volumes', request_typ='GET', data=None, def xms_failed_rename_snapshot_request(object_type='volumes', request_typ='GET', data=None, - name=None, idx=None): + name=None, idx=None, ver='v1'): if request_typ == 'POST': xms_data['volumes'][27] = {} return { @@ -176,7 +198,7 @@ def xms_failed_rename_snapshot_request(object_type='volumes', "href": "https://host/api/json/v2/types/snapshots/27", "rel": "self"}]} elif request_typ == 'PUT': - raise exception.VolumeBackendAPIException(msg='Failed to delete') + raise exception.VolumeBackendAPIException(data='Failed to delete') elif request_typ == 'DELETE': del xms_data['volumes'][27] @@ -192,7 +214,8 @@ class CommonData(object): 'initiator': 'iqn.1993-08.org.debian:01:222', 'wwpns': ["123456789012345", "123456789054321"], 'wwnns': ["223456789012345", "223456789054321"], - 'host': 'fakehost'} + 'host': 'fakehost', + } test_volume = {'name': 'vol1', 'size': 1, @@ -202,14 +225,20 @@ class CommonData(object): 'project_id': 'project', 'display_name': 'vol1', 'display_description': 'test volume', - 'volume_type_id': None} + 'volume_type_id': None, + 'consistencygroup_id': + '192eb39b-6c2f-420c-bae3-3cfd117f0345', + } test_snapshot = D() test_snapshot.update({'name': 'snapshot1', 'size': 1, 'id': '192eb39b-6c2f-420c-bae3-3cfd117f0002', 'volume_name': 'vol-vol1', 'volume_id': '192eb39b-6c2f-420c-bae3-3cfd117f0001', - 'project_id': 'project'}) + 'project_id': 'project', + 'consistencygroup_id': + '192eb39b-6c2f-420c-bae3-3cfd117f0345', + }) test_snapshot.__dict__.update(test_snapshot) test_volume2 = {'name': 'vol2', 'size': 1, @@ -219,7 +248,10 @@ class CommonData(object): 'project_id': 'project', 'display_name': 'vol2', 'display_description': 'test volume 2', - 'volume_type_id': None} + 'volume_type_id': None, + 'consistencygroup_id': + '192eb39b-6c2f-420c-bae3-3cfd117f0345', + } test_clone = {'name': 'clone1', 'size': 1, 'volume_name': 'vol3', @@ -228,30 +260,49 @@ class CommonData(object): 'project_id': 'project', 'display_name': 'clone1', 'display_description': 'volume created from snapshot', - 'volume_type_id': None} + 'volume_type_id': None, + 'consistencygroup_id': + '192eb39b-6c2f-420c-bae3-3cfd117f0345', + } unmanaged1 = {'id': 'unmanaged1', 'name': 'unmanaged1', - 'size': 3} + 'size': 3, + } + context = {'user': 'admin', } + group = {'id': '192eb39b-6c2f-420c-bae3-3cfd117f0345', + 'name': 'cg1', + 'status': 'OK', + } + cgsnapshot = mock.Mock(id='192eb39b-6c2f-420c-bae3-3cfd117f9876', + consistencygroup_id=group['id']) + + def cgsnap_getitem(self, val): + return self.__dict__[val] + + cgsnapshot.__getitem__ = cgsnap_getitem @mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOClient.req') class EMCXIODriverISCSITestCase(test.TestCase): def setUp(self): super(EMCXIODriverISCSITestCase, self).setUp() + clean_xms_data() - configuration = mock.Mock() - configuration.san_login = '' - configuration.san_password = '' - configuration.san_ip = '' - configuration.xtremio_cluster_name = '' - configuration.xtremio_provisioning_factor = 20.0 + config = mock.Mock() + config.san_login = '' + config.san_password = '' + config.san_ip = '' + config.xtremio_cluster_name = 'brick1' + config.xtremio_provisioning_factor = 20.0 def safe_get(key): - getattr(configuration, key) - - configuration.safe_get = safe_get - self.driver = xtremio.XtremIOISCSIDriver(configuration=configuration) + getattr(config, key) + config.safe_get = safe_get + self.driver = xtremio.XtremIOISCSIDriver(configuration=config) + self.driver.client = xtremio.XtremIOClient4(config, + config + .xtremio_cluster_name) self.data = CommonData() def test_check_for_setup_error(self, req): @@ -265,30 +316,28 @@ class EMCXIODriverISCSITestCase(test.TestCase): def test_create_extend_delete_volume(self, req): req.side_effect = xms_request - clean_xms_data() self.driver.create_volume(self.data.test_volume) self.driver.extend_volume(self.data.test_volume, 5) self.driver.delete_volume(self.data.test_volume) def test_create_delete_snapshot(self, req): req.side_effect = xms_request - clean_xms_data() self.driver.create_volume(self.data.test_volume) self.driver.create_snapshot(self.data.test_snapshot) self.assertEqual(self.data.test_snapshot['id'], - xms_data['volumes'][3]['name']) + xms_data['volumes'][2]['name']) self.driver.delete_snapshot(self.data.test_snapshot) self.driver.delete_volume(self.data.test_volume) def test_failed_rename_snapshot(self, req): req.side_effect = xms_failed_rename_snapshot_request - self.driver.create_snapshot(self.data.test_snapshot) - self.assertIn(27, xms_data['volumes']) - clean_xms_data() + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_snapshot, + self.data.test_snapshot) + self.assertEqual(0, len(xms_data['volumes'])) def test_volume_from_snapshot(self, req): req.side_effect = xms_request - clean_xms_data() xms_data['volumes'] = {} self.driver.create_volume(self.data.test_volume) self.driver.create_snapshot(self.data.test_snapshot) @@ -300,7 +349,6 @@ class EMCXIODriverISCSITestCase(test.TestCase): def test_clone_volume(self, req): req.side_effect = xms_request - clean_xms_data() self.driver.create_volume(self.data.test_volume) self.driver.create_cloned_volume(self.data.test_clone, self.data.test_volume) @@ -309,7 +357,6 @@ class EMCXIODriverISCSITestCase(test.TestCase): def test_duplicate_volume(self, req): req.side_effect = xms_request - clean_xms_data() self.driver.create_volume(self.data.test_volume) self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume, self.data.test_volume) @@ -317,7 +364,6 @@ class EMCXIODriverISCSITestCase(test.TestCase): def test_no_portals_configured(self, req): req.side_effect = xms_request - clean_xms_data() portals = xms_data['iscsi-portals'].copy() xms_data['iscsi-portals'].clear() lunmap = {'lun': 4} @@ -327,7 +373,6 @@ class EMCXIODriverISCSITestCase(test.TestCase): def test_initialize_terminate_connection(self, req): req.side_effect = xms_request - clean_xms_data() self.driver.create_volume(self.data.test_volume) map_data = self.driver.initialize_connection(self.data.test_volume, self.data.connector) @@ -337,7 +382,6 @@ class EMCXIODriverISCSITestCase(test.TestCase): def test_initialize_connection_bad_ig(self, req): req.side_effect = xms_bad_request - clean_xms_data() self.assertRaises(exception.VolumeBackendAPIException, self.driver.initialize_connection, self.data.test_volume, @@ -346,17 +390,17 @@ class EMCXIODriverISCSITestCase(test.TestCase): def test_get_stats(self, req): req.side_effect = xms_request - clean_xms_data() stats = self.driver.get_volume_stats(True) self.assertEqual(stats['volume_backend_name'], self.driver.backend_name) def test_manage_unmanage(self, req): req.side_effect = xms_request - clean_xms_data() - xms_data['volumes'] = {'unmanaged1': {'vol-name': 'unmanaged1', - 'index': 'unmanaged1', - 'vol-size': '3'}} + xms_data['volumes'] = {1: {'name': 'unmanaged1', + 'index': 1, + 'vol-size': '3', + }, + } ref_vol = {"source-name": "unmanaged1"} invalid_ref = {"source-name": "invalid"} self.assertRaises(exception.ManageExistingInvalidReference, @@ -371,26 +415,60 @@ class EMCXIODriverISCSITestCase(test.TestCase): self.data.test_volume2) self.driver.unmanage(self.data.test_volume) + @mock.patch('cinder.objects.snapshot.SnapshotList.get_all_for_cgsnapshot') + def test_cg_operations(self, get_all_for_cgsnapshot, req): + req.side_effect = xms_request + d = self.data + snapshot_obj = fake_snapshot.fake_snapshot_obj(d.context) + snapshot_obj.consistencygroup_id = d.group['id'] + get_all_for_cgsnapshot.return_value = [snapshot_obj] + + self.driver.create_consistencygroup(d.context, d.group) + self.assertEqual(1, len(xms_data['consistency-groups'])) + self.driver.update_consistencygroup(d.context, d.group, + add_volumes=[d.test_volume, + d.test_volume2]) + self.assertEqual(2, len(xms_data['consistency-group-volumes'])) + self.driver.update_consistencygroup(d.context, d.group, + remove_volumes=[d.test_volume2]) + self.assertEqual(1, len(xms_data['consistency-group-volumes'])) + self.driver.db = mock.Mock() + (self.driver.db. + volume_get_all_by_group.return_value) = [mock.MagicMock()] + self.driver.create_cgsnapshot(d.context, d.cgsnapshot) + snaps_name = self.driver._get_cgsnap_name(d.cgsnapshot) + snaps = xms_data['volumes'][1] + snaps['index'] = 1 + xms_data['snapshot-sets'] = {snaps_name: snaps, 1: snaps} + self.assertRaises(exception.InvalidInput, + self.driver.create_consistencygroup_from_src, + d.context, d.group, []) + self.driver.delete_cgsnapshot(d.context, d.cgsnapshot) + self.driver.delete_consistencygroup(d.context, d.group) + @mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOClient.req') class EMCXIODriverFibreChannelTestCase(test.TestCase): def setUp(self): super(EMCXIODriverFibreChannelTestCase, self).setUp() + clean_xms_data() - configuration = mock.Mock() - configuration.san_login = '' - configuration.san_password = '' - configuration.san_ip = '' - configuration.xtremio_cluster_name = '' - configuration.xtremio_provisioning_factor = 20.0 + config = mock.Mock() + config.san_login = '' + config.san_password = '' + config.san_ip = '' + config.xtremio_cluster_name = '' + config.xtremio_provisioning_factor = 20.0 self.driver = xtremio.XtremIOFibreChannelDriver( - configuration=configuration) + configuration=config) + self.driver.client = xtremio.XtremIOClient4(config, + config. + xtremio_cluster_name) self.data = CommonData() def test_initialize_terminate_connection(self, req): req.side_effect = xms_request - clean_xms_data() self.driver.create_volume(self.data.test_volume) map_data = self.driver.initialize_connection(self.data.test_volume, self.data.connector) diff --git a/cinder/volume/drivers/emc/xtremio.py b/cinder/volume/drivers/emc/xtremio.py index 38efc48cf..2e604419e 100644 --- a/cinder/volume/drivers/emc/xtremio.py +++ b/cinder/volume/drivers/emc/xtremio.py @@ -22,7 +22,8 @@ supported XtremIO version 2.4 and up 1.0.3 - update logging level, add translation 1.0.4 - support for FC zones 1.0.5 - add support for XtremIO 4.0 -1.0.6 - add support for iSCSI and CA validation +1.0.6 - add support for iSCSI multipath, CA validation, consistency groups, + R/O snapshots """ import json @@ -38,6 +39,7 @@ import six from cinder import exception from cinder.i18n import _, _LE, _LI, _LW +from cinder import objects from cinder.volume import driver from cinder.volume.drivers.san import san from cinder.zonemanager import utils as fczm_utils @@ -163,6 +165,9 @@ class XtremIOClient(object): """ raise NotImplementedError() + def get_extra_capabilities(self): + return {} + class XtremIOClient3(XtremIOClient): def __init__(self, configuration, cluster_id): @@ -217,6 +222,9 @@ class XtremIOClient4(XtremIOClient): super(XtremIOClient4, self).__init__(configuration, cluster_id) self._cluster_name = None + def get_extra_capabilities(self): + return {'consistencygroup_support': True} + def find_lunmap(self, ig_name, vol_name): try: return (self.req('lun-maps', @@ -269,6 +277,10 @@ class XtremIOClient4(XtremIOClient): self.req(typ, 'DELETE', idx=int(idx)) raise + def add_vol_to_cg(self, vol_id, cg_id): + add_data = {'vol-id': vol_id, 'cg-id': cg_id} + self.req('consistency-group-volumes', 'POST', add_data, ver='v2') + class XtremIOVolumeDriver(san.SanDriver): """Executes commands relating to Volumes.""" @@ -322,17 +334,28 @@ class XtremIOVolumeDriver(san.SanDriver): data = {'vol-name': volume['id'], 'vol-size': str(volume['size']) + 'g' } - self.client.req('volumes', 'POST', data) + if volume.get('consistencygroup_id'): + self.client.add_vol_to_cg(volume['id'], + volume['consistencygroup_id']) + def create_volume_from_snapshot(self, volume, snapshot): """Creates a volume from a snapshot.""" self.client.create_snapshot(snapshot.id, volume['id']) + if snapshot.get('consistencygroup_id'): + self.client.add_vol_to_cg(volume['id'], + snapshot['consistencygroup_id']) + def create_cloned_volume(self, volume, src_vref): """Creates a clone of the specified volume.""" self.client.create_snapshot(src_vref['id'], volume['id']) + if volume.get('consistencygroup_id'): + self.client.add_vol_to_cg(volume['id'], + volume['consistencygroup_id']) + def delete_volume(self, volume): """Deletes a volume.""" try: @@ -370,7 +393,9 @@ class XtremIOVolumeDriver(san.SanDriver): 'thick_provisioning_support': False, 'reserved_percentage': self.configuration.reserved_percentage, - 'QoS_support': False} + 'QoS_support': False, + } + self._stats.update(self.client.get_extra_capabilities()) def get_volume_stats(self, refresh=False): """Get volume stats. @@ -435,7 +460,7 @@ class XtremIOVolumeDriver(san.SanDriver): self.client.req('volumes', 'PUT', data, name=volume['id']) except exception.NotFound: msg = _("can't find the volume to extend") - raise (exception.VolumeDriverException(message=msg)) + raise exception.VolumeDriverException(message=msg) def check_for_export(self, context, volume_id): """Make sure volume is exported.""" @@ -479,6 +504,117 @@ class XtremIOVolumeDriver(san.SanDriver): def _get_ig(self, connector): raise NotImplementedError() + def create_consistencygroup(self, context, group): + """Creates a consistency group. + + :param context: the context + :param group: the group object to be created + :returns: dict -- modelUpdate = {'status': 'available'} + :raises: VolumeBackendAPIException + """ + create_data = {'consistency-group-name': group['id']} + self.client.req('consistency-groups', 'POST', data=create_data, + ver='v2') + return {'status': 'available'} + + def delete_consistencygroup(self, context, group): + """Deletes a consistency group.""" + self.client.req('consistency-groups', 'DELETE', name=group['id'], + ver='v2') + + volumes = self.db.volume_get_all_by_group(context, group['id']) + + for volume in volumes: + self.delete_volume(volume) + volume.status = 'deleted' + + model_update = {'status': group['status']} + + return model_update, volumes + + def create_consistencygroup_from_src(self, context, group, volumes, + cgsnapshot=None, snapshots=None): + """Creates a consistencygroup from source. + + :param context: the context of the caller. + :param group: the dictionary of the consistency group to be created. + :param volumes: a list of volume dictionaries in the group. + :param cgsnapshot: the dictionary of the cgsnapshot as source. + :param snapshots: a list of snapshot dictionaries in the cgsnapshot. + :return model_update, volumes_model_update + """ + if cgsnapshot and snapshots: + for volume, snapshot in zip(volumes, snapshots): + self.create_volume_from_snapshot(volume, snapshot) + create_data = {'consistency-group-name': group['id'], + 'vol-list': [v['id'] for v in volumes]} + self.client.req('consistency-groups', 'POST', data=create_data, + ver='v2') + else: + msg = _("create_consistencygroup_from_src only supports a" + " cgsnapshot source, other sources cannot be used.") + raise exception.InvalidInput(msg) + + return None, None + + def update_consistencygroup(self, context, group, + add_volumes=None, remove_volumes=None): + """Updates a consistency group. + + :param context: the context of the caller. + :param group: the dictionary of the consistency group to be updated. + :param add_volumes: a list of volume dictionaries to be added. + :param remove_volumes: a list of volume dictionaries to be removed. + :return model_update, add_volumes_update, remove_volumes_update + """ + add_volumes = add_volumes if add_volumes else [] + remove_volumes = remove_volumes if remove_volumes else [] + for vol in add_volumes: + add_data = {'vol-id': vol['id'], 'cg-id': group['id']} + self.client.req('consistency-group-volumes', 'POST', add_data, + ver='v2') + for vol in remove_volumes: + remove_data = {'vol-id': vol['id'], 'cg-id': group['id']} + self.client.req('consistency-group-volumes', 'DELETE', remove_data, + name=group['id'], ver='v2') + return None, None, None + + def _get_cgsnap_name(self, cgsnapshot): + return '%(cg)s%(snap)s' % {'cg': cgsnapshot['consistencygroup_id'] + .replace('-', ''), + 'snap': cgsnapshot['id'].replace('-', '')} + + def create_cgsnapshot(self, context, cgsnapshot): + """Creates a cgsnapshot.""" + data = {'consistency-group-id': cgsnapshot['consistencygroup_id'], + 'snapshot-set-name': self._get_cgsnap_name(cgsnapshot)} + self.client.req('snapshots', 'POST', data, ver='v2') + + snapshots = objects.SnapshotList().get_all_for_cgsnapshot( + context, cgsnapshot['id']) + + for snapshot in snapshots: + snapshot.status = 'available' + + model_update = {'status': 'available'} + + return model_update, snapshots + + def delete_cgsnapshot(self, context, cgsnapshot): + """Deletes a cgsnapshot.""" + self.client.req('snapshot-sets', 'DELETE', + name=self._get_cgsnap_name(cgsnapshot), ver='v2') + + snapshots = objects.SnapshotList().get_all_for_cgsnapshot( + context, cgsnapshot['id']) + + for snapshot in snapshots: + snapshot.status = 'deleted' + + model_update = {'status': cgsnapshot.status} + + return model_update, snapshots + class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver): """Executes commands relating to ISCSI volumes.