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
This commit is contained in:
peter_wang 2015-12-28 13:56:18 +08:00 committed by peter_wang
parent d428d9bc84
commit 719bedd625
5 changed files with 105 additions and 24 deletions

View File

@ -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),

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -0,0 +1,3 @@
---
features:
- Configrable migration rate in VNX driver via metadata