From d7796ef737b0b9c70fabf1416b42ad42c2d27b4a Mon Sep 17 00:00:00 2001 From: Avishay Traeger Date: Thu, 18 Jul 2013 16:17:21 +0300 Subject: [PATCH] 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 --- cinderclient/tests/v1/fakes.py | 3 +++ cinderclient/tests/v1/test_volumes.py | 5 ++++ cinderclient/tests/v2/fakes.py | 3 +++ cinderclient/tests/v2/test_volumes.py | 5 ++++ cinderclient/v1/shell.py | 14 +++++++++++ cinderclient/v1/volumes.py | 34 +++++++++++++++++++++++++++ cinderclient/v2/shell.py | 14 +++++++++++ cinderclient/v2/volumes.py | 34 +++++++++++++++++++++++++++ 8 files changed, 112 insertions(+) diff --git a/cinderclient/tests/v1/fakes.py b/cinderclient/tests/v1/fakes.py index 69f3d1fb5..40ddd4cdc 100644 --- a/cinderclient/tests/v1/fakes.py +++ b/cinderclient/tests/v1/fakes.py @@ -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) diff --git a/cinderclient/tests/v1/test_volumes.py b/cinderclient/tests/v1/test_volumes.py index 2da750947..bb7343810 100644 --- a/cinderclient/tests/v1/test_volumes.py +++ b/cinderclient/tests/v1/test_volumes.py @@ -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') diff --git a/cinderclient/tests/v2/fakes.py b/cinderclient/tests/v2/fakes.py index 745fd393f..474d10c40 100644 --- a/cinderclient/tests/v2/fakes.py +++ b/cinderclient/tests/v2/fakes.py @@ -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) diff --git a/cinderclient/tests/v2/test_volumes.py b/cinderclient/tests/v2/test_volumes.py index 594bba4e9..73ee4a856 100644 --- a/cinderclient/tests/v2/test_volumes.py +++ b/cinderclient/tests/v2/test_volumes.py @@ -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') diff --git a/cinderclient/v1/shell.py b/cinderclient/v1/shell.py index 91c204631..12331ef77 100644 --- a/cinderclient/v1/shell.py +++ b/cinderclient/v1/shell.py @@ -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='', help='ID of the volume to migrate') +@utils.arg('host', metavar='', help='Destination host') +@utils.arg('--force-host-copy', metavar='', + 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) diff --git a/cinderclient/v1/volumes.py b/cinderclient/v1/volumes.py index 6d63e723e..6804d59bb 100644 --- a/cinderclient/v1/volumes.py +++ b/cinderclient/v1/volumes.py @@ -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] diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index 6e1adbc8b..9515bcaf6 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -800,6 +800,20 @@ def do_upload_to_image(cs, args): args.disk_format)) +@utils.arg('volume', metavar='', help='ID of the volume to migrate') +@utils.arg('host', metavar='', help='Destination host') +@utils.arg('--force-host-copy', metavar='', + 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='', help='ID of the volume to backup.') @utils.arg('--container', metavar='', diff --git a/cinderclient/v2/volumes.py b/cinderclient/v2/volumes.py index be4a9e662..2ec885d01 100644 --- a/cinderclient/v2/volumes.py +++ b/cinderclient/v2/volumes.py @@ -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]