summaryrefslogtreecommitdiff
path: root/openstackclient/compute
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient/compute')
-rw-r--r--openstackclient/compute/v2/aggregate.py44
-rw-r--r--openstackclient/compute/v2/flavor.py21
-rw-r--r--openstackclient/compute/v2/hypervisor.py24
-rw-r--r--openstackclient/compute/v2/server.py83
-rw-r--r--openstackclient/compute/v2/server_group.py91
-rw-r--r--openstackclient/compute/v2/usage.py103
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