From 54d4da112a6e84db5bda497364a49b9debfc2904 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 12 Jan 2021 17:18:56 +0000 Subject: Add support for microversion v2.88 The key change here is that the 'GET /os-hypervisors/{id}/uptime' API will now returns a HTTP 404 starting in 2.88. The 'GET /os-hypervisors/{id}' will instead now include an 'uptime' value. The 'novaclient.v2.hypervisors.HypervisorManager.uptime' method is updated to handle this. Change-Id: Ib99fbd820a586c14527ff64b319df0b7a44e1b8b Signed-off-by: Stephen Finucane --- novaclient/__init__.py | 2 +- novaclient/tests/unit/fixture_data/hypervisors.py | 89 +++++++++++++++--- novaclient/tests/unit/v2/test_hypervisors.py | 104 ++++++++++++++++++--- novaclient/tests/unit/v2/test_shell.py | 10 ++ novaclient/v2/hypervisors.py | 28 +++++- novaclient/v2/shell.py | 10 ++ .../notes/microversion-v2_88-d91136020e3a3621.yaml | 16 ++++ 7 files changed, 228 insertions(+), 31 deletions(-) create mode 100644 releasenotes/notes/microversion-v2_88-d91136020e3a3621.yaml diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 0b29e4f1..50ad89d8 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.87") +API_MAX_VERSION = api_versions.APIVersion("2.88") diff --git a/novaclient/tests/unit/fixture_data/hypervisors.py b/novaclient/tests/unit/fixture_data/hypervisors.py index eeb5d51e..3c04daa7 100644 --- a/novaclient/tests/unit/fixture_data/hypervisors.py +++ b/novaclient/tests/unit/fixture_data/hypervisors.py @@ -27,15 +27,41 @@ class V1(base.Fixture): service_id_1 = 1 service_id_2 = 2 + @staticmethod + def _transform_hypervisor_details(hypervisor): + """Transform a detailed hypervisor view from 2.53 to 2.88.""" + del hypervisor['current_workload'] + del hypervisor['disk_available_least'] + del hypervisor['free_ram_mb'] + del hypervisor['free_disk_gb'] + del hypervisor['local_gb'] + del hypervisor['local_gb_used'] + del hypervisor['memory_mb'] + del hypervisor['memory_mb_used'] + del hypervisor['running_vms'] + del hypervisor['vcpus'] + del hypervisor['vcpus_used'] + hypervisor['uptime'] = 'fake uptime' + def setUp(self): super(V1, self).setUp() - uuid_as_id = (api_versions.APIVersion(self.api_version) >= - api_versions.APIVersion('2.53')) + + api_version = api_versions.APIVersion(self.api_version) get_os_hypervisors = { 'hypervisors': [ - {'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1'}, - {'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2'}, + { + 'id': self.hyper_id_1, + 'hypervisor_hostname': 'hyper1', + 'state': 'up', + 'status': 'enabled', + }, + { + 'id': self.hyper_id_2, + 'hypervisor_hostname': 'hyper2', + 'state': 'up', + 'status': 'enabled', + }, ] } @@ -67,7 +93,9 @@ class V1(base.Fixture): 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', - 'disk_available_least': 100 + 'disk_available_least': 100, + 'state': 'up', + 'status': 'enabled', }, { 'id': self.hyper_id_2, @@ -89,11 +117,17 @@ class V1(base.Fixture): 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', - 'disk_available_least': 100 + 'disk_available_least': 100, + 'state': 'up', + 'status': 'enabled', } ] } + if api_version >= api_versions.APIVersion('2.88'): + for hypervisor in get_os_hypervisors_detail['hypervisors']: + self._transform_hypervisor_details(hypervisor) + self.requests_mock.get(self.url('detail'), json=get_os_hypervisors_detail, headers=self.headers) @@ -121,12 +155,22 @@ class V1(base.Fixture): get_os_hypervisors_search = { 'hypervisors': [ - {'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1'}, - {'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2'} + { + 'id': self.hyper_id_1, + 'hypervisor_hostname': 'hyper1', + 'state': 'up', + 'status': 'enabled', + }, + { + 'id': self.hyper_id_2, + 'hypervisor_hostname': 'hyper2', + 'state': 'up', + 'status': 'enabled', + }, ] } - if uuid_as_id: + if api_version >= api_versions.APIVersion('2.53'): url = self.url(hypervisor_hostname_pattern='hyper') else: url = self.url('hyper', 'search') @@ -134,7 +178,7 @@ class V1(base.Fixture): json=get_os_hypervisors_search, headers=self.headers) - if uuid_as_id: + if api_version >= api_versions.APIVersion('2.53'): get_os_hypervisors_search_u_v2_53 = { 'error_name': 'BadRequest', 'message': 'Invalid input for query parameters ' @@ -164,6 +208,8 @@ class V1(base.Fixture): { 'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1', + 'state': 'up', + 'status': 'enabled', 'servers': [ {'name': 'inst1', 'uuid': 'uuid1'}, {'name': 'inst2', 'uuid': 'uuid2'} @@ -172,6 +218,8 @@ class V1(base.Fixture): { 'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2', + 'state': 'up', + 'status': 'enabled', 'servers': [ {'name': 'inst3', 'uuid': 'uuid3'}, {'name': 'inst4', 'uuid': 'uuid4'} @@ -180,7 +228,7 @@ class V1(base.Fixture): ] } - if uuid_as_id: + if api_version >= api_versions.APIVersion('2.53'): url = self.url(hypervisor_hostname_pattern='hyper', with_servers=True) else: @@ -207,10 +255,16 @@ class V1(base.Fixture): 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', - 'disk_available_least': 100 + 'disk_available_least': 100, + 'state': 'up', + 'status': 'enabled', } } + if api_version >= api_versions.APIVersion('2.88'): + self._transform_hypervisor_details( + get_os_hypervisors_hyper1['hypervisor']) + self.requests_mock.get(self.url(self.hyper_id_1), json=get_os_hypervisors_hyper1, headers=self.headers) @@ -219,7 +273,9 @@ class V1(base.Fixture): 'hypervisor': { 'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1', - 'uptime': 'fake uptime' + 'uptime': 'fake uptime', + 'state': 'up', + 'status': 'enabled', } } @@ -228,10 +284,15 @@ class V1(base.Fixture): headers=self.headers) -class V2_53(V1): +class V253(V1): """Fixture data for the os-hypervisors 2.53 API.""" api_version = '2.53' hyper_id_1 = 'd480b1b6-2255-43c2-b2c2-d60d42c2c074' hyper_id_2 = '43a8214d-f36a-4fc0-a25c-3cf35c17522d' service_id_1 = 'a87743ff-9c29-42ff-805d-2444659b5fc0' service_id_2 = '0486ab8b-1cfc-4ccb-9d94-9f22ec8bbd6b' + + +class V288(V253): + """Fixture data for the os-hypervisors 2.88 API.""" + api_version = '2.88' diff --git a/novaclient/tests/unit/v2/test_hypervisors.py b/novaclient/tests/unit/v2/test_hypervisors.py index 1c216b64..be48914f 100644 --- a/novaclient/tests/unit/v2/test_hypervisors.py +++ b/novaclient/tests/unit/v2/test_hypervisors.py @@ -63,7 +63,9 @@ class HypervisorsTest(utils.FixturedTestCase): current_workload=2, running_vms=2, cpu_info='cpu_info', - disk_available_least=100), + disk_available_least=100, + state='up', + status='enabled'), dict(id=self.data_fixture.hyper_id_2, service=dict(id=self.data_fixture.service_id_2, host="compute2"), @@ -81,7 +83,24 @@ class HypervisorsTest(utils.FixturedTestCase): current_workload=2, running_vms=2, cpu_info='cpu_info', - disk_available_least=100)] + disk_available_least=100, + state='up', + status='enabled')] + + if self.cs.api_version >= api_versions.APIVersion('2.88'): + for hypervisor in expected: + del hypervisor['current_workload'] + del hypervisor['disk_available_least'] + del hypervisor['free_ram_mb'] + del hypervisor['free_disk_gb'] + del hypervisor['local_gb'] + del hypervisor['local_gb_used'] + del hypervisor['memory_mb'] + del hypervisor['memory_mb_used'] + del hypervisor['running_vms'] + del hypervisor['vcpus'] + del hypervisor['vcpus_used'] + hypervisor['uptime'] = 'fake uptime' result = self.cs.hypervisors.list() self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) @@ -93,9 +112,13 @@ class HypervisorsTest(utils.FixturedTestCase): def test_hypervisor_search(self): expected = [ dict(id=self.data_fixture.hyper_id_1, - hypervisor_hostname='hyper1'), + hypervisor_hostname='hyper1', + state='up', + status='enabled'), dict(id=self.data_fixture.hyper_id_2, - hypervisor_hostname='hyper2')] + hypervisor_hostname='hyper2', + state='up', + status='enabled')] result = self.cs.hypervisors.search('hyper') self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) @@ -131,11 +154,15 @@ class HypervisorsTest(utils.FixturedTestCase): expected = [ dict(id=self.data_fixture.hyper_id_1, hypervisor_hostname='hyper1', + state='up', + status='enabled', servers=[ dict(name='inst1', uuid='uuid1'), dict(name='inst2', uuid='uuid2')]), dict(id=self.data_fixture.hyper_id_2, hypervisor_hostname='hyper2', + state='up', + status='enabled', servers=[ dict(name='inst3', uuid='uuid3'), dict(name='inst4', uuid='uuid4')]), @@ -171,7 +198,23 @@ class HypervisorsTest(utils.FixturedTestCase): current_workload=2, running_vms=2, cpu_info='cpu_info', - disk_available_least=100) + disk_available_least=100, + state='up', + status='enabled') + + if self.cs.api_version >= api_versions.APIVersion('2.88'): + del expected['current_workload'] + del expected['disk_available_least'] + del expected['free_ram_mb'] + del expected['free_disk_gb'] + del expected['local_gb'] + del expected['local_gb_used'] + del expected['memory_mb'] + del expected['memory_mb_used'] + del expected['running_vms'] + del expected['vcpus'] + del expected['vcpus_used'] + expected['uptime'] = 'fake uptime' result = self.cs.hypervisors.get(self.data_fixture.hyper_id_1) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) @@ -184,7 +227,9 @@ class HypervisorsTest(utils.FixturedTestCase): expected = dict( id=self.data_fixture.hyper_id_1, hypervisor_hostname="hyper1", - uptime="fake uptime") + uptime="fake uptime", + state='up', + status='enabled') result = self.cs.hypervisors.uptime(self.data_fixture.hyper_id_1) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) @@ -215,11 +260,6 @@ class HypervisorsTest(utils.FixturedTestCase): self.compare_to_expected(expected, result) - def test_hypervisor_statistics_data_model(self): - result = self.cs.hypervisor_stats.statistics() - self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) - self.assert_called('GET', '/os-hypervisors/statistics') - # Test for Bug #1370415, the line below used to raise AttributeError self.assertEqual("", result.__repr__()) @@ -237,19 +277,23 @@ class HypervisorsV233Test(HypervisorsTest): self.assertEqual([v], self.requests_mock.last_request.qs[k]) -class HypervisorsV2_53Test(HypervisorsV233Test): +class HypervisorsV253Test(HypervisorsV233Test): """Tests the os-hypervisors 2.53 API bindings.""" - data_fixture_class = data.V2_53 + data_fixture_class = data.V253 def setUp(self): - super(HypervisorsV2_53Test, self).setUp() + super(HypervisorsV253Test, self).setUp() self.cs.api_version = api_versions.APIVersion("2.53") def test_hypervisor_search_detailed(self): expected = [ dict(id=self.data_fixture.hyper_id_1, + state='up', + status='enabled', hypervisor_hostname='hyper1'), dict(id=self.data_fixture.hyper_id_2, + state='up', + status='enabled', hypervisor_hostname='hyper2')] result = self.cs.hypervisors.search('hyper', detailed=True) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) @@ -257,3 +301,35 @@ class HypervisorsV2_53Test(HypervisorsV233Test): 'GET', '/os-hypervisors/detail?hypervisor_hostname_pattern=hyper') for idx, hyper in enumerate(result): self.compare_to_expected(expected[idx], hyper) + + +class HypervisorsV288Test(HypervisorsV253Test): + data_fixture_class = data.V288 + + def setUp(self): + super().setUp() + self.cs.api_version = api_versions.APIVersion('2.88') + + def test_hypervisor_uptime(self): + expected = { + 'id': self.data_fixture.hyper_id_1, + 'hypervisor_hostname': 'hyper1', + 'uptime': 'fake uptime', + 'state': 'up', + 'status': 'enabled', + } + + result = self.cs.hypervisors.uptime(self.data_fixture.hyper_id_1) + self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'GET', '/os-hypervisors/%s' % self.data_fixture.hyper_id_1) + + self.compare_to_expected(expected, result) + + def test_hypervisor_statistics(self): + exc = self.assertRaises( + exceptions.UnsupportedVersion, + self.cs.hypervisor_stats.statistics) + self.assertIn( + "The 'statistics' API is removed in API version 2.88 or later.", + str(exc)) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index fd9cbfac..17c8974d 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -3575,6 +3575,16 @@ class ShellTest(utils.TestCase): self.run_command('hypervisor-stats') self.assert_called('GET', '/os-hypervisors/statistics') + def test_hypervisor_stats_v2_88(self): + """Tests nova hypervisor-stats at the 2.88 microversion.""" + ex = self.assertRaises( + exceptions.CommandError, self.run_command, + 'hypervisor-stats', api_version='2.88') + self.assertIn( + 'The hypervisor-stats command is not supported in API version ' + '2.88 or later.', + str(ex)) + def test_quota_show(self): self.run_command( 'quota-show --tenant ' diff --git a/novaclient/v2/hypervisors.py b/novaclient/v2/hypervisors.py index c705dc65..f0cc5863 100644 --- a/novaclient/v2/hypervisors.py +++ b/novaclient/v2/hypervisors.py @@ -123,8 +123,25 @@ class HypervisorManager(base.ManagerWithFind): :param hypervisor: Either a Hypervisor object or an ID. Starting with microversion 2.53 the ID must be a UUID value. """ - return self._get("/os-hypervisors/%s/uptime" % base.getid(hypervisor), - "hypervisor") + # Starting with microversion 2.88, the '/os-hypervisors/{id}/uptime' + # route is removed in favour of returning 'uptime' in the response of + # the '/os-hypervisors/{id}' route. This behaves slightly differently, + # in that it won't error out if a virt driver doesn't support reporting + # uptime or if the hypervisor is down, but it's a good enough + # approximation + if self.api_version < api_versions.APIVersion("2.88"): + return self._get( + "/os-hypervisors/%s/uptime" % base.getid(hypervisor), + "hypervisor") + + resp, body = self.api.client.get( + "/os-hypervisors/%s" % base.getid(hypervisor) + ) + content = { + k: v for k, v in body['hypervisor'].items() + if k in ('id', 'hypervisor_hostname', 'state', 'status', 'uptime') + } + return self.resource_class(self, content, loaded=True, resp=resp) def statistics(self): """ @@ -145,8 +162,15 @@ class HypervisorStats(base.Resource): class HypervisorStatsManager(base.Manager): resource_class = HypervisorStats + @api_versions.wraps("2.0", "2.87") def statistics(self): """ Get hypervisor statistics over all compute nodes. """ return self._get("/os-hypervisors/statistics", "hypervisor_statistics") + + @api_versions.wraps("2.88") + def statistics(self): + raise exceptions.UnsupportedVersion( + _("The 'statistics' API is removed in API version 2.88 or later.") + ) diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 23f5486f..eb0315fd 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -4049,12 +4049,22 @@ def do_hypervisor_uptime(cs, args): utils.print_dict(hyper.to_dict()) +@api_versions.wraps('2.0', '2.87') def do_hypervisor_stats(cs, args): """Get hypervisor statistics over all compute nodes.""" stats = cs.hypervisor_stats.statistics() utils.print_dict(stats.to_dict()) +@api_versions.wraps('2.88') +def do_hypervisor_stats(cs, args): + msg = _( + "The hypervisor-stats command is not supported in API version 2.88 " + "or later." + ) + raise exceptions.CommandError(msg) + + @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--port', diff --git a/releasenotes/notes/microversion-v2_88-d91136020e3a3621.yaml b/releasenotes/notes/microversion-v2_88-d91136020e3a3621.yaml new file mode 100644 index 00000000..994c6802 --- /dev/null +++ b/releasenotes/notes/microversion-v2_88-d91136020e3a3621.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Added support for `microversion 2.88`_. The + ``novaclient.v2.hypervisors.HypervisorManager.uptime`` method will now + transparently switch between the ``/os-hypervisors/{id}/uptime`` API, + which is deprecated in 2.88, and the ``/os-hypervisors/{id}`` API, which + now includes uptime information, based on the microversion. + + .. _microversion 2.88: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id80 +deprecations: + - | + The ``nova hypervisor-stats`` command and underlying + ``novaclient.v2.hypervisors.HypervisorStatsManager.statistics`` API are + deprecated starting in microversion 2.88 and will return an error starting + on this version. -- cgit v1.2.1