diff --git a/doc/source/cli/nova.rst b/doc/source/cli/nova.rst index f3bc8eab9..64bdf2da9 100644 --- a/doc/source/cli/nova.rst +++ b/doc/source/cli/nova.rst @@ -2195,6 +2195,10 @@ nova list List servers. +Note that from microversion 2.69, during partial infrastructure failures in the +deployment, the output of this command may return partial results for the servers +present in the failure domain. + **Optional arguments:** ``--reservation-id `` @@ -3363,6 +3367,10 @@ nova service-list Show a list of all running services. Filter by host & binary. +Note that from microversion 2.69, during partial infrastructure failures in the +deployment, the output of this command may return partial results for the +services present in the failure domain. + **Optional arguments:** ``--host `` @@ -3430,6 +3438,10 @@ nova show Show details about the given server. +Note that from microversion 2.69, during partial infrastructure failures in the +deployment, the output of this command may return partial results for the server +if it exists in the failure domain. + **Positional arguments:** ```` diff --git a/novaclient/__init__.py b/novaclient/__init__.py index a4eef58c7..a41a77af3 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.68") +API_MAX_VERSION = api_versions.APIVersion("2.69") diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index c6f34c344..0b72a6773 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -392,14 +392,32 @@ class FakeSessionClient(base_client.SessionClient): # def get_servers(self, **kw): - return (200, {}, {"servers": [ + servers = {"servers": [ {'id': '1234', 'name': 'sample-server'}, {'id': '5678', 'name': 'sample-server2'}, {'id': '9014', 'name': 'help'} - ]}) + ]} + if self.api_version >= api_versions.APIVersion('2.69'): + # include "partial results" from non-responsive part of + # infrastructure. + servers['servers'].append( + {'id': '9015', + 'status': "UNKNOWN", + "links": [ + { + "href": "http://fake/v2.1/", + "rel": "self" + }, + { + "href": "http://fake", + "rel": "bookmark" + } + ]} + ) + return (200, {}, servers) def get_servers_detail(self, **kw): - return (200, {}, {"servers": [ + servers = {"servers": [ { "id": '1234', "name": "sample-server", @@ -538,7 +556,29 @@ class FakeSessionClient(base_client.SessionClient): "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", }, - ]}) + ]} + if self.api_version >= api_versions.APIVersion('2.69'): + # include "partial results" from non-responsive part of + # infrastructure. + servers['servers'].append( + { + "id": "9015", + "status": "UNKNOWN", + "tenant_id": "6f70656e737461636b20342065766572", + "created": "2018-12-03T21:06:18Z", + "links": [ + { + "href": "http://fake/v2.1/", + "rel": "self" + }, + { + "href": "http://fake", + "rel": "bookmark" + } + ] + } + ) + return (200, {}, servers) def post_servers(self, body, **kw): assert set(body.keys()) <= set(['server', 'os:scheduler_hints']) @@ -599,6 +639,27 @@ class FakeSessionClient(base_client.SessionClient): r = {'server': self.get_servers_detail()[2]['servers'][4]} return (200, {}, r) + def get_servers_9015(self, **kw): + r = {'server': self.get_servers_detail()[2]['servers'][5]} + r['server']["OS-EXT-AZ:availability_zone"] = 'geneva' + r['server']["OS-EXT-STS:power_state"] = 0 + flavor = { + "disk": 1, + "ephemeral": 0, + "original_name": "m1.tiny", + "ram": 512, + "swap": 0, + "vcpus": 1, + "extra_specs": {} + } + image = { + "id": "c99d7632-bd66-4be9-aed5-3dd14b223a76", + } + r['server']['image'] = image + r['server']['flavor'] = flavor + r['server']['user_id'] = "fake" + return (200, {}, r) + def delete_os_server_groups_12345(self, **kw): return (202, {}, None) @@ -1644,24 +1705,35 @@ class FakeSessionClient(base_client.SessionClient): else: service_id_1 = 1 service_id_2 = 2 - return (200, FAKE_RESPONSE_HEADERS, - {'services': [{'binary': binary, - 'host': host, - 'zone': 'nova', - 'status': 'enabled', - 'state': 'up', - 'updated_at': datetime.datetime( - 2012, 10, 29, 13, 42, 2), - 'id': service_id_1}, - {'binary': binary, - 'host': host, - 'zone': 'nova', - 'status': 'disabled', - 'state': 'down', - 'updated_at': datetime.datetime( - 2012, 9, 18, 8, 3, 38), - 'id': service_id_2}, - ]}) + services = { + 'services': [ + {'binary': binary, + 'host': host, + 'zone': 'nova', + 'status': 'enabled', + 'state': 'up', + 'updated_at': datetime.datetime( + 2012, 10, 29, 13, 42, 2), + 'id': service_id_1}, + {'binary': binary, + 'host': host, + 'zone': 'nova', + 'status': 'disabled', + 'state': 'down', + 'updated_at': datetime.datetime( + 2012, 9, 18, 8, 3, 38), + 'id': service_id_2}, + ] + } + if self.api_version >= api_versions.APIVersion('2.69'): + services['services'].append( + { + "binary": "nova-compute", + "host": "host-down", + "status": "UNKNOWN" + } + ) + return (200, FAKE_RESPONSE_HEADERS, services) def put_os_services_enable(self, body, **kw): return (200, FAKE_RESPONSE_HEADERS, diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index fbf939104..eecd9315e 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -2939,6 +2939,21 @@ class ShellTest(utils.TestCase): self.run_command('service-list', api_version='2.53') self.assert_called('GET', '/os-services') + def test_services_list_v269_with_down_cells(self): + """Tests nova service-list at the 2.69 microversion.""" + stdout, _stderr = self.run_command('service-list', api_version='2.69') + self.assertEqual('''\ ++--------------------------------------+--------------+-----------+------+----------+-------+---------------------+-----------------+-------------+ +| Id | Binary | Host | Zone | Status | State | Updated_at | Disabled Reason | Forced down | ++--------------------------------------+--------------+-----------+------+----------+-------+---------------------+-----------------+-------------+ +| 75e9eabc-ed3b-4f11-8bba-add1e7e7e2de | nova-compute | host1 | nova | enabled | up | 2012-10-29 13:42:02 | | | +| 1f140183-c914-4ddf-8757-6df73028aa86 | nova-compute | host1 | nova | disabled | down | 2012-09-18 08:03:38 | | | +| | nova-compute | host-down | | UNKNOWN | | | | | ++--------------------------------------+--------------+-----------+------+----------+-------+---------------------+-----------------+-------------+ +''', # noqa + stdout) + self.assert_called('GET', '/os-services') + def test_services_list_with_host(self): self.run_command('service-list --host host1') self.assert_called('GET', '/os-services?host=host1') @@ -4021,6 +4036,15 @@ class ShellTest(utils.TestCase): 63, # There are no version-wrapped shell method changes for this. 65, # There are no version-wrapped shell method changes for this. 67, # There are no version-wrapped shell method changes for this. + 69, # NOTE(tssurya): 2.69 adds support for missing keys in the + # responses of `GET /servers``, ``GET /servers/detail``, + # ``GET /servers/{server_id}`` and ``GET /os-services`` when + # a cell is down to return minimal constructs. From 2.69 and + # upwards, if the response for ``GET /servers/detail`` does + # not have the 'flavor' key for those instances in the down + # cell, they will be handled on the client side by being + # skipped when forming the detailed lists for embedded + # flavor information. ]) versions_supported = set(range(0, novaclient.API_MAX_VERSION.ver_minor + 1)) @@ -4098,6 +4122,70 @@ class ShellTest(utils.TestCase): self.run_command('list --not-tags-any tag1,tag2', api_version='2.26') self.assert_called('GET', '/servers/detail?not-tags-any=tag1%2Ctag2') + def test_list_detail_v269_with_down_cells(self): + """Tests nova list at the 2.69 microversion.""" + stdout, _stderr = self.run_command('list', api_version='2.69') + self.assertIn('''\ ++------+----------------+---------+------------+-------------+----------------------------------------------+ +| ID | Name | Status | Task State | Power State | Networks | ++------+----------------+---------+------------+-------------+----------------------------------------------+ +| 9015 | | UNKNOWN | N/A | N/A | | +| 9014 | help | ACTIVE | N/A | N/A | | +| 1234 | sample-server | BUILD | N/A | N/A | private=10.11.12.13; public=1.2.3.4, 5.6.7.8 | +| 5678 | sample-server2 | ACTIVE | N/A | N/A | private=10.13.12.13; public=4.5.6.7, 5.6.9.8 | +| 9012 | sample-server3 | ACTIVE | N/A | N/A | private=10.13.12.13; public=4.5.6.7, 5.6.9.8 | +| 9013 | sample-server4 | ACTIVE | N/A | N/A | | ++------+----------------+---------+------------+-------------+----------------------------------------------+ +''', # noqa + stdout) + self.assert_called('GET', '/servers/detail') + + def test_list_v269_with_down_cells(self): + stdout, _stderr = self.run_command( + 'list --minimal', api_version='2.69') + expected = '''\ ++------+----------------+ +| ID | Name | ++------+----------------+ +| 9015 | | +| 9014 | help | +| 1234 | sample-server | +| 5678 | sample-server2 | ++------+----------------+ +''' + self.assertEqual(expected, stdout) + self.assert_called('GET', '/servers') + + def test_show_v269_with_down_cells(self): + stdout, _stderr = self.run_command('show 9015', api_version='2.69') + self.assertEqual('''\ ++-----------------------------+---------------------------------------------------+ +| Property | Value | ++-----------------------------+---------------------------------------------------+ +| OS-EXT-AZ:availability_zone | geneva | +| OS-EXT-STS:power_state | 0 | +| created | 2018-12-03T21:06:18Z | +| flavor:disk | 1 | +| flavor:ephemeral | 0 | +| flavor:extra_specs | {} | +| flavor:original_name | m1.tiny | +| flavor:ram | 512 | +| flavor:swap | 0 | +| flavor:vcpus | 1 | +| id | 9015 | +| image | CentOS 5.2 (c99d7632-bd66-4be9-aed5-3dd14b223a76) | +| status | UNKNOWN | +| tenant_id | 6f70656e737461636b20342065766572 | +| user_id | fake | ++-----------------------------+---------------------------------------------------+ +''', # noqa + stdout) + FAKE_UUID_2 = 'c99d7632-bd66-4be9-aed5-3dd14b223a76' + self.assert_called('GET', '/servers?name=9015', pos=0) + self.assert_called('GET', '/servers?name=9015', pos=1) + self.assert_called('GET', '/servers/9015', pos=2) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=3) + class PollForStatusTestCase(utils.TestCase): @mock.patch("novaclient.v2.shell.time") diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index a86c0d8ed..0ff601561 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -1670,7 +1670,18 @@ def do_list(cs, args): # For detailed lists, if we have embedded flavor information then replace # the "flavor" attribute with more detailed information. if detailed and have_embedded_flavor_info: - _expand_dict_attr(servers, 'flavor') + if cs.api_version >= api_versions.APIVersion('2.69'): + # NOTE(tssurya): From 2.69, we will have the key 'flavor' missing + # in the server response during infrastructure failure situations. + # For those servers with partial constructs we just skip the + # process of expanding the flavor information. + servers_final = [] + for server in servers: + if hasattr(server, 'flavor'): + servers_final.append(server) + _expand_dict_attr(servers_final, 'flavor') + else: + _expand_dict_attr(servers, 'flavor') if servers: cols, fmts = _get_list_table_columns_and_formatters( diff --git a/releasenotes/notes/bp-handling-down-cell-728cdb1efd1ea75b.yaml b/releasenotes/notes/bp-handling-down-cell-728cdb1efd1ea75b.yaml new file mode 100644 index 000000000..0403d80f3 --- /dev/null +++ b/releasenotes/notes/bp-handling-down-cell-728cdb1efd1ea75b.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + From microversion 2.69 the results of ``nova list``, ``nova show`` and + ``nova service-list`` may contain missing information in their outputs + when there are partial infrastructure failure periods in the deployment. + See `Handling Down Cells`_ for more information on the missing keys/info. + + .. _Handling Down Cells: https://developer.openstack.org/api-guide/compute/down_cells.html