From 3742e51312ae3acce9c1d3d3276590207d39acad Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Tue, 9 Sep 2014 18:18:21 -0400 Subject: [PATCH] EMC VNX Direct Driver Consistency Group support 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 | 311 ++++++++++++++++++++- cinder/volume/drivers/emc/emc_cli_fc.py | 19 ++ cinder/volume/drivers/emc/emc_cli_iscsi.py | 19 ++ cinder/volume/drivers/emc/emc_vnx_cli.py | 292 ++++++++++++++++++- 4 files changed, 628 insertions(+), 13 deletions(-) diff --git a/cinder/tests/test_emc_vnxdirect.py b/cinder/tests/test_emc_vnxdirect.py index 5910ff44194..6d6200b997a 100644 --- a/cinder/tests/test_emc_vnxdirect.py +++ b/cinder/tests/test_emc_vnxdirect.py @@ -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.") diff --git a/cinder/volume/drivers/emc/emc_cli_fc.py b/cinder/volume/drivers/emc/emc_cli_fc.py index 0ce6f58cc8e..e4ccff55050 100644 --- a/cinder/volume/drivers/emc/emc_cli_fc.py +++ b/cinder/volume/drivers/emc/emc_cli_fc.py @@ -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 diff --git a/cinder/volume/drivers/emc/emc_cli_iscsi.py b/cinder/volume/drivers/emc/emc_cli_iscsi.py index c7d37d36879..6c69c768cdb 100644 --- a/cinder/volume/drivers/emc/emc_cli_iscsi.py +++ b/cinder/volume/drivers/emc/emc_cli_iscsi.py @@ -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 diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 7bc575b80a1..218d1ca40ea 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -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']