]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
CG Modification Support in EMC VNX Cinder Driver
authorpeter_wang <wangxu198709@gmail.com>
Wed, 11 Feb 2015 03:04:40 +0000 (22:04 -0500)
committerpeter_wang <wangxu198709@gmail.com>
Wed, 4 Mar 2015 21:22:11 +0000 (16:22 -0500)
BP consistency-groups-kilo-update is introducing CG Modification
support. This patch is adding EMC VNX support for the new API.

Change-Id: I9f9a59aa21402b2010fa754d70a2b6127d35da19
Implements: blueprint cg-modification-vnx

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 89b2bbae4c6ea77fca34300e29a45638a62c61fe..20805c533f3935e3dfa93c4eb01b57a34dbc2e11 100644 (file)
@@ -43,7 +43,7 @@ class EMCVNXCLIDriverTestData():
         'provider_auth': None,
         'host': "host@backendsec#unit_test_pool",
         'project_id': 'project',
-        'provider_location': 'system^FNM11111|type^lun|id^1|version^05.02.00',
+        'provider_location': 'system^FNM11111|type^lun|id^1|version^05.03.00',
         'display_name': 'vol1',
         'display_description': 'test volume',
         'volume_type_id': None,
@@ -111,7 +111,7 @@ class EMCVNXCLIDriverTestData():
         'consistencygroup_id': None,
         'volume_admin_metadata': [{'key': 'attached_mode', 'value': 'rw'},
                                   {'key': 'readonly', 'value': 'False'}],
-        'provider_location': 'system^FNM11111|type^lun|id^1|version^05.02.00',
+        'provider_location': 'system^FNM11111|type^lun|id^1|version^05.03.00',
     }
 
     test_volume2 = {
@@ -135,8 +135,22 @@ class EMCVNXCLIDriverTestData():
         'provider_auth': None,
         'host': "host@backendsec#unit_test_pool",
         'project_id': 'project',
-        'display_name': 'vol2',
-        'consistencygroup_id': None,
+        'display_name': 'vol1_in_cg',
+        'provider_location': 'system^FNM11111|type^lun|id^1',
+        'consistencygroup_id': 'consistencygroup_id',
+        'display_description': 'test volume',
+        'volume_type_id': None}
+
+    volume2_in_cg = {
+        'name': 'vol2',
+        'size': 1,
+        'volume_name': 'vol2',
+        'id': '3',
+        'provider_auth': None,
+        'project_id': 'project',
+        'display_name': 'vol2_in_cg',
+        'provider_location': 'system^FNM11111|type^lun|id^3',
+        'consistencygroup_id': 'consistencygroup_id',
         'display_description': 'test volume',
         'volume_type_id': None}
 
@@ -177,7 +191,7 @@ class EMCVNXCLIDriverTestData():
         'display_name': 'failed_vol',
         'display_description': 'Volume 1 in SG',
         'volume_type_id': None,
-        'provider_location': 'system^fakesn|type^lun|id^4|version^05.02.00'}
+        'provider_location': 'system^fakesn|type^lun|id^4|version^05.03.00'}
 
     test_volume2_in_sg = {
         'name': 'vol2_in_sg',
@@ -190,7 +204,7 @@ class EMCVNXCLIDriverTestData():
         'display_name': 'failed_vol',
         'display_description': 'Volume 2 in SG',
         'volume_type_id': None,
-        'provider_location': 'system^fakesn|type^lun|id^3|version^05.02.00'}
+        'provider_location': 'system^fakesn|type^lun|id^3|version^05.03.00'}
 
     test_snapshot = {
         'name': 'snapshot1',
@@ -257,7 +271,7 @@ class EMCVNXCLIDriverTestData():
         'volume_type': [],
         'attached_host': None,
         'provider_location':
-        'system^FNM11111|type^lun|id^1|version^05.02.00',
+        'system^FNM11111|type^lun|id^1|version^05.03.00',
         '_name_id': None, 'volume_metadata': []}
 
     test_new_type = {'name': 'voltype0', 'qos_specs_id': None,
@@ -276,6 +290,23 @@ class EMCVNXCLIDriverTestData():
                   'volume_backend_name': 'pool_backend_1',
                   'storage_protocol': 'iSCSI'}}
 
+    test_volume4 = {'migration_status': None, 'availability_zone': 'nova',
+                    'id': '1181d1b2-cea3-4f55-8fa8-3360d026ce24',
+                    'name': 'vol4',
+                    'size': 2L,
+                    'volume_admin_metadata': [],
+                    'status': 'available',
+                    'volume_type_id':
+                    '19fdd0dd-03b3-4d7c-b541-f4df46f308c8',
+                    'deleted': False, 'provider_location':
+                    'system^FNM11111|type^lun|id^4',
+                    'host': 'ubuntu-server12@array_backend_1',
+                    'source_volid': None, 'provider_auth': None,
+                    'display_name': 'vol-test02', 'instance_uuid': None,
+                    'attach_status': 'detached',
+                    'volume_type': [],
+                    '_name_id': None, 'volume_metadata': []}
+
     test_volume5 = {'migration_status': None, 'availability_zone': 'nova',
                     'id': '1181d1b2-cea3-4f55-8fa8-3360d026ce25',
                     'name_id': '1181d1b2-cea3-4f55-8fa8-3360d026ce25',
@@ -528,11 +559,8 @@ class EMCVNXCLIDriverTestData():
         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',
+        return ('snap', '-group',
                 '-addmember', '-id', cg_name, '-res', lun_id)
 
     def CREATE_CG_SNAPSHOT(self, cg_name, snap_name):
@@ -546,6 +574,14 @@ class EMCVNXCLIDriverTestData():
     def GET_CG_BY_NAME_CMD(self, cg_name):
         return ('snap', '-group', '-list', '-id', cg_name)
 
+    def REMOVE_LUNS_FROM_CG_CMD(self, cg_name, remove_ids):
+        return ('snap', '-group', '-rmmember', '-id', cg_name, '-res',
+                ','.join(remove_ids))
+
+    def REPLACE_LUNS_IN_CG_CMD(self, cg_name, new_ids):
+        return ('snap', '-group', '-replmember', '-id', cg_name, '-res',
+                ','.join(new_ids))
+
     def CONSISTENCY_GROUP_VOLUMES(self):
         volumes = []
         volumes.append(self.test_volume)
@@ -558,6 +594,18 @@ class EMCVNXCLIDriverTestData():
         snaps.append(self.test_snapshot)
         return snaps
 
+    def VOLUMES_NOT_IN_CG(self):
+        add_volumes = []
+        add_volumes.append(self.test_volume4)
+        add_volumes.append(self.test_volume5)
+        return add_volumes
+
+    def VOLUMES_IN_CG(self):
+        remove_volumes = []
+        remove_volumes.append(self.volume_in_cg)
+        remove_volumes.append(self.volume2_in_cg)
+        return remove_volumes
+
     def CG_PROPERTY(self, cg_name):
         return """
 Name:  %(cg_name)s
@@ -565,7 +613,13 @@ Description:
 Allow auto delete:  No
 Member LUN ID(s):  1, 3
 State:  Ready
-""" % {'cg_name': cg_name}
+""" % {'cg_name': cg_name}, 0
+
+    def CG_REPL_ERROR(self):
+        return """
+        The specified LUN is already a member
+        of another consistency group. (0x716d8045)
+        """, 71
 
     POOL_PROPERTY = ("""\
 Pool Name:  unit_test_pool
@@ -2824,7 +2878,7 @@ Time Remaining:  0 second(s)
             mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'),
                       poll=False),
             mock.call(*self.testData.ADD_LUN_TO_CG_CMD(
-                'cg_id', 1))]
+                'cg_id', 1), poll=False)]
         fake_cli.assert_has_calls(expect_cmd)
 
     def test_create_cloned_volume_from_consistnecy_group(self):
@@ -2911,6 +2965,75 @@ Time Remaining:  0 second(s)
                       poll=True)]
         fake_cli.assert_has_calls(expect_cmd)
 
+    def test_update_consistencygroup(self):
+        cg_name = self.testData.test_cg['id']
+        commands = [self.testData.GET_CG_BY_NAME_CMD(cg_name)]
+        results = [self.testData.CG_PROPERTY(cg_name)]
+        fake_cli = self.driverSetup(commands, results)
+
+        (model_update, add_vols, remove_vols) = (
+            self.driver.update_consistencygroup(None, self.testData.test_cg,
+                                                self.testData.
+                                                VOLUMES_NOT_IN_CG(),
+                                                self.testData.VOLUMES_IN_CG()))
+        expect_cmd = [
+            mock.call(*self.testData.REPLACE_LUNS_IN_CG_CMD(
+                cg_name, ['4', '5']), poll=False)]
+        fake_cli.assert_has_calls(expect_cmd)
+        self.assertEqual('available', model_update['status'])
+
+    def test_update_consistencygroup_remove_all(self):
+        cg_name = self.testData.test_cg['id']
+        commands = [self.testData.GET_CG_BY_NAME_CMD(cg_name)]
+        results = [self.testData.CG_PROPERTY(cg_name)]
+        fake_cli = self.driverSetup(commands, results)
+
+        (model_update, add_vols, remove_vols) = (
+            self.driver.update_consistencygroup(None, self.testData.test_cg,
+                                                None,
+                                                self.testData.VOLUMES_IN_CG()))
+        expect_cmd = [
+            mock.call(*self.testData.REMOVE_LUNS_FROM_CG_CMD(
+                cg_name, ['1', '3']), poll=False)]
+        fake_cli.assert_has_calls(expect_cmd)
+        self.assertEqual('available', model_update['status'])
+
+    def test_update_consistencygroup_remove_not_in_cg(self):
+        cg_name = self.testData.test_cg['id']
+        commands = [self.testData.GET_CG_BY_NAME_CMD(cg_name)]
+        results = [self.testData.CG_PROPERTY(cg_name)]
+        fake_cli = self.driverSetup(commands, results)
+
+        (model_update, add_vols, remove_vols) = (
+            self.driver.update_consistencygroup(None, self.testData.test_cg,
+                                                None,
+                                                self.testData.
+                                                VOLUMES_NOT_IN_CG()))
+        expect_cmd = [
+            mock.call(*self.testData.REPLACE_LUNS_IN_CG_CMD(
+                cg_name, ['1', '3']), poll=False)]
+        fake_cli.assert_has_calls(expect_cmd)
+        self.assertEqual('available', model_update['status'])
+
+    def test_update_consistencygroup_error(self):
+        cg_name = self.testData.test_cg['id']
+        commands = [self.testData.GET_CG_BY_NAME_CMD(cg_name),
+                    self.testData.REPLACE_LUNS_IN_CG_CMD(
+                    cg_name, ['1', '3'])]
+        results = [self.testData.CG_PROPERTY(cg_name),
+                   self.testData.CG_REPL_ERROR()]
+        fake_cli = self.driverSetup(commands, results)
+        self.assertRaises(exception.EMCVnxCLICmdError,
+                          self.driver.update_consistencygroup,
+                          None,
+                          self.testData.test_cg,
+                          [],
+                          self.testData.VOLUMES_NOT_IN_CG())
+        expect_cmd = [
+            mock.call(*self.testData.REPLACE_LUNS_IN_CG_CMD(
+                cg_name, ['1', '3']), poll=False)]
+        fake_cli.assert_has_calls(expect_cmd)
+
     def test_deregister_initiator(self):
         fake_cli = self.driverSetup()
         self.driver.cli.destroy_empty_sg = True
index 8d03cd19db3a0f29d85c7d25d4048979b00d6069..d7fd46f7c2e520c705f643809e84b0c3404cc1d7 100644 (file)
@@ -52,6 +52,7 @@ class EMCCLIFCDriver(driver.FibreChannelDriver):
                 robust enhancement
         5.1.0 - iSCSI multipath enhancement
         5.2.0 - Pool-aware scheduler support
+        5.3.0 - Consistency group modification support
     """
 
     def __init__(self, *args, **kwargs):
@@ -238,3 +239,11 @@ class EMCCLIFCDriver(driver.FibreChannelDriver):
     def get_pool(self, volume):
         """Returns the pool name of a volume."""
         return self.cli.get_pool(volume)
+
+    def update_consistencygroup(self, context, group,
+                                add_volumes,
+                                remove_volumes):
+        """Updates LUNs in consistency group."""
+        return self.cli.update_consistencygroup(context, group,
+                                                add_volumes,
+                                                remove_volumes)
index 6049bd26975489edc011fa5234fc9e0f3df239da..fb7b1ce9803ae1a33b370a634de48bd605736132 100644 (file)
@@ -50,6 +50,7 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
                 robust enhancement
         5.1.0 - iSCSI multipath enhancement
         5.2.0 - Pool-aware scheduler support
+        5.3.0 - Consistency group modification support
     """
 
     def __init__(self, *args, **kwargs):
@@ -217,3 +218,11 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
     def get_pool(self, volume):
         """Returns the pool name of a volume."""
         return self.cli.get_pool(volume)
+
+    def update_consistencygroup(self, context, group,
+                                add_volumes,
+                                remove_volumes):
+        """Updates LUNs in consistency group."""
+        return self.cli.update_consistencygroup(context, group,
+                                                add_volumes,
+                                                remove_volumes)
index 705354c86bd30eb3282080c4950e4d965556630f..578c0afc9b4c209cebe8aa4d80282ff6a8609c3e 100644 (file)
@@ -589,42 +589,68 @@ class CommandLineHelper(object):
             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]
+                # Handle case when no lun in cg Member LUN ID(s):  None
+                luns_of_cg = m.groups()[3].replace('None', '').strip()
+                data['Luns'] = ([lun.strip() for lun in luns_of_cg.split(',')]
+                                if luns_of_cg else [])
                 LOG.debug("Found consistent group %s.", data['Name'])
 
         return data
 
-    def add_lun_to_consistency_group(self, cg_name, lun_id):
-        add_lun_to_cg_cmd = ('-np', 'snap', '-group',
+    def add_lun_to_consistency_group(self, cg_name, lun_id, poll=False):
+        add_lun_to_cg_cmd = ('snap', '-group',
                              '-addmember', '-id',
                              cg_name, '-res', lun_id)
 
-        out, rc = self.command_execute(*add_lun_to_cg_cmd)
+        out, rc = self.command_execute(*add_lun_to_cg_cmd, poll=poll)
         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)
+            LOG.error(_LE("Can not add the lun %(lun)s to consistency "
+                          "group %(cg_name)s."), {'lun': lun_id,
+                                                  'cg_name': cg_name})
             self._raise_cli_error(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."),
+                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."),
+                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)
 
+    def remove_luns_from_consistencygroup(self, cg_name, remove_ids,
+                                          poll=False):
+        """Removes LUN(s) from cg"""
+        remove_luns_cmd = ('snap', '-group', '-rmmember',
+                           '-id', cg_name,
+                           '-res', ','.join(remove_ids))
+        out, rc = self.command_execute(*remove_luns_cmd, poll=poll)
+        if rc != 0:
+            LOG.error(_LE("Can not remove LUNs %(luns)s in consistency "
+                          "group %(cg_name)s."), {'luns': remove_ids,
+                                                  'cg_name': cg_name})
+            self._raise_cli_error(remove_luns_cmd, rc, out)
+
+    def replace_luns_in_consistencygroup(self, cg_name, new_ids,
+                                         poll=False):
+        """Replaces LUN(s) with new_ids for cg"""
+        replace_luns_cmd = ('snap', '-group', '-replmember',
+                            '-id', cg_name,
+                            '-res', ','.join(new_ids))
+        out, rc = self.command_execute(*replace_luns_cmd, poll=poll)
+        if rc != 0:
+            LOG.error(_LE("Can not place new LUNs %(luns)s in consistency "
+                          "group %(cg_name)s."), {'luns': new_ids,
+                                                  'cg_name': cg_name})
+            self._raise_cli_error(replace_luns_cmd, rc, out)
+
     def delete_consistencygroup(self, cg_name):
         delete_cg_cmd = ('-np', 'snap', '-group',
                          '-destroy', '-id', cg_name)
@@ -1556,7 +1582,7 @@ class CommandLineHelper(object):
 class EMCVnxCliBase(object):
     """This class defines the functions to use the native CLI functionality."""
 
-    VERSION = '05.02.01'
+    VERSION = '05.03.00'
     stats = {'driver_version': VERSION,
              'storage_protocol': None,
              'vendor_name': 'EMC',
@@ -2216,6 +2242,41 @@ class EMCVnxCliBase(object):
 
         return model_update, volumes
 
+    def update_consistencygroup(self, context,
+                                group,
+                                add_volumes,
+                                remove_volumes):
+        """Adds or removes LUN(s) to/from an existing consistency group"""
+        model_update = {'status': 'available'}
+        cg_name = group['id']
+        add_ids = [six.text_type(self.get_lun_id(vol))
+                   for vol in add_volumes] if add_volumes else []
+        remove_ids = [six.text_type(self.get_lun_id(vol))
+                      for vol in remove_volumes] if remove_volumes else []
+
+        data = self._client.get_consistency_group_by_name(cg_name)
+        ids_curr = data['Luns']
+        ids_later = []
+
+        if ids_curr:
+            ids_later.extend(ids_curr)
+        ids_later.extend(add_ids)
+        for remove_id in remove_ids:
+            if remove_id in ids_later:
+                ids_later.remove(remove_id)
+            else:
+                LOG.warning(_LW("LUN with id %(remove_id)s is not present "
+                                "in cg %(cg_name)s, skip it."),
+                            {'remove_id': remove_id, 'cg_name': cg_name})
+        # Remove all from cg
+        if not ids_later:
+            self._client.remove_luns_from_consistencygroup(cg_name,
+                                                           ids_curr)
+        else:
+            self._client.replace_luns_in_consistencygroup(cg_name,
+                                                          ids_later)
+        return model_update, None, None
+
     def create_cgsnapshot(self, driver, context, cgsnapshot):
         """Creates a cgsnapshot (snap group)."""
         cgsnapshot_id = cgsnapshot['id']