From e0c7d2c6731744374ea83385305624254454b0ed Mon Sep 17 00:00:00 2001 From: ShaoHe Feng Date: Mon, 29 Feb 2016 23:07:33 +0100 Subject: [PATCH] Add two server-migration commands and bump migration-list command 1. Add two new commands Add nova client server-migration-list and server-migration-show 2. Bump and old command Add migration_type field for migration-list command Partially implements blueprint live-migration-progress-report Depends-On: Ia92ecbe3c99082e3a34adf4fd29041b1a95ef21e Change-Id: I071198fa9ba0699383bdebf4fab54714a435e6c3 --- novaclient/__init__.py | 2 +- .../unit/fixture_data/server_migrations.py | 51 ++++++++++ .../tests/unit/v2/contrib/test_migrations.py | 12 +++ novaclient/tests/unit/v2/fakes.py | 95 +++++++++++++++---- .../tests/unit/v2/test_server_migrations.py | 49 ++++++++++ novaclient/tests/unit/v2/test_shell.py | 14 +++ novaclient/v2/contrib/migrations.py | 15 ++- novaclient/v2/server_migrations.py | 27 +++++- novaclient/v2/shell.py | 36 +++++++ 9 files changed, 279 insertions(+), 22 deletions(-) diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 509d2a20d..c7bff3351 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.22") +API_MAX_VERSION = api_versions.APIVersion("2.23") diff --git a/novaclient/tests/unit/fixture_data/server_migrations.py b/novaclient/tests/unit/fixture_data/server_migrations.py index 3aed49a54..93f299fbe 100644 --- a/novaclient/tests/unit/fixture_data/server_migrations.py +++ b/novaclient/tests/unit/fixture_data/server_migrations.py @@ -25,3 +25,54 @@ class Fixture(base.Fixture): self.requests.register_uri('POST', url, status_code=202, headers=self.json_headers) + + get_migrations = {'migrations': [ + { + "created_at": "2016-01-29T13:42:02.000000", + "dest_compute": "compute2", + "dest_host": "1.2.3.4", + "dest_node": "node2", + "id": 1, + "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", + "source_compute": "compute1", + "source_node": "node1", + "status": "running", + "memory_total_bytes": 123456, + "memory_processed_bytes": 12345, + "memory_remaining_bytes": 120000, + "disk_total_bytes": 234567, + "disk_processed_bytes": 23456, + "disk_remaining_bytes": 230000, + "updated_at": "2016-01-29T13:42:02.000000" + }]} + + url = self.url('1234', 'migrations') + self.requests.register_uri('GET', url, + status_code=200, + json=get_migrations, + headers=self.json_headers) + + get_migration = {'migration': { + "created_at": "2016-01-29T13:42:02.000000", + "dest_compute": "compute2", + "dest_host": "1.2.3.4", + "dest_node": "node2", + "id": 1, + "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", + "source_compute": "compute1", + "source_node": "node1", + "status": "running", + "memory_total_bytes": 123456, + "memory_processed_bytes": 12345, + "memory_remaining_bytes": 120000, + "disk_total_bytes": 234567, + "disk_processed_bytes": 23456, + "disk_remaining_bytes": 230000, + "updated_at": "2016-01-29T13:42:02.000000" + }} + + url = self.url('1234', 'migrations', '1') + self.requests.register_uri('GET', url, + status_code=200, + json=get_migration, + headers=self.json_headers) diff --git a/novaclient/tests/unit/v2/contrib/test_migrations.py b/novaclient/tests/unit/v2/contrib/test_migrations.py index 29cac5d44..8d12633e1 100644 --- a/novaclient/tests/unit/v2/contrib/test_migrations.py +++ b/novaclient/tests/unit/v2/contrib/test_migrations.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from novaclient import api_versions from novaclient import extension from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes @@ -30,6 +31,17 @@ class MigrationsTest(utils.TestCase): cs.assert_called('GET', '/os-migrations') for m in ml: self.assertIsInstance(m, migrations.Migration) + self.assertRaises(AttributeError, getattr, m, "migration_type") + + def test_list_migrations_v223(self): + cs = fakes.FakeClient(extensions=extensions, + api_version=api_versions.APIVersion("2.23")) + ml = cs.migrations.list() + self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) + cs.assert_called('GET', '/os-migrations') + for m in ml: + self.assertIsInstance(m, migrations.Migration) + self.assertEqual(m.migration_type, 'live-migration') def test_list_migrations_with_filters(self): ml = cs.migrations.list('host1', 'finished', 'child1') diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index d3d1e4051..85f4b74bc 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -23,8 +23,10 @@ import six from six.moves.urllib import parse import novaclient +from novaclient import api_versions from novaclient import client as base_client from novaclient import exceptions +from novaclient.i18n import _ from novaclient.tests.unit import fakes from novaclient.tests.unit import utils from novaclient.v2 import client @@ -58,7 +60,8 @@ class FakeClient(fakes.FakeClient, client.Client): 'project_id', 'auth_url', extensions=kwargs.get('extensions'), direct_use=False) - self.api_version = api_version + self.api_version = api_version or api_versions.APIVersion("2.1") + kwargs["api_version"] = self.api_version self.client = FakeHTTPClient(**kwargs) @@ -91,6 +94,7 @@ class FakeHTTPClient(base_client.HTTPClient): self.http_log_debug = 'http_log_debug' self.last_request_id = None self.management_url = self.get_endpoint() + self.api_version = kwargs.get("api_version") def _cs_request(self, url, method, **kwargs): # Check that certain things are called correctly @@ -2367,21 +2371,26 @@ class FakeHTTPClient(base_client.HTTPClient): return self.get_os_cells_capacities() def get_os_migrations(self, **kw): - migrations = {'migrations': [ - { - "created_at": "2012-10-29T13:42:02.000000", - "dest_compute": "compute2", - "dest_host": "1.2.3.4", - "dest_node": "node2", - "id": 1234, - "instance_uuid": "instance_id_123", - "new_instance_type_id": 2, - "old_instance_type_id": 1, - "source_compute": "compute1", - "source_node": "node1", - "status": "Done", - "updated_at": "2012-10-29T13:42:02.000000" - }]} + migration = { + "created_at": "2012-10-29T13:42:02.000000", + "dest_compute": "compute2", + "dest_host": "1.2.3.4", + "dest_node": "node2", + "id": 1234, + "instance_uuid": "instance_id_123", + "new_instance_type_id": 2, + "old_instance_type_id": 1, + "source_compute": "compute1", + "source_node": "node1", + "status": "Done", + "updated_at": "2012-10-29T13:42:02.000000" + } + + if self.api_version >= api_versions.APIVersion("2.23"): + migration.update({"migration_type": "live-migration"}) + + migrations = {'migrations': [migration]} + return (200, FAKE_RESPONSE_HEADERS, migrations) def post_os_server_external_events(self, **kw): @@ -2435,6 +2444,57 @@ class FakeHTTPClient(base_client.HTTPClient): def post_servers_1234_migrations_1_action(self, body): return (202, {}, None) + def get_servers_1234_migrations_1(self, **kw): + # TODO(Shaohe Feng) this condition check can be a decorator + if self.api_version < api_versions.APIVersion("2.23"): + raise exceptions.UnsupportedVersion(_("Unsupport version %s") + % self.api_version) + migration = {"migration": { + "created_at": "2016-01-29T13:42:02.000000", + "dest_compute": "compute2", + "dest_host": "1.2.3.4", + "dest_node": "node2", + "id": 1, + "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", + "source_compute": "compute1", + "source_node": "node1", + "status": "running", + "memory_total_bytes": 123456, + "memory_processed_bytes": 12345, + "memory_remaining_bytes": 120000, + "disk_total_bytes": 234567, + "disk_processed_bytes": 23456, + "disk_remaining_bytes": 230000, + "updated_at": "2016-01-29T13:42:02.000000" + }} + return (200, FAKE_RESPONSE_HEADERS, migration) + + def get_servers_1234_migrations(self, **kw): + # TODO(Shaohe Feng) this condition check can be a decorator + if self.api_version < api_versions.APIVersion("2.23"): + raise exceptions.UnsupportedVersion(_("Unsupport version %s") + % self.api_version) + migrations = {'migrations': [ + { + "created_at": "2016-01-29T13:42:02.000000", + "dest_compute": "compute2", + "dest_host": "1.2.3.4", + "dest_node": "node2", + "id": 1, + "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", + "source_compute": "compute1", + "source_node": "node1", + "status": "running", + "memory_total_bytes": 123456, + "memory_processed_bytes": 12345, + "memory_remaining_bytes": 120000, + "disk_total_bytes": 234567, + "disk_processed_bytes": 23456, + "disk_remaining_bytes": 230000, + "updated_at": "2016-01-29T13:42:02.000000" + }]} + return (200, FAKE_RESPONSE_HEADERS, migrations) + class FakeSessionClient(fakes.FakeClient, client.Client): @@ -2443,6 +2503,7 @@ class FakeSessionClient(fakes.FakeClient, client.Client): 'project_id', 'auth_url', extensions=kwargs.get('extensions'), api_version=api_version, direct_use=False) + kwargs['api_version'] = api_version self.client = FakeSessionMockClient(**kwargs) @@ -2461,7 +2522,7 @@ class FakeSessionMockClient(base_client.SessionClient, FakeHTTPClient): self.interface = None self.region_name = None self.version = None - + self.api_version = kwargs.get('api_version') self.auth.get_auth_ref.return_value.project_id = 'tenant_id' def request(self, url, method, **kwargs): diff --git a/novaclient/tests/unit/v2/test_server_migrations.py b/novaclient/tests/unit/v2/test_server_migrations.py index 12c5a269e..e4a2b4bac 100644 --- a/novaclient/tests/unit/v2/test_server_migrations.py +++ b/novaclient/tests/unit/v2/test_server_migrations.py @@ -14,9 +14,12 @@ # under the License. from novaclient import api_versions +from novaclient import base from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import server_migrations as data from novaclient.tests.unit import utils +from novaclient.tests.unit.v2 import fakes +from novaclient.v2 import server_migrations class ServerMigrationsTest(utils.FixturedTestCase): @@ -31,3 +34,49 @@ class ServerMigrationsTest(utils.FixturedTestCase): body = {'force_complete': None} self.cs.server_migrations.live_migrate_force_complete(1234, 1) self.assert_called('POST', '/servers/1234/migrations/1/action', body) + + +class ServerMigrationsTestV223(ServerMigrationsTest): + + migration = { + "created_at": "2016-01-29T13:42:02.000000", + "dest_compute": "compute2", + "dest_host": "1.2.3.4", + "dest_node": "node2", + "id": 1, + "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", + "source_compute": "compute1", + "source_node": "node1", + "status": "running", + "memory_total_bytes": 123456, + "memory_processed_bytes": 12345, + "memory_remaining_bytes": 120000, + "disk_total_bytes": 234567, + "disk_processed_bytes": 23456, + "disk_remaining_bytes": 230000, + "updated_at": "2016-01-29T13:42:02.000000" + } + + def setUp(self): + super(ServerMigrationsTestV223, self).setUp() + self.cs.api_version = api_versions.APIVersion("2.23") + + def test_list_migrations(self): + ml = self.cs.server_migrations.list(1234) + + self.assertIsInstance(ml, base.ListWithMeta) + self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) + for k in self.migration: + self.assertEqual(self.migration[k], getattr(ml[0], k)) + + self.assert_called('GET', '/servers/1234/migrations') + + def test_get_migration(self): + migration = self.cs.server_migrations.get(1234, 1) + + self.assertIsInstance(migration, server_migrations.ServerMigration) + for k in migration._info: + self.assertEqual(self.migration[k], migration._info[k]) + self.assert_request_id(migration, fakes.FAKE_REQUEST_ID_LIST) + + self.assert_called('GET', '/servers/1234/migrations/1') diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index cb30ce40a..7d1502eb2 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -1687,6 +1687,16 @@ class ShellTest(utils.TestCase): self.assert_called('POST', '/servers/1234/migrations/1/action', {'force_complete': None}) + def test_list_migrations(self): + self.run_command('server-migration-list sample-server', + api_version='2.23') + self.assert_called('GET', '/servers/1234/migrations') + + def test_get_migration(self): + self.run_command('server-migration-show sample-server 1', + api_version='2.23') + self.assert_called('GET', '/servers/1234/migrations/1') + def test_host_evacuate_live_with_no_target_host(self): self.run_command('host-evacuate-live hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) @@ -2501,6 +2511,10 @@ class ShellTest(utils.TestCase): self.run_command('migration-list') self.assert_called('GET', '/os-migrations') + def test_migration_list_v223(self): + self.run_command('migration-list', api_version="2.23") + self.assert_called('GET', '/os-migrations') + def test_migration_list_with_filters(self): self.run_command('migration-list --host host1 --cell_name child1 ' '--status finished') diff --git a/novaclient/v2/contrib/migrations.py b/novaclient/v2/contrib/migrations.py index 15959818a..6af813182 100644 --- a/novaclient/v2/contrib/migrations.py +++ b/novaclient/v2/contrib/migrations.py @@ -16,6 +16,7 @@ migration interface from six.moves.urllib import parse +from novaclient import api_versions from novaclient import base from novaclient.i18n import _ from novaclient.openstack.common import cliutils @@ -71,11 +72,11 @@ class MigrationManager(base.ManagerWithFind): help=_('Fetch migrations for the given cell_name.')) def do_migration_list(cs, args): """Print a list of migrations.""" - _print_migrations(cs.migrations.list(args.host, args.status, - args.cell_name)) + migrations = cs.migrations.list(args.host, args.status, args.cell_name) + _print_migrations(cs, migrations) -def _print_migrations(migrations): +def _print_migrations(cs, migrations): fields = ['Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', 'Dest Host', 'Status', 'Instance UUID', 'Old Flavor', 'New Flavor', 'Created At', 'Updated At'] @@ -86,6 +87,14 @@ def _print_migrations(migrations): def new_flavor(migration): return migration.new_instance_type_id + def migration_type(migration): + return migration.migration_type + formatters = {'Old Flavor': old_flavor, 'New Flavor': new_flavor} + if cs.api_version >= api_versions.APIVersion("2.23"): + fields.insert(0, "Id") + fields.append("Type") + formatters.update({"Type": migration_type}) + utils.print_list(migrations, fields, formatters) diff --git a/novaclient/v2/server_migrations.py b/novaclient/v2/server_migrations.py index 476c0a80e..68ee94c9b 100644 --- a/novaclient/v2/server_migrations.py +++ b/novaclient/v2/server_migrations.py @@ -22,7 +22,7 @@ class ServerMigration(base.Resource): return "" -class ServerMigrationsManager(base.Manager): +class ServerMigrationsManager(base.ManagerWithFind): resource_class = ServerMigration @api_versions.wraps("2.22") @@ -40,3 +40,28 @@ class ServerMigrationsManager(base.Manager): base.getid(migration)), body=body) return self.convert_into_with_meta(body, resp) + + @api_versions.wraps("2.23") + def get(self, server, migration): + """ + Get a migration of a specified server + + :param server: The :class:`Server` (or its ID) + :param migration: Migration id that will be gotten. + :returns: An instance of + novaclient.v2.server_migrations.ServerMigration + """ + return self._get('/servers/%s/migrations/%s' % + (base.getid(server), base.getid(migration)), + 'migration') + + @api_versions.wraps("2.23") + def list(self, server): + """ + Get a migrations list of a specified server + + :param server: The :class:`Server` (or its ID) + :returns: An instance of novaclient.base.ListWithMeta + """ + return self._list( + '/servers/%s/migrations' % base.getid(server), "migrations") diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 9693fc713..1dc728f82 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -3847,6 +3847,42 @@ def do_live_migration_force_complete(cs, args): cs.server_migrations.live_migrate_force_complete(server, args.migration) +@api_versions.wraps("2.23") +@cliutils.arg('server', metavar='', help=_('Name or ID of server.')) +def do_server_migration_list(cs, args): + """Get the migrations list of specified server.""" + server = _find_server(cs, args.server) + migrations = cs.server_migrations.list(server) + + fields = ['Id', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Created At', 'Updated At'] + + format_name = ["Total Memory Bytes", "Processed Memory Bytes", + "Remaining Memory Bytes", "Total Disk Bytes", + "Processed Disk Bytes", "Remaining Disk Bytes"] + + format_key = ["memory_total_bytes", "memory_processed_bytes", + "memory_remaining_bytes", "disk_total_bytes", + "disk_processed_bytes", "disk_remaining_bytes"] + + formatters = map(lambda field: utils._make_field_formatter(field)[1], + format_key) + formatters = dict(zip(format_name, formatters)) + + utils.print_list(migrations, fields + format_name, formatters) + + +@api_versions.wraps("2.23") +@cliutils.arg('server', metavar='', help=_('Name or ID of server.')) +@cliutils.arg('migration', metavar='', help=_('ID of migration.')) +def do_server_migration_show(cs, args): + """Get the migration of specified server.""" + server = _find_server(cs, args.server) + migration = cs.server_migrations.get(server, args.migration) + utils.print_dict(migration._info) + + @cliutils.arg( '--all-tenants', action='store_const',