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']