summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/__init__.py4
-rw-r--r--openstackclient/api/image_v2.py17
-rw-r--r--openstackclient/common/clientmanager.py19
-rw-r--r--openstackclient/common/extension.py6
-rw-r--r--openstackclient/common/limits.py32
-rw-r--r--openstackclient/common/project_purge.py4
-rw-r--r--openstackclient/common/versions.py114
-rw-r--r--openstackclient/compute/v2/console.py7
-rw-r--r--openstackclient/compute/v2/flavor.py28
-rw-r--r--openstackclient/compute/v2/host.py2
-rw-r--r--openstackclient/compute/v2/server.py197
-rw-r--r--openstackclient/compute/v2/server_backup.py16
-rw-r--r--openstackclient/compute/v2/server_image.py15
-rw-r--r--openstackclient/compute/v2/service.py26
-rw-r--r--openstackclient/compute/v2/usage.py8
-rw-r--r--openstackclient/identity/v3/endpoint.py2
-rw-r--r--openstackclient/identity/v3/group.py5
-rw-r--r--openstackclient/identity/v3/implied_role.py6
-rw-r--r--openstackclient/identity/v3/limit.py238
-rw-r--r--openstackclient/identity/v3/registered_limit.py260
-rw-r--r--openstackclient/identity/v3/role.py41
-rw-r--r--openstackclient/identity/v3/role_assignment.py35
-rw-r--r--openstackclient/identity/v3/token.py6
-rw-r--r--openstackclient/image/v1/image.py10
-rw-r--r--openstackclient/image/v2/image.py113
-rw-r--r--openstackclient/network/client.py52
-rw-r--r--openstackclient/network/common.py25
-rw-r--r--openstackclient/network/v2/floating_ip.py168
-rw-r--r--openstackclient/network/v2/floating_ip_pool.py25
-rw-r--r--openstackclient/network/v2/network.py32
-rw-r--r--openstackclient/network/v2/network_qos_rule.py13
-rw-r--r--openstackclient/network/v2/port.py41
-rw-r--r--openstackclient/network/v2/router.py14
-rw-r--r--openstackclient/network/v2/security_group.py43
-rw-r--r--openstackclient/network/v2/subnet.py21
-rw-r--r--openstackclient/network/v2/subnet_pool.py10
-rw-r--r--openstackclient/tests/functional/common/test_module.py2
-rw-r--r--openstackclient/tests/functional/common/test_versions.py31
-rw-r--r--openstackclient/tests/functional/compute/v2/test_aggregate.py18
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server.py40
-rw-r--r--openstackclient/tests/functional/identity/v3/common.py93
-rw-r--r--openstackclient/tests/functional/identity/v3/test_limit.py192
-rw-r--r--openstackclient/tests/functional/identity/v3/test_registered_limit.py184
-rw-r--r--openstackclient/tests/functional/identity/v3/test_role.py25
-rw-r--r--openstackclient/tests/functional/image/base.py24
-rw-r--r--openstackclient/tests/functional/image/v1/test_image.py55
-rw-r--r--openstackclient/tests/functional/image/v2/test_image.py132
-rw-r--r--openstackclient/tests/functional/network/v2/test_network.py70
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_agent.py45
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_flavor.py6
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_qos_rule.py153
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_segment.py17
-rw-r--r--openstackclient/tests/functional/network/v2/test_port.py90
-rw-r--r--openstackclient/tests/functional/object/v1/test_object.py32
-rw-r--r--openstackclient/tests/functional/volume/v1/test_transfer_request.py37
-rw-r--r--openstackclient/tests/functional/volume/v1/test_volume_type.py119
-rw-r--r--openstackclient/tests/functional/volume/v2/test_qos.py5
-rw-r--r--openstackclient/tests/functional/volume/v2/test_transfer_request.py42
-rw-r--r--openstackclient/tests/functional/volume/v2/test_volume_type.py136
-rw-r--r--openstackclient/tests/functional/volume/v3/test_transfer_request.py2
-rw-r--r--openstackclient/tests/unit/common/test_limits.py125
-rw-r--r--openstackclient/tests/unit/common/test_project_purge.py30
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py111
-rw-r--r--openstackclient/tests/unit/compute/v2/test_flavor.py136
-rw-r--r--openstackclient/tests/unit/compute/v2/test_host.py7
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py283
-rw-r--r--openstackclient/tests/unit/compute/v2/test_service.py10
-rw-r--r--openstackclient/tests/unit/fakes.py9
-rw-r--r--openstackclient/tests/unit/identity/v3/fakes.py50
-rw-r--r--openstackclient/tests/unit/identity/v3/test_endpoint.py41
-rw-r--r--openstackclient/tests/unit/identity/v3/test_implied_role.py32
-rw-r--r--openstackclient/tests/unit/identity/v3/test_limit.py382
-rw-r--r--openstackclient/tests/unit/identity/v3/test_registered_limit.py510
-rw-r--r--openstackclient/tests/unit/identity/v3/test_role_assignment.py42
-rw-r--r--openstackclient/tests/unit/image/v2/test_image.py92
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py5
-rw-r--r--openstackclient/tests/unit/network/v2/test_floating_ip_network.py193
-rw-r--r--openstackclient/tests/unit/network/v2/test_network.py21
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_qos_rule.py55
-rw-r--r--openstackclient/tests/unit/network/v2/test_port.py18
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group_network.py174
-rw-r--r--openstackclient/tests/unit/network/v2/test_subnet.py53
-rw-r--r--openstackclient/tests/unit/volume/test_find_resource.py4
-rw-r--r--openstackclient/tests/unit/volume/v2/fakes.py198
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py154
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume_backend.py168
-rw-r--r--openstackclient/volume/client.py21
-rw-r--r--openstackclient/volume/v1/volume_type.py8
-rw-r--r--openstackclient/volume/v2/volume.py66
-rw-r--r--openstackclient/volume/v2/volume_backend.py113
-rw-r--r--openstackclient/volume/v2/volume_type.py8
91 files changed, 5308 insertions, 1016 deletions
diff --git a/openstackclient/__init__.py b/openstackclient/__init__.py
index 89deee32..e6e7f7c0 100644
--- a/openstackclient/__init__.py
+++ b/openstackclient/__init__.py
@@ -11,10 +11,10 @@
# under the License.
#
-__all__ = ['__version__']
-
import pbr.version
+__all__ = ['__version__']
+
version_info = pbr.version.VersionInfo('python-openstackclient')
try:
__version__ = version_info.version_string()
diff --git a/openstackclient/api/image_v2.py b/openstackclient/api/image_v2.py
index c3628121..d0163189 100644
--- a/openstackclient/api/image_v2.py
+++ b/openstackclient/api/image_v2.py
@@ -31,6 +31,7 @@ class APIv2(image_v1.APIv1):
detailed=False,
public=False,
private=False,
+ community=False,
shared=False,
**filter
):
@@ -44,25 +45,29 @@ class APIv2(image_v1.APIv1):
Return public images if True
:param private:
Return private images if True
+ :param community:
+ Return commuity images if True
:param shared:
Return shared images if True
- If public, private and shared are all True or all False then all
- images are returned. All arguments False is equivalent to no filter
- and all images are returned. All arguments True is a filter that
- includes all public, private and shared images which is the same set
- as all images.
+ If public, private, community and shared are all True or all False
+ then all images are returned. All arguments False is equivalent to no
+ filter and all images are returned. All arguments True is a filter
+ that includes all public, private, community and shared images which
+ is the same set as all images.
http://docs.openstack.org/api/openstack-image-service/2.0/content/list-images.html
"""
- if not public and not private and not shared:
+ if not public and not private and not community and not shared:
# No filtering for all False
filter.pop('visibility', None)
elif public:
filter['visibility'] = 'public'
elif private:
filter['visibility'] = 'private'
+ elif community:
+ filter['visibility'] = 'community'
elif shared:
filter['visibility'] = 'shared'
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index 89781052..aa1045e4 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -125,6 +125,25 @@ class ClientManager(clientmanager.ClientManager):
# use Network API by default
return self.is_service_available('network') is not False
+ def is_compute_endpoint_enabled(self):
+ """Check if Compute endpoint is enabled"""
+
+ return self.is_service_available('compute') is not False
+
+ def is_volume_endpoint_enabled(self, volume_client):
+ """Check if volume endpoint is enabled"""
+ # NOTE(jcross): Cinder did some interesting things with their service
+ # name so we need to figure out which version to look
+ # for when calling is_service_available()
+ volume_version = volume_client.api_version.ver_major
+ if self.is_service_available(
+ "volumev%s" % volume_version) is not False:
+ return True
+ elif self.is_service_available('volume') is not False:
+ return True
+ else:
+ return False
+
# Plugin Support
diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py
index 139f43ab..71206618 100644
--- a/openstackclient/common/extension.py
+++ b/openstackclient/common/extension.py
@@ -75,8 +75,10 @@ class ListExtension(command.Lister):
# by default we want to show everything, unless the
# user specifies one or more of the APIs to show
# for now, only identity and compute are supported.
- show_all = (not parsed_args.identity and not parsed_args.compute
- and not parsed_args.volume and not parsed_args.network)
+ show_all = (not parsed_args.identity and
+ not parsed_args.compute and
+ not parsed_args.volume and
+ not parsed_args.network)
if parsed_args.identity or show_all:
identity_client = self.app.client_manager.identity
diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py
index 957f1d02..19db35d7 100644
--- a/openstackclient/common/limits.py
+++ b/openstackclient/common/limits.py
@@ -83,24 +83,34 @@ class ShowLimits(command.Lister):
project_id = utils.find_resource(identity_client.projects,
parsed_args.project).id
- compute_limits = compute_client.limits.get(parsed_args.is_reserved,
- tenant_id=project_id)
- volume_limits = volume_client.limits.get()
+ compute_limits = None
+ volume_limits = None
+ if self.app.client_manager.is_compute_endpoint_enabled():
+ compute_limits = compute_client.limits.get(parsed_args.is_reserved,
+ tenant_id=project_id)
+
+ if self.app.client_manager.is_volume_endpoint_enabled(volume_client):
+ volume_limits = volume_client.limits.get()
+
+ data = []
if parsed_args.is_absolute:
- compute_limits = compute_limits.absolute
- volume_limits = volume_limits.absolute
+ if compute_limits:
+ data.append(compute_limits.absolute)
+ if volume_limits:
+ data.append(volume_limits.absolute)
columns = ["Name", "Value"]
return (columns, (utils.get_item_properties(s, columns)
- for s in itertools.chain(compute_limits, volume_limits)))
+ for s in itertools.chain(*data)))
elif parsed_args.is_rate:
- compute_limits = compute_limits.rate
- volume_limits = volume_limits.rate
+ if compute_limits:
+ data.append(compute_limits.rate)
+ if volume_limits:
+ data.append(volume_limits.rate)
columns = ["Verb", "URI", "Value", "Remain", "Unit",
"Next Available"]
return (columns, (utils.get_item_properties(s, columns)
- for s in itertools.chain(compute_limits, volume_limits)))
-
+ for s in itertools.chain(*data)))
else:
- return ({}, {})
+ return {}, {}
diff --git a/openstackclient/common/project_purge.py b/openstackclient/common/project_purge.py
index 5b1d0072..76ed4563 100644
--- a/openstackclient/common/project_purge.py
+++ b/openstackclient/common/project_purge.py
@@ -85,7 +85,7 @@ class ProjectPurge(command.Command):
# servers
try:
compute_client = self.app.client_manager.compute
- search_opts = {'tenant_id': project_id}
+ search_opts = {'tenant_id': project_id, 'all_tenants': True}
data = compute_client.servers.list(search_opts=search_opts)
self.delete_objects(
compute_client.servers.delete, data, 'server', dry_run)
@@ -110,7 +110,7 @@ class ProjectPurge(command.Command):
# volumes, snapshots, backups
volume_client = self.app.client_manager.volume
- search_opts = {'project_id': project_id}
+ search_opts = {'project_id': project_id, 'all_tenants': True}
try:
data = volume_client.volume_snapshots.list(search_opts=search_opts)
self.delete_objects(
diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py
new file mode 100644
index 00000000..6a93d300
--- /dev/null
+++ b/openstackclient/common/versions.py
@@ -0,0 +1,114 @@
+# Copyright 2018 Red Hat, Inc.
+#
+# 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.
+
+"""Versions Action Implementation"""
+
+import os_service_types
+from osc_lib.command import command
+
+from openstackclient.i18n import _
+
+
+class ShowVersions(command.Lister):
+ _description = _("Show available versions of services")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowVersions, self).get_parser(prog_name)
+ interface_group = parser.add_mutually_exclusive_group()
+ interface_group.add_argument(
+ "--all-interfaces",
+ dest="is_all_interfaces",
+ action="store_true",
+ default=False,
+ help=_("Show values for all interfaces"),
+ )
+ interface_group.add_argument(
+ '--interface',
+ default='public',
+ metavar='<interface>',
+ help=_('Show versions for a specific interface.'),
+ )
+ parser.add_argument(
+ '--region-name',
+ metavar='<region_name>',
+ help=_('Show versions for a specific region.'),
+ )
+ parser.add_argument(
+ '--service',
+ metavar='<region_name>',
+ help=_('Show versions for a specific service.'),
+ )
+ parser.add_argument(
+ '--status',
+ metavar='<region_name>',
+ help=_('Show versions for a specific status.'
+ ' [Valid values are SUPPORTED, CURRENT,'
+ ' DEPRECATED, EXPERIMENTAL]'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+
+ interface = parsed_args.interface
+ if parsed_args.is_all_interfaces:
+ interface = None
+
+ session = self.app.client_manager.session
+ version_data = session.get_all_version_data(
+ interface=interface,
+ region_name=parsed_args.region_name)
+
+ columns = [
+ "Region Name",
+ "Service Type",
+ "Version",
+ "Status",
+ "Endpoint",
+ "Min Microversion",
+ "Max Microversion",
+ ]
+
+ status = parsed_args.status
+ if status:
+ status = status.upper()
+
+ service = parsed_args.service
+ if service:
+ # Normalize service type argument to official type
+ service_type_manager = os_service_types.ServiceTypes()
+ service = service_type_manager.get_service_type(service)
+
+ versions = []
+ for region_name, interfaces in version_data.items():
+ for interface, services in interfaces.items():
+ for service_type, service_versions in services.items():
+ if service and service != service_type:
+ # TODO(mordred) Once there is a version of
+ # keystoneauth that can do this filtering
+ # before making all the discovery calls, switch
+ # to that.
+ continue
+ for data in service_versions:
+ if status and status != data['status']:
+ continue
+ versions.append((
+ region_name,
+ service_type,
+ data['version'],
+ data['status'],
+ data['url'],
+ data['min_microversion'],
+ data['max_microversion'],
+ ))
+ return (columns, versions)
diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py
index 25f92108..b2f7288f 100644
--- a/openstackclient/compute/v2/console.py
+++ b/openstackclient/compute/v2/console.py
@@ -15,8 +15,6 @@
"""Compute v2 Console action implementations"""
-import sys
-
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import utils
@@ -60,7 +58,10 @@ class ShowConsoleLog(command.Command):
length += 1
data = server.get_console_output(length=length)
- sys.stdout.write(data)
+
+ if data and data[-1] != '\n':
+ data += '\n'
+ self.app.stdout.write(data)
class ShowConsoleURL(command.ShowOne):
diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index 0f5dd742..2cc5f1e8 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -17,6 +17,7 @@
import logging
+from novaclient import api_versions
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
@@ -134,6 +135,12 @@ class CreateFlavor(command.ShowOne):
help=_("Allow <project> to access private flavor (name or ID) "
"(Must be used with --private option)"),
)
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_("Description for the flavor.(Supported by API versions "
+ "'2.55' - '2.latest'")
+ )
identity_common.add_project_domain_option_to_parser(parser)
return parser
@@ -145,6 +152,11 @@ class CreateFlavor(command.ShowOne):
msg = _("--project is only allowed with --private")
raise exceptions.CommandError(msg)
+ if parsed_args.description:
+ if compute_client.api_version < api_versions.APIVersion("2.55"):
+ msg = _("--os-compute-api-version 2.55 or later is required")
+ raise exceptions.CommandError(msg)
+
args = (
parsed_args.name,
parsed_args.ram,
@@ -154,7 +166,8 @@ class CreateFlavor(command.ShowOne):
parsed_args.ephemeral,
parsed_args.swap,
parsed_args.rxtx_factor,
- parsed_args.public
+ parsed_args.public,
+ parsed_args.description
)
flavor = compute_client.flavors.create(*args)
@@ -332,6 +345,12 @@ class SetFlavor(command.Command):
help=_('Set flavor access to project (name or ID) '
'(admin only)'),
)
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_("Set description for the flavor.(Supported by API "
+ "versions '2.55' - '2.latest'")
+ )
identity_common.add_project_domain_option_to_parser(parser)
return parser
@@ -380,6 +399,13 @@ class SetFlavor(command.Command):
raise exceptions.CommandError(_("Command Failed: One or more of"
" the operations failed"))
+ if parsed_args.description:
+ if compute_client.api_version < api_versions.APIVersion("2.55"):
+ msg = _("--os-compute-api-version 2.55 or later is required")
+ raise exceptions.CommandError(msg)
+ compute_client.flavors.update(flavor=parsed_args.flavor,
+ description=parsed_args.description)
+
class ShowFlavor(command.ShowOne):
_description = _("Display flavor details")
diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py
index 9fdfd927..07c92a8c 100644
--- a/openstackclient/compute/v2/host.py
+++ b/openstackclient/compute/v2/host.py
@@ -97,7 +97,7 @@ class SetHost(command.Command):
compute_client.api.host_set(
parsed_args.host,
- kwargs
+ **kwargs
)
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 3341fbfe..4f672428 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -20,7 +20,6 @@ import getpass
import io
import logging
import os
-import sys
from novaclient import api_versions
from novaclient.v2 import servers
@@ -122,17 +121,21 @@ def _prefix_checked_value(prefix):
return func
-def _prep_server_detail(compute_client, image_client, server):
+def _prep_server_detail(compute_client, image_client, server, refresh=True):
"""Prepare the detailed server dict for printing
:param compute_client: a compute client instance
+ :param image_client: an image client instance
:param server: a Server resource
+ :param refresh: Flag indicating if ``server`` is already the latest version
+ or if it needs to be refreshed, for example when showing
+ the latest details of a server after creating it.
:rtype: a dict of server details
"""
- info = server._info.copy()
-
- server = utils.find_resource(compute_client.servers, info['id'])
- info.update(server._info)
+ info = server.to_dict()
+ if refresh:
+ server = utils.find_resource(compute_client.servers, info['id'])
+ info.update(server.to_dict())
# Convert the image blob to a name
image_info = info.get('image', {})
@@ -146,12 +149,18 @@ def _prep_server_detail(compute_client, image_client, server):
# Convert the flavor blob to a name
flavor_info = info.get('flavor', {})
- flavor_id = flavor_info.get('id', '')
- try:
- flavor = utils.find_resource(compute_client.flavors, flavor_id)
- info['flavor'] = "%s (%s)" % (flavor.name, flavor_id)
- except Exception:
- info['flavor'] = flavor_id
+ # Microversion 2.47 puts the embedded flavor into the server response
+ # body but omits the id, so if not present we just expose the flavor
+ # dict in the server output.
+ if 'id' in flavor_info:
+ flavor_id = flavor_info.get('id', '')
+ try:
+ flavor = utils.find_resource(compute_client.flavors, flavor_id)
+ info['flavor'] = "%s (%s)" % (flavor.name, flavor_id)
+ except Exception:
+ info['flavor'] = flavor_id
+ else:
+ info['flavor'] = utils.format_dict(flavor_info)
if 'os-extended-volumes:volumes_attached' in info:
info.update(
@@ -180,7 +189,7 @@ def _prep_server_detail(compute_client, image_client, server):
if 'tenant_id' in info:
info['project_id'] = info.pop('tenant_id')
- # Map power state num to meanful string
+ # Map power state num to meaningful string
if 'OS-EXT-STS:power_state' in info:
info['OS-EXT-STS:power_state'] = _format_servers_list_power_state(
info['OS-EXT-STS:power_state'])
@@ -191,12 +200,6 @@ def _prep_server_detail(compute_client, image_client, server):
return info
-def _show_progress(progress):
- if progress:
- sys.stdout.write('\rProgress: %s' % progress)
- sys.stdout.flush()
-
-
class AddFixedIP(command.Command):
_description = _("Add fixed IP address to server")
@@ -444,6 +447,12 @@ class CreateServer(command.ShowOne):
help=_('Create server boot disk from this image (name or ID)'),
)
disk_group.add_argument(
+ '--image-property',
+ metavar='<key=value>',
+ action=parseractions.KeyValueAction,
+ help=_("Image property to be matched"),
+ )
+ disk_group.add_argument(
'--volume',
metavar='<volume>',
help=_('Create server using this volume as the boot disk (name '
@@ -599,6 +608,12 @@ class CreateServer(command.ShowOne):
return parser
def take_action(self, parsed_args):
+
+ def _show_progress(progress):
+ if progress:
+ self.app.stdout.write('\rProgress: %s' % progress)
+ self.app.stdout.flush()
+
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
image_client = self.app.client_manager.image
@@ -611,6 +626,45 @@ class CreateServer(command.ShowOne):
parsed_args.image,
)
+ if not image and parsed_args.image_property:
+ def emit_duplicated_warning(img, image_property):
+ img_uuid_list = [str(image.id) for image in img]
+ LOG.warning(_('Multiple matching images: %(img_uuid_list)s\n'
+ 'Using image: %(chosen_one)s') %
+ {'img_uuid_list': img_uuid_list,
+ 'chosen_one': img_uuid_list[0]})
+
+ def _match_image(image_api, wanted_properties):
+ image_list = image_api.image_list()
+ images_matched = []
+ for img in image_list:
+ img_dict = {}
+ # exclude any unhashable entries
+ for key, value in img.items():
+ try:
+ set([key, value])
+ except TypeError:
+ pass
+ else:
+ img_dict[key] = value
+ if all(k in img_dict and img_dict[k] == v
+ for k, v in wanted_properties.items()):
+ images_matched.append(img)
+ else:
+ return []
+ return images_matched
+
+ images = _match_image(image_client.api, parsed_args.image_property)
+ if len(images) > 1:
+ emit_duplicated_warning(images,
+ parsed_args.image_property)
+ if images:
+ image = images[0]
+ else:
+ raise exceptions.CommandError(_("No images match the "
+ "property expected by "
+ "--image-property"))
+
# Lookup parsed_args.volume
volume = None
if parsed_args.volume:
@@ -838,11 +892,11 @@ class CreateServer(command.ShowOne):
server.id,
callback=_show_progress,
):
- sys.stdout.write('\n')
+ self.app.stdout.write('\n')
else:
LOG.error(_('Error creating server: %s'),
parsed_args.server_name)
- sys.stdout.write(_('Error creating server\n'))
+ self.app.stdout.write(_('Error creating server\n'))
raise SystemExit
details = _prep_server_detail(compute_client, image_client, server)
@@ -896,6 +950,12 @@ class DeleteServer(command.Command):
return parser
def take_action(self, parsed_args):
+
+ def _show_progress(progress):
+ if progress:
+ self.app.stdout.write('\rProgress: %s' % progress)
+ self.app.stdout.flush()
+
compute_client = self.app.client_manager.compute
for server in parsed_args.server:
server_obj = utils.find_resource(
@@ -907,11 +967,11 @@ class DeleteServer(command.Command):
server_obj.id,
callback=_show_progress,
):
- sys.stdout.write('\n')
+ self.app.stdout.write('\n')
else:
LOG.error(_('Error deleting server: %s'),
server_obj.id)
- sys.stdout.write(_('Error deleting server\n'))
+ self.app.stdout.write(_('Error deleting server\n'))
raise SystemExit
@@ -1172,7 +1232,7 @@ class ListServer(command.Lister):
# Create a dict that maps image_id to image object.
# Needed so that we can display the "Image Name" column.
# "Image Name" is not crucial, so we swallow any exceptions.
- if not parsed_args.no_name_lookup:
+ if data and not parsed_args.no_name_lookup:
try:
images_list = self.app.client_manager.image.images.list()
for i in images_list:
@@ -1184,9 +1244,9 @@ class ListServer(command.Lister):
# Create a dict that maps flavor_id to flavor object.
# Needed so that we can display the "Flavor Name" column.
# "Flavor Name" is not crucial, so we swallow any exceptions.
- if not parsed_args.no_name_lookup:
+ if data and not parsed_args.no_name_lookup:
try:
- flavors_list = compute_client.flavors.list()
+ flavors_list = compute_client.flavors.list(is_public=None)
for i in flavors_list:
flavors[i.id] = i
except Exception:
@@ -1209,6 +1269,10 @@ class ListServer(command.Lister):
s.flavor_name = flavor.name
s.flavor_id = s.flavor['id']
else:
+ # TODO(mriedem): Fix this for microversion >= 2.47 where the
+ # flavor is embedded in the server response without the id.
+ # We likely need to drop the Flavor ID column in that case if
+ # --long is specified.
s.flavor_name = ''
s.flavor_id = ''
@@ -1314,6 +1378,11 @@ class MigrateServer(command.Command):
def take_action(self, parsed_args):
+ def _show_progress(progress):
+ if progress:
+ self.app.stdout.write('\rProgress: %s' % progress)
+ self.app.stdout.flush()
+
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -1321,11 +1390,13 @@ class MigrateServer(command.Command):
parsed_args.server,
)
if parsed_args.live:
- server.live_migrate(
- host=parsed_args.live,
- block_migration=parsed_args.block_migration,
- disk_over_commit=parsed_args.disk_overcommit,
- )
+ kwargs = {
+ 'host': parsed_args.live,
+ 'block_migration': parsed_args.block_migration
+ }
+ if compute_client.api_version < api_versions.APIVersion('2.25'):
+ kwargs['disk_over_commit'] = parsed_args.disk_overcommit
+ server.live_migrate(**kwargs)
else:
if parsed_args.block_migration or parsed_args.disk_overcommit:
raise exceptions.CommandError("--live must be specified if "
@@ -1337,13 +1408,14 @@ class MigrateServer(command.Command):
if utils.wait_for_status(
compute_client.servers.get,
server.id,
+ success_status=['active', 'verify_resize'],
callback=_show_progress,
):
- sys.stdout.write(_('Complete\n'))
+ self.app.stdout.write(_('Complete\n'))
else:
LOG.error(_('Error migrating server: %s'),
server.id)
- sys.stdout.write(_('Error migrating server\n'))
+ self.app.stdout.write(_('Error migrating server\n'))
raise SystemExit
@@ -1404,6 +1476,12 @@ class RebootServer(command.Command):
return parser
def take_action(self, parsed_args):
+
+ def _show_progress(progress):
+ if progress:
+ self.app.stdout.write('\rProgress: %s' % progress)
+ self.app.stdout.flush()
+
compute_client = self.app.client_manager.compute
server = utils.find_resource(
compute_client.servers, parsed_args.server)
@@ -1415,11 +1493,11 @@ class RebootServer(command.Command):
server.id,
callback=_show_progress,
):
- sys.stdout.write(_('Complete\n'))
+ self.app.stdout.write(_('Complete\n'))
else:
LOG.error(_('Error rebooting server: %s'),
server.id)
- sys.stdout.write(_('Error rebooting server\n'))
+ self.app.stdout.write(_('Error rebooting server\n'))
raise SystemExit
@@ -1445,6 +1523,13 @@ class RebuildServer(command.ShowOne):
help=_("Set the password on the rebuilt instance"),
)
parser.add_argument(
+ '--property',
+ metavar='<key=value>',
+ action=parseractions.KeyValueAction,
+ help=_('Set a property on the rebuilt instance '
+ '(repeat option to set multiple values)'),
+ )
+ parser.add_argument(
'--wait',
action='store_true',
help=_('Wait for rebuild to complete'),
@@ -1452,6 +1537,12 @@ class RebuildServer(command.ShowOne):
return parser
def take_action(self, parsed_args):
+
+ def _show_progress(progress):
+ if progress:
+ self.app.stdout.write('\rProgress: %s' % progress)
+ self.app.stdout.flush()
+
compute_client = self.app.client_manager.compute
image_client = self.app.client_manager.image
@@ -1459,24 +1550,30 @@ class RebuildServer(command.ShowOne):
compute_client.servers, parsed_args.server)
# If parsed_args.image is not set, default to the currently used one.
- image_id = parsed_args.image or server._info.get('image', {}).get('id')
+ image_id = parsed_args.image or server.to_dict().get(
+ 'image', {}).get('id')
image = utils.find_resource(image_client.images, image_id)
- server = server.rebuild(image, parsed_args.password)
+ kwargs = {}
+ if parsed_args.property:
+ kwargs['meta'] = parsed_args.property
+
+ server = server.rebuild(image, parsed_args.password, **kwargs)
if parsed_args.wait:
if utils.wait_for_status(
compute_client.servers.get,
server.id,
callback=_show_progress,
):
- sys.stdout.write(_('Complete\n'))
+ self.app.stdout.write(_('Complete\n'))
else:
LOG.error(_('Error rebuilding server: %s'),
server.id)
- sys.stdout.write(_('Error rebuilding server\n'))
+ self.app.stdout.write(_('Error rebuilding server\n'))
raise SystemExit
- details = _prep_server_detail(compute_client, image_client, server)
+ details = _prep_server_detail(compute_client, image_client, server,
+ refresh=False)
return zip(*sorted(six.iteritems(details)))
@@ -1758,6 +1855,11 @@ the new server and restart the old one.""")
def take_action(self, parsed_args):
+ def _show_progress(progress):
+ if progress:
+ self.app.stdout.write('\rProgress: %s' % progress)
+ self.app.stdout.flush()
+
compute_client = self.app.client_manager.compute
server = utils.find_resource(
compute_client.servers,
@@ -1776,11 +1878,11 @@ the new server and restart the old one.""")
success_status=['active', 'verify_resize'],
callback=_show_progress,
):
- sys.stdout.write(_('Complete\n'))
+ self.app.stdout.write(_('Complete\n'))
else:
LOG.error(_('Error resizing server: %s'),
server.id)
- sys.stdout.write(_('Error resizing server\n'))
+ self.app.stdout.write(_('Error resizing server\n'))
raise SystemExit
elif parsed_args.confirm:
compute_client.servers.confirm_resize(server)
@@ -1921,7 +2023,9 @@ class ShelveServer(command.Command):
class ShowServer(command.ShowOne):
- _description = _("Show server details")
+ _description = _(
+ "Show server details. Specify ``--os-compute-api-version 2.47`` "
+ "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)
@@ -1946,11 +2050,14 @@ class ShowServer(command.ShowOne):
if parsed_args.diagnostics:
(resp, data) = server.diagnostics()
if not resp.status_code == 200:
- sys.stderr.write(_("Error retrieving diagnostics data\n"))
+ self.app.stderr.write(_(
+ "Error retrieving diagnostics data\n"
+ ))
return ({}, {})
else:
data = _prep_server_detail(compute_client,
- self.app.client_manager.image, server)
+ self.app.client_manager.image, server,
+ refresh=False)
return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py
index ddcf9101..a79f5f70 100644
--- a/openstackclient/compute/v2/server_backup.py
+++ b/openstackclient/compute/v2/server_backup.py
@@ -15,8 +15,6 @@
"""Compute v2 Server action implementations"""
-import sys
-
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -26,12 +24,6 @@ import six
from openstackclient.i18n import _
-def _show_progress(progress):
- if progress:
- sys.stderr.write('\rProgress: %s' % progress)
- sys.stderr.flush()
-
-
class CreateServerBackup(command.ShowOne):
_description = _("Create a server backup image")
@@ -74,6 +66,12 @@ class CreateServerBackup(command.ShowOne):
return parser
def take_action(self, parsed_args):
+
+ def _show_progress(progress):
+ if progress:
+ self.app.stderr.write('\rProgress: %s' % progress)
+ self.app.stderr.flush()
+
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -114,7 +112,7 @@ class CreateServerBackup(command.ShowOne):
image.id,
callback=_show_progress,
):
- sys.stdout.write('\n')
+ self.app.stdout.write('\n')
else:
msg = _('Error creating server backup: %s') % parsed_args.name
raise exceptions.CommandError(msg)
diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py
index c66e0674..3bc5d94a 100644
--- a/openstackclient/compute/v2/server_image.py
+++ b/openstackclient/compute/v2/server_image.py
@@ -16,7 +16,6 @@
"""Compute v2 Server action implementations"""
import logging
-import sys
from osc_lib.command import command
from osc_lib import exceptions
@@ -30,12 +29,6 @@ from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
-def _show_progress(progress):
- if progress:
- sys.stdout.write('\rProgress: %s' % progress)
- sys.stdout.flush()
-
-
class CreateServerImage(command.ShowOne):
_description = _("Create a new server disk image from an existing server")
@@ -64,6 +57,12 @@ class CreateServerImage(command.ShowOne):
return parser
def take_action(self, parsed_args):
+
+ def _show_progress(progress):
+ if progress:
+ self.app.stdout.write('\rProgress: %s' % progress)
+ self.app.stdout.flush()
+
compute_client = self.app.client_manager.compute
server = utils.find_resource(
@@ -92,7 +91,7 @@ class CreateServerImage(command.ShowOne):
image_id,
callback=_show_progress,
):
- sys.stdout.write('\n')
+ self.app.stdout.write('\n')
else:
LOG.error(_('Error creating server image: %s'),
parsed_args.server)
diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py
index 7331d29d..18e6d9d9 100644
--- a/openstackclient/compute/v2/service.py
+++ b/openstackclient/compute/v2/service.py
@@ -17,6 +17,7 @@
import logging
+from novaclient import api_versions
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -192,18 +193,23 @@ class SetService(command.Command):
result += 1
force_down = None
- try:
- if parsed_args.down:
- force_down = True
- if parsed_args.up:
- force_down = False
- if force_down is not None:
+ if parsed_args.down:
+ force_down = True
+ if parsed_args.up:
+ force_down = False
+ if force_down is not None:
+ if compute_client.api_version < api_versions.APIVersion(
+ '2.11'):
+ msg = _('--os-compute-api-version 2.11 or later is '
+ 'required')
+ raise exceptions.CommandError(msg)
+ try:
cs.force_down(parsed_args.host, parsed_args.service,
force_down=force_down)
- except Exception:
- state = "down" if force_down else "up"
- LOG.error("Failed to set service state to %s", state)
- result += 1
+ except Exception:
+ state = "down" if force_down else "up"
+ LOG.error("Failed to set service state to %s", state)
+ result += 1
if result > 0:
msg = _("Compute service %(service)s of host %(host)s failed to "
diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py
index 3edcffe4..4320bf90 100644
--- a/openstackclient/compute/v2/usage.py
+++ b/openstackclient/compute/v2/usage.py
@@ -16,7 +16,6 @@
"""Usage action implementations"""
import datetime
-import sys
from osc_lib.command import command
from osc_lib import utils
@@ -96,7 +95,7 @@ class ListUsage(command.Lister):
pass
if parsed_args.formatter == 'table' and len(usage_list) > 0:
- sys.stdout.write(_("Usage from %(start)s to %(end)s: \n") % {
+ self.app.stdout.write(_("Usage from %(start)s to %(end)s: \n") % {
"start": start.strftime(dateformat),
"end": end.strftime(dateformat),
})
@@ -168,8 +167,9 @@ class ShowUsage(command.ShowOne):
usage = compute_client.usage.get(project, start, end)
if parsed_args.formatter == 'table':
- sys.stdout.write(_("Usage from %(start)s to %(end)s on "
- "project %(project)s: \n") % {
+ self.app.stdout.write(_(
+ "Usage from %(start)s to %(end)s on project %(project)s: \n"
+ ) % {
"start": start.strftime(dateformat),
"end": end.strftime(dateformat),
"project": project,
diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py
index 3229240e..858b5036 100644
--- a/openstackclient/identity/v3/endpoint.py
+++ b/openstackclient/identity/v3/endpoint.py
@@ -199,7 +199,7 @@ class ListEndpoint(command.Lister):
metavar='<project>',
help=_('Project to list filters (name or ID)'),
)
- common.add_project_domain_option_to_parser(list_group)
+ common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py
index 39c8547c..02eeadd6 100644
--- a/openstackclient/identity/v3/group.py
+++ b/openstackclient/identity/v3/group.py
@@ -16,7 +16,6 @@
"""Group action implementations"""
import logging
-import sys
from keystoneauth1 import exceptions as ks_exc
from osc_lib.command import command
@@ -122,7 +121,7 @@ class CheckUserInGroup(command.Command):
'user': parsed_args.user,
'group': parsed_args.group,
}
- sys.stderr.write(msg)
+ self.app.stderr.write(msg)
else:
raise e
else:
@@ -130,7 +129,7 @@ class CheckUserInGroup(command.Command):
'user': parsed_args.user,
'group': parsed_args.group,
}
- sys.stdout.write(msg)
+ self.app.stdout.write(msg)
class CreateGroup(command.ShowOne):
diff --git a/openstackclient/identity/v3/implied_role.py b/openstackclient/identity/v3/implied_role.py
index c7623389..4e3df88a 100644
--- a/openstackclient/identity/v3/implied_role.py
+++ b/openstackclient/identity/v3/implied_role.py
@@ -71,7 +71,7 @@ class CreateImpliedRole(command.ShowOne):
identity_client = self.app.client_manager.identity
(prior_role_id, implied_role_id) = _get_role_ids(
identity_client, parsed_args)
- response = identity_client.roles.create_implied(
+ response = identity_client.inference_rules.create(
prior_role_id, implied_role_id)
response._info.pop('links', None)
return zip(*sorted([(k, v['id'])
@@ -101,7 +101,7 @@ class DeleteImpliedRole(command.Command):
identity_client = self.app.client_manager.identity
(prior_role_id, implied_role_id) = _get_role_ids(
identity_client, parsed_args)
- identity_client.roles.delete_implied(
+ identity_client.inference_rules.delete(
prior_role_id, implied_role_id)
@@ -125,5 +125,5 @@ class ListImpliedRole(command.Lister):
implies['name'])
identity_client = self.app.client_manager.identity
- response = identity_client.roles.list_inference_roles()
+ response = identity_client.inference_rules.list_inference_roles()
return (self._COLUMNS, _list_implied(response))
diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py
new file mode 100644
index 00000000..c6f1cb1f
--- /dev/null
+++ b/openstackclient/identity/v3/limit.py
@@ -0,0 +1,238 @@
+# 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.
+#
+
+"""Limits action implementations."""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as common_utils
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateLimit(command.ShowOne):
+ _description = _("Create a limit")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_('Description of the limit'),
+ )
+ parser.add_argument(
+ '--region',
+ metavar='<region>',
+ help=_('Region for the limit to affect.'),
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ required=True,
+ help=_('Project to associate the resource limit to'),
+ )
+ parser.add_argument(
+ '--service',
+ metavar='<service>',
+ required=True,
+ help=_('Service responsible for the resource to limit'),
+ )
+ parser.add_argument(
+ '--resource-limit',
+ metavar='<resource-limit>',
+ required=True,
+ type=int,
+ help=_('The resource limit for the project to assume'),
+ )
+ parser.add_argument(
+ 'resource_name',
+ metavar='<resource-name>',
+ help=_('The name of the resource to limit'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+
+ project = common_utils.find_project(
+ identity_client, parsed_args.project
+ )
+ service = common_utils.find_service(
+ identity_client, parsed_args.service
+ )
+ region = None
+ if parsed_args.region:
+ region = utils.find_resource(
+ identity_client.regions, parsed_args.region
+ )
+
+ limit = identity_client.limits.create(
+ project,
+ service,
+ parsed_args.resource_name,
+ parsed_args.resource_limit,
+ description=parsed_args.description,
+ region=region
+ )
+
+ limit._info.pop('links', None)
+ return zip(*sorted(six.iteritems(limit._info)))
+
+
+class ListLimit(command.Lister):
+ _description = _("List limits")
+
+ def get_parser(self, prog_name):
+ parser = super(ListLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ '--service',
+ metavar='<service>',
+ help=_('Service responsible for the resource to limit'),
+ )
+ parser.add_argument(
+ '--resource-name',
+ metavar='<resource-name>',
+ dest='resource_name',
+ help=_('The name of the resource to limit'),
+ )
+ parser.add_argument(
+ '--region',
+ metavar='<region>',
+ help=_('Region for the registered limit to affect.'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+
+ service = None
+ if parsed_args.service:
+ service = common_utils.find_service(
+ identity_client, parsed_args.service
+ )
+ region = None
+ if parsed_args.region:
+ region = utils.find_resource(
+ identity_client.regions, parsed_args.region
+ )
+
+ limits = identity_client.limits.list(
+ service=service,
+ resource_name=parsed_args.resource_name,
+ region=region
+ )
+
+ columns = (
+ 'ID', 'Project ID', 'Service ID', 'Resource Name',
+ 'Resource Limit', 'Description', 'Region ID'
+ )
+ return (
+ columns,
+ (utils.get_item_properties(s, columns) for s in limits),
+ )
+
+
+class ShowLimit(command.ShowOne):
+ _description = _("Display limit details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ 'limit_id',
+ metavar='<limit-id>',
+ help=_('Limit to display (ID)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+ limit = identity_client.limits.get(parsed_args.limit_id)
+ limit._info.pop('links', None)
+ return zip(*sorted(six.iteritems(limit._info)))
+
+
+class SetLimit(command.ShowOne):
+ _description = _("Update information about a limit")
+
+ def get_parser(self, prog_name):
+ parser = super(SetLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ 'limit_id',
+ metavar='<limit-id>',
+ help=_('Limit to update (ID)'),
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_('Description of the limit'),
+ )
+ parser.add_argument(
+ '--resource-limit',
+ metavar='<resource-limit>',
+ dest='resource_limit',
+ type=int,
+ help=_('The resource limit for the project to assume'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+
+ limit = identity_client.limits.update(
+ parsed_args.limit_id,
+ description=parsed_args.description,
+ resource_limit=parsed_args.resource_limit
+ )
+
+ limit._info.pop('links', None)
+
+ return zip(*sorted(six.iteritems(limit._info)))
+
+
+class DeleteLimit(command.Command):
+ _description = _("Delete a limit")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ 'limit_id',
+ metavar='<limit-id>',
+ nargs="+",
+ help=_('Limit to delete (ID)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+
+ errors = 0
+ for limit_id in parsed_args.limit_id:
+ try:
+ identity_client.limits.delete(limit_id)
+ except Exception as e:
+ errors += 1
+ LOG.error(_("Failed to delete limit with ID "
+ "'%(id)s': %(e)s"),
+ {'id': limit_id, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.limit_id)
+ msg = (_("%(errors)s of %(total)s limits failed to "
+ "delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py
new file mode 100644
index 00000000..72e07297
--- /dev/null
+++ b/openstackclient/identity/v3/registered_limit.py
@@ -0,0 +1,260 @@
+# 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.
+#
+
+"""Registered limits action implementations."""
+
+import logging
+
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+import six
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as common_utils
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateRegisteredLimit(command.ShowOne):
+ _description = _("Create a registered limit")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateRegisteredLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_('Description of the registered limit'),
+ )
+ parser.add_argument(
+ '--region',
+ metavar='<region>',
+ help=_('Region for the registered limit to affect'),
+ )
+ parser.add_argument(
+ '--service',
+ metavar='<service>',
+ required=True,
+ help=_('Service responsible for the resource to limit (required)'),
+ )
+ parser.add_argument(
+ '--default-limit',
+ type=int,
+ metavar='<default-limit>',
+ required=True,
+ help=_('The default limit for the resources to assume (required)'),
+ )
+ parser.add_argument(
+ 'resource_name',
+ metavar='<resource-name>',
+ help=_('The name of the resource to limit'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+
+ service = utils.find_resource(
+ identity_client.services, parsed_args.service
+ )
+ region = None
+ if parsed_args.region:
+ region = utils.find_resource(
+ identity_client.regions, parsed_args.region
+ )
+
+ registered_limit = identity_client.registered_limits.create(
+ service,
+ parsed_args.resource_name,
+ parsed_args.default_limit,
+ description=parsed_args.description,
+ region=region
+ )
+
+ registered_limit._info.pop('links', None)
+ return zip(*sorted(six.iteritems(registered_limit._info)))
+
+
+class DeleteRegisteredLimit(command.Command):
+ _description = _("Delete a registered limit")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteRegisteredLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ 'registered_limit_id',
+ metavar='<registered-limit-id>',
+ nargs="+",
+ help=_('Registered limit to delete (ID)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+
+ errors = 0
+ for registered_limit_id in parsed_args.registered_limit_id:
+ try:
+ identity_client.registered_limits.delete(registered_limit_id)
+ except Exception as e:
+ errors += 1
+ from pprint import pprint
+ pprint(type(e))
+ LOG.error(_("Failed to delete registered limit with ID "
+ "'%(id)s': %(e)s"),
+ {'id': registered_limit_id, 'e': e})
+
+ if errors > 0:
+ total = len(parsed_args.registered_limit_id)
+ msg = (_("%(errors)s of %(total)s registered limits failed to "
+ "delete.") % {'errors': errors, 'total': total})
+ raise exceptions.CommandError(msg)
+
+
+class ListRegisteredLimit(command.Lister):
+ _description = _("List registered limits")
+
+ def get_parser(self, prog_name):
+ parser = super(ListRegisteredLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ '--service',
+ metavar='<service>',
+ help=_('Service responsible for the resource to limit'),
+ )
+ parser.add_argument(
+ '--resource-name',
+ metavar='<resource-name>',
+ dest='resource_name',
+ help=_('The name of the resource to limit'),
+ )
+ parser.add_argument(
+ '--region',
+ metavar='<region>',
+ help=_('Region for the limit to affect.'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+
+ service = None
+ if parsed_args.service:
+ service = common_utils.find_service(
+ identity_client, parsed_args.service
+ )
+ region = None
+ if parsed_args.region:
+ region = utils.find_resource(
+ identity_client.regions, parsed_args.region
+ )
+
+ registered_limits = identity_client.registered_limits.list(
+ service=service,
+ resource_name=parsed_args.resource_name,
+ region=region
+ )
+
+ columns = (
+ 'ID', 'Service ID', 'Resource Name', 'Default Limit',
+ 'Description', 'Region ID'
+ )
+ return (
+ columns,
+ (utils.get_item_properties(s, columns) for s in registered_limits),
+ )
+
+
+class SetRegisteredLimit(command.ShowOne):
+ _description = _("Update information about a registered limit")
+
+ def get_parser(self, prog_name):
+ parser = super(SetRegisteredLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ 'registered_limit_id',
+ metavar='<registered-limit-id>',
+ help=_('Registered limit to update (ID)'),
+ )
+ parser.add_argument(
+ '--service',
+ metavar='<service>',
+ help=_('Service responsible for the resource to limit'),
+ )
+ parser.add_argument(
+ '--resource-name',
+ metavar='<resource-name>',
+ help=_('The name of the resource to limit'),
+ )
+ parser.add_argument(
+ '--default-limit',
+ metavar='<default-limit>',
+ type=int,
+ help=_('The default limit for the resources to assume'),
+ )
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help=_('Description of the registered limit'),
+ )
+ parser.add_argument(
+ '--region',
+ metavar='<region>',
+ help=_('Region for the registered limit to affect.'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+
+ service = None
+ if parsed_args.service:
+ service = common_utils.find_service(
+ identity_client, parsed_args.service
+ )
+
+ region = None
+ if parsed_args.region:
+ region = utils.find_resource(
+ identity_client.regions, parsed_args.region
+ )
+
+ registered_limit = identity_client.registered_limits.update(
+ parsed_args.registered_limit_id,
+ service=service,
+ resource_name=parsed_args.resource_name,
+ default_limit=parsed_args.default_limit,
+ description=parsed_args.description,
+ region=region
+ )
+
+ registered_limit._info.pop('links', None)
+ return zip(*sorted(six.iteritems(registered_limit._info)))
+
+
+class ShowRegisteredLimit(command.ShowOne):
+ _description = _("Display registered limit details")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowRegisteredLimit, self).get_parser(prog_name)
+ parser.add_argument(
+ 'registered_limit_id',
+ metavar='<registered-limit-id>',
+ help=_('Registered limit to display (ID)'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
+ registered_limit = identity_client.registered_limits.get(
+ parsed_args.registered_limit_id
+ )
+ registered_limit._info.pop('links', None)
+ return zip(*sorted(six.iteritems(registered_limit._info)))
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index 1bbf5f07..58a76f8a 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -31,13 +31,18 @@ LOG = logging.getLogger(__name__)
def _add_identity_and_resource_options_to_parser(parser):
- domain_or_project = parser.add_mutually_exclusive_group()
- domain_or_project.add_argument(
+ system_or_domain_or_project = parser.add_mutually_exclusive_group()
+ system_or_domain_or_project.add_argument(
+ '--system',
+ metavar='<system>',
+ help=_('Include <system> (all)'),
+ )
+ system_or_domain_or_project.add_argument(
'--domain',
metavar='<domain>',
help=_('Include <domain> (name or ID)'),
)
- domain_or_project.add_argument(
+ system_or_domain_or_project.add_argument(
'--project',
metavar='<project>',
help=_('Include <project> (name or ID)'),
@@ -62,7 +67,14 @@ def _add_identity_and_resource_options_to_parser(parser):
def _process_identity_and_resource_options(parsed_args,
identity_client_manager):
kwargs = {}
- if parsed_args.user and parsed_args.domain:
+ if parsed_args.user and parsed_args.system:
+ kwargs['user'] = common.find_user(
+ identity_client_manager,
+ parsed_args.user,
+ parsed_args.user_domain,
+ ).id
+ kwargs['system'] = parsed_args.system
+ elif parsed_args.user and parsed_args.domain:
kwargs['user'] = common.find_user(
identity_client_manager,
parsed_args.user,
@@ -83,6 +95,13 @@ def _process_identity_and_resource_options(parsed_args,
parsed_args.project,
parsed_args.project_domain,
).id
+ elif parsed_args.group and parsed_args.system:
+ kwargs['group'] = common.find_group(
+ identity_client_manager,
+ parsed_args.group,
+ parsed_args.group_domain,
+ ).id
+ kwargs['system'] = parsed_args.system
elif parsed_args.group and parsed_args.domain:
kwargs['group'] = common.find_group(
identity_client_manager,
@@ -109,8 +128,8 @@ def _process_identity_and_resource_options(parsed_args,
class AddRole(command.Command):
- _description = _("Adds a role assignment to a user or group on a domain "
- "or project")
+ _description = _("Adds a role assignment to a user or group on the "
+ "system, a domain, or a project")
def get_parser(self, prog_name):
parser = super(AddRole, self).get_parser(prog_name)
@@ -126,8 +145,8 @@ class AddRole(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if (not parsed_args.user and not parsed_args.domain
- and not parsed_args.group and not parsed_args.project):
+ if (not parsed_args.user and not parsed_args.domain and
+ not parsed_args.group and not parsed_args.project):
msg = _("Role not added, incorrect set of arguments "
"provided. See openstack --help for more details")
raise exceptions.CommandError(msg)
@@ -381,7 +400,7 @@ class ListRole(command.Lister):
class RemoveRole(command.Command):
- _description = _("Removes a role assignment from domain/project : "
+ _description = _("Removes a role assignment from system/domain/project : "
"user/group")
def get_parser(self, prog_name):
@@ -399,8 +418,8 @@ class RemoveRole(command.Command):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if (not parsed_args.user and not parsed_args.domain
- and not parsed_args.group and not parsed_args.project):
+ if (not parsed_args.user and not parsed_args.domain and
+ not parsed_args.group and not parsed_args.project):
msg = _("Incorrect set of arguments provided. "
"See openstack --help for more details")
raise exceptions.CommandError(msg)
diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py
index a362adb0..9c2f3d24 100644
--- a/openstackclient/identity/v3/role_assignment.py
+++ b/openstackclient/identity/v3/role_assignment.py
@@ -55,17 +55,22 @@ class ListRoleAssignment(command.Lister):
help=_('Group to filter (name or ID)'),
)
common.add_group_domain_option_to_parser(parser)
- domain_or_project = parser.add_mutually_exclusive_group()
- domain_or_project.add_argument(
+ system_or_domain_or_project = parser.add_mutually_exclusive_group()
+ system_or_domain_or_project.add_argument(
'--domain',
metavar='<domain>',
help=_('Domain to filter (name or ID)'),
)
- domain_or_project.add_argument(
+ system_or_domain_or_project.add_argument(
'--project',
metavar='<project>',
help=_('Project to filter (name or ID)'),
)
+ system_or_domain_or_project.add_argument(
+ '--system',
+ metavar='<system>',
+ help=_('Filter based on system role assignments'),
+ )
common.add_project_domain_option_to_parser(parser)
common.add_inherited_option_to_parser(parser)
parser.add_argument(
@@ -85,7 +90,8 @@ class ListRoleAssignment(command.Lister):
def _as_tuple(self, assignment):
return (assignment.role, assignment.user, assignment.group,
- assignment.project, assignment.domain, assignment.inherited)
+ assignment.project, assignment.domain, assignment.system,
+ assignment.inherited)
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
@@ -117,6 +123,10 @@ class ListRoleAssignment(command.Lister):
auth_ref.user_id
)
+ system = None
+ if parsed_args.system:
+ system = parsed_args.system
+
domain = None
if parsed_args.domain:
domain = common.find_domain(
@@ -149,7 +159,9 @@ class ListRoleAssignment(command.Lister):
include_names = True if parsed_args.names else False
effective = True if parsed_args.effective else False
- columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited')
+ columns = (
+ 'Role', 'User', 'Group', 'Project', 'Domain', 'System', 'Inherited'
+ )
inherited_to = 'projects' if parsed_args.inherited else None
data = identity_client.role_assignments.list(
@@ -157,6 +169,7 @@ class ListRoleAssignment(command.Lister):
user=user,
group=group,
project=project,
+ system=system,
role=role,
effective=effective,
os_inherit_extension_inherited_to=inherited_to,
@@ -174,14 +187,24 @@ class ListRoleAssignment(command.Lister):
else:
setattr(assignment, 'project', scope['project']['id'])
assignment.domain = ''
+ assignment.system = ''
elif 'domain' in scope:
if include_names:
setattr(assignment, 'domain', scope['domain']['name'])
else:
setattr(assignment, 'domain', scope['domain']['id'])
assignment.project = ''
-
+ assignment.system = ''
+ elif 'system' in scope:
+ # NOTE(lbragstad): If, or when, keystone supports role
+ # assignments on subsets of a system, this will have to evolve
+ # to handle that case instead of hardcoding to the entire
+ # system.
+ setattr(assignment, 'system', 'all')
+ assignment.domain = ''
+ assignment.project = ''
else:
+ assignment.system = ''
assignment.domain = ''
assignment.project = ''
diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py
index effb9e35..1933ecad 100644
--- a/openstackclient/identity/v3/token.py
+++ b/openstackclient/identity/v3/token.py
@@ -192,6 +192,12 @@ class IssueToken(command.ShowOne):
data['user_id'] = auth_ref.user_id
if auth_ref.domain_id:
data['domain_id'] = auth_ref.domain_id
+ if auth_ref.system_scoped:
+ # NOTE(lbragstad): This could change in the future when, or if,
+ # keystone supports the ability to scope to a subset of the entire
+ # deployment system. When that happens, this will have to relay
+ # scope information and IDs like we do for projects and domains.
+ data['system'] = 'all'
return zip(*sorted(six.iteritems(data)))
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 7a8e67bf..7ecaa3ef 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -21,11 +21,6 @@ import logging
import os
import sys
-if os.name == "nt":
- import msvcrt
-else:
- msvcrt = None
-
from glanceclient.common import utils as gc_utils
from osc_lib.cli import parseractions
from osc_lib.command import command
@@ -35,6 +30,11 @@ import six
from openstackclient.api import utils as api_utils
from openstackclient.i18n import _
+if os.name == "nt":
+ import msvcrt
+else:
+ msvcrt = None
+
CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"]
DEFAULT_CONTAINER_FORMAT = 'bare'
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index 7e6a7aa1..1e67692a 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -16,10 +16,11 @@
"""Image V2 Action Implementations"""
import argparse
+from base64 import b64encode
import logging
-import sys
from glanceclient.common import utils as gc_utils
+from openstack.image import image_signer
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
@@ -184,6 +185,22 @@ class CreateImage(command.ShowOne):
help=_("Force image creation if volume is in use "
"(only meaningful with --volume)"),
)
+ parser.add_argument(
+ '--sign-key-path',
+ metavar="<sign-key-path>",
+ default=[],
+ help=_("Sign the image using the specified private key. "
+ "Only use in combination with --sign-cert-id")
+ )
+ parser.add_argument(
+ '--sign-cert-id',
+ metavar="<sign-cert-id>",
+ default=[],
+ help=_("The specified certificate UUID is a reference to "
+ "the certificate in the key manager that corresponds "
+ "to the public key and is used for signature validation. "
+ "Only use in combination with --sign-key-path")
+ )
protected_group = parser.add_mutually_exclusive_group()
protected_group.add_argument(
"--protected",
@@ -336,6 +353,46 @@ class CreateImage(command.ShowOne):
parsed_args.project_domain,
).id
+ # sign an image using a given local private key file
+ if parsed_args.sign_key_path or parsed_args.sign_cert_id:
+ if not parsed_args.file:
+ msg = (_("signing an image requires the --file option, "
+ "passing files via stdin when signing is not "
+ "supported."))
+ raise exceptions.CommandError(msg)
+ if (len(parsed_args.sign_key_path) < 1 or
+ len(parsed_args.sign_cert_id) < 1):
+ msg = (_("'sign-key-path' and 'sign-cert-id' must both be "
+ "specified when attempting to sign an image."))
+ raise exceptions.CommandError(msg)
+ else:
+ sign_key_path = parsed_args.sign_key_path
+ sign_cert_id = parsed_args.sign_cert_id
+ signer = image_signer.ImageSigner()
+ try:
+ pw = utils.get_password(
+ self.app.stdin,
+ prompt=("Please enter private key password, leave "
+ "empty if none: "),
+ confirm=False)
+ if not pw or len(pw) < 1:
+ pw = None
+ signer.load_private_key(
+ sign_key_path,
+ password=pw)
+ except Exception:
+ msg = (_("Error during sign operation: private key could "
+ "not be loaded."))
+ raise exceptions.CommandError(msg)
+
+ signature = signer.generate_signature(fp)
+ signature_b64 = b64encode(signature)
+ kwargs['img_signature'] = signature_b64
+ kwargs['img_signature_certificate_uuid'] = sign_cert_id
+ kwargs['img_signature_hash_method'] = signer.hash_method
+ if signer.padding_method:
+ kwargs['img_signature_key_type'] = signer.padding_method
+
# If a volume is specified.
if parsed_args.volume:
volume_client = self.app.client_manager.volume
@@ -441,6 +498,13 @@ class ListImage(command.Lister):
help=_("List only private images"),
)
public_group.add_argument(
+ "--community",
+ dest="community",
+ action="store_true",
+ default=False,
+ help=_("List only community images"),
+ )
+ public_group.add_argument(
"--shared",
dest="shared",
action="store_true",
@@ -466,6 +530,12 @@ class ListImage(command.Lister):
help=_("Filter images based on status.")
)
parser.add_argument(
+ '--tag',
+ metavar='<tag>',
+ default=None,
+ help=_('Filter images based on tag.'),
+ )
+ parser.add_argument(
'--long',
action='store_true',
default=False,
@@ -511,6 +581,8 @@ class ListImage(command.Lister):
kwargs['public'] = True
if parsed_args.private:
kwargs['private'] = True
+ if parsed_args.community:
+ kwargs['community'] = True
if parsed_args.shared:
kwargs['shared'] = True
if parsed_args.limit:
@@ -522,6 +594,8 @@ class ListImage(command.Lister):
kwargs['name'] = parsed_args.name
if parsed_args.status:
kwargs['status'] = parsed_args.status
+ if parsed_args.tag:
+ kwargs['tag'] = parsed_args.tag
if parsed_args.long:
columns = (
'ID',
@@ -578,7 +652,7 @@ class ListImage(command.Lister):
property_field='properties',
)
- data = utils.sort_items(data, parsed_args.sort)
+ data = utils.sort_items(data, parsed_args.sort, str)
return (
column_headers,
@@ -592,6 +666,39 @@ class ListImage(command.Lister):
)
+class ListImageProjects(command.Lister):
+ _description = _("List projects associated with image")
+
+ def get_parser(self, prog_name):
+ parser = super(ListImageProjects, self).get_parser(prog_name)
+ parser.add_argument(
+ "image",
+ metavar="<image>",
+ help=_("Image (name or ID)"),
+ )
+ common.add_project_domain_option_to_parser(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ image_client = self.app.client_manager.image
+ columns = (
+ "Image ID",
+ "Member ID",
+ "Status"
+ )
+
+ image_id = utils.find_resource(
+ image_client.images,
+ parsed_args.image).id
+
+ data = image_client.image_members.list(image_id)
+
+ return (columns,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
class RemoveProjectImage(command.Command):
_description = _("Disassociate project with image")
@@ -653,7 +760,7 @@ class SaveImage(command.Command):
if data.wrapped is None:
msg = _('Image %s has no data.') % image.id
LOG.error(msg)
- sys.stdout.write(msg + '\n')
+ self.app.stdout.write(msg + '\n')
raise SystemExit
gc_utils.save_image(data, parsed_args.file)
diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py
index 5183cbda..39936fde 100644
--- a/openstackclient/network/client.py
+++ b/openstackclient/network/client.py
@@ -13,16 +13,6 @@
import logging
-from openstack import connection
-
-
-# NOTE(dtroyer): Attempt an import to detect if the SDK installed is new
-# enough to not use Profile. If so, use that.
-try:
- from openstack.config import loader as config # noqa
- profile = None
-except ImportError:
- from openstack import profile
from osc_lib import utils
from openstackclient.i18n import _
@@ -41,37 +31,17 @@ API_VERSIONS = {
def make_client(instance):
"""Returns a network proxy"""
- if getattr(instance, "sdk_connection", None) is None:
- if profile is None:
- # If the installed OpenStackSDK is new enough to not require a
- # Profile obejct and osc-lib is not new enough to have created
- # it for us, make an SDK Connection.
- # NOTE(dtroyer): This can be removed when this bit is in the
- # released osc-lib in requirements.txt.
- conn = connection.Connection(
- config=instance._cli_options,
- session=instance.session,
- )
- else:
- # Fall back to the original Connection creation
- prof = profile.Profile()
- prof.set_region(API_NAME, instance.region_name)
- prof.set_version(API_NAME, instance._api_version[API_NAME])
- prof.set_interface(API_NAME, instance.interface)
- conn = connection.Connection(
- authenticator=instance.session.auth,
- verify=instance.session.verify,
- cert=instance.session.cert,
- profile=prof,
- )
-
- instance.sdk_connection = conn
-
- conn = instance.sdk_connection
- LOG.debug('Connection: %s', conn)
- LOG.debug('Network client initialized using OpenStack SDK: %s',
- conn.network)
- return conn.network
+ # NOTE(dtroyer): As of osc-lib 1.8.0 and OpenStackSDK 0.10.0 the
+ # old Profile interface and separate client creation
+ # for each API that uses the SDK is unnecessary. This
+ # callback remains as a remnant of the original plugin
+ # interface and to avoid the code churn of changing all
+ # of the existing references.
+ LOG.debug(
+ 'Network client initialized using OpenStack SDK: %s',
+ instance.sdk_connection.network,
+ )
+ return instance.sdk_connection.network
def build_option_parser(parser):
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py
index 37bf1406..d22b2caa 100644
--- a/openstackclient/network/common.py
+++ b/openstackclient/network/common.py
@@ -12,6 +12,7 @@
#
import abc
+import contextlib
import logging
import openstack.exceptions
@@ -24,6 +25,30 @@ from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
+_required_opt_extensions_map = {
+ 'allowed_address_pairs': 'allowed-address-pairs',
+ 'dns_domain': 'dns-integration',
+ 'dns_name': 'dns-integration',
+ 'extra_dhcp_opts': 'extra_dhcp_opt',
+ 'qos_policy_id': 'qos',
+ 'security_groups': 'security-groups',
+}
+
+
+@contextlib.contextmanager
+def check_missing_extension_if_error(client_manager, attrs):
+ # If specified option requires extension, then try to
+ # find out if it exists. If it does not exist,
+ # then an exception with the appropriate message
+ # will be thrown from within client.find_extension
+ try:
+ yield
+ except openstack.exceptions.HttpException:
+ for opt, ext in _required_opt_extensions_map.items():
+ if opt in attrs:
+ client_manager.find_extension(ext, ignore_missing=False)
+ raise
+
@six.add_metaclass(abc.ABCMeta)
class NetworkAndComputeCommand(command.Command):
diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py
index 4d07d9da..e1ec8274 100644
--- a/openstackclient/network/v2/floating_ip.py
+++ b/openstackclient/network/v2/floating_ip.py
@@ -13,8 +13,6 @@
"""IP Floating action implementations"""
-import logging
-
from osc_lib.command import command
from osc_lib import utils
@@ -22,6 +20,12 @@ from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
from openstackclient.network import common
from openstackclient.network import sdk_utils
+from openstackclient.network.v2 import _tag
+
+
+_formatters = {
+ 'port_details': utils.format_dict,
+}
def _get_network_columns(item):
@@ -81,6 +85,12 @@ def _get_attrs(client_manager, parsed_args):
).id
attrs['tenant_id'] = project_id
+ if parsed_args.dns_domain:
+ attrs['dns_domain'] = parsed_args.dns_domain
+
+ if parsed_args.dns_name:
+ attrs['dns_name'] = parsed_args.dns_name
+
return attrs
@@ -138,12 +148,32 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne):
metavar='<project>',
help=_("Owner's project (name or ID)")
)
+ parser.add_argument(
+ '--dns-domain',
+ metavar='<dns-domain>',
+ dest='dns_domain',
+ help=_("Set DNS domain for this floating IP")
+ )
+ parser.add_argument(
+ '--dns-name',
+ metavar='<dns-name>',
+ dest='dns_name',
+ help=_("Set DNS name for this floating IP")
+ )
+
identity_common.add_project_domain_option_to_parser(parser)
+ _tag.add_tag_option_to_parser_for_create(parser, _('floating IP'))
return parser
def take_action_network(self, client, parsed_args):
attrs = _get_attrs(self.app.client_manager, parsed_args)
- obj = client.create_ip(**attrs)
+ with common.check_missing_extension_if_error(
+ self.app.client_manager.network, attrs):
+ obj = client.create_ip(**attrs)
+
+ # tags cannot be set when created, so tags need to be set later.
+ _tag.update_tags_for_set(client, obj, parsed_args)
+
display_columns, columns = _get_network_columns(obj)
data = utils.get_item_properties(obj, columns)
return (display_columns, data)
@@ -155,30 +185,6 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne):
return (columns, data)
-class CreateIPFloating(CreateFloatingIP):
- _description = _("Create floating IP")
-
- # TODO(tangchen): Remove this class and ``ip floating create`` command
- # two cycles after Mitaka.
-
- # This notifies cliff to not display the help for this command
- deprecated = True
-
- log = logging.getLogger('deprecated')
-
- def take_action_network(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip create" instead.'))
- return super(CreateIPFloating, self).take_action_network(
- client, parsed_args)
-
- def take_action_compute(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip create" instead.'))
- return super(CreateIPFloating, self).take_action_compute(
- client, parsed_args)
-
-
class DeleteFloatingIP(common.NetworkAndComputeDelete):
_description = _("Delete floating IP(s)")
@@ -206,30 +212,6 @@ class DeleteFloatingIP(common.NetworkAndComputeDelete):
client.api.floating_ip_delete(self.r)
-class DeleteIPFloating(DeleteFloatingIP):
- _description = _("Delete floating IP(s)")
-
- # TODO(tangchen): Remove this class and ``ip floating delete`` command
- # two cycles after Mitaka.
-
- # This notifies cliff to not display the help for this command
- deprecated = True
-
- log = logging.getLogger('deprecated')
-
- def take_action_network(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip delete" instead.'))
- return super(DeleteIPFloating, self).take_action_network(
- client, parsed_args)
-
- def take_action_compute(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip delete" instead.'))
- return super(DeleteIPFloating, self).take_action_compute(
- client, parsed_args)
-
-
class ListFloatingIP(common.NetworkAndComputeLister):
# TODO(songminglong): Use SDK resource mapped attribute names once
# the OSC minimum requirements include SDK 1.0
@@ -280,6 +262,7 @@ class ListFloatingIP(common.NetworkAndComputeLister):
help=_("List floating IP(s) according to "
"given router (name or ID)")
)
+ _tag.add_tag_filtering_option_to_parser(parser, _('floating IP'))
return parser
@@ -308,11 +291,17 @@ class ListFloatingIP(common.NetworkAndComputeLister):
'router_id',
'status',
'description',
+ 'tags',
+ 'dns_name',
+ 'dns_domain',
)
headers = headers + (
'Router',
'Status',
'Description',
+ 'Tags',
+ 'DNS Name',
+ 'DNS Domain',
)
query = {}
@@ -342,6 +331,8 @@ class ListFloatingIP(common.NetworkAndComputeLister):
ignore_missing=False)
query['router_id'] = router.id
+ _tag.get_tag_filtering_args(parsed_args, query)
+
data = client.ips(**query)
return (headers,
@@ -375,30 +366,6 @@ class ListFloatingIP(common.NetworkAndComputeLister):
) for s in data))
-class ListIPFloating(ListFloatingIP):
- _description = _("List floating IP(s)")
-
- # TODO(tangchen): Remove this class and ``ip floating list`` command
- # two cycles after Mitaka.
-
- # This notifies cliff to not display the help for this command
- deprecated = True
-
- log = logging.getLogger('deprecated')
-
- def take_action_network(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip list" instead.'))
- return super(ListIPFloating, self).take_action_network(
- client, parsed_args)
-
- def take_action_compute(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip list" instead.'))
- return super(ListIPFloating, self).take_action_compute(
- client, parsed_args)
-
-
class SetFloatingIP(command.Command):
_description = _("Set floating IP Properties")
@@ -407,11 +374,10 @@ class SetFloatingIP(command.Command):
parser.add_argument(
'floating_ip',
metavar='<floating-ip>',
- help=_("Floating IP to associate (IP address or ID)"))
+ help=_("Floating IP to modify (IP address or ID)"))
parser.add_argument(
'--port',
metavar='<port>',
- required=True,
help=_("Associate the floating IP with port (name or ID)")),
parser.add_argument(
'--fixed-ip-address',
@@ -431,6 +397,9 @@ class SetFloatingIP(command.Command):
action='store_true',
help=_("Remove the QoS policy attached to the floating IP")
)
+
+ _tag.add_tag_option_to_parser_for_set(parser, _('floating IP'))
+
return parser
def take_action(self, parsed_args):
@@ -440,9 +409,11 @@ class SetFloatingIP(command.Command):
parsed_args.floating_ip,
ignore_missing=False,
)
- port = client.find_port(parsed_args.port,
- ignore_missing=False)
- attrs['port_id'] = port.id
+ if parsed_args.port:
+ port = client.find_port(parsed_args.port,
+ ignore_missing=False)
+ attrs['port_id'] = port.id
+
if parsed_args.fixed_ip_address:
attrs['fixed_ip_address'] = parsed_args.fixed_ip_address
@@ -453,7 +424,11 @@ class SetFloatingIP(command.Command):
if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy:
attrs['qos_policy_id'] = None
- client.update_ip(obj, **attrs)
+ if attrs:
+ client.update_ip(obj, **attrs)
+
+ # tags is a subresource and it needs to be updated separately.
+ _tag.update_tags_for_set(client, obj, parsed_args)
class ShowFloatingIP(common.NetworkAndComputeShowOne):
@@ -473,7 +448,7 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne):
ignore_missing=False,
)
display_columns, columns = _get_network_columns(obj)
- data = utils.get_item_properties(obj, columns)
+ data = utils.get_item_properties(obj, columns, formatters=_formatters)
return (display_columns, data)
def take_action_compute(self, client, parsed_args):
@@ -483,30 +458,6 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne):
return (columns, data)
-class ShowIPFloating(ShowFloatingIP):
- _description = _("Display floating IP details")
-
- # TODO(tangchen): Remove this class and ``ip floating show`` command
- # two cycles after Mitaka.
-
- # This notifies cliff to not display the help for this command
- deprecated = True
-
- log = logging.getLogger('deprecated')
-
- def take_action_network(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip show" instead.'))
- return super(ShowIPFloating, self).take_action_network(
- client, parsed_args)
-
- def take_action_compute(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip show" instead.'))
- return super(ShowIPFloating, self).take_action_compute(
- client, parsed_args)
-
-
class UnsetFloatingIP(command.Command):
_description = _("Unset floating IP Properties")
@@ -528,6 +479,8 @@ class UnsetFloatingIP(command.Command):
default=False,
help=_("Remove the QoS policy attached to the floating IP")
)
+ _tag.add_tag_option_to_parser_for_unset(parser, _('floating IP'))
+
return parser
def take_action(self, parsed_args):
@@ -544,3 +497,6 @@ class UnsetFloatingIP(command.Command):
if attrs:
client.update_ip(obj, **attrs)
+
+ # tags is a subresource and it needs to be updated separately.
+ _tag.update_tags_for_unset(client, obj, parsed_args)
diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py
index ebb15da8..32852004 100644
--- a/openstackclient/network/v2/floating_ip_pool.py
+++ b/openstackclient/network/v2/floating_ip_pool.py
@@ -13,7 +13,6 @@
"""Floating IP Pool action implementations"""
-import logging
from osc_lib import exceptions
from osc_lib import utils
@@ -40,27 +39,3 @@ class ListFloatingIPPool(common.NetworkAndComputeLister):
(utils.get_dict_properties(
s, columns,
) for s in data))
-
-
-class ListIPFloatingPool(ListFloatingIPPool):
- _description = _("List pools of floating IP addresses")
-
- # TODO(tangchen): Remove this class and ``ip floating pool list`` command
- # two cycles after Mitaka.
-
- # This notifies cliff to not display the help for this command
- deprecated = True
-
- log = logging.getLogger('deprecated')
-
- def take_action_network(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip pool list" instead.'))
- return super(ListIPFloatingPool, self).take_action_network(
- client, parsed_args)
-
- def take_action_compute(self, client, parsed_args):
- self.log.warning(_('This command has been deprecated. '
- 'Please use "floating ip pool list" instead.'))
- return super(ListIPFloatingPool, self).take_action_compute(
- client, parsed_args)
diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py
index 4c1725c5..0fdf62c9 100644
--- a/openstackclient/network/v2/network.py
+++ b/openstackclient/network/v2/network.py
@@ -107,6 +107,10 @@ def _get_attrs_network(client_manager, parsed_args):
if parsed_args.description:
attrs['description'] = parsed_args.description
+ # set mtu
+ if parsed_args.mtu:
+ attrs['mtu'] = parsed_args.mtu
+
# update_external_network_options
if parsed_args.internal:
attrs['router:external'] = False
@@ -130,6 +134,9 @@ def _get_attrs_network(client_manager, parsed_args):
attrs['qos_policy_id'] = _qos_policy.id
if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy:
attrs['qos_policy_id'] = None
+ # Update DNS network options
+ if parsed_args.dns_domain:
+ attrs['dns_domain'] = parsed_args.dns_domain
return attrs
@@ -167,6 +174,13 @@ def _add_additional_network_options(parser):
dest='segmentation_id',
help=_("VLAN ID for VLAN networks or Tunnel ID for "
"GENEVE/GRE/VXLAN networks"))
+ parser.add_argument(
+ '--dns-domain',
+ metavar='<dns-domain>',
+ dest='dns_domain',
+ help=_("Set DNS domain for this network "
+ "(requires DNS integration extension)")
+ )
# TODO(sindhu): Use the SDK resource mapped attribute names once the
@@ -217,6 +231,11 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
metavar='<description>',
help=_("Set network description")
)
+ parser.add_argument(
+ '--mtu',
+ metavar='<mtu>',
+ help=_("Set network mtu")
+ )
identity_common.add_project_domain_option_to_parser(parser)
parser.add_argument(
'--availability-zone-hint',
@@ -299,8 +318,10 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
attrs['vlan_transparent'] = True
if parsed_args.no_transparent_vlan:
attrs['vlan_transparent'] = False
+ with common.check_missing_extension_if_error(
+ self.app.client_manager.network, attrs):
+ obj = client.create_network(**attrs)
- obj = client.create_network(**attrs)
# tags cannot be set when created, so tags need to be set later.
_tag.update_tags_for_set(client, obj, parsed_args)
display_columns, columns = _get_columns_network(obj)
@@ -619,6 +640,11 @@ class SetNetwork(command.Command):
metavar="<description",
help=_("Set network description")
)
+ parser.add_argument(
+ '--mtu',
+ metavar="<mtu",
+ help=_("Set network mtu")
+ )
port_security_group = parser.add_mutually_exclusive_group()
port_security_group.add_argument(
'--enable-port-security',
@@ -676,7 +702,9 @@ class SetNetwork(command.Command):
attrs = _get_attrs_network(self.app.client_manager, parsed_args)
if attrs:
- client.update_network(obj, **attrs)
+ with common.check_missing_extension_if_error(
+ self.app.client_manager.network, attrs):
+ client.update_network(obj, **attrs)
# tags is a subresource and it needs to be updated separately.
_tag.update_tags_for_set(client, obj, parsed_args)
diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py
index f50e58b3..28c5600a 100644
--- a/openstackclient/network/v2/network_qos_rule.py
+++ b/openstackclient/network/v2/network_qos_rule.py
@@ -29,11 +29,11 @@ RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth'
MANDATORY_PARAMETERS = {
RULE_TYPE_MINIMUM_BANDWIDTH: {'min_kbps', 'direction'},
RULE_TYPE_DSCP_MARKING: {'dscp_mark'},
- RULE_TYPE_BANDWIDTH_LIMIT: {'max_kbps', 'max_burst_kbps'}}
+ RULE_TYPE_BANDWIDTH_LIMIT: {'max_kbps'}}
OPTIONAL_PARAMETERS = {
RULE_TYPE_MINIMUM_BANDWIDTH: set(),
RULE_TYPE_DSCP_MARKING: set(),
- RULE_TYPE_BANDWIDTH_LIMIT: {'direction'}}
+ RULE_TYPE_BANDWIDTH_LIMIT: {'direction', 'max_burst_kbps'}}
DIRECTION_EGRESS = 'egress'
DIRECTION_INGRESS = 'ingress'
DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,
@@ -62,11 +62,11 @@ def _check_type_parameters(attrs, type, is_create):
notreq_params -= type_params
if is_create and None in map(attrs.get, req_params):
msg = (_('"Create" rule command for type "%(rule_type)s" requires '
- 'arguments %(args)s') %
+ 'arguments: %(args)s') %
{'rule_type': type, 'args': ", ".join(sorted(req_params))})
raise exceptions.CommandError(msg)
if set(attrs.keys()) & notreq_params:
- msg = (_('Rule type "%(rule_type)s" only requires arguments %(args)s')
+ msg = (_('Rule type "%(rule_type)s" only requires arguments: %(args)s')
% {'rule_type': type, 'args': ", ".join(sorted(type_params))})
raise exceptions.CommandError(msg)
@@ -141,7 +141,10 @@ def _add_rule_arguments(parser):
dest='max_burst_kbits',
metavar='<max-burst-kbits>',
type=int,
- help=_('Maximum burst in kilobits, 0 means automatic')
+ help=_('Maximum burst in kilobits, 0 or not specified means '
+ 'automatic, which is 80%% of the bandwidth limit, which works '
+ 'for typical TCP traffic. For details check the QoS user '
+ 'workflow.')
)
parser.add_argument(
'--dscp-mark',
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index f13ee7b9..0d276d9d 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -25,6 +25,7 @@ from osc_lib import utils
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
+from openstackclient.network import common
from openstackclient.network import sdk_utils
from openstackclient.network.v2 import _tag
@@ -215,6 +216,9 @@ def _prepare_filter_fixed_ips(client_manager, parsed_args):
if 'ip-address' in ip_spec:
ips.append('ip_address=%s' % ip_spec['ip-address'])
+
+ if 'ip-substring' in ip_spec:
+ ips.append('ip_address_substr=%s' % ip_spec['ip-substring'])
return ips
@@ -255,7 +259,7 @@ def _add_updatable_args(parser):
'normal', 'baremetal', 'virtio-forwarder'],
help=_("VNIC type for this port (direct | direct-physical | "
"macvtap | normal | baremetal | virtio-forwarder, "
- " default: normal)")
+ "default: normal)")
)
# NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not
# remove before 3.x release or Mar 2017.
@@ -278,8 +282,8 @@ def _add_updatable_args(parser):
)
parser.add_argument(
'--dns-name',
- metavar='dns-name',
- help=_("Set DNS name to this port "
+ metavar='<dns-name>',
+ help=_("Set DNS name for this port "
"(requires DNS integration extension)")
)
@@ -434,7 +438,10 @@ class CreatePort(command.ShowOne):
if parsed_args.qos_policy:
attrs['qos_policy_id'] = client.find_qos_policy(
parsed_args.qos_policy, ignore_missing=False).id
- obj = client.create_port(**attrs)
+ with common.check_missing_extension_if_error(
+ self.app.client_manager.network, attrs):
+ obj = client.create_port(**attrs)
+
# tags cannot be set when created, so tags need to be set later.
_tag.update_tags_for_set(client, obj, parsed_args)
display_columns, columns = _get_columns(obj)
@@ -531,11 +538,13 @@ class ListPort(command.Lister):
identity_common.add_project_domain_option_to_parser(parser)
parser.add_argument(
'--fixed-ip',
- metavar='subnet=<subnet>,ip-address=<ip-address>',
+ metavar=('subnet=<subnet>,ip-address=<ip-address>,'
+ 'ip-substring=<ip-substring>'),
action=parseractions.MultiKeyValueAction,
- optional_keys=['subnet', 'ip-address'],
+ optional_keys=['subnet', 'ip-address', 'ip-substring'],
help=_("Desired IP and/or subnet for filtering ports "
- "(name or ID): subnet=<subnet>,ip-address=<ip-address> "
+ "(name or ID): subnet=<subnet>,ip-address=<ip-address>,"
+ "ip-substring=<ip-substring> "
"(repeat option to set multiple fixed IP addresses)"),
)
_tag.add_tag_filtering_option_to_parser(parser, _('ports'))
@@ -660,7 +669,7 @@ class SetPort(command.Command):
parser.add_argument(
'--no-binding-profile',
action='store_true',
- help=_("Clear existing information of binding:profile."
+ help=_("Clear existing information of binding:profile. "
"Specify both --binding-profile and --no-binding-profile "
"to overwrite the current binding:profile information.")
)
@@ -714,9 +723,9 @@ class SetPort(command.Command):
'--no-allowed-address',
dest='no_allowed_address_pair',
action='store_true',
- help=_("Clear existing allowed-address pairs associated"
- "with this port."
- "(Specify both --allowed-address and --no-allowed-address"
+ help=_("Clear existing allowed-address pairs associated "
+ "with this port. "
+ "(Specify both --allowed-address and --no-allowed-address "
"to overwrite the current allowed-address pairs)")
)
parser.add_argument(
@@ -785,7 +794,9 @@ class SetPort(command.Command):
attrs['data_plane_status'] = parsed_args.data_plane_status
if attrs:
- client.update_port(obj, **attrs)
+ with common.check_missing_extension_if_error(
+ self.app.client_manager.network, attrs):
+ client.update_port(obj, **attrs)
# tags is a subresource and it needs to be updated separately.
_tag.update_tags_for_set(client, obj, parsed_args)
@@ -832,7 +843,7 @@ class UnsetPort(command.Command):
'--binding-profile',
metavar='<binding-profile-key>',
action='append',
- help=_("Desired key which should be removed from binding:profile"
+ help=_("Desired key which should be removed from binding:profile "
"(repeat option to unset multiple binding:profile data)"))
parser.add_argument(
'--security-group',
@@ -856,8 +867,8 @@ class UnsetPort(command.Command):
required_keys=['ip-address'],
optional_keys=['mac-address'],
help=_("Desired allowed-address pair which should be removed "
- "from this port: ip-address=<ip-address> "
- "[,mac-address=<mac-address>] (repeat option to set "
+ "from this port: ip-address=<ip-address>"
+ "[,mac-address=<mac-address>] (repeat option to unset "
"multiple allowed-address pairs)")
)
parser.add_argument(
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index caf3236a..746452e4 100644
--- a/openstackclient/network/v2/router.py
+++ b/openstackclient/network/v2/router.py
@@ -86,8 +86,8 @@ def _get_attrs(client_manager, parsed_args):
attrs['distributed'] = False
if parsed_args.distributed:
attrs['distributed'] = True
- if ('availability_zone_hints' in parsed_args
- and parsed_args.availability_zone_hints is not None):
+ if ('availability_zone_hints' in parsed_args and
+ parsed_args.availability_zone_hints is not None):
attrs['availability_zone_hints'] = parsed_args.availability_zone_hints
if parsed_args.description is not None:
attrs['description'] = parsed_args.description
@@ -563,7 +563,7 @@ class SetRouter(command.Command):
metavar='subnet=<subnet>,ip-address=<ip-address>',
action=parseractions.MultiKeyValueAction,
optional_keys=['subnet', 'ip-address'],
- help=_("Desired IP and/or subnet (name or ID)"
+ help=_("Desired IP and/or subnet (name or ID) "
"on external gateway: "
"subnet=<subnet>,ip-address=<ip-address> "
"(repeat option to set multiple fixed IP addresses)")
@@ -611,10 +611,10 @@ class SetRouter(command.Command):
elif parsed_args.no_route or parsed_args.clear_routes:
attrs['routes'] = []
if (parsed_args.disable_snat or parsed_args.enable_snat or
- parsed_args.fixed_ip) and not parsed_args.external_gateway:
- msg = (_("You must specify '--external-gateway' in order"
- "to update the SNAT or fixed-ip values"))
- raise exceptions.CommandError(msg)
+ parsed_args.fixed_ip) and not parsed_args.external_gateway:
+ msg = (_("You must specify '--external-gateway' in order "
+ "to update the SNAT or fixed-ip values"))
+ raise exceptions.CommandError(msg)
if parsed_args.external_gateway:
gateway_info = {}
network = client.find_network(
diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py
index 75af3587..ed6c8d7c 100644
--- a/openstackclient/network/v2/security_group.py
+++ b/openstackclient/network/v2/security_group.py
@@ -15,6 +15,7 @@
import argparse
+from osc_lib.command import command
from osc_lib import utils
import six
@@ -23,6 +24,7 @@ from openstackclient.identity import common as identity_common
from openstackclient.network import common
from openstackclient.network import sdk_utils
from openstackclient.network import utils as network_utils
+from openstackclient.network.v2 import _tag
def _format_network_security_group_rules(sg_rules):
@@ -106,6 +108,7 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne):
help=_("Owner's project (name or ID)")
)
identity_common.add_project_domain_option_to_parser(parser)
+ _tag.add_tag_option_to_parser_for_create(parser, _('security group'))
return parser
def _get_description(self, parsed_args):
@@ -130,6 +133,8 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne):
# Create the security group and display the results.
obj = client.create_security_group(**attrs)
+ # tags cannot be set when created, so tags need to be set later.
+ _tag.update_tags_for_set(client, obj, parsed_args)
display_columns, property_columns = _get_columns(obj)
data = utils.get_item_properties(
obj,
@@ -198,6 +203,7 @@ class ListSecurityGroup(common.NetworkAndComputeLister):
"(name or ID)")
)
identity_common.add_project_domain_option_to_parser(parser)
+ _tag.add_tag_filtering_option_to_parser(parser, _('security group'))
return parser
def update_parser_compute(self, parser):
@@ -220,19 +226,23 @@ class ListSecurityGroup(common.NetworkAndComputeLister):
).id
filters['tenant_id'] = project_id
filters['project_id'] = project_id
+
+ _tag.get_tag_filtering_args(parsed_args, filters)
data = client.security_groups(**filters)
columns = (
"ID",
"Name",
"Description",
- "Project ID"
+ "Project ID",
+ "tags"
)
column_headers = (
"ID",
"Name",
"Description",
- "Project"
+ "Project",
+ "Tags"
)
return (column_headers,
(utils.get_item_properties(
@@ -282,6 +292,10 @@ class SetSecurityGroup(common.NetworkAndComputeCommand):
)
return parser
+ def update_parser_network(self, parser):
+ _tag.add_tag_option_to_parser_for_set(parser, _('security group'))
+ return parser
+
def take_action_network(self, client, parsed_args):
obj = client.find_security_group(parsed_args.group,
ignore_missing=False)
@@ -295,6 +309,9 @@ class SetSecurityGroup(common.NetworkAndComputeCommand):
# the update.
client.update_security_group(obj, **attrs)
+ # tags is a subresource and it needs to be updated separately.
+ _tag.update_tags_for_set(client, obj, parsed_args)
+
def take_action_compute(self, client, parsed_args):
data = client.api.security_group_find(parsed_args.group)
@@ -344,3 +361,25 @@ class ShowSecurityGroup(common.NetworkAndComputeShowOne):
formatters=_formatters_compute
)
return (display_columns, data)
+
+
+class UnsetSecurityGroup(command.Command):
+ _description = _("Unset security group properties")
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetSecurityGroup, self).get_parser(prog_name)
+ parser.add_argument(
+ 'group',
+ metavar="<group>",
+ help=_("Security group to modify (name or ID)")
+ )
+ _tag.add_tag_option_to_parser_for_unset(parser, _('security group'))
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_security_group(parsed_args.group,
+ ignore_missing=False)
+
+ # tags is a subresource and it needs to be updated separately.
+ _tag.update_tags_for_unset(client, obj, parsed_args)
diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py
index 2c71e1e0..d252a7a5 100644
--- a/openstackclient/network/v2/subnet.py
+++ b/openstackclient/network/v2/subnet.py
@@ -167,6 +167,7 @@ def convert_entries_to_gateway(entries):
def _get_attrs(client_manager, parsed_args, is_create=True):
attrs = {}
+ client = client_manager.network
if 'name' in parsed_args and parsed_args.name is not None:
attrs['name'] = str(parsed_args.name)
@@ -179,7 +180,6 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
parsed_args.project_domain,
).id
attrs['tenant_id'] = project_id
- client = client_manager.network
attrs['network_id'] = client.find_network(parsed_args.network,
ignore_missing=False).id
if parsed_args.subnet_pool is not None:
@@ -200,10 +200,10 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode
if parsed_args.ipv6_address_mode is not None:
attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode
- if parsed_args.network_segment is not None:
- attrs['segment_id'] = client.find_segment(
- parsed_args.network_segment, ignore_missing=False).id
+ if parsed_args.network_segment is not None:
+ attrs['segment_id'] = client.find_segment(
+ parsed_args.network_segment, ignore_missing=False).id
if 'gateway' in parsed_args and parsed_args.gateway is not None:
gateway = parsed_args.gateway.lower()
@@ -247,6 +247,7 @@ class CreateSubnet(command.ShowOne):
parser = super(CreateSubnet, self).get_parser(prog_name)
parser.add_argument(
'name',
+ metavar='<name>',
help=_("New subnet name")
)
parser.add_argument(
@@ -412,7 +413,7 @@ class ListSubnet(command.Lister):
choices=[4, 6],
metavar='<ip-version>',
dest='ip_version',
- help=_("List only subnets of given IP version in output."
+ help=_("List only subnets of given IP version in output. "
"Allowed values for IP version are 4 and 6."),
)
dhcp_enable_group = parser.add_mutually_exclusive_group()
@@ -559,6 +560,14 @@ class SetSubnet(command.Command):
"e.g.: --gateway 192.168.9.1, --gateway none.")
)
parser.add_argument(
+ '--network-segment',
+ metavar='<network-segment>',
+ help=_("Network segment to associate with this subnet (name or "
+ "ID). It is only allowed to set the segment if the current "
+ "value is `None`, the network must also have only one "
+ "segment and only one subnet can exist on the network.")
+ )
+ parser.add_argument(
'--description',
metavar='<description>',
help=_("Set subnet description")
@@ -581,7 +590,7 @@ class SetSubnet(command.Command):
if not parsed_args.no_host_route:
attrs['host_routes'] += obj.host_routes
elif parsed_args.no_host_route:
- attrs['host_routes'] = ''
+ attrs['host_routes'] = []
if 'allocation_pools' in attrs:
if not parsed_args.no_allocation_pool:
attrs['allocation_pools'] += obj.allocation_pools
diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py
index a5839868..81765ca1 100644
--- a/openstackclient/network/v2/subnet_pool.py
+++ b/openstackclient/network/v2/subnet_pool.py
@@ -191,8 +191,9 @@ class CreateSubnetPool(command.ShowOne):
'--default-quota',
type=int,
metavar='<num-ip-addresses>',
- help=_("Set default quota for subnet pool as the number of"
- "IP addresses allowed in a subnet")),
+ help=_("Set default per-project quota for this subnet pool "
+ "as the number of IP addresses that can be allocated "
+ "from the subnet pool")),
_tag.add_tag_option_to_parser_for_create(parser, _('subnet pool'))
return parser
@@ -389,8 +390,9 @@ class SetSubnetPool(command.Command):
'--default-quota',
type=int,
metavar='<num-ip-addresses>',
- help=_("Set default quota for subnet pool as the number of"
- "IP addresses allowed in a subnet")),
+ help=_("Set default per-project quota for this subnet pool "
+ "as the number of IP addresses that can be allocated "
+ "from the subnet pool")),
_tag.add_tag_option_to_parser_for_set(parser, _('subnet pool'))
return parser
diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py
index d589f19c..41aabb7f 100644
--- a/openstackclient/tests/functional/common/test_module.py
+++ b/openstackclient/tests/functional/common/test_module.py
@@ -46,7 +46,7 @@ class ModuleTest(base.TestCase):
class CommandTest(base.TestCase):
"""Functional tests for openstackclient command list."""
GROUPS = [
- 'openstack.volume.v2',
+ 'openstack.volume.v3',
'openstack.network.v2',
'openstack.image.v2',
'openstack.identity.v3',
diff --git a/openstackclient/tests/functional/common/test_versions.py b/openstackclient/tests/functional/common/test_versions.py
new file mode 100644
index 00000000..adc74ebc
--- /dev/null
+++ b/openstackclient/tests/functional/common/test_versions.py
@@ -0,0 +1,31 @@
+# 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 VersionsTests(base.TestCase):
+ """Functional tests for versions."""
+
+ def test_versions_show(self):
+ # TODO(mordred) Make this better. The trick is knowing what in the
+ # payload to test for.
+ cmd_output = json.loads(self.openstack(
+ 'versions show -f json'
+ ))
+ self.assertIsNotNone(cmd_output)
+ self.assertIn(
+ "Region Name",
+ cmd_output[0],
+ )
diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py
index cf9d2bc0..71026757 100644
--- a/openstackclient/tests/functional/compute/v2/test_aggregate.py
+++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py
@@ -11,6 +11,7 @@
# under the License.
import json
+import time
import uuid
from openstackclient.tests.functional import base
@@ -51,6 +52,23 @@ class AggregateTests(base.TestCase):
cmd_output['availability_zone']
)
+ # Loop a few times since this is timing-sensitive
+ # Just hard-code it for now, since there is no pause and it is
+ # racy we shouldn't have to wait too long, a minute seems reasonable
+ wait_time = 0
+ while wait_time < 60:
+ cmd_output = json.loads(self.openstack(
+ 'aggregate show -f json ' +
+ name2
+ ))
+ if cmd_output['name'] != name2:
+ # Hang out for a bit and try again
+ print('retrying aggregate check')
+ wait_time += 10
+ time.sleep(10)
+ else:
+ break
+
del_output = self.openstack(
'aggregate delete ' +
name1 + ' ' +
diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py
index 13fdfa06..3cb72d9f 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 json
+import time
import uuid
from tempest.lib import exceptions
@@ -255,10 +256,24 @@ class ServerTests(common.ComputeTestCase):
floating_ip
)
self.assertEqual("", raw_output)
- cmd_output = json.loads(self.openstack(
- 'server show -f json ' +
- name
- ))
+
+ # Loop a few times since this is timing-sensitive
+ # Just hard-code it for now, since there is no pause and it is
+ # racy we shouldn't have to wait too long, a minute seems reasonable
+ wait_time = 0
+ while wait_time < 60:
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ name
+ ))
+ if floating_ip not in cmd_output['addresses']:
+ # Hang out for a bit and try again
+ print('retrying floating IP check')
+ wait_time += 10
+ time.sleep(10)
+ else:
+ break
+
self.assertIn(
floating_ip,
cmd_output['addresses'],
@@ -272,6 +287,23 @@ class ServerTests(common.ComputeTestCase):
)
self.assertEqual("", raw_output)
+ # Loop a few times since this is timing-sensitive
+ # Just hard-code it for now, since there is no pause and it is
+ # racy we shouldn't have to wait too long, a minute seems reasonable
+ wait_time = 0
+ while wait_time < 60:
+ cmd_output = json.loads(self.openstack(
+ 'server show -f json ' +
+ name
+ ))
+ if floating_ip in cmd_output['addresses']:
+ # Hang out for a bit and try again
+ print('retrying floating IP check')
+ wait_time += 10
+ time.sleep(10)
+ else:
+ break
+
cmd_output = json.loads(self.openstack(
'server show -f json ' +
name
diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py
index 33cb5d86..58468bc7 100644
--- a/openstackclient/tests/functional/identity/v3/common.py
+++ b/openstackclient/tests/functional/identity/v3/common.py
@@ -52,6 +52,17 @@ class IdentityTests(base.TestCase):
'id', 'relay_state_prefix', 'sp_url']
SERVICE_PROVIDER_LIST_HEADERS = ['ID', 'Enabled', 'Description',
'Auth URL']
+ IMPLIED_ROLE_LIST_HEADERS = ['Prior Role ID', 'Prior Role Name',
+ 'Implied Role ID', 'Implied Role Name']
+ REGISTERED_LIMIT_FIELDS = ['id', 'service_id', 'resource_name',
+ 'default_limit', 'description', 'region_id']
+ REGISTERED_LIMIT_LIST_HEADERS = ['ID', 'Service ID', 'Resource Name',
+ 'Default Limit', 'Description',
+ 'Region ID']
+ LIMIT_FIELDS = ['id', 'project_id', 'service_id', 'resource_name',
+ 'resource_limit', 'description', 'region_id']
+ LIMIT_LIST_HEADERS = ['ID', 'Project ID', 'Service ID', 'Resource Name',
+ 'Resource Limit', 'Description', 'Region ID']
@classmethod
def setUpClass(cls):
@@ -149,6 +160,17 @@ class IdentityTests(base.TestCase):
self.assertEqual(role_name, role['name'])
return role_name
+ def _create_dummy_implied_role(self, add_clean_up=True):
+ role_name = self._create_dummy_role(add_clean_up)
+ implied_role_name = self._create_dummy_role(add_clean_up)
+ self.openstack(
+ 'implied role create '
+ '--implied-role %(implied_role)s '
+ '%(role)s' % {'implied_role': implied_role_name,
+ 'role': role_name})
+
+ return implied_role_name, role_name
+
def _create_dummy_group(self, add_clean_up=True):
group_name = data_utils.rand_name('TestGroup')
description = data_utils.rand_name('description')
@@ -306,3 +328,74 @@ class IdentityTests(base.TestCase):
items = self.parse_show(raw_output)
self.assert_show_fields(items, self.SERVICE_PROVIDER_FIELDS)
return service_provider
+
+ def _create_dummy_registered_limit(self, add_clean_up=True):
+ service_name = self._create_dummy_service()
+ resource_name = data_utils.rand_name('resource_name')
+ params = {
+ 'service_name': service_name,
+ 'default_limit': 10,
+ 'resource_name': resource_name
+ }
+ raw_output = self.openstack(
+ 'registered limit create'
+ ' --service %(service_name)s'
+ ' --default-limit %(default_limit)s'
+ ' %(resource_name)s' % params
+ )
+ items = self.parse_show(raw_output)
+ registered_limit_id = self._extract_value_from_items('id', items)
+
+ if add_clean_up:
+ self.addCleanup(
+ self.openstack,
+ 'registered limit delete %s' % registered_limit_id
+ )
+
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+ return registered_limit_id
+
+ def _extract_value_from_items(self, key, items):
+ for d in items:
+ for k, v in d.iteritems():
+ if k == key:
+ return v
+
+ def _create_dummy_limit(self, add_clean_up=True):
+ registered_limit_id = self._create_dummy_registered_limit()
+
+ raw_output = self.openstack(
+ 'registered limit show %s' % registered_limit_id
+ )
+ items = self.parse_show(raw_output)
+ resource_name = self._extract_value_from_items('resource_name', items)
+ service_id = self._extract_value_from_items('service_id', items)
+ resource_limit = 15
+
+ project_name = self._create_dummy_project()
+ raw_output = self.openstack('project show %s' % project_name)
+ items = self.parse_show(raw_output)
+ project_id = self._extract_value_from_items('id', items)
+
+ params = {
+ 'project_id': project_id,
+ 'service_id': service_id,
+ 'resource_name': resource_name,
+ 'resource_limit': resource_limit
+ }
+
+ raw_output = self.openstack(
+ 'limit create'
+ ' --project %(project_id)s'
+ ' --service %(service_id)s'
+ ' --resource-limit %(resource_limit)s'
+ ' %(resource_name)s' % params
+ )
+ items = self.parse_show(raw_output)
+ limit_id = self._extract_value_from_items('id', items)
+
+ if add_clean_up:
+ self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
+
+ self.assert_show_fields(items, self.LIMIT_FIELDS)
+ return limit_id
diff --git a/openstackclient/tests/functional/identity/v3/test_limit.py b/openstackclient/tests/functional/identity/v3/test_limit.py
new file mode 100644
index 00000000..03bcb06e
--- /dev/null
+++ b/openstackclient/tests/functional/identity/v3/test_limit.py
@@ -0,0 +1,192 @@
+# 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.
+
+from tempest.lib.common.utils import data_utils
+
+from openstackclient.tests.functional.identity.v3 import common
+
+
+class LimitTestCase(common.IdentityTests):
+
+ def test_limit_create_with_service_name(self):
+ registered_limit_id = self._create_dummy_registered_limit()
+ raw_output = self.openstack(
+ 'registered limit show %s' % registered_limit_id
+ )
+ items = self.parse_show(raw_output)
+ service_id = self._extract_value_from_items('service_id', items)
+ resource_name = self._extract_value_from_items('resource_name', items)
+
+ raw_output = self.openstack('service show %s' % service_id)
+ items = self.parse_show(raw_output)
+ service_name = self._extract_value_from_items('name', items)
+
+ project_name = self._create_dummy_project()
+ raw_output = self.openstack('project show %s' % project_name)
+ items = self.parse_show(raw_output)
+ project_id = self._extract_value_from_items('id', items)
+
+ params = {
+ 'project_id': project_id,
+ 'service_name': service_name,
+ 'resource_name': resource_name,
+ 'resource_limit': 15
+ }
+ raw_output = self.openstack(
+ 'limit create'
+ ' --project %(project_id)s'
+ ' --service %(service_name)s'
+ ' --resource-limit %(resource_limit)s'
+ ' %(resource_name)s' % params
+ )
+ items = self.parse_show(raw_output)
+ limit_id = self._extract_value_from_items('id', items)
+ self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
+
+ self.assert_show_fields(items, self.LIMIT_FIELDS)
+
+ def test_limit_create_with_project_name(self):
+ registered_limit_id = self._create_dummy_registered_limit()
+ raw_output = self.openstack(
+ 'registered limit show %s' % registered_limit_id
+ )
+ items = self.parse_show(raw_output)
+ service_id = self._extract_value_from_items('service_id', items)
+ resource_name = self._extract_value_from_items('resource_name', items)
+
+ raw_output = self.openstack('service show %s' % service_id)
+ items = self.parse_show(raw_output)
+ service_name = self._extract_value_from_items('name', items)
+
+ project_name = self._create_dummy_project()
+
+ params = {
+ 'project_name': project_name,
+ 'service_name': service_name,
+ 'resource_name': resource_name,
+ 'resource_limit': 15
+ }
+ raw_output = self.openstack(
+ 'limit create'
+ ' --project %(project_name)s'
+ ' --service %(service_name)s'
+ ' --resource-limit %(resource_limit)s'
+ ' %(resource_name)s' % params
+ )
+ items = self.parse_show(raw_output)
+ limit_id = self._extract_value_from_items('id', items)
+ self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
+
+ self.assert_show_fields(items, self.LIMIT_FIELDS)
+ registered_limit_id = self._create_dummy_registered_limit()
+
+ def test_limit_create_with_service_id(self):
+ self._create_dummy_limit()
+
+ def test_limit_create_with_project_id(self):
+ self._create_dummy_limit()
+
+ def test_limit_create_with_options(self):
+ registered_limit_id = self._create_dummy_registered_limit()
+ region_id = self._create_dummy_region()
+
+ params = {
+ 'region_id': region_id,
+ 'registered_limit_id': registered_limit_id
+ }
+
+ raw_output = self.openstack(
+ 'registered limit set'
+ ' %(registered_limit_id)s'
+ ' --region %(region_id)s' % params
+ )
+ items = self.parse_show(raw_output)
+ service_id = self._extract_value_from_items('service_id', items)
+ resource_name = self._extract_value_from_items('resource_name', items)
+
+ project_name = self._create_dummy_project()
+ raw_output = self.openstack('project show %s' % project_name)
+ items = self.parse_show(raw_output)
+ project_id = self._extract_value_from_items('id', items)
+ description = data_utils.arbitrary_string()
+
+ params = {
+ 'project_id': project_id,
+ 'service_id': service_id,
+ 'resource_name': resource_name,
+ 'resource_limit': 15,
+ 'region_id': region_id,
+ 'description': description
+ }
+ raw_output = self.openstack(
+ 'limit create'
+ ' --project %(project_id)s'
+ ' --service %(service_id)s'
+ ' --resource-limit %(resource_limit)s'
+ ' --region %(region_id)s'
+ ' --description %(description)s'
+ ' %(resource_name)s' % params
+ )
+ items = self.parse_show(raw_output)
+ limit_id = self._extract_value_from_items('id', items)
+ self.addCleanup(self.openstack, 'limit delete %s' % limit_id)
+
+ self.assert_show_fields(items, self.LIMIT_FIELDS)
+
+ def test_limit_show(self):
+ limit_id = self._create_dummy_limit()
+ raw_output = self.openstack('limit show %s' % limit_id)
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.LIMIT_FIELDS)
+
+ def test_limit_set_description(self):
+ limit_id = self._create_dummy_limit()
+
+ params = {
+ 'description': data_utils.arbitrary_string(),
+ 'limit_id': limit_id
+ }
+
+ raw_output = self.openstack(
+ 'limit set'
+ ' --description %(description)s'
+ ' %(limit_id)s' % params
+ )
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.LIMIT_FIELDS)
+
+ def test_limit_set_resource_limit(self):
+ limit_id = self._create_dummy_limit()
+
+ params = {
+ 'resource_limit': 5,
+ 'limit_id': limit_id
+ }
+
+ raw_output = self.openstack(
+ 'limit set'
+ ' --resource-limit %(resource_limit)s'
+ ' %(limit_id)s' % params
+ )
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.LIMIT_FIELDS)
+
+ def test_limit_list(self):
+ self._create_dummy_limit()
+ raw_output = self.openstack('limit list')
+ items = self.parse_listing(raw_output)
+ self.assert_table_structure(items, self.LIMIT_LIST_HEADERS)
+
+ def test_limit_delete(self):
+ limit_id = self._create_dummy_limit(add_clean_up=False)
+ raw_output = self.openstack('limit delete %s' % limit_id)
+ self.assertEqual(0, len(raw_output))
diff --git a/openstackclient/tests/functional/identity/v3/test_registered_limit.py b/openstackclient/tests/functional/identity/v3/test_registered_limit.py
new file mode 100644
index 00000000..09e90ce2
--- /dev/null
+++ b/openstackclient/tests/functional/identity/v3/test_registered_limit.py
@@ -0,0 +1,184 @@
+# 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.
+
+from tempest.lib.common.utils import data_utils
+
+from openstackclient.tests.functional.identity.v3 import common
+
+
+class RegisteredLimitTestCase(common.IdentityTests):
+
+ def test_registered_limit_create_with_service_name(self):
+ self._create_dummy_registered_limit()
+
+ def test_registered_limit_create_with_service_id(self):
+ service_name = self._create_dummy_service()
+ raw_output = self.openstack(
+ 'service show'
+ ' %(service_name)s' % {'service_name': service_name}
+ )
+ service_items = self.parse_show(raw_output)
+ service_id = self._extract_value_from_items('id', service_items)
+
+ raw_output = self.openstack(
+ 'registered limit create'
+ ' --service %(service_id)s'
+ ' --default-limit %(default_limit)s'
+ ' %(resource_name)s' % {
+ 'service_id': service_id,
+ 'default_limit': 10,
+ 'resource_name': 'cores'
+ }
+ )
+ items = self.parse_show(raw_output)
+ registered_limit_id = self._extract_value_from_items('id', items)
+ self.addCleanup(
+ self.openstack,
+ 'registered limit delete'
+ ' %(registered_limit_id)s' % {
+ 'registered_limit_id': registered_limit_id
+ }
+ )
+
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+
+ def test_registered_limit_create_with_options(self):
+ service_name = self._create_dummy_service()
+ region_id = self._create_dummy_region()
+ params = {
+ 'service_name': service_name,
+ 'resource_name': 'cores',
+ 'default_limit': 10,
+ 'description': 'default limit for cores',
+ 'region_id': region_id
+ }
+
+ raw_output = self.openstack(
+ 'registered limit create'
+ ' --description \'%(description)s\''
+ ' --region %(region_id)s'
+ ' --service %(service_name)s'
+ ' --default-limit %(default_limit)s'
+ ' %(resource_name)s' % params
+ )
+ items = self.parse_show(raw_output)
+ registered_limit_id = self._extract_value_from_items('id', items)
+ self.addCleanup(
+ self.openstack,
+ 'registered limit delete %(registered_limit_id)s' % {
+ 'registered_limit_id': registered_limit_id
+ }
+ )
+
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+
+ def test_registered_limit_show(self):
+ registered_limit_id = self._create_dummy_registered_limit()
+ raw_output = self.openstack(
+ 'registered limit show %(registered_limit_id)s' % {
+ 'registered_limit_id': registered_limit_id
+ }
+ )
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+
+ def test_registered_limit_set_region_id(self):
+ region_id = self._create_dummy_region()
+ registered_limit_id = self._create_dummy_registered_limit()
+
+ params = {
+ 'registered_limit_id': registered_limit_id,
+ 'region_id': region_id
+ }
+ raw_output = self.openstack(
+ 'registered limit set'
+ ' %(registered_limit_id)s'
+ ' --region %(region_id)s' % params
+ )
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+
+ def test_registered_limit_set_description(self):
+ registered_limit_id = self._create_dummy_registered_limit()
+ params = {
+ 'registered_limit_id': registered_limit_id,
+ 'description': 'updated description'
+ }
+ raw_output = self.openstack(
+ 'registered limit set'
+ ' %(registered_limit_id)s'
+ ' --description \'%(description)s\'' % params
+ )
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+
+ def test_registered_limit_set_service(self):
+ registered_limit_id = self._create_dummy_registered_limit()
+ service_name = self._create_dummy_service()
+ params = {
+ 'registered_limit_id': registered_limit_id,
+ 'service': service_name
+ }
+ raw_output = self.openstack(
+ 'registered limit set'
+ ' %(registered_limit_id)s'
+ ' --service %(service)s' % params
+ )
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+
+ def test_registered_limit_set_default_limit(self):
+ registered_limit_id = self._create_dummy_registered_limit()
+ params = {
+ 'registered_limit_id': registered_limit_id,
+ 'default_limit': 20
+ }
+ raw_output = self.openstack(
+ 'registered limit set'
+ ' %(registered_limit_id)s'
+ ' --default-limit %(default_limit)s' % params
+ )
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+
+ def test_registered_limit_set_resource_name(self):
+ registered_limit_id = self._create_dummy_registered_limit()
+ resource_name = data_utils.rand_name('resource_name')
+ params = {
+ 'registered_limit_id': registered_limit_id,
+ 'resource_name': resource_name
+ }
+ raw_output = self.openstack(
+ 'registered limit set'
+ ' %(registered_limit_id)s'
+ ' --resource-name %(resource_name)s' % params
+ )
+ items = self.parse_show(raw_output)
+ self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS)
+
+ def test_registered_limit_list(self):
+ self._create_dummy_registered_limit()
+ raw_output = self.openstack('registered limit list')
+ items = self.parse_listing(raw_output)
+ self.assert_table_structure(items, self.REGISTERED_LIMIT_LIST_HEADERS)
+
+ def test_registered_limit_delete(self):
+ registered_limit_id = self._create_dummy_registered_limit(
+ add_clean_up=False
+ )
+ raw_output = self.openstack(
+ 'registered limit delete'
+ ' %(registered_limit_id)s' % {
+ 'registered_limit_id': registered_limit_id
+ }
+ )
+ self.assertEqual(0, len(raw_output))
diff --git a/openstackclient/tests/functional/identity/v3/test_role.py b/openstackclient/tests/functional/identity/v3/test_role.py
index ab8af9c0..fb9e0614 100644
--- a/openstackclient/tests/functional/identity/v3/test_role.py
+++ b/openstackclient/tests/functional/identity/v3/test_role.py
@@ -143,3 +143,28 @@ class RoleTests(common.IdentityTests):
'role': role_name})
self.assertEqual(0, len(add_raw_output))
self.assertEqual(0, len(remove_raw_output))
+
+ def test_implied_role_list(self):
+ self._create_dummy_implied_role()
+ raw_output = self.openstack('implied role list')
+ items = self.parse_listing(raw_output)
+ self.assert_table_structure(items, self.IMPLIED_ROLE_LIST_HEADERS)
+ self.assertEqual(3, len(items))
+
+ def test_implied_role_create(self):
+ role_name = self._create_dummy_role()
+ implied_role_name = self._create_dummy_role()
+ self.openstack(
+ 'implied role create '
+ '--implied-role %(implied_role)s '
+ '%(role)s' % {'implied_role': implied_role_name,
+ 'role': role_name})
+
+ def test_implied_role_delete(self):
+ implied_role_name, role_name = self._create_dummy_implied_role()
+ raw_output = self.openstack(
+ 'implied role delete '
+ '--implied-role %(implied_role)s '
+ '%(role)s' % {'implied_role': implied_role_name,
+ 'role': role_name})
+ self.assertEqual(0, len(raw_output))
diff --git a/openstackclient/tests/functional/image/base.py b/openstackclient/tests/functional/image/base.py
new file mode 100644
index 00000000..4b2ab64b
--- /dev/null
+++ b/openstackclient/tests/functional/image/base.py
@@ -0,0 +1,24 @@
+# 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.
+
+from openstackclient.tests.functional import base
+
+
+class BaseImageTests(base.TestCase):
+ """Functional tests for Image commands"""
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseImageTests, cls).setUpClass()
+ # TODO(dtroyer): maybe do image API discovery here to determine
+ # what is available, it isn't in the service catalog
+ cls.haz_v1_api = False
diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py
index fa073f99..30490bf5 100644
--- a/openstackclient/tests/functional/image/v1/test_image.py
+++ b/openstackclient/tests/functional/image/v1/test_image.py
@@ -15,50 +15,47 @@ import uuid
import fixtures
-from openstackclient.tests.functional import base
+from openstackclient.tests.functional.image import base
-class ImageTests(base.TestCase):
- """Functional tests for image. """
+class ImageTests(base.BaseImageTests):
+ """Functional tests for Image commands"""
- NAME = uuid.uuid4().hex
- OTHER_NAME = uuid.uuid4().hex
+ def setUp(self):
+ super(ImageTests, self).setUp()
+ if not self.haz_v1_api:
+ self.skipTest('No Image v1 API present')
- @classmethod
- def setUpClass(cls):
- super(ImageTests, cls).setUpClass()
- json_output = json.loads(cls.openstack(
+ self.name = uuid.uuid4().hex
+ json_output = json.loads(self.openstack(
'--os-image-api-version 1 '
'image create -f json ' +
- cls.NAME
+ self.name
))
- cls.image_id = json_output["id"]
- cls.assertOutput(cls.NAME, json_output['name'])
+ self.image_id = json_output["id"]
+ self.assertOutput(self.name, json_output['name'])
+
+ ver_fixture = fixtures.EnvironmentVariable(
+ 'OS_IMAGE_API_VERSION', '1'
+ )
+ self.useFixture(ver_fixture)
- @classmethod
- def tearDownClass(cls):
+ def tearDown(self):
try:
- cls.openstack(
+ self.openstack(
'--os-image-api-version 1 '
'image delete ' +
- cls.image_id
+ self.image_id
)
finally:
- super(ImageTests, cls).tearDownClass()
-
- def setUp(self):
- super(ImageTests, self).setUp()
- ver_fixture = fixtures.EnvironmentVariable(
- 'OS_IMAGE_API_VERSION', '1'
- )
- self.useFixture(ver_fixture)
+ super(ImageTests, self).tearDown()
def test_image_list(self):
json_output = json.loads(self.openstack(
'image list -f json '
))
self.assertIn(
- self.NAME,
+ self.name,
[img['Name'] for img in json_output]
)
@@ -72,11 +69,11 @@ class ImageTests(base.TestCase):
'--min-ram 5 ' +
'--disk-format qcow2 ' +
'--public ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image show -f json ' +
- self.NAME
+ self.name
))
self.assertEqual(
4,
@@ -100,11 +97,11 @@ class ImageTests(base.TestCase):
'--property a=b ' +
'--property c=d ' +
'--public ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image show -f json ' +
- self.NAME
+ self.name
))
self.assertEqual(
"a='b', c='d'",
diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py
index 278ba5b9..3185c3bd 100644
--- a/openstackclient/tests/functional/image/v2/test_image.py
+++ b/openstackclient/tests/functional/image/v2/test_image.py
@@ -16,59 +16,55 @@ import uuid
import fixtures
# from glanceclient import exc as image_exceptions
-from openstackclient.tests.functional import base
+from openstackclient.tests.functional.image import base
-class ImageTests(base.TestCase):
- """Functional tests for image. """
+class ImageTests(base.BaseImageTests):
+ """Functional tests for Image commands"""
- NAME = uuid.uuid4().hex
- OTHER_NAME = uuid.uuid4().hex
+ def setUp(self):
+ super(ImageTests, self).setUp()
- @classmethod
- def setUpClass(cls):
- super(ImageTests, cls).setUpClass()
- json_output = json.loads(cls.openstack(
+ self.name = uuid.uuid4().hex
+ self.image_tag = 'my_tag'
+ json_output = json.loads(self.openstack(
'--os-image-api-version 2 '
- 'image create -f json ' +
- cls.NAME
+ 'image create -f json --tag {tag} {name}'.format(
+ tag=self.image_tag, name=self.name)
))
- cls.image_id = json_output["id"]
- cls.assertOutput(cls.NAME, json_output['name'])
+ self.image_id = json_output["id"]
+ self.assertOutput(self.name, json_output['name'])
+
+ ver_fixture = fixtures.EnvironmentVariable(
+ 'OS_IMAGE_API_VERSION', '2'
+ )
+ self.useFixture(ver_fixture)
- @classmethod
- def tearDownClass(cls):
+ def tearDown(self):
try:
- cls.openstack(
+ self.openstack(
'--os-image-api-version 2 '
'image delete ' +
- cls.image_id
+ self.image_id
)
finally:
- super(ImageTests, cls).tearDownClass()
-
- def setUp(self):
- super(ImageTests, self).setUp()
- ver_fixture = fixtures.EnvironmentVariable(
- 'OS_IMAGE_API_VERSION', '2'
- )
- self.useFixture(ver_fixture)
+ super(ImageTests, self).tearDown()
def test_image_list(self):
json_output = json.loads(self.openstack(
'image list -f json '
))
self.assertIn(
- self.NAME,
+ self.name,
[img['Name'] for img in json_output]
)
def test_image_list_with_name_filter(self):
json_output = json.loads(self.openstack(
- 'image list --name ' + self.NAME + ' -f json'
+ 'image list --name ' + self.name + ' -f json'
))
self.assertIn(
- self.NAME,
+ self.name,
[img['Name'] for img in json_output]
)
@@ -81,6 +77,16 @@ class ImageTests(base.TestCase):
[img['Status'] for img in json_output]
)
+ def test_image_list_with_tag_filter(self):
+ json_output = json.loads(self.openstack(
+ 'image list --tag ' + self.image_tag + ' --long -f json'
+ ))
+ for taglist in [img['Tags'].split(', ') for img in json_output]:
+ self.assertIn(
+ self.image_tag,
+ taglist
+ )
+
def test_image_attributes(self):
"""Test set, unset, show on attributes, tags and properties"""
@@ -90,11 +96,11 @@ class ImageTests(base.TestCase):
'--min-disk 4 ' +
'--min-ram 5 ' +
'--public ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image show -f json ' +
- self.NAME
+ self.name
))
self.assertEqual(
4,
@@ -115,59 +121,63 @@ class ImageTests(base.TestCase):
'--property a=b ' +
'--property c=d ' +
'--public ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image show -f json ' +
- self.NAME
+ self.name
))
- self.assertEqual(
- "a='b', c='d'",
- json_output["properties"],
- )
+ # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of
+ # new artributes in the returned data
+ self.assertIn("a='b'", json_output["properties"])
+ self.assertIn("c='d'", json_output["properties"])
self.openstack(
'image unset ' +
'--property a ' +
'--property c ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image show -f json ' +
- self.NAME
+ self.name
))
- self.assertNotIn(
- 'properties',
- json_output,
- )
+ # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of
+ # new artributes in the returned data
+ self.assertNotIn("a='b'", json_output["properties"])
+ self.assertNotIn("c='d'", json_output["properties"])
# Test tags
+ self.assertNotIn(
+ '01',
+ json_output["tags"].split(', ')
+ )
self.openstack(
'image set ' +
'--tag 01 ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image show -f json ' +
- self.NAME
+ self.name
))
- self.assertEqual(
+ self.assertIn(
'01',
- json_output["tags"],
+ json_output["tags"].split(', ')
)
self.openstack(
'image unset ' +
'--tag 01 ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image show -f json ' +
- self.NAME
+ self.name
))
- self.assertEqual(
- '',
- json_output["tags"],
+ self.assertNotIn(
+ '01',
+ json_output["tags"].split(', ')
)
def test_image_set_rename(self):
@@ -207,7 +217,7 @@ class ImageTests(base.TestCase):
json_output = json.loads(self.openstack(
'image show -f json ' +
- self.NAME
+ self.name
))
# NOTE(dtroyer): Until OSC supports --shared flags in create and set
# we can not properly test membership. Sometimes the
@@ -215,47 +225,47 @@ class ImageTests(base.TestCase):
if json_output["visibility"] == 'shared':
self.openstack(
'image add project ' +
- self.NAME + ' ' +
+ self.name + ' ' +
my_project_id
)
# self.addCleanup(
# self.openstack,
# 'image remove project ' +
- # self.NAME + ' ' +
+ # self.name + ' ' +
# my_project_id
# )
self.openstack(
'image set ' +
'--accept ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image list -f json ' +
'--shared'
))
self.assertIn(
- self.NAME,
+ self.name,
[img['Name'] for img in json_output]
)
self.openstack(
'image set ' +
'--reject ' +
- self.NAME
+ self.name
)
json_output = json.loads(self.openstack(
'image list -f json ' +
'--shared'
))
# self.assertNotIn(
- # self.NAME,
+ # self.name,
# [img['Name'] for img in json_output]
# )
self.openstack(
'image remove project ' +
- self.NAME + ' ' +
+ self.name + ' ' +
my_project_id
)
@@ -265,11 +275,11 @@ class ImageTests(base.TestCase):
# image_exceptions.HTTPForbidden,
# self.openstack,
# 'image add project ' +
- # self.NAME + ' ' +
+ # self.name + ' ' +
# my_project_id
# )
# self.openstack(
# 'image set ' +
# '--share ' +
- # self.NAME
+ # self.name
# )
diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py
index 40fb382a..9cef135f 100644
--- a/openstackclient/tests/functional/network/v2/test_network.py
+++ b/openstackclient/tests/functional/network/v2/test_network.py
@@ -209,7 +209,7 @@ class NetworkTests(common.NetworkTagTests):
cmd_output["description"],
)
- del_output = self.openstack('network delete ' + name1 + ' ' + name2)
+ del_output = self.openstack('network delete %s %s' % (name1, name2))
self.assertOutput('', del_output)
def test_network_list(self):
@@ -224,7 +224,7 @@ class NetworkTests(common.NetworkTagTests):
network_options +
name1
))
- self.addCleanup(self.openstack, 'network delete ' + name1)
+ self.addCleanup(self.openstack, 'network delete %s' % name1)
self.assertIsNotNone(cmd_output["id"])
if self.haz_network:
self.assertEqual(
@@ -264,10 +264,8 @@ class NetworkTests(common.NetworkTagTests):
else:
network_options = '--subnet 4.5.6.7/28 '
cmd_output = json.loads(self.openstack(
- 'network create -f json ' +
- '--share ' +
- network_options +
- name2
+ 'network create -f json --share %s%s' %
+ (network_options, name2)
))
self.addCleanup(self.openstack, 'network delete ' + name2)
self.assertIsNotNone(cmd_output["id"])
@@ -313,8 +311,7 @@ class NetworkTests(common.NetworkTagTests):
# Test list --long
if self.haz_network:
cmd_output = json.loads(self.openstack(
- "network list -f json " +
- "--long"
+ "network list -f json --long"
))
col_name = [x["Name"] for x in cmd_output]
self.assertIn(name1, col_name)
@@ -323,9 +320,7 @@ class NetworkTests(common.NetworkTagTests):
# Test list --long --enable
if self.haz_network:
cmd_output = json.loads(self.openstack(
- "network list -f json " +
- "--enable " +
- "--long"
+ "network list -f json --enable --long"
))
col_name = [x["Name"] for x in cmd_output]
self.assertIn(name1, col_name)
@@ -334,9 +329,7 @@ class NetworkTests(common.NetworkTagTests):
# Test list --long --disable
if self.haz_network:
cmd_output = json.loads(self.openstack(
- "network list -f json " +
- "--disable " +
- "--long"
+ "network list -f json --disable --long"
))
col_name = [x["Name"] for x in cmd_output]
self.assertNotIn(name1, col_name)
@@ -345,8 +338,7 @@ class NetworkTests(common.NetworkTagTests):
# Test list --share
if self.haz_network:
cmd_output = json.loads(self.openstack(
- "network list -f json " +
- "--share "
+ "network list -f json --share "
))
col_name = [x["Name"] for x in cmd_output]
self.assertNotIn(name1, col_name)
@@ -355,8 +347,7 @@ class NetworkTests(common.NetworkTagTests):
# Test list --no-share
if self.haz_network:
cmd_output = json.loads(self.openstack(
- "network list -f json " +
- "--no-share "
+ "network list -f json --no-share "
))
col_name = [x["Name"] for x in cmd_output]
self.assertIn(name1, col_name)
@@ -368,12 +359,10 @@ class NetworkTests(common.NetworkTagTests):
name1 = uuid.uuid4().hex
cmd_output1 = json.loads(self.openstack(
- 'network create -f json ' +
- '--description aaaa ' +
- name1
+ 'network create -f json --description aaaa %s' % name1
))
- self.addCleanup(self.openstack, 'network delete ' + name1)
+ self.addCleanup(self.openstack, 'network delete %s' % name1)
# Get network ID
network_id = cmd_output1['id']
@@ -386,20 +375,19 @@ class NetworkTests(common.NetworkTagTests):
# Add Agent to Network
self.openstack(
- 'network agent add network --dhcp '
- + agent_id + ' ' + network_id
+ 'network agent add network --dhcp %s %s' % (agent_id, network_id)
)
# Test network list --agent
cmd_output3 = json.loads(self.openstack(
- 'network list -f json --agent ' + agent_id
+ 'network list -f json --agent %s' % agent_id
))
# Cleanup
# Remove Agent from Network
self.openstack(
- 'network agent remove network --dhcp '
- + agent_id + ' ' + network_id
+ 'network agent remove network --dhcp %s %s' %
+ (agent_id, network_id)
)
# Assert
@@ -415,16 +403,16 @@ class NetworkTests(common.NetworkTagTests):
name = uuid.uuid4().hex
cmd_output = json.loads(self.openstack(
- 'network create -f json ' +
- '--description aaaa ' +
- '--enable ' +
- '--no-share ' +
- '--internal ' +
- '--no-default ' +
- '--enable-port-security ' +
+ 'network create -f json '
+ '--description aaaa '
+ '--enable '
+ '--no-share '
+ '--internal '
+ '--no-default '
+ '--enable-port-security %s' %
name
))
- self.addCleanup(self.openstack, 'network delete ' + name)
+ self.addCleanup(self.openstack, 'network delete %s' % name)
self.assertIsNotNone(cmd_output["id"])
self.assertEqual(
'aaaa',
@@ -453,12 +441,12 @@ class NetworkTests(common.NetworkTagTests):
)
raw_output = self.openstack(
- 'network set ' +
- '--description cccc ' +
- '--disable ' +
- '--share ' +
- '--external ' +
- '--disable-port-security ' +
+ 'network set '
+ '--description cccc '
+ '--disable '
+ '--share '
+ '--external '
+ '--disable-port-security %s' %
name
)
self.assertOutput('', raw_output)
diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py
index 0c74ea1d..86769e0c 100644
--- a/openstackclient/tests/functional/network/v2/test_network_agent.py
+++ b/openstackclient/tests/functional/network/v2/test_network_agent.py
@@ -42,8 +42,7 @@ class NetworkAgentTests(common.NetworkTests):
# agent show
cmd_output = json.loads(self.openstack(
- 'network agent show -f json ' +
- agent_ids[0]
+ 'network agent show -f json %s' % agent_ids[0]
))
self.assertEqual(
agent_ids[0],
@@ -52,15 +51,12 @@ class NetworkAgentTests(common.NetworkTests):
# agent set
raw_output = self.openstack(
- 'network agent set ' +
- '--disable ' +
- agent_ids[0]
+ 'network agent set --disable %s' % agent_ids[0]
)
self.assertOutput('', raw_output)
cmd_output = json.loads(self.openstack(
- 'network agent show -f json ' +
- agent_ids[0]
+ 'network agent show -f json %s' % agent_ids[0]
))
self.assertEqual(
"DOWN",
@@ -68,15 +64,12 @@ class NetworkAgentTests(common.NetworkTests):
)
raw_output = self.openstack(
- 'network agent set ' +
- '--enable ' +
- agent_ids[0]
+ 'network agent set --enable %s' % agent_ids[0]
)
self.assertOutput('', raw_output)
cmd_output = json.loads(self.openstack(
- 'network agent show -f json ' +
- agent_ids[0]
+ 'network agent show -f json %s' % agent_ids[0]
))
self.assertEqual(
"UP",
@@ -98,12 +91,10 @@ class NetworkAgentListTests(common.NetworkTests):
name1 = uuid.uuid4().hex
cmd_output1 = json.loads(self.openstack(
- 'network create -f json ' +
- '--description aaaa ' +
- name1
+ 'network create -f json --description aaaa %s' % name1
))
- self.addCleanup(self.openstack, 'network delete ' + name1)
+ self.addCleanup(self.openstack, 'network delete %s' % name1)
# Get network ID
network_id = cmd_output1['id']
@@ -116,20 +107,20 @@ class NetworkAgentListTests(common.NetworkTests):
# Add Agent to Network
self.openstack(
- 'network agent add network --dhcp '
- + agent_id + ' ' + network_id
+ 'network agent add network --dhcp %s %s' %
+ (agent_id, network_id)
)
# Test network agent list --network
cmd_output3 = json.loads(self.openstack(
- 'network agent list -f json --network ' + network_id
+ 'network agent list -f json --network %s' % network_id
))
# Cleanup
# Remove Agent from Network
self.openstack(
- 'network agent remove network --dhcp '
- + agent_id + ' ' + network_id
+ 'network agent remove network --dhcp %s %s' %
+ (agent_id, network_id)
)
# Assert
@@ -142,9 +133,9 @@ class NetworkAgentListTests(common.NetworkTests):
"""Add agent to router, list agents on router, delete."""
name = uuid.uuid4().hex
cmd_output = json.loads(self.openstack(
- 'router create -f json ' + name))
+ 'router create -f json %s' % name))
- self.addCleanup(self.openstack, 'router delete ' + name)
+ self.addCleanup(self.openstack, 'router delete %s' % name)
# Get router ID
router_id = cmd_output['id']
# Get l3 agent id
@@ -157,19 +148,19 @@ class NetworkAgentListTests(common.NetworkTests):
# Add router to agent
self.openstack(
- 'network agent add router --l3 ' + agent_id + ' ' + router_id)
+ 'network agent add router --l3 %s %s' % (agent_id, router_id))
# Test router list --agent
cmd_output = json.loads(self.openstack(
- 'network agent list -f json --router ' + router_id))
+ 'network agent list -f json --router %s' % router_id))
agent_ids = [x['ID'] for x in cmd_output]
self.assertIn(agent_id, agent_ids)
# Remove router from agent
self.openstack(
- 'network agent remove router --l3 ' + agent_id + ' ' + router_id)
+ 'network agent remove router --l3 %s %s' % (agent_id, router_id))
cmd_output = json.loads(self.openstack(
- 'network agent list -f json --router ' + router_id))
+ 'network agent list -f json --router %s' % router_id))
agent_ids = [x['ID'] for x in cmd_output]
self.assertNotIn(agent_id, agent_ids)
diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py
index 47e7b440..ba3de2cd 100644
--- a/openstackclient/tests/functional/network/v2/test_network_flavor.py
+++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py
@@ -39,13 +39,13 @@ class NetworkFlavorTests(common.NetworkTests):
# Create Service Flavor
cmd_output2 = json.loads(self.openstack(
'network flavor profile create -f json --description '
- + 'fakedescription' + ' --enable --metainfo ' + 'Extrainfo'
+ 'fakedescription --enable --metainfo Extrainfo'
))
service_profile_id = cmd_output2.get('id')
- self.addCleanup(self.openstack, 'network flavor delete ' +
+ self.addCleanup(self.openstack, 'network flavor delete %s' %
flavor_id)
- self.addCleanup(self.openstack, 'network flavor profile delete ' +
+ self.addCleanup(self.openstack, 'network flavor profile delete %s' %
service_profile_id)
# Add flavor to service profile
self.openstack(
diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py
index 770abe94..98e588e8 100644
--- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py
+++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py
@@ -28,62 +28,60 @@ class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests):
if not self.haz_network:
self.skipTest("No Network service present")
- self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex
+ self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex
self.openstack(
- 'network qos policy create ' +
- self.QOS_POLICY_NAME
+ 'network qos policy create %s' % self.QOS_POLICY_NAME
)
self.addCleanup(self.openstack,
- 'network qos policy delete ' + self.QOS_POLICY_NAME)
+ 'network qos policy delete %s' % self.QOS_POLICY_NAME)
cmd_output = json.loads(self.openstack(
- 'network qos rule create -f json ' +
- '--type minimum-bandwidth ' +
- '--min-kbps 2800 ' +
- '--egress ' +
+ 'network qos rule create -f json '
+ '--type minimum-bandwidth '
+ '--min-kbps 2800 '
+ '--egress %s' %
self.QOS_POLICY_NAME
))
self.RULE_ID = cmd_output['id']
self.addCleanup(self.openstack,
- 'network qos rule delete ' +
- self.QOS_POLICY_NAME + ' ' +
- self.RULE_ID)
+ 'network qos rule delete %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID))
self.assertTrue(self.RULE_ID)
def test_qos_rule_create_delete(self):
# This is to check the output of qos rule delete
policy_name = uuid.uuid4().hex
- self.openstack('network qos policy create -f json ' + policy_name)
+ self.openstack('network qos policy create -f json %s' % policy_name)
self.addCleanup(self.openstack,
- 'network qos policy delete ' + policy_name)
+ 'network qos policy delete %s' % policy_name)
rule = json.loads(self.openstack(
- 'network qos rule create -f json ' +
- '--type minimum-bandwidth ' +
- '--min-kbps 2800 ' +
- '--egress ' + policy_name
+ 'network qos rule create -f json '
+ '--type minimum-bandwidth '
+ '--min-kbps 2800 '
+ '--egress %s' % policy_name
))
raw_output = self.openstack(
- 'network qos rule delete ' +
- policy_name + ' ' + rule['id'])
+ 'network qos rule delete %s %s' %
+ (policy_name, rule['id']))
self.assertEqual('', raw_output)
def test_qos_rule_list(self):
cmd_output = json.loads(self.openstack(
- 'network qos rule list -f json ' + self.QOS_POLICY_NAME))
+ 'network qos rule list -f json %s' % self.QOS_POLICY_NAME))
self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output])
def test_qos_rule_show(self):
cmd_output = json.loads(self.openstack(
- 'network qos rule show -f json ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID))
+ 'network qos rule show -f json %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID)))
self.assertEqual(self.RULE_ID, cmd_output['id'])
def test_qos_rule_set(self):
- self.openstack('network qos rule set --min-kbps 7500 ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID)
+ self.openstack('network qos rule set --min-kbps 7500 %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID))
cmd_output = json.loads(self.openstack(
- 'network qos rule show -f json ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID))
+ 'network qos rule show -f json %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID)))
self.assertEqual(7500, cmd_output['min_kbps'])
@@ -96,58 +94,57 @@ class NetworkQosRuleTestsDSCPMarking(common.NetworkTests):
if not self.haz_network:
self.skipTest("No Network service present")
- self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex
+ self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex
self.openstack(
- 'network qos policy create ' +
- self.QOS_POLICY_NAME
+ 'network qos policy create %s' % self.QOS_POLICY_NAME
)
self.addCleanup(self.openstack,
- 'network qos policy delete ' + self.QOS_POLICY_NAME)
+ 'network qos policy delete %s' % self.QOS_POLICY_NAME)
cmd_output = json.loads(self.openstack(
- 'network qos rule create -f json ' +
- '--type dscp-marking ' +
- '--dscp-mark 8 ' +
+ 'network qos rule create -f json '
+ '--type dscp-marking '
+ '--dscp-mark 8 %s' %
self.QOS_POLICY_NAME
))
self.RULE_ID = cmd_output['id']
self.addCleanup(self.openstack,
- 'network qos rule delete ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID)
+ 'network qos rule delete %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID))
self.assertTrue(self.RULE_ID)
def test_qos_rule_create_delete(self):
# This is to check the output of qos rule delete
policy_name = uuid.uuid4().hex
- self.openstack('network qos policy create -f json ' + policy_name)
+ self.openstack('network qos policy create -f json %s' % policy_name)
self.addCleanup(self.openstack,
- 'network qos policy delete ' + policy_name)
+ 'network qos policy delete %s' % policy_name)
rule = json.loads(self.openstack(
- 'network qos rule create -f json ' +
- '--type dscp-marking ' +
- '--dscp-mark 8 ' + policy_name
+ 'network qos rule create -f json '
+ '--type dscp-marking '
+ '--dscp-mark 8 %s' % policy_name
))
raw_output = self.openstack(
- 'network qos rule delete ' +
- policy_name + ' ' + rule['id'])
+ 'network qos rule delete %s %s' %
+ (policy_name, rule['id']))
self.assertEqual('', raw_output)
def test_qos_rule_list(self):
cmd_output = json.loads(self.openstack(
- 'network qos rule list -f json ' + self.QOS_POLICY_NAME))
+ 'network qos rule list -f json %s' % self.QOS_POLICY_NAME))
self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output])
def test_qos_rule_show(self):
cmd_output = json.loads(self.openstack(
- 'network qos rule show -f json ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID))
+ 'network qos rule show -f json %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID)))
self.assertEqual(self.RULE_ID, cmd_output['id'])
def test_qos_rule_set(self):
- self.openstack('network qos rule set --dscp-mark 32 ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID)
+ self.openstack('network qos rule set --dscp-mark 32 %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID))
cmd_output = json.loads(self.openstack(
- 'network qos rule show -f json ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID))
+ 'network qos rule show -f json %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID)))
self.assertEqual(32, cmd_output['dscp_mark'])
@@ -160,65 +157,63 @@ class NetworkQosRuleTestsBandwidthLimit(common.NetworkTests):
if not self.haz_network:
self.skipTest("No Network service present")
- self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex
+ self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex
self.openstack(
- 'network qos policy create ' +
- self.QOS_POLICY_NAME
+ 'network qos policy create %s' % self.QOS_POLICY_NAME
)
self.addCleanup(self.openstack,
- 'network qos policy delete ' + self.QOS_POLICY_NAME)
+ 'network qos policy delete %s' % self.QOS_POLICY_NAME)
cmd_output = json.loads(self.openstack(
- 'network qos rule create -f json ' +
- '--type bandwidth-limit ' +
- '--max-kbps 10000 ' +
- '--max-burst-kbits 1400 ' +
- '--egress ' +
+ 'network qos rule create -f json '
+ '--type bandwidth-limit '
+ '--max-kbps 10000 '
+ '--egress %s' %
self.QOS_POLICY_NAME
))
self.RULE_ID = cmd_output['id']
self.addCleanup(self.openstack,
- 'network qos rule delete ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID)
+ 'network qos rule delete %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID))
self.assertTrue(self.RULE_ID)
def test_qos_rule_create_delete(self):
# This is to check the output of qos rule delete
policy_name = uuid.uuid4().hex
- self.openstack('network qos policy create -f json ' + policy_name)
+ self.openstack('network qos policy create -f json %s' % policy_name)
self.addCleanup(self.openstack,
- 'network qos policy delete ' + policy_name)
+ 'network qos policy delete %s' % policy_name)
rule = json.loads(self.openstack(
- 'network qos rule create -f json ' +
- '--type bandwidth-limit ' +
- '--max-kbps 10000 ' +
- '--max-burst-kbits 1400 ' +
- '--egress ' + policy_name
+ 'network qos rule create -f json '
+ '--type bandwidth-limit '
+ '--max-kbps 10000 '
+ '--max-burst-kbits 1400 '
+ '--egress %s' % policy_name
))
raw_output = self.openstack(
- 'network qos rule delete ' +
- policy_name + ' ' + rule['id'])
+ 'network qos rule delete %s %s' %
+ (policy_name, rule['id']))
self.assertEqual('', raw_output)
def test_qos_rule_list(self):
cmd_output = json.loads(self.openstack(
- 'network qos rule list -f json '
- + self.QOS_POLICY_NAME))
+ 'network qos rule list -f json %s' %
+ self.QOS_POLICY_NAME))
self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output])
def test_qos_rule_show(self):
cmd_output = json.loads(self.openstack(
- 'network qos rule show -f json ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID))
+ 'network qos rule show -f json %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID)))
self.assertEqual(self.RULE_ID, cmd_output['id'])
def test_qos_rule_set(self):
- self.openstack('network qos rule set --max-kbps 15000 ' +
- '--max-burst-kbits 1800 ' +
- '--ingress ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID)
+ self.openstack('network qos rule set --max-kbps 15000 '
+ '--max-burst-kbits 1800 '
+ '--ingress %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID))
cmd_output = json.loads(self.openstack(
- 'network qos rule show -f json ' +
- self.QOS_POLICY_NAME + ' ' + self.RULE_ID))
+ 'network qos rule show -f json %s %s' %
+ (self.QOS_POLICY_NAME, self.RULE_ID)))
self.assertEqual(15000, cmd_output['max_kbps'])
self.assertEqual(1800, cmd_output['max_burst_kbps'])
self.assertEqual('ingress', cmd_output['direction'])
diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py
index 8940273f..6ffb11cf 100644
--- a/openstackclient/tests/functional/network/v2/test_network_segment.py
+++ b/openstackclient/tests/functional/network/v2/test_network_segment.py
@@ -113,9 +113,20 @@ class NetworkSegmentTests(common.NetworkTests):
self.openstack,
'network segment delete ' + name
)
- self.assertIsNone(
- json_output["description"],
- )
+
+ extension_output = json.loads(self.openstack(
+ "extension list -f json "
+ ))
+ ext_alias = [x["Alias"] for x in extension_output]
+ if "standard-attr-segment" in ext_alias:
+ self.assertEqual(
+ '',
+ json_output["description"],
+ )
+ else:
+ self.assertIsNone(
+ json_output["description"],
+ )
new_description = 'new_description'
cmd_output = self.openstack(
diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py
index 7357c0ed..e3067d90 100644
--- a/openstackclient/tests/functional/network/v2/test_port.py
+++ b/openstackclient/tests/functional/network/v2/test_port.py
@@ -33,7 +33,7 @@ class PortTests(common.NetworkTagTests):
# Create a network for the port tests
cls.openstack(
- 'network create ' + cls.NETWORK_NAME
+ 'network create %s' % cls.NETWORK_NAME
)
@classmethod
@@ -41,7 +41,7 @@ class PortTests(common.NetworkTagTests):
try:
if cls.haz_network:
raw_output = cls.openstack(
- 'network delete ' + cls.NETWORK_NAME
+ 'network delete %s' % cls.NETWORK_NAME
)
cls.assertOutput('', raw_output)
finally:
@@ -56,8 +56,8 @@ class PortTests(common.NetworkTagTests):
def test_port_delete(self):
"""Test create, delete multiple"""
json_output = json.loads(self.openstack(
- 'port create -f json --network ' +
- self.NETWORK_NAME + ' ' + self.NAME
+ 'port create -f json --network %s %s' %
+ (self.NETWORK_NAME, self.NAME)
))
id1 = json_output.get('id')
self.assertIsNotNone(id1)
@@ -65,8 +65,8 @@ class PortTests(common.NetworkTagTests):
self.assertEqual(self.NAME, json_output.get('name'))
json_output = json.loads(self.openstack(
- 'port create -f json --network ' + self.NETWORK_NAME + ' ' +
- self.NAME + 'x'
+ 'port create -f json --network %s %sx' %
+ (self.NETWORK_NAME, self.NAME)
))
id2 = json_output.get('id')
self.assertIsNotNone(id2)
@@ -74,31 +74,31 @@ class PortTests(common.NetworkTagTests):
self.assertEqual(self.NAME + 'x', json_output.get('name'))
# Clean up after ourselves
- raw_output = self.openstack('port delete ' + id1 + ' ' + id2)
+ raw_output = self.openstack('port delete %s %s' % (id1, id2))
self.assertOutput('', raw_output)
def test_port_list(self):
"""Test create defaults, list, delete"""
json_output = json.loads(self.openstack(
- 'port create -f json --network ' + self.NETWORK_NAME + ' ' +
- self.NAME
+ 'port create -f json --network %s %s' %
+ (self.NETWORK_NAME, self.NAME)
))
id1 = json_output.get('id')
self.assertIsNotNone(id1)
mac1 = json_output.get('mac_address')
self.assertIsNotNone(mac1)
- self.addCleanup(self.openstack, 'port delete ' + id1)
+ self.addCleanup(self.openstack, 'port delete %s' % id1)
self.assertEqual(self.NAME, json_output.get('name'))
json_output = json.loads(self.openstack(
- 'port create -f json --network ' + self.NETWORK_NAME + ' ' +
- self.NAME + 'x'
+ 'port create -f json --network %s %sx' %
+ (self.NETWORK_NAME, self.NAME)
))
id2 = json_output.get('id')
self.assertIsNotNone(id2)
mac2 = json_output.get('mac_address')
self.assertIsNotNone(mac2)
- self.addCleanup(self.openstack, 'port delete ' + id2)
+ self.addCleanup(self.openstack, 'port delete %s' % id2)
self.assertEqual(self.NAME + 'x', json_output.get('name'))
# Test list
@@ -122,7 +122,7 @@ class PortTests(common.NetworkTagTests):
# Test list --mac-address
json_output = json.loads(self.openstack(
- 'port list -f json --mac-address ' + mac2
+ 'port list -f json --mac-address %s' % mac2
))
item_map = {item.get('ID'): item.get('MAC Address') for item in
json_output}
@@ -145,26 +145,26 @@ class PortTests(common.NetworkTagTests):
"""Test create, set, show, delete"""
name = uuid.uuid4().hex
json_output = json.loads(self.openstack(
- 'port create -f json ' +
- '--network ' + self.NETWORK_NAME + ' ' +
- '--description xyzpdq ' +
- '--disable ' +
- name
+ 'port create -f json '
+ '--network %s '
+ '--description xyzpdq '
+ '--disable %s' %
+ (self.NETWORK_NAME, name)
))
id1 = json_output.get('id')
- self.addCleanup(self.openstack, 'port delete ' + id1)
+ self.addCleanup(self.openstack, 'port delete %s' % id1)
self.assertEqual(name, json_output.get('name'))
self.assertEqual('xyzpdq', json_output.get('description'))
self.assertEqual('DOWN', json_output.get('admin_state_up'))
raw_output = self.openstack(
- 'port set ' + '--enable ' +
+ 'port set --enable %s' %
name
)
self.assertOutput('', raw_output)
json_output = json.loads(self.openstack(
- 'port show -f json ' + name
+ 'port show -f json %s' % name
))
sg_id = json_output.get('security_group_ids')
@@ -174,30 +174,30 @@ class PortTests(common.NetworkTagTests):
self.assertIsNotNone(json_output.get('mac_address'))
raw_output = self.openstack(
- 'port unset --security-group ' + sg_id + ' ' + id1)
+ 'port unset --security-group %s %s' % (sg_id, id1))
self.assertOutput('', raw_output)
json_output = json.loads(self.openstack(
- 'port show -f json ' + name
+ 'port show -f json %s' % name
))
self.assertEqual('', json_output.get('security_group_ids'))
def test_port_admin_set(self):
"""Test create, set (as admin), show, delete"""
json_output = json.loads(self.openstack(
- 'port create -f json ' +
- '--network ' + self.NETWORK_NAME + ' ' + self.NAME
+ 'port create -f json '
+ '--network %s %s' % (self.NETWORK_NAME, self.NAME)
))
id_ = json_output.get('id')
- self.addCleanup(self.openstack, 'port delete ' + id_)
+ self.addCleanup(self.openstack, 'port delete %s' % id_)
raw_output = self.openstack(
'--os-username admin '
- + 'port set --mac-address 11:22:33:44:55:66 '
- + self.NAME)
+ 'port set --mac-address 11:22:33:44:55:66 %s' %
+ self.NAME)
self.assertOutput('', raw_output)
json_output = json.loads(self.openstack(
- 'port show -f json ' + self.NAME
+ 'port show -f json %s' % self.NAME
))
self.assertEqual(json_output.get('mac_address'), '11:22:33:44:55:66')
@@ -205,41 +205,41 @@ class PortTests(common.NetworkTagTests):
"""Test create, set, show, delete"""
sg_name1 = uuid.uuid4().hex
json_output = json.loads(self.openstack(
- 'security group create -f json ' +
+ 'security group create -f json %s' %
sg_name1
))
sg_id1 = json_output.get('id')
- self.addCleanup(self.openstack, 'security group delete ' + sg_id1)
+ self.addCleanup(self.openstack, 'security group delete %s' % sg_id1)
sg_name2 = uuid.uuid4().hex
json_output = json.loads(self.openstack(
- 'security group create -f json ' +
+ 'security group create -f json %s' %
sg_name2
))
sg_id2 = json_output.get('id')
- self.addCleanup(self.openstack, 'security group delete ' + sg_id2)
+ self.addCleanup(self.openstack, 'security group delete %s' % sg_id2)
name = uuid.uuid4().hex
json_output = json.loads(self.openstack(
- 'port create -f json ' +
- '--network ' + self.NETWORK_NAME + ' ' +
- '--security-group ' + sg_name1 + ' ' +
- name
+ 'port create -f json '
+ '--network %s '
+ '--security-group %s %s' %
+ (self.NETWORK_NAME, sg_name1, name)
))
id1 = json_output.get('id')
- self.addCleanup(self.openstack, 'port delete ' + id1)
+ self.addCleanup(self.openstack, 'port delete %s' % id1)
self.assertEqual(name, json_output.get('name'))
self.assertEqual(sg_id1, json_output.get('security_group_ids'))
raw_output = self.openstack(
- 'port set ' +
- '--security-group ' + sg_name2 + ' ' +
- name
+ 'port set '
+ '--security-group %s %s' %
+ (sg_name2, name)
)
self.assertOutput('', raw_output)
json_output = json.loads(self.openstack(
- 'port show -f json ' + name
+ 'port show -f json %s' % name
))
self.assertEqual(name, json_output.get('name'))
self.assertIn(
@@ -254,11 +254,11 @@ class PortTests(common.NetworkTagTests):
)
raw_output = self.openstack(
- 'port unset --security-group ' + sg_id1 + ' ' + id1)
+ 'port unset --security-group %s %s' % (sg_id1, id1))
self.assertOutput('', raw_output)
json_output = json.loads(self.openstack(
- 'port show -f json ' + name
+ 'port show -f json %s' % name
))
self.assertEqual(
# TODO(dtroyer): output formatters should do this on JSON!
diff --git a/openstackclient/tests/functional/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py
index d1a73c54..226ef8ad 100644
--- a/openstackclient/tests/functional/object/v1/test_object.py
+++ b/openstackclient/tests/functional/object/v1/test_object.py
@@ -53,39 +53,39 @@ class ObjectTests(common.ObjectStoreTests):
self.openstack('container save ' + self.CONTAINER_NAME)
# TODO(stevemar): Assert returned fields
- raw_output = self.openstack('object create ' + self.CONTAINER_NAME
- + ' ' + object_file)
+ raw_output = self.openstack('object create %s %s' %
+ (self.CONTAINER_NAME, object_file))
items = self.parse_listing(raw_output)
self.assert_show_fields(items, OBJECT_FIELDS)
- raw_output = self.openstack('object list ' + self.CONTAINER_NAME)
+ raw_output = self.openstack('object list %s' % self.CONTAINER_NAME)
items = self.parse_listing(raw_output)
self.assert_table_structure(items, BASIC_LIST_HEADERS)
- self.openstack('object save ' + self.CONTAINER_NAME
- + ' ' + object_file)
+ self.openstack('object save %s %s' %
+ (self.CONTAINER_NAME, object_file))
# TODO(stevemar): Assert returned fields
tmp_file = 'tmp.txt'
self.addCleanup(os.remove, tmp_file)
- self.openstack('object save ' + self.CONTAINER_NAME
- + ' ' + object_file + ' --file ' + tmp_file)
+ self.openstack('object save %s %s --file %s' %
+ (self.CONTAINER_NAME, object_file, tmp_file))
# TODO(stevemar): Assert returned fields
- raw_output = self.openstack('object save ' + self.CONTAINER_NAME
- + ' ' + object_file + ' --file -')
+ raw_output = self.openstack('object save %s %s --file -' %
+ (self.CONTAINER_NAME, object_file))
self.assertEqual(raw_output, 'test content')
- self.openstack('object show ' + self.CONTAINER_NAME
- + ' ' + object_file)
+ self.openstack('object show %s %s' %
+ (self.CONTAINER_NAME, object_file))
# TODO(stevemar): Assert returned fields
- raw_output = self.openstack('object delete ' + self.CONTAINER_NAME
- + ' ' + object_file)
+ raw_output = self.openstack('object delete %s %s' %
+ (self.CONTAINER_NAME, object_file))
self.assertEqual(0, len(raw_output))
- self.openstack('object create ' + self.CONTAINER_NAME
- + ' ' + object_file)
- raw_output = self.openstack('container delete -r ' +
+ self.openstack('object create %s %s' %
+ (self.CONTAINER_NAME, object_file))
+ raw_output = self.openstack('container delete -r %s' %
self.CONTAINER_NAME)
self.assertEqual(0, len(raw_output))
diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py
index 73191fc9..0399e6cc 100644
--- a/openstackclient/tests/functional/volume/v1/test_transfer_request.py
+++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py
@@ -29,20 +29,13 @@ class TransferRequestTests(common.BaseVolumeTests):
'volume create -f json --size 1 ' + cls.VOLUME_NAME))
cls.assertOutput(cls.VOLUME_NAME, cmd_output['name'])
- cmd_output = json.loads(cls.openstack(
- 'volume transfer request create -f json ' +
- cls.VOLUME_NAME +
- ' --name ' + cls.NAME))
- cls.assertOutput(cls.NAME, cmd_output['name'])
+ cls.wait_for_status("volume", cls.VOLUME_NAME, "available")
@classmethod
def tearDownClass(cls):
try:
- raw_output_transfer = cls.openstack(
- 'volume transfer request delete ' + cls.NAME)
raw_output_volume = cls.openstack(
'volume delete ' + cls.VOLUME_NAME)
- cls.assertOutput('', raw_output_transfer)
cls.assertOutput('', raw_output_volume)
finally:
super(TransferRequestTests, cls).tearDownClass()
@@ -79,12 +72,28 @@ class TransferRequestTests(common.BaseVolumeTests):
'volume delete ' + volume_name)
self.assertEqual('', raw_output)
- def test_volume_transfer_request_list(self):
+ def test_volume_transfer_request_list_show(self):
+ name = uuid.uuid4().hex
cmd_output = json.loads(self.openstack(
- 'volume transfer request list -f json'))
- self.assertIn(self.NAME, [req['Name'] for req in cmd_output])
+ 'volume transfer request create -f json ' +
+ ' --name ' + name + ' ' +
+ self.VOLUME_NAME
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume transfer request delete ' + name
+ )
+ self.assertOutput(name, cmd_output['name'])
+ auth_key = cmd_output['auth_key']
+ self.assertTrue(auth_key)
- def test_volume_transfer_request_show(self):
cmd_output = json.loads(self.openstack(
- 'volume transfer request show -f json ' + self.NAME))
- self.assertEqual(self.NAME, cmd_output['name'])
+ 'volume transfer request list -f json'
+ ))
+ self.assertIn(name, [req['Name'] for req in cmd_output])
+
+ cmd_output = json.loads(self.openstack(
+ 'volume transfer request show -f json ' +
+ name
+ ))
+ self.assertEqual(name, cmd_output['name'])
diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py
index 74e14070..eb9d7f64 100644
--- a/openstackclient/tests/functional/volume/v1/test_volume_type.py
+++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py
@@ -20,70 +20,100 @@ from openstackclient.tests.functional.volume.v1 import common
class VolumeTypeTests(common.BaseVolumeTests):
"""Functional tests for volume type. """
- NAME = uuid.uuid4().hex
-
- @classmethod
- def setUpClass(cls):
- super(VolumeTypeTests, cls).setUpClass()
- cmd_output = json.loads(cls.openstack(
- 'volume type create -f json ' + cls.NAME))
- cls.assertOutput(cls.NAME, cmd_output['name'])
-
- @classmethod
- def tearDownClass(cls):
- try:
- raw_output = cls.openstack('volume type delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
- finally:
- super(VolumeTypeTests, cls).tearDownClass()
-
- def test_volume_type_list(self):
+ def test_volume_type_create_list(self):
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' +
+ name,
+ )
+ self.assertEqual(name, cmd_output['name'])
+
+ cmd_output = json.loads(self.openstack(
+ 'volume type show -f json %s' % name
+ ))
+ self.assertEqual(self.NAME, cmd_output['name'])
+
cmd_output = json.loads(self.openstack('volume type list -f json'))
self.assertIn(self.NAME, [t['Name'] for t in cmd_output])
- def test_volume_type_show(self):
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
- self.assertEqual(self.NAME, cmd_output['name'])
+ 'volume type list -f json --default'
+ ))
+ self.assertEqual(1, len(cmd_output))
+ self.assertEqual('lvmdriver-1', cmd_output[0]['Name'])
def test_volume_type_set_unset_properties(self):
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' + name
+ )
+ self.assertEqual(name, cmd_output['name'])
+
raw_output = self.openstack(
- 'volume type set --property a=b --property c=d ' + self.NAME)
+ 'volume type set --property a=b --property c=d %s' % name
+ )
self.assertEqual("", raw_output)
-
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
+ 'volume type show -f json %s' % name
+ ))
+ # TODO(amotoki): properties output should be machine-readable
self.assertEqual("a='b', c='d'", cmd_output['properties'])
- raw_output = self.openstack('volume type unset --property a '
- + self.NAME)
+ raw_output = self.openstack(
+ 'volume type unset --property a %s' % name
+ )
self.assertEqual("", raw_output)
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
+ 'volume type show -f json %s' % name
+ ))
self.assertEqual("c='d'", cmd_output['properties'])
def test_volume_type_set_unset_multiple_properties(self):
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' + name
+ )
+ self.assertEqual(name, cmd_output['name'])
+
raw_output = self.openstack(
- 'volume type set --property a=b --property c=d ' + self.NAME)
+ 'volume type set --property a=b --property c=d %s' % name
+ )
self.assertEqual("", raw_output)
-
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
+ 'volume type show -f json %s' % name
+ ))
self.assertEqual("a='b', c='d'", cmd_output['properties'])
raw_output = self.openstack(
- 'volume type unset --property a --property c ' + self.NAME)
+ 'volume type unset --property a --property c %s' % name
+ )
self.assertEqual("", raw_output)
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
+ 'volume type show -f json %s' % name
+ ))
self.assertEqual("", cmd_output['properties'])
def test_multi_delete(self):
vol_type1 = uuid.uuid4().hex
vol_type2 = uuid.uuid4().hex
- self.openstack('volume type create ' + vol_type1)
+ self.openstack('volume type create %s' % vol_type1)
time.sleep(5)
- self.openstack('volume type create ' + vol_type2)
+ self.openstack('volume type create %s' % vol_type2)
time.sleep(5)
cmd = 'volume type delete %s %s' % (vol_type1, vol_type2)
raw_output = self.openstack(cmd)
@@ -140,8 +170,21 @@ class VolumeTypeTests(common.BaseVolumeTests):
'--encryption-control-location front-end ' +
self.NAME)
self.assertEqual('', raw_output)
+
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' + name,
+ )
+ self.assertEqual(name, cmd_output['name'])
+
cmd_output = json.loads(self.openstack(
- 'volume type show -f json --encryption-type ' + self.NAME))
+ 'volume type show -f json --encryption-type ' + name
+ ))
expected = ["provider='LuksEncryptor'",
"cipher='aes-xts-plain64'",
"key_size='128'",
@@ -150,10 +193,12 @@ class VolumeTypeTests(common.BaseVolumeTests):
self.assertIn(attr, cmd_output['encryption'])
# test unset encryption type
raw_output = self.openstack(
- 'volume type unset --encryption-type ' + self.NAME)
+ 'volume type unset --encryption-type ' + name
+ )
self.assertEqual('', raw_output)
cmd_output = json.loads(self.openstack(
- 'volume type show -f json --encryption-type ' + self.NAME))
+ 'volume type show -f json --encryption-type ' + name
+ ))
self.assertEqual('', cmd_output['encryption'])
# test delete encryption type
raw_output = self.openstack('volume type delete ' + encryption_type)
diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py
index 888f12b1..646becc1 100644
--- a/openstackclient/tests/functional/volume/v2/test_qos.py
+++ b/openstackclient/tests/functional/volume/v2/test_qos.py
@@ -125,7 +125,6 @@ class QosTests(common.BaseVolumeTests):
def test_volume_qos_asso_disasso(self):
"""Tests associate and disassociate qos with volume type"""
vol_type1 = uuid.uuid4().hex
- vol_type2 = uuid.uuid4().hex
cmd_output = json.loads(self.openstack(
'volume type create -f json ' +
vol_type1
@@ -134,6 +133,9 @@ class QosTests(common.BaseVolumeTests):
vol_type1,
cmd_output['name']
)
+ self.addCleanup(self.openstack, 'volume type delete ' + vol_type1)
+
+ vol_type2 = uuid.uuid4().hex
cmd_output = json.loads(self.openstack(
'volume type create -f json ' +
vol_type2
@@ -142,7 +144,6 @@ class QosTests(common.BaseVolumeTests):
vol_type2,
cmd_output['name']
)
- self.addCleanup(self.openstack, 'volume type delete ' + vol_type1)
self.addCleanup(self.openstack, 'volume type delete ' + vol_type2)
name = uuid.uuid4().hex
diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py
index 33495af6..33d8ce77 100644
--- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py
+++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py
@@ -21,29 +21,24 @@ class TransferRequestTests(common.BaseVolumeTests):
NAME = uuid.uuid4().hex
VOLUME_NAME = uuid.uuid4().hex
+ API_VERSION = '2'
@classmethod
def setUpClass(cls):
super(TransferRequestTests, cls).setUpClass()
cmd_output = json.loads(cls.openstack(
+ '--os-volume-api-version ' + cls.API_VERSION + ' ' +
'volume create -f json --size 1 ' + cls.VOLUME_NAME))
cls.assertOutput(cls.VOLUME_NAME, cmd_output['name'])
- cmd_output = json.loads(cls.openstack(
- 'volume transfer request create -f json ' +
- cls.VOLUME_NAME +
- ' --name ' + cls.NAME))
- cls.assertOutput(cls.NAME, cmd_output['name'])
+ cls.wait_for_status("volume", cls.VOLUME_NAME, "available")
@classmethod
def tearDownClass(cls):
try:
- raw_output_transfer = cls.openstack(
- 'volume transfer request delete ' + cls.NAME)
raw_output_volume = cls.openstack(
'volume delete ' + cls.VOLUME_NAME)
- cls.assertOutput('', raw_output_transfer)
cls.assertOutput('', raw_output_volume)
finally:
super(TransferRequestTests, cls).tearDownClass()
@@ -80,12 +75,31 @@ class TransferRequestTests(common.BaseVolumeTests):
'volume delete ' + volume_name)
self.assertEqual('', raw_output)
- def test_volume_transfer_request_list(self):
+ def test_volume_transfer_request_list_show(self):
+ name = uuid.uuid4().hex
cmd_output = json.loads(self.openstack(
- 'volume transfer request list -f json'))
- self.assertIn(self.NAME, [req['Name'] for req in cmd_output])
+ '--os-volume-api-version ' + self.API_VERSION + ' ' +
+ 'volume transfer request create -f json ' +
+ ' --name ' + name + ' ' +
+ self.VOLUME_NAME
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume transfer request delete ' + name
+ )
+ self.assertEqual(name, cmd_output['name'])
+ auth_key = cmd_output['auth_key']
+ self.assertTrue(auth_key)
+
+ cmd_output = json.loads(self.openstack(
+ '--os-volume-api-version ' + self.API_VERSION + ' ' +
+ 'volume transfer request list -f json'
+ ))
+ self.assertIn(name, [req['Name'] for req in cmd_output])
- def test_volume_transfer_request_show(self):
cmd_output = json.loads(self.openstack(
- 'volume transfer request show -f json ' + self.NAME))
- self.assertEqual(self.NAME, cmd_output['name'])
+ '--os-volume-api-version ' + self.API_VERSION + ' ' +
+ 'volume transfer request show -f json ' +
+ name
+ ))
+ self.assertEqual(name, cmd_output['name'])
diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py
index 99630e6b..d8dd5bd6 100644
--- a/openstackclient/tests/functional/volume/v2/test_volume_type.py
+++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py
@@ -20,84 +20,121 @@ from openstackclient.tests.functional.volume.v2 import common
class VolumeTypeTests(common.BaseVolumeTests):
"""Functional tests for volume type. """
- NAME = uuid.uuid4().hex
-
- @classmethod
- def setUpClass(cls):
- super(VolumeTypeTests, cls).setUpClass()
- cmd_output = json.loads(cls.openstack(
- 'volume type create -f json --private ' + cls.NAME))
- cls.assertOutput(cls.NAME, cmd_output['name'])
-
- @classmethod
- def tearDownClass(cls):
- try:
- raw_output = cls.openstack('volume type delete ' + cls.NAME)
- cls.assertOutput('', raw_output)
- finally:
- super(VolumeTypeTests, cls).tearDownClass()
-
- def test_volume_type_list(self):
+ def test_volume_type_create_list(self):
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' + name,
+ )
+ self.assertEqual(name, cmd_output['name'])
+
+ cmd_output = json.loads(self.openstack(
+ 'volume type show -f json %s' % name
+ ))
+ self.assertEqual(name, cmd_output['name'])
+
cmd_output = json.loads(self.openstack('volume type list -f json'))
- self.assertIn(self.NAME, [t['Name'] for t in cmd_output])
+ self.assertIn(name, [t['Name'] for t in cmd_output])
- def test_volume_type_list_default(self):
cmd_output = json.loads(self.openstack(
- 'volume type list -f json --default'))
+ 'volume type list -f json --default'
+ ))
self.assertEqual(1, len(cmd_output))
self.assertEqual('lvmdriver-1', cmd_output[0]['Name'])
- def test_volume_type_show(self):
+ def test_volume_type_set_unset_properties(self):
+ name = uuid.uuid4().hex
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
- self.assertEqual(self.NAME, cmd_output['name'])
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' + name
+ )
+ self.assertEqual(name, cmd_output['name'])
- def test_volume_type_set_unset_properties(self):
raw_output = self.openstack(
- 'volume type set --property a=b --property c=d ' + self.NAME)
+ 'volume type set --property a=b --property c=d %s' % name
+ )
self.assertEqual("", raw_output)
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
+ 'volume type show -f json %s' % name
+ ))
# TODO(amotoki): properties output should be machine-readable
self.assertEqual("a='b', c='d'", cmd_output['properties'])
- raw_output = self.openstack('volume type unset --property a '
- + self.NAME)
+ raw_output = self.openstack(
+ 'volume type unset --property a %s' % name
+ )
self.assertEqual("", raw_output)
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
+ 'volume type show -f json %s' % name
+ ))
self.assertEqual("c='d'", cmd_output['properties'])
def test_volume_type_set_unset_multiple_properties(self):
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' + name
+ )
+ self.assertEqual(name, cmd_output['name'])
+
raw_output = self.openstack(
- 'volume type set --property a=b --property c=d ' + self.NAME)
+ 'volume type set --property a=b --property c=d %s' % name
+ )
self.assertEqual("", raw_output)
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
+ 'volume type show -f json %s' % name
+ ))
self.assertEqual("a='b', c='d'", cmd_output['properties'])
raw_output = self.openstack(
- 'volume type unset --property a --property c ' + self.NAME)
+ 'volume type unset --property a --property c %s' % name
+ )
self.assertEqual("", raw_output)
cmd_output = json.loads(self.openstack(
- 'volume type show -f json ' + self.NAME))
+ 'volume type show -f json %s' % name
+ ))
self.assertEqual("", cmd_output['properties'])
def test_volume_type_set_unset_project(self):
+ name = uuid.uuid4().hex
+ cmd_output = json.loads(self.openstack(
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' + name
+ )
+ self.assertEqual(name, cmd_output['name'])
+
raw_output = self.openstack(
- 'volume type set --project admin ' + self.NAME)
+ 'volume type set --project admin %s' % name
+ )
self.assertEqual("", raw_output)
raw_output = self.openstack(
- 'volume type unset --project admin ' + self.NAME)
+ 'volume type unset --project admin %s' % name
+ )
self.assertEqual("", raw_output)
def test_multi_delete(self):
vol_type1 = uuid.uuid4().hex
vol_type2 = uuid.uuid4().hex
- self.openstack('volume type create ' + vol_type1)
+ self.openstack('volume type create %s' % vol_type1)
time.sleep(5)
- self.openstack('volume type create ' + vol_type2)
+ self.openstack('volume type create %s' % vol_type2)
time.sleep(5)
cmd = 'volume type delete %s %s' % (vol_type1, vol_type2)
raw_output = self.openstack(cmd)
@@ -108,6 +145,7 @@ class VolumeTypeTests(common.BaseVolumeTests):
# these to new test format when beef up all tests for
# volume tye commands.
def test_encryption_type(self):
+ name = uuid.uuid4().hex
encryption_type = uuid.uuid4().hex
# test create new encryption type
cmd_output = json.loads(self.openstack(
@@ -162,16 +200,28 @@ class VolumeTypeTests(common.BaseVolumeTests):
for attr in expected:
self.assertIn(attr, cmd_output['encryption'])
# test set new encryption type
+ cmd_output = json.loads(self.openstack(
+ 'volume type create -f json --private ' +
+ name,
+ ))
+ self.addCleanup(
+ self.openstack,
+ 'volume type delete ' + name,
+ )
+ self.assertEqual(name, cmd_output['name'])
+
raw_output = self.openstack(
'volume type set '
'--encryption-provider LuksEncryptor '
'--encryption-cipher aes-xts-plain64 '
'--encryption-key-size 128 '
'--encryption-control-location front-end ' +
- self.NAME)
+ name)
self.assertEqual('', raw_output)
+
cmd_output = json.loads(self.openstack(
- 'volume type show -f json --encryption-type ' + self.NAME))
+ 'volume type show -f json --encryption-type ' + name
+ ))
expected = ["provider='LuksEncryptor'",
"cipher='aes-xts-plain64'",
"key_size='128'",
@@ -180,10 +230,12 @@ class VolumeTypeTests(common.BaseVolumeTests):
self.assertIn(attr, cmd_output['encryption'])
# test unset encryption type
raw_output = self.openstack(
- 'volume type unset --encryption-type ' + self.NAME)
+ 'volume type unset --encryption-type ' + name
+ )
self.assertEqual('', raw_output)
cmd_output = json.loads(self.openstack(
- 'volume type show -f json --encryption-type ' + self.NAME))
+ 'volume type show -f json --encryption-type ' + name
+ ))
self.assertEqual('', cmd_output['encryption'])
# test delete encryption type
raw_output = self.openstack('volume type delete ' + encryption_type)
diff --git a/openstackclient/tests/functional/volume/v3/test_transfer_request.py b/openstackclient/tests/functional/volume/v3/test_transfer_request.py
index b3253237..f16dfafa 100644
--- a/openstackclient/tests/functional/volume/v3/test_transfer_request.py
+++ b/openstackclient/tests/functional/volume/v3/test_transfer_request.py
@@ -17,3 +17,5 @@ from openstackclient.tests.functional.volume.v3 import common
class TransferRequestTests(common.BaseVolumeTests, v2.TransferRequestTests):
"""Functional tests for transfer request. """
+
+ API_VERSION = '3'
diff --git a/openstackclient/tests/unit/common/test_limits.py b/openstackclient/tests/unit/common/test_limits.py
new file mode 100644
index 00000000..d73db2cb
--- /dev/null
+++ b/openstackclient/tests/unit/common/test_limits.py
@@ -0,0 +1,125 @@
+# 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.
+#
+
+from openstackclient.common import limits
+from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
+
+
+class TestComputeLimits(compute_fakes.TestComputev2):
+
+ absolute_columns = [
+ 'Name',
+ 'Value',
+ ]
+
+ rate_columns = [
+ "Verb",
+ "URI",
+ "Value",
+ "Remain",
+ "Unit",
+ "Next Available"
+ ]
+
+ def setUp(self):
+ super(TestComputeLimits, self).setUp()
+ self.app.client_manager.volume_endpoint_enabled = False
+ self.compute = self.app.client_manager.compute
+
+ self.fake_limits = compute_fakes.FakeLimits()
+ self.compute.limits.get.return_value = self.fake_limits
+
+ def test_compute_show_absolute(self):
+ arglist = ['--absolute']
+ verifylist = [('is_absolute', True)]
+ cmd = limits.ShowLimits(self.app, None)
+ parsed_args = self.check_parser(cmd, arglist, verifylist)
+
+ columns, data = cmd.take_action(parsed_args)
+
+ ret_limits = list(data)
+ compute_reference_limits = self.fake_limits.absolute_limits()
+
+ self.assertEqual(self.absolute_columns, columns)
+ self.assertEqual(compute_reference_limits, ret_limits)
+ self.assertEqual(19, len(ret_limits))
+
+ def test_compute_show_rate(self):
+ arglist = ['--rate']
+ verifylist = [('is_rate', True)]
+ cmd = limits.ShowLimits(self.app, None)
+ parsed_args = self.check_parser(cmd, arglist, verifylist)
+
+ columns, data = cmd.take_action(parsed_args)
+
+ ret_limits = list(data)
+ compute_reference_limits = self.fake_limits.rate_limits()
+
+ self.assertEqual(self.rate_columns, columns)
+ self.assertEqual(compute_reference_limits, ret_limits)
+ self.assertEqual(3, len(ret_limits))
+
+
+class TestVolumeLimits(volume_fakes.TestVolume):
+ absolute_columns = [
+ 'Name',
+ 'Value',
+ ]
+
+ rate_columns = [
+ "Verb",
+ "URI",
+ "Value",
+ "Remain",
+ "Unit",
+ "Next Available"
+ ]
+
+ def setUp(self):
+ super(TestVolumeLimits, self).setUp()
+ self.app.client_manager.compute_endpoint_enabled = False
+ self.volume = self.app.client_manager.volume
+
+ self.fake_limits = volume_fakes.FakeLimits()
+ self.volume.limits.get.return_value = self.fake_limits
+
+ def test_volume_show_absolute(self):
+ arglist = ['--absolute']
+ verifylist = [('is_absolute', True)]
+ cmd = limits.ShowLimits(self.app, None)
+ parsed_args = self.check_parser(cmd, arglist, verifylist)
+
+ columns, data = cmd.take_action(parsed_args)
+
+ ret_limits = list(data)
+ compute_reference_limits = self.fake_limits.absolute_limits()
+
+ self.assertEqual(self.absolute_columns, columns)
+ self.assertEqual(compute_reference_limits, ret_limits)
+ self.assertEqual(10, len(ret_limits))
+
+ def test_volume_show_rate(self):
+ arglist = ['--rate']
+ verifylist = [('is_rate', True)]
+ cmd = limits.ShowLimits(self.app, None)
+ parsed_args = self.check_parser(cmd, arglist, verifylist)
+
+ columns, data = cmd.take_action(parsed_args)
+
+ ret_limits = list(data)
+ compute_reference_limits = self.fake_limits.rate_limits()
+
+ self.assertEqual(self.rate_columns, columns)
+ self.assertEqual(compute_reference_limits, ret_limits)
+ self.assertEqual(3, len(ret_limits))
diff --git a/openstackclient/tests/unit/common/test_project_purge.py b/openstackclient/tests/unit/common/test_project_purge.py
index 2385eae8..6e8ce188 100644
--- a/openstackclient/tests/unit/common/test_project_purge.py
+++ b/openstackclient/tests/unit/common/test_project_purge.py
@@ -117,10 +117,11 @@ class TestProjectPurge(TestProjectPurgeInit):
self.projects_mock.get.assert_called_once_with(self.project.id)
self.projects_mock.delete.assert_called_once_with(self.project.id)
self.servers_mock.list.assert_called_once_with(
- search_opts={'tenant_id': self.project.id})
+ search_opts={'tenant_id': self.project.id, 'all_tenants': True})
kwargs = {'filters': {'owner': self.project.id}}
self.images_mock.list.assert_called_once_with(**kwargs)
- volume_search_opts = {'project_id': self.project.id}
+ volume_search_opts = {'project_id': self.project.id,
+ 'all_tenants': True}
self.volumes_mock.list.assert_called_once_with(
search_opts=volume_search_opts)
self.snapshots_mock.list.assert_called_once_with(
@@ -152,10 +153,11 @@ class TestProjectPurge(TestProjectPurgeInit):
self.projects_mock.get.assert_called_once_with(self.project.id)
self.projects_mock.delete.assert_not_called()
self.servers_mock.list.assert_called_once_with(
- search_opts={'tenant_id': self.project.id})
+ search_opts={'tenant_id': self.project.id, 'all_tenants': True})
kwargs = {'filters': {'owner': self.project.id}}
self.images_mock.list.assert_called_once_with(**kwargs)
- volume_search_opts = {'project_id': self.project.id}
+ volume_search_opts = {'project_id': self.project.id,
+ 'all_tenants': True}
self.volumes_mock.list.assert_called_once_with(
search_opts=volume_search_opts)
self.snapshots_mock.list.assert_called_once_with(
@@ -187,10 +189,11 @@ class TestProjectPurge(TestProjectPurgeInit):
self.projects_mock.get.assert_called_once_with(self.project.id)
self.projects_mock.delete.assert_not_called()
self.servers_mock.list.assert_called_once_with(
- search_opts={'tenant_id': self.project.id})
+ search_opts={'tenant_id': self.project.id, 'all_tenants': True})
kwargs = {'filters': {'owner': self.project.id}}
self.images_mock.list.assert_called_once_with(**kwargs)
- volume_search_opts = {'project_id': self.project.id}
+ volume_search_opts = {'project_id': self.project.id,
+ 'all_tenants': True}
self.volumes_mock.list.assert_called_once_with(
search_opts=volume_search_opts)
self.snapshots_mock.list.assert_called_once_with(
@@ -223,10 +226,11 @@ class TestProjectPurge(TestProjectPurgeInit):
self.projects_mock.get.assert_not_called()
self.projects_mock.delete.assert_called_once_with(self.project.id)
self.servers_mock.list.assert_called_once_with(
- search_opts={'tenant_id': self.project.id})
+ search_opts={'tenant_id': self.project.id, 'all_tenants': True})
kwargs = {'filters': {'owner': self.project.id}}
self.images_mock.list.assert_called_once_with(**kwargs)
- volume_search_opts = {'project_id': self.project.id}
+ volume_search_opts = {'project_id': self.project.id,
+ 'all_tenants': True}
self.volumes_mock.list.assert_called_once_with(
search_opts=volume_search_opts)
self.snapshots_mock.list.assert_called_once_with(
@@ -259,10 +263,11 @@ class TestProjectPurge(TestProjectPurgeInit):
self.projects_mock.get.assert_called_once_with(self.project.id)
self.projects_mock.delete.assert_called_once_with(self.project.id)
self.servers_mock.list.assert_called_once_with(
- search_opts={'tenant_id': self.project.id})
+ search_opts={'tenant_id': self.project.id, 'all_tenants': True})
kwargs = {'filters': {'owner': self.project.id}}
self.images_mock.list.assert_called_once_with(**kwargs)
- volume_search_opts = {'project_id': self.project.id}
+ volume_search_opts = {'project_id': self.project.id,
+ 'all_tenants': True}
self.volumes_mock.list.assert_called_once_with(
search_opts=volume_search_opts)
self.snapshots_mock.list.assert_called_once_with(
@@ -295,10 +300,11 @@ class TestProjectPurge(TestProjectPurgeInit):
self.projects_mock.get.assert_called_once_with(self.project.id)
self.projects_mock.delete.assert_called_once_with(self.project.id)
self.servers_mock.list.assert_called_once_with(
- search_opts={'tenant_id': self.project.id})
+ search_opts={'tenant_id': self.project.id, 'all_tenants': True})
kwargs = {'filters': {'owner': self.project.id}}
self.images_mock.list.assert_called_once_with(**kwargs)
- volume_search_opts = {'project_id': self.project.id}
+ volume_search_opts = {'project_id': self.project.id,
+ 'all_tenants': True}
self.volumes_mock.list.assert_called_once_with(
search_opts=volume_search_opts)
self.snapshots_mock.list.assert_called_once_with(
diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py
index 1d97cecb..234bbd9b 100644
--- a/openstackclient/tests/unit/compute/v2/fakes.py
+++ b/openstackclient/tests/unit/compute/v2/fakes.py
@@ -150,6 +150,9 @@ class FakeComputev2Client(object):
self.images = mock.Mock()
self.images.resource_class = fakes.FakeResource(None, {})
+ self.limits = mock.Mock()
+ self.limits.resource_class = fakes.FakeResource(None, {})
+
self.servers = mock.Mock()
self.servers.resource_class = fakes.FakeResource(None, {})
@@ -765,6 +768,7 @@ class FakeFlavor(object):
'rxtx_factor': 1.0,
'OS-FLV-DISABLED:disabled': False,
'os-flavor-access:is_public': True,
+ 'description': 'description',
'OS-FLV-EXT-DATA:ephemeral': 0,
'properties': {'property': 'value'},
}
@@ -1395,3 +1399,110 @@ class FakeQuota(object):
quota.project_id = quota_attrs['id']
return quota
+
+
+class FakeLimits(object):
+ """Fake limits"""
+
+ def __init__(self, absolute_attrs=None, rate_attrs=None):
+ self.absolute_limits_attrs = {
+ 'maxServerMeta': 128,
+ 'maxTotalInstances': 10,
+ 'maxPersonality': 5,
+ 'totalServerGroupsUsed': 0,
+ 'maxImageMeta': 128,
+ 'maxPersonalitySize': 10240,
+ 'maxTotalRAMSize': 51200,
+ 'maxServerGroups': 10,
+ 'maxSecurityGroupRules': 20,
+ 'maxTotalKeypairs': 100,
+ 'totalCoresUsed': 0,
+ 'totalRAMUsed': 0,
+ 'maxSecurityGroups': 10,
+ 'totalFloatingIpsUsed': 0,
+ 'totalInstancesUsed': 0,
+ 'maxServerGroupMembers': 10,
+ 'maxTotalFloatingIps': 10,
+ 'totalSecurityGroupsUsed': 0,
+ 'maxTotalCores': 20,
+ }
+ absolute_attrs = absolute_attrs or {}
+ self.absolute_limits_attrs.update(absolute_attrs)
+
+ self.rate_limits_attrs = [{
+ "uri": "*",
+ "limit": [
+ {
+ "value": 10,
+ "verb": "POST",
+ "remaining": 2,
+ "unit": "MINUTE",
+ "next-available": "2011-12-15T22:42:45Z"
+ },
+ {
+ "value": 10,
+ "verb": "PUT",
+ "remaining": 2,
+ "unit": "MINUTE",
+ "next-available": "2011-12-15T22:42:45Z"
+ },
+ {
+ "value": 100,
+ "verb": "DELETE",
+ "remaining": 100,
+ "unit": "MINUTE",
+ "next-available": "2011-12-15T22:42:45Z"
+ }
+ ]
+ }]
+
+ @property
+ def absolute(self):
+ for (name, value) in self.absolute_limits_attrs.items():
+ yield FakeAbsoluteLimit(name, value)
+
+ def absolute_limits(self):
+ reference_data = []
+ for (name, value) in self.absolute_limits_attrs.items():
+ reference_data.append((name, value))
+ return reference_data
+
+ @property
+ def rate(self):
+ for group in self.rate_limits_attrs:
+ uri = group['uri']
+ for rate in group['limit']:
+ yield FakeRateLimit(rate['verb'], uri, rate['value'],
+ rate['remaining'], rate['unit'],
+ rate['next-available'])
+
+ def rate_limits(self):
+ reference_data = []
+ for group in self.rate_limits_attrs:
+ uri = group['uri']
+ for rate in group['limit']:
+ reference_data.append((rate['verb'], uri, rate['value'],
+ rate['remaining'], rate['unit'],
+ rate['next-available']))
+ return reference_data
+
+
+class FakeAbsoluteLimit(object):
+ """Data model that represents an absolute limit"""
+
+ def __init__(self, name, value):
+ self.name = name
+ self.value = value
+
+
+class FakeRateLimit(object):
+ """Data model that represents a flattened view of a single rate limit"""
+
+ def __init__(self, verb, uri, value, remain,
+ unit, next_available):
+ self.verb = verb
+ self.uri = uri
+ self.value = value
+ self.remain = remain
+ self.unit = unit
+ self.next_available = next_available
diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py
index 4cdbb25b..a112fc1f 100644
--- a/openstackclient/tests/unit/compute/v2/test_flavor.py
+++ b/openstackclient/tests/unit/compute/v2/test_flavor.py
@@ -16,6 +16,7 @@
import mock
from mock import call
+import novaclient
from osc_lib import exceptions
from osc_lib import utils
@@ -50,6 +51,7 @@ class TestFlavorCreate(TestFlavor):
columns = (
'OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral',
+ 'description',
'disk',
'id',
'name',
@@ -63,6 +65,7 @@ class TestFlavorCreate(TestFlavor):
data = (
flavor.disabled,
flavor.ephemeral,
+ flavor.description,
flavor.disk,
flavor.id,
flavor.name,
@@ -101,7 +104,8 @@ class TestFlavorCreate(TestFlavor):
0,
0,
1.0,
- True
+ True,
+ None,
)
columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*default_args)
@@ -120,6 +124,7 @@ class TestFlavorCreate(TestFlavor):
'--vcpus', str(self.flavor.vcpus),
'--rxtx-factor', str(self.flavor.rxtx_factor),
'--public',
+ '--description', str(self.flavor.description),
'--property', 'property=value',
self.flavor.name,
]
@@ -132,6 +137,7 @@ class TestFlavorCreate(TestFlavor):
('vcpus', self.flavor.vcpus),
('rxtx_factor', self.flavor.rxtx_factor),
('public', True),
+ ('description', self.flavor.description),
('property', {'property': 'value'}),
('name', self.flavor.name),
]
@@ -147,8 +153,13 @@ class TestFlavorCreate(TestFlavor):
self.flavor.swap,
self.flavor.rxtx_factor,
self.flavor.is_public,
+ self.flavor.description,
)
- columns, data = self.cmd.take_action(parsed_args)
+ self.app.client_manager.compute.api_version = 2.55
+ with mock.patch.object(novaclient.api_versions,
+ 'APIVersion',
+ return_value=2.55):
+ columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*args)
self.flavor.set_keys.assert_called_once_with({'property': 'value'})
self.flavor.get_keys.assert_called_once_with()
@@ -168,6 +179,7 @@ class TestFlavorCreate(TestFlavor):
'--vcpus', str(self.flavor.vcpus),
'--rxtx-factor', str(self.flavor.rxtx_factor),
'--private',
+ '--description', str(self.flavor.description),
'--project', self.project.id,
'--property', 'key1=value1',
'--property', 'key2=value2',
@@ -181,6 +193,7 @@ class TestFlavorCreate(TestFlavor):
('vcpus', self.flavor.vcpus),
('rxtx_factor', self.flavor.rxtx_factor),
('public', False),
+ ('description', 'description'),
('project', self.project.id),
('property', {'key1': 'value1', 'key2': 'value2'}),
('name', self.flavor.name),
@@ -197,8 +210,13 @@ class TestFlavorCreate(TestFlavor):
self.flavor.swap,
self.flavor.rxtx_factor,
self.flavor.is_public,
+ self.flavor.description,
)
- columns, data = self.cmd.take_action(parsed_args)
+ self.app.client_manager.compute.api_version = 2.55
+ with mock.patch.object(novaclient.api_versions,
+ 'APIVersion',
+ return_value=2.55):
+ columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*args)
self.flavor_access_mock.add_tenant_access.assert_called_with(
self.flavor.id,
@@ -234,6 +252,79 @@ class TestFlavorCreate(TestFlavor):
arglist,
verifylist)
+ def test_flavor_create_with_description_api_newer(self):
+ arglist = [
+ '--id', self.flavor.id,
+ '--ram', str(self.flavor.ram),
+ '--disk', str(self.flavor.disk),
+ '--ephemeral', str(self.flavor.ephemeral),
+ '--swap', str(self.flavor.swap),
+ '--vcpus', str(self.flavor.vcpus),
+ '--rxtx-factor', str(self.flavor.rxtx_factor),
+ '--private',
+ '--description', 'fake description',
+ self.flavor.name,
+ ]
+ verifylist = [
+ ('id', self.flavor.id),
+ ('ram', self.flavor.ram),
+ ('disk', self.flavor.disk),
+ ('ephemeral', self.flavor.ephemeral),
+ ('swap', self.flavor.swap),
+ ('vcpus', self.flavor.vcpus),
+ ('rxtx_factor', self.flavor.rxtx_factor),
+ ('public', False),
+ ('description', 'fake description'),
+ ('name', self.flavor.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = 2.55
+ with mock.patch.object(novaclient.api_versions,
+ 'APIVersion',
+ return_value=2.55):
+ columns, data = self.cmd.take_action(parsed_args)
+
+ args = (
+ self.flavor.name,
+ self.flavor.ram,
+ self.flavor.vcpus,
+ self.flavor.disk,
+ self.flavor.id,
+ self.flavor.ephemeral,
+ self.flavor.swap,
+ self.flavor.rxtx_factor,
+ False,
+ 'fake description',
+ )
+
+ self.flavors_mock.create.assert_called_once_with(*args)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_flavor_create_with_description_api_older(self):
+ arglist = [
+ '--id', self.flavor.id,
+ '--ram', str(self.flavor.ram),
+ '--vcpus', str(self.flavor.vcpus),
+ '--description', 'description',
+ self.flavor.name,
+ ]
+ verifylist = [
+ ('ram', self.flavor.ram),
+ ('vcpus', self.flavor.vcpus),
+ ('description', 'description'),
+ ('name', self.flavor.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.app.client_manager.compute.api_version = 2.54
+ with mock.patch.object(novaclient.api_versions,
+ 'APIVersion',
+ return_value=2.55):
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
class TestFlavorDelete(TestFlavor):
@@ -622,6 +713,42 @@ class TestFlavorSet(TestFlavor):
self.flavor_access_mock.add_tenant_access.assert_not_called()
self.assertIsNone(result)
+ def test_flavor_set_description_api_newer(self):
+ arglist = [
+ '--description', 'description',
+ self.flavor.id,
+ ]
+ verifylist = [
+ ('description', 'description'),
+ ('flavor', self.flavor.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = 2.55
+ with mock.patch.object(novaclient.api_versions,
+ 'APIVersion',
+ return_value=2.55):
+ result = self.cmd.take_action(parsed_args)
+ self.flavors_mock.update.assert_called_with(
+ flavor=self.flavor.id, description='description')
+ self.assertIsNone(result)
+
+ def test_flavor_set_description_api_older(self):
+ arglist = [
+ '--description', 'description',
+ self.flavor.id,
+ ]
+ verifylist = [
+ ('description', 'description'),
+ ('flavor', self.flavor.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = 2.54
+ with mock.patch.object(novaclient.api_versions,
+ 'APIVersion',
+ return_value=2.55):
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
class TestFlavorShow(TestFlavor):
@@ -633,6 +760,7 @@ class TestFlavorShow(TestFlavor):
'OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral',
'access_project_ids',
+ 'description',
'disk',
'id',
'name',
@@ -648,6 +776,7 @@ class TestFlavorShow(TestFlavor):
flavor.disabled,
flavor.ephemeral,
None,
+ flavor.description,
flavor.disk,
flavor.id,
flavor.name,
@@ -710,6 +839,7 @@ class TestFlavorShow(TestFlavor):
private_flavor.disabled,
private_flavor.ephemeral,
self.flavor_access.tenant_id,
+ private_flavor.description,
private_flavor.disk,
private_flavor.id,
private_flavor.name,
diff --git a/openstackclient/tests/unit/compute/v2/test_host.py b/openstackclient/tests/unit/compute/v2/test_host.py
index 329095de..244da413 100644
--- a/openstackclient/tests/unit/compute/v2/test_host.py
+++ b/openstackclient/tests/unit/compute/v2/test_host.py
@@ -111,8 +111,7 @@ class TestHostSet(TestHost):
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
- body = {}
- h_mock.assert_called_with(self.host['host'], body)
+ h_mock.assert_called_with(self.host['host'])
def test_host_set(self, h_mock):
h_mock.return_value = self.host
@@ -133,8 +132,8 @@ class TestHostSet(TestHost):
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
- body = {'status': 'enable', 'maintenance_mode': 'disable'}
- h_mock.assert_called_with(self.host['host'], body)
+ h_mock.assert_called_with(self.host['host'], status='enable',
+ maintenance_mode='disable')
@mock.patch(
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 8f82e7cd..44e433d8 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -1075,7 +1075,7 @@ class TestServerCreate(TestServer):
mock_wait_for_status.assert_called_once_with(
self.servers_mock.get,
self.new_server.id,
- callback=server._show_progress,
+ callback=mock.ANY,
)
kwargs = dict(
@@ -1125,7 +1125,7 @@ class TestServerCreate(TestServer):
mock_wait_for_status.assert_called_once_with(
self.servers_mock.get,
self.new_server.id,
- callback=server._show_progress,
+ callback=mock.ANY,
)
kwargs = dict(
@@ -1578,6 +1578,164 @@ class TestServerCreate(TestServer):
self.cmd.take_action,
parsed_args)
+ def test_server_create_image_property(self):
+ arglist = [
+ '--image-property', 'hypervisor_type=qemu',
+ '--flavor', 'flavor1',
+ '--nic', 'none',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image_property', {'hypervisor_type': 'qemu'}),
+ ('flavor', 'flavor1'),
+ ('nic', ['none']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ _image = image_fakes.FakeImage.create_one_image()
+ # create a image_info as the side_effect of the fake image_list()
+ image_info = {
+ 'id': _image.id,
+ 'name': _image.name,
+ 'owner': _image.owner,
+ 'hypervisor_type': 'qemu',
+ }
+ self.api_mock = mock.Mock()
+ self.api_mock.image_list.side_effect = [
+ [image_info], [],
+ ]
+ self.app.client_manager.image.api = self.api_mock
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = dict(
+ files={},
+ reservation_id=None,
+ min_count=1,
+ max_count=1,
+ security_groups=[],
+ userdata=None,
+ key_name=None,
+ availability_zone=None,
+ block_device_mapping_v2=[],
+ nics='none',
+ meta=None,
+ scheduler_hints={},
+ config_drive=None,
+ )
+ # ServerManager.create(name, image, flavor, **kwargs)
+ self.servers_mock.create.assert_called_with(
+ self.new_server.name,
+ image_info,
+ self.flavor,
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist(), data)
+
+ def test_server_create_image_property_multi(self):
+ arglist = [
+ '--image-property', 'hypervisor_type=qemu',
+ '--image-property', 'hw_disk_bus=ide',
+ '--flavor', 'flavor1',
+ '--nic', 'none',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image_property', {'hypervisor_type': 'qemu',
+ 'hw_disk_bus': 'ide'}),
+ ('flavor', 'flavor1'),
+ ('nic', ['none']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ _image = image_fakes.FakeImage.create_one_image()
+ # create a image_info as the side_effect of the fake image_list()
+ image_info = {
+ 'id': _image.id,
+ 'name': _image.name,
+ 'owner': _image.owner,
+ 'hypervisor_type': 'qemu',
+ 'hw_disk_bus': 'ide',
+ }
+ self.api_mock = mock.Mock()
+ self.api_mock.image_list.side_effect = [
+ [image_info], [],
+ ]
+ self.app.client_manager.image.api = self.api_mock
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = dict(
+ files={},
+ reservation_id=None,
+ min_count=1,
+ max_count=1,
+ security_groups=[],
+ userdata=None,
+ key_name=None,
+ availability_zone=None,
+ block_device_mapping_v2=[],
+ nics='none',
+ meta=None,
+ scheduler_hints={},
+ config_drive=None,
+ )
+ # ServerManager.create(name, image, flavor, **kwargs)
+ self.servers_mock.create.assert_called_with(
+ self.new_server.name,
+ image_info,
+ self.flavor,
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist(), data)
+
+ def test_server_create_image_property_missed(self):
+ arglist = [
+ '--image-property', 'hypervisor_type=qemu',
+ '--image-property', 'hw_disk_bus=virtio',
+ '--flavor', 'flavor1',
+ '--nic', 'none',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image_property', {'hypervisor_type': 'qemu',
+ 'hw_disk_bus': 'virtio'}),
+ ('flavor', 'flavor1'),
+ ('nic', ['none']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ _image = image_fakes.FakeImage.create_one_image()
+ # create a image_info as the side_effect of the fake image_list()
+ image_info = {
+ 'id': _image.id,
+ 'name': _image.name,
+ 'owner': _image.owner,
+ 'hypervisor_type': 'qemu',
+ 'hw_disk_bus': 'ide',
+ }
+ self.api_mock = mock.Mock()
+ self.api_mock.image_list.side_effect = [
+ [image_info], [],
+ ]
+ self.app.client_manager.image.api = self.api_mock
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
class TestServerDelete(TestServer):
@@ -1644,7 +1802,7 @@ class TestServerDelete(TestServer):
mock_wait_for_delete.assert_called_once_with(
self.servers_mock,
servers[0].id,
- callback=server._show_progress
+ callback=mock.ANY,
)
self.assertIsNone(result)
@@ -1666,7 +1824,7 @@ class TestServerDelete(TestServer):
mock_wait_for_delete.assert_called_once_with(
self.servers_mock,
servers[0].id,
- callback=server._show_progress
+ callback=mock.ANY,
)
@@ -1839,6 +1997,25 @@ class TestServerList(TestServer):
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
+ def test_server_list_no_servers(self):
+ arglist = []
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ('deleted', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.servers_mock.list.return_value = []
+ self.data = ()
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.list.assert_called_with(**self.kwargs)
+ self.assertEqual(0, self.images_mock.list.call_count)
+ self.assertEqual(0, self.flavors_mock.list.call_count)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
def test_server_list_long_option(self):
arglist = [
'--long',
@@ -2080,6 +2257,9 @@ class TestServerMigrate(TestServer):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.24')
+
result = self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(self.server.id)
@@ -2101,6 +2281,9 @@ class TestServerMigrate(TestServer):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.24')
+
result = self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(self.server.id)
@@ -2122,6 +2305,9 @@ class TestServerMigrate(TestServer):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.24')
+
result = self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(self.server.id)
@@ -2144,6 +2330,9 @@ class TestServerMigrate(TestServer):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.24')
+
result = self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(self.server.id)
@@ -2153,6 +2342,28 @@ class TestServerMigrate(TestServer):
self.assertNotCalled(self.servers_mock.migrate)
self.assertIsNone(result)
+ def test_server_live_migrate_225(self):
+ arglist = [
+ '--live', 'fakehost', self.server.id,
+ ]
+ verifylist = [
+ ('live', 'fakehost'),
+ ('block_migration', False),
+ ('wait', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.25')
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.live_migrate.assert_called_with(block_migration=False,
+ host='fakehost')
+ self.assertNotCalled(self.servers_mock.migrate)
+ self.assertIsNone(result)
+
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
def test_server_migrate_with_wait(self, mock_wait_for_status):
arglist = [
@@ -2223,17 +2434,17 @@ class TestServerRebuild(TestServer):
self.images_mock.get.return_value = self.image
# Fake the rebuilt new server.
- new_server = compute_fakes.FakeServer.create_one_server()
-
- # Fake the server to be rebuilt. The IDs of them should be the same.
attrs = {
- 'id': new_server.id,
'image': {
'id': self.image.id
},
'networks': {},
'adminPass': 'passw0rd',
}
+ new_server = compute_fakes.FakeServer.create_one_server(attrs=attrs)
+
+ # Fake the server to be rebuilt. The IDs of them should be the same.
+ attrs['id'] = new_server.id
methods = {
'rebuild': new_server,
}
@@ -2302,7 +2513,7 @@ class TestServerRebuild(TestServer):
mock_wait_for_status.assert_called_once_with(
self.servers_mock.get,
self.server.id,
- callback=server._show_progress,
+ callback=mock.ANY,
# **kwargs
)
@@ -2327,13 +2538,34 @@ class TestServerRebuild(TestServer):
mock_wait_for_status.assert_called_once_with(
self.servers_mock.get,
self.server.id,
- callback=server._show_progress
+ callback=mock.ANY,
)
self.servers_mock.get.assert_called_with(self.server.id)
self.images_mock.get.assert_called_with(self.image.id)
self.server.rebuild.assert_called_with(self.image, None)
+ def test_rebuild_with_property(self):
+ arglist = [
+ self.server.id,
+ '--property', 'key1=value1',
+ '--property', 'key2=value2'
+ ]
+ expected_property = {'key1': 'value1', 'key2': 'value2'}
+ verifylist = [
+ ('server', self.server.id),
+ ('property', expected_property)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # Get the command object to test
+ self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.images_mock.get.assert_called_with(self.image.id)
+ self.server.rebuild.assert_called_with(
+ self.image, None, meta=expected_property)
+
class TestServerRemoveFixedIP(TestServer):
@@ -2813,7 +3045,7 @@ class TestServerResize(TestServer):
mock_wait_for_status.assert_called_once_with(
self.servers_mock.get,
self.server.id,
- callback=server._show_progress,
+ callback=mock.ANY,
**kwargs
)
@@ -2853,7 +3085,7 @@ class TestServerResize(TestServer):
mock_wait_for_status.assert_called_once_with(
self.servers_mock.get,
self.server.id,
- callback=server._show_progress,
+ callback=mock.ANY,
**kwargs
)
@@ -3109,6 +3341,33 @@ class TestServerShow(TestServer):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_show_embedded_flavor(self):
+ # Tests using --os-compute-api-version >= 2.47 where the flavor
+ # details are embedded in the server response body excluding the id.
+ arglist = [
+ self.server.name,
+ ]
+ verifylist = [
+ ('diagnostics', False),
+ ('server', self.server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.server.info['flavor'] = {
+ 'ephemeral': 0,
+ 'ram': 512,
+ 'original_name': 'm1.tiny',
+ 'vcpus': 1,
+ 'extra_specs': {},
+ 'swap': 0,
+ 'disk': 1
+ }
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.assertEqual(self.columns, columns)
+ # Since the flavor details are in a dict we can't be sure of the
+ # ordering so just assert that one of the keys is in the output.
+ self.assertIn('original_name', data[2])
+
def test_show_diagnostics(self):
arglist = [
'--diagnostics',
diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py
index 8403efc9..bd299123 100644
--- a/openstackclient/tests/unit/compute/v2/test_service.py
+++ b/openstackclient/tests/unit/compute/v2/test_service.py
@@ -15,7 +15,7 @@
import mock
from mock import call
-
+from novaclient import api_versions
from osc_lib import exceptions
from openstackclient.compute.v2 import service
@@ -340,6 +340,8 @@ class TestServiceSet(TestService):
('service', self.service.binary),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.11')
result = self.cmd.take_action(parsed_args)
self.service_mock.force_down.assert_called_once_with(
self.service.host, self.service.binary, force_down=False)
@@ -359,6 +361,8 @@ class TestServiceSet(TestService):
('service', self.service.binary),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.11')
result = self.cmd.take_action(parsed_args)
self.service_mock.force_down.assert_called_once_with(
self.service.host, self.service.binary, force_down=True)
@@ -380,6 +384,8 @@ class TestServiceSet(TestService):
('service', self.service.binary),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.11')
result = self.cmd.take_action(parsed_args)
self.service_mock.enable.assert_called_once_with(
self.service.host, self.service.binary)
@@ -402,6 +408,8 @@ class TestServiceSet(TestService):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.11')
with mock.patch.object(self.service_mock, 'enable',
side_effect=Exception()):
self.assertRaises(exceptions.CommandError,
diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py
index 65c76b3e..bca457e4 100644
--- a/openstackclient/tests/unit/fakes.py
+++ b/openstackclient/tests/unit/fakes.py
@@ -106,6 +106,7 @@ class FakeApp(object):
def __init__(self, _stdout, _log):
self.stdout = _stdout
self.client_manager = None
+ self.api_version = {}
self.stdin = sys.stdin
self.stdout = _stdout or sys.stdout
self.stderr = sys.stderr
@@ -140,6 +141,8 @@ class FakeClientManager(object):
self.auth_ref = None
self.auth_plugin_name = None
self.network_endpoint_enabled = True
+ self.compute_endpoint_enabled = True
+ self.volume_endpoint_enabled = True
def get_configuration(self):
return {
@@ -155,6 +158,12 @@ class FakeClientManager(object):
def is_network_endpoint_enabled(self):
return self.network_endpoint_enabled
+ def is_compute_endpoint_enabled(self):
+ return self.compute_endpoint_enabled
+
+ def is_volume_endpoint_enabled(self, client):
+ return self.volume_endpoint_enabled
+
class FakeModule(object):
diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py
index 77928792..27ee9fd0 100644
--- a/openstackclient/tests/unit/identity/v3/fakes.py
+++ b/openstackclient/tests/unit/identity/v3/fakes.py
@@ -486,6 +486,50 @@ APP_CRED_OPTIONS = {
'secret': app_cred_secret
}
+registered_limit_id = 'registered-limit-id'
+registered_limit_default_limit = 10
+registered_limit_description = 'default limit of foobars'
+registered_limit_resource_name = 'foobars'
+REGISTERED_LIMIT = {
+ 'id': registered_limit_id,
+ 'default_limit': registered_limit_default_limit,
+ 'resource_name': registered_limit_resource_name,
+ 'service_id': service_id,
+ 'description': None,
+ 'region_id': None
+}
+REGISTERED_LIMIT_OPTIONS = {
+ 'id': registered_limit_id,
+ 'default_limit': registered_limit_default_limit,
+ 'resource_name': registered_limit_resource_name,
+ 'service_id': service_id,
+ 'description': registered_limit_description,
+ 'region_id': region_id
+}
+
+limit_id = 'limit-id'
+limit_resource_limit = 15
+limit_description = 'limit of foobars'
+limit_resource_name = 'foobars'
+LIMIT = {
+ 'id': limit_id,
+ 'project_id': project_id,
+ 'resource_limit': limit_resource_limit,
+ 'resource_name': limit_resource_name,
+ 'service_id': service_id,
+ 'description': None,
+ 'region_id': None
+}
+LIMIT_OPTIONS = {
+ 'id': limit_id,
+ 'project_id': project_id,
+ 'resource_limit': limit_resource_limit,
+ 'resource_name': limit_resource_name,
+ 'service_id': service_id,
+ 'description': limit_description,
+ 'region_id': region_id
+}
+
def fake_auth_ref(fake_token, fake_service=None):
"""Create an auth_ref using keystoneauth's fixtures"""
@@ -576,6 +620,12 @@ class FakeIdentityv3Client(object):
self.application_credentials = mock.Mock()
self.application_credentials.resource_class = fakes.FakeResource(None,
{})
+ self.inference_rules = mock.Mock()
+ self.inference_rules.resource_class = fakes.FakeResource(None, {})
+ self.registered_limits = mock.Mock()
+ self.registered_limits.resource_class = fakes.FakeResource(None, {})
+ self.limits = mock.Mock()
+ self.limits.resource_class = fakes.FakeResource(None, {})
class FakeFederationManager(object):
diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py
index bfe930d6..62dcf58d 100644
--- a/openstackclient/tests/unit/identity/v3/test_endpoint.py
+++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py
@@ -439,6 +439,47 @@ class TestEndpointList(TestEndpoint):
)
self.assertEqual(datalist, tuple(data))
+ def test_endpoint_list_project_with_project_domain(self):
+ project = identity_fakes.FakeProject.create_one_project()
+ domain = identity_fakes.FakeDomain.create_one_domain()
+
+ self.ep_filter_mock.list_endpoints_for_project.return_value = [
+ self.endpoint
+ ]
+ self.projects_mock.get.return_value = project
+
+ arglist = [
+ '--project', project.name,
+ '--project-domain', domain.name
+ ]
+ verifylist = [
+ ('project', project.name),
+ ('project_domain', domain.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+ self.ep_filter_mock.list_endpoints_for_project.assert_called_with(
+ project=project.id
+ )
+
+ self.assertEqual(self.columns, columns)
+ datalist = (
+ (
+ self.endpoint.id,
+ self.endpoint.region,
+ self.service.name,
+ self.service.type,
+ True,
+ self.endpoint.interface,
+ self.endpoint.url,
+ ),
+ )
+ self.assertEqual(datalist, tuple(data))
+
class TestEndpointSet(TestEndpoint):
diff --git a/openstackclient/tests/unit/identity/v3/test_implied_role.py b/openstackclient/tests/unit/identity/v3/test_implied_role.py
index 08273f73..74968129 100644
--- a/openstackclient/tests/unit/identity/v3/test_implied_role.py
+++ b/openstackclient/tests/unit/identity/v3/test_implied_role.py
@@ -25,26 +25,32 @@ class TestRole(identity_fakes.TestIdentityv3):
def setUp(self):
super(TestRole, self).setUp()
+ identity_client = self.app.client_manager.identity
+
# Get a shortcut to the UserManager Mock
- self.users_mock = self.app.client_manager.identity.users
+ self.users_mock = identity_client.users
self.users_mock.reset_mock()
# Get a shortcut to the UserManager Mock
- self.groups_mock = self.app.client_manager.identity.groups
+ self.groups_mock = identity_client.groups
self.groups_mock.reset_mock()
# Get a shortcut to the DomainManager Mock
- self.domains_mock = self.app.client_manager.identity.domains
+ self.domains_mock = identity_client.domains
self.domains_mock.reset_mock()
# Get a shortcut to the ProjectManager Mock
- self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock = identity_client.projects
self.projects_mock.reset_mock()
# Get a shortcut to the RoleManager Mock
- self.roles_mock = self.app.client_manager.identity.roles
+ self.roles_mock = identity_client.roles
self.roles_mock.reset_mock()
+ # Get a shortcut to the InferenceRuleManager Mock
+ self.inference_rules_mock = identity_client.inference_rules
+ self.inference_rules_mock.reset_mock()
+
def _is_inheritance_testcase(self):
return False
@@ -67,12 +73,13 @@ class TestImpliedRoleCreate(TestRole):
),
]
- self.roles_mock.create_implied.return_value = fakes.FakeResource(
+ fake_resource = fakes.FakeResource(
None,
{'prior_role': copy.deepcopy(identity_fakes.ROLES[0]),
'implied': copy.deepcopy(identity_fakes.ROLES[1]), },
loaded=True,
)
+ self.inference_rules_mock.create.return_value = fake_resource
self.cmd = implied_role.CreateImpliedRole(self.app, None)
@@ -93,8 +100,8 @@ class TestImpliedRoleCreate(TestRole):
# data to be shown.
columns, data = self.cmd.take_action(parsed_args)
- # RoleManager.create_implied(prior, implied)
- self.roles_mock.create_implied.assert_called_with(
+ # InferenceRuleManager.create(prior, implied)
+ self.inference_rules_mock.create.assert_called_with(
identity_fakes.ROLES[0]['id'],
identity_fakes.ROLES[1]['id']
)
@@ -126,12 +133,13 @@ class TestImpliedRoleDelete(TestRole):
),
]
- self.roles_mock.delete_implied.return_value = fakes.FakeResource(
+ fake_resource = fakes.FakeResource(
None,
{'prior-role': copy.deepcopy(identity_fakes.ROLES[0]),
'implied': copy.deepcopy(identity_fakes.ROLES[1]), },
loaded=True,
)
+ self.inference_rules_mock.delete.return_value = fake_resource
self.cmd = implied_role.DeleteImpliedRole(self.app, None)
@@ -147,7 +155,7 @@ class TestImpliedRoleDelete(TestRole):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
- self.roles_mock.delete_implied.assert_called_with(
+ self.inference_rules_mock.delete.assert_called_with(
identity_fakes.ROLES[0]['id'],
identity_fakes.ROLES[1]['id']
)
@@ -158,7 +166,7 @@ class TestImpliedRoleList(TestRole):
def setUp(self):
super(TestImpliedRoleList, self).setUp()
- self.roles_mock.list_inference_roles.return_value = (
+ self.inference_rules_mock.list_inference_roles.return_value = (
identity_fakes.FakeImpliedRoleResponse.create_list())
self.cmd = implied_role.ListImpliedRole(self.app, None)
@@ -168,7 +176,7 @@ class TestImpliedRoleList(TestRole):
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.roles_mock.list_inference_roles.assert_called_with()
+ self.inference_rules_mock.list_inference_roles.assert_called_with()
collist = ['Prior Role ID', 'Prior Role Name',
'Implied Role ID', 'Implied Role Name']
diff --git a/openstackclient/tests/unit/identity/v3/test_limit.py b/openstackclient/tests/unit/identity/v3/test_limit.py
new file mode 100644
index 00000000..44c0358d
--- /dev/null
+++ b/openstackclient/tests/unit/identity/v3/test_limit.py
@@ -0,0 +1,382 @@
+# 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 copy
+
+from keystoneauth1.exceptions import http as ksa_exceptions
+from osc_lib import exceptions
+
+from openstackclient.identity.v3 import limit
+from openstackclient.tests.unit import fakes
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
+
+
+class TestLimit(identity_fakes.TestIdentityv3):
+
+ def setUp(self):
+ super(TestLimit, self).setUp()
+
+ identity_manager = self.app.client_manager.identity
+
+ self.limit_mock = identity_manager.limits
+
+ self.services_mock = identity_manager.services
+ self.services_mock.reset_mock()
+
+ self.projects_mock = identity_manager.projects
+ self.projects_mock.reset_mock()
+
+ self.regions_mock = identity_manager.regions
+ self.regions_mock.reset_mock()
+
+
+class TestLimitCreate(TestLimit):
+
+ def setUp(self):
+ super(TestLimitCreate, self).setUp()
+
+ self.service = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.SERVICE),
+ loaded=True
+ )
+ self.services_mock.get.return_value = self.service
+
+ self.project = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True
+ )
+ self.projects_mock.get.return_value = self.project
+
+ self.region = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.REGION),
+ loaded=True
+ )
+ self.regions_mock.get.return_value = self.region
+
+ self.cmd = limit.CreateLimit(self.app, None)
+
+ def test_limit_create_without_options(self):
+ self.limit_mock.create.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.LIMIT),
+ loaded=True
+ )
+
+ resource_limit = 15
+ arglist = [
+ '--project', identity_fakes.project_id,
+ '--service', identity_fakes.service_id,
+ '--resource-limit', str(resource_limit),
+ identity_fakes.limit_resource_name
+ ]
+ verifylist = [
+ ('project', identity_fakes.project_id),
+ ('service', identity_fakes.service_id),
+ ('resource_name', identity_fakes.limit_resource_name),
+ ('resource_limit', resource_limit)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {'description': None, 'region': None}
+ self.limit_mock.create.assert_called_with(
+ self.project,
+ self.service,
+ identity_fakes.limit_resource_name,
+ resource_limit,
+ **kwargs
+ )
+
+ collist = ('description', 'id', 'project_id', 'region_id',
+ 'resource_limit', 'resource_name', 'service_id')
+ self.assertEqual(collist, columns)
+ datalist = (
+ None,
+ identity_fakes.limit_id,
+ identity_fakes.project_id,
+ None,
+ resource_limit,
+ identity_fakes.limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+ def test_limit_create_with_options(self):
+ self.limit_mock.create.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.LIMIT_OPTIONS),
+ loaded=True
+ )
+
+ resource_limit = 15
+ arglist = [
+ '--project', identity_fakes.project_id,
+ '--service', identity_fakes.service_id,
+ '--resource-limit', str(resource_limit),
+ '--region', identity_fakes.region_id,
+ '--description', identity_fakes.limit_description,
+ identity_fakes.limit_resource_name
+ ]
+ verifylist = [
+ ('project', identity_fakes.project_id),
+ ('service', identity_fakes.service_id),
+ ('resource_name', identity_fakes.limit_resource_name),
+ ('resource_limit', resource_limit),
+ ('region', identity_fakes.region_id),
+ ('description', identity_fakes.limit_description)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'description': identity_fakes.limit_description,
+ 'region': self.region
+ }
+ self.limit_mock.create.assert_called_with(
+ self.project,
+ self.service,
+ identity_fakes.limit_resource_name,
+ resource_limit,
+ **kwargs
+ )
+
+ collist = ('description', 'id', 'project_id', 'region_id',
+ 'resource_limit', 'resource_name', 'service_id')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.limit_description,
+ identity_fakes.limit_id,
+ identity_fakes.project_id,
+ identity_fakes.region_id,
+ resource_limit,
+ identity_fakes.limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+
+class TestLimitDelete(TestLimit):
+
+ def setUp(self):
+ super(TestLimitDelete, self).setUp()
+ self.cmd = limit.DeleteLimit(self.app, None)
+
+ def test_limit_delete(self):
+ self.limit_mock.delete.return_value = None
+
+ arglist = [identity_fakes.limit_id]
+ verifylist = [
+ ('limit_id', [identity_fakes.limit_id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.limit_mock.delete.assert_called_with(
+ identity_fakes.limit_id
+ )
+ self.assertIsNone(result)
+
+ def test_limit_delete_with_exception(self):
+ return_value = ksa_exceptions.NotFound()
+ self.limit_mock.delete.side_effect = return_value
+
+ arglist = ['fake-limit-id']
+ verifylist = [
+ ('limit_id', ['fake-limit-id'])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(
+ '1 of 1 limits failed to delete.', str(e)
+ )
+
+
+class TestLimitShow(TestLimit):
+
+ def setUp(self):
+ super(TestLimitShow, self).setUp()
+
+ self.limit_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.LIMIT),
+ loaded=True
+ )
+
+ self.cmd = limit.ShowLimit(self.app, None)
+
+ def test_limit_show(self):
+ arglist = [identity_fakes.limit_id]
+ verifylist = [('limit_id', identity_fakes.limit_id)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.limit_mock.get.assert_called_with(identity_fakes.limit_id)
+
+ collist = (
+ 'description', 'id', 'project_id', 'region_id', 'resource_limit',
+ 'resource_name', 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ None,
+ identity_fakes.limit_id,
+ identity_fakes.project_id,
+ None,
+ identity_fakes.limit_resource_limit,
+ identity_fakes.limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+
+class TestLimitSet(TestLimit):
+
+ def setUp(self):
+ super(TestLimitSet, self).setUp()
+ self.cmd = limit.SetLimit(self.app, None)
+
+ def test_limit_set_description(self):
+ limit = copy.deepcopy(identity_fakes.LIMIT)
+ limit['description'] = identity_fakes.limit_description
+ self.limit_mock.update.return_value = fakes.FakeResource(
+ None, limit, loaded=True
+ )
+
+ arglist = [
+ '--description', identity_fakes.limit_description,
+ identity_fakes.limit_id
+ ]
+ verifylist = [
+ ('description', identity_fakes.limit_description),
+ ('limit_id', identity_fakes.limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.limit_mock.update.assert_called_with(
+ identity_fakes.limit_id,
+ description=identity_fakes.limit_description,
+ resource_limit=None
+ )
+
+ collist = (
+ 'description', 'id', 'project_id', 'region_id', 'resource_limit',
+ 'resource_name', 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.limit_description,
+ identity_fakes.limit_id,
+ identity_fakes.project_id,
+ None,
+ identity_fakes.limit_resource_limit,
+ identity_fakes.limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+ def test_limit_set_resource_limit(self):
+ resource_limit = 20
+ limit = copy.deepcopy(identity_fakes.LIMIT)
+ limit['resource_limit'] = resource_limit
+ self.limit_mock.update.return_value = fakes.FakeResource(
+ None, limit, loaded=True
+ )
+
+ arglist = [
+ '--resource-limit', str(resource_limit),
+ identity_fakes.limit_id
+ ]
+ verifylist = [
+ ('resource_limit', resource_limit),
+ ('limit_id', identity_fakes.limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.limit_mock.update.assert_called_with(
+ identity_fakes.limit_id,
+ description=None,
+ resource_limit=resource_limit
+ )
+
+ collist = (
+ 'description', 'id', 'project_id', 'region_id', 'resource_limit',
+ 'resource_name', 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ None,
+ identity_fakes.limit_id,
+ identity_fakes.project_id,
+ None,
+ resource_limit,
+ identity_fakes.limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+
+class TestLimitList(TestLimit):
+
+ def setUp(self):
+ super(TestLimitList, self).setUp()
+
+ self.limit_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.LIMIT),
+ loaded=True
+ )
+ ]
+
+ self.cmd = limit.ListLimit(self.app, None)
+
+ def test_limit_list(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.limit_mock.list.assert_called_with(
+ service=None, resource_name=None, region=None
+ )
+
+ collist = (
+ 'ID', 'Project ID', 'Service ID', 'Resource Name',
+ 'Resource Limit', 'Description', 'Region ID'
+ )
+ self.assertEqual(collist, columns)
+ datalist = ((
+ identity_fakes.limit_id,
+ identity_fakes.project_id,
+ identity_fakes.service_id,
+ identity_fakes.limit_resource_name,
+ identity_fakes.limit_resource_limit,
+ None,
+ None
+ ), )
+ self.assertEqual(datalist, tuple(data))
diff --git a/openstackclient/tests/unit/identity/v3/test_registered_limit.py b/openstackclient/tests/unit/identity/v3/test_registered_limit.py
new file mode 100644
index 00000000..262ca4f9
--- /dev/null
+++ b/openstackclient/tests/unit/identity/v3/test_registered_limit.py
@@ -0,0 +1,510 @@
+# 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 copy
+
+from keystoneauth1.exceptions import http as ksa_exceptions
+from osc_lib import exceptions
+
+from openstackclient.identity.v3 import registered_limit
+from openstackclient.tests.unit import fakes
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
+
+
+class TestRegisteredLimit(identity_fakes.TestIdentityv3):
+
+ def setUp(self):
+ super(TestRegisteredLimit, self).setUp()
+
+ identity_manager = self.app.client_manager.identity
+ self.registered_limit_mock = identity_manager.registered_limits
+
+ self.services_mock = identity_manager.services
+ self.services_mock.reset_mock()
+
+ self.regions_mock = identity_manager.regions
+ self.regions_mock.reset_mock()
+
+
+class TestRegisteredLimitCreate(TestRegisteredLimit):
+
+ def setUp(self):
+ super(TestRegisteredLimitCreate, self).setUp()
+
+ self.service = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.SERVICE),
+ loaded=True
+ )
+ self.services_mock.get.return_value = self.service
+
+ self.region = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.REGION),
+ loaded=True
+ )
+ self.regions_mock.get.return_value = self.region
+
+ self.cmd = registered_limit.CreateRegisteredLimit(self.app, None)
+
+ def test_registered_limit_create_without_options(self):
+ self.registered_limit_mock.create.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.REGISTERED_LIMIT),
+ loaded=True
+ )
+
+ resource_name = identity_fakes.registered_limit_resource_name
+ default_limit = identity_fakes.registered_limit_default_limit
+ arglist = [
+ '--service', identity_fakes.service_id,
+ '--default-limit', '10',
+ resource_name,
+ ]
+
+ verifylist = [
+ ('service', identity_fakes.service_id),
+ ('default_limit', default_limit),
+ ('resource_name', resource_name)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {'description': None, 'region': None}
+ self.registered_limit_mock.create.assert_called_with(
+ self.service, resource_name, default_limit, **kwargs
+ )
+
+ collist = ('default_limit', 'description', 'id', 'region_id',
+ 'resource_name', 'service_id')
+
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.registered_limit_default_limit,
+ None,
+ identity_fakes.registered_limit_id,
+ None,
+ identity_fakes.registered_limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+ def test_registered_limit_create_with_options(self):
+ self.registered_limit_mock.create.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.REGISTERED_LIMIT_OPTIONS),
+ loaded=True
+ )
+
+ resource_name = identity_fakes.registered_limit_resource_name
+ default_limit = identity_fakes.registered_limit_default_limit
+ description = identity_fakes.registered_limit_description
+ arglist = [
+ '--region', identity_fakes.region_id,
+ '--description', description,
+ '--service', identity_fakes.service_id,
+ '--default-limit', '10',
+ resource_name
+ ]
+
+ verifylist = [
+ ('region', identity_fakes.region_id),
+ ('description', description),
+ ('service', identity_fakes.service_id),
+ ('default_limit', default_limit),
+ ('resource_name', resource_name)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {'description': description, 'region': self.region}
+ self.registered_limit_mock.create.assert_called_with(
+ self.service, resource_name, default_limit, **kwargs
+ )
+
+ collist = ('default_limit', 'description', 'id', 'region_id',
+ 'resource_name', 'service_id')
+
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.registered_limit_default_limit,
+ description,
+ identity_fakes.registered_limit_id,
+ identity_fakes.region_id,
+ identity_fakes.registered_limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+
+class TestRegisteredLimitDelete(TestRegisteredLimit):
+
+ def setUp(self):
+ super(TestRegisteredLimitDelete, self).setUp()
+
+ self.cmd = registered_limit.DeleteRegisteredLimit(self.app, None)
+
+ def test_registered_limit_delete(self):
+ self.registered_limit_mock.delete.return_value = None
+
+ arglist = [identity_fakes.registered_limit_id]
+ verifylist = [
+ ('registered_limit_id', [identity_fakes.registered_limit_id])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.registered_limit_mock.delete.assert_called_with(
+ identity_fakes.registered_limit_id
+ )
+ self.assertIsNone(result)
+
+ def test_registered_limit_delete_with_exception(self):
+ return_value = ksa_exceptions.NotFound()
+ self.registered_limit_mock.delete.side_effect = return_value
+
+ arglist = ['fake-registered-limit-id']
+ verifylist = [
+ ('registered_limit_id', ['fake-registered-limit-id'])
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(
+ '1 of 1 registered limits failed to delete.', str(e)
+ )
+
+
+class TestRegisteredLimitShow(TestRegisteredLimit):
+
+ def setUp(self):
+ super(TestRegisteredLimitShow, self).setUp()
+
+ self.registered_limit_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.REGISTERED_LIMIT),
+ loaded=True
+ )
+
+ self.cmd = registered_limit.ShowRegisteredLimit(self.app, None)
+
+ def test_registered_limit_show(self):
+ arglist = [identity_fakes.registered_limit_id]
+ verifylist = [
+ ('registered_limit_id', identity_fakes.registered_limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.registered_limit_mock.get.assert_called_with(
+ identity_fakes.registered_limit_id
+ )
+
+ collist = (
+ 'default_limit', 'description', 'id', 'region_id', 'resource_name',
+ 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.registered_limit_default_limit,
+ None,
+ identity_fakes.registered_limit_id,
+ None,
+ identity_fakes.registered_limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+
+class TestRegisteredLimitSet(TestRegisteredLimit):
+
+ def setUp(self):
+ super(TestRegisteredLimitSet, self).setUp()
+ self.cmd = registered_limit.SetRegisteredLimit(self.app, None)
+
+ def test_registered_limit_set_description(self):
+ registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
+ registered_limit['description'] = (
+ identity_fakes.registered_limit_description
+ )
+ self.registered_limit_mock.update.return_value = fakes.FakeResource(
+ None, registered_limit, loaded=True
+ )
+
+ arglist = [
+ '--description', identity_fakes.registered_limit_description,
+ identity_fakes.registered_limit_id
+ ]
+ verifylist = [
+ ('description', identity_fakes.registered_limit_description),
+ ('registered_limit_id', identity_fakes.registered_limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.registered_limit_mock.update.assert_called_with(
+ identity_fakes.registered_limit_id,
+ service=None,
+ resource_name=None,
+ default_limit=None,
+ description=identity_fakes.registered_limit_description,
+ region=None
+ )
+
+ collist = (
+ 'default_limit', 'description', 'id', 'region_id', 'resource_name',
+ 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.registered_limit_default_limit,
+ identity_fakes.registered_limit_description,
+ identity_fakes.registered_limit_id,
+ None,
+ identity_fakes.registered_limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+ def test_registered_limit_set_default_limit(self):
+ registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
+ default_limit = 20
+ registered_limit['default_limit'] = default_limit
+ self.registered_limit_mock.update.return_value = fakes.FakeResource(
+ None, registered_limit, loaded=True
+ )
+
+ arglist = [
+ '--default-limit', str(default_limit),
+ identity_fakes.registered_limit_id
+ ]
+ verifylist = [
+ ('default_limit', default_limit),
+ ('registered_limit_id', identity_fakes.registered_limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.registered_limit_mock.update.assert_called_with(
+ identity_fakes.registered_limit_id,
+ service=None,
+ resource_name=None,
+ default_limit=default_limit,
+ description=None,
+ region=None
+ )
+
+ collist = (
+ 'default_limit', 'description', 'id', 'region_id', 'resource_name',
+ 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ default_limit,
+ None,
+ identity_fakes.registered_limit_id,
+ None,
+ identity_fakes.registered_limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+ def test_registered_limit_set_resource_name(self):
+ registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
+ resource_name = 'volumes'
+ registered_limit['resource_name'] = resource_name
+ self.registered_limit_mock.update.return_value = fakes.FakeResource(
+ None, registered_limit, loaded=True
+ )
+
+ arglist = [
+ '--resource-name', resource_name,
+ identity_fakes.registered_limit_id
+ ]
+ verifylist = [
+ ('resource_name', resource_name),
+ ('registered_limit_id', identity_fakes.registered_limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.registered_limit_mock.update.assert_called_with(
+ identity_fakes.registered_limit_id,
+ service=None,
+ resource_name=resource_name,
+ default_limit=None,
+ description=None,
+ region=None
+ )
+
+ collist = (
+ 'default_limit', 'description', 'id', 'region_id', 'resource_name',
+ 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.registered_limit_default_limit,
+ None,
+ identity_fakes.registered_limit_id,
+ None,
+ resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+ def test_registered_limit_set_service(self):
+ registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
+ service = identity_fakes.FakeService.create_one_service()
+ registered_limit['service_id'] = service.id
+ self.registered_limit_mock.update.return_value = fakes.FakeResource(
+ None, registered_limit, loaded=True
+ )
+ self.services_mock.get.return_value = service
+
+ arglist = [
+ '--service', service.id,
+ identity_fakes.registered_limit_id
+ ]
+ verifylist = [
+ ('service', service.id),
+ ('registered_limit_id', identity_fakes.registered_limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.registered_limit_mock.update.assert_called_with(
+ identity_fakes.registered_limit_id,
+ service=service,
+ resource_name=None,
+ default_limit=None,
+ description=None,
+ region=None
+ )
+
+ collist = (
+ 'default_limit', 'description', 'id', 'region_id', 'resource_name',
+ 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.registered_limit_default_limit,
+ None,
+ identity_fakes.registered_limit_id,
+ None,
+ identity_fakes.registered_limit_resource_name,
+ service.id
+ )
+ self.assertEqual(datalist, data)
+
+ def test_registered_limit_set_region(self):
+ registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT)
+ region = identity_fakes.REGION
+ region['id'] = 'RegionTwo'
+ region = fakes.FakeResource(
+ None,
+ copy.deepcopy(region),
+ loaded=True
+ )
+ registered_limit['region_id'] = region.id
+ self.registered_limit_mock.update.return_value = fakes.FakeResource(
+ None, registered_limit, loaded=True
+ )
+ self.regions_mock.get.return_value = region
+
+ arglist = [
+ '--region', region.id,
+ identity_fakes.registered_limit_id
+ ]
+ verifylist = [
+ ('region', region.id),
+ ('registered_limit_id', identity_fakes.registered_limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.registered_limit_mock.update.assert_called_with(
+ identity_fakes.registered_limit_id,
+ service=None,
+ resource_name=None,
+ default_limit=None,
+ description=None,
+ region=region
+ )
+
+ collist = (
+ 'default_limit', 'description', 'id', 'region_id', 'resource_name',
+ 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.registered_limit_default_limit,
+ None,
+ identity_fakes.registered_limit_id,
+ region.id,
+ identity_fakes.registered_limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
+
+
+class TestRegisteredLimitList(TestRegisteredLimit):
+
+ def setUp(self):
+ super(TestRegisteredLimitList, self).setUp()
+
+ self.registered_limit_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.REGISTERED_LIMIT),
+ loaded=True
+ )
+
+ self.cmd = registered_limit.ShowRegisteredLimit(self.app, None)
+
+ def test_limit_show(self):
+ arglist = [identity_fakes.registered_limit_id]
+ verifylist = [
+ ('registered_limit_id', identity_fakes.registered_limit_id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.registered_limit_mock.get.assert_called_with(
+ identity_fakes.registered_limit_id
+ )
+
+ collist = (
+ 'default_limit', 'description', 'id', 'region_id', 'resource_name',
+ 'service_id'
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.registered_limit_default_limit,
+ None,
+ identity_fakes.registered_limit_id,
+ None,
+ identity_fakes.registered_limit_resource_name,
+ identity_fakes.service_id
+ )
+ self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py
index 835837e6..bff6c56d 100644
--- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py
+++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py
@@ -34,6 +34,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
'Group',
'Project',
'Domain',
+ 'System',
'Inherited',
)
@@ -95,6 +96,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
group=None,
effective=False,
role=None,
@@ -110,12 +112,14 @@ class TestRoleAssignmentList(TestRoleAssignment):
'',
identity_fakes.project_id,
'',
+ '',
False
), (identity_fakes.role_id,
'',
identity_fakes.group_id,
identity_fakes.project_id,
'',
+ '',
False
),)
self.assertEqual(datalist, tuple(data))
@@ -143,6 +147,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', identity_fakes.user_name),
('group', None),
+ ('system', None),
('domain', None),
('project', None),
('role', None),
@@ -159,6 +164,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
user=self.users_mock.get(),
group=None,
project=None,
@@ -174,12 +180,14 @@ class TestRoleAssignmentList(TestRoleAssignment):
'',
'',
identity_fakes.domain_id,
+ '',
False
), (identity_fakes.role_id,
identity_fakes.user_id,
'',
identity_fakes.project_id,
'',
+ '',
False
),)
self.assertEqual(datalist, tuple(data))
@@ -207,6 +215,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', None),
('group', identity_fakes.group_name),
+ ('system', None),
('domain', None),
('project', None),
('role', None),
@@ -223,6 +232,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
group=self.groups_mock.get(),
effective=False,
project=None,
@@ -238,12 +248,14 @@ class TestRoleAssignmentList(TestRoleAssignment):
identity_fakes.group_id,
'',
identity_fakes.domain_id,
+ '',
False
), (identity_fakes.role_id,
'',
identity_fakes.group_id,
identity_fakes.project_id,
'',
+ '',
False
),)
self.assertEqual(datalist, tuple(data))
@@ -271,6 +283,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', None),
('group', None),
+ ('system', None),
('domain', identity_fakes.domain_name),
('project', None),
('role', None),
@@ -287,6 +300,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=self.domains_mock.get(),
+ system=None,
group=None,
effective=False,
project=None,
@@ -302,12 +316,14 @@ class TestRoleAssignmentList(TestRoleAssignment):
'',
'',
identity_fakes.domain_id,
+ '',
False
), (identity_fakes.role_id,
'',
identity_fakes.group_id,
'',
identity_fakes.domain_id,
+ '',
False
),)
self.assertEqual(datalist, tuple(data))
@@ -335,6 +351,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', None),
('group', None),
+ ('system', None),
('domain', None),
('project', identity_fakes.project_name),
('role', None),
@@ -351,6 +368,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
group=None,
effective=False,
project=self.projects_mock.get(),
@@ -366,12 +384,14 @@ class TestRoleAssignmentList(TestRoleAssignment):
'',
identity_fakes.project_id,
'',
+ '',
False
), (identity_fakes.role_id,
'',
identity_fakes.group_id,
identity_fakes.project_id,
'',
+ '',
False
),)
self.assertEqual(datalist, tuple(data))
@@ -398,6 +418,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', None),
('group', None),
+ ('system', None),
('domain', None),
('project', None),
('role', None),
@@ -416,6 +437,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
user=self.users_mock.get(),
group=None,
project=self.projects_mock.get(),
@@ -431,6 +453,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
'',
identity_fakes.project_id,
'',
+ '',
False
),)
self.assertEqual(datalist, tuple(data))
@@ -456,6 +479,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', None),
('group', None),
+ ('system', None),
('domain', None),
('project', None),
('role', None),
@@ -472,6 +496,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
group=None,
effective=True,
project=None,
@@ -487,12 +512,14 @@ class TestRoleAssignmentList(TestRoleAssignment):
'',
identity_fakes.project_id,
'',
+ '',
False
), (identity_fakes.role_id,
identity_fakes.user_id,
'',
'',
identity_fakes.domain_id,
+ '',
False
),)
self.assertEqual(tuple(data), datalist)
@@ -520,6 +547,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', None),
('group', None),
+ ('system', None),
('domain', None),
('project', None),
('role', None),
@@ -536,6 +564,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
group=None,
effective=False,
project=None,
@@ -551,12 +580,14 @@ class TestRoleAssignmentList(TestRoleAssignment):
'',
identity_fakes.project_id,
'',
+ '',
True
), (identity_fakes.role_id,
identity_fakes.user_id,
'',
'',
identity_fakes.domain_id,
+ '',
True
),)
self.assertEqual(datalist, tuple(data))
@@ -584,6 +615,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', None),
('group', None),
+ ('system', None),
('domain', None),
('project', None),
('role', None),
@@ -602,6 +634,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
group=None,
effective=False,
project=None,
@@ -610,7 +643,9 @@ class TestRoleAssignmentList(TestRoleAssignment):
os_inherit_extension_inherited_to=None,
include_names=True)
- collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited')
+ collist = (
+ 'Role', 'User', 'Group', 'Project', 'Domain', 'System', 'Inherited'
+ )
self.assertEqual(columns, collist)
datalist1 = ((
@@ -620,12 +655,14 @@ class TestRoleAssignmentList(TestRoleAssignment):
'@'.join([identity_fakes.project_name,
identity_fakes.domain_name]),
'',
+ '',
False
), (identity_fakes.role_name,
'@'.join([identity_fakes.user_name, identity_fakes.domain_name]),
'',
'',
identity_fakes.domain_name,
+ '',
False
),)
self.assertEqual(tuple(data), datalist1)
@@ -648,6 +685,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
verifylist = [
('user', None),
('group', None),
+ ('system', None),
('domain', None),
('project', None),
('role', identity_fakes.ROLE_2['name']),
@@ -664,6 +702,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
self.role_assignments_mock.list.assert_called_with(
domain=None,
+ system=None,
user=None,
group=None,
project=None,
@@ -679,6 +718,7 @@ class TestRoleAssignmentList(TestRoleAssignment):
'',
'',
identity_fakes.domain_id,
+ '',
False
),)
self.assertEqual(datalist, tuple(data))
diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py
index e1a79d13..170a7f03 100644
--- a/openstackclient/tests/unit/image/v2/test_image.py
+++ b/openstackclient/tests/unit/image/v2/test_image.py
@@ -527,6 +527,7 @@ class TestImageList(TestImage):
verifylist = [
('public', False),
('private', False),
+ ('community', False),
('shared', False),
('long', False),
]
@@ -550,6 +551,7 @@ class TestImageList(TestImage):
verifylist = [
('public', True),
('private', False),
+ ('community', False),
('shared', False),
('long', False),
]
@@ -574,6 +576,7 @@ class TestImageList(TestImage):
verifylist = [
('public', False),
('private', True),
+ ('community', False),
('shared', False),
('long', False),
]
@@ -591,6 +594,31 @@ class TestImageList(TestImage):
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
+ def test_image_list_community_option(self):
+ arglist = [
+ '--community',
+ ]
+ verifylist = [
+ ('public', False),
+ ('private', False),
+ ('community', True),
+ ('shared', False),
+ ('long', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+ self.api_mock.image_list.assert_called_with(
+ community=True,
+ marker=self._image.id,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist, tuple(data))
+
def test_image_list_shared_option(self):
arglist = [
'--shared',
@@ -598,6 +626,7 @@ class TestImageList(TestImage):
verifylist = [
('public', False),
('private', False),
+ ('community', False),
('shared', True),
('long', False),
]
@@ -708,7 +737,8 @@ class TestImageList(TestImage):
)
si_mock.assert_called_with(
[self._image],
- 'name:asc'
+ 'name:asc',
+ str,
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
@@ -779,6 +809,66 @@ class TestImageList(TestImage):
status='active', marker=self._image.id
)
+ def test_image_list_tag_option(self):
+ arglist = [
+ '--tag', 'abc',
+ ]
+ verifylist = [
+ ('tag', 'abc'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.api_mock.image_list.assert_called_with(
+ tag='abc', marker=self._image.id
+ )
+
+
+class TestListImageProjects(TestImage):
+
+ project = identity_fakes.FakeProject.create_one_project()
+ _image = image_fakes.FakeImage.create_one_image()
+ member = image_fakes.FakeImage.create_one_image_member(
+ attrs={'image_id': _image.id,
+ 'member_id': project.id}
+ )
+
+ columns = (
+ "Image ID",
+ "Member ID",
+ "Status"
+ )
+
+ datalist = ((
+ _image.id,
+ member.member_id,
+ member.status,
+ ))
+
+ def setUp(self):
+ super(TestListImageProjects, self).setUp()
+
+ self.images_mock.get.return_value = self._image
+ self.image_members_mock.list.return_value = self.datalist
+
+ self.cmd = image.ListImageProjects(self.app, None)
+
+ def test_image_member_list(self):
+ arglist = [
+ self._image.id
+ ]
+ verifylist = [
+ ('image', self._image.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.image_members_mock.list.assert_called_with(self._image.id)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(len(self.datalist), len(tuple(data)))
+
class TestRemoveProjectImage(TestImage):
diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py
index 4291dd1d..aec7402f 100644
--- a/openstackclient/tests/unit/network/v2/fakes.py
+++ b/openstackclient/tests/unit/network/v2/fakes.py
@@ -335,6 +335,8 @@ class FakeNetwork(object):
'name': 'network-name-' + uuid.uuid4().hex,
'status': 'ACTIVE',
'description': 'network-description-' + uuid.uuid4().hex,
+ 'dns_domain': 'example.org.',
+ 'mtu': '1350',
'tenant_id': 'project-id-' + uuid.uuid4().hex,
'admin_state_up': True,
'shared': False,
@@ -347,7 +349,6 @@ class FakeNetwork(object):
'availability_zone_hints': [],
'is_default': False,
'port_security_enabled': True,
- 'tags': ['test'],
'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex,
'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex,
'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex,
@@ -1132,6 +1133,7 @@ class FakeSecurityGroup(object):
'description': 'security-group-description-' + uuid.uuid4().hex,
'project_id': 'project-id-' + uuid.uuid4().hex,
'security_group_rules': [],
+ 'tags': []
}
# Overwrite default attributes.
@@ -1380,6 +1382,7 @@ class FakeFloatingIP(object):
'tenant_id': 'project-id-' + uuid.uuid4().hex,
'description': 'floating-ip-description-' + uuid.uuid4().hex,
'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex,
+ 'tags': [],
}
# Overwrite default attributes.
diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py
index f19849c4..209c01cf 100644
--- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py
+++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py
@@ -44,11 +44,13 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
subnet = network_fakes.FakeSubnet.create_one_subnet()
port = network_fakes.FakePort.create_one_port()
- # The floating ip to be deleted.
+ # The floating ip created.
floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip(
attrs={
'floating_network_id': floating_network.id,
'port_id': port.id,
+ 'dns_domain': 'example.org.',
+ 'dns_name': 'fip1',
}
)
@@ -65,6 +67,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
'qos_policy_id',
'router_id',
'status',
+ 'tags',
)
data = (
@@ -80,12 +83,14 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
floating_ip.qos_policy_id,
floating_ip.router_id,
floating_ip.status,
+ floating_ip.tags,
)
def setUp(self):
super(TestCreateFloatingIPNetwork, self).setUp()
self.network.create_ip = mock.Mock(return_value=self.floating_ip)
+ self.network.set_tags = mock.Mock(return_value=None)
self.network.find_network = mock.Mock(
return_value=self.floating_network)
@@ -126,6 +131,8 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
'--floating-ip-address', self.floating_ip.floating_ip_address,
'--fixed-ip-address', self.floating_ip.fixed_ip_address,
'--description', self.floating_ip.description,
+ '--dns-domain', self.floating_ip.dns_domain,
+ '--dns-name', self.floating_ip.dns_name,
self.floating_ip.floating_network_id,
]
verifylist = [
@@ -134,6 +141,8 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
('fixed_ip_address', self.floating_ip.fixed_ip_address),
('network', self.floating_ip.floating_network_id),
('description', self.floating_ip.description),
+ ('dns_domain', self.floating_ip.dns_domain),
+ ('dns_name', self.floating_ip.dns_name),
('floating_ip_address', self.floating_ip.floating_ip_address),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -147,6 +156,8 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
'fixed_ip_address': self.floating_ip.fixed_ip_address,
'floating_network_id': self.floating_ip.floating_network_id,
'description': self.floating_ip.description,
+ 'dns_domain': self.floating_ip.dns_domain,
+ 'dns_name': self.floating_ip.dns_name,
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -221,6 +232,42 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def _test_create_with_tag(self, add_tags=True):
+ arglist = [self.floating_ip.floating_network_id]
+ if add_tags:
+ arglist += ['--tag', 'red', '--tag', 'blue']
+ else:
+ arglist += ['--no-tag']
+
+ verifylist = [
+ ('network', self.floating_ip.floating_network_id),
+ ]
+ if add_tags:
+ verifylist.append(('tags', ['red', 'blue']))
+ else:
+ verifylist.append(('no_tag', True))
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_ip.assert_called_once_with(**{
+ 'floating_network_id': self.floating_ip.floating_network_id,
+ })
+ if add_tags:
+ self.network.set_tags.assert_called_once_with(
+ self.floating_ip,
+ tests_utils.CompareBySet(['red', 'blue']))
+ else:
+ self.assertFalse(self.network.set_tags.called)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_with_tags(self):
+ self._test_create_with_tag(add_tags=True)
+
+ def test_create_with_no_tag(self):
+ self._test_create_with_tag(add_tags=False)
+
class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork):
@@ -353,6 +400,9 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork):
'Router',
'Status',
'Description',
+ 'Tags',
+ 'DNS Name',
+ 'DNS Domain',
)
data = []
@@ -376,6 +426,9 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork):
ip.router_id,
ip.status,
ip.description,
+ ip.tags,
+ ip.dns_domain,
+ ip.dns_name,
))
def setUp(self):
@@ -539,6 +592,31 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork):
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, list(data))
+ def test_list_with_tag_options(self):
+ arglist = [
+ '--tags', 'red,blue',
+ '--any-tags', 'red,green',
+ '--not-tags', 'orange,yellow',
+ '--not-any-tags', 'black,white',
+ ]
+ verifylist = [
+ ('tags', ['red', 'blue']),
+ ('any_tags', ['red', 'green']),
+ ('not_tags', ['orange', 'yellow']),
+ ('not_any_tags', ['black', 'white']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ips.assert_called_once_with(
+ **{'tags': 'red,blue',
+ 'any_tags': 'red,green',
+ 'not_tags': 'orange,yellow',
+ 'not_any_tags': 'black,white'}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestShowFloatingIPNetwork(TestFloatingIPNetwork):
@@ -558,6 +636,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork):
'qos_policy_id',
'router_id',
'status',
+ 'tags',
)
data = (
@@ -573,6 +652,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork):
floating_ip.qos_policy_id,
floating_ip.router_id,
floating_ip.status,
+ floating_ip.tags,
)
def setUp(self):
@@ -609,11 +689,12 @@ class TestSetFloatingIP(TestFloatingIPNetwork):
subnet = network_fakes.FakeSubnet.create_one_subnet()
port = network_fakes.FakePort.create_one_port()
- # The floating ip to be deleted.
+ # The floating ip to be set.
floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip(
attrs={
'floating_network_id': floating_network.id,
'port_id': port.id,
+ 'tags': ['green', 'red'],
}
)
@@ -622,6 +703,7 @@ class TestSetFloatingIP(TestFloatingIPNetwork):
self.network.find_ip = mock.Mock(return_value=self.floating_ip)
self.network.find_port = mock.Mock(return_value=self.port)
self.network.update_ip = mock.Mock(return_value=None)
+ self.network.set_tags = mock.Mock(return_value=None)
# Get the command object to test
self.cmd = fip.SetFloatingIP(self.app, self.namespace)
@@ -677,6 +759,31 @@ class TestSetFloatingIP(TestFloatingIPNetwork):
self.network.update_ip.assert_called_once_with(
self.floating_ip, **attrs)
+ def test_qos_policy_option(self):
+ qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()
+ self.network.find_qos_policy = mock.Mock(return_value=qos_policy)
+ arglist = [
+ "--qos-policy", qos_policy.id,
+ self.floating_ip.id,
+ ]
+ verifylist = [
+ ('qos_policy', qos_policy.id),
+ ('floating_ip', self.floating_ip.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'qos_policy_id': qos_policy.id,
+ }
+ self.network.find_ip.assert_called_once_with(
+ self.floating_ip.id,
+ ignore_missing=False,
+ )
+ self.network.update_ip.assert_called_once_with(
+ self.floating_ip, **attrs)
+
def test_port_and_qos_policy_option(self):
qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()
self.network.find_qos_policy = mock.Mock(return_value=qos_policy)
@@ -705,6 +812,29 @@ class TestSetFloatingIP(TestFloatingIPNetwork):
self.network.update_ip.assert_called_once_with(
self.floating_ip, **attrs)
+ def test_no_qos_policy_option(self):
+ arglist = [
+ "--no-qos-policy",
+ self.floating_ip.id,
+ ]
+ verifylist = [
+ ('no_qos_policy', True),
+ ('floating_ip', self.floating_ip.id),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'qos_policy_id': None,
+ }
+ self.network.find_ip.assert_called_once_with(
+ self.floating_ip.id,
+ ignore_missing=False,
+ )
+ self.network.update_ip.assert_called_once_with(
+ self.floating_ip, **attrs)
+
def test_port_and_no_qos_policy_option(self):
arglist = [
"--no-qos-policy",
@@ -731,6 +861,33 @@ class TestSetFloatingIP(TestFloatingIPNetwork):
self.network.update_ip.assert_called_once_with(
self.floating_ip, **attrs)
+ def _test_set_tags(self, with_tags=True):
+ if with_tags:
+ arglist = ['--tag', 'red', '--tag', 'blue']
+ verifylist = [('tags', ['red', 'blue'])]
+ expected_args = ['red', 'blue', 'green']
+ else:
+ arglist = ['--no-tag']
+ verifylist = [('no_tag', True)]
+ expected_args = []
+ arglist.extend([self.floating_ip.id])
+ verifylist.extend([('floating_ip', self.floating_ip.id)])
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertFalse(self.network.update_ip.called)
+ self.network.set_tags.assert_called_once_with(
+ self.floating_ip,
+ tests_utils.CompareBySet(expected_args))
+ self.assertIsNone(result)
+
+ def test_set_with_tags(self):
+ self._test_set_tags(with_tags=True)
+
+ def test_set_with_no_tag(self):
+ self._test_set_tags(with_tags=False)
+
class TestUnsetFloatingIP(TestFloatingIPNetwork):
@@ -738,11 +895,12 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork):
subnet = network_fakes.FakeSubnet.create_one_subnet()
port = network_fakes.FakePort.create_one_port()
- # The floating ip to be deleted.
+ # The floating ip to be unset.
floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip(
attrs={
'floating_network_id': floating_network.id,
'port_id': port.id,
+ 'tags': ['green', 'red'],
}
)
@@ -750,6 +908,7 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork):
super(TestUnsetFloatingIP, self).setUp()
self.network.find_ip = mock.Mock(return_value=self.floating_ip)
self.network.update_ip = mock.Mock(return_value=None)
+ self.network.set_tags = mock.Mock(return_value=None)
# Get the command object to test
self.cmd = fip.UnsetFloatingIP(self.app, self.namespace)
@@ -803,3 +962,31 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork):
self.floating_ip, **attrs)
self.assertIsNone(result)
+
+ def _test_unset_tags(self, with_tags=True):
+ if with_tags:
+ arglist = ['--tag', 'red', '--tag', 'blue']
+ verifylist = [('tags', ['red', 'blue'])]
+ expected_args = ['green']
+ else:
+ arglist = ['--all-tag']
+ verifylist = [('all_tag', True)]
+ expected_args = []
+ arglist.append(self.floating_ip.id)
+ verifylist.append(
+ ('floating_ip', self.floating_ip.id))
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertFalse(self.network.update_ip.called)
+ self.network.set_tags.assert_called_once_with(
+ self.floating_ip,
+ tests_utils.CompareBySet(expected_args))
+ self.assertIsNone(result)
+
+ def test_unset_with_tags(self):
+ self._test_unset_tags(with_tags=True)
+
+ def test_unset_with_all_tag(self):
+ self._test_unset_tags(with_tags=False)
diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py
index 357088f3..0f57f0ee 100644
--- a/openstackclient/tests/unit/network/v2/test_network.py
+++ b/openstackclient/tests/unit/network/v2/test_network.py
@@ -60,10 +60,12 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'availability_zone_hints',
'availability_zones',
'description',
+ 'dns_domain',
'id',
'ipv4_address_scope',
'ipv6_address_scope',
'is_default',
+ 'mtu',
'name',
'port_security_enabled',
'project_id',
@@ -83,10 +85,12 @@ class TestCreateNetworkIdentityV3(TestNetwork):
utils.format_list(_network.availability_zone_hints),
utils.format_list(_network.availability_zones),
_network.description,
+ _network.dns_domain,
_network.id,
_network.ipv4_address_scope_id,
_network.ipv6_address_scope_id,
_network.is_default,
+ _network.mtu,
_network.name,
_network.is_port_security_enabled,
_network.project_id,
@@ -149,6 +153,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
"--disable",
"--share",
"--description", self._network.description,
+ "--mtu", self._network.mtu,
"--project", self.project.name,
"--project-domain", self.domain.name,
"--availability-zone-hint", "nova",
@@ -159,12 +164,14 @@ class TestCreateNetworkIdentityV3(TestNetwork):
"--qos-policy", self.qos_policy.id,
"--transparent-vlan",
"--enable-port-security",
+ "--dns-domain", "example.org.",
self._network.name,
]
verifylist = [
('disable', True),
('share', True),
('description', self._network.description),
+ ('mtu', self._network.mtu),
('project', self.project.name),
('project_domain', self.domain.name),
('availability_zone_hints', ["nova"]),
@@ -177,6 +184,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
('transparent_vlan', True),
('enable_port_security', True),
('name', self._network.name),
+ ('dns_domain', 'example.org.'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -188,6 +196,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'name': self._network.name,
'shared': True,
'description': self._network.description,
+ 'mtu': self._network.mtu,
# TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor
'tenant_id': self.project.id,
'project_id': self.project.id,
@@ -199,6 +208,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'qos_policy_id': self.qos_policy.id,
'vlan_transparent': True,
'port_security_enabled': True,
+ 'dns_domain': 'example.org.',
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -282,10 +292,12 @@ class TestCreateNetworkIdentityV2(TestNetwork):
'availability_zone_hints',
'availability_zones',
'description',
+ 'dns_domain',
'id',
'ipv4_address_scope',
'ipv6_address_scope',
'is_default',
+ 'mtu',
'name',
'port_security_enabled',
'project_id',
@@ -305,10 +317,12 @@ class TestCreateNetworkIdentityV2(TestNetwork):
utils.format_list(_network.availability_zone_hints),
utils.format_list(_network.availability_zones),
_network.description,
+ _network.dns_domain,
_network.id,
_network.ipv4_address_scope_id,
_network.ipv6_address_scope_id,
_network.is_default,
+ _network.mtu,
_network.name,
_network.is_port_security_enabled,
_network.project_id,
@@ -894,6 +908,7 @@ class TestSetNetwork(TestNetwork):
'--name', 'noob',
'--share',
'--description', self._network.description,
+ '--dns-domain', 'example.org.',
'--external',
'--default',
'--provider-network-type', 'vlan',
@@ -915,6 +930,7 @@ class TestSetNetwork(TestNetwork):
('segmentation_id', '400'),
('enable_port_security', True),
('qos_policy', self.qos_policy.name),
+ ('dns_domain', 'example.org.'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -932,6 +948,7 @@ class TestSetNetwork(TestNetwork):
'provider:segmentation_id': '400',
'port_security_enabled': True,
'qos_policy_id': self.qos_policy.id,
+ 'dns_domain': 'example.org.',
}
self.network.update_network.assert_called_once_with(
self._network, **attrs)
@@ -1019,10 +1036,12 @@ class TestShowNetwork(TestNetwork):
'availability_zone_hints',
'availability_zones',
'description',
+ 'dns_domain',
'id',
'ipv4_address_scope',
'ipv6_address_scope',
'is_default',
+ 'mtu',
'name',
'port_security_enabled',
'project_id',
@@ -1042,10 +1061,12 @@ class TestShowNetwork(TestNetwork):
utils.format_list(_network.availability_zone_hints),
utils.format_list(_network.availability_zones),
_network.description,
+ _network.dns_domain,
_network.id,
_network.ipv4_address_scope_id,
_network.ipv6_address_scope_id,
_network.is_default,
+ _network.mtu,
_network.name,
_network.is_port_security_enabled,
_network.project_id,
diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py
index 176bc86d..5b54d318 100644
--- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py
+++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py
@@ -127,7 +127,7 @@ class TestCreateNetworkQosRuleMinimumBandwidth(TestNetworkQosRule):
self.cmd.take_action(parsed_args)
except exceptions.CommandError as e:
msg = ('"Create" rule command for type "minimum-bandwidth" '
- 'requires arguments direction, min_kbps')
+ 'requires arguments: direction, min_kbps')
self.assertEqual(msg, str(e))
@@ -213,7 +213,7 @@ class TestCreateNetworkQosRuleDSCPMarking(TestNetworkQosRule):
self.cmd.take_action(parsed_args)
except exceptions.CommandError as e:
msg = ('"Create" rule command for type "dscp-marking" '
- 'requires arguments dscp_mark')
+ 'requires arguments: dscp_mark')
self.assertEqual(msg, str(e))
@@ -266,6 +266,49 @@ class TestCreateNetworkQosRuleBandwidtLimit(TestNetworkQosRule):
arglist = [
'--type', RULE_TYPE_BANDWIDTH_LIMIT,
'--max-kbps', str(self.new_rule.max_kbps),
+ '--egress',
+ self.new_rule.qos_policy_id,
+ ]
+
+ verifylist = [
+ ('type', RULE_TYPE_BANDWIDTH_LIMIT),
+ ('max_kbps', self.new_rule.max_kbps),
+ ('egress', True),
+ ('qos_policy', self.new_rule.qos_policy_id),
+ ]
+
+ rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule(
+ {'qos_policy_id': self.qos_policy.id,
+ 'type': RULE_TYPE_BANDWIDTH_LIMIT})
+ rule.max_burst_kbits = 0
+ expected_data = (
+ rule.direction,
+ rule.id,
+ rule.max_burst_kbits,
+ rule.max_kbps,
+ rule.project_id,
+ rule.qos_policy_id,
+ rule.type,
+ )
+
+ with mock.patch.object(
+ self.network, "create_qos_bandwidth_limit_rule",
+ return_value=rule) as create_qos_bandwidth_limit_rule:
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ create_qos_bandwidth_limit_rule.assert_called_once_with(
+ self.qos_policy.id,
+ **{'max_kbps': self.new_rule.max_kbps,
+ 'direction': self.new_rule.direction}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(expected_data, data)
+
+ def test_create_all_options(self):
+ arglist = [
+ '--type', RULE_TYPE_BANDWIDTH_LIMIT,
+ '--max-kbps', str(self.new_rule.max_kbps),
'--max-burst-kbits', str(self.new_rule.max_burst_kbits),
'--egress',
self.new_rule.qos_policy_id,
@@ -309,7 +352,7 @@ class TestCreateNetworkQosRuleBandwidtLimit(TestNetworkQosRule):
self.cmd.take_action(parsed_args)
except exceptions.CommandError as e:
msg = ('"Create" rule command for type "bandwidth-limit" '
- 'requires arguments max_burst_kbps, max_kbps')
+ 'requires arguments: max_kbps')
self.assertEqual(msg, str(e))
@@ -579,7 +622,7 @@ class TestSetNetworkQosRuleMinimumBandwidth(TestNetworkQosRule):
self.cmd.take_action(parsed_args)
except exceptions.CommandError as e:
msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type '
- '"minimum-bandwidth" only requires arguments direction, '
+ '"minimum-bandwidth" only requires arguments: direction, '
'min_kbps' % {'rule': self.new_rule.id})
self.assertEqual(msg, str(e))
@@ -673,7 +716,7 @@ class TestSetNetworkQosRuleDSCPMarking(TestNetworkQosRule):
self.cmd.take_action(parsed_args)
except exceptions.CommandError as e:
msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type '
- '"dscp-marking" only requires arguments dscp_mark' %
+ '"dscp-marking" only requires arguments: dscp_mark' %
{'rule': self.new_rule.id})
self.assertEqual(msg, str(e))
@@ -837,7 +880,7 @@ class TestSetNetworkQosRuleBandwidthLimit(TestNetworkQosRule):
self.cmd.take_action(parsed_args)
except exceptions.CommandError as e:
msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type '
- '"bandwidth-limit" only requires arguments direction, '
+ '"bandwidth-limit" only requires arguments: direction, '
'max_burst_kbps, max_kbps' % {'rule': self.new_rule.id})
self.assertEqual(msg, str(e))
diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py
index 03e1d841..78d7fd6c 100644
--- a/openstackclient/tests/unit/network/v2/test_port.py
+++ b/openstackclient/tests/unit/network/v2/test_port.py
@@ -867,6 +867,24 @@ class TestListPort(TestPort):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_port_list_fixed_ip_opt_ip_address_substr(self):
+ ip_address_ss = self._ports[0].fixed_ips[0]['ip_address'][:-1]
+ arglist = [
+ '--fixed-ip', "ip-substring=%s" % ip_address_ss,
+ ]
+ verifylist = [
+ ('fixed_ip', [{'ip-substring': ip_address_ss}])
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.ports.assert_called_once_with(**{
+ 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss]})
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
def test_port_list_fixed_ip_opt_subnet_id(self):
subnet_id = self._ports[0].fixed_ips[0]['subnet_id']
arglist = [
diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py
index 35b7e366..83208287 100644
--- a/openstackclient/tests/unit/network/v2/test_security_group_network.py
+++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py
@@ -40,8 +40,8 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
project = identity_fakes.FakeProject.create_one_project()
domain = identity_fakes.FakeDomain.create_one_domain()
# The security group to be created.
- _security_group = \
- network_fakes.FakeSecurityGroup.create_one_security_group()
+ _security_group = (
+ network_fakes.FakeSecurityGroup.create_one_security_group())
columns = (
'description',
@@ -49,6 +49,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
'name',
'project_id',
'rules',
+ 'tags',
)
data = (
@@ -57,6 +58,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
_security_group.name,
_security_group.project_id,
'',
+ _security_group.tags,
)
def setUp(self):
@@ -67,6 +69,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
self.projects_mock.get.return_value = self.project
self.domains_mock.get.return_value = self.domain
+ self.network.set_tags = mock.Mock(return_value=None)
# Get the command object to test
self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace)
@@ -118,6 +121,43 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def _test_create_with_tag(self, add_tags=True):
+ arglist = [self._security_group.name]
+ if add_tags:
+ arglist += ['--tag', 'red', '--tag', 'blue']
+ else:
+ arglist += ['--no-tag']
+
+ verifylist = [
+ ('name', self._security_group.name),
+ ]
+ if add_tags:
+ verifylist.append(('tags', ['red', 'blue']))
+ else:
+ verifylist.append(('no_tag', True))
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ self.network.create_security_group.assert_called_once_with(**{
+ 'description': self._security_group.name,
+ 'name': self._security_group.name,
+ })
+ if add_tags:
+ self.network.set_tags.assert_called_once_with(
+ self._security_group,
+ tests_utils.CompareBySet(['red', 'blue']))
+ else:
+ self.assertFalse(self.network.set_tags.called)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_with_tags(self):
+ self._test_create_with_tag(add_tags=True)
+
+ def test_create_with_no_tag(self):
+ self._test_create_with_tag(add_tags=False)
+
class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork):
@@ -214,6 +254,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
'Name',
'Description',
'Project',
+ 'Tags',
)
data = []
@@ -223,6 +264,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
grp.name,
grp.description,
grp.project_id,
+ grp.tags,
))
def setUp(self):
@@ -300,12 +342,38 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
+ def test_list_with_tag_options(self):
+ arglist = [
+ '--tags', 'red,blue',
+ '--any-tags', 'red,green',
+ '--not-tags', 'orange,yellow',
+ '--not-any-tags', 'black,white',
+ ]
+ verifylist = [
+ ('tags', ['red', 'blue']),
+ ('any_tags', ['red', 'green']),
+ ('not_tags', ['orange', 'yellow']),
+ ('not_any_tags', ['black', 'white']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.security_groups.assert_called_once_with(
+ **{'tags': 'red,blue',
+ 'any_tags': 'red,green',
+ 'not_tags': 'orange,yellow',
+ 'not_any_tags': 'black,white'}
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork):
# The security group to be set.
- _security_group = \
- network_fakes.FakeSecurityGroup.create_one_security_group()
+ _security_group = (
+ network_fakes.FakeSecurityGroup.create_one_security_group(
+ attrs={'tags': ['green', 'red']}))
def setUp(self):
super(TestSetSecurityGroupNetwork, self).setUp()
@@ -314,6 +382,7 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork):
self.network.find_security_group = mock.Mock(
return_value=self._security_group)
+ self.network.set_tags = mock.Mock(return_value=None)
# Get the command object to test
self.cmd = security_group.SetSecurityGroup(self.app, self.namespace)
@@ -366,6 +435,34 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork):
)
self.assertIsNone(result)
+ def _test_set_tags(self, with_tags=True):
+ if with_tags:
+ arglist = ['--tag', 'red', '--tag', 'blue']
+ verifylist = [('tags', ['red', 'blue'])]
+ expected_args = ['red', 'blue', 'green']
+ else:
+ arglist = ['--no-tag']
+ verifylist = [('no_tag', True)]
+ expected_args = []
+ arglist.append(self._security_group.name)
+ verifylist.append(
+ ('group', self._security_group.name))
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertTrue(self.network.update_security_group.called)
+ self.network.set_tags.assert_called_once_with(
+ self._security_group,
+ tests_utils.CompareBySet(expected_args))
+ self.assertIsNone(result)
+
+ def test_set_with_tags(self):
+ self._test_set_tags(with_tags=True)
+
+ def test_set_with_no_tag(self):
+ self._test_set_tags(with_tags=False)
+
class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork):
@@ -385,6 +482,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork):
'name',
'project_id',
'rules',
+ 'tags',
)
data = (
@@ -394,6 +492,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork):
_security_group.project_id,
security_group._format_network_security_group_rules(
[_security_group_rule._info]),
+ _security_group.tags,
)
def setUp(self):
@@ -424,3 +523,70 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork):
self._security_group.id, ignore_missing=False)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+
+
+class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork):
+
+ # The security group to be unset.
+ _security_group = (
+ network_fakes.FakeSecurityGroup.create_one_security_group(
+ attrs={'tags': ['green', 'red']}))
+
+ def setUp(self):
+ super(TestUnsetSecurityGroupNetwork, self).setUp()
+
+ self.network.update_security_group = mock.Mock(return_value=None)
+
+ self.network.find_security_group = mock.Mock(
+ return_value=self._security_group)
+ self.network.set_tags = mock.Mock(return_value=None)
+
+ # Get the command object to test
+ self.cmd = security_group.UnsetSecurityGroup(self.app, self.namespace)
+
+ def test_set_no_options(self):
+ self.assertRaises(tests_utils.ParserException,
+ self.check_parser, self.cmd, [], [])
+
+ def test_set_no_updates(self):
+ arglist = [
+ self._security_group.name,
+ ]
+ verifylist = [
+ ('group', self._security_group.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertFalse(self.network.update_security_group.called)
+ self.assertFalse(self.network.set_tags.called)
+ self.assertIsNone(result)
+
+ def _test_unset_tags(self, with_tags=True):
+ if with_tags:
+ arglist = ['--tag', 'red', '--tag', 'blue']
+ verifylist = [('tags', ['red', 'blue'])]
+ expected_args = ['green']
+ else:
+ arglist = ['--all-tag']
+ verifylist = [('all_tag', True)]
+ expected_args = []
+ arglist.append(self._security_group.name)
+ verifylist.append(
+ ('group', self._security_group.name))
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ self.assertFalse(self.network.update_security_group.called)
+ self.network.set_tags.assert_called_once_with(
+ self._security_group,
+ tests_utils.CompareBySet(expected_args))
+ self.assertIsNone(result)
+
+ def test_unset_with_tags(self):
+ self._test_unset_tags(with_tags=True)
+
+ def test_unset_with_all_tag(self):
+ self._test_unset_tags(with_tags=False)
diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py
index b7f741cd..39cb4f53 100644
--- a/openstackclient/tests/unit/network/v2/test_subnet.py
+++ b/openstackclient/tests/unit/network/v2/test_subnet.py
@@ -1046,6 +1046,36 @@ class TestSetSubnet(TestSubnet):
_testsubnet, **attrs)
self.assertIsNone(result)
+ def test_clear_options(self):
+ _testsubnet = network_fakes.FakeSubnet.create_one_subnet(
+ {'host_routes': [{'destination': '10.20.20.0/24',
+ 'nexthop': '10.20.20.1'}],
+ 'allocation_pools': [{'start': '8.8.8.200',
+ 'end': '8.8.8.250'}],
+ 'dns_nameservers': ['10.0.0.1'], })
+ self.network.find_subnet = mock.Mock(return_value=_testsubnet)
+ arglist = [
+ '--no-host-route',
+ '--no-allocation-pool',
+ '--no-dns-nameservers',
+ _testsubnet.name,
+ ]
+ verifylist = [
+ ('no_dns_nameservers', True),
+ ('no_host_route', True),
+ ('no_allocation_pool', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {
+ 'host_routes': [],
+ 'allocation_pools': [],
+ 'dns_nameservers': [],
+ }
+ self.network.update_subnet.assert_called_once_with(
+ _testsubnet, **attrs)
+ self.assertIsNone(result)
+
def _test_set_tags(self, with_tags=True):
if with_tags:
arglist = ['--tag', 'red', '--tag', 'blue']
@@ -1074,6 +1104,29 @@ class TestSetSubnet(TestSubnet):
def test_set_with_no_tag(self):
self._test_set_tags(with_tags=False)
+ def test_set_segment(self):
+ _net = network_fakes.FakeNetwork.create_one_network()
+ _segment = network_fakes.FakeNetworkSegment.create_one_network_segment(
+ attrs={'network_id': _net.id})
+ _subnet = network_fakes.FakeSubnet.create_one_subnet(
+ {'host_routes': [{'destination': '10.20.20.0/24',
+ 'nexthop': '10.20.20.1'}],
+ 'allocation_pools': [{'start': '8.8.8.200',
+ 'end': '8.8.8.250'}],
+ 'dns_nameservers': ["10.0.0.1"],
+ 'network_id': _net.id,
+ 'segment_id': None})
+ self.network.find_subnet = mock.Mock(return_value=_subnet)
+ self.network.find_segment = mock.Mock(return_value=_segment)
+ arglist = ['--network-segment', _segment.id, _subnet.name]
+ verifylist = [('network_segment', _segment.id)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+ attrs = {'segment_id': _segment.id}
+ self.network.update_subnet.assert_called_once_with(_subnet, **attrs)
+ self.network.update_subnet.assert_called_with(_subnet, **attrs)
+ self.assertIsNone(result)
+
class TestShowSubnet(TestSubnet):
# The subnets to be shown
diff --git a/openstackclient/tests/unit/volume/test_find_resource.py b/openstackclient/tests/unit/volume/test_find_resource.py
index dbf9592f..60591eff 100644
--- a/openstackclient/tests/unit/volume/test_find_resource.py
+++ b/openstackclient/tests/unit/volume/test_find_resource.py
@@ -15,8 +15,8 @@
import mock
-from cinderclient.v1 import volume_snapshots
-from cinderclient.v1 import volumes
+from cinderclient.v3 import volume_snapshots
+from cinderclient.v3 import volumes
from osc_lib import exceptions
from osc_lib import utils
diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py
index 27f37bd8..8d1db63e 100644
--- a/openstackclient/tests/unit/volume/v2/fakes.py
+++ b/openstackclient/tests/unit/volume/v2/fakes.py
@@ -193,6 +193,100 @@ class FakeService(object):
return services
+class FakeCapability(object):
+ """Fake capability."""
+
+ @staticmethod
+ def create_one_capability(attrs=None):
+ """Create a fake volume backend capability.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes of the Capabilities.
+ :return:
+ A FakeResource object with capability name and attrs.
+ """
+ # Set default attribute
+ capability_info = {
+ "namespace": "OS::Storage::Capabilities::fake",
+ "vendor_name": "OpenStack",
+ "volume_backend_name": "lvmdriver-1",
+ "pool_name": "pool",
+ "driver_version": "2.0.0",
+ "storage_protocol": "iSCSI",
+ "display_name": "Capabilities of Cinder LVM driver",
+ "description": "Blah, blah.",
+ "visibility": "public",
+ "replication_targets": [],
+ "properties": {
+ "compression": {
+ "title": "Compression",
+ "description": "Enables compression.",
+ "type": "boolean"
+ },
+ "qos": {
+ "title": "QoS",
+ "description": "Enables QoS.",
+ "type": "boolean"
+ },
+ "replication": {
+ "title": "Replication",
+ "description": "Enables replication.",
+ "type": "boolean"
+ },
+ "thin_provisioning": {
+ "title": "Thin Provisioning",
+ "description": "Sets thin provisioning.",
+ "type": "boolean"
+ }
+ }
+ }
+
+ # Overwrite default attributes if there are some attributes set
+ capability_info.update(attrs or {})
+
+ capability = fakes.FakeResource(
+ None,
+ capability_info,
+ loaded=True)
+
+ return capability
+
+
+class FakePool(object):
+ """Fake Pools."""
+
+ @staticmethod
+ def create_one_pool(attrs=None):
+ """Create a fake pool.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes of the pool
+ :return:
+ A FakeResource object with pool name and attrs.
+ """
+ # Set default attribute
+ pool_info = {
+ 'name': 'host@lvmdriver-1#lvmdriver-1',
+ 'storage_protocol': 'iSCSI',
+ 'thick_provisioning_support': False,
+ 'thin_provisioning_support': True,
+ 'total_volumes': 99,
+ 'total_capacity_gb': 1000.00,
+ 'allocated_capacity_gb': 100,
+ 'max_over_subscription_ratio': 200.0,
+ }
+
+ # Overwrite default attributes if there are some attributes set
+ pool_info.update(attrs or {})
+
+ pool = fakes.FakeResource(
+ None,
+ pool_info,
+ loaded=True)
+
+ return pool
+
+
class FakeVolumeClient(object):
def __init__(self, **kwargs):
@@ -200,6 +294,8 @@ class FakeVolumeClient(object):
self.volumes.resource_class = fakes.FakeResource(None, {})
self.extensions = mock.Mock()
self.extensions.resource_class = fakes.FakeResource(None, {})
+ self.limits = mock.Mock()
+ self.limits.resource_class = fakes.FakeResource(None, {})
self.volume_snapshots = mock.Mock()
self.volume_snapshots.resource_class = fakes.FakeResource(None, {})
self.backups = mock.Mock()
@@ -231,6 +327,10 @@ class FakeVolumeClient(object):
self.cgsnapshots.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
+ self.capabilities = mock.Mock()
+ self.capabilities.resource_class = fakes.FakeResource(None, {})
+ self.pools = mock.Mock()
+ self.pools.resource_class = fakes.FakeResource(None, {})
class TestVolume(utils.TestCommand):
@@ -1004,3 +1104,101 @@ class FakeQuota(object):
quota.project_id = quota_attrs['id']
return quota
+
+
+class FakeLimits(object):
+ """Fake limits"""
+
+ def __init__(self, absolute_attrs=None):
+ self.absolute_limits_attrs = {
+ 'totalSnapshotsUsed': 1,
+ 'maxTotalBackups': 10,
+ 'maxTotalVolumeGigabytes': 1000,
+ 'maxTotalSnapshots': 10,
+ 'maxTotalBackupGigabytes': 1000,
+ 'totalBackupGigabytesUsed': 0,
+ 'maxTotalVolumes': 10,
+ 'totalVolumesUsed': 4,
+ 'totalBackupsUsed': 0,
+ 'totalGigabytesUsed': 35
+ }
+ absolute_attrs = absolute_attrs or {}
+ self.absolute_limits_attrs.update(absolute_attrs)
+
+ self.rate_limits_attrs = [{
+ "uri": "*",
+ "limit": [
+ {
+ "value": 10,
+ "verb": "POST",
+ "remaining": 2,
+ "unit": "MINUTE",
+ "next-available": "2011-12-15T22:42:45Z"
+ },
+ {
+ "value": 10,
+ "verb": "PUT",
+ "remaining": 2,
+ "unit": "MINUTE",
+ "next-available": "2011-12-15T22:42:45Z"
+ },
+ {
+ "value": 100,
+ "verb": "DELETE",
+ "remaining": 100,
+ "unit": "MINUTE",
+ "next-available": "2011-12-15T22:42:45Z"
+ }
+ ]
+ }]
+
+ @property
+ def absolute(self):
+ for (name, value) in self.absolute_limits_attrs.items():
+ yield FakeAbsoluteLimit(name, value)
+
+ def absolute_limits(self):
+ reference_data = []
+ for (name, value) in self.absolute_limits_attrs.items():
+ reference_data.append((name, value))
+ return reference_data
+
+ @property
+ def rate(self):
+ for group in self.rate_limits_attrs:
+ uri = group['uri']
+ for rate in group['limit']:
+ yield FakeRateLimit(rate['verb'], uri, rate['value'],
+ rate['remaining'], rate['unit'],
+ rate['next-available'])
+
+ def rate_limits(self):
+ reference_data = []
+ for group in self.rate_limits_attrs:
+ uri = group['uri']
+ for rate in group['limit']:
+ reference_data.append((rate['verb'], uri, rate['value'],
+ rate['remaining'], rate['unit'],
+ rate['next-available']))
+ return reference_data
+
+
+class FakeAbsoluteLimit(object):
+ """Data model that represents an absolute limit."""
+
+ def __init__(self, name, value):
+ self.name = name
+ self.value = value
+
+
+class FakeRateLimit(object):
+ """Data model that represents a flattened view of a single rate limit."""
+
+ def __init__(self, verb, uri, value, remain,
+ unit, next_available):
+ self.verb = verb
+ self.uri = uri
+ self.value = value
+ self.remain = remain
+ self.unit = unit
+ self.next_available = next_available
diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py
index 2fa924b8..183fb228 100644
--- a/openstackclient/tests/unit/volume/v2/test_volume.py
+++ b/openstackclient/tests/unit/volume/v2/test_volume.py
@@ -126,15 +126,11 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=None,
- project_id=None,
availability_zone=None,
metadata=None,
imageRef=None,
source_volid=None,
consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
scheduler_hints=None,
)
@@ -152,7 +148,6 @@ class TestVolumeCreate(TestVolume):
'--availability-zone', self.new_volume.availability_zone,
'--consistency-group', consistency_group.id,
'--hint', 'k=v',
- '--multi-attach',
self.new_volume.name,
]
verifylist = [
@@ -162,7 +157,6 @@ class TestVolumeCreate(TestVolume):
('availability_zone', self.new_volume.availability_zone),
('consistency_group', consistency_group.id),
('hint', {'k': 'v'}),
- ('multi_attach', True),
('name', self.new_volume.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -178,112 +172,50 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=self.new_volume.description,
volume_type=self.new_volume.volume_type,
- user_id=None,
- project_id=None,
availability_zone=self.new_volume.availability_zone,
metadata=None,
imageRef=None,
source_volid=None,
consistencygroup_id=consistency_group.id,
- source_replica=None,
- multiattach=True,
scheduler_hints={'k': 'v'},
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, data)
- def test_volume_create_user_project_id(self):
- # Return a project
- self.projects_mock.get.return_value = self.project
- # Return a user
- self.users_mock.get.return_value = self.user
-
+ def test_volume_create_user(self):
arglist = [
'--size', str(self.new_volume.size),
- '--project', self.project.id,
'--user', self.user.id,
self.new_volume.name,
]
verifylist = [
('size', self.new_volume.size),
- ('project', self.project.id),
('user', self.user.id),
('name', self.new_volume.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- # In base command class ShowOne in cliff, abstract method take_action()
- # returns a two-part tuple with a tuple of column names and a tuple of
- # data to be shown.
- columns, data = self.cmd.take_action(parsed_args)
-
- self.volumes_mock.create.assert_called_with(
- size=self.new_volume.size,
- snapshot_id=None,
- name=self.new_volume.name,
- description=None,
- volume_type=None,
- user_id=self.user.id,
- project_id=self.project.id,
- availability_zone=None,
- metadata=None,
- imageRef=None,
- source_volid=None,
- consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
- scheduler_hints=None,
- )
-
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist, data)
-
- def test_volume_create_user_project_name(self):
- # Return a project
- self.projects_mock.get.return_value = self.project
- # Return a user
- self.users_mock.get.return_value = self.user
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+ self.volumes_mock.create.assert_not_called()
+ def test_volume_create_project(self):
arglist = [
'--size', str(self.new_volume.size),
- '--project', self.project.name,
- '--user', self.user.name,
+ '--project', self.project.id,
self.new_volume.name,
]
verifylist = [
('size', self.new_volume.size),
- ('project', self.project.name),
- ('user', self.user.name),
+ ('project', self.project.id),
('name', self.new_volume.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- # In base command class ShowOne in cliff, abstract method take_action()
- # returns a two-part tuple with a tuple of column names and a tuple of
- # data to be shown.
- columns, data = self.cmd.take_action(parsed_args)
-
- self.volumes_mock.create.assert_called_with(
- size=self.new_volume.size,
- snapshot_id=None,
- name=self.new_volume.name,
- description=None,
- volume_type=None,
- user_id=self.user.id,
- project_id=self.project.id,
- availability_zone=None,
- metadata=None,
- imageRef=None,
- source_volid=None,
- consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
- scheduler_hints=None,
- )
-
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist, data)
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+ self.volumes_mock.create.assert_not_called()
def test_volume_create_properties(self):
arglist = [
@@ -310,15 +242,11 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=None,
- project_id=None,
availability_zone=None,
metadata={'Alpha': 'a', 'Beta': 'b'},
imageRef=None,
source_volid=None,
consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
scheduler_hints=None,
)
@@ -352,15 +280,11 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=None,
- project_id=None,
availability_zone=None,
metadata=None,
imageRef=image.id,
source_volid=None,
consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
scheduler_hints=None,
)
@@ -394,15 +318,11 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=None,
- project_id=None,
availability_zone=None,
metadata=None,
imageRef=image.id,
source_volid=None,
consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
scheduler_hints=None,
)
@@ -430,20 +350,16 @@ class TestVolumeCreate(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
self.volumes_mock.create.assert_called_once_with(
- size=None,
+ size=snapshot.size,
snapshot_id=snapshot.id,
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=None,
- project_id=None,
availability_zone=None,
metadata=None,
imageRef=None,
source_volid=None,
consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
scheduler_hints=None,
)
@@ -477,15 +393,11 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=None,
- project_id=None,
availability_zone=None,
metadata=None,
imageRef=None,
source_volid=None,
consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
scheduler_hints=None,
)
@@ -523,15 +435,11 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=None,
- project_id=None,
availability_zone=None,
metadata=None,
imageRef=None,
source_volid=None,
consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
scheduler_hints=None,
)
@@ -578,15 +486,11 @@ class TestVolumeCreate(TestVolume):
name=self.new_volume.name,
description=None,
volume_type=None,
- user_id=None,
- project_id=None,
availability_zone=None,
metadata=None,
imageRef=None,
source_volid=None,
consistencygroup_id=None,
- source_replica=None,
- multiattach=False,
scheduler_hints=None,
)
@@ -598,40 +502,6 @@ class TestVolumeCreate(TestVolume):
self.volumes_mock.update_readonly_flag.assert_called_with(
self.new_volume.id, True)
- def test_volume_create_with_source_replicated(self):
- self.volumes_mock.get.return_value = self.new_volume
- arglist = [
- '--source-replicated', self.new_volume.id,
- self.new_volume.name,
- ]
- verifylist = [
- ('source_replicated', self.new_volume.id),
- ('name', self.new_volume.name),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- columns, data = self.cmd.take_action(parsed_args)
- self.volumes_mock.create.assert_called_once_with(
- size=None,
- snapshot_id=None,
- name=self.new_volume.name,
- description=None,
- volume_type=None,
- user_id=None,
- project_id=None,
- availability_zone=None,
- metadata=None,
- imageRef=None,
- source_volid=None,
- consistencygroup_id=None,
- source_replica=self.new_volume.id,
- multiattach=False,
- scheduler_hints=None,
- )
-
- self.assertEqual(self.columns, columns)
- self.assertEqual(self.datalist, data)
-
def test_volume_create_without_size(self):
arglist = [
self.new_volume.name,
@@ -649,7 +519,6 @@ class TestVolumeCreate(TestVolume):
'--image', 'source_image',
'--source', 'source_volume',
'--snapshot', 'source_snapshot',
- '--source-replicated', 'source_replicated_volume',
'--size', str(self.new_volume.size),
self.new_volume.name,
]
@@ -657,7 +526,6 @@ class TestVolumeCreate(TestVolume):
('image', 'source_image'),
('source', 'source_volume'),
('snapshot', 'source_snapshot'),
- ('source-replicated', 'source_replicated_volume'),
('size', self.new_volume.size),
('name', self.new_volume.name),
]
diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backend.py b/openstackclient/tests/unit/volume/v2/test_volume_backend.py
new file mode 100644
index 00000000..db188660
--- /dev/null
+++ b/openstackclient/tests/unit/volume/v2/test_volume_backend.py
@@ -0,0 +1,168 @@
+#
+# 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.
+#
+
+from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
+from openstackclient.volume.v2 import volume_backend
+
+
+class TestShowVolumeCapability(volume_fakes.TestVolume):
+ """Test backend capability functionality."""
+
+ # The capability to be listed
+ capability = volume_fakes.FakeCapability.create_one_capability()
+
+ def setUp(self):
+ super(TestShowVolumeCapability, self).setUp()
+
+ # Get a shortcut to the capability Mock
+ self.capability_mock = self.app.client_manager.volume.capabilities
+ self.capability_mock.get.return_value = self.capability
+
+ # Get the command object to test
+ self.cmd = volume_backend.ShowCapability(self.app, None)
+
+ def test_capability_show(self):
+ arglist = [
+ 'fake',
+ ]
+ verifylist = [
+ ('host', 'fake'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ expected_columns = [
+ 'Title',
+ 'Key',
+ 'Type',
+ 'Description',
+ ]
+
+ # confirming if all expected columns are present in the result.
+ self.assertEqual(expected_columns, columns)
+
+ capabilities = [
+ 'Compression',
+ 'Replication',
+ 'QoS',
+ 'Thin Provisioning',
+ ]
+
+ # confirming if all expected values are present in the result.
+ for cap in data:
+ self.assertTrue(cap[0] in capabilities)
+
+ # checking if proper call was made to get capabilities
+ self.capability_mock.get.assert_called_with(
+ 'fake',
+ )
+
+
+class TestListVolumePool(volume_fakes.TestVolume):
+ """Tests for volume backend pool listing."""
+
+ # The pool to be listed
+ pools = volume_fakes.FakePool.create_one_pool()
+
+ def setUp(self):
+ super(TestListVolumePool, self).setUp()
+
+ self.pool_mock = self.app.client_manager.volume.pools
+ self.pool_mock.list.return_value = [self.pools]
+
+ # Get the command object to test
+ self.cmd = volume_backend.ListPool(self.app, None)
+
+ def test_pool_list(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ expected_columns = [
+ 'Name',
+ ]
+
+ # confirming if all expected columns are present in the result.
+ self.assertEqual(expected_columns, columns)
+
+ datalist = ((
+ self.pools.name,
+ ), )
+
+ # confirming if all expected values are present in the result.
+ self.assertEqual(datalist, tuple(data))
+
+ # checking if proper call was made to list pools
+ self.pool_mock.list.assert_called_with(
+ detailed=False,
+ )
+
+ # checking if long columns are present in output
+ self.assertNotIn("total_volumes", columns)
+ self.assertNotIn("storage_protocol", columns)
+
+ def test_service_list_with_long_option(self):
+ arglist = [
+ '--long'
+ ]
+ verifylist = [
+ ('long', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # In base command class Lister in cliff, abstract method take_action()
+ # returns a tuple containing the column names and an iterable
+ # containing the data to be listed.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ expected_columns = [
+ 'Name',
+ 'Protocol',
+ 'Thick',
+ 'Thin',
+ 'Volumes',
+ 'Capacity',
+ 'Allocated',
+ 'Max Over Ratio',
+ ]
+
+ # confirming if all expected columns are present in the result.
+ self.assertEqual(expected_columns, columns)
+
+ datalist = ((
+ self.pools.name,
+ self.pools.storage_protocol,
+ self.pools.thick_provisioning_support,
+ self.pools.thin_provisioning_support,
+ self.pools.total_volumes,
+ self.pools.total_capacity_gb,
+ self.pools.allocated_capacity_gb,
+ self.pools.max_over_subscription_ratio,
+ ), )
+
+ # confirming if all expected values are present in the result.
+ self.assertEqual(datalist, tuple(data))
+
+ self.pool_mock.list.assert_called_with(
+ detailed=True,
+ )
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index c4b0dfca..e0e670a9 100644
--- a/openstackclient/volume/client.py
+++ b/openstackclient/volume/client.py
@@ -37,13 +37,20 @@ def make_client(instance):
# Defer client imports until we actually need them
from cinderclient import extension
- from cinderclient.v1.contrib import list_extensions
- from cinderclient.v1 import volume_snapshots
- from cinderclient.v1 import volumes
-
- # Monkey patch for v1 cinderclient
- volumes.Volume.NAME_ATTR = 'display_name'
- volume_snapshots.Snapshot.NAME_ATTR = 'display_name'
+ from cinderclient.v3.contrib import list_extensions
+ from cinderclient.v3 import volume_snapshots
+ from cinderclient.v3 import volumes
+
+ # Try a small import to check if cinderclient v1 is supported
+ try:
+ from cinderclient.v1 import services # noqa
+ except Exception:
+ del API_VERSIONS['1']
+
+ if instance._api_version[API_NAME] == '1':
+ # Monkey patch for v1 cinderclient
+ volumes.Volume.NAME_ATTR = 'display_name'
+ volume_snapshots.Snapshot.NAME_ATTR = 'display_name'
volume_client = utils.get_client_class(
API_NAME,
diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py
index f9baa5be..b4d8eaca 100644
--- a/openstackclient/volume/v1/volume_type.py
+++ b/openstackclient/volume/v1/volume_type.py
@@ -70,8 +70,8 @@ class CreateVolumeType(command.ShowOne):
parser.add_argument(
'--encryption-provider',
metavar='<provider>',
- help=_('Set the class that provides encryption support for '
- 'this volume type (e.g "LuksEncryptor") (admin only) '
+ help=_('Set the encryption provider format for '
+ 'this volume type (e.g "luks" or "plain") (admin only) '
'(This option is required when setting encryption type '
'of a volume. Consider using other encryption options '
'such as: "--encryption-cipher", "--encryption-key-size" '
@@ -254,8 +254,8 @@ class SetVolumeType(command.Command):
parser.add_argument(
'--encryption-provider',
metavar='<provider>',
- help=_('Set the class that provides encryption support for '
- 'this volume type (e.g "LuksEncryptor") (admin only) '
+ help=_('Set the encryption provider format for '
+ 'this volume type (e.g "luks" or "plain") (admin only) '
'(This option is required when setting encryption type '
'of a volume. Consider using other encryption options '
'such as: "--encryption-cipher", "--encryption-key-size" '
diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py
index 61f846b0..7a5c207a 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -14,6 +14,7 @@
"""Volume V2 Volume action implementations"""
+import argparse
import copy
import logging
@@ -37,7 +38,7 @@ def _check_size_arg(args):
volume is not specified.
"""
- if ((args.snapshot or args.source or args.source_replicated)
+ if ((args.snapshot or args.source)
is None and args.size is None):
msg = _("--size is a required option if snapshot "
"or source volume is not specified.")
@@ -59,7 +60,7 @@ class CreateVolume(command.ShowOne):
metavar="<size>",
type=int,
help=_("Volume size in GB (Required unless --snapshot or "
- "--source or --source-replicated is specified)"),
+ "--source is specified)"),
)
parser.add_argument(
"--type",
@@ -85,7 +86,7 @@ class CreateVolume(command.ShowOne):
source_group.add_argument(
"--source-replicated",
metavar="<replicated-volume>",
- help=_("Replicated volume to clone (name or ID)"),
+ help=argparse.SUPPRESS,
)
parser.add_argument(
"--description",
@@ -95,12 +96,12 @@ class CreateVolume(command.ShowOne):
parser.add_argument(
'--user',
metavar='<user>',
- help=_('Specify an alternate user (name or ID)'),
+ help=argparse.SUPPRESS,
)
parser.add_argument(
'--project',
metavar='<project>',
- help=_('Specify an alternate project (name or ID)'),
+ help=argparse.SUPPRESS,
)
parser.add_argument(
"--availability-zone",
@@ -158,7 +159,6 @@ class CreateVolume(command.ShowOne):
def take_action(self, parsed_args):
_check_size_arg(parsed_args)
- identity_client = self.app.client_manager.identity
volume_client = self.app.client_manager.volume
image_client = self.app.client_manager.image
@@ -168,12 +168,6 @@ class CreateVolume(command.ShowOne):
volume_client.volumes,
parsed_args.source).id
- replicated_source_volume = None
- if parsed_args.source_replicated:
- replicated_source_volume = utils.find_resource(
- volume_client.volumes,
- parsed_args.source_replicated).id
-
consistency_group = None
if parsed_args.consistency_group:
consistency_group = utils.find_resource(
@@ -186,39 +180,53 @@ class CreateVolume(command.ShowOne):
image_client.images,
parsed_args.image).id
+ size = parsed_args.size
+
snapshot = None
if parsed_args.snapshot:
- snapshot = utils.find_resource(
+ snapshot_obj = utils.find_resource(
volume_client.volume_snapshots,
- parsed_args.snapshot).id
-
- project = None
+ parsed_args.snapshot)
+ snapshot = snapshot_obj.id
+ # Cinder requires a value for size when creating a volume
+ # even if creating from a snapshot. Cinder will create the
+ # volume with at least the same size as the snapshot anyway,
+ # so since we have the object here, just override the size
+ # value if it's either not given or is smaller than the
+ # snapshot size.
+ size = max(size or 0, snapshot_obj.size)
+
+ # NOTE(abishop): Cinder's volumes.create() has 'project_id' and
+ # 'user_id' args, but they're not wired up to anything. The only way
+ # to specify an alternate project or user for the volume is to use
+ # the identity overrides (e.g. "--os-project-id").
+ #
+ # Now, if the project or user arg is specified then the command is
+ # rejected. Otherwise, Cinder would actually create a volume, but
+ # without the specified property.
if parsed_args.project:
- project = utils.find_resource(
- identity_client.projects,
- parsed_args.project).id
-
- user = None
+ raise exceptions.CommandError(
+ _("ERROR: --project is deprecated, please use"
+ " --os-project-name or --os-project-id instead."))
if parsed_args.user:
- user = utils.find_resource(
- identity_client.users,
- parsed_args.user).id
+ raise exceptions.CommandError(
+ _("ERROR: --user is deprecated, please use"
+ " --os-username instead."))
+ if parsed_args.multi_attach:
+ LOG.warning(_("'--multi-attach' option is no longer supported by "
+ "the block storage service."))
volume = volume_client.volumes.create(
- size=parsed_args.size,
+ size=size,
snapshot_id=snapshot,
name=parsed_args.name,
description=parsed_args.description,
volume_type=parsed_args.type,
- user_id=user,
- project_id=project,
availability_zone=parsed_args.availability_zone,
metadata=parsed_args.property,
imageRef=image,
source_volid=source_volume,
consistencygroup_id=consistency_group,
- source_replica=replicated_source_volume,
- multiattach=parsed_args.multi_attach,
scheduler_hints=parsed_args.hint,
)
diff --git a/openstackclient/volume/v2/volume_backend.py b/openstackclient/volume/v2/volume_backend.py
new file mode 100644
index 00000000..c5194d35
--- /dev/null
+++ b/openstackclient/volume/v2/volume_backend.py
@@ -0,0 +1,113 @@
+#
+# 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.
+#
+
+"""Storage backend action implementations"""
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+
+class ShowCapability(command.Lister):
+ _description = _("Show capability command")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowCapability, self).get_parser(prog_name)
+ parser.add_argument(
+ "host",
+ metavar="<host>",
+ help=_("List capabilities of specified host (host@backend-name)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+
+ columns = [
+ 'Title',
+ 'Key',
+ 'Type',
+ 'Description',
+ ]
+
+ data = volume_client.capabilities.get(parsed_args.host)
+
+ # The get capabilities API is... interesting. We only want the names of
+ # the capabilities that can set for a backend through extra specs, so
+ # we need to extract out that part of the mess that is returned.
+ print_data = []
+ keys = data.properties
+ for key in keys:
+ # Stuff the key into the details to make it easier to output
+ capability_data = data.properties[key]
+ capability_data['key'] = key
+ print_data.append(capability_data)
+
+ return (columns,
+ (utils.get_dict_properties(
+ s, columns,
+ ) for s in print_data))
+
+
+class ListPool(command.Lister):
+ _description = _("List pool command")
+
+ def get_parser(self, prog_name):
+ parser = super(ListPool, self).get_parser(prog_name)
+ parser.add_argument(
+ "--long",
+ action="store_true",
+ default=False,
+ help=_("Show detailed information about pools.")
+ )
+ # TODO(smcginnis): Starting with Cinder microversion 3.33, user is also
+ # able to pass in --filters with a <key>=<value> pair to filter on.
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+
+ if parsed_args.long:
+ columns = [
+ 'name',
+ 'storage_protocol',
+ 'thick_provisioning_support',
+ 'thin_provisioning_support',
+ 'total_volumes',
+ 'total_capacity_gb',
+ 'allocated_capacity_gb',
+ 'max_over_subscription_ratio',
+ ]
+ headers = [
+ 'Name',
+ 'Protocol',
+ 'Thick',
+ 'Thin',
+ 'Volumes',
+ 'Capacity',
+ 'Allocated',
+ 'Max Over Ratio'
+ ]
+ else:
+ columns = [
+ 'Name',
+ ]
+ headers = columns
+
+ data = volume_client.pools.list(detailed=parsed_args.long)
+ return (headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py
index 64c4d652..71e94a2b 100644
--- a/openstackclient/volume/v2/volume_type.py
+++ b/openstackclient/volume/v2/volume_type.py
@@ -112,8 +112,8 @@ class CreateVolumeType(command.ShowOne):
parser.add_argument(
'--encryption-provider',
metavar='<provider>',
- help=_('Set the class that provides encryption support for '
- 'this volume type (e.g "LuksEncryptor") (admin only) '
+ help=_('Set the encryption provider format for '
+ 'this volume type (e.g "luks" or "plain") (admin only) '
'(This option is required when setting encryption type '
'of a volume. Consider using other encryption options '
'such as: "--encryption-cipher", "--encryption-key-size" '
@@ -371,8 +371,8 @@ class SetVolumeType(command.Command):
parser.add_argument(
'--encryption-provider',
metavar='<provider>',
- help=_('Set the class that provides encryption support for '
- 'this volume type (e.g "LuksEncryptor") (admin only) '
+ help=_('Set the encryption provider format for '
+ 'this volume type (e.g "luks" or "plain") (admin only) '
'(This option is required when setting encryption type '
'of a volume for the first time. Consider using other '
'encryption options such as: "--encryption-cipher", '