Implement ability to migrate volume
Implements ability to call migrate_volume and migrate_volume_completion APIs. The former includes shell code while the latter is called by Nova and should not be invoked via shell. Change-Id: I6e81d7a6321f367a356f0a0dee385221363a4227
This commit is contained in:
@@ -308,6 +308,9 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
assert 'status' in body[action]
|
assert 'status' in body[action]
|
||||||
elif action == 'os-extend':
|
elif action == 'os-extend':
|
||||||
assert body[action].keys() == ['new_size']
|
assert body[action].keys() == ['new_size']
|
||||||
|
elif action == 'os-migrate_volume':
|
||||||
|
assert 'host' in body[action]
|
||||||
|
assert 'force_host_copy' in body[action]
|
||||||
else:
|
else:
|
||||||
raise AssertionError("Unexpected action: %s" % action)
|
raise AssertionError("Unexpected action: %s" % action)
|
||||||
return (resp, {}, _body)
|
return (resp, {}, _body)
|
||||||
|
@@ -91,3 +91,8 @@ class VolumesTest(utils.TestCase):
|
|||||||
def test_get_encryption_metadata(self):
|
def test_get_encryption_metadata(self):
|
||||||
cs.volumes.get_encryption_metadata('1234')
|
cs.volumes.get_encryption_metadata('1234')
|
||||||
cs.assert_called('GET', '/volumes/1234/encryption')
|
cs.assert_called('GET', '/volumes/1234/encryption')
|
||||||
|
|
||||||
|
def test_migrate(self):
|
||||||
|
v = cs.volumes.get('1234')
|
||||||
|
cs.volumes.migrate_volume(v, 'dest', False)
|
||||||
|
cs.assert_called('POST', '/volumes/1234/action')
|
||||||
|
@@ -315,6 +315,9 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
assert 'status' in body[action]
|
assert 'status' in body[action]
|
||||||
elif action == 'os-extend':
|
elif action == 'os-extend':
|
||||||
assert body[action].keys() == ['new_size']
|
assert body[action].keys() == ['new_size']
|
||||||
|
elif action == 'os-migrate_volume':
|
||||||
|
assert 'host' in body[action]
|
||||||
|
assert 'force_host_copy' in body[action]
|
||||||
else:
|
else:
|
||||||
raise AssertionError("Unexpected action: %s" % action)
|
raise AssertionError("Unexpected action: %s" % action)
|
||||||
return (resp, {}, _body)
|
return (resp, {}, _body)
|
||||||
|
@@ -94,3 +94,8 @@ class VolumesTest(utils.TestCase):
|
|||||||
def test_get_encryption_metadata(self):
|
def test_get_encryption_metadata(self):
|
||||||
cs.volumes.get_encryption_metadata('1234')
|
cs.volumes.get_encryption_metadata('1234')
|
||||||
cs.assert_called('GET', '/volumes/1234/encryption')
|
cs.assert_called('GET', '/volumes/1234/encryption')
|
||||||
|
|
||||||
|
def test_migrate(self):
|
||||||
|
v = cs.volumes.get('1234')
|
||||||
|
cs.volumes.migrate_volume(v, 'dest', False)
|
||||||
|
cs.assert_called('POST', '/volumes/1234/action')
|
||||||
|
@@ -1046,3 +1046,17 @@ def do_encryption_type_create(cs, args):
|
|||||||
|
|
||||||
result = cs.volume_encryption_types.create(volume_type, body)
|
result = cs.volume_encryption_types.create(volume_type, body)
|
||||||
_print_volume_encryption_type_list([result])
|
_print_volume_encryption_type_list([result])
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('volume', metavar='<volume>', help='ID of the volume to migrate')
|
||||||
|
@utils.arg('host', metavar='<host>', help='Destination host')
|
||||||
|
@utils.arg('--force-host-copy', metavar='<True|False>',
|
||||||
|
help='Optional flag to force the use of the generic '
|
||||||
|
'host-based migration mechanism, bypassing driver '
|
||||||
|
'optimizations (Default=False).',
|
||||||
|
default=False)
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_migrate(cs, args):
|
||||||
|
"""Migrate the volume to the new host."""
|
||||||
|
volume = _find_volume(cs, args.volume)
|
||||||
|
volume.migrate_volume(args.host, args.force_host_copy)
|
||||||
|
@@ -114,6 +114,15 @@ class Volume(base.Resource):
|
|||||||
|
|
||||||
self.manager.extend(self, volume, new_size)
|
self.manager.extend(self, volume, new_size)
|
||||||
|
|
||||||
|
def migrate_volume(self, host, force_host_copy):
|
||||||
|
"""Migrate the volume to a new host."""
|
||||||
|
self.manager.migrate_volume(self, host, force_host_copy)
|
||||||
|
|
||||||
|
# def migrate_volume_completion(self, old_volume, new_volume, error):
|
||||||
|
# """Complete the migration of the volume."""
|
||||||
|
# self.manager.migrate_volume_completion(self, old_volume,
|
||||||
|
# new_volume, error)
|
||||||
|
|
||||||
|
|
||||||
class VolumeManager(base.ManagerWithFind):
|
class VolumeManager(base.ManagerWithFind):
|
||||||
"""
|
"""
|
||||||
@@ -361,3 +370,28 @@ class VolumeManager(base.ManagerWithFind):
|
|||||||
:return: a dictionary of volume encryption metadata
|
:return: a dictionary of volume encryption metadata
|
||||||
"""
|
"""
|
||||||
return self._get("/volumes/%s/encryption" % volume_id)._info
|
return self._get("/volumes/%s/encryption" % volume_id)._info
|
||||||
|
|
||||||
|
def migrate_volume(self, volume, host, force_host_copy):
|
||||||
|
"""Migrate volume to new host.
|
||||||
|
|
||||||
|
:param volume: The :class:`Volume` to migrate
|
||||||
|
:param host: The destination host
|
||||||
|
:param force_host_copy: Skip driver optimizations
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._action('os-migrate_volume',
|
||||||
|
volume,
|
||||||
|
{'host': host, 'force_host_copy': force_host_copy})
|
||||||
|
|
||||||
|
def migrate_volume_completion(self, old_volume, new_volume, error):
|
||||||
|
"""Complete the migration from the old volume to the temp new one.
|
||||||
|
|
||||||
|
:param old_volume: The original :class:`Volume` in the migration
|
||||||
|
:param new_volume: The new temporary :class:`Volume` in the migration
|
||||||
|
:param error: Inform of an error to cause migration cleanup
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_volume_id = base.getid(new_volume)
|
||||||
|
return self._action('os-migrate_volume_completion',
|
||||||
|
old_volume,
|
||||||
|
{'new_volume': new_volume_id, 'error': error})[1]
|
||||||
|
@@ -800,6 +800,20 @@ def do_upload_to_image(cs, args):
|
|||||||
args.disk_format))
|
args.disk_format))
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('volume', metavar='<volume>', help='ID of the volume to migrate')
|
||||||
|
@utils.arg('host', metavar='<host>', help='Destination host')
|
||||||
|
@utils.arg('--force-host-copy', metavar='<True|False>',
|
||||||
|
help='Optional flag to force the use of the generic '
|
||||||
|
'host-based migration mechanism, bypassing driver '
|
||||||
|
'optimizations (Default=False).',
|
||||||
|
default=False)
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_migrate(cs, args):
|
||||||
|
"""Migrate the volume to the new host."""
|
||||||
|
volume = _find_volume(cs, args.volume)
|
||||||
|
volume.migrate_volume(args.host, args.force_host_copy)
|
||||||
|
|
||||||
|
|
||||||
@utils.arg('volume', metavar='<volume>',
|
@utils.arg('volume', metavar='<volume>',
|
||||||
help='ID of the volume to backup.')
|
help='ID of the volume to backup.')
|
||||||
@utils.arg('--container', metavar='<container>',
|
@utils.arg('--container', metavar='<container>',
|
||||||
|
@@ -112,6 +112,15 @@ class Volume(base.Resource):
|
|||||||
|
|
||||||
self.manager.extend(self, volume, new_size)
|
self.manager.extend(self, volume, new_size)
|
||||||
|
|
||||||
|
def migrate_volume(self, host, force_host_copy):
|
||||||
|
"""Migrate the volume to a new host."""
|
||||||
|
self.manager.migrate_volume(self, host, force_host_copy)
|
||||||
|
|
||||||
|
# def migrate_volume_completion(self, old_volume, new_volume, error):
|
||||||
|
# """Complete the migration of the volume."""
|
||||||
|
# self.manager.migrate_volume_completion(self, old_volume,
|
||||||
|
# new_volume, error)
|
||||||
|
|
||||||
|
|
||||||
class VolumeManager(base.ManagerWithFind):
|
class VolumeManager(base.ManagerWithFind):
|
||||||
"""Manage :class:`Volume` resources."""
|
"""Manage :class:`Volume` resources."""
|
||||||
@@ -343,3 +352,28 @@ class VolumeManager(base.ManagerWithFind):
|
|||||||
:return: a dictionary of volume encryption metadata
|
:return: a dictionary of volume encryption metadata
|
||||||
"""
|
"""
|
||||||
return self._get("/volumes/%s/encryption" % volume_id)._info
|
return self._get("/volumes/%s/encryption" % volume_id)._info
|
||||||
|
|
||||||
|
def migrate_volume(self, volume, host, force_host_copy):
|
||||||
|
"""Migrate volume to new host.
|
||||||
|
|
||||||
|
:param volume: The :class:`Volume` to migrate
|
||||||
|
:param host: The destination host
|
||||||
|
:param force_host_copy: Skip driver optimizations
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._action('os-migrate_volume',
|
||||||
|
volume,
|
||||||
|
{'host': host, 'force_host_copy': force_host_copy})
|
||||||
|
|
||||||
|
def migrate_volume_completion(self, old_volume, new_volume, error):
|
||||||
|
"""Complete the migration from the old volume to the temp new one.
|
||||||
|
|
||||||
|
:param old_volume: The original :class:`Volume` in the migration
|
||||||
|
:param new_volume: The new temporary :class:`Volume` in the migration
|
||||||
|
:param error: Inform of an error to cause migration cleanup
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_volume_id = base.getid(new_volume)
|
||||||
|
return self._action('os-migrate_volume_completion',
|
||||||
|
old_volume,
|
||||||
|
{'new_volume': new_volume_id, 'error': error})[1]
|
||||||
|
Reference in New Issue
Block a user