diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/compute/v2/aggregate.py | 44 | ||||
| -rw-r--r-- | openstackclient/compute/v2/flavor.py | 21 | ||||
| -rw-r--r-- | openstackclient/compute/v2/hypervisor.py | 24 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server.py | 83 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server_group.py | 91 | ||||
| -rw-r--r-- | openstackclient/compute/v2/usage.py | 103 | ||||
| -rw-r--r-- | openstackclient/tests/functional/compute/v2/test_server_group.py | 12 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_aggregate.py | 20 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_flavor.py | 10 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_hypervisor.py | 45 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_server.py | 71 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_server_group.py | 79 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_usage.py | 42 |
13 files changed, 448 insertions, 197 deletions
diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 8b70f426..e39eb2d2 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -101,6 +101,7 @@ class CreateAggregate(command.ShowOne): "--property", metavar="<key=value>", action=parseractions.KeyValueAction, + dest="properties", help=_("Property to add to this aggregate " "(repeat option to set multiple properties)") ) @@ -116,10 +117,10 @@ class CreateAggregate(command.ShowOne): aggregate = compute_client.create_aggregate(**attrs) - if parsed_args.property: + if parsed_args.properties: aggregate = compute_client.set_aggregate_metadata( aggregate.id, - parsed_args.property, + parsed_args.properties, ) display_columns, columns = _get_aggregate_columns(aggregate) @@ -269,12 +270,12 @@ class SetAggregate(command.Command): "--property", metavar="<key=value>", action=parseractions.KeyValueAction, + dest="properties", help=_("Property to set on <aggregate> " "(repeat option to set multiple properties)") ) parser.add_argument( "--no-property", - dest="no_property", action="store_true", help=_("Remove all properties from <aggregate> " "(specify both --property and --no-property to " @@ -296,21 +297,20 @@ class SetAggregate(command.Command): if kwargs: compute_client.update_aggregate(aggregate.id, **kwargs) - set_property = {} + properties = {} if parsed_args.no_property: # NOTE(RuiChen): "availability_zone" can not be unset from # properties. It is already excluded from show and create output. - set_property.update({key: None - for key in aggregate.metadata.keys() - if key != 'availability_zone'}) - if parsed_args.property: - set_property.update(parsed_args.property) - - if set_property: - compute_client.set_aggregate_metadata( - aggregate.id, - set_property - ) + properties.update({ + key: None for key in aggregate.metadata.keys() + if key != 'availability_zone' + }) + + if parsed_args.properties: + properties.update(parsed_args.properties) + + if properties: + compute_client.set_aggregate_metadata(aggregate.id, properties) class ShowAggregate(command.ShowOne): @@ -354,7 +354,9 @@ class UnsetAggregate(command.Command): parser.add_argument( "--property", metavar="<key>", - action='append', + action="append", + default=[], + dest="properties", help=_("Property to remove from aggregate " "(repeat option to remove multiple properties)") ) @@ -365,12 +367,10 @@ class UnsetAggregate(command.Command): aggregate = compute_client.find_aggregate( parsed_args.aggregate, ignore_missing=False) - unset_property = {} - if parsed_args.property: - unset_property.update({key: None for key in parsed_args.property}) - if unset_property: - compute_client.set_aggregate_metadata( - aggregate, unset_property) + properties = {key: None for key in parsed_args.properties} + + if properties: + compute_client.set_aggregate_metadata(aggregate.id, properties) class CacheImageForAggregate(command.Command): diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index fa98e131..a55aba2a 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -128,6 +128,7 @@ class CreateFlavor(command.ShowOne): "--property", metavar="<key=value>", action=parseractions.KeyValueAction, + dest="properties", help=_("Property to add for this flavor " "(repeat option to set multiple properties)") ) @@ -191,12 +192,12 @@ class CreateFlavor(command.ShowOne): msg = _("Failed to add project %(project)s access to " "flavor: %(e)s") LOG.error(msg, {'project': parsed_args.project, 'e': e}) - if parsed_args.property: + if parsed_args.properties: try: flavor = compute_client.create_flavor_extra_specs( - flavor, parsed_args.property) + flavor, parsed_args.properties) except Exception as e: - LOG.error(_("Failed to set flavor property: %s"), e) + LOG.error(_("Failed to set flavor properties: %s"), e) display_columns, columns = _get_flavor_columns(flavor) data = utils.get_dict_properties(flavor, columns, @@ -398,6 +399,7 @@ class SetFlavor(command.Command): "--property", metavar="<key=value>", action=parseractions.KeyValueAction, + dest="properties", help=_("Property to add or modify for this flavor " "(repeat option to set multiple properties)") ) @@ -447,15 +449,15 @@ class SetFlavor(command.Command): compute_client.delete_flavor_extra_specs_property( flavor.id, key) except Exception as e: - LOG.error(_("Failed to clear flavor property: %s"), e) + LOG.error(_("Failed to clear flavor properties: %s"), e) result += 1 - if parsed_args.property: + if parsed_args.properties: try: compute_client.create_flavor_extra_specs( - flavor.id, parsed_args.property) + flavor.id, parsed_args.properties) except Exception as e: - LOG.error(_("Failed to set flavor property: %s"), e) + LOG.error(_("Failed to set flavor properties: %s"), e) result += 1 if parsed_args.project: @@ -537,6 +539,7 @@ class UnsetFlavor(command.Command): "--property", metavar="<key>", action='append', + dest="properties", help=_("Property to remove from flavor " "(repeat option to unset multiple properties)") ) @@ -563,8 +566,8 @@ class UnsetFlavor(command.Command): raise exceptions.CommandError(_(e.message)) result = 0 - if parsed_args.property: - for key in parsed_args.property: + if parsed_args.properties: + for key in parsed_args.properties: try: compute_client.delete_flavor_extra_specs_property( flavor.id, key) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 7f110028..8fdb6698 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -15,9 +15,12 @@ """Hypervisor action implementations""" +import json import re +from novaclient import api_versions from novaclient import exceptions as nova_exceptions +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -86,8 +89,8 @@ class ShowHypervisor(command.ShowOne): if aggregates: # Hypervisors in nova cells are prefixed by "<cell>@" if "@" in hypervisor['service']['host']: - cell, service_host = hypervisor['service']['host'].split('@', - 1) + cell, service_host = hypervisor['service']['host'].split( + '@', 1) else: cell = None service_host = hypervisor['service']['host'] @@ -125,4 +128,19 @@ class ShowHypervisor(command.ShowOne): hypervisor["service_host"] = hypervisor["service"]["host"] del hypervisor["service"] - return zip(*sorted(hypervisor.items())) + if compute_client.api_version < api_versions.APIVersion('2.28'): + # microversion 2.28 transformed this to a JSON blob rather than a + # string; on earlier fields, do this manually + if hypervisor['cpu_info']: + hypervisor['cpu_info'] = json.loads(hypervisor['cpu_info']) + else: + hypervisor['cpu_info'] = {} + + columns = tuple(sorted(hypervisor)) + data = utils.get_dict_properties( + hypervisor, columns, + formatters={ + 'cpu_info': format_columns.DictColumn, + }) + + return (columns, data) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 2d6a4b18..c49c1815 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -614,6 +614,7 @@ class CreateServer(command.ShowOne): '--image-property', metavar='<key=value>', action=parseractions.KeyValueAction, + dest='image_properties', help=_("Image property to be matched"), ) disk_group.add_argument( @@ -659,6 +660,7 @@ class CreateServer(command.ShowOne): '--property', metavar='<key=value>', action=parseractions.KeyValueAction, + dest='properties', help=_( 'Set a property on this server ' '(repeat option to set multiple values)' @@ -886,8 +888,8 @@ class CreateServer(command.ShowOne): image = image_client.find_image( parsed_args.image, ignore_missing=False) - if not image and parsed_args.image_property: - def emit_duplicated_warning(img, image_property): + if not image and parsed_args.image_properties: + def emit_duplicated_warning(img): img_uuid_list = [str(image.id) for image in img] LOG.warning( 'Multiple matching images: %(img_uuid_list)s\n' @@ -930,9 +932,9 @@ class CreateServer(command.ShowOne): return images_matched - images = _match_image(image_client, parsed_args.image_property) + images = _match_image(image_client, parsed_args.image_properties) if len(images) > 1: - emit_duplicated_warning(images, parsed_args.image_property) + emit_duplicated_warning(images, parsed_args.image_properties) if images: image = images[0] else: @@ -1195,7 +1197,7 @@ class CreateServer(command.ShowOne): config_drive = parsed_args.config_drive boot_kwargs = dict( - meta=parsed_args.property, + meta=parsed_args.properties, files=files, reservation_id=None, min_count=parsed_args.min, @@ -2670,6 +2672,7 @@ class RebuildServer(command.ShowOne): '--property', metavar='<key=value>', action=parseractions.KeyValueAction, + dest='properties', help=_( 'Set a new property on the rebuilt server ' '(repeat option to set multiple values)' @@ -2811,8 +2814,8 @@ class RebuildServer(command.ShowOne): if parsed_args.preserve_ephemeral is not None: kwargs['preserve_ephemeral'] = parsed_args.preserve_ephemeral - if parsed_args.property: - kwargs['meta'] = parsed_args.property + if parsed_args.properties: + kwargs['meta'] = parsed_args.properties if parsed_args.description: if compute_client.api_version < api_versions.APIVersion('2.19'): @@ -3475,9 +3478,10 @@ class SetServer(command.Command): help=_('Set new root password (interactive only)'), ) parser.add_argument( - "--property", - metavar="<key=value>", + '--property', + metavar='<key=value>', action=parseractions.KeyValueAction, + dest='properties', help=_('Property to add/change for this server ' '(repeat option to set multiple properties)'), ) @@ -3518,11 +3522,8 @@ class SetServer(command.Command): if parsed_args.name: server.update(name=parsed_args.name) - if parsed_args.property: - compute_client.servers.set_meta( - server, - parsed_args.property, - ) + if parsed_args.properties: + compute_client.servers.set_meta(server, parsed_args.properties) if parsed_args.state: server.reset_state(state=parsed_args.state) @@ -3580,7 +3581,8 @@ class ShelveServer(command.Command): class ShowServer(command.ShowOne): _description = _( "Show server details. Specify ``--os-compute-api-version 2.47`` " - "or higher to see the embedded flavor information for the server.") + "or higher to see the embedded flavor information for the server." + ) def get_parser(self, prog_name): parser = super(ShowServer, self).get_parser(prog_name) @@ -3589,18 +3591,29 @@ class ShowServer(command.ShowOne): metavar='<server>', help=_('Server (name or ID)'), ) - parser.add_argument( + # TODO(stephenfin): This should be a separate command, not a flag + diagnostics_group = parser.add_mutually_exclusive_group() + diagnostics_group.add_argument( '--diagnostics', action='store_true', default=False, help=_('Display server diagnostics information'), ) + diagnostics_group.add_argument( + '--topology', + action='store_true', + default=False, + help=_( + 'Include topology information in the output ' + '(supported by --os-compute-api-version 2.78 or above)' + ), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - server = utils.find_resource(compute_client.servers, - parsed_args.server) + server = utils.find_resource( + compute_client.servers, parsed_args.server) if parsed_args.diagnostics: (resp, data) = server.diagnostics() @@ -3609,10 +3622,26 @@ class ShowServer(command.ShowOne): "Error retrieving diagnostics data\n" )) return ({}, {}) - else: - data = _prep_server_detail(compute_client, - self.app.client_manager.image, server, - refresh=False) + return zip(*sorted(data.items())) + + topology = None + if parsed_args.topology: + if compute_client.api_version < api_versions.APIVersion('2.78'): + msg = _( + '--os-compute-api-version 2.78 or greater is required to ' + 'support the --topology option' + ) + raise exceptions.CommandError(msg) + + topology = server.topology() + + data = _prep_server_detail( + compute_client, self.app.client_manager.image, server, + refresh=False) + + if topology: + data['topology'] = format_columns.DictColumn(topology) + return zip(*sorted(data.items())) @@ -3909,6 +3938,7 @@ class UnsetServer(command.Command): metavar='<key>', action='append', default=[], + dest='properties', help=_('Property key to remove from server ' '(repeat option to remove multiple values)'), ) @@ -3928,7 +3958,7 @@ class UnsetServer(command.Command): help=_( 'Tag to remove from the server. ' 'Specify multiple times to remove multiple tags. ' - '(supported by --os-compute-api-version 2.26 or later' + '(supported by --os-compute-api-version 2.26 or above)' ), ) return parser @@ -3940,11 +3970,8 @@ class UnsetServer(command.Command): parsed_args.server, ) - if parsed_args.property: - compute_client.servers.delete_meta( - server, - parsed_args.property, - ) + if parsed_args.properties: + compute_client.servers.delete_meta(server, parsed_args.properties) if parsed_args.description: if compute_client.api_version < api_versions.APIVersion("2.19"): diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index a3363244..783fdbfe 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -18,6 +18,8 @@ import logging from novaclient import api_versions +from osc_lib.cli import format_columns +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -29,8 +31,9 @@ LOG = logging.getLogger(__name__) _formatters = { - 'policies': utils.format_list, - 'members': utils.format_list, + 'members': format_columns.ListColumn, + 'policies': format_columns.ListColumn, + 'rules': format_columns.DictColumn, } @@ -67,7 +70,19 @@ class CreateServerGroup(command.ShowOne): "Add a policy to <name> " "Specify --os-compute-api-version 2.15 or higher for the " "'soft-affinity' or 'soft-anti-affinity' policy." - ) + ), + ) + parser.add_argument( + '--rule', + metavar='<key=value>', + action=parseractions.KeyValueAction, + default={}, + dest='rules', + help=_( + "A rule for the policy. Currently, only the " + "'max_server_per_host' rule is supported for the " + "'anti-affinity' policy." + ), ) return parser @@ -83,18 +98,30 @@ class CreateServerGroup(command.ShowOne): ) raise exceptions.CommandError(msg % parsed_args.policy) - policy_arg = {'policies': [parsed_args.policy]} - if compute_client.api_version >= api_versions.APIVersion("2.64"): - policy_arg = {'policy': parsed_args.policy} + if parsed_args.rules: + if compute_client.api_version < api_versions.APIVersion('2.64'): + msg = _( + '--os-compute-api-version 2.64 or greater is required to ' + 'support the --rule option' + ) + raise exceptions.CommandError(msg) + + if compute_client.api_version < api_versions.APIVersion('2.64'): + kwargs = {'policies': [parsed_args.policy]} + else: + kwargs = { + 'policy': parsed_args.policy, + 'rules': parsed_args.rules or None, + } server_group = compute_client.server_groups.create( - name=parsed_args.name, **policy_arg) + name=parsed_args.name, **kwargs) info.update(server_group._info) columns = _get_columns(info) - data = utils.get_dict_properties(info, columns, - formatters=_formatters) + data = utils.get_dict_properties( + info, columns, formatters=_formatters) return columns, data @@ -160,30 +187,36 @@ class ListServerGroup(command.Lister): if compute_client.api_version >= api_versions.APIVersion("2.64"): policy_key = 'Policy' + columns = ( + 'id', + 'name', + policy_key.lower(), + ) + column_headers = ( + 'ID', + 'Name', + policy_key, + ) if parsed_args.long: - column_headers = columns = ( - 'ID', - 'Name', - policy_key, + columns += ( + 'members', + 'project_id', + 'user_id', + ) + column_headers += ( 'Members', 'Project Id', 'User Id', ) - else: - column_headers = columns = ( - 'ID', - 'Name', - policy_key, - ) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={ - 'Policies': utils.format_list, - 'Members': utils.format_list, - } - ) for s in data)) + return ( + column_headers, + ( + utils.get_item_properties( + s, columns, formatters=_formatters, + ) for s in data + ), + ) class ShowServerGroup(command.ShowOne): @@ -205,6 +238,6 @@ class ShowServerGroup(command.ShowOne): info = {} info.update(group._info) columns = _get_columns(info) - data = utils.get_dict_properties(info, columns, - formatters=_formatters) + data = utils.get_dict_properties( + info, columns, formatters=_formatters) return columns, data diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 307c238a..69fa04e8 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -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 diff --git a/openstackclient/tests/functional/compute/v2/test_server_group.py b/openstackclient/tests/functional/compute/v2/test_server_group.py index 44ecda1d..3dff3dcd 100644 --- a/openstackclient/tests/functional/compute/v2/test_server_group.py +++ b/openstackclient/tests/functional/compute/v2/test_server_group.py @@ -33,7 +33,7 @@ class ServerGroupTests(base.TestCase): cmd_output['name'] ) self.assertEqual( - 'affinity', + ['affinity'], cmd_output['policies'] ) @@ -47,7 +47,7 @@ class ServerGroupTests(base.TestCase): cmd_output['name'] ) self.assertEqual( - 'anti-affinity', + ['anti-affinity'], cmd_output['policies'] ) @@ -74,7 +74,7 @@ class ServerGroupTests(base.TestCase): cmd_output['name'] ) self.assertEqual( - 'affinity', + ['affinity'], cmd_output['policies'] ) @@ -91,7 +91,7 @@ class ServerGroupTests(base.TestCase): cmd_output['name'] ) self.assertEqual( - 'anti-affinity', + ['anti-affinity'], cmd_output['policies'] ) @@ -102,5 +102,5 @@ class ServerGroupTests(base.TestCase): self.assertIn(name1, names) self.assertIn(name2, names) policies = [x["Policies"] for x in cmd_output] - self.assertIn('affinity', policies) - self.assertIn('anti-affinity', policies) + self.assertIn(['affinity'], policies) + self.assertIn(['anti-affinity'], policies) diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index e12edd0f..8563f988 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -138,7 +138,7 @@ class TestAggregateCreate(TestAggregate): 'ag1', ] verifylist = [ - ('property', {'key1': 'value1', 'key2': 'value2'}), + ('properties', {'key1': 'value1', 'key2': 'value2'}), ('name', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -146,7 +146,7 @@ class TestAggregateCreate(TestAggregate): self.sdk_client.create_aggregate.assert_called_once_with( name=parsed_args.name) self.sdk_client.set_aggregate_metadata.assert_called_once_with( - self.fake_ag.id, parsed_args.property) + self.fake_ag.id, parsed_args.properties) self.assertEqual(self.columns, columns) self.assertItemsEqual(self.data, data) @@ -378,7 +378,7 @@ class TestAggregateSet(TestAggregate): 'ag1', ] verifylist = [ - ('property', {'key1': 'value1', 'key2': 'value2'}), + ('properties', {'key1': 'value1', 'key2': 'value2'}), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -388,7 +388,7 @@ class TestAggregateSet(TestAggregate): parsed_args.aggregate, ignore_missing=False) self.assertNotCalled(self.sdk_client.update_aggregate) self.sdk_client.set_aggregate_metadata.assert_called_once_with( - self.fake_ag.id, parsed_args.property) + self.fake_ag.id, parsed_args.properties) self.assertIsNone(result) def test_aggregate_set_with_no_property_and_property(self): @@ -399,7 +399,7 @@ class TestAggregateSet(TestAggregate): ] verifylist = [ ('no_property', True), - ('property', {'key2': 'value2'}), + ('properties', {'key2': 'value2'}), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -509,14 +509,14 @@ class TestAggregateUnset(TestAggregate): 'ag1', ] verifylist = [ - ('property', ['unset_key']), + ('properties', ['unset_key']), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.sdk_client.set_aggregate_metadata.assert_called_once_with( - self.fake_ag, {'unset_key': None}) + self.fake_ag.id, {'unset_key': None}) self.assertIsNone(result) def test_aggregate_unset_multiple_properties(self): @@ -526,14 +526,14 @@ class TestAggregateUnset(TestAggregate): 'ag1', ] verifylist = [ - ('property', ['unset_key1', 'unset_key2']), + ('properties', ['unset_key1', 'unset_key2']), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.sdk_client.set_aggregate_metadata.assert_called_once_with( - self.fake_ag, {'unset_key1': None, 'unset_key2': None}) + self.fake_ag.id, {'unset_key1': None, 'unset_key2': None}) self.assertIsNone(result) def test_aggregate_unset_no_option(self): @@ -541,7 +541,7 @@ class TestAggregateUnset(TestAggregate): 'ag1', ] verifylist = [ - ('property', None), + ('properties', []), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 8c1147fd..ee4479b0 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -160,7 +160,7 @@ class TestFlavorCreate(TestFlavor): ('rxtx_factor', self.flavor.rxtx_factor), ('public', True), ('description', self.flavor.description), - ('property', {'property': 'value'}), + ('properties', {'property': 'value'}), ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -232,7 +232,7 @@ class TestFlavorCreate(TestFlavor): ('public', False), ('description', 'description'), ('project', self.project.id), - ('property', {'key1': 'value1', 'key2': 'value2'}), + ('properties', {'key1': 'value1', 'key2': 'value2'}), ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -688,7 +688,7 @@ class TestFlavorSet(TestFlavor): 'baremetal' ] verifylist = [ - ('property', {'FOO': '"B A R"'}), + ('properties', {'FOO': '"B A R"'}), ('flavor', 'baremetal') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1017,7 +1017,7 @@ class TestFlavorUnset(TestFlavor): 'baremetal' ] verifylist = [ - ('property', ['property']), + ('properties', ['property']), ('flavor', 'baremetal'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1039,7 +1039,7 @@ class TestFlavorUnset(TestFlavor): 'baremetal' ] verifylist = [ - ('property', ['property1', 'property2']), + ('properties', ['property1', 'property2']), ('flavor', 'baremetal'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index 7200d04e..518109c6 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -14,8 +14,11 @@ # import copy +import json +from novaclient import api_versions from novaclient import exceptions as nova_exceptions +from osc_lib.cli import format_columns from osc_lib import exceptions from openstackclient.compute.v2 import hypervisor @@ -247,7 +250,7 @@ class TestHypervisorShow(TestHypervisor): ) self.data = ( [], - {'aaa': 'aaa'}, + format_columns.DictColumn({'aaa': 'aaa'}), 0, 50, 50, @@ -278,6 +281,35 @@ class TestHypervisorShow(TestHypervisor): self.cmd = hypervisor.ShowHypervisor(self.app, None) def test_hypervisor_show(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.28') + + arglist = [ + self.hypervisor.hypervisor_hostname, + ] + verifylist = [ + ('hypervisor', self.hypervisor.hypervisor_hostname), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, data) + + def test_hypervisor_show_pre_v228(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.27') + + # before microversion 2.28, nova returned a stringified version of this + # field + self.hypervisor._info['cpu_info'] = json.dumps( + self.hypervisor._info['cpu_info']) + self.hypervisors_mock.get.return_value = self.hypervisor + arglist = [ self.hypervisor.hypervisor_hostname, ] @@ -292,9 +324,12 @@ class TestHypervisorShow(TestHypervisor): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemsEqual(self.data, data) + + def test_hypervisor_show_uptime_not_implemented(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.28') - def test_hyprvisor_show_uptime_not_implemented(self): arglist = [ self.hypervisor.hypervisor_hostname, ] @@ -337,7 +372,7 @@ class TestHypervisorShow(TestHypervisor): ) expected_data = ( [], - {'aaa': 'aaa'}, + format_columns.DictColumn({'aaa': 'aaa'}), 0, 50, 50, @@ -361,4 +396,4 @@ class TestHypervisorShow(TestHypervisor): ) self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) + self.assertItemsEqual(expected_data, data) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 0eeb2cec..f046925a 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1159,7 +1159,7 @@ class TestServerCreate(TestServer): ('image', 'image1'), ('flavor', 'flavor1'), ('key_name', 'keyname'), - ('property', {'Beta': 'b'}), + ('properties', {'Beta': 'b'}), ('security_group', ['securitygroup']), ('hint', {'a': ['b', 'c']}), ('config_drive', True), @@ -2227,7 +2227,7 @@ class TestServerCreate(TestServer): self.new_server.name, ] verifylist = [ - ('image_property', {'hypervisor_type': 'qemu'}), + ('image_properties', {'hypervisor_type': 'qemu'}), ('flavor', 'flavor1'), ('nic', ['none']), ('config_drive', False), @@ -2282,7 +2282,7 @@ class TestServerCreate(TestServer): self.new_server.name, ] verifylist = [ - ('image_property', {'hypervisor_type': 'qemu', + ('image_properties', {'hypervisor_type': 'qemu', 'hw_disk_bus': 'ide'}), ('flavor', 'flavor1'), ('nic', ['none']), @@ -2338,7 +2338,7 @@ class TestServerCreate(TestServer): self.new_server.name, ] verifylist = [ - ('image_property', {'hypervisor_type': 'qemu', + ('image_properties', {'hypervisor_type': 'qemu', 'hw_disk_bus': 'virtio'}), ('flavor', 'flavor1'), ('nic', ['none']), @@ -2370,7 +2370,7 @@ class TestServerCreate(TestServer): ] verifylist = [ - ('image_property', + ('image_properties', {'owner_specified.openstack.object': 'image/cirros'}), ('flavor', 'flavor1'), ('nic', ['none']), @@ -5109,10 +5109,10 @@ class TestServerRebuild(TestServer): '--property', 'key1=value1', '--property', 'key2=value2' ] - expected_property = {'key1': 'value1', 'key2': 'value2'} + expected_properties = {'key1': 'value1', 'key2': 'value2'} verifylist = [ ('server', self.server.id), - ('property', expected_property) + ('properties', expected_properties) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5122,7 +5122,7 @@ class TestServerRebuild(TestServer): self.servers_mock.get.assert_called_with(self.server.id) self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with( - self.image, None, meta=expected_property) + self.image, None, meta=expected_properties) def test_rebuild_with_keypair_name(self): self.app.client_manager.compute.api_version = \ @@ -6281,13 +6281,13 @@ class TestServerSet(TestServer): 'foo_vm', ] verifylist = [ - ('property', {'key1': 'value1', 'key2': 'value2'}), + ('properties', {'key1': 'value1', 'key2': 'value2'}), ('server', 'foo_vm'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.servers_mock.set_meta.assert_called_once_with( - self.fake_servers[0], parsed_args.property) + self.fake_servers[0], parsed_args.properties) self.assertIsNone(result) @mock.patch.object(getpass, 'getpass', @@ -6421,6 +6421,10 @@ class TestServerShow(TestServer): self.image = image_fakes.FakeImage.create_one_image() self.flavor = compute_fakes.FakeFlavor.create_one_flavor() + self.topology = { + 'nodes': [{'vcpu_set': [0, 1]}, {'vcpu_set': [2, 3]}], + 'pagesize_kb': None, + } server_info = { 'image': {'id': self.image.id}, 'flavor': {'id': self.flavor.id}, @@ -6434,6 +6438,7 @@ class TestServerShow(TestServer): resp.status_code = 200 server_method = { 'diagnostics': (resp, {'test': 'test'}), + 'topology': self.topology, } self.server = compute_fakes.FakeServer.create_one_server( attrs=server_info, methods=server_method) @@ -6484,6 +6489,7 @@ class TestServerShow(TestServer): ] verifylist = [ ('diagnostics', False), + ('topology', False), ('server', self.server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -6501,6 +6507,7 @@ class TestServerShow(TestServer): ] verifylist = [ ('diagnostics', False), + ('topology', False), ('server', self.server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -6527,6 +6534,7 @@ class TestServerShow(TestServer): ] verifylist = [ ('diagnostics', True), + ('topology', False), ('server', self.server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -6536,6 +6544,47 @@ class TestServerShow(TestServer): self.assertEqual(('test',), columns) self.assertEqual(('test',), data) + def test_show_topology(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.78') + + arglist = [ + '--topology', + self.server.name, + ] + verifylist = [ + ('diagnostics', False), + ('topology', True), + ('server', self.server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.columns += ('topology',) + self.data += (format_columns.DictColumn(self.topology),) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_show_topology_pre_v278(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.77') + + arglist = [ + '--topology', + self.server.name, + ] + verifylist = [ + ('diagnostics', False), + ('topology', True), + ('server', self.server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + class TestServerStart(TestServer): @@ -6666,7 +6715,7 @@ class TestServerUnset(TestServer): 'foo_vm', ] verifylist = [ - ('property', ['key1', 'key2']), + ('properties', ['key1', 'key2']), ('server', 'foo_vm'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index bf0ea0ba..732c1881 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -16,6 +16,7 @@ from unittest import mock from novaclient import api_versions +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -39,9 +40,9 @@ class TestServerGroup(compute_fakes.TestComputev2): data = ( fake_server_group.id, - utils.format_list(fake_server_group.members), + format_columns.ListColumn(fake_server_group.members), fake_server_group.name, - utils.format_list(fake_server_group.policies), + format_columns.ListColumn(fake_server_group.policies), fake_server_group.project_id, fake_server_group.user_id, ) @@ -70,7 +71,7 @@ class TestServerGroupV264(TestServerGroup): data = ( fake_server_group.id, - utils.format_list(fake_server_group.members), + format_columns.ListColumn(fake_server_group.members), fake_server_group.name, fake_server_group.policy, fake_server_group.project_id, @@ -105,8 +106,8 @@ class TestServerGroupCreate(TestServerGroup): policies=[parsed_args.policy], ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) def test_server_group_create_with_soft_policies(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -127,8 +128,8 @@ class TestServerGroupCreate(TestServerGroup): policies=[parsed_args.policy], ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) def test_server_group_create_with_soft_policies_pre_v215(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -151,27 +152,54 @@ class TestServerGroupCreate(TestServerGroup): '--os-compute-api-version 2.15 or greater is required', str(ex)) - def test_server_group_create_v264(self): + def test_server_group_create_with_rules(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.64') arglist = [ '--policy', 'soft-anti-affinity', + '--rule', 'max_server_per_host=2', 'affinity_group', ] verifylist = [ ('policy', 'soft-anti-affinity'), + ('rules', {'max_server_per_host': '2'}), ('name', 'affinity_group'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.create.assert_called_once_with( name=parsed_args.name, - policy=parsed_args.policy, + policy=parsed_args.policy, # should be 'policy', not 'policies' + rules=parsed_args.rules, ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_server_group_create_with_rules_pre_v264(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.63') + + arglist = [ + '--policy', 'soft-anti-affinity', + '--rule', 'max_server_per_host=2', + 'affinity_group', + ] + verifylist = [ + ('policy', 'soft-anti-affinity'), + ('rules', {'max_server_per_host': '2'}), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.64 or greater is required', + str(ex)) class TestServerGroupDelete(TestServerGroup): @@ -275,14 +303,14 @@ class TestServerGroupList(TestServerGroup): list_data = (( TestServerGroup.fake_server_group.id, TestServerGroup.fake_server_group.name, - utils.format_list(TestServerGroup.fake_server_group.policies), + format_columns.ListColumn(TestServerGroup.fake_server_group.policies), ),) list_data_long = (( TestServerGroup.fake_server_group.id, TestServerGroup.fake_server_group.name, - utils.format_list(TestServerGroup.fake_server_group.policies), - utils.format_list(TestServerGroup.fake_server_group.members), + format_columns.ListColumn(TestServerGroup.fake_server_group.policies), + format_columns.ListColumn(TestServerGroup.fake_server_group.members), TestServerGroup.fake_server_group.project_id, TestServerGroup.fake_server_group.user_id, ),) @@ -303,8 +331,8 @@ class TestServerGroupList(TestServerGroup): columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.list.assert_called_once_with(False) - self.assertEqual(self.list_columns, columns) - self.assertEqual(self.list_data, tuple(data)) + self.assertCountEqual(self.list_columns, columns) + self.assertCountEqual(self.list_data, tuple(data)) def test_server_group_list_with_all_projects_and_long(self): arglist = [ @@ -319,8 +347,8 @@ class TestServerGroupList(TestServerGroup): columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.list.assert_called_once_with(True) - self.assertEqual(self.list_columns_long, columns) - self.assertEqual(self.list_data_long, tuple(data)) + self.assertCountEqual(self.list_columns_long, columns) + self.assertCountEqual(self.list_data_long, tuple(data)) class TestServerGroupListV264(TestServerGroupV264): @@ -350,7 +378,8 @@ class TestServerGroupListV264(TestServerGroupV264): TestServerGroupV264.fake_server_group.id, TestServerGroupV264.fake_server_group.name, TestServerGroupV264.fake_server_group.policy, - utils.format_list(TestServerGroupV264.fake_server_group.members), + format_columns.ListColumn( + TestServerGroupV264.fake_server_group.members), TestServerGroupV264.fake_server_group.project_id, TestServerGroupV264.fake_server_group.user_id, ),) @@ -373,8 +402,8 @@ class TestServerGroupListV264(TestServerGroupV264): columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.list.assert_called_once_with(False) - self.assertEqual(self.list_columns, columns) - self.assertEqual(self.list_data, tuple(data)) + self.assertCountEqual(self.list_columns, columns) + self.assertCountEqual(self.list_data, tuple(data)) def test_server_group_list_with_all_projects_and_long(self): arglist = [ @@ -389,8 +418,8 @@ class TestServerGroupListV264(TestServerGroupV264): columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.list.assert_called_once_with(True) - self.assertEqual(self.list_columns_long, columns) - self.assertEqual(self.list_data_long, tuple(data)) + self.assertCountEqual(self.list_columns_long, columns) + self.assertCountEqual(self.list_data_long, tuple(data)) class TestServerGroupShow(TestServerGroup): @@ -412,5 +441,5 @@ class TestServerGroupShow(TestServerGroup): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) diff --git a/openstackclient/tests/unit/compute/v2/test_usage.py b/openstackclient/tests/unit/compute/v2/test_usage.py index c0871025..bbccb9bd 100644 --- a/openstackclient/tests/unit/compute/v2/test_usage.py +++ b/openstackclient/tests/unit/compute/v2/test_usage.py @@ -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): |
