2.47: Show flavor info in server details
This adds support for microversion 2.47 which directly embeds the flavor information in the server details. With this change, CLI requests with microversion >= 2.47 will no longer need to do additional queries to get the flavor and flavor extra_specs information. Instead, the flavor information will be output as separate key/value pairs with the keys namespaced with the "flavor:" prefix. As one would expect, these keys can also be specified as output fields when listing servers. Change-Id: Ic00ec95485485dff0fd4dcf8cad6ca56a481d512 Implements: blueprint instance-flavor-api Depends-On: If646149efb7eec8c90bf7d07c39ff4c495349941
This commit is contained in:
parent
c23324ef48
commit
78986dcae2
@ -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.46")
|
||||
API_MAX_VERSION = api_versions.APIVersion("2.47")
|
||||
|
@ -159,3 +159,15 @@ class TestServersListNovaClient(base.ClientTestBase):
|
||||
# Cut header and footer of the table
|
||||
for server in precreated_servers:
|
||||
self.assertIn(server.id, output)
|
||||
|
||||
def test_list_minimal(self):
|
||||
name = uuidutils.generate_uuid()
|
||||
uuid = self._create_server(name).id
|
||||
server_output = self.nova("list --minimal")
|
||||
# The only fields output are "ID" and "Name"
|
||||
output_uuid = self._get_column_value_from_single_row_table(
|
||||
server_output, 'ID')
|
||||
output_name = self._get_column_value_from_single_row_table(
|
||||
server_output, 'Name')
|
||||
self.assertEqual(output_uuid, uuid)
|
||||
self.assertEqual(output_name, name)
|
||||
|
@ -263,3 +263,66 @@ class TestServersAutoAllocateNetworkCLI(base.ClientTestBase):
|
||||
network = self._find_network_in_table(server_info)
|
||||
self.assertIsNone(
|
||||
network, 'Unexpected network allocation: %s' % server_info)
|
||||
|
||||
|
||||
class TestServersDetailsFlavorInfo(base.ClientTestBase):
|
||||
|
||||
COMPUTE_API_VERSION = '2.47'
|
||||
|
||||
def _validate_flavor_details(self, flavor_details, server_details):
|
||||
# This is a mapping between the keys used in the flavor GET response
|
||||
# and the keys used for the flavor information embedded in the server
|
||||
# details.
|
||||
flavor_key_mapping = {
|
||||
"OS-FLV-EXT-DATA:ephemeral": "flavor:ephemeral",
|
||||
"disk": "flavor:disk",
|
||||
"extra_specs": "flavor:extra_specs",
|
||||
"name": "flavor:original_name",
|
||||
"ram": "flavor:ram",
|
||||
"swap": "flavor:swap",
|
||||
"vcpus": "flavor:vcpus",
|
||||
}
|
||||
|
||||
for key in flavor_key_mapping:
|
||||
flavor_val = self._get_value_from_the_table(
|
||||
flavor_details, key)
|
||||
server_flavor_val = self._get_value_from_the_table(
|
||||
server_details, flavor_key_mapping[key])
|
||||
if key is "swap" and flavor_val is "":
|
||||
# "flavor-show" displays zero swap as empty string.
|
||||
flavor_val = '0'
|
||||
self.assertEqual(flavor_val, server_flavor_val)
|
||||
|
||||
def _setup_extra_specs(self, flavor_id):
|
||||
extra_spec_key = "dummykey"
|
||||
self.nova('flavor-key', params=('%(flavor)s set %(key)s=dummyval' %
|
||||
{'flavor': flavor_id,
|
||||
'key': extra_spec_key}))
|
||||
unset_params = ('%(flavor)s unset %(key)s' %
|
||||
{'flavor': flavor_id, 'key': extra_spec_key})
|
||||
self.addCleanup(self.nova, 'flavor-key', params=unset_params)
|
||||
|
||||
def test_show(self):
|
||||
self._setup_extra_specs(self.flavor.id)
|
||||
uuid = self._create_server().id
|
||||
server_output = self.nova("show %s" % uuid)
|
||||
flavor_output = self.nova("flavor-show %s" % self.flavor.id)
|
||||
self._validate_flavor_details(flavor_output, server_output)
|
||||
|
||||
def test_show_minimal(self):
|
||||
uuid = self._create_server().id
|
||||
server_output = self.nova("show --minimal %s" % uuid)
|
||||
server_output_flavor = self._get_value_from_the_table(
|
||||
server_output, 'flavor')
|
||||
self.assertEqual(self.flavor.name, server_output_flavor)
|
||||
|
||||
def test_list(self):
|
||||
self._setup_extra_specs(self.flavor.id)
|
||||
self._create_server()
|
||||
server_output = self.nova("list --fields flavor:disk")
|
||||
# namespaced fields get reformatted slightly as column names
|
||||
server_flavor_val = self._get_column_value_from_single_row_table(
|
||||
server_output, 'flavor: Disk')
|
||||
flavor_output = self.nova("flavor-show %s" % self.flavor.id)
|
||||
flavor_val = self._get_value_from_the_table(flavor_output, 'disk')
|
||||
self.assertEqual(flavor_val, server_flavor_val)
|
||||
|
@ -364,6 +364,7 @@ class ShellTest(utils.TestCase):
|
||||
def setUp(self):
|
||||
super(ShellTest, self).setUp()
|
||||
self.mock_client = mock.MagicMock()
|
||||
self.mock_client.return_value.api_version = novaclient.API_MIN_VERSION
|
||||
self.useFixture(fixtures.MonkeyPatch('novaclient.client.Client',
|
||||
self.mock_client))
|
||||
self.nc_util = mock.patch('novaclient.utils.isunauthenticated').start()
|
||||
|
@ -2974,6 +2974,8 @@ class ShellTest(utils.TestCase):
|
||||
44, # There are no version-wrapped shell method changes for this.
|
||||
45, # There are no version-wrapped shell method changes for this.
|
||||
46, # There are no version-wrapped shell method changes for this.
|
||||
47, # NOTE(cfriesen): 47 adds support for flavor details embedded
|
||||
# within the server details
|
||||
])
|
||||
versions_supported = set(range(0,
|
||||
novaclient.API_MAX_VERSION.ver_minor + 1))
|
||||
|
@ -971,6 +971,21 @@ def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
|
||||
time.sleep(poll_period)
|
||||
|
||||
|
||||
def _expand_dict_attr(collection, attr):
|
||||
"""Expand item attribute whose value is a dict.
|
||||
|
||||
Take a collection of items where the named attribute is known to have a
|
||||
dictionary value and replace the named attribute with multiple attributes
|
||||
whose names are the keys of the dictionary namespaced with the original
|
||||
attribute name.
|
||||
"""
|
||||
for item in collection:
|
||||
field = getattr(item, attr)
|
||||
delattr(item, attr)
|
||||
for subkey in field.keys():
|
||||
setattr(item, attr + ':' + subkey, field[subkey])
|
||||
|
||||
|
||||
def _translate_keys(collection, convert):
|
||||
for item in collection:
|
||||
keys = item.__dict__.keys()
|
||||
@ -1503,8 +1518,15 @@ def do_list(cs, args):
|
||||
if arg in args:
|
||||
search_opts[arg] = getattr(args, arg)
|
||||
|
||||
filters = {'flavor': lambda f: f['id'],
|
||||
'security_groups': utils.format_security_groups}
|
||||
filters = {'security_groups': utils.format_security_groups}
|
||||
|
||||
# In microversion 2.47 we started embedding flavor info in server details.
|
||||
have_embedded_flavor_info = (
|
||||
cs.api_version >= api_versions.APIVersion('2.47'))
|
||||
# If we don't have embedded flavor info then we only report the flavor id
|
||||
# rather than looking up the rest of the information.
|
||||
if not have_embedded_flavor_info:
|
||||
filters['flavor'] = lambda f: f['id']
|
||||
|
||||
id_col = 'ID'
|
||||
|
||||
@ -1548,6 +1570,11 @@ def do_list(cs, args):
|
||||
cols = []
|
||||
fmts = {}
|
||||
|
||||
# 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 servers:
|
||||
cols, fmts = _get_list_table_columns_and_formatters(
|
||||
args.fields, servers, exclude_fields=('id',), filters=filters)
|
||||
@ -2131,6 +2158,22 @@ def _print_server(cs, args, server=None, wrap=0):
|
||||
info['%s network' % network_label] = ', '.join(address_list)
|
||||
|
||||
flavor = info.get('flavor', {})
|
||||
if cs.api_version >= api_versions.APIVersion('2.47'):
|
||||
# The "flavor" field is a JSON representation of a dict containing the
|
||||
# flavor information used at boot.
|
||||
if minimal:
|
||||
# To retain something similar to the previous behaviour, keep the
|
||||
# 'flavor' field name but just output the original name.
|
||||
info['flavor'] = flavor['original_name']
|
||||
else:
|
||||
# Replace the "flavor" field with individual namespaced fields.
|
||||
del info['flavor']
|
||||
for key in flavor.keys():
|
||||
info['flavor:' + key] = flavor[key]
|
||||
else:
|
||||
# Prior to microversion 2.47 we just have the ID of the flavor so we
|
||||
# need to retrieve the flavor information (which may have changed
|
||||
# since the instance was booted).
|
||||
flavor_id = flavor.get('id', '')
|
||||
if minimal:
|
||||
info['flavor'] = flavor_id
|
||||
|
18
releasenotes/notes/microversion-v2_47-4aa54fbbd519e421.yaml
Normal file
18
releasenotes/notes/microversion-v2_47-4aa54fbbd519e421.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added support for microversion 2.47 which returns the flavor details
|
||||
directly embedded in the server details when listing or showing servers.
|
||||
With this change, CLI requests with microversion >= 2.47 will no longer
|
||||
need to do additional queries to get the flavor and flavor extra_specs
|
||||
information. Instead, the flavor information will be output as
|
||||
separate key/value pairs with the keys namespaced with the
|
||||
"flavor:" prefix. As one would expect, these keys can also be
|
||||
specified as output fields when listing servers, like this:
|
||||
|
||||
``nova list --fields name,flavor:original_name``
|
||||
|
||||
When displaying details of a single server, the ``--minimal`` option will
|
||||
display a ``flavor`` field with a value of the ``original_name`` of the
|
||||
flavor. Prior to this microversion the value was the ``id`` of the flavor.
|
||||
|
Loading…
Reference in New Issue
Block a user