]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
XtremIO volume driver consistency group support
authorShay Halsband <shay.halsband@emc.com>
Thu, 21 May 2015 18:34:18 +0000 (21:34 +0300)
committerShay Halsband <shay.halsband@emc.com>
Thu, 23 Jul 2015 14:54:04 +0000 (14:54 +0000)
- 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

cinder/tests/unit/test_emc_xtremio.py
cinder/volume/drivers/emc/xtremio.py

index 9c6a00c70bd6ddc7d9a66b68fa0a398bfce3f9a7..13f0b23a6229441a853d3b353e70dc214d36f842 100644 (file)
 #    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)
index 38efc48cff8de0ea73ebc0ead24d71ef7b696d18..2e604419e2e0926edca62b07b0798b0dfcbab417 100644 (file)
@@ -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.