From: peter_wang Date: Wed, 11 Feb 2015 03:04:40 +0000 (-0500) Subject: CG Modification Support in EMC VNX Cinder Driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=891ba802d4b033c80f77ea7c12d87bd284324d6b;p=openstack-build%2Fcinder-build.git CG Modification Support in EMC VNX Cinder Driver 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 --- diff --git a/cinder/tests/test_emc_vnxdirect.py b/cinder/tests/test_emc_vnxdirect.py index 89b2bbae4..20805c533 100644 --- a/cinder/tests/test_emc_vnxdirect.py +++ b/cinder/tests/test_emc_vnxdirect.py @@ -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 diff --git a/cinder/volume/drivers/emc/emc_cli_fc.py b/cinder/volume/drivers/emc/emc_cli_fc.py index 8d03cd19d..d7fd46f7c 100644 --- a/cinder/volume/drivers/emc/emc_cli_fc.py +++ b/cinder/volume/drivers/emc/emc_cli_fc.py @@ -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) diff --git a/cinder/volume/drivers/emc/emc_cli_iscsi.py b/cinder/volume/drivers/emc/emc_cli_iscsi.py index 6049bd269..fb7b1ce98 100644 --- a/cinder/volume/drivers/emc/emc_cli_iscsi.py +++ b/cinder/volume/drivers/emc/emc_cli_iscsi.py @@ -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) diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 705354c86..578c0afc9 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -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']