diff options
Diffstat (limited to 'openstackclient/compute')
| -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 |
6 files changed, 251 insertions, 115 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 |
