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:
		| @@ -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 | ||||
|   | ||||
| @@ -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): | ||||
|  | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Stephen Finucane
					Stephen Finucane