Merge "Infinidat: add storage assisted volume migration"
This commit is contained in:
commit
7a312ab1a8
@ -52,11 +52,16 @@ TEST_TARGET_PORTAL4 = '{}:{}'.format(TEST_IP_ADDRESS4, TEST_ISCSI_TCP_PORT2)
|
||||
TEST_FC_PROTOCOL = 'fc'
|
||||
TEST_ISCSI_PROTOCOL = 'iscsi'
|
||||
TEST_VOLUME_SOURCE_NAME = 'test-volume'
|
||||
TEST_VOLUME_TYPE = 'MASTER'
|
||||
TEST_VOLUME_SOURCE_ID = 12345
|
||||
TEST_VOLUME_METADATA = {'cinder_id': fake.VOLUME_ID}
|
||||
TEST_SNAPSHOT_SOURCE_NAME = 'test-snapshot'
|
||||
TEST_SNAPSHOT_SOURCE_ID = 67890
|
||||
TEST_SNAPSHOT_METADATA = {'cinder_id': fake.SNAPSHOT_ID}
|
||||
TEST_POOL_NAME = 'pool'
|
||||
TEST_POOL_NAME2 = 'pool2'
|
||||
TEST_SYSTEM_SERIAL = 123
|
||||
TEST_SYSTEM_SERIAL2 = 456
|
||||
|
||||
test_volume = mock.Mock(id=fake.VOLUME_ID, name_id=fake.VOLUME_ID, size=1,
|
||||
volume_type_id=fake.VOLUME_TYPE_ID, group_id=None,
|
||||
@ -113,7 +118,7 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
|
||||
configuration.SHARED_CONF_GROUP)
|
||||
self.override_config('san_password', 'password',
|
||||
configuration.SHARED_CONF_GROUP)
|
||||
self.override_config('infinidat_pool_name', 'pool')
|
||||
self.override_config('infinidat_pool_name', TEST_POOL_NAME)
|
||||
self.driver = infinidat.InfiniboxVolumeDriver(
|
||||
configuration=self.configuration)
|
||||
self._system = self._infinibox_mock()
|
||||
@ -139,9 +144,8 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
|
||||
self._mock_new_volume = mock.Mock()
|
||||
self._mock_volume.get_id.return_value = TEST_VOLUME_SOURCE_ID
|
||||
self._mock_volume.get_name.return_value = TEST_VOLUME_SOURCE_NAME
|
||||
self._mock_volume.get_type.return_value = 'MASTER'
|
||||
self._mock_volume.get_pool_name.return_value = (
|
||||
self.configuration.infinidat_pool_name)
|
||||
self._mock_volume.get_type.return_value = TEST_VOLUME_TYPE
|
||||
self._mock_volume.get_pool_name.return_value = TEST_POOL_NAME
|
||||
self._mock_volume.get_size.return_value = 1 * units.Gi
|
||||
self._mock_volume.has_children.return_value = False
|
||||
self._mock_volume.get_qos_policy.return_value = None
|
||||
@ -180,6 +184,7 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
|
||||
result.components.nodes.get_all.return_value = []
|
||||
result.qos_policies.create.return_value = self._mock_qos_policy
|
||||
result.qos_policies.safe_get.return_value = None
|
||||
result.get_serial.return_value = TEST_SYSTEM_SERIAL
|
||||
return result
|
||||
|
||||
def _raise_infinisdk(self, *args, **kwargs):
|
||||
@ -797,7 +802,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
||||
@mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
|
||||
def test_manage_existing_invalid_pool(self, *mocks):
|
||||
existing_ref = {'source-name': TEST_VOLUME_SOURCE_NAME}
|
||||
self._mock_volume.get_pool_name.return_value = 'invalid'
|
||||
self._mock_volume.get_pool_name.return_value = TEST_POOL_NAME2
|
||||
self.assertRaises(exception.InvalidConfigurationValue,
|
||||
self.driver.manage_existing,
|
||||
test_volume, existing_ref)
|
||||
@ -913,7 +918,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
||||
@mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs')
|
||||
def test_manage_existing_snapshot_invalid_pool(self, *mocks):
|
||||
existing_ref = {'source-name': TEST_SNAPSHOT_SOURCE_NAME}
|
||||
self._mock_volume.get_pool_name.return_value = 'invalid'
|
||||
self._mock_volume.get_pool_name.return_value = TEST_POOL_NAME2
|
||||
self.assertRaises(exception.InvalidConfigurationValue,
|
||||
self.driver.manage_existing_snapshot,
|
||||
test_snapshot, existing_ref)
|
||||
@ -1049,6 +1054,72 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
|
||||
test_volume)
|
||||
self.assertEqual(0, self._log.error.call_count)
|
||||
|
||||
@ddt.data(None, {})
|
||||
def test_migrate_volume_no_host(self, host):
|
||||
expected = False, None
|
||||
update = self.driver.migrate_volume(None, test_volume, host)
|
||||
self.assertEqual(expected, update)
|
||||
|
||||
@ddt.data(None, {})
|
||||
def test_migrate_volume_no_capabilities(self, capabilities):
|
||||
expected = False, None
|
||||
host = {'capabilities': capabilities}
|
||||
update = self.driver.migrate_volume(None, test_volume, host)
|
||||
self.assertEqual(expected, update)
|
||||
|
||||
@ddt.data(None, 123, 'location')
|
||||
def test_migrate_volume_invalid_location_info(self, location_info):
|
||||
expected = False, None
|
||||
capabilities = {'location_info': location_info}
|
||||
host = {'capabilities': capabilities}
|
||||
update = self.driver.migrate_volume(None, test_volume, host)
|
||||
self.assertEqual(expected, update)
|
||||
|
||||
def test_migrate_volume_invalid_driver(self):
|
||||
expected = False, None
|
||||
location_info = 'vendor:0:/path'
|
||||
capabilities = {'location_info': location_info}
|
||||
host = {'capabilities': capabilities}
|
||||
update = self.driver.migrate_volume(None, test_volume, host)
|
||||
self.assertEqual(expected, update)
|
||||
|
||||
def test_migrate_volume_invalid_serial(self):
|
||||
expected = False, None
|
||||
location_info = '%s:%s:%s' % (self.driver.__class__.__name__,
|
||||
TEST_SYSTEM_SERIAL2, TEST_POOL_NAME2)
|
||||
capabilities = {'location_info': location_info}
|
||||
host = {'capabilities': capabilities}
|
||||
update = self.driver.migrate_volume(None, test_volume, host)
|
||||
self.assertEqual(expected, update)
|
||||
|
||||
def test_migrate_volume_same_pool(self):
|
||||
expected = True, None
|
||||
location_info = '%s:%s:%s' % (self.driver.__class__.__name__,
|
||||
TEST_SYSTEM_SERIAL, TEST_POOL_NAME)
|
||||
capabilities = {'location_info': location_info}
|
||||
host = {'capabilities': capabilities}
|
||||
update = self.driver.migrate_volume(None, test_volume, host)
|
||||
self.assertEqual(expected, update)
|
||||
|
||||
def test_migrate_volume_no_pool(self):
|
||||
expected = False, None
|
||||
self._system.pools.safe_get.return_value = None
|
||||
location_info = '%s:%s:%s' % (self.driver.__class__.__name__,
|
||||
TEST_SYSTEM_SERIAL, TEST_POOL_NAME2)
|
||||
capabilities = {'location_info': location_info}
|
||||
host = {'capabilities': capabilities}
|
||||
update = self.driver.migrate_volume(None, test_volume, host)
|
||||
self.assertEqual(expected, update)
|
||||
|
||||
def test_migrate_volume(self):
|
||||
expected = True, None
|
||||
location_info = '%s:%s:%s' % (self.driver.__class__.__name__,
|
||||
TEST_SYSTEM_SERIAL, TEST_POOL_NAME2)
|
||||
capabilities = {'location_info': location_info}
|
||||
host = {'capabilities': capabilities}
|
||||
update = self.driver.migrate_volume(None, test_volume, host)
|
||||
self.assertEqual(expected, update)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase):
|
||||
|
@ -128,10 +128,11 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
||||
1.11 - fixed generic volume migration
|
||||
1.12 - fixed volume multi-attach
|
||||
1.13 - fixed consistency groups feature
|
||||
1.14 - added storage assisted volume migration
|
||||
|
||||
"""
|
||||
|
||||
VERSION = '1.13'
|
||||
VERSION = '1.14'
|
||||
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "INFINIDAT_CI"
|
||||
@ -573,6 +574,10 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
||||
def get_volume_stats(self, refresh=False):
|
||||
if self._volume_stats is None or refresh:
|
||||
pool = self._get_infinidat_pool()
|
||||
location_info = '%(driver)s:%(serial)s:%(pool)s' % {
|
||||
'driver': self.__class__.__name__,
|
||||
'serial': self._system.get_serial(),
|
||||
'pool': self.configuration.infinidat_pool_name}
|
||||
free_capacity_bytes = (pool.get_free_physical_capacity() /
|
||||
capacity.byte)
|
||||
physical_capacity_bytes = (pool.get_physical_capacity() /
|
||||
@ -587,6 +592,7 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
||||
vendor_name=VENDOR_NAME,
|
||||
driver_version=self.VERSION,
|
||||
storage_protocol=self._protocol,
|
||||
location_info=location_info,
|
||||
consistencygroup_support=False,
|
||||
total_capacity_gb=total_capacity_gb,
|
||||
free_capacity_gb=free_capacity_gb,
|
||||
@ -1325,3 +1331,44 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
|
||||
new_volume_name, volume_name, error)
|
||||
return model_update
|
||||
return {'_name_id': None, 'provider_location': None}
|
||||
|
||||
@infinisdk_to_cinder_exceptions
|
||||
def migrate_volume(self, ctxt, volume, host):
|
||||
"""Migrate a volume within the same InfiniBox system."""
|
||||
LOG.debug('Starting volume migration for volume %s to host %s',
|
||||
volume.name, host)
|
||||
if not (host and 'capabilities' in host):
|
||||
LOG.error('No capabilities found for host %s', host)
|
||||
return False, None
|
||||
capabilities = host['capabilities']
|
||||
if not (capabilities and 'location_info' in capabilities):
|
||||
LOG.error('No location info found for host %s', host)
|
||||
return False, None
|
||||
location = capabilities['location_info']
|
||||
try:
|
||||
driver, serial, pool = location.split(':')
|
||||
serial = int(serial)
|
||||
except (AttributeError, ValueError) as error:
|
||||
LOG.error('Invalid location info %s found for host %s: %s',
|
||||
location, host, error)
|
||||
return False, None
|
||||
if driver != self.__class__.__name__:
|
||||
LOG.debug('Unsupported storage driver %s found for host %s',
|
||||
driver, host)
|
||||
return False, None
|
||||
if serial != self._system.get_serial():
|
||||
LOG.error('Unable to migrate volume %s to remote host %s',
|
||||
volume.name, host)
|
||||
return False, None
|
||||
infinidat_volume = self._get_infinidat_volume(volume)
|
||||
if pool == infinidat_volume.get_pool_name():
|
||||
LOG.debug('Volume %s already migrated to pool %s',
|
||||
volume.name, pool)
|
||||
return True, None
|
||||
infinidat_pool = self._system.pools.safe_get(name=pool)
|
||||
if infinidat_pool is None:
|
||||
LOG.error('Destination pool %s not found on host %s', pool, host)
|
||||
return False, None
|
||||
infinidat_volume.move_pool(infinidat_pool)
|
||||
LOG.info('Migrated volume %s to pool %s', volume.name, pool)
|
||||
return True, None
|
||||
|
@ -26,6 +26,7 @@ Supported operations
|
||||
* Manage and unmanage volumes and snapshots.
|
||||
* List manageable volumes and snapshots.
|
||||
* Attach a volume to multiple instances at once (multi-attach).
|
||||
* Host and storage assisted volume migration.
|
||||
|
||||
External package installation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Infinidat: Added support for storage assisted volume migration
|
||||
within a same InfiniBox host (iSCSI and FC).
|
Loading…
Reference in New Issue
Block a user