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:
Chris Friesen 2017-02-16 11:28:54 -06:00
parent c23324ef48
commit 78986dcae2
7 changed files with 150 additions and 11 deletions

View File

@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
# when client supported the max version, and bumped sequentially, otherwise # when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some # the client may break due to server side new version may include some
# backward incompatible change. # backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.46") API_MAX_VERSION = api_versions.APIVersion("2.47")

View File

@ -159,3 +159,15 @@ class TestServersListNovaClient(base.ClientTestBase):
# Cut header and footer of the table # Cut header and footer of the table
for server in precreated_servers: for server in precreated_servers:
self.assertIn(server.id, output) 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)

View File

@ -263,3 +263,66 @@ class TestServersAutoAllocateNetworkCLI(base.ClientTestBase):
network = self._find_network_in_table(server_info) network = self._find_network_in_table(server_info)
self.assertIsNone( self.assertIsNone(
network, 'Unexpected network allocation: %s' % server_info) 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)

View File

@ -364,6 +364,7 @@ class ShellTest(utils.TestCase):
def setUp(self): def setUp(self):
super(ShellTest, self).setUp() super(ShellTest, self).setUp()
self.mock_client = mock.MagicMock() self.mock_client = mock.MagicMock()
self.mock_client.return_value.api_version = novaclient.API_MIN_VERSION
self.useFixture(fixtures.MonkeyPatch('novaclient.client.Client', self.useFixture(fixtures.MonkeyPatch('novaclient.client.Client',
self.mock_client)) self.mock_client))
self.nc_util = mock.patch('novaclient.utils.isunauthenticated').start() self.nc_util = mock.patch('novaclient.utils.isunauthenticated').start()

View File

@ -2974,6 +2974,8 @@ class ShellTest(utils.TestCase):
44, # There are no version-wrapped shell method changes for this. 44, # There are no version-wrapped shell method changes for this.
45, # 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. 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, versions_supported = set(range(0,
novaclient.API_MAX_VERSION.ver_minor + 1)) novaclient.API_MAX_VERSION.ver_minor + 1))

View File

@ -971,6 +971,21 @@ def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
time.sleep(poll_period) 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): def _translate_keys(collection, convert):
for item in collection: for item in collection:
keys = item.__dict__.keys() keys = item.__dict__.keys()
@ -1503,8 +1518,15 @@ def do_list(cs, args):
if arg in args: if arg in args:
search_opts[arg] = getattr(args, arg) search_opts[arg] = getattr(args, arg)
filters = {'flavor': lambda f: f['id'], filters = {'security_groups': utils.format_security_groups}
'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' id_col = 'ID'
@ -1548,6 +1570,11 @@ def do_list(cs, args):
cols = [] cols = []
fmts = {} 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: if servers:
cols, fmts = _get_list_table_columns_and_formatters( cols, fmts = _get_list_table_columns_and_formatters(
args.fields, servers, exclude_fields=('id',), filters=filters) args.fields, servers, exclude_fields=('id',), filters=filters)
@ -2131,15 +2158,31 @@ def _print_server(cs, args, server=None, wrap=0):
info['%s network' % network_label] = ', '.join(address_list) info['%s network' % network_label] = ', '.join(address_list)
flavor = info.get('flavor', {}) flavor = info.get('flavor', {})
flavor_id = flavor.get('id', '') if cs.api_version >= api_versions.APIVersion('2.47'):
if minimal: # The "flavor" field is a JSON representation of a dict containing the
info['flavor'] = flavor_id # 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: else:
try: # Prior to microversion 2.47 we just have the ID of the flavor so we
info['flavor'] = '%s (%s)' % (_find_flavor(cs, flavor_id).name, # need to retrieve the flavor information (which may have changed
flavor_id) # since the instance was booted).
except Exception: flavor_id = flavor.get('id', '')
info['flavor'] = '%s (%s)' % (_("Flavor not found"), flavor_id) if minimal:
info['flavor'] = flavor_id
else:
try:
info['flavor'] = '%s (%s)' % (_find_flavor(cs, flavor_id).name,
flavor_id)
except Exception:
info['flavor'] = '%s (%s)' % (_("Flavor not found"), flavor_id)
if 'security_groups' in info: if 'security_groups' in info:
# when we have multiple nics the info will include the # when we have multiple nics the info will include the

View 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.