]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
EMC VNX Direct Driver Consistency Group support
authorXing Yang <xing.yang@emc.com>
Tue, 9 Sep 2014 22:18:21 +0000 (18:18 -0400)
committerXing Yang <xing.yang@emc.com>
Thu, 11 Sep 2014 17:11:42 +0000 (13:11 -0400)
Consistency Group support is newly introduced in Juno.

This commit is to add the changes in EMC VNX Direct Driver to
support CG.

Implements: blueprint emc-vnx-direct-driver-cg-support

Change-Id: I6c30ef2609ca8a559f5635129ce6a7c960a9f0a7

cinder/tests/test_emc_vnxdirect.py
cinder/volume/drivers/emc/emc_cli_fc.py
cinder/volume/drivers/emc/emc_cli_iscsi.py
cinder/volume/drivers/emc/emc_vnx_cli.py

index 5910ff441940c34bbc0f7759149dc93d1bf118aa..6d6200b997a06203bfcfe19658865ecc7e52ffd0 100644 (file)
@@ -46,6 +46,35 @@ class EMCVNXCLIDriverTestData():
         'display_name': 'vol1',
         'display_description': 'test volume',
         'volume_type_id': None,
+        'consistencygroup_id': None,
+        'volume_admin_metadata': [{'key': 'readonly', 'value': 'True'}]
+    }
+
+    test_volume_clone_cg = {
+        'name': 'vol1',
+        'size': 1,
+        'volume_name': 'vol1',
+        'id': '1',
+        'provider_auth': None,
+        'project_id': 'project',
+        'display_name': 'vol1',
+        'display_description': 'test volume',
+        'volume_type_id': None,
+        'consistencygroup_id': None,
+        'volume_admin_metadata': [{'key': 'readonly', 'value': 'True'}]
+    }
+
+    test_volume_cg = {
+        'name': 'vol1',
+        'size': 1,
+        'volume_name': 'vol1',
+        'id': '1',
+        'provider_auth': None,
+        'project_id': 'project',
+        'display_name': 'vol1',
+        'display_description': 'test volume',
+        'volume_type_id': None,
+        'consistencygroup_id': 'cg_id',
         'volume_admin_metadata': [{'key': 'readonly', 'value': 'True'}]
     }
 
@@ -59,7 +88,8 @@ class EMCVNXCLIDriverTestData():
         'display_name': 'vol1',
         'display_description': 'test volume',
         'volume_type_id': None,
-        'volume_admin_metadata': [{'key': 'access_mode', 'value': 'rw'},
+        'consistencygroup_id': None,
+        'volume_admin_metadata': [{'key': 'attached_mode', 'value': 'rw'},
                                   {'key': 'readonly', 'value': 'False'}]
     }
 
@@ -71,6 +101,19 @@ class EMCVNXCLIDriverTestData():
         'provider_auth': None,
         'project_id': 'project',
         'display_name': 'vol2',
+        'consistencygroup_id': None,
+        'display_description': 'test volume',
+        'volume_type_id': None}
+
+    volume_in_cg = {
+        'name': 'vol2',
+        'size': 1,
+        'volume_name': 'vol2',
+        'id': '1',
+        'provider_auth': None,
+        'project_id': 'project',
+        'display_name': 'vol2',
+        'consistencygroup_id': None,
         'display_description': 'test volume',
         'volume_type_id': None}
 
@@ -82,6 +125,7 @@ class EMCVNXCLIDriverTestData():
         'provider_auth': None,
         'project_id': 'project',
         'display_name': 'thin_vol',
+        'consistencygroup_id': None,
         'display_description': 'vol with type',
         'volume_type_id': 'abc1-2320-9013-8813-8941-1374-8112-1231'}
 
@@ -93,6 +137,7 @@ class EMCVNXCLIDriverTestData():
         'provider_auth': None,
         'project_id': 'project',
         'display_name': 'failed_vol',
+        'consistencygroup_id': None,
         'display_description': 'test failed volume',
         'volume_type_id': None}
     test_snapshot = {
@@ -101,6 +146,8 @@ class EMCVNXCLIDriverTestData():
         'id': '4444',
         'volume_name': 'vol1',
         'volume_size': 1,
+        'consistencygroup_id': None,
+        'cgsnapshot_id': None,
         'project_id': 'project'}
     test_failed_snapshot = {
         'name': 'failed_snapshot',
@@ -117,6 +164,18 @@ class EMCVNXCLIDriverTestData():
         'provider_auth': None,
         'project_id': 'project',
         'display_name': 'clone1',
+        'consistencygroup_id': None,
+        'display_description': 'volume created from snapshot',
+        'volume_type_id': None}
+    test_clone_cg = {
+        'name': 'clone1',
+        'size': 1,
+        'id': '2',
+        'volume_name': 'vol1',
+        'provider_auth': None,
+        'project_id': 'project',
+        'display_name': 'clone1',
+        'consistencygroup_id': 'consistencygroup_id',
         'display_description': 'volume created from snapshot',
         'volume_type_id': None}
     connector = {
@@ -206,6 +265,26 @@ class EMCVNXCLIDriverTestData():
                    'volume_backend_name': 'array_backend_1',
                    'storage_protocol': 'iSCSI'}}
 
+    test_cg = {'id': 'consistencygroup_id',
+               'name': 'group_name',
+               'status': 'deleting'}
+
+    test_cgsnapshot = {
+        'consistencygroup_id': 'consistencygroup_id',
+        'id': 'cgsnapshot_id',
+        'status': 'available'}
+
+    test_member_cgsnapshot = {
+        'name': 'snapshot1',
+        'size': 1,
+        'id': 'cgsnapshot_id',
+        'volume_name': 'vol1',
+        'volume_size': 1,
+        'consistencygroup_id': 'consistencygroup_id',
+        'cgsnapshot_id': 'cgsnapshot_id',
+        'project_id': 'project'
+    }
+
     test_lun_id = 1
     test_existing_ref = {'id': test_lun_id}
     test_pool_name = 'Pool_02_SASFLASH'
@@ -324,6 +403,53 @@ class EMCVNXCLIDriverTestData():
         return ('-np', 'storagepool', '-list', '-name',
                 storage_pool, '-fastcache')
 
+    def CREATE_CONSISTENCYGROUP_CMD(self, cg_name):
+        return ('-np', 'snap', '-group', '-create',
+                '-name', cg_name, '-allowSnapAutoDelete', 'no')
+
+    def DELETE_CONSISTENCYGROUP_CMD(self, cg_name):
+        return ('-np', 'snap', '-group', '-destroy',
+                '-id', cg_name)
+
+    def GET_CONSISTENCYGROUP_BY_NAME(self, cg_name):
+        return ('snap', '-group', '-list', '-id', cg_name)
+
+    def ADD_LUN_TO_CG_CMD(self, cg_name, lun_id):
+        return ('-np', 'snap', '-group',
+                '-addmember', '-id', cg_name, '-res', lun_id)
+
+    def CREATE_CG_SNAPSHOT(self, cg_name, snap_name):
+        return ('-np', 'snap', '-create', '-res', cg_name,
+                '-resType', 'CG', '-name', snap_name, '-allowReadWrite',
+                'yes', '-allowAutoDelete', 'no')
+
+    def DELETE_CG_SNAPSHOT(self, snap_name):
+        return ('-np', 'snap', '-destroy', '-id', snap_name, '-o')
+
+    def GET_CG_BY_NAME_CMD(self, cg_name):
+        return ('snap', '-group', '-list', '-id', cg_name)
+
+    def CONSISTENCY_GROUP_VOLUMES(self):
+        volumes = []
+        volumes.append(self.test_volume)
+        volumes.append(self.test_volume)
+        return volumes
+
+    def SNAPS_IN_SNAP_GROUP(self):
+        snaps = []
+        snaps.append(self.test_snapshot)
+        snaps.append(self.test_snapshot)
+        return snaps
+
+    def CG_PROPERTY(self, cg_name):
+        return """
+Name:  %(cg_name)s
+Description:
+Allow auto delete:  No
+Member LUN ID(s):  1, 3
+State:  Ready
+""" % {'cg_name': cg_name}
+
     POOL_PROPERTY = ("""\
 Pool Name:  unit_test_pool
 Pool ID:  1
@@ -851,7 +977,7 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase):
             "volume backend name is not correct")
         self.assertTrue(stats['location_info'] == "unit_test_pool|fakeSerial")
         self.assertTrue(
-            stats['driver_version'] == "04.00.00",
+            stats['driver_version'] == "04.01.00",
             "driver version is incorrect.")
 
     @mock.patch("cinder.volume.drivers.emc.emc_vnx_cli."
@@ -2028,6 +2154,185 @@ Time Remaining:  0 second(s)
             'volume_admin_metadata': [{'key': 'readonly', 'value': 'True'}]}
         self.assertEqual(self.driver.cli.get_lun_id(volume_02), 2)
 
+    def test_create_consistency_group(self):
+        cg_name = self.testData.test_cg['id']
+        commands = [self.testData.CREATE_CONSISTENCYGROUP_CMD(cg_name)]
+        results = [SUCCEED]
+        fake_cli = self.driverSetup(commands, results)
+
+        model_update = self.driver.create_consistencygroup(
+            None, self.testData.test_cg)
+        self.assertDictMatch({'status': 'available'}, model_update)
+        expect_cmd = [
+            mock.call(
+                *self.testData.CREATE_CONSISTENCYGROUP_CMD(
+                    cg_name))]
+        fake_cli.assert_has_calls(expect_cmd)
+
+    def test_delete_consistency_group(self):
+        cg_name = self.testData.test_cg['id']
+        commands = [self.testData.DELETE_CONSISTENCYGROUP_CMD(cg_name),
+                    self.testData.LUN_DELETE_CMD('vol1')]
+        results = [SUCCEED, SUCCEED]
+        fake_cli = self.driverSetup(commands, results)
+        self.driver.db = mock.MagicMock()
+        self.driver.db.volume_get_all_by_group.return_value =\
+            self.testData.CONSISTENCY_GROUP_VOLUMES()
+        self.driver.delete_consistencygroup(None,
+                                            self.testData.test_cg)
+        expect_cmd = [
+            mock.call(
+                *self.testData.DELETE_CONSISTENCYGROUP_CMD(
+                    cg_name)),
+            mock.call(
+                *self.testData.LUN_DELETE_CMD('vol1')),
+            mock.call(
+                *self.testData.LUN_DELETE_CMD('vol1'))]
+        fake_cli.assert_has_calls(expect_cmd)
+
+    def test_create_cgsnapshot(self):
+        cgsnapshot = self.testData.test_cgsnapshot['id']
+        cg_name = self.testData.test_cgsnapshot['consistencygroup_id']
+        commands = [self.testData.CREATE_CG_SNAPSHOT(cg_name, cgsnapshot)]
+        results = [SUCCEED]
+        fake_cli = self.driverSetup(commands, results)
+        self.driver.db = mock.MagicMock()
+        self.driver.db.volume_get_all_by_group.return_value =\
+            self.testData.SNAPS_IN_SNAP_GROUP()
+        self.driver.create_cgsnapshot(None, self.testData.test_cgsnapshot)
+        expect_cmd = [
+            mock.call(
+                *self.testData.CREATE_CG_SNAPSHOT(
+                    cg_name, cgsnapshot))]
+        fake_cli.assert_has_calls(expect_cmd)
+
+    def test_delete_cgsnapshot(self):
+        snap_name = self.testData.test_cgsnapshot['id']
+        commands = [self.testData.DELETE_CG_SNAPSHOT(snap_name)]
+        results = [SUCCEED]
+        fake_cli = self.driverSetup(commands, results)
+        self.driver.db = mock.MagicMock()
+        self.driver.db.snapshot_get_all_for_cgsnapshot.return_value =\
+            self.testData.SNAPS_IN_SNAP_GROUP()
+        self.driver.delete_cgsnapshot(None,
+                                      self.testData.test_cgsnapshot)
+        expect_cmd = [
+            mock.call(
+                *self.testData.DELETE_CG_SNAPSHOT(
+                    snap_name))]
+        fake_cli.assert_has_calls(expect_cmd)
+
+    @mock.patch(
+        "eventlet.event.Event.wait",
+        mock.Mock(return_value=None))
+    def test_add_volume_to_cg(self):
+        commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol1'),
+                    self.testData.ADD_LUN_TO_CG_CMD('cg_id', 1),
+                    self.testData.GET_CG_BY_NAME_CMD('cg_id')
+                    ]
+        results = [self.testData.LUN_PROPERTY('vol1', True),
+                   SUCCEED,
+                   self.testData.CG_PROPERTY('cg_id')]
+        fake_cli = self.driverSetup(commands, results)
+
+        self.driver.create_volume(self.testData.test_volume_cg)
+
+        expect_cmd = [
+            mock.call(*self.testData.LUN_CREATION_CMD(
+                'vol1', 1,
+                'unit_test_pool',
+                None, None)),
+            mock.call('lun', '-list', '-name', 'vol1',
+                      '-state', '-status', '-opDetails',
+                      '-userCap', '-owner', '-attachedSnapshot'),
+            mock.call(*self.testData.ADD_LUN_TO_CG_CMD(
+                'cg_id', 1))]
+        fake_cli.assert_has_calls(expect_cmd)
+
+    def test_create_cloned_volume_from_consistnecy_group(self):
+        cmd_smp = ('lun', '-list', '-name', 'vol1', '-attachedSnapshot')
+        output_smp = ("""LOGICAL UNIT NUMBER 1
+                     Name:  vol1
+                     Attached Snapshot:  N/A""", 0)
+        cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD("vol1_dest")
+        output_dest = self.testData.LUN_PROPERTY("vol1_dest")
+        cmd_migrate = self.testData.MIGRATION_CMD(1, 1)
+        output_migrate = ("", 0)
+        cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1)
+        output_migrate_verify = (r'The specified source LUN '
+                                 'is not currently migrating', 23)
+        cg_name = self.testData.test_cgsnapshot['consistencygroup_id']
+
+        commands = [cmd_smp, cmd_dest, cmd_migrate,
+                    cmd_migrate_verify]
+        results = [output_smp, output_dest, output_migrate,
+                   output_migrate_verify]
+        fake_cli = self.driverSetup(commands, results)
+
+        self.driver.create_cloned_volume(self.testData.test_volume_clone_cg,
+                                         self.testData.test_clone_cg)
+        tmp_cgsnapshot = 'tmp-cgsnapshot-' + self.testData.test_volume['id']
+        expect_cmd = [
+            mock.call(
+                *self.testData.CREATE_CG_SNAPSHOT(cg_name, tmp_cgsnapshot)),
+            mock.call(*self.testData.SNAP_MP_CREATE_CMD(name='vol1',
+                                                        source='clone1')),
+            mock.call(
+                *self.testData.SNAP_ATTACH_CMD(
+                    name='vol1', snapName=tmp_cgsnapshot)),
+            mock.call(*self.testData.LUN_CREATION_CMD(
+                'vol1_dest', 1, 'unit_test_pool', None, None)),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')),
+            mock.call(*self.testData.MIGRATION_CMD(1, 1),
+                      retry_disable=True),
+            mock.call(*self.testData.MIGRATION_VERIFY_CMD(1)),
+            mock.call('lun', '-list', '-name', 'vol1', '-attachedSnapshot'),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')),
+            mock.call(*self.testData.DELETE_CG_SNAPSHOT(tmp_cgsnapshot))]
+        fake_cli.assert_has_calls(expect_cmd)
+
+    def test_create_volume_from_cgsnapshot(self):
+        cmd_smp = ('lun', '-list', '-name', 'vol2', '-attachedSnapshot')
+        output_smp = ("""LOGICAL UNIT NUMBER 1
+                     Name:  vol2
+                     Attached Snapshot:  N/A""", 0)
+        cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD("vol2_dest")
+        output_dest = self.testData.LUN_PROPERTY("vol2_dest")
+        cmd_migrate = self.testData.MIGRATION_CMD(1, 1)
+        output_migrate = ("", 0)
+        cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1)
+        output_migrate_verify = (r'The specified source LUN '
+                                 'is not currently migrating', 23)
+        commands = [cmd_smp, cmd_dest, cmd_migrate, cmd_migrate_verify]
+        results = [output_smp, output_dest, output_migrate,
+                   output_migrate_verify]
+        fake_cli = self.driverSetup(commands, results)
+
+        self.driver.create_volume_from_snapshot(
+            self.testData.volume_in_cg, self.testData.test_member_cgsnapshot)
+        expect_cmd = [
+            mock.call(
+                *self.testData.SNAP_MP_CREATE_CMD(
+                    name='vol2', source='vol1')),
+            mock.call(
+                *self.testData.SNAP_ATTACH_CMD(
+                    name='vol2', snapName='cgsnapshot_id')),
+            mock.call(*self.testData.LUN_CREATION_CMD(
+                'vol2_dest', 1, 'unit_test_pool', None, None)),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2')),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')),
+            mock.call(*self.testData.MIGRATION_CMD(1, 1),
+                      retry_disable=True),
+            mock.call(*self.testData.MIGRATION_VERIFY_CMD(1)),
+            mock.call('lun', '-list', '-name', 'vol2', '-attachedSnapshot'),
+            mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2'))]
+        fake_cli.assert_has_calls(expect_cmd)
+
     def succeed_fake_command_execute(self, *command, **kwargv):
         return SUCCEED
 
@@ -2394,7 +2699,7 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase):
             "volume backend name is not correct")
         self.assertTrue(stats['location_info'] == "unit_test_pool|fakeSerial")
         self.assertTrue(
-            stats['driver_version'] == "04.00.00",
+            stats['driver_version'] == "04.01.00",
             "driver version is incorrect.")
 
 
index 0ce6f58cc8e12bd9a5136009948dc52d4e89ecf3..e4ccff5505073c860421fe601e88fd8fd9875ae1 100644 (file)
@@ -46,6 +46,7 @@ class EMCCLIFCDriver(driver.FibreChannelDriver):
                 FAST Cache Support), Storage-assisted Retype,
                 External Volume Management, Read-only Volume,
                 FC Auto Zoning
+        4.1.0 - Consistency group support
     """
 
     def __init__(self, *args, **kwargs):
@@ -217,3 +218,21 @@ class EMCCLIFCDriver(driver.FibreChannelDriver):
         """Return size of volume to be managed by manage_existing.
         """
         return self.cli.manage_existing_get_size(volume, existing_ref)
+
+    def create_consistencygroup(self, context, group):
+        """Creates a consistencygroup."""
+        return self.cli.create_consistencygroup(context, group)
+
+    def delete_consistencygroup(self, context, group):
+        """Deletes a consistency group."""
+        return self.cli.delete_consistencygroup(
+            self, context, group)
+
+    def create_cgsnapshot(self, context, cgsnapshot):
+        """Creates a cgsnapshot."""
+        return self.cli.create_cgsnapshot(
+            self, context, cgsnapshot)
+
+    def delete_cgsnapshot(self, context, cgsnapshot):
+        """Deletes a cgsnapshot."""
+        return self.cli.delete_cgsnapshot(self, context, cgsnapshot)
\ No newline at end of file
index c7d37d36879bdf21abd1fbb16b3eb61d090ae8c3..6c69c768cdbfd3e7264ddf42d4fd1528380381f9 100644 (file)
@@ -43,6 +43,7 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
                 FAST Cache Support), Storage-assisted Retype,
                 External Volume Management, Read-only Volume,
                 FC Auto Zoning
+        4.1.0 - Consistency group support
     """
 
     def __init__(self, *args, **kwargs):
@@ -174,3 +175,21 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
         """Return size of volume to be managed by manage_existing.
         """
         return self.cli.manage_existing_get_size(volume, existing_ref)
+
+    def create_consistencygroup(self, context, group):
+        """Creates a consistencygroup."""
+        return self.cli.create_consistencygroup(context, group)
+
+    def delete_consistencygroup(self, context, group):
+        """Deletes a consistency group."""
+        return self.cli.delete_consistencygroup(
+            self, context, group)
+
+    def create_cgsnapshot(self, context, cgsnapshot):
+        """Creates a cgsnapshot."""
+        return self.cli.create_cgsnapshot(
+            self, context, cgsnapshot)
+
+    def delete_cgsnapshot(self, context, cgsnapshot):
+        """Deletes a cgsnapshot."""
+        return self.cli.delete_cgsnapshot(self, context, cgsnapshot)
\ No newline at end of file
index 7bc575b80a18f1580bc9de1e634dcc52142835a2..218d1ca40ea94f261da7db68f7465c11369465c4 100644 (file)
@@ -22,6 +22,7 @@ import re
 import time
 
 from oslo.config import cfg
+import six
 
 from cinder import exception
 from cinder.exception import EMCVnxCLICmdError
@@ -187,6 +188,9 @@ class CommandLineHelper(object):
 
     POOL_ALL = [POOL_TOTAL_CAPACITY, POOL_FREE_CAPACITY]
 
+    CLI_RESP_PATTERN_CG_NOT_FOUND = 'Cannot find'
+    CLI_RESP_PATTERN_SNAP_NOT_FOUND = 'The specified snapshot does not exist'
+
     def __init__(self, configuration):
         configuration.append_config_values(san.san_opts)
 
@@ -281,7 +285,8 @@ class CommandLineHelper(object):
 
     @log_enter_exit
     def create_lun_with_advance_feature(self, pool, name, size,
-                                        provisioning, tiering):
+                                        provisioning, tiering,
+                                        consistencygroup_id=None):
         command_create_lun = ['lun', '-create',
                               '-capacity', size,
                               '-sq', 'gb',
@@ -305,7 +310,19 @@ class CommandLineHelper(object):
         except EMCVnxCLICmdError as ex:
             with excutils.save_and_reraise_exception():
                 self.delete_lun(name)
-                LOG.error(_("Failed to enable compression on lun: %s") % ex)
+                LOG.error(_("Error on enable compression on lun %s.")
+                          % six.text_type(ex))
+
+        # handle consistency group
+        try:
+            if consistencygroup_id:
+                self.add_lun_to_consistency_group(
+                    consistencygroup_id, data['lun_id'])
+        except EMCVnxCLICmdError as ex:
+            with excutils.save_and_reraise_exception():
+                self.delete_lun(name)
+                LOG.error(_("Error on adding lun to consistency"
+                            " group. %s") % six.text_type(ex))
         return data
 
     @log_enter_exit
@@ -432,6 +449,143 @@ class CommandLineHelper(object):
             if rc != 0:
                 raise EMCVnxCLICmdError(command_modify_lun, rc, out)
 
+    @log_enter_exit
+    def create_consistencygroup(self, context, group):
+        """create the consistency group."""
+        cg_name = group['id']
+        command_create_cg = ('-np', 'snap', '-group',
+                             '-create',
+                             '-name', cg_name,
+                             '-allowSnapAutoDelete', 'no')
+
+        out, rc = self.command_execute(*command_create_cg)
+        if rc != 0:
+            # Ignore the error if consistency group already exists
+            if (rc == 33 and
+                    out.find("(0x716d8021)") >= 0):
+                LOG.warn(_('Consistency group %(name)s already '
+                           'exists. Message: %(msg)s') %
+                         {'name': cg_name, 'msg': out})
+            else:
+                raise EMCVnxCLICmdError(command_create_cg, rc, out)
+
+    @log_enter_exit
+    def get_consistency_group_by_name(self, cg_name):
+        cmd = ('snap', '-group', '-list', '-id', cg_name)
+        data = {
+            'Name': None,
+            'Luns': None,
+            'State': None
+        }
+        out, rc = self.command_execute(*cmd)
+        if rc == 0:
+            cg_pat = r"Name:(.*)\n"\
+                     r"Description:(.*)\n"\
+                     r"Allow auto delete:(.*)\n"\
+                     r"Member LUN ID\(s\):(.*)\n"\
+                     r"State:(.*)\n"
+            for m in re.finditer(cg_pat, out):
+                data['Name'] = m.groups()[0].strip()
+                data['State'] = m.groups()[4].strip()
+                luns_of_cg = m.groups()[3].split(',')
+                if luns_of_cg:
+                    data['Luns'] = [lun.strip() for lun in luns_of_cg]
+                LOG.debug("Found consistent group %s." % data['Name'])
+
+        return data
+
+    @log_enter_exit
+    def add_lun_to_consistency_group(self, cg_name, lun_id):
+        add_lun_to_cg_cmd = ('-np', 'snap', '-group',
+                             '-addmember', '-id',
+                             cg_name, '-res', lun_id)
+
+        out, rc = self.command_execute(*add_lun_to_cg_cmd)
+        if rc != 0:
+            msg = (_("Can not add the lun %(lun)s to consistency "
+                   "group %(cg_name)s.") % {'lun': lun_id,
+                                            'cg_name': cg_name})
+            LOG.error(msg)
+            raise EMCVnxCLICmdError(add_lun_to_cg_cmd, rc, out)
+
+        def add_lun_to_consistency_success():
+            data = self.get_consistency_group_by_name(cg_name)
+            if str(lun_id) in data['Luns']:
+                LOG.debug(("Add lun %(lun)s to consistency "
+                           "group %(cg_name)s successfully.") %
+                          {'lun': lun_id, 'cg_name': cg_name})
+                return True
+            else:
+                LOG.debug(("Adding lun %(lun)s to consistency "
+                           "group %(cg_name)s.") %
+                          {'lun': lun_id, 'cg_name': cg_name})
+                return False
+
+        self._wait_for_a_condition(add_lun_to_consistency_success,
+                                   interval=INTERVAL_30_SEC)
+
+    @log_enter_exit
+    def delete_consistencygroup(self, cg_name):
+        delete_cg_cmd = ('-np', 'snap', '-group',
+                         '-destroy', '-id', cg_name)
+        out, rc = self.command_execute(*delete_cg_cmd)
+        if rc != 0:
+            # Ignore the error if CG doesn't exist
+            if rc == 13 and out.find(self.CLI_RESP_PATTERN_CG_NOT_FOUND) >= 0:
+                LOG.warn(_("CG %(cg_name)s does not exist. "
+                           "Message: %(msg)s") %
+                         {'cg_name': cg_name, 'msg': out})
+            elif rc == 1 and out.find("0x712d8801") >= 0:
+                LOG.warn(_("CG %(cg_name)s is deleting. "
+                           "Message: %(msg)s") %
+                         {'cg_name': cg_name, 'msg': out})
+            else:
+                raise EMCVnxCLICmdError(delete_cg_cmd, rc, out)
+        else:
+            LOG.info(_('Consistency group %s was deleted '
+                       'successfully.') % cg_name)
+
+    @log_enter_exit
+    def create_cgsnapshot(self, cgsnapshot):
+        """Create a cgsnapshot (snap group)."""
+        cg_name = cgsnapshot['consistencygroup_id']
+        snap_name = cgsnapshot['id']
+        create_cg_snap_cmd = ('-np', 'snap', '-create',
+                              '-res', cg_name,
+                              '-resType', 'CG',
+                              '-name', snap_name,
+                              '-allowReadWrite', 'yes',
+                              '-allowAutoDelete', 'no')
+
+        out, rc = self.command_execute(*create_cg_snap_cmd)
+        if rc != 0:
+            # Ignore the error if cgsnapshot already exists
+            if (rc == 5 and
+                    out.find("(0x716d8005)") >= 0):
+                LOG.warn(_('Cgsnapshot name %(name)s already '
+                           'exists. Message: %(msg)s') %
+                         {'name': snap_name, 'msg': out})
+            else:
+                raise EMCVnxCLICmdError(create_cg_snap_cmd, rc, out)
+
+    @log_enter_exit
+    def delete_cgsnapshot(self, cgsnapshot):
+        """Delete a cgsnapshot (snap group)."""
+        snap_name = cgsnapshot['id']
+        delete_cg_snap_cmd = ('-np', 'snap', '-destroy',
+                              '-id', snap_name, '-o')
+
+        out, rc = self.command_execute(*delete_cg_snap_cmd)
+        if rc != 0:
+            # Ignore the error if cgsnapshot does not exist.
+            if (rc == 5 and
+                    out.find(self.CLI_RESP_PATTERN_SNAP_NOT_FOUND) >= 0):
+                LOG.warn(_('Snapshot %(name)s for consistency group '
+                           'does not exist. Message: %(msg)s') %
+                         {'name': snap_name, 'msg': out})
+            else:
+                raise EMCVnxCLICmdError(delete_cg_snap_cmd, rc, out)
+
     @log_enter_exit
     def create_snapshot(self, volume_name, name):
         data = self.get_lun_by_name(volume_name)
@@ -445,15 +599,15 @@ class CommandLineHelper(object):
             out, rc = self.command_execute(*command_create_snapshot)
             if rc != 0:
                 # Ignore the error that due to retry
-                if rc == 5 and \
-                        out.find("(0x716d8005)") >= 0:
+                if (rc == 5 and
+                        out.find("(0x716d8005)") >= 0):
                     LOG.warn(_('Snapshot %(name)s already exists. '
                                'Message: %(msg)s') %
                              {'name': name, 'msg': out})
                 else:
                     raise EMCVnxCLICmdError(command_create_snapshot, rc, out)
         else:
-            msg = _('Failed to get LUN ID for volume %s') % volume_name
+            msg = _('Failed to get LUN ID for volume %s.') % volume_name
             raise exception.VolumeBackendAPIException(data=msg)
 
     @log_enter_exit
@@ -1265,7 +1419,7 @@ class CommandLineHelper(object):
 class EMCVnxCliBase(object):
     """This class defines the functions to use the native CLI functionality."""
 
-    VERSION = '04.00.00'
+    VERSION = '04.01.00'
     stats = {'driver_version': VERSION,
              'free_capacity_gb': 'unknown',
              'reserved_percentage': 0,
@@ -1346,7 +1500,7 @@ class EMCVnxCliBase(object):
 
         data = self._client.create_lun_with_advance_feature(
             pool, volumename, volumesize,
-            provisioning, tiering)
+            provisioning, tiering, volume['consistencygroup_id'])
         pl_dict = {'system': self.get_array_serial(),
                    'type': 'lun',
                    'id': str(data['lun_id'])}
@@ -1676,6 +1830,10 @@ class EMCVnxCliBase(object):
             self.stats['fast_cache_enabled'] = 'True'
         else:
             self.stats['fast_cache_enabled'] = 'False'
+        if '-VNXSnapshots' in self.enablers:
+            self.stats['consistencygroup_support'] = 'True'
+        else:
+            self.stats['consistencygroup_support'] = 'False'
 
         return self.stats
 
@@ -1722,7 +1880,10 @@ class EMCVnxCliBase(object):
     @log_enter_exit
     def create_volume_from_snapshot(self, volume, snapshot):
         """Creates a volume from a snapshot."""
-        snapshot_name = snapshot['name']
+        if snapshot['cgsnapshot_id']:
+            snapshot_name = snapshot['cgsnapshot_id']
+        else:
+            snapshot_name = snapshot['name']
         source_volume_name = snapshot['volume_name']
         volume_name = volume['name']
         volume_size = snapshot['volume_size']
@@ -1772,21 +1933,132 @@ class EMCVnxCliBase(object):
         """Creates a clone of the specified volume."""
         source_volume_name = src_vref['name']
         volume_size = src_vref['size']
+        consistencygroup_id = src_vref['consistencygroup_id']
         snapshot_name = 'tmp-snap-%s' % volume['id']
+        tmp_cgsnapshot_name = None
+        if consistencygroup_id:
+            tmp_cgsnapshot_name = 'tmp-cgsnapshot-%s' % volume['id']
 
         snapshot = {
             'name': snapshot_name,
             'volume_name': source_volume_name,
             'volume_size': volume_size,
+            'cgsnapshot_id': tmp_cgsnapshot_name,
+            'consistencygroup_id': consistencygroup_id,
+            'id': tmp_cgsnapshot_name
         }
         # Create temp Snapshot
-        self.create_snapshot(snapshot)
+        if consistencygroup_id:
+            self._client.create_cgsnapshot(snapshot)
+        else:
+            self.create_snapshot(snapshot)
+
         # Create volume
         model_update = self.create_volume_from_snapshot(volume, snapshot)
         # Delete temp Snapshot
-        self.delete_snapshot(snapshot)
+        if consistencygroup_id:
+            self._client.delete_cgsnapshot(snapshot)
+        else:
+            self.delete_snapshot(snapshot)
+        return model_update
+
+    @log_enter_exit
+    def create_consistencygroup(self, context, group):
+        """Create a consistency group."""
+        LOG.info(_('Start to create consistency group: %(group_name)s '
+                   'id: %(id)s') %
+                 {'group_name': group['name'], 'id': group['id']})
+
+        model_update = {'status': 'available'}
+        try:
+            self._client.create_consistencygroup(context, group)
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                msg = (_('Create consistency group %s failed.')
+                       % group['id'])
+                LOG.error(msg)
+
         return model_update
 
+    @log_enter_exit
+    def delete_consistencygroup(self, driver, context, group):
+        """Delete a consistency group."""
+        cg_name = group['id']
+        volumes = driver.db.volume_get_all_by_group(context, group['id'])
+
+        model_update = {}
+        model_update['status'] = group['status']
+        LOG.info(_('Start to delete consistency group: %(cg_name)s')
+                 % {'cg_name': cg_name})
+        try:
+            self._client.delete_consistencygroup(cg_name)
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                msg = (_('Delete consistency group %s failed.')
+                       % cg_name)
+                LOG.error(msg)
+
+        for volume_ref in volumes:
+            try:
+                self._client.delete_lun(volume_ref['name'])
+                volume_ref['status'] = 'deleted'
+            except Exception:
+                volume_ref['status'] = 'error_deleting'
+                model_update['status'] = 'error_deleting'
+
+        return model_update, volumes
+
+    @log_enter_exit
+    def create_cgsnapshot(self, driver, context, cgsnapshot):
+        """Create a cgsnapshot (snap group)."""
+        cgsnapshot_id = cgsnapshot['id']
+        snapshots = driver.db.snapshot_get_all_for_cgsnapshot(
+            context, cgsnapshot_id)
+
+        model_update = {}
+        LOG.info(_('Start to create cgsnapshot for consistency group'
+                   ': %(group_name)s') %
+                 {'group_name': cgsnapshot['consistencygroup_id']})
+
+        try:
+            self._client.create_cgsnapshot(cgsnapshot)
+            for snapshot in snapshots:
+                snapshot['status'] = 'available'
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                msg = (_('Create cg snapshot %s failed.')
+                       % cgsnapshot_id)
+                LOG.error(msg)
+
+        model_update['status'] = 'available'
+
+        return model_update, snapshots
+
+    @log_enter_exit
+    def delete_cgsnapshot(self, driver, context, cgsnapshot):
+        """delete a cgsnapshot (snap group)."""
+        cgsnapshot_id = cgsnapshot['id']
+        snapshots = driver.db.snapshot_get_all_for_cgsnapshot(
+            context, cgsnapshot_id)
+
+        model_update = {}
+        model_update['status'] = cgsnapshot['status']
+        LOG.info(_('Delete cgsnapshot %(snap_name)s for consistency group: '
+                   '%(group_name)s') % {'snap_name': cgsnapshot['id'],
+                 'group_name': cgsnapshot['consistencygroup_id']})
+
+        try:
+            self._client.delete_cgsnapshot(cgsnapshot)
+            for snapshot in snapshots:
+                snapshot['status'] = 'deleted'
+        except Exception:
+            with excutils.save_and_reraise_exception():
+                msg = (_('Delete cgsnapshot %s failed.')
+                       % cgsnapshot_id)
+                LOG.error(msg)
+
+        return model_update, snapshots
+
     def get_lun_id_by_name(self, volume_name):
         data = self._client.get_lun_by_name(volume_name)
         return data['lun_id']