API microversion 2.69: Handles Down Cells

This patch explicitly points out the change needed while
forming the detailed lists for embedded flavor information.
In those cases where the server response for nova list
has the flavor key missing for the instances in the down cell,
the servers will be skipped.

Depends-On: https://review.openstack.org/591657/

Related to blueprint handling-down-cell

Change-Id: I007d9a68309b0d3aa85a4edf5026043154d4f42a
This commit is contained in:
Surya Seetharaman 2018-07-02 15:46:13 +02:00 committed by Matt Riedemann
parent 874b03068f
commit 14a45183ee
6 changed files with 216 additions and 24 deletions

View File

@ -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 <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 <hostname>``
@ -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:**
``<server>``

View File

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

View File

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

View File

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

View File

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

View File

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