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]
|
||||
elif action == 'os-extend':
|
||||
assert body[action].keys() == ['new_size']
|
||||
elif action == 'os-migrate_volume':
|
||||
assert 'host' in body[action]
|
||||
assert 'force_host_copy' in body[action]
|
||||
else:
|
||||
raise AssertionError("Unexpected action: %s" % action)
|
||||
return (resp, {}, _body)
|
||||
|
@@ -91,3 +91,8 @@ class VolumesTest(utils.TestCase):
|
||||
def test_get_encryption_metadata(self):
|
||||
cs.volumes.get_encryption_metadata('1234')
|
||||
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]
|
||||
elif action == 'os-extend':
|
||||
assert body[action].keys() == ['new_size']
|
||||
elif action == 'os-migrate_volume':
|
||||
assert 'host' in body[action]
|
||||
assert 'force_host_copy' in body[action]
|
||||
else:
|
||||
raise AssertionError("Unexpected action: %s" % action)
|
||||
return (resp, {}, _body)
|
||||
|
@@ -94,3 +94,8 @@ class VolumesTest(utils.TestCase):
|
||||
def test_get_encryption_metadata(self):
|
||||
cs.volumes.get_encryption_metadata('1234')
|
||||
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)
|
||||
_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)
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -361,3 +370,28 @@ class VolumeManager(base.ManagerWithFind):
|
||||
:return: a dictionary of volume encryption metadata
|
||||
"""
|
||||
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))
|
||||
|
||||
|
||||
@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>',
|
||||
help='ID of the volume to backup.')
|
||||
@utils.arg('--container', metavar='<container>',
|
||||
|
@@ -112,6 +112,15 @@ class Volume(base.Resource):
|
||||
|
||||
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):
|
||||
"""Manage :class:`Volume` resources."""
|
||||
@@ -343,3 +352,28 @@ class VolumeManager(base.ManagerWithFind):
|
||||
:return: a dictionary of volume encryption metadata
|
||||
"""
|
||||
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