summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/cli/data/glance.csv9
-rw-r--r--openstackclient/compute/v2/hypervisor_stats.py40
-rw-r--r--openstackclient/compute/v2/server.py101
-rw-r--r--openstackclient/compute/v2/server_migration.py48
-rw-r--r--openstackclient/image/v2/metadef_namespaces.py247
-rw-r--r--openstackclient/network/v2/network_agent.py7
-rw-r--r--openstackclient/tests/functional/compute/v2/test_hypervisor.py52
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server.py90
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py61
-rw-r--r--openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py51
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py31
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_migration.py120
-rw-r--r--openstackclient/tests/unit/image/v2/fakes.py9
-rw-r--r--openstackclient/tests/unit/image/v2/test_metadef_namespaces.py150
-rw-r--r--releasenotes/notes/add-baremetal-agent-type-7c46365e8d457ac8.yaml5
-rw-r--r--releasenotes/notes/image-metadef-namespace-b940206bece64f97.yaml10
-rw-r--r--releasenotes/notes/switch-hypervisor-to-sdk-f6495f070b034718.yaml3
-rw-r--r--releasenotes/notes/switch-server-migration-to-sdk-4e4530f787f90fd2.yaml5
-rw-r--r--releasenotes/notes/switch-server-show-to-sdk-44a614aebf2c6da6.yaml7
-rw-r--r--setup.cfg5
20 files changed, 845 insertions, 206 deletions
diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv
index d5c65f2d..adca8c0e 100644
--- a/doc/source/cli/data/glance.csv
+++ b/doc/source/cli/data/glance.csv
@@ -1,6 +1,10 @@
+cache-clear,,"Clear all images from cache, queue or both."
+cache-delete,,Delete image from cache/caching queue.
+cache-list,,Get cache state.
+cache-queue,,Queue image(s) for caching.
explain,WONTFIX,Describe a specific model.
image-create,image create,Create a new image.
-image-create-via-import,,EXPERIMENTAL: Create a new image via image import.
+image-create-via-import, image create --import,"EXPERIMENTAL: Create a new image via image import using glance-direct import method. Missing support for web-download, copy-image and glance-download import methods. The OSC command is also missing support for importing image to specified store as well as all stores (--store, --stores, --all-stores) and skip or stop processing if import fails to one of the store (--allow-failure)"
image-deactivate,image set --deactivate,Deactivate specified image.
image-delete,image delete,Delete specified image.
image-download,image save,Download a specific image.
@@ -11,6 +15,7 @@ image-show,image show,Describe a specific image.
image-stage,image stage,Upload data for a specific image to staging.
image-tag-delete,image unset --tag <tag>,Delete the tag associated with the given image.
image-tag-update,image set --tag <tag>,Update an image with the given tag.
+image-tasks,,Get tasks associated with image.
image-update,image set,Update an existing image.
image-upload,,Upload data for a specific image.
import-info,,Print import methods available from Glance.
@@ -49,6 +54,7 @@ md-tag-show,,Describe a specific metadata definitions tag inside a namespace.
md-tag-update,,Rename a metadata definitions tag inside a namespace.
member-create,image add project,Create member for a given image.
member-delete,image remove project,Delete image member.
+member-get,,Show details of an image member
member-list,image member list,Describe sharing permissions by image.
member-update,image set --accept --reject --status,Update the status of a member for a given image.
stores-delete,,Delete image from specific store.
@@ -56,5 +62,6 @@ stores-info,,Print available backends from Glance.
task-create,WONTFIX,Create a new task.
task-list,image task list,List tasks you can access.
task-show,image task show,Describe a specific task.
+usage,,Get quota usage information.
bash-completion,complete,Prints arguments for bash_completion.
help,help,Display help about this program or one of its subcommands.
diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py
index 4493e080..cb63a800 100644
--- a/openstackclient/compute/v2/hypervisor_stats.py
+++ b/openstackclient/compute/v2/hypervisor_stats.py
@@ -11,19 +11,49 @@
# under the License.
#
-
"""Hypervisor Stats action implementations"""
from osc_lib.command import command
+from osc_lib import utils
from openstackclient.i18n import _
+def _get_hypervisor_stat_columns(item):
+ column_map = {
+ # NOTE(gtema): If we decide to use SDK names - empty this
+ 'disk_available': 'disk_available_least',
+ 'local_disk_free': 'free_disk_gb',
+ 'local_disk_size': 'local_gb',
+ 'local_disk_used': 'local_gb_used',
+ 'memory_free': 'free_ram_mb',
+ 'memory_size': 'memory_mb',
+ 'memory_used': 'memory_mb_used',
+
+ }
+ hidden_columns = ['id', 'links', 'location', 'name']
+ return utils.get_osc_show_columns_for_sdk_resource(
+ item, column_map, hidden_columns)
+
+
class ShowHypervisorStats(command.ShowOne):
_description = _("Display hypervisor stats details")
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- hypervisor_stats = compute_client.hypervisors.statistics().to_dict()
-
- return zip(*sorted(hypervisor_stats.items()))
+ # The command is deprecated since it is being dropped in Nova.
+ self.log.warning(
+ _("This command is deprecated.")
+ )
+ compute_client = self.app.client_manager.sdk_connection.compute
+ # We do API request directly cause this deprecated method is not and
+ # will not be supported by OpenStackSDK.
+ response = compute_client.get(
+ '/os-hypervisors/statistics',
+ microversion='2.1')
+ hypervisor_stats = response.json().get('hypervisor_statistics')
+
+ display_columns, columns = _get_hypervisor_stat_columns(
+ hypervisor_stats)
+ data = utils.get_dict_properties(
+ hypervisor_stats, columns)
+ return (display_columns, data)
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index f93031ec..85693e17 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -77,6 +77,10 @@ class AddressesColumn(cliff_columns.FormattableColumn):
except Exception:
return 'N/A'
+ def machine_readable(self):
+ return {k: [i['addr'] for i in v if 'addr' in i]
+ for k, v in self._value.items()}
+
class HostColumn(cliff_columns.FormattableColumn):
"""Generate a formatted string of a hostname."""
@@ -133,14 +137,61 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
the latest details of a server after creating it.
:rtype: a dict of server details
"""
+ # Note: Some callers of this routine pass a novaclient server, and others
+ # pass an SDK server. Column names may be different across those cases.
info = server.to_dict()
if refresh:
server = utils.find_resource(compute_client.servers, info['id'])
info.update(server.to_dict())
+ # Some commands using this routine were originally implemented with the
+ # nova python wrappers, and were later migrated to use the SDK. Map the
+ # SDK's property names to the original property names to maintain backward
+ # compatibility for existing users. Data is duplicated under both the old
+ # and new name so users can consume the data by either name.
+ column_map = {
+ 'access_ipv4': 'accessIPv4',
+ 'access_ipv6': 'accessIPv6',
+ 'admin_password': 'adminPass',
+ 'admin_password': 'adminPass',
+ 'volumes': 'os-extended-volumes:volumes_attached',
+ 'availability_zone': 'OS-EXT-AZ:availability_zone',
+ 'block_device_mapping': 'block_device_mapping_v2',
+ 'compute_host': 'OS-EXT-SRV-ATTR:host',
+ 'created_at': 'created',
+ 'disk_config': 'OS-DCF:diskConfig',
+ 'flavor_id': 'flavorRef',
+ 'has_config_drive': 'config_drive',
+ 'host_id': 'hostId',
+ 'fault': 'fault',
+ 'hostname': 'OS-EXT-SRV-ATTR:hostname',
+ 'hypervisor_hostname': 'OS-EXT-SRV-ATTR:hypervisor_hostname',
+ 'image_id': 'imageRef',
+ 'instance_name': 'OS-EXT-SRV-ATTR:instance_name',
+ 'is_locked': 'locked',
+ 'kernel_id': 'OS-EXT-SRV-ATTR:kernel_id',
+ 'launch_index': 'OS-EXT-SRV-ATTR:launch_index',
+ 'launched_at': 'OS-SRV-USG:launched_at',
+ 'power_state': 'OS-EXT-STS:power_state',
+ 'project_id': 'tenant_id',
+ 'ramdisk_id': 'OS-EXT-SRV-ATTR:ramdisk_id',
+ 'reservation_id': 'OS-EXT-SRV-ATTR:reservation_id',
+ 'root_device_name': 'OS-EXT-SRV-ATTR:root_device_name',
+ 'scheduler_hints': 'OS-SCH-HNT:scheduler_hints',
+ 'task_state': 'OS-EXT-STS:task_state',
+ 'terminated_at': 'OS-SRV-USG:terminated_at',
+ 'updated_at': 'updated',
+ 'user_data': 'OS-EXT-SRV-ATTR:user_data',
+ 'vm_state': 'OS-EXT-STS:vm_state',
+ }
+
+ info.update({
+ column_map[column]: data for column, data in info.items()
+ if column in column_map})
+
# Convert the image blob to a name
image_info = info.get('image', {})
- if image_info:
+ if image_info and any(image_info.values()):
image_id = image_info.get('id', '')
try:
image = image_client.get_image(image_id)
@@ -188,7 +239,9 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
# NOTE(dtroyer): novaclient splits these into separate entries...
# Format addresses in a useful way
- info['addresses'] = format_columns.DictListColumn(server.networks)
+ info['addresses'] = (
+ AddressesColumn(info['addresses']) if 'addresses' in info
+ else format_columns.DictListColumn(info.get('networks')))
# Map 'metadata' field to 'properties'
info['properties'] = format_columns.DictColumn(info.pop('metadata'))
@@ -290,9 +343,11 @@ class AddFixedIP(command.ShowOne):
return ((), ())
kwargs = {
- 'net_id': net_id,
- 'fixed_ip': parsed_args.fixed_ip_address,
+ 'net_id': net_id
}
+ if parsed_args.fixed_ip_address:
+ kwargs['fixed_ips'] = [
+ {"ip_address": parsed_args.fixed_ip_address}]
if parsed_args.tag:
kwargs['tag'] = parsed_args.tag
@@ -451,8 +506,7 @@ class AddPort(command.Command):
port_id = parsed_args.port
kwargs = {
- 'port_id': port_id,
- 'fixed_ip': None,
+ 'port_id': port_id
}
if parsed_args.tag:
@@ -506,8 +560,7 @@ class AddNetwork(command.Command):
net_id = parsed_args.network
kwargs = {
- 'net_id': net_id,
- 'fixed_ip': None,
+ 'net_id': net_id
}
if parsed_args.tag:
@@ -2584,9 +2637,9 @@ class ListServer(command.Lister):
columns += ('Metadata',)
column_headers += ('Properties',)
- # convert back to tuple
- column_headers = tuple(column_headers)
- columns = tuple(columns)
+ # remove duplicates
+ column_headers = tuple(dict.fromkeys(column_headers))
+ columns = tuple(dict.fromkeys(columns))
if parsed_args.marker is not None:
# Check if both "--marker" and "--deleted" are used.
@@ -4325,32 +4378,34 @@ class ShowServer(command.ShowOne):
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)
+ compute_client = self.app.client_manager.sdk_connection.compute
+
+ # Find by name or ID, then get the full details of the server
+ server = compute_client.find_server(
+ parsed_args.server, ignore_missing=False)
+ server = compute_client.get_server(server)
if parsed_args.diagnostics:
- (resp, data) = server.diagnostics()
- if not resp.status_code == 200:
- self.app.stderr.write(_(
- "Error retrieving diagnostics data\n"
- ))
- return ({}, {})
+ data = compute_client.get_server_diagnostics(server)
return zip(*sorted(data.items()))
topology = None
if parsed_args.topology:
- if compute_client.api_version < api_versions.APIVersion('2.78'):
+ if not sdk_utils.supports_microversion(compute_client, '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()
+ topology = server.fetch_topology(compute_client)
data = _prep_server_detail(
- compute_client, self.app.client_manager.image, server,
+ # TODO(dannosliwcd): Replace these clients with SDK clients after
+ # all callers of _prep_server_detail() are using the SDK.
+ self.app.client_manager.compute,
+ self.app.client_manager.image,
+ server,
refresh=False)
if topology:
diff --git a/openstackclient/compute/v2/server_migration.py b/openstackclient/compute/v2/server_migration.py
index 919b67bd..016d15d7 100644
--- a/openstackclient/compute/v2/server_migration.py
+++ b/openstackclient/compute/v2/server_migration.py
@@ -15,6 +15,7 @@
import uuid
from novaclient import api_versions
+from openstack import utils as sdk_utils
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -130,22 +131,22 @@ class ListMigration(command.Lister):
# the same as the column header names.
columns = [
'source_node', 'dest_node', 'source_compute', 'dest_compute',
- 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id',
- 'new_instance_type_id', 'created_at', 'updated_at',
+ 'dest_host', 'status', 'server_id', 'old_flavor_id',
+ 'new_flavor_id', 'created_at', 'updated_at',
]
# Insert migrations UUID after ID
- if compute_client.api_version >= api_versions.APIVersion("2.59"):
+ if sdk_utils.supports_microversion(compute_client, "2.59"):
column_headers.insert(0, "UUID")
columns.insert(0, "uuid")
- if compute_client.api_version >= api_versions.APIVersion("2.23"):
+ if sdk_utils.supports_microversion(compute_client, "2.23"):
column_headers.insert(0, "Id")
columns.insert(0, "id")
column_headers.insert(len(column_headers) - 2, "Type")
columns.insert(len(columns) - 2, "migration_type")
- if compute_client.api_version >= api_versions.APIVersion("2.80"):
+ if sdk_utils.supports_microversion(compute_client, "2.80"):
if parsed_args.project:
column_headers.insert(len(column_headers) - 2, "Project")
columns.insert(len(columns) - 2, "project_id")
@@ -159,19 +160,23 @@ class ListMigration(command.Lister):
)
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
identity_client = self.app.client_manager.identity
- search_opts = {
- 'host': parsed_args.host,
- 'status': parsed_args.status,
- }
+ search_opts = {}
+
+ if parsed_args.host is not None:
+ search_opts['host'] = parsed_args.host
+
+ if parsed_args.status is not None:
+ search_opts['status'] = parsed_args.status
if parsed_args.server:
- search_opts['instance_uuid'] = utils.find_resource(
- compute_client.servers,
- parsed_args.server,
- ).id
+ server = compute_client.find_server(parsed_args.server)
+ if server is None:
+ msg = _('Unable to find server: %s') % parsed_args.server
+ raise exceptions.CommandError(msg)
+ search_opts['instance_uuid'] = server.id
if parsed_args.type:
migration_type = parsed_args.type
@@ -181,7 +186,7 @@ class ListMigration(command.Lister):
search_opts['migration_type'] = migration_type
if parsed_args.marker:
- if compute_client.api_version < api_versions.APIVersion('2.59'):
+ if not sdk_utils.supports_microversion(compute_client, "2.59"):
msg = _(
'--os-compute-api-version 2.59 or greater is required to '
'support the --marker option'
@@ -190,16 +195,17 @@ class ListMigration(command.Lister):
search_opts['marker'] = parsed_args.marker
if parsed_args.limit:
- if compute_client.api_version < api_versions.APIVersion('2.59'):
+ if not sdk_utils.supports_microversion(compute_client, "2.59"):
msg = _(
'--os-compute-api-version 2.59 or greater is required to '
'support the --limit option'
)
raise exceptions.CommandError(msg)
search_opts['limit'] = parsed_args.limit
+ search_opts['paginated'] = False
if parsed_args.changes_since:
- if compute_client.api_version < api_versions.APIVersion('2.59'):
+ if not sdk_utils.supports_microversion(compute_client, "2.59"):
msg = _(
'--os-compute-api-version 2.59 or greater is required to '
'support the --changes-since option'
@@ -208,7 +214,7 @@ class ListMigration(command.Lister):
search_opts['changes_since'] = parsed_args.changes_since
if parsed_args.changes_before:
- if compute_client.api_version < api_versions.APIVersion('2.66'):
+ if not sdk_utils.supports_microversion(compute_client, "2.66"):
msg = _(
'--os-compute-api-version 2.66 or greater is required to '
'support the --changes-before option'
@@ -217,7 +223,7 @@ class ListMigration(command.Lister):
search_opts['changes_before'] = parsed_args.changes_before
if parsed_args.project:
- if compute_client.api_version < api_versions.APIVersion('2.80'):
+ if not sdk_utils.supports_microversion(compute_client, "2.80"):
msg = _(
'--os-compute-api-version 2.80 or greater is required to '
'support the --project option'
@@ -231,7 +237,7 @@ class ListMigration(command.Lister):
).id
if parsed_args.user:
- if compute_client.api_version < api_versions.APIVersion('2.80'):
+ if not sdk_utils.supports_microversion(compute_client, "2.80"):
msg = _(
'--os-compute-api-version 2.80 or greater is required to '
'support the --user option'
@@ -244,7 +250,7 @@ class ListMigration(command.Lister):
parsed_args.user_domain,
).id
- migrations = compute_client.migrations.list(**search_opts)
+ migrations = list(compute_client.migrations(**search_opts))
return self.print_migrations(parsed_args, compute_client, migrations)
diff --git a/openstackclient/image/v2/metadef_namespaces.py b/openstackclient/image/v2/metadef_namespaces.py
index 158fd94e..f09f2002 100644
--- a/openstackclient/image/v2/metadef_namespaces.py
+++ b/openstackclient/image/v2/metadef_namespaces.py
@@ -15,8 +15,11 @@
"""Image V2 Action Implementations"""
+import logging
+
from osc_lib.cli import format_columns
from osc_lib.command import command
+from osc_lib import exceptions
from osc_lib import utils
from openstackclient.i18n import _
@@ -25,6 +28,149 @@ _formatters = {
'tags': format_columns.ListColumn,
}
+LOG = logging.getLogger(__name__)
+
+
+def _format_namespace(namespace):
+ info = {}
+
+ fields_to_show = [
+ 'created_at',
+ 'description',
+ 'display_name',
+ 'namespace',
+ 'owner',
+ 'protected',
+ 'schema',
+ 'visibility',
+ ]
+
+ namespace = namespace.to_dict(ignore_none=True, original_names=True)
+
+ # split out the usual key and the properties which are top-level
+ for key in namespace:
+ if key in fields_to_show:
+ info[key] = namespace.get(key)
+ elif key == "resource_type_associations":
+ info[key] = [resource_type['name']
+ for resource_type in namespace.get(key)]
+ elif key == 'properties':
+ info['properties'] = list(namespace.get(key).keys())
+
+ return info
+
+
+class CreateMetadefNameSpace(command.ShowOne):
+ _description = _("Create a metadef namespace")
+
+ def get_parser(self, prog_name):
+ parser = super().get_parser(prog_name)
+ parser.add_argument(
+ "namespace",
+ metavar="<namespace>",
+ help=_("New metadef namespace name"),
+ )
+ parser.add_argument(
+ "--display-name",
+ metavar="<display_name>",
+ help=_("A user-friendly name for the namespace."),
+ )
+ parser.add_argument(
+ "--description",
+ metavar="<description>",
+ help=_("A description of the namespace"),
+ )
+ visibility_group = parser.add_mutually_exclusive_group()
+ visibility_group.add_argument(
+ "--public",
+ action="store_const",
+ const="public",
+ dest="visibility",
+ help=_("Set namespace visibility 'public'"),
+ )
+ visibility_group.add_argument(
+ "--private",
+ action="store_const",
+ const="private",
+ dest="visibility",
+ help=_("Set namespace visibility 'private'"),
+ )
+ protected_group = parser.add_mutually_exclusive_group()
+ protected_group.add_argument(
+ "--protected",
+ action="store_const",
+ const=True,
+ dest="is_protected",
+ help=_("Prevent metadef namespace from being deleted"),
+ )
+ protected_group.add_argument(
+ "--unprotected",
+ action="store_const",
+ const=False,
+ dest="is_protected",
+ help=_("Allow metadef namespace to be deleted (default)"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ image_client = self.app.client_manager.image
+ filter_keys = [
+ 'namespace',
+ 'display_name',
+ 'description'
+ ]
+ kwargs = {}
+
+ for key in filter_keys:
+ argument = getattr(parsed_args, key, None)
+ if argument is not None:
+ kwargs[key] = argument
+
+ if parsed_args.is_protected is not None:
+ kwargs['protected'] = parsed_args.is_protected
+
+ if parsed_args.visibility is not None:
+ kwargs['visibility'] = parsed_args.visibility
+
+ data = image_client.create_metadef_namespace(**kwargs)
+
+ return zip(*sorted(data.items()))
+
+
+class DeleteMetadefNameSpace(command.Command):
+ _description = _("Delete metadef namespace")
+
+ def get_parser(self, prog_name):
+ parser = super().get_parser(prog_name)
+ parser.add_argument(
+ "namespace_name",
+ metavar="<namespace_name>",
+ nargs="+",
+ help=_("An identifier (a name) for the namespace"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ image_client = self.app.client_manager.image
+
+ result = 0
+ for i in parsed_args.namespace_name:
+ try:
+ namespace = image_client.get_metadef_namespace(i)
+ image_client.delete_metadef_namespace(namespace.id)
+ except Exception as e:
+ result += 1
+ LOG.error(_("Failed to delete namespace with name or "
+ "ID '%(namespace)s': %(e)s"),
+ {'namespace': i, 'e': e}
+ )
+
+ if result > 0:
+ total = len(parsed_args.namespace_name)
+ msg = (_("%(result)s of %(total)s namespace failed "
+ "to delete.") % {'result': result, 'total': total})
+ raise exceptions.CommandError(msg)
+
class ListMetadefNameSpaces(command.Lister):
_description = _("List metadef namespaces")
@@ -63,3 +209,104 @@ class ListMetadefNameSpaces(command.Lister):
formatters=_formatters,
) for s in data)
)
+
+
+class SetMetadefNameSpace(command.Command):
+ _description = _("Set metadef namespace properties")
+
+ def get_parser(self, prog_name):
+ parser = super().get_parser(prog_name)
+ parser.add_argument(
+ "namespace",
+ metavar="<namespace>",
+ help=_("Namespace (name) for the namespace"),
+ )
+ parser.add_argument(
+ "--display-name",
+ metavar="<display_name>",
+ help=_("Set a user-friendly name for the namespace."),
+ )
+ parser.add_argument(
+ "--description",
+ metavar="<description>",
+ help=_("Set the description of the namespace"),
+ )
+ visibility_group = parser.add_mutually_exclusive_group()
+ visibility_group.add_argument(
+ "--public",
+ action="store_const",
+ const="public",
+ dest="visibility",
+ help=_("Set namespace visibility 'public'"),
+ )
+ visibility_group.add_argument(
+ "--private",
+ action="store_const",
+ const="private",
+ dest="visibility",
+ help=_("Set namespace visibility 'private'"),
+ )
+ protected_group = parser.add_mutually_exclusive_group()
+ protected_group.add_argument(
+ "--protected",
+ action="store_const",
+ const=True,
+ dest="is_protected",
+ help=_("Prevent metadef namespace from being deleted"),
+ )
+ protected_group.add_argument(
+ "--unprotected",
+ action="store_const",
+ const=False,
+ dest="is_protected",
+ help=_("Allow metadef namespace to be deleted (default)"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ image_client = self.app.client_manager.image
+
+ namespace = parsed_args.namespace
+
+ filter_keys = [
+ 'namespace',
+ 'display_name',
+ 'description'
+ ]
+ kwargs = {}
+
+ for key in filter_keys:
+ argument = getattr(parsed_args, key, None)
+ if argument is not None:
+ kwargs[key] = argument
+
+ if parsed_args.is_protected is not None:
+ kwargs['protected'] = parsed_args.is_protected
+
+ if parsed_args.visibility is not None:
+ kwargs['visibility'] = parsed_args.visibility
+
+ image_client.update_metadef_namespace(namespace, **kwargs)
+
+
+class ShowMetadefNameSpace(command.ShowOne):
+ _description = _("Show a metadef namespace")
+
+ def get_parser(self, prog_name):
+ parser = super().get_parser(prog_name)
+ parser.add_argument(
+ "namespace_name",
+ metavar="<namespace_name>",
+ help=_("Namespace (name) for the namespace"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ image_client = self.app.client_manager.image
+
+ namespace_name = parsed_args.namespace_name
+
+ data = image_client.get_metadef_namespace(namespace_name)
+ info = _format_namespace(data)
+
+ return zip(*sorted(info.items()))
diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py
index d963b3bf..f67f67bd 100644
--- a/openstackclient/network/v2/network_agent.py
+++ b/openstackclient/network/v2/network_agent.py
@@ -168,11 +168,11 @@ class ListNetworkAgent(command.Lister):
metavar='<agent-type>',
choices=["bgp", "dhcp", "open-vswitch", "linux-bridge", "ofa",
"l3", "loadbalancer", "metering", "metadata", "macvtap",
- "nic"],
+ "nic", "baremetal"],
help=_("List only agents with the specified agent type. "
"The supported agent types are: bgp, dhcp, open-vswitch, "
"linux-bridge, ofa, l3, loadbalancer, metering, "
- "metadata, macvtap, nic.")
+ "metadata, macvtap, nic, baremetal.")
)
parser.add_argument(
'--host',
@@ -231,7 +231,8 @@ class ListNetworkAgent(command.Lister):
'metering': 'Metering agent',
'metadata': 'Metadata agent',
'macvtap': 'Macvtap agent',
- 'nic': 'NIC Switch agent'
+ 'nic': 'NIC Switch agent',
+ 'baremetal': 'Baremetal Node'
}
filters = {}
diff --git a/openstackclient/tests/functional/compute/v2/test_hypervisor.py b/openstackclient/tests/functional/compute/v2/test_hypervisor.py
new file mode 100644
index 00000000..9bc23280
--- /dev/null
+++ b/openstackclient/tests/functional/compute/v2/test_hypervisor.py
@@ -0,0 +1,52 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from openstackclient.tests.functional import base
+
+
+class HypervisorTests(base.TestCase):
+ """Functional tests for hypervisor."""
+
+ def test_hypervisor_list(self):
+ """Test create defaults, list filters, delete"""
+ # Test list
+ cmd_output = json.loads(self.openstack(
+ "hypervisor list -f json --os-compute-api-version 2.1"
+ ))
+ ids1 = [x["ID"] for x in cmd_output]
+ self.assertIsNotNone(cmd_output)
+
+ cmd_output = json.loads(self.openstack(
+ "hypervisor list -f json"
+ ))
+ ids2 = [x["ID"] for x in cmd_output]
+ self.assertIsNotNone(cmd_output)
+
+ # Show test - old microversion
+ for i in ids1:
+ cmd_output = json.loads(self.openstack(
+ "hypervisor show %s -f json "
+ " --os-compute-api-version 2.1"
+ % (i)
+ ))
+ self.assertIsNotNone(cmd_output)
+ # When we list hypervisors with older MV we get ids as integers. We
+ # need to verify that show finds resources independently
+ # Show test - latest microversion
+ for i in ids2:
+ cmd_output = json.loads(self.openstack(
+ "hypervisor show %s -f json"
+ % (i)
+ ))
+ self.assertIsNotNone(cmd_output)
diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py
index 830b3543..37183a79 100644
--- a/openstackclient/tests/functional/compute/v2/test_server.py
+++ b/openstackclient/tests/functional/compute/v2/test_server.py
@@ -11,6 +11,7 @@
# under the License.
import itertools
+import json
import time
import uuid
@@ -288,6 +289,33 @@ class ServerTests(common.ComputeTestCase):
)
self.assertOutput("", raw_output)
+ def test_server_show(self):
+ """Test server show"""
+ cmd_output = self.server_create()
+ name = cmd_output['name']
+
+ # Simple show
+ cmd_output = json.loads(self.openstack(
+ f'server show -f json {name}'
+ ))
+ self.assertEqual(
+ name,
+ cmd_output["name"],
+ )
+
+ # Show diagnostics
+ cmd_output = json.loads(self.openstack(
+ f'server show -f json {name} --diagnostics'
+ ))
+ self.assertIn('driver', cmd_output)
+
+ # Show topology
+ cmd_output = json.loads(self.openstack(
+ f'server show -f json {name} --topology '
+ f'--os-compute-api-version 2.78'
+ ))
+ self.assertIn('topology', cmd_output)
+
def test_server_actions(self):
"""Test server action pairs
@@ -1250,6 +1278,62 @@ class ServerTests(common.ComputeTestCase):
addresses = cmd_output['addresses']['private']
self.assertNotIn(ip_address, addresses)
+ def test_server_add_fixed_ip(self):
+ name = uuid.uuid4().hex
+ cmd_output = self.openstack(
+ 'server create ' +
+ '--network private ' +
+ '--flavor ' + self.flavor_name + ' ' +
+ '--image ' + self.image_name + ' ' +
+ '--wait ' +
+ name,
+ parse_output=True,
+ )
+
+ self.assertIsNotNone(cmd_output['id'])
+ self.assertEqual(name, cmd_output['name'])
+ self.addCleanup(self.openstack, 'server delete --wait ' + name)
+
+ # create port, record its ip address to use in later call,
+ # then delete - this is to figure out what should be a free ip
+ # in the subnet
+ port_name = uuid.uuid4().hex
+
+ cmd_output = self.openstack(
+ 'port list',
+ parse_output=True,
+ )
+ self.assertNotIn(port_name, cmd_output)
+
+ cmd_output = self.openstack(
+ 'port create ' +
+ '--network private ' + port_name,
+ parse_output=True,
+ )
+ self.assertIsNotNone(cmd_output['id'])
+ ip_address = cmd_output['fixed_ips'][0]['ip_address']
+ self.openstack('port delete ' + port_name)
+
+ # add fixed ip to server, assert the ip address appears
+ self.openstack('server add fixed ip --fixed-ip-address ' + ip_address +
+ ' ' + name + ' private')
+
+ wait_time = 0
+ while wait_time < 60:
+ cmd_output = self.openstack(
+ 'server show ' + name,
+ parse_output=True,
+ )
+ if ip_address not in cmd_output['addresses']['private']:
+ # Hang out for a bit and try again
+ print('retrying add fixed ip check')
+ wait_time += 10
+ time.sleep(10)
+ else:
+ break
+ addresses = cmd_output['addresses']['private']
+ self.assertIn(ip_address, addresses)
+
def test_server_add_remove_volume(self):
volume_wait_for = volume_common.BaseVolumeTests.wait_for_status
@@ -1331,3 +1415,9 @@ class ServerTests(common.ComputeTestCase):
raw_output = self.openstack('server volume list ' + server_name)
self.assertEqual('\n', raw_output)
+
+ def test_server_migration_list(self):
+ # Verify that the command does not raise an exception when we list
+ # migrations, including when we specify a query.
+ self.openstack('server migration list')
+ self.openstack('server migration list --limit 1')
diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py
index 4a27a941..b2702128 100644
--- a/openstackclient/tests/unit/compute/v2/fakes.py
+++ b/openstackclient/tests/unit/compute/v2/fakes.py
@@ -341,67 +341,6 @@ class FakeExtension(object):
return extension
-class FakeHypervisorStats(object):
- """Fake one or more hypervisor stats."""
-
- @staticmethod
- def create_one_hypervisor_stats(attrs=None):
- """Create a fake hypervisor stats.
-
- :param dict attrs:
- A dictionary with all attributes
- :return:
- A FakeResource object, with count, current_workload, and so on
- """
- attrs = attrs or {}
-
- # Set default attributes.
- stats_info = {
- 'count': 2,
- 'current_workload': 0,
- 'disk_available_least': 50,
- 'free_disk_gb': 100,
- 'free_ram_mb': 23000,
- 'local_gb': 100,
- 'local_gb_used': 0,
- 'memory_mb': 23800,
- 'memory_mb_used': 1400,
- 'running_vms': 3,
- 'vcpus': 8,
- 'vcpus_used': 3,
- }
-
- # Overwrite default attributes.
- stats_info.update(attrs)
-
- # Set default method.
- hypervisor_stats_method = {'to_dict': stats_info}
-
- hypervisor_stats = fakes.FakeResource(
- info=copy.deepcopy(stats_info),
- methods=copy.deepcopy(hypervisor_stats_method),
- loaded=True)
- return hypervisor_stats
-
- @staticmethod
- def create_hypervisors_stats(attrs=None, count=2):
- """Create multiple fake hypervisors stats.
-
- :param dict attrs:
- A dictionary with all attributes
- :param int count:
- The number of hypervisors to fake
- :return:
- A list of FakeResource objects faking the hypervisors
- """
- hypervisors = []
- for i in range(0, count):
- hypervisors.append(
- FakeHypervisorStats.create_one_hypervisor_stats(attrs))
-
- return hypervisors
-
-
class FakeSecurityGroup(object):
"""Fake one or more security groups."""
diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py b/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py
index 40086f9b..7bc7468a 100644
--- a/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py
+++ b/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py
@@ -12,9 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
#
+from unittest import mock
from openstackclient.compute.v2 import hypervisor_stats
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit import fakes
class TestHypervisorStats(compute_fakes.TestComputev2):
@@ -23,20 +25,55 @@ class TestHypervisorStats(compute_fakes.TestComputev2):
super(TestHypervisorStats, self).setUp()
# Get a shortcut to the compute client hypervisors mock
- self.hypervisors_mock = self.app.client_manager.compute.hypervisors
- self.hypervisors_mock.reset_mock()
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.app.client_manager.sdk_connection.compute = mock.Mock()
+ self.sdk_client = self.app.client_manager.sdk_connection.compute
+ self.sdk_client.get = mock.Mock()
+
+
+# Not in fakes.py because hypervisor stats has been deprecated
+
+def create_one_hypervisor_stats(attrs=None):
+ """Create a fake hypervisor stats.
+
+ :param dict attrs:
+ A dictionary with all attributes
+ :return:
+ A dictionary that contains hypervisor stats information keys
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ stats_info = {
+ 'count': 2,
+ 'current_workload': 0,
+ 'disk_available_least': 50,
+ 'free_disk_gb': 100,
+ 'free_ram_mb': 23000,
+ 'local_gb': 100,
+ 'local_gb_used': 0,
+ 'memory_mb': 23800,
+ 'memory_mb_used': 1400,
+ 'running_vms': 3,
+ 'vcpus': 8,
+ 'vcpus_used': 3,
+ }
+
+ # Overwrite default attributes.
+ stats_info.update(attrs)
+
+ return stats_info
class TestHypervisorStatsShow(TestHypervisorStats):
+ _stats = create_one_hypervisor_stats()
+
def setUp(self):
super(TestHypervisorStatsShow, self).setUp()
- self.hypervisor_stats = \
- compute_fakes.FakeHypervisorStats.create_one_hypervisor_stats()
-
- self.hypervisors_mock.statistics.return_value =\
- self.hypervisor_stats
+ self.sdk_client.get.return_value = fakes.FakeResponse(
+ data={'hypervisor_statistics': self._stats})
self.cmd = hypervisor_stats.ShowHypervisorStats(self.app, None)
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index ea8ded5c..00f9b3a7 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -423,8 +423,7 @@ class TestServerAddFixedIP(TestServer):
self.assertEqual(expected_data, tuple(data))
self.sdk_client.create_server_interface.assert_called_once_with(
servers[0].id,
- net_id=network['id'],
- fixed_ip=None
+ net_id=network['id']
)
@mock.patch.object(sdk_utils, 'supports_microversion')
@@ -479,7 +478,7 @@ class TestServerAddFixedIP(TestServer):
self.sdk_client.create_server_interface.assert_called_once_with(
servers[0].id,
net_id=network['id'],
- fixed_ip='5.6.7.8'
+ fixed_ips=[{'ip_address': '5.6.7.8'}]
)
@mock.patch.object(sdk_utils, 'supports_microversion')
@@ -536,7 +535,7 @@ class TestServerAddFixedIP(TestServer):
self.sdk_client.create_server_interface.assert_called_once_with(
servers[0].id,
net_id=network['id'],
- fixed_ip='5.6.7.8',
+ fixed_ips=[{'ip_address': '5.6.7.8'}],
tag='tag1',
)
@@ -847,7 +846,7 @@ class TestServerAddPort(TestServer):
result = self.cmd.take_action(parsed_args)
self.sdk_client.create_server_interface.assert_called_once_with(
- servers[0], port_id=port_id, fixed_ip=None)
+ servers[0], port_id=port_id)
self.assertIsNone(result)
def test_server_add_port(self):
@@ -885,7 +884,6 @@ class TestServerAddPort(TestServer):
self.sdk_client.create_server_interface.assert_called_once_with(
servers[0],
port_id='fake-port',
- fixed_ip=None,
tag='tag1')
@mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
@@ -1288,7 +1286,7 @@ class TestServerAddNetwork(TestServer):
result = self.cmd.take_action(parsed_args)
self.sdk_client.create_server_interface.assert_called_once_with(
- servers[0], net_id=net_id, fixed_ip=None)
+ servers[0], net_id=net_id)
self.assertIsNone(result)
def test_server_add_network(self):
@@ -1327,7 +1325,6 @@ class TestServerAddNetwork(TestServer):
self.sdk_client.create_server_interface.assert_called_once_with(
servers[0],
net_id='fake-network',
- fixed_ip=None,
tag='tag1'
)
@@ -4721,6 +4718,7 @@ class TestServerList(_TestServerList):
self.assertIn('Availability Zone', columns)
self.assertIn('Host', columns)
self.assertIn('Properties', columns)
+ self.assertCountEqual(columns, set(columns))
def test_server_list_no_name_lookup_option(self):
self.data = tuple(
@@ -7948,20 +7946,15 @@ class TestServerShow(TestServer):
'tenant_id': 'tenant-id-xxx',
'networks': {'public': ['10.20.30.40', '2001:db8::f']},
}
- # Fake the server.diagnostics() method. The return value contains http
- # response and data. The data is a dict. Sincce this method itself is
- # faked, we don't need to fake everything of the return value exactly.
- resp = mock.Mock()
- resp.status_code = 200
+ self.sdk_client.get_server_diagnostics.return_value = {'test': 'test'}
server_method = {
- 'diagnostics': (resp, {'test': 'test'}),
- 'topology': self.topology,
+ 'fetch_topology': self.topology,
}
self.server = compute_fakes.FakeServer.create_one_server(
attrs=server_info, methods=server_method)
# This is the return value for utils.find_resource()
- self.servers_mock.get.return_value = self.server
+ self.sdk_client.get_server.return_value = self.server
self.get_image_mock.return_value = self.image
self.flavors_mock.get.return_value = self.flavor
@@ -8062,8 +8055,7 @@ class TestServerShow(TestServer):
self.assertEqual(('test',), data)
def test_show_topology(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.78')
+ self._set_mock_microversion('2.78')
arglist = [
'--topology',
@@ -8085,8 +8077,7 @@ class TestServerShow(TestServer):
self.assertCountEqual(self.data, data)
def test_show_topology_pre_v278(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.77')
+ self._set_mock_microversion('2.77')
arglist = [
'--topology',
diff --git a/openstackclient/tests/unit/compute/v2/test_server_migration.py b/openstackclient/tests/unit/compute/v2/test_server_migration.py
index c4cbac47..93c1865a 100644
--- a/openstackclient/tests/unit/compute/v2/test_server_migration.py
+++ b/openstackclient/tests/unit/compute/v2/test_server_migration.py
@@ -13,6 +13,7 @@
from unittest import mock
from novaclient import api_versions
+from openstack import utils as sdk_utils
from osc_lib import exceptions
from osc_lib import utils as common_utils
@@ -35,10 +36,6 @@ class TestServerMigration(compute_fakes.TestComputev2):
self.app.client_manager.compute.server_migrations
self.server_migrations_mock.reset_mock()
- # Get a shortcut to the compute client MigrationManager mock
- self.migrations_mock = self.app.client_manager.compute.migrations
- self.migrations_mock.reset_mock()
-
self.app.client_manager.sdk_connection = mock.Mock()
self.app.client_manager.sdk_connection.compute = mock.Mock()
self.sdk_client = self.app.client_manager.sdk_connection.compute
@@ -53,22 +50,21 @@ class TestListMigration(TestServerMigration):
'Old Flavor', 'New Flavor', 'Created At', 'Updated At'
]
- # These are the fields that come back in the response from the REST API.
MIGRATION_FIELDS = [
'source_node', 'dest_node', 'source_compute', 'dest_compute',
- 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id',
- 'new_instance_type_id', 'created_at', 'updated_at'
+ 'dest_host', 'status', 'server_id', 'old_flavor_id',
+ 'new_flavor_id', 'created_at', 'updated_at'
]
def setUp(self):
super().setUp()
self.server = compute_fakes.FakeServer.create_one_server()
- self.servers_mock.get.return_value = self.server
+ self.sdk_client.find_server.return_value = self.server
self.migrations = compute_fakes.FakeMigration.create_migrations(
count=3)
- self.migrations_mock.list.return_value = self.migrations
+ self.sdk_client.migrations.return_value = self.migrations
self.data = (common_utils.get_item_properties(
s, self.MIGRATION_FIELDS) for s in self.migrations)
@@ -76,6 +72,20 @@ class TestListMigration(TestServerMigration):
# Get the command object to test
self.cmd = server_migration.ListMigration(self.app, None)
+ patcher = mock.patch.object(
+ sdk_utils, 'supports_microversion', return_value=True)
+ self.addCleanup(patcher.stop)
+ self.supports_microversion_mock = patcher.start()
+ self._set_mock_microversion(
+ self.app.client_manager.compute.api_version.get_string())
+
+ def _set_mock_microversion(self, mock_v):
+ """Set a specific microversion for the mock supports_microversion()."""
+ self.supports_microversion_mock.reset_mock(return_value=True)
+ self.supports_microversion_mock.side_effect = (
+ lambda _, v:
+ api_versions.APIVersion(v) <= api_versions.APIVersion(mock_v))
+
def test_server_migration_list_no_options(self):
arglist = []
verifylist = []
@@ -84,12 +94,9 @@ class TestListMigration(TestServerMigration):
columns, data = self.cmd.take_action(parsed_args)
# Set expected values
- kwargs = {
- 'status': None,
- 'host': None,
- }
+ kwargs = {}
- self.migrations_mock.list.assert_called_with(**kwargs)
+ self.sdk_client.migrations.assert_called_with(**kwargs)
self.assertEqual(self.MIGRATION_COLUMNS, columns)
self.assertEqual(tuple(self.data), tuple(data))
@@ -117,8 +124,8 @@ class TestListMigration(TestServerMigration):
'migration_type': 'migration',
}
- self.servers_mock.get.assert_called_with('server1')
- self.migrations_mock.list.assert_called_with(**kwargs)
+ self.sdk_client.find_server.assert_called_with('server1')
+ self.sdk_client.migrations.assert_called_with(**kwargs)
self.assertEqual(self.MIGRATION_COLUMNS, columns)
self.assertEqual(tuple(self.data), tuple(data))
@@ -133,18 +140,17 @@ class TestListMigrationV223(TestListMigration):
'Type', 'Created At', 'Updated At'
]
- # These are the fields that come back in the response from the REST API.
+ # These are the Migration object fields.
MIGRATION_FIELDS = [
'id', 'source_node', 'dest_node', 'source_compute', 'dest_compute',
- 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id',
- 'new_instance_type_id', 'migration_type', 'created_at', 'updated_at'
+ 'dest_host', 'status', 'server_id', 'old_flavor_id',
+ 'new_flavor_id', 'migration_type', 'created_at', 'updated_at'
]
def setUp(self):
super().setUp()
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.23')
+ self._set_mock_microversion('2.23')
def test_server_migration_list(self):
arglist = [
@@ -159,10 +165,9 @@ class TestListMigrationV223(TestListMigration):
# Set expected values
kwargs = {
'status': 'migrating',
- 'host': None,
}
- self.migrations_mock.list.assert_called_with(**kwargs)
+ self.sdk_client.migrations.assert_called_with(**kwargs)
self.assertEqual(self.MIGRATION_COLUMNS, columns)
self.assertEqual(tuple(self.data), tuple(data))
@@ -177,19 +182,18 @@ class TestListMigrationV259(TestListMigration):
'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At'
]
- # These are the fields that come back in the response from the REST API.
+ # These are the Migration object fields.
MIGRATION_FIELDS = [
'id', 'uuid', 'source_node', 'dest_node', 'source_compute',
- 'dest_compute', 'dest_host', 'status', 'instance_uuid',
- 'old_instance_type_id', 'new_instance_type_id', 'migration_type',
+ 'dest_compute', 'dest_host', 'status', 'server_id',
+ 'old_flavor_id', 'new_flavor_id', 'migration_type',
'created_at', 'updated_at'
]
def setUp(self):
super().setUp()
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.59')
+ self._set_mock_microversion('2.59')
def test_server_migration_list(self):
arglist = [
@@ -211,19 +215,18 @@ class TestListMigrationV259(TestListMigration):
kwargs = {
'status': 'migrating',
'limit': 1,
+ 'paginated': False,
'marker': 'test_kp',
- 'host': None,
'changes_since': '2019-08-09T08:03:25Z',
}
- self.migrations_mock.list.assert_called_with(**kwargs)
+ self.sdk_client.migrations.assert_called_with(**kwargs)
self.assertEqual(self.MIGRATION_COLUMNS, columns)
self.assertEqual(tuple(self.data), tuple(data))
def test_server_migration_list_with_limit_pre_v259(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.58')
+ self._set_mock_microversion('2.58')
arglist = [
'--status', 'migrating',
'--limit', '1'
@@ -242,8 +245,7 @@ class TestListMigrationV259(TestListMigration):
str(ex))
def test_server_migration_list_with_marker_pre_v259(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.58')
+ self._set_mock_microversion('2.58')
arglist = [
'--status', 'migrating',
'--marker', 'test_kp'
@@ -262,8 +264,7 @@ class TestListMigrationV259(TestListMigration):
str(ex))
def test_server_migration_list_with_changes_since_pre_v259(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.58')
+ self._set_mock_microversion('2.58')
arglist = [
'--status', 'migrating',
'--changes-since', '2019-08-09T08:03:25Z'
@@ -291,19 +292,18 @@ class TestListMigrationV266(TestListMigration):
'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At'
]
- # These are the fields that come back in the response from the REST API.
+ # These are the Migration object fields.
MIGRATION_FIELDS = [
'id', 'uuid', 'source_node', 'dest_node', 'source_compute',
- 'dest_compute', 'dest_host', 'status', 'instance_uuid',
- 'old_instance_type_id', 'new_instance_type_id', 'migration_type',
+ 'dest_compute', 'dest_host', 'status', 'server_id',
+ 'old_flavor_id', 'new_flavor_id', 'migration_type',
'created_at', 'updated_at'
]
def setUp(self):
super().setUp()
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.66')
+ self._set_mock_microversion('2.66')
def test_server_migration_list_with_changes_before(self):
arglist = [
@@ -327,20 +327,19 @@ class TestListMigrationV266(TestListMigration):
kwargs = {
'status': 'migrating',
'limit': 1,
+ 'paginated': False,
'marker': 'test_kp',
- 'host': None,
'changes_since': '2019-08-07T08:03:25Z',
'changes_before': '2019-08-09T08:03:25Z',
}
- self.migrations_mock.list.assert_called_with(**kwargs)
+ self.sdk_client.migrations.assert_called_with(**kwargs)
self.assertEqual(self.MIGRATION_COLUMNS, columns)
self.assertEqual(tuple(self.data), tuple(data))
def test_server_migration_list_with_changes_before_pre_v266(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.65')
+ self._set_mock_microversion('2.65')
arglist = [
'--status', 'migrating',
'--changes-before', '2019-08-09T08:03:25Z'
@@ -368,11 +367,11 @@ class TestListMigrationV280(TestListMigration):
'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At'
]
- # These are the fields that come back in the response from the REST API.
+ # These are the Migration object fields.
MIGRATION_FIELDS = [
'id', 'uuid', 'source_node', 'dest_node', 'source_compute',
- 'dest_compute', 'dest_host', 'status', 'instance_uuid',
- 'old_instance_type_id', 'new_instance_type_id', 'migration_type',
+ 'dest_compute', 'dest_host', 'status', 'server_id',
+ 'old_flavor_id', 'new_flavor_id', 'migration_type',
'created_at', 'updated_at'
]
@@ -391,8 +390,7 @@ class TestListMigrationV280(TestListMigration):
self.projects_mock.get.return_value = self.project
self.users_mock.get.return_value = self.user
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.80')
+ self._set_mock_microversion('2.80')
def test_server_migration_list_with_project(self):
arglist = [
@@ -418,14 +416,14 @@ class TestListMigrationV280(TestListMigration):
kwargs = {
'status': 'migrating',
'limit': 1,
+ 'paginated': False,
'marker': 'test_kp',
- 'host': None,
'project_id': self.project.id,
'changes_since': '2019-08-07T08:03:25Z',
'changes_before': "2019-08-09T08:03:25Z",
}
- self.migrations_mock.list.assert_called_with(**kwargs)
+ self.sdk_client.migrations.assert_called_with(**kwargs)
self.MIGRATION_COLUMNS.insert(
len(self.MIGRATION_COLUMNS) - 2, "Project")
@@ -439,8 +437,7 @@ class TestListMigrationV280(TestListMigration):
self.MIGRATION_FIELDS.remove('project_id')
def test_get_migrations_with_project_pre_v280(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.79')
+ self._set_mock_microversion('2.79')
arglist = [
'--status', 'migrating',
'--changes-before', '2019-08-09T08:03:25Z',
@@ -478,20 +475,21 @@ class TestListMigrationV280(TestListMigration):
('user', self.user.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
columns, data = self.cmd.take_action(parsed_args)
# Set expected values
kwargs = {
'status': 'migrating',
'limit': 1,
+ 'paginated': False,
'marker': 'test_kp',
- 'host': None,
'user_id': self.user.id,
'changes_since': '2019-08-07T08:03:25Z',
'changes_before': "2019-08-09T08:03:25Z",
}
- self.migrations_mock.list.assert_called_with(**kwargs)
+ self.sdk_client.migrations.assert_called_with(**kwargs)
self.MIGRATION_COLUMNS.insert(
len(self.MIGRATION_COLUMNS) - 2, "User")
@@ -505,8 +503,7 @@ class TestListMigrationV280(TestListMigration):
self.MIGRATION_FIELDS.remove('user_id')
def test_get_migrations_with_user_pre_v280(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.79')
+ self._set_mock_microversion('2.79')
arglist = [
'--status', 'migrating',
'--changes-before', '2019-08-09T08:03:25Z',
@@ -550,14 +547,14 @@ class TestListMigrationV280(TestListMigration):
kwargs = {
'status': 'migrating',
'limit': 1,
- 'host': None,
+ 'paginated': False,
'project_id': self.project.id,
'user_id': self.user.id,
'changes_since': '2019-08-07T08:03:25Z',
'changes_before': "2019-08-09T08:03:25Z",
}
- self.migrations_mock.list.assert_called_with(**kwargs)
+ self.sdk_client.migrations.assert_called_with(**kwargs)
self.MIGRATION_COLUMNS.insert(
len(self.MIGRATION_COLUMNS) - 2, "Project")
@@ -576,8 +573,7 @@ class TestListMigrationV280(TestListMigration):
self.MIGRATION_FIELDS.remove('user_id')
def test_get_migrations_with_project_and_user_pre_v280(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.79')
+ self._set_mock_microversion('2.79')
arglist = [
'--status', 'migrating',
'--changes-before', '2019-08-09T08:03:25Z',
diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py
index ded9ff31..8ddd9a09 100644
--- a/openstackclient/tests/unit/image/v2/fakes.py
+++ b/openstackclient/tests/unit/image/v2/fakes.py
@@ -239,7 +239,11 @@ def create_tasks(attrs=None, count=2):
class FakeMetadefNamespaceClient:
def __init__(self, **kwargs):
+ self.create_metadef_namespace = mock.Mock()
+ self.delete_metadef_namespace = mock.Mock()
self.metadef_namespaces = mock.Mock()
+ self.get_metadef_namespace = mock.Mock()
+ self.update_metadef_namespace = mock.Mock()
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
@@ -277,10 +281,11 @@ def create_one_metadef_namespace(attrs=None):
'display_name': 'Flavor Quota',
'namespace': 'OS::Compute::Quota',
'owner': 'admin',
- 'resource_type_associations': ['OS::Nova::Flavor'],
+ # 'resource_type_associations': ['OS::Nova::Flavor'],
+ # The part that receives the list type factor is not implemented.
'visibility': 'public',
}
# Overwrite default attributes if there are some attributes set
metadef_namespace_list.update(attrs)
- return metadef_namespace.MetadefNamespace(metadef_namespace_list)
+ return metadef_namespace.MetadefNamespace(**metadef_namespace_list)
diff --git a/openstackclient/tests/unit/image/v2/test_metadef_namespaces.py b/openstackclient/tests/unit/image/v2/test_metadef_namespaces.py
index 5eae289c..7ed11838 100644
--- a/openstackclient/tests/unit/image/v2/test_metadef_namespaces.py
+++ b/openstackclient/tests/unit/image/v2/test_metadef_namespaces.py
@@ -30,8 +30,89 @@ class TestMetadefNamespaces(md_namespace_fakes.TestMetadefNamespaces):
self.domain_mock.reset_mock()
-class TestMetadefNamespaceList(TestMetadefNamespaces):
+class TestMetadefNamespaceCreate(TestMetadefNamespaces):
+ _metadef_namespace = md_namespace_fakes.create_one_metadef_namespace()
+
+ expected_columns = (
+ 'created_at',
+ 'description',
+ 'display_name',
+ 'id',
+ 'is_protected',
+ 'location',
+ 'name',
+ 'namespace',
+ 'owner',
+ 'resource_type_associations',
+ 'updated_at',
+ 'visibility'
+ )
+ expected_data = (
+ _metadef_namespace.created_at,
+ _metadef_namespace.description,
+ _metadef_namespace.display_name,
+ _metadef_namespace.id,
+ _metadef_namespace.is_protected,
+ _metadef_namespace.location,
+ _metadef_namespace.name,
+ _metadef_namespace.namespace,
+ _metadef_namespace.owner,
+ _metadef_namespace.resource_type_associations,
+ _metadef_namespace.updated_at,
+ _metadef_namespace.visibility
+ )
+
+ def setUp(self):
+ super().setUp()
+
+ self.client.create_metadef_namespace.return_value \
+ = self._metadef_namespace
+ self.cmd = metadef_namespaces.CreateMetadefNameSpace(self.app, None)
+ self.datalist = self._metadef_namespace
+
+ def test_namespace_create(self):
+ arglist = [
+ self._metadef_namespace.namespace
+ ]
+
+ verifylist = [
+
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.assertEqual(self.expected_columns, columns)
+ self.assertEqual(self.expected_data, data)
+
+
+class TestMetadefNamespaceDelete(TestMetadefNamespaces):
+ _metadef_namespace = md_namespace_fakes.create_one_metadef_namespace()
+
+ def setUp(self):
+ super().setUp()
+
+ self.client.delete_metadef_namespace.return_value \
+ = self._metadef_namespace
+ self.cmd = metadef_namespaces.DeleteMetadefNameSpace(self.app, None)
+ self.datalist = self._metadef_namespace
+
+ def test_namespace_create(self):
+ arglist = [
+ self._metadef_namespace.namespace
+ ]
+
+ verifylist = [
+
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertIsNone(result)
+
+class TestMetadefNamespaceList(TestMetadefNamespaces):
_metadef_namespace = [md_namespace_fakes.create_one_metadef_namespace()]
columns = [
@@ -65,3 +146,70 @@ class TestMetadefNamespaceList(TestMetadefNamespaces):
self.assertEqual(self.columns, columns)
self.assertEqual(getattr(self.datalist[0], 'namespace'),
next(data)[0])
+
+
+class TestMetadefNamespaceSet(TestMetadefNamespaces):
+ _metadef_namespace = md_namespace_fakes.create_one_metadef_namespace()
+
+ def setUp(self):
+ super().setUp()
+
+ self.client.update_metadef_namespace.return_value \
+ = self._metadef_namespace
+ self.cmd = metadef_namespaces.SetMetadefNameSpace(self.app, None)
+ self.datalist = self._metadef_namespace
+
+ def test_namespace_set_no_options(self):
+ arglist = [
+ self._metadef_namespace.namespace
+ ]
+ verifylist = [
+ ('namespace', self._metadef_namespace.namespace),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertIsNone(result)
+
+
+class TestMetadefNamespaceShow(TestMetadefNamespaces):
+ _metadef_namespace = md_namespace_fakes.create_one_metadef_namespace()
+
+ expected_columns = (
+ 'created_at',
+ 'display_name',
+ 'namespace',
+ 'owner',
+ 'visibility'
+ )
+ expected_data = (
+ _metadef_namespace.created_at,
+ _metadef_namespace.display_name,
+ _metadef_namespace.namespace,
+ _metadef_namespace.owner,
+ _metadef_namespace.visibility
+ )
+
+ def setUp(self):
+ super().setUp()
+
+ self.client.get_metadef_namespace.return_value \
+ = self._metadef_namespace
+ self.cmd = metadef_namespaces.ShowMetadefNameSpace(self.app, None)
+
+ def test_namespace_show_no_options(self):
+ arglist = [
+ self._metadef_namespace.namespace
+ ]
+
+ verifylist = [
+
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.assertEqual(self.expected_columns, columns)
+ self.assertEqual(self.expected_data, data)
diff --git a/releasenotes/notes/add-baremetal-agent-type-7c46365e8d457ac8.yaml b/releasenotes/notes/add-baremetal-agent-type-7c46365e8d457ac8.yaml
new file mode 100644
index 00000000..a9a3a0df
--- /dev/null
+++ b/releasenotes/notes/add-baremetal-agent-type-7c46365e8d457ac8.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add ``baremetal`` agent type to ``--agent-type`` option for
+ ``network agent list`` command.
diff --git a/releasenotes/notes/image-metadef-namespace-b940206bece64f97.yaml b/releasenotes/notes/image-metadef-namespace-b940206bece64f97.yaml
new file mode 100644
index 00000000..361e57fe
--- /dev/null
+++ b/releasenotes/notes/image-metadef-namespace-b940206bece64f97.yaml
@@ -0,0 +1,10 @@
+---
+features:
+ - Add ``openstack image metadef namespace create`` command
+ to create metadef namespace for the image service.
+ - Add ``openstack image metadef namespace delete`` command
+ to delete image metadef namespace.
+ - Add ``openstack image metadef namespace set`` command
+ to update metadef namespace for the image service.
+ - Add ``openstack image metadef namespace show`` command
+ to show metadef namespace for the image service.
diff --git a/releasenotes/notes/switch-hypervisor-to-sdk-f6495f070b034718.yaml b/releasenotes/notes/switch-hypervisor-to-sdk-f6495f070b034718.yaml
new file mode 100644
index 00000000..6f1721b1
--- /dev/null
+++ b/releasenotes/notes/switch-hypervisor-to-sdk-f6495f070b034718.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - Switch hypervisor operations to consume OpenStackSDK
diff --git a/releasenotes/notes/switch-server-migration-to-sdk-4e4530f787f90fd2.yaml b/releasenotes/notes/switch-server-migration-to-sdk-4e4530f787f90fd2.yaml
new file mode 100644
index 00000000..318ac097
--- /dev/null
+++ b/releasenotes/notes/switch-server-migration-to-sdk-4e4530f787f90fd2.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ The ``server migration *`` commands now use the OpenStackSDK instead of
+ novaclient.
diff --git a/releasenotes/notes/switch-server-show-to-sdk-44a614aebf2c6da6.yaml b/releasenotes/notes/switch-server-show-to-sdk-44a614aebf2c6da6.yaml
new file mode 100644
index 00000000..c116f6e0
--- /dev/null
+++ b/releasenotes/notes/switch-server-show-to-sdk-44a614aebf2c6da6.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ The ``server show`` command now uses the OpenStack SDK instead of the
+ Python nova bindings. The command prints data fields both by their
+ novaclient names used in previous releases as well as the names used in the
+ SDK.
diff --git a/setup.cfg b/setup.cfg
index fa3d30fe..42ce970b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -386,7 +386,12 @@ openstack.image.v2 =
image_stage = openstackclient.image.v2.image:StageImage
image_task_show = openstackclient.image.v2.task:ShowTask
image_task_list = openstackclient.image.v2.task:ListTask
+
+ image_metadef_namespace_create = openstackclient.image.v2.metadef_namespaces:CreateMetadefNameSpace
+ image_metadef_namespace_delete = openstackclient.image.v2.metadef_namespaces:DeleteMetadefNameSpace
image_metadef_namespace_list = openstackclient.image.v2.metadef_namespaces:ListMetadefNameSpaces
+ image_metadef_namespace_set = openstackclient.image.v2.metadef_namespaces:SetMetadefNameSpace
+ image_metadef_namespace_show = openstackclient.image.v2.metadef_namespaces:ShowMetadefNameSpace
openstack.network.v2 =
address_group_create = openstackclient.network.v2.address_group:CreateAddressGroup