compute: Fix 'usage * -f yaml' output

Make use of 'FormattableColumn'-derived formatters, which provide better
output than what we were using before, particularly for the YAML output
format.

Change-Id: Ic770f27cb1f74222636f05350f97400808adffbf
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
Stephen Finucane 2020-11-05 11:22:05 +00:00
parent 03776d82e5
commit af5e9d16e8
3 changed files with 107 additions and 44 deletions

View File

@ -17,7 +17,9 @@
import collections
import datetime
import functools
from cliff import columns as cliff_columns
from novaclient import api_versions
from osc_lib.command import command
from osc_lib import utils
@ -25,6 +27,57 @@ from osc_lib import utils
from openstackclient.i18n import _
# TODO(stephenfin): This exists in a couple of places and should be moved to a
# common module
class ProjectColumn(cliff_columns.FormattableColumn):
"""Formattable column for project column.
Unlike the parent FormattableColumn class, the initializer of the class
takes project_cache as the second argument.
``osc_lib.utils.get_item_properties`` instantiates ``FormattableColumn``
objects with a single parameter, the column value, so you need to pass a
partially initialized class like ``functools.partial(ProjectColumn,
project_cache)`` to use this.
"""
def __init__(self, value, project_cache=None):
super().__init__(value)
self.project_cache = project_cache or {}
def human_readable(self):
project = self._value
if not project:
return ''
if project in self.project_cache.keys():
return self.project_cache[project].name
return project
class CountColumn(cliff_columns.FormattableColumn):
def human_readable(self):
return len(self._value)
class FloatColumn(cliff_columns.FormattableColumn):
def human_readable(self):
return float("%.2f" % self._value)
def _formatters(project_cache):
return {
'tenant_id': functools.partial(
ProjectColumn, project_cache=project_cache),
'server_usages': CountColumn,
'total_memory_mb_usage': FloatColumn,
'total_vcpus_usage': FloatColumn,
'total_local_gb_usage': FloatColumn,
}
def _get_usage_marker(usage):
marker = None
if hasattr(usage, 'server_usages') and usage.server_usages:
@ -147,17 +200,15 @@ class ListUsage(command.Lister):
"end": end.strftime(dateformat),
})
return (column_headers,
(utils.get_item_properties(
return (
column_headers,
(
utils.get_item_properties(
s, columns,
formatters={
'tenant_id': _format_project,
'server_usages': lambda x: len(x),
'total_memory_mb_usage': lambda x: float("%.2f" % x),
'total_vcpus_usage': lambda x: float("%.2f" % x),
'total_local_gb_usage': lambda x: float("%.2f" % x),
},
) for s in usage_list))
formatters=_formatters(project_cache),
) for s in usage_list
),
)
class ShowUsage(command.ShowOne):
@ -222,17 +273,21 @@ class ShowUsage(command.ShowOne):
"project": project,
})
info = {}
info['Servers'] = (
len(usage.server_usages)
if hasattr(usage, "server_usages") else None)
info['RAM MB-Hours'] = (
float("%.2f" % usage.total_memory_mb_usage)
if hasattr(usage, "total_memory_mb_usage") else None)
info['CPU Hours'] = (
float("%.2f" % usage.total_vcpus_usage)
if hasattr(usage, "total_vcpus_usage") else None)
info['Disk GB-Hours'] = (
float("%.2f" % usage.total_local_gb_usage)
if hasattr(usage, "total_local_gb_usage") else None)
return zip(*sorted(info.items()))
columns = (
"tenant_id",
"server_usages",
"total_memory_mb_usage",
"total_vcpus_usage",
"total_local_gb_usage"
)
column_headers = (
"Project",
"Servers",
"RAM MB-Hours",
"CPU Hours",
"Disk GB-Hours"
)
data = utils.get_item_properties(
usage, columns, formatters=_formatters(None))
return column_headers, data

View File

@ -16,7 +16,7 @@ from unittest import mock
from novaclient import api_versions
from openstackclient.compute.v2 import usage
from openstackclient.compute.v2 import usage as usage_cmds
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
@ -49,11 +49,11 @@ class TestUsageList(TestUsage):
)
data = [(
usages[0].tenant_id,
len(usages[0].server_usages),
float("%.2f" % usages[0].total_memory_mb_usage),
float("%.2f" % usages[0].total_vcpus_usage),
float("%.2f" % usages[0].total_local_gb_usage),
usage_cmds.ProjectColumn(usages[0].tenant_id),
usage_cmds.CountColumn(usages[0].server_usages),
usage_cmds.FloatColumn(usages[0].total_memory_mb_usage),
usage_cmds.FloatColumn(usages[0].total_vcpus_usage),
usage_cmds.FloatColumn(usages[0].total_local_gb_usage),
)]
def setUp(self):
@ -63,7 +63,7 @@ class TestUsageList(TestUsage):
self.projects_mock.list.return_value = [self.project]
# Get the command object to test
self.cmd = usage.ListUsage(self.app, None)
self.cmd = usage_cmds.ListUsage(self.app, None)
def test_usage_list_no_options(self):
@ -79,8 +79,8 @@ class TestUsageList(TestUsage):
self.projects_mock.list.assert_called_with()
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertCountEqual(self.columns, columns)
self.assertCountEqual(tuple(self.data), tuple(data))
def test_usage_list_with_options(self):
arglist = [
@ -102,8 +102,8 @@ class TestUsageList(TestUsage):
datetime.datetime(2016, 12, 20, 0, 0),
detailed=True)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertCountEqual(self.columns, columns)
self.assertCountEqual(tuple(self.data), tuple(data))
def test_usage_list_with_pagination(self):
arglist = []
@ -127,8 +127,8 @@ class TestUsageList(TestUsage):
mock.call(mock.ANY, mock.ANY, detailed=True,
marker=self.usages[0]['server_usages'][0]['instance_id'])
])
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertCountEqual(self.columns, columns)
self.assertCountEqual(tuple(self.data), tuple(data))
class TestUsageShow(TestUsage):
@ -139,17 +139,19 @@ class TestUsageShow(TestUsage):
attrs={'tenant_id': project.name})
columns = (
'Project',
'Servers',
'RAM MB-Hours',
'CPU Hours',
'Disk GB-Hours',
'RAM MB-Hours',
'Servers',
)
data = (
float("%.2f" % usage.total_vcpus_usage),
float("%.2f" % usage.total_local_gb_usage),
float("%.2f" % usage.total_memory_mb_usage),
len(usage.server_usages),
usage_cmds.ProjectColumn(usage.tenant_id),
usage_cmds.CountColumn(usage.server_usages),
usage_cmds.FloatColumn(usage.total_memory_mb_usage),
usage_cmds.FloatColumn(usage.total_vcpus_usage),
usage_cmds.FloatColumn(usage.total_local_gb_usage),
)
def setUp(self):
@ -159,7 +161,7 @@ class TestUsageShow(TestUsage):
self.projects_mock.get.return_value = self.project
# Get the command object to test
self.cmd = usage.ShowUsage(self.app, None)
self.cmd = usage_cmds.ShowUsage(self.app, None)
def test_usage_show_no_options(self):

View File

@ -8,3 +8,9 @@ fixes:
will now be rendered as objects. In addition, the ``power_state`` field
will now be humanized and rendered as a string value when using the table
formatter.
- |
The ``usage list`` and ``usage show`` commands will now display the name
of the project being queried rather than the ID when using the table
formatter. In addition, the ``server_usages``, ``total_memory_mb_usage``,
``total_vcpus_usage`` and ``total_local_gb_usage`` values will only be
humanized when using the table formatter.