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:
Avishay Traeger
2013-07-18 16:17:21 +03:00
parent b757c348b7
commit d7796ef737
8 changed files with 112 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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