From 719bedd6254b4203e19fa7467d8fa524e673ae56 Mon Sep 17 00:00:00 2001 From: peter_wang Date: Mon, 28 Dec 2015 13:56:18 +0800 Subject: [PATCH] VNX: Allow set migrate rate when migrating volumes VNX driver is leveraging LUN migration functionality when clone/migrate/retype volume. The migration rate is set to 'high' by hard code. This patch will allow admin to control the migration rate by adding volume metadata before triggering any operations involving LUN migration. The required metadata key is 'migrate_rate', available metadata values are 'high', 'asap', 'low' and 'medium'. DocImpact Change-Id: Ie9a875dd63dd60351e46a66c99977d9b6fd23244 Closes-Bug: 1529553 --- cinder/tests/unit/test_emc_vnx.py | 79 +++++++++++++++---- cinder/volume/drivers/emc/emc_cli_fc.py | 1 + cinder/volume/drivers/emc/emc_cli_iscsi.py | 1 + cinder/volume/drivers/emc/emc_vnx_cli.py | 45 ++++++++--- ...rable-migration-rate-5e0a2235777c314f.yaml | 3 + 5 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 releasenotes/notes/vnx-configurable-migration-rate-5e0a2235777c314f.yaml diff --git a/cinder/tests/unit/test_emc_vnx.py b/cinder/tests/unit/test_emc_vnx.py index 61644d340..38046cb89 100644 --- a/cinder/tests/unit/test_emc_vnx.py +++ b/cinder/tests/unit/test_emc_vnx.py @@ -12,6 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import ddt import json import os import re @@ -707,9 +708,9 @@ class EMCVNXCLIDriverTestData(object): '-initialTier', tier, '-tieringPolicy', policy) - def MIGRATION_CMD(self, src_id=1, dest_id=1): + def MIGRATION_CMD(self, src_id=1, dest_id=1, rate='high'): cmd = ("migrate", "-start", "-source", src_id, "-dest", dest_id, - "-rate", "high", "-o") + "-rate", rate, "-o") return cmd def MIGRATION_VERIFY_CMD(self, src_id): @@ -1621,6 +1622,7 @@ class DriverTestCaseBase(test.TestCase): return _safe_get +@ddt.ddt class EMCVNXCLIDriverISCSITestCase(DriverTestCaseBase): def generate_driver(self, conf): return emc_cli_iscsi.EMCCLIISCSIDriver(configuration=conf) @@ -2018,7 +2020,7 @@ Time Remaining: 0 second(s) 23)]] fake_cli = self.driverSetup(commands, results) fakehost = {'capabilities': {'location_info': - "unit_test_pool2|fake_serial", + 'unit_test_pool2|fake_serial', 'storage_protocol': 'iSCSI'}} ret = self.driver.migrate_volume(None, self.testData.test_volume, fakehost)[0] @@ -2063,7 +2065,7 @@ Time Remaining: 0 second(s) 'currently migrating', 23)]] fake_cli = self.driverSetup(commands, results) fake_host = {'capabilities': {'location_info': - "unit_test_pool2|fake_serial", + 'unit_test_pool2|fake_serial', 'storage_protocol': 'iSCSI'}} ret = self.driver.migrate_volume(None, self.testData.test_volume, fake_host)[0] @@ -2078,6 +2080,51 @@ Time Remaining: 0 second(s) poll=False)] fake_cli.assert_has_calls(expect_cmd) + @mock.patch("cinder.volume.drivers.emc.emc_vnx_cli." + "CommandLineHelper.create_lun_by_cmd", + mock.Mock( + return_value={'lun_id': 1})) + @mock.patch( + "cinder.volume.drivers.emc.emc_vnx_cli.EMCVnxCliBase.get_lun_id", + mock.Mock( + side_effect=[1, 1])) + def test_volume_migration_with_rate(self): + + test_volume_asap = self.testData.test_volume.copy() + test_volume_asap.update({'metadata': {'migrate_rate': 'asap'}}) + commands = [self.testData.MIGRATION_CMD(rate="asap"), + self.testData.MIGRATION_VERIFY_CMD(1)] + FAKE_MIGRATE_PROPERTY = """\ +Source LU Name: volume-f6247ae1-8e1c-4927-aa7e-7f8e272e5c3d +Source LU ID: 63950 +Dest LU Name: volume-f6247ae1-8e1c-4927-aa7e-7f8e272e5c3d_dest +Dest LU ID: 136 +Migration Rate: ASAP +Current State: MIGRATED +Percent Complete: 100 +Time Remaining: 0 second(s) +""" + results = [SUCCEED, + [(FAKE_MIGRATE_PROPERTY, 0), + ('The specified source LUN is not ' + 'currently migrating', 23)]] + fake_cli = self.driverSetup(commands, results) + fake_host = {'capabilities': {'location_info': + 'unit_test_pool2|fake_serial', + 'storage_protocol': 'iSCSI'}} + ret = self.driver.migrate_volume(None, test_volume_asap, + fake_host)[0] + self.assertTrue(ret) + # verification + expect_cmd = [mock.call(*self.testData.MIGRATION_CMD(rate='asap'), + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=False)] + fake_cli.assert_has_calls(expect_cmd) + @mock.patch("cinder.volume.drivers.emc.emc_vnx_cli." "CommandLineHelper.create_lun_by_cmd", mock.Mock( @@ -2106,7 +2153,7 @@ Time Remaining: 0 second(s) 23)]] fake_cli = self.driverSetup(commands, results) fakehost = {'capabilities': {'location_info': - "unit_test_pool2|fake_serial", + 'unit_test_pool2|fake_serial', 'storage_protocol': 'iSCSI'}} ret = self.driver.migrate_volume(None, self.testData.test_volume5, fakehost)[0] @@ -2134,7 +2181,7 @@ Time Remaining: 0 second(s) results = [FAKE_ERROR_RETURN] fake_cli = self.driverSetup(commands, results) fakehost = {'capabilities': {'location_info': - "unit_test_pool2|fake_serial", + 'unit_test_pool2|fake_serial', 'storage_protocol': 'iSCSI'}} ret = self.driver.migrate_volume(None, self.testData.test_volume, fakehost)[0] @@ -2166,7 +2213,7 @@ Time Remaining: 0 second(s) SUCCEED] fake_cli = self.driverSetup(commands, results) fake_host = {'capabilities': {'location_info': - "unit_test_pool2|fake_serial", + 'unit_test_pool2|fake_serial', 'storage_protocol': 'iSCSI'}} self.assertRaisesRegex(exception.VolumeBackendAPIException, @@ -2219,7 +2266,7 @@ Time Remaining: 0 second(s) 'currently migrating', 23)]] fake_cli = self.driverSetup(commands, results) fake_host = {'capabilities': {'location_info': - "unit_test_pool2|fake_serial", + 'unit_test_pool2|fake_serial', 'storage_protocol': 'iSCSI'}} vol = EMCVNXCLIDriverTestData.convert_volume( @@ -2744,18 +2791,20 @@ Time Remaining: 0 second(s) fake_cli.assert_has_calls(expect_cmd) - def test_create_volume_from_snapshot(self): + @ddt.data('high', 'asap', 'low', 'medium') + def test_create_volume_from_snapshot(self, migrate_rate): test_snapshot = EMCVNXCLIDriverTestData.convert_snapshot( self.testData.test_snapshot) test_volume = EMCVNXCLIDriverTestData.convert_volume( self.testData.test_volume2) + test_volume.metadata = {'migrate_rate': migrate_rate} cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD( build_migration_dest_name(test_volume.name)) cmd_dest_np = self.testData.LUN_PROPERTY_ALL_CMD( build_migration_dest_name(test_volume.name)) output_dest = self.testData.LUN_PROPERTY( build_migration_dest_name(test_volume.name)) - cmd_migrate = self.testData.MIGRATION_CMD(1, 1) + cmd_migrate = self.testData.MIGRATION_CMD(1, 1, rate=migrate_rate) output_migrate = ("", 0) cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1) output_migrate_verify = (r'The specified source LUN ' @@ -2785,7 +2834,7 @@ Time Remaining: 0 second(s) mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( build_migration_dest_name(test_volume.name)), poll=False), - mock.call(*self.testData.MIGRATION_CMD(1, 1), + mock.call(*self.testData.MIGRATION_CMD(1, 1, rate=migrate_rate), retry_disable=True, poll=True), mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), @@ -2945,7 +2994,8 @@ Time Remaining: 0 second(s) mock.call(*self.testData.LUN_DELETE_CMD('volume-2'))] fake_cli.assert_has_calls(expect_cmd) - def test_create_cloned_volume(self): + @ddt.data('high', 'asap', 'low', 'medium') + def test_create_cloned_volume(self, migrate_rate): cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD( build_migration_dest_name('volume-2')) cmd_dest_p = self.testData.LUN_PROPERTY_ALL_CMD( @@ -2954,7 +3004,7 @@ Time Remaining: 0 second(s) build_migration_dest_name('volume-2')) cmd_clone = self.testData.LUN_PROPERTY_ALL_CMD("volume-2") output_clone = self.testData.LUN_PROPERTY("volume-2") - cmd_migrate = self.testData.MIGRATION_CMD(1, 1) + cmd_migrate = self.testData.MIGRATION_CMD(1, 1, rate=migrate_rate) output_migrate = ("", 0) cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1) output_migrate_verify = (r'The specified source LUN ' @@ -2970,6 +3020,7 @@ Time Remaining: 0 second(s) volume = EMCVNXCLIDriverTestData.convert_volume(volume) # Make sure this size is used volume.size = 10 + volume.metadata = {'migrate_rate': migrate_rate} self.driver.create_cloned_volume(volume, self.testData.test_volume) tmp_snap = 'tmp-snap-' + volume.id expect_cmd = [ @@ -2990,7 +3041,7 @@ Time Remaining: 0 second(s) build_migration_dest_name('volume-2')), poll=False), mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( build_migration_dest_name('volume-2')), poll=False), - mock.call(*self.testData.MIGRATION_CMD(1, 1), + mock.call(*self.testData.MIGRATION_CMD(1, 1, rate=migrate_rate), poll=True, retry_disable=True), mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), diff --git a/cinder/volume/drivers/emc/emc_cli_fc.py b/cinder/volume/drivers/emc/emc_cli_fc.py index 2d32e9dcd..ee0e2c9a7 100644 --- a/cinder/volume/drivers/emc/emc_cli_fc.py +++ b/cinder/volume/drivers/emc/emc_cli_fc.py @@ -60,6 +60,7 @@ class EMCCLIFCDriver(driver.FibreChannelDriver): Support efficient non-disruptive backup 7.0.0 - Clone consistency group support Replication v2 support(managed) + Configurable migration rate support """ def __init__(self, *args, **kwargs): diff --git a/cinder/volume/drivers/emc/emc_cli_iscsi.py b/cinder/volume/drivers/emc/emc_cli_iscsi.py index c495cf7bc..0e1a062cf 100644 --- a/cinder/volume/drivers/emc/emc_cli_iscsi.py +++ b/cinder/volume/drivers/emc/emc_cli_iscsi.py @@ -58,6 +58,7 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver): Support efficient non-disruptive backup 7.0.0 - Clone consistency group support Replication v2 support(managed) + Configurable migration rate support """ def __init__(self, *args, **kwargs): diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 346e9cca8..ff08f9d2a 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -272,6 +272,13 @@ class VNXError(_Enum): for error_code in error_codes]) +class VNXMigrationRate(_Enum): + LOW = 'low' + MEDIUM = 'medium' + HIGH = 'high' + ASAP = 'asap' + + class VNXProvisionEnum(_Enum): THIN = 'thin' THICK = 'thick' @@ -1269,11 +1276,11 @@ class CommandLineHelper(object): return rc - def migrate_lun(self, src_id, dst_id): + def migrate_lun(self, src_id, dst_id, rate=VNXMigrationRate.HIGH): command_migrate_lun = ('migrate', '-start', '-source', src_id, '-dest', dst_id, - '-rate', 'high', + '-rate', rate, '-o') # SP HA is not supported by LUN migration out, rc = self.command_execute(*command_migrate_lun, @@ -1286,9 +1293,10 @@ class CommandLineHelper(object): return rc def migrate_lun_without_verification(self, src_id, dst_id, - dst_name=None): + dst_name=None, + rate=VNXMigrationRate.HIGH): try: - self.migrate_lun(src_id, dst_id) + self.migrate_lun(src_id, dst_id, rate) return True except exception.EMCVnxCLICmdError as ex: migration_succeed = False @@ -1398,9 +1406,10 @@ class CommandLineHelper(object): def migrate_lun_with_verification(self, src_id, dst_id, - dst_name=None): + dst_name=None, + rate=VNXMigrationRate.HIGH): migration_started = self.migrate_lun_without_verification( - src_id, dst_id, dst_name) + src_id, dst_id, dst_name, rate) if not migration_started: return False @@ -2368,6 +2377,18 @@ class EMCVnxCliBase(object): specs = self.get_volumetype_extraspecs(volume) self._get_and_validate_extra_specs(specs) + def _get_migration_rate(self, volume): + metadata = self._get_volume_metadata(volume) + rate = metadata.get('migrate_rate', VNXMigrationRate.HIGH) + if rate: + if rate.lower() in VNXMigrationRate.get_all(): + return rate.lower() + else: + LOG.warning(_LW('Unknown migration rate specified, ' + 'using [high] as migration rate.')) + + return VNXMigrationRate.HIGH + def _get_and_validate_extra_specs(self, specs): """Checks on extra specs combinations.""" if "storagetype:pool" in specs: @@ -2614,7 +2635,8 @@ class EMCVnxCliBase(object): dst_id = data['lun_id'] moved = self._client.migrate_lun_with_verification( - src_id, dst_id, new_volume_name) + src_id, dst_id, new_volume_name, + rate=self._get_migration_rate(volume)) lun_type = self._extract_provider_location( volume['provider_location'], 'type') @@ -2929,6 +2951,7 @@ class EMCVnxCliBase(object): new_lun_id, 'smp', base_lun_name) volume_metadata['snapcopy'] = 'True' else: + store_spec.update({'rate': self._get_migration_rate(volume)}) work_flow.add(CreateSMPTask(), AttachSnapTask(), CreateDestLunTask(), @@ -3005,6 +3028,7 @@ class EMCVnxCliBase(object): new_lun_id, 'smp', base_lun_name) else: # snapcopy feature disabled, need to migrate + store_spec.update({'rate': self._get_migration_rate(volume)}) work_flow.add(CreateSnapshotTask(), CreateSMPTask(), AttachSnapTask(), @@ -4480,15 +4504,16 @@ class MigrateLunTask(task.Task): rebind=rebind) self.wait_for_completion = wait_for_completion - def execute(self, client, new_smp_id, lun_data, *args, **kwargs): + def execute(self, client, new_smp_id, lun_data, rate=VNXMigrationRate.HIGH, + *args, **kwargs): LOG.debug('MigrateLunTask.execute') dest_vol_lun_id = lun_data['lun_id'] - LOG.debug('Migrating Mount Point Volume ID: %s', new_smp_id) if self.wait_for_completion: migrated = client.migrate_lun_with_verification(new_smp_id, dest_vol_lun_id, - None) + None, + rate) else: migrated = client.migrate_lun_without_verification( new_smp_id, dest_vol_lun_id, None) diff --git a/releasenotes/notes/vnx-configurable-migration-rate-5e0a2235777c314f.yaml b/releasenotes/notes/vnx-configurable-migration-rate-5e0a2235777c314f.yaml new file mode 100644 index 000000000..8c1d3b35d --- /dev/null +++ b/releasenotes/notes/vnx-configurable-migration-rate-5e0a2235777c314f.yaml @@ -0,0 +1,3 @@ +--- +features: + - Configrable migration rate in VNX driver via metadata -- 2.45.2