# 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
'-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):
return _safe_get
+@ddt.ddt
class EMCVNXCLIDriverISCSITestCase(DriverTestCaseBase):
def generate_driver(self, conf):
return emc_cli_iscsi.EMCCLIISCSIDriver(configuration=conf)
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]
'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]
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(
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]
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]
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,
'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(
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 '
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),
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(
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 '
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 = [
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),
for error_code in error_codes])
+class VNXMigrationRate(_Enum):
+ LOW = 'low'
+ MEDIUM = 'medium'
+ HIGH = 'high'
+ ASAP = 'asap'
+
+
class VNXProvisionEnum(_Enum):
THIN = 'thin'
THICK = 'thick'
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,
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
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
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:
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')
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(),
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(),
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)