summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/object_store_v1.py2
-rw-r--r--openstackclient/common/quota.py13
-rw-r--r--openstackclient/common/sdk_utils.py4
-rw-r--r--openstackclient/compute/v2/agent.py28
-rw-r--r--openstackclient/compute/v2/aggregate.py263
-rw-r--r--openstackclient/compute/v2/console.py54
-rw-r--r--openstackclient/compute/v2/flavor.py275
-rw-r--r--openstackclient/compute/v2/keypair.py241
-rw-r--r--openstackclient/compute/v2/server.py1171
-rw-r--r--openstackclient/compute/v2/server_backup.py5
-rw-r--r--openstackclient/compute/v2/server_group.py42
-rw-r--r--openstackclient/compute/v2/server_image.py4
-rw-r--r--openstackclient/identity/common.py2
-rw-r--r--openstackclient/identity/v2_0/catalog.py2
-rw-r--r--openstackclient/identity/v3/access_rule.py3
-rw-r--r--openstackclient/identity/v3/identity_provider.py24
-rw-r--r--openstackclient/identity/v3/role.py68
-rw-r--r--openstackclient/image/v1/image.py6
-rw-r--r--openstackclient/image/v2/image.py8
-rw-r--r--openstackclient/network/common.py22
-rw-r--r--openstackclient/network/v2/network_meter_rule.py22
-rw-r--r--openstackclient/network/v2/port.py50
-rw-r--r--openstackclient/network/v2/security_group_rule.py17
-rw-r--r--openstackclient/shell.py8
-rw-r--r--openstackclient/tests/functional/base.py4
-rw-r--r--openstackclient/tests/functional/common/test_quota.py44
-rw-r--r--openstackclient/tests/functional/compute/v2/test_agent.py196
-rw-r--r--openstackclient/tests/functional/compute/v2/test_flavor.py43
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server.py58
-rw-r--r--openstackclient/tests/functional/examples/__init__.py0
-rw-r--r--openstackclient/tests/functional/examples/test_examples.py28
-rw-r--r--openstackclient/tests/unit/common/test_quota.py23
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py149
-rw-r--r--openstackclient/tests/unit/compute/v2/test_aggregate.py273
-rw-r--r--openstackclient/tests/unit/compute/v2/test_console.py163
-rw-r--r--openstackclient/tests/unit/compute/v2/test_flavor.py534
-rw-r--r--openstackclient/tests/unit/compute/v2/test_keypair.py495
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py1453
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_backup.py6
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_group.py163
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_image.py6
-rw-r--r--openstackclient/tests/unit/compute/v2/test_service.py5
-rw-r--r--openstackclient/tests/unit/fakes.py3
-rw-r--r--openstackclient/tests/unit/identity/v2_0/test_catalog.py17
-rw-r--r--openstackclient/tests/unit/identity/v2_0/test_project.py2
-rw-r--r--openstackclient/tests/unit/identity/v2_0/test_user.py8
-rw-r--r--openstackclient/tests/unit/identity/v3/test_catalog.py10
-rw-r--r--openstackclient/tests/unit/identity/v3/test_identity_provider.py75
-rw-r--r--openstackclient/tests/unit/identity/v3/test_role.py379
-rw-r--r--openstackclient/tests/unit/image/v1/test_image.py10
-rw-r--r--openstackclient/tests/unit/image/v2/test_image.py31
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py9
-rw-r--r--openstackclient/tests/unit/network/v2/test_ip_availability.py8
-rw-r--r--openstackclient/tests/unit/network/v2/test_network.py46
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_agent.py14
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_meter_rule.py12
-rw-r--r--openstackclient/tests/unit/network/v2/test_port.py293
-rw-r--r--openstackclient/tests/unit/network/v2/test_router.py32
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group_compute.py10
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group_network.py16
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py1
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group_rule_network.py16
-rw-r--r--openstackclient/tests/unit/network/v2/test_subnet.py42
-rw-r--r--openstackclient/tests/unit/network/v2/test_subnet_pool.py40
-rw-r--r--openstackclient/tests/unit/object/v1/fakes.py3
-rw-r--r--openstackclient/tests/unit/object/v1/test_object_all.py6
-rw-r--r--openstackclient/tests/unit/test_shell.py9
-rw-r--r--openstackclient/tests/unit/utils.py18
-rw-r--r--openstackclient/tests/unit/volume/v1/test_qos_specs.py12
-rw-r--r--openstackclient/tests/unit/volume/v1/test_type.py14
-rw-r--r--openstackclient/tests/unit/volume/v1/test_volume.py36
-rw-r--r--openstackclient/tests/unit/volume/v1/test_volume_backup.py10
-rw-r--r--openstackclient/tests/unit/volume/v2/test_consistency_group.py14
-rw-r--r--openstackclient/tests/unit/volume/v2/test_qos_specs.py12
-rw-r--r--openstackclient/tests/unit/volume/v2/test_type.py24
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py40
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume_backend.py2
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume_backup.py4
-rw-r--r--openstackclient/volume/client.py11
-rw-r--r--openstackclient/volume/v2/volume.py10
80 files changed, 5569 insertions, 1707 deletions
diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py
index 8092abd0..67c79230 100644
--- a/openstackclient/api/object_store_v1.py
+++ b/openstackclient/api/object_store_v1.py
@@ -17,9 +17,9 @@ import io
import logging
import os
import sys
+import urllib
from osc_lib import utils
-from six.moves import urllib
from openstackclient.api import api
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index 11de986b..643cb4e4 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -161,6 +161,13 @@ class BaseQuota(object):
raise
return quota._info
+ def _network_quota_to_dict(self, network_quota):
+ if type(network_quota) is not dict:
+ dict_quota = network_quota.to_dict()
+ else:
+ dict_quota = network_quota
+ return {k: v for k, v in dict_quota.items() if v is not None}
+
def get_network_quota(self, parsed_args):
quota_class = (
parsed_args.quota_class if 'quota_class' in parsed_args else False)
@@ -174,13 +181,11 @@ class BaseQuota(object):
client = self.app.client_manager.network
if default:
network_quota = client.get_quota_default(project)
- if type(network_quota) is not dict:
- network_quota = network_quota.to_dict()
+ network_quota = self._network_quota_to_dict(network_quota)
else:
network_quota = client.get_quota(project,
details=detail)
- if type(network_quota) is not dict:
- network_quota = network_quota.to_dict()
+ network_quota = self._network_quota_to_dict(network_quota)
if detail:
# NOTE(slaweq): Neutron returns values with key "used" but
# Nova for example returns same data with key "in_use"
diff --git a/openstackclient/common/sdk_utils.py b/openstackclient/common/sdk_utils.py
index 9f085617..af9c74f9 100644
--- a/openstackclient/common/sdk_utils.py
+++ b/openstackclient/common/sdk_utils.py
@@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import six
-
def get_osc_show_columns_for_sdk_resource(
sdk_resource,
@@ -44,7 +42,7 @@ def get_osc_show_columns_for_sdk_resource(
for col_name in invisible_columns:
if col_name in display_columns:
display_columns.remove(col_name)
- for sdk_attr, osc_attr in six.iteritems(osc_column_map):
+ for sdk_attr, osc_attr in osc_column_map.items():
if sdk_attr in display_columns:
attr_map[osc_attr] = sdk_attr
display_columns.remove(sdk_attr)
diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py
index 3feb99ec..15fb0f9c 100644
--- a/openstackclient/compute/v2/agent.py
+++ b/openstackclient/compute/v2/agent.py
@@ -28,7 +28,12 @@ LOG = logging.getLogger(__name__)
class CreateAgent(command.ShowOne):
- _description = _("Create compute agent")
+ """Create compute agent.
+
+ The compute agent functionality is hypervisor specific and is only
+ supported by the XenAPI hypervisor driver. It was removed from nova in the
+ 23.0.0 (Wallaby) release.
+ """
def get_parser(self, prog_name):
parser = super(CreateAgent, self).get_parser(prog_name)
@@ -80,7 +85,12 @@ class CreateAgent(command.ShowOne):
class DeleteAgent(command.Command):
- _description = _("Delete compute agent(s)")
+ """Delete compute agent(s).
+
+ The compute agent functionality is hypervisor specific and is only
+ supported by the XenAPI hypervisor driver. It was removed from nova in the
+ 23.0.0 (Wallaby) release.
+ """
def get_parser(self, prog_name):
parser = super(DeleteAgent, self).get_parser(prog_name)
@@ -111,7 +121,12 @@ class DeleteAgent(command.Command):
class ListAgent(command.Lister):
- _description = _("List compute agents")
+ """List compute agents.
+
+ The compute agent functionality is hypervisor specific and is only
+ supported by the XenAPI hypervisor driver. It was removed from nova in the
+ 23.0.0 (Wallaby) release.
+ """
def get_parser(self, prog_name):
parser = super(ListAgent, self).get_parser(prog_name)
@@ -141,7 +156,12 @@ class ListAgent(command.Lister):
class SetAgent(command.Command):
- _description = _("Set compute agent properties")
+ """Set compute agent properties.
+
+ The compute agent functionality is hypervisor specific and is only
+ supported by the XenAPI hypervisor driver. It was removed from nova in the
+ 23.0.0 (Wallaby) release.
+ """
def get_parser(self, prog_name):
parser = super(SetAgent, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py
index 599659a3..8b70f426 100644
--- a/openstackclient/compute/v2/aggregate.py
+++ b/openstackclient/compute/v2/aggregate.py
@@ -18,6 +18,7 @@
import logging
+from openstack import utils as sdk_utils
from osc_lib.cli import format_columns
from osc_lib.cli import parseractions
from osc_lib.command import command
@@ -30,6 +31,25 @@ from openstackclient.i18n import _
LOG = logging.getLogger(__name__)
+_aggregate_formatters = {
+ 'Hosts': format_columns.ListColumn,
+ 'Metadata': format_columns.DictColumn,
+ 'hosts': format_columns.ListColumn,
+ 'metadata': format_columns.DictColumn,
+}
+
+
+def _get_aggregate_columns(item):
+ # To maintain backwards compatibility we need to rename sdk props to
+ # whatever OSC was using before
+ column_map = {
+ 'metadata': 'properties',
+ }
+ hidden_columns = ['links', 'location']
+ return utils.get_osc_show_columns_for_sdk_resource(
+ item, column_map, hidden_columns)
+
+
class AddAggregateHost(command.ShowOne):
_description = _("Add host to aggregate")
@@ -48,26 +68,18 @@ class AddAggregateHost(command.ShowOne):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
- aggregate = utils.find_resource(
- compute_client.aggregates,
- parsed_args.aggregate,
- )
- data = compute_client.aggregates.add_host(aggregate, parsed_args.host)
-
- info = {}
- info.update(data._info)
-
- # Special mapping for columns to make the output easier to read:
- # 'metadata' --> 'properties'
- info.update(
- {
- 'hosts': format_columns.ListColumn(info.pop('hosts')),
- 'properties': format_columns.DictColumn(info.pop('metadata')),
- },
- )
- return zip(*sorted(info.items()))
+ aggregate = compute_client.find_aggregate(
+ parsed_args.aggregate, ignore_missing=False)
+
+ aggregate = compute_client.add_host_to_aggregate(
+ aggregate.id, parsed_args.host)
+
+ display_columns, columns = _get_aggregate_columns(aggregate)
+ data = utils.get_item_properties(
+ aggregate, columns, formatters=_aggregate_formatters)
+ return (display_columns, data)
class CreateAggregate(command.ShowOne):
@@ -95,36 +107,25 @@ class CreateAggregate(command.ShowOne):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
- info = {}
- data = compute_client.aggregates.create(
- parsed_args.name,
- parsed_args.zone,
- )
- info.update(data._info)
+ attrs = {'name': parsed_args.name}
+
+ if parsed_args.zone:
+ attrs['availability_zone'] = parsed_args.zone
+
+ aggregate = compute_client.create_aggregate(**attrs)
if parsed_args.property:
- info.update(compute_client.aggregates.set_metadata(
- data,
+ aggregate = compute_client.set_aggregate_metadata(
+ aggregate.id,
parsed_args.property,
- )._info)
-
- # Special mapping for columns to make the output easier to read:
- # 'metadata' --> 'properties'
- hosts = None
- properties = None
- if 'hosts' in info.keys():
- hosts = format_columns.ListColumn(info.pop('hosts'))
- if 'metadata' in info.keys():
- properties = format_columns.DictColumn(info.pop('metadata'))
- info.update(
- {
- 'hosts': hosts,
- 'properties': properties,
- },
- )
- return zip(*sorted(info.items()))
+ )
+
+ display_columns, columns = _get_aggregate_columns(aggregate)
+ data = utils.get_item_properties(
+ aggregate, columns, formatters=_aggregate_formatters)
+ return (display_columns, data)
class DeleteAggregate(command.Command):
@@ -141,13 +142,14 @@ class DeleteAggregate(command.Command):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
result = 0
for a in parsed_args.aggregate:
try:
- data = utils.find_resource(
- compute_client.aggregates, a)
- compute_client.aggregates.delete(data.id)
+ aggregate = compute_client.find_aggregate(
+ a, ignore_missing=False)
+ compute_client.delete_aggregate(
+ aggregate.id, ignore_missing=False)
except Exception as e:
result += 1
LOG.error(_("Failed to delete aggregate with name or "
@@ -175,15 +177,15 @@ class ListAggregate(command.Lister):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
- data = compute_client.aggregates.list()
+ aggregates = list(compute_client.aggregates())
if parsed_args.long:
# Remove availability_zone from metadata because Nova doesn't
- for d in data:
- if 'availability_zone' in d.metadata:
- d.metadata.pop('availability_zone')
+ for aggregate in aggregates:
+ if 'availability_zone' in aggregate.metadata:
+ aggregate.metadata.pop('availability_zone')
# This is the easiest way to change column headers
column_headers = (
"ID",
@@ -204,14 +206,11 @@ class ListAggregate(command.Lister):
"Availability Zone",
)
- return (column_headers,
- (utils.get_item_properties(
- s, columns,
- formatters={
- 'Hosts': format_columns.ListColumn,
- 'Metadata': format_columns.DictColumn,
- },
- ) for s in data))
+ data = (
+ utils.get_item_properties(
+ s, columns, formatters=_aggregate_formatters
+ ) for s in aggregates)
+ return (column_headers, data)
class RemoveAggregateHost(command.ShowOne):
@@ -232,29 +231,18 @@ class RemoveAggregateHost(command.ShowOne):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
- aggregate = utils.find_resource(
- compute_client.aggregates,
- parsed_args.aggregate,
- )
- data = compute_client.aggregates.remove_host(
- aggregate,
- parsed_args.host,
- )
+ aggregate = compute_client.find_aggregate(
+ parsed_args.aggregate, ignore_missing=False)
- info = {}
- info.update(data._info)
+ aggregate = compute_client.remove_host_from_aggregate(
+ aggregate.id, parsed_args.host)
- # Special mapping for columns to make the output easier to read:
- # 'metadata' --> 'properties'
- info.update(
- {
- 'hosts': format_columns.ListColumn(info.pop('hosts')),
- 'properties': format_columns.DictColumn(info.pop('metadata')),
- },
- )
- return zip(*sorted(info.items()))
+ display_columns, columns = _get_aggregate_columns(aggregate)
+ data = utils.get_item_properties(
+ aggregate, columns, formatters=_aggregate_formatters)
+ return (display_columns, data)
class SetAggregate(command.Command):
@@ -296,11 +284,9 @@ class SetAggregate(command.Command):
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- aggregate = utils.find_resource(
- compute_client.aggregates,
- parsed_args.aggregate,
- )
+ compute_client = self.app.client_manager.sdk_connection.compute
+ aggregate = compute_client.find_aggregate(
+ parsed_args.aggregate, ignore_missing=False)
kwargs = {}
if parsed_args.name:
@@ -308,18 +294,12 @@ class SetAggregate(command.Command):
if parsed_args.zone:
kwargs['availability_zone'] = parsed_args.zone
if kwargs:
- compute_client.aggregates.update(
- aggregate,
- kwargs
- )
+ compute_client.update_aggregate(aggregate.id, **kwargs)
set_property = {}
if parsed_args.no_property:
- # NOTE(RuiChen): "availability_zone" is removed from response of
- # aggregate show and create commands, don't see it
- # anywhere, so pop it, avoid the unexpected server
- # exception(can't unset the availability zone from
- # aggregate metadata in nova).
+ # NOTE(RuiChen): "availability_zone" can not be unset from
+ # properties. It is already excluded from show and create output.
set_property.update({key: None
for key in aggregate.metadata.keys()
if key != 'availability_zone'})
@@ -327,8 +307,8 @@ class SetAggregate(command.Command):
set_property.update(parsed_args.property)
if set_property:
- compute_client.aggregates.set_metadata(
- aggregate,
+ compute_client.set_aggregate_metadata(
+ aggregate.id,
set_property
)
@@ -347,31 +327,18 @@ class ShowAggregate(command.ShowOne):
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- data = utils.find_resource(
- compute_client.aggregates,
- parsed_args.aggregate,
- )
+ compute_client = self.app.client_manager.sdk_connection.compute
+ aggregate = compute_client.find_aggregate(
+ parsed_args.aggregate, ignore_missing=False)
+
# Remove availability_zone from metadata because Nova doesn't
- if 'availability_zone' in data.metadata:
- data.metadata.pop('availability_zone')
-
- # Special mapping for columns to make the output easier to read:
- # 'metadata' --> 'properties'
- data._info.update(
- {
- 'hosts': format_columns.ListColumn(
- data._info.pop('hosts')
- ),
- 'properties': format_columns.DictColumn(
- data._info.pop('metadata')
- ),
- },
- )
+ if 'availability_zone' in aggregate.metadata:
+ aggregate.metadata.pop('availability_zone')
- info = {}
- info.update(data._info)
- return zip(*sorted(info.items()))
+ display_columns, columns = _get_aggregate_columns(aggregate)
+ data = utils.get_item_properties(
+ aggregate, columns, formatters=_aggregate_formatters)
+ return (display_columns, data)
class UnsetAggregate(command.Command):
@@ -394,14 +361,56 @@ class UnsetAggregate(command.Command):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- aggregate = utils.find_resource(
- compute_client.aggregates,
- parsed_args.aggregate)
+ compute_client = self.app.client_manager.sdk_connection.compute
+ aggregate = compute_client.find_aggregate(
+ parsed_args.aggregate, ignore_missing=False)
unset_property = {}
if parsed_args.property:
unset_property.update({key: None for key in parsed_args.property})
if unset_property:
- compute_client.aggregates.set_metadata(aggregate,
- unset_property)
+ compute_client.set_aggregate_metadata(
+ aggregate, unset_property)
+
+
+class CacheImageForAggregate(command.Command):
+ _description = _("Request image caching for aggregate")
+ # NOTE(gtema): According to stephenfin and dansmith there is no and will
+ # not be anything to return.
+
+ def get_parser(self, prog_name):
+ parser = super(CacheImageForAggregate, self).get_parser(prog_name)
+ parser.add_argument(
+ 'aggregate',
+ metavar='<aggregate>',
+ help=_("Aggregate (name or ID)")
+ )
+ parser.add_argument(
+ 'image',
+ metavar='<image>',
+ nargs='+',
+ help=_("Image ID to request caching for aggregate (name or ID). "
+ "May be specified multiple times.")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.sdk_connection.compute
+
+ if not sdk_utils.supports_microversion(compute_client, '2.81'):
+ msg = _(
+ 'This operation requires server support for '
+ 'API microversion 2.81'
+ )
+ raise exceptions.CommandError(msg)
+
+ aggregate = compute_client.find_aggregate(
+ parsed_args.aggregate, ignore_missing=False)
+
+ images = []
+ for img in parsed_args.image:
+ image = self.app.client_manager.sdk_connection.image.find_image(
+ img, ignore_missing=False)
+ images.append(image.id)
+
+ compute_client.aggregate_precache_images(aggregate.id, images)
diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py
index 110b21b8..0ab5c8a2 100644
--- a/openstackclient/compute/v2/console.py
+++ b/openstackclient/compute/v2/console.py
@@ -22,6 +22,15 @@ from osc_lib import utils
from openstackclient.i18n import _
+def _get_console_columns(item):
+ # To maintain backwards compatibility we need to rename sdk props to
+ # whatever OSC was using before
+ column_map = {}
+ hidden_columns = ['id', 'links', 'location', 'name']
+ return utils.get_osc_show_columns_for_sdk_resource(
+ item, column_map, hidden_columns)
+
+
class ShowConsoleLog(command.Command):
_description = _("Show server's console output")
@@ -44,19 +53,18 @@ class ShowConsoleLog(command.Command):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
- server = utils.find_resource(
- compute_client.servers,
- parsed_args.server,
+ server = compute_client.find_server(
+ name_or_id=parsed_args.server,
+ ignore_missing=False
)
- length = parsed_args.lines
- if length:
- # NOTE(dtroyer): get_console_output() appears to shortchange the
- # output by one line
- length += 1
- data = server.get_console_output(length=length)
+ output = compute_client.get_server_console_output(
+ server.id, length=parsed_args.lines)
+ data = None
+ if output:
+ data = output.get('output', None)
if data and data[-1] != '\n':
data += '\n'
@@ -120,21 +128,15 @@ class ShowConsoleURL(command.ShowOne):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- server = utils.find_resource(
- compute_client.servers,
+ compute_client = self.app.client_manager.sdk_connection.compute
+ server = compute_client.find_server(
parsed_args.server,
- )
+ ignore_missing=False)
+
+ data = compute_client.create_console(server.id,
+ console_type=parsed_args.url_type)
+
+ display_columns, columns = _get_console_columns(data)
+ data = utils.get_dict_properties(data, columns)
- data = server.get_console_url(parsed_args.url_type)
- if not data:
- return ({}, {})
-
- info = {}
- # NOTE(Rui Chen): Return 'remote_console' in compute microversion API
- # 2.6 and later, return 'console' in compute
- # microversion API from 2.0 to 2.5, do compatibility
- # handle for different microversion API.
- console_data = data.get('remote_console', data.get('console'))
- info.update(console_data)
- return zip(*sorted(info.items()))
+ return (display_columns, data)
diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index 42649db5..8477e8ef 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -17,7 +17,9 @@
import logging
-from novaclient import api_versions
+from openstack import exceptions as sdk_exceptions
+from openstack import utils as sdk_utils
+from osc_lib.cli import format_columns
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
@@ -30,22 +32,25 @@ from openstackclient.identity import common as identity_common
LOG = logging.getLogger(__name__)
-def _find_flavor(compute_client, flavor):
- try:
- return compute_client.flavors.get(flavor)
- except Exception as ex:
- if type(ex).__name__ == 'NotFound':
- pass
- else:
- raise
- try:
- return compute_client.flavors.find(name=flavor, is_public=None)
- except Exception as ex:
- if type(ex).__name__ == 'NotFound':
- msg = _("No flavor with a name or ID of '%s' exists.") % flavor
- raise exceptions.CommandError(msg)
- else:
- raise
+_formatters = {
+ 'extra_specs': format_columns.DictColumn,
+ 'properties': format_columns.DictColumn
+}
+
+
+def _get_flavor_columns(item):
+ # To maintain backwards compatibility we need to rename sdk props to
+ # whatever OSC was using before
+ column_map = {
+ 'extra_specs': 'properties',
+ 'ephemeral': 'OS-FLV-EXT-DATA:ephemeral',
+ 'is_disabled': 'OS-FLV-DISABLED:disabled',
+ 'is_public': 'os-flavor-access:is_public'
+
+ }
+ hidden_columns = ['links', 'location']
+ return utils.get_osc_show_columns_for_sdk_resource(
+ item, column_map, hidden_columns)
class CreateFlavor(command.ShowOne):
@@ -61,9 +66,7 @@ class CreateFlavor(command.ShowOne):
parser.add_argument(
"--id",
metavar="<id>",
- default='auto',
- help=_("Unique flavor ID; 'auto' creates a UUID "
- "(default: auto)")
+ help=_("Unique flavor ID")
)
parser.add_argument(
"--ram",
@@ -144,32 +147,36 @@ class CreateFlavor(command.ShowOne):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
identity_client = self.app.client_manager.identity
if parsed_args.project and parsed_args.public:
msg = _("--project is only allowed with --private")
raise exceptions.CommandError(msg)
+ args = {
+ 'name': parsed_args.name,
+ 'ram': parsed_args.ram,
+ 'vcpus': parsed_args.vcpus,
+ 'disk': parsed_args.disk,
+ 'id': parsed_args.id,
+ 'ephemeral': parsed_args.ephemeral,
+ 'swap': parsed_args.swap,
+ 'rxtx_factor': parsed_args.rxtx_factor,
+ 'is_public': parsed_args.public,
+ }
+
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")
+ if not sdk_utils.supports_microversion(compute_client, '2.55'):
+ msg = _(
+ 'The --description parameter requires server support for '
+ 'API microversion 2.55'
+ )
raise exceptions.CommandError(msg)
- args = (
- parsed_args.name,
- parsed_args.ram,
- parsed_args.vcpus,
- parsed_args.disk,
- parsed_args.id,
- parsed_args.ephemeral,
- parsed_args.swap,
- parsed_args.rxtx_factor,
- parsed_args.public,
- parsed_args.description
- )
+ args['description'] = parsed_args.description
- flavor = compute_client.flavors.create(*args)
+ flavor = compute_client.create_flavor(**args)
if parsed_args.project:
try:
@@ -178,7 +185,7 @@ class CreateFlavor(command.ShowOne):
parsed_args.project,
parsed_args.project_domain,
).id
- compute_client.flavor_access.add_tenant_access(
+ compute_client.flavor_add_tenant_access(
flavor.id, project_id)
except Exception as e:
msg = _("Failed to add project %(project)s access to "
@@ -186,15 +193,16 @@ class CreateFlavor(command.ShowOne):
LOG.error(msg, {'project': parsed_args.project, 'e': e})
if parsed_args.property:
try:
- flavor.set_keys(parsed_args.property)
+ flavor = compute_client.create_flavor_extra_specs(
+ flavor, parsed_args.property)
except Exception as e:
LOG.error(_("Failed to set flavor property: %s"), e)
- flavor_info = flavor._info.copy()
- flavor_info.pop("links")
- flavor_info['properties'] = utils.format_dict(flavor.get_keys())
+ display_columns, columns = _get_flavor_columns(flavor)
+ data = utils.get_dict_properties(flavor, columns,
+ formatters=_formatters)
- return zip(*sorted(flavor_info.items()))
+ return (display_columns, data)
class DeleteFlavor(command.Command):
@@ -211,12 +219,12 @@ class DeleteFlavor(command.Command):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
result = 0
for f in parsed_args.flavor:
try:
- flavor = _find_flavor(compute_client, f)
- compute_client.flavors.delete(flavor.id)
+ flavor = compute_client.find_flavor(f, ignore_missing=False)
+ compute_client.delete_flavor(flavor.id)
except Exception as e:
result += 1
LOG.error(_("Failed to delete flavor with name or "
@@ -275,41 +283,67 @@ class ListFlavor(command.Lister):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
+ # is_public is ternary - None means give all flavors,
+ # True is public only and False is private only
+ # By default Nova assumes True and gives admins public flavors
+ # and flavors from their own projects only.
+ is_public = None if parsed_args.all else parsed_args.public
+
+ query_attrs = {
+ 'is_public': is_public
+ }
+ if parsed_args.marker:
+ query_attrs['marker'] = parsed_args.marker
+ if parsed_args.limit:
+ query_attrs['limit'] = parsed_args.limit
+ if parsed_args.limit or parsed_args.marker:
+ # User passed explicit pagination request, switch off SDK
+ # pagination
+ query_attrs['paginated'] = False
+
+ data = list(compute_client.flavors(**query_attrs))
+ # Even if server supports 2.61 some policy might stop it sending us
+ # extra_specs. So try to fetch them if they are absent
+ for f in data:
+ if not f.extra_specs:
+ compute_client.fetch_flavor_extra_specs(f)
+
columns = (
+ "id",
+ "name",
+ "ram",
+ "disk",
+ "ephemeral",
+ "vcpus",
+ "is_public"
+ )
+ if parsed_args.long:
+ columns += (
+ "swap",
+ "rxtx_factor",
+ "extra_specs",
+ )
+
+ column_headers = (
"ID",
"Name",
"RAM",
"Disk",
"Ephemeral",
"VCPUs",
- "Is Public",
+ "Is Public"
)
-
- # is_public is ternary - None means give all flavors,
- # True is public only and False is private only
- # By default Nova assumes True and gives admins public flavors
- # and flavors from their own projects only.
- is_public = None if parsed_args.all else parsed_args.public
-
- data = compute_client.flavors.list(is_public=is_public,
- marker=parsed_args.marker,
- limit=parsed_args.limit)
-
if parsed_args.long:
- columns = columns + (
+ column_headers += (
"Swap",
"RXTX Factor",
"Properties",
)
- for f in data:
- f.properties = f.get_keys()
-
- column_headers = columns
return (column_headers,
(utils.get_item_properties(
- s, columns, formatters={'Properties': utils.format_dict},
+ s, columns, formatters=_formatters,
) for s in data))
@@ -355,24 +389,42 @@ class SetFlavor(command.Command):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
identity_client = self.app.client_manager.identity
- flavor = _find_flavor(compute_client, parsed_args.flavor)
+ try:
+ flavor = compute_client.find_flavor(
+ parsed_args.flavor,
+ get_extra_specs=True,
+ ignore_missing=False)
+ except sdk_exceptions.ResourceNotFound as e:
+ raise exceptions.CommandError(e.message)
+
+ if parsed_args.description:
+ if not sdk_utils.supports_microversion(compute_client, '2.55'):
+ msg = _(
+ 'The --description parameter requires server support for '
+ 'API microversion 2.55'
+ )
+ raise exceptions.CommandError(msg)
+
+ compute_client.update_flavor(
+ flavor=flavor.id, description=parsed_args.description)
result = 0
- key_list = []
if parsed_args.no_property:
try:
- for key in flavor.get_keys().keys():
- key_list.append(key)
- flavor.unset_keys(key_list)
+ for key in flavor.extra_specs.keys():
+ compute_client.delete_flavor_extra_specs_property(
+ flavor.id, key)
except Exception as e:
LOG.error(_("Failed to clear flavor property: %s"), e)
result += 1
+
if parsed_args.property:
try:
- flavor.set_keys(parsed_args.property)
+ compute_client.create_flavor_extra_specs(
+ flavor.id, parsed_args.property)
except Exception as e:
LOG.error(_("Failed to set flavor property: %s"), e)
result += 1
@@ -388,7 +440,7 @@ class SetFlavor(command.Command):
parsed_args.project,
parsed_args.project_domain,
).id
- compute_client.flavor_access.add_tenant_access(
+ compute_client.flavor_add_tenant_access(
flavor.id, project_id)
except Exception as e:
LOG.error(_("Failed to set flavor access to project: %s"), e)
@@ -398,13 +450,6 @@ 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")
@@ -419,34 +464,34 @@ class ShowFlavor(command.ShowOne):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- resource_flavor = _find_flavor(compute_client, parsed_args.flavor)
+ compute_client = self.app.client_manager.sdk_connection.compute
+ flavor = compute_client.find_flavor(
+ parsed_args.flavor, get_extra_specs=True, ignore_missing=False)
access_projects = None
# get access projects list of this flavor
- if not resource_flavor.is_public:
+ if not flavor.is_public:
try:
- flavor_access = compute_client.flavor_access.list(
- flavor=resource_flavor.id)
- projects = [utils.get_field(access, 'tenant_id')
- for access in flavor_access]
- # TODO(Huanxuan Ao): This format case can be removed after
- # patch https://review.opendev.org/#/c/330223/ merged.
- access_projects = utils.format_list(projects)
+ flavor_access = compute_client.get_flavor_access(
+ flavor=flavor.id)
+ access_projects = [
+ utils.get_field(access, 'tenant_id')
+ for access in flavor_access]
except Exception as e:
msg = _("Failed to get access projects list "
"for flavor '%(flavor)s': %(e)s")
LOG.error(msg, {'flavor': parsed_args.flavor, 'e': e})
- flavor = resource_flavor._info.copy()
- flavor.update({
- 'access_project_ids': access_projects
- })
- flavor.pop("links", None)
+ # Since we need to inject "access_project_id" into resource - convert
+ # it to dict and treat it respectively
+ flavor = flavor.to_dict()
+ flavor['access_project_ids'] = access_projects
- flavor['properties'] = utils.format_dict(resource_flavor.get_keys())
+ display_columns, columns = _get_flavor_columns(flavor)
+ data = utils.get_dict_properties(
+ flavor, columns, formatters=_formatters)
- return zip(*sorted(flavor.items()))
+ return (display_columns, data)
class UnsetFlavor(command.Command):
@@ -477,32 +522,40 @@ class UnsetFlavor(command.Command):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
identity_client = self.app.client_manager.identity
- flavor = _find_flavor(compute_client, parsed_args.flavor)
+ try:
+ flavor = compute_client.find_flavor(
+ parsed_args.flavor,
+ get_extra_specs=True,
+ ignore_missing=False)
+ except sdk_exceptions.ResourceNotFound as e:
+ raise exceptions.CommandError(_(e.message))
result = 0
if parsed_args.property:
- try:
- flavor.unset_keys(parsed_args.property)
- except Exception as e:
- LOG.error(_("Failed to unset flavor property: %s"), e)
- result += 1
+ for key in parsed_args.property:
+ try:
+ compute_client.delete_flavor_extra_specs_property(
+ flavor.id, key)
+ except sdk_exceptions.SDKException as e:
+ LOG.error(_("Failed to unset flavor property: %s"), e)
+ result += 1
if parsed_args.project:
try:
if flavor.is_public:
msg = _("Cannot remove access for a public flavor")
raise exceptions.CommandError(msg)
- else:
- project_id = identity_common.find_project(
- identity_client,
- parsed_args.project,
- parsed_args.project_domain,
- ).id
- compute_client.flavor_access.remove_tenant_access(
- flavor.id, project_id)
+
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ compute_client.flavor_remove_tenant_access(
+ flavor.id, project_id)
except Exception as e:
LOG.error(_("Failed to remove flavor access from project: %s"),
e)
diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py
index 2b365ceb..19e30bff 100644
--- a/openstackclient/compute/v2/keypair.py
+++ b/openstackclient/compute/v2/keypair.py
@@ -20,16 +20,31 @@ import logging
import os
import sys
+from openstack import utils as sdk_utils
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
LOG = logging.getLogger(__name__)
+def _get_keypair_columns(item, hide_pub_key=False, hide_priv_key=False):
+ # To maintain backwards compatibility we need to rename sdk props to
+ # whatever OSC was using before
+ column_map = {}
+ hidden_columns = ['links', 'location']
+ if hide_pub_key:
+ hidden_columns.append('public_key')
+ if hide_priv_key:
+ hidden_columns.append('private_key')
+ return utils.get_osc_show_columns_for_sdk_resource(
+ item, column_map, hidden_columns)
+
+
class CreateKeypair(command.ShowOne):
_description = _("Create new public or private key for server ssh access")
@@ -53,10 +68,33 @@ class CreateKeypair(command.ShowOne):
help=_("Filename for private key to save. If not used, "
"print private key in console.")
)
+ parser.add_argument(
+ '--type',
+ metavar='<type>',
+ choices=['ssh', 'x509'],
+ help=_(
+ "Keypair type. Can be ssh or x509. "
+ "(Supported by API versions '2.2' - '2.latest')"
+ ),
+ )
+ parser.add_argument(
+ '--user',
+ metavar='<user>',
+ help=_(
+ 'The owner of the keypair. (admin only) (name or ID). '
+ 'Requires ``--os-compute-api-version`` 2.10 or greater.'
+ ),
+ )
+ identity_common.add_user_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
+ identity_client = self.app.client_manager.identity
+
+ kwargs = {
+ 'name': parsed_args.name
+ }
public_key = parsed_args.public_key
if public_key:
@@ -70,17 +108,40 @@ class CreateKeypair(command.ShowOne):
"exception": e}
)
- keypair = compute_client.keypairs.create(
- parsed_args.name,
- public_key=public_key,
- )
+ kwargs['public_key'] = public_key
+
+ if parsed_args.type:
+ if not sdk_utils.supports_microversion(compute_client, '2.2'):
+ msg = _(
+ '--os-compute-api-version 2.2 or greater is required to '
+ 'support the --type option'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs['key_type'] = parsed_args.type
+
+ if parsed_args.user:
+ if not sdk_utils.supports_microversion(compute_client, '2.10'):
+ msg = _(
+ '--os-compute-api-version 2.10 or greater is required to '
+ 'support the --user option'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs['user_id'] = identity_common.find_user(
+ identity_client,
+ parsed_args.user,
+ parsed_args.user_domain,
+ ).id
+
+ keypair = compute_client.create_keypair(**kwargs)
private_key = parsed_args.private_key
# Save private key into specified file
if private_key:
try:
with io.open(
- os.path.expanduser(parsed_args.private_key), 'w+'
+ os.path.expanduser(parsed_args.private_key), 'w+'
) as p:
p.write(keypair.private_key)
except IOError as e:
@@ -93,14 +154,12 @@ class CreateKeypair(command.ShowOne):
# NOTE(dtroyer): how do we want to handle the display of the private
# key when it needs to be communicated back to the user
# For now, duplicate nova keypair-add command output
- info = {}
if public_key or private_key:
- info.update(keypair._info)
- if 'public_key' in info:
- del info['public_key']
- if 'private_key' in info:
- del info['private_key']
- return zip(*sorted(info.items()))
+ display_columns, columns = _get_keypair_columns(
+ keypair, hide_pub_key=True, hide_priv_key=True)
+ data = utils.get_item_properties(keypair, columns)
+
+ return (display_columns, data)
else:
sys.stdout.write(keypair.private_key)
return ({}, {})
@@ -117,16 +176,42 @@ class DeleteKeypair(command.Command):
nargs='+',
help=_("Name of key(s) to delete (name only)")
)
+ parser.add_argument(
+ '--user',
+ metavar='<user>',
+ help=_(
+ 'The owner of the keypair. (admin only) (name or ID). '
+ 'Requires ``--os-compute-api-version`` 2.10 or greater.'
+ ),
+ )
+ identity_common.add_user_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
+ identity_client = self.app.client_manager.identity
+
+ kwargs = {}
result = 0
+
+ if parsed_args.user:
+ if not sdk_utils.supports_microversion(compute_client, '2.10'):
+ msg = _(
+ '--os-compute-api-version 2.10 or greater is required to '
+ 'support the --user option'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs['user_id'] = identity_common.find_user(
+ identity_client,
+ parsed_args.user,
+ parsed_args.user_domain,
+ ).id
+
for n in parsed_args.name:
try:
- data = utils.find_resource(
- compute_client.keypairs, n)
- compute_client.keypairs.delete(data.name)
+ compute_client.delete_keypair(
+ n, **kwargs, ignore_missing=False)
except Exception as e:
result += 1
LOG.error(_("Failed to delete key with name "
@@ -142,18 +227,85 @@ class DeleteKeypair(command.Command):
class ListKeypair(command.Lister):
_description = _("List key fingerprints")
+ def get_parser(self, prog_name):
+ parser = super().get_parser(prog_name)
+ user_group = parser.add_mutually_exclusive_group()
+ user_group.add_argument(
+ '--user',
+ metavar='<user>',
+ help=_(
+ 'Show keypairs for another user (admin only) (name or ID). '
+ 'Requires ``--os-compute-api-version`` 2.10 or greater.'
+ ),
+ )
+ identity_common.add_user_domain_option_to_parser(parser)
+ user_group.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_(
+ 'Show keypairs for all users associated with project '
+ '(admin only) (name or ID). '
+ 'Requires ``--os-compute-api-version`` 2.10 or greater.'
+ ),
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ return parser
+
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
+ identity_client = self.app.client_manager.identity
+
+ if parsed_args.project:
+ if not sdk_utils.supports_microversion(compute_client, '2.10'):
+ msg = _(
+ '--os-compute-api-version 2.10 or greater is required to '
+ 'support the --project option'
+ )
+ raise exceptions.CommandError(msg)
+
+ # NOTE(stephenfin): This is done client side because nova doesn't
+ # currently support doing so server-side. If this is slow, we can
+ # think about spinning up a threadpool or similar.
+ project = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ users = identity_client.users.list(tenant_id=project)
+
+ data = []
+ for user in users:
+ data.extend(compute_client.keypairs(user_id=user.id))
+ elif parsed_args.user:
+ if not sdk_utils.supports_microversion(compute_client, '2.10'):
+ msg = _(
+ '--os-compute-api-version 2.10 or greater is required to '
+ 'support the --user option'
+ )
+ raise exceptions.CommandError(msg)
+
+ user = identity_common.find_user(
+ identity_client,
+ parsed_args.user,
+ parsed_args.user_domain,
+ )
+
+ data = compute_client.keypairs(user_id=user.id)
+ else:
+ data = compute_client.keypairs()
+
columns = (
"Name",
"Fingerprint"
)
- data = compute_client.keypairs.list()
- return (columns,
- (utils.get_item_properties(
- s, columns,
- ) for s in data))
+ if sdk_utils.supports_microversion(compute_client, '2.2'):
+ columns += ("Type", )
+
+ return (
+ columns,
+ (utils.get_item_properties(s, columns) for s in data),
+ )
class ShowKeypair(command.ShowOne):
@@ -172,20 +324,45 @@ class ShowKeypair(command.ShowOne):
default=False,
help=_("Show only bare public key paired with the generated key")
)
+ parser.add_argument(
+ '--user',
+ metavar='<user>',
+ help=_(
+ 'The owner of the keypair. (admin only) (name or ID). '
+ 'Requires ``--os-compute-api-version`` 2.10 or greater.'
+ ),
+ )
+ identity_common.add_user_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- keypair = utils.find_resource(compute_client.keypairs,
- parsed_args.name)
+ compute_client = self.app.client_manager.sdk_connection.compute
+ identity_client = self.app.client_manager.identity
+
+ kwargs = {}
+
+ if parsed_args.user:
+ if not sdk_utils.supports_microversion(compute_client, '2.10'):
+ msg = _(
+ '--os-compute-api-version 2.10 or greater is required to '
+ 'support the --user option'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs['user_id'] = identity_common.find_user(
+ identity_client,
+ parsed_args.user,
+ parsed_args.user_domain,
+ ).id
+
+ keypair = compute_client.find_keypair(
+ parsed_args.name, **kwargs, ignore_missing=False)
- info = {}
- info.update(keypair._info)
if not parsed_args.public_key:
- del info['public_key']
- return zip(*sorted(info.items()))
+ display_columns, columns = _get_keypair_columns(
+ keypair, hide_pub_key=True)
+ data = utils.get_item_properties(keypair, columns)
+ return (display_columns, data)
else:
- # NOTE(dtroyer): a way to get the public key in a similar form
- # as the private key in the create command
sys.stdout.write(keypair.public_key)
return ({}, {})
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 93e9f966..24c09ed1 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -21,15 +21,15 @@ import io
import logging
import os
+import iso8601
from novaclient import api_versions
from novaclient.v2 import servers
from openstack import exceptions as sdk_exceptions
+from osc_lib.cli import format_columns
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
-from oslo_utils import timeutils
-import six
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
@@ -38,6 +38,8 @@ from openstackclient.network import common as network_common
LOG = logging.getLogger(__name__)
+IMAGE_STRING_FOR_BFV = 'N/A (booted from volume)'
+
def _format_servers_list_networks(networks):
"""Return a formatted string of a server's networks
@@ -94,7 +96,7 @@ def _get_ip_address(addresses, address_type, ip_address_family):
for network in addresses:
for addy in addresses[network]:
# Case where it is list of strings
- if isinstance(addy, six.string_types):
+ if isinstance(addy, str):
if new_address_type == 'fixed':
return addresses[network][0]
else:
@@ -147,6 +149,12 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
info['image'] = "%s (%s)" % (image.name, image_id)
except Exception:
info['image'] = image_id
+ else:
+ # NOTE(melwitt): An server booted from a volume will have no image
+ # associated with it. We fill in the image with "N/A (booted from
+ # volume)" to help users who want to be able to grep for
+ # boot-from-volume servers when using the CLI.
+ info['image'] = IMAGE_STRING_FOR_BFV
# Convert the flavor blob to a name
flavor_info = info.get('flavor', {})
@@ -166,14 +174,14 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
if 'os-extended-volumes:volumes_attached' in info:
info.update(
{
- 'volumes_attached': utils.format_list_of_dicts(
+ 'volumes_attached': format_columns.ListDictColumn(
info.pop('os-extended-volumes:volumes_attached'))
}
)
if 'security_groups' in info:
info.update(
{
- 'security_groups': utils.format_list_of_dicts(
+ 'security_groups': format_columns.ListDictColumn(
info.pop('security_groups'))
}
)
@@ -182,9 +190,14 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
info['addresses'] = _format_servers_list_networks(server.networks)
# Map 'metadata' field to 'properties'
- info.update(
- {'properties': utils.format_dict(info.pop('metadata'))}
- )
+ if not info['metadata']:
+ info.update(
+ {'properties': utils.format_dict(info.pop('metadata'))}
+ )
+ else:
+ info.update(
+ {'properties': format_columns.DictColumn(info.pop('metadata'))}
+ )
# Migrate tenant_id to project_id naming
if 'tenant_id' in info:
@@ -223,6 +236,14 @@ class AddFixedIP(command.Command):
metavar="<ip-address>",
help=_("Requested fixed IP address"),
)
+ parser.add_argument(
+ '--tag',
+ metavar='<tag>',
+ help=_(
+ 'Tag for the attached interface. '
+ '(supported by --os-compute-api-version 2.52 or above)'
+ )
+ )
return parser
def take_action(self, parsed_args):
@@ -233,11 +254,23 @@ class AddFixedIP(command.Command):
network = compute_client.api.network_find(parsed_args.network)
- server.interface_attach(
- port_id=None,
- net_id=network['id'],
- fixed_ip=parsed_args.fixed_ip_address,
- )
+ kwargs = {
+ 'port_id': None,
+ 'net_id': network['id'],
+ 'fixed_ip': parsed_args.fixed_ip_address,
+ }
+
+ if parsed_args.tag:
+ if compute_client.api_version < api_versions.APIVersion('2.49'):
+ msg = _(
+ '--os-compute-api-version 2.49 or greater is required to '
+ 'support the --tag option'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs['tag'] = parsed_args.tag
+
+ server.interface_attach(**kwargs)
class AddFloatingIP(network_common.NetworkAndComputeCommand):
@@ -279,6 +312,10 @@ class AddFloatingIP(network_common.NetworkAndComputeCommand):
parsed_args.server,
)
ports = list(client.ports(device_id=server.id))
+ if not ports:
+ msg = _('No attached ports found to associate floating IP with')
+ raise exceptions.CommandError(msg)
+
# If the fixed IP address was specified, we need to find the
# corresponding port.
if parsed_args.fixed_ip_address:
@@ -341,6 +378,14 @@ class AddPort(command.Command):
metavar="<port>",
help=_("Port to add to the server (name or ID)"),
)
+ parser.add_argument(
+ '--tag',
+ metavar='<tag>',
+ help=_(
+ "Tag for the attached interface. "
+ "(Supported by API versions '2.49' - '2.latest')"
+ )
+ )
return parser
def take_action(self, parsed_args):
@@ -356,7 +401,22 @@ class AddPort(command.Command):
else:
port_id = parsed_args.port
- server.interface_attach(port_id=port_id, net_id=None, fixed_ip=None)
+ kwargs = {
+ 'port_id': port_id,
+ 'net_id': None,
+ 'fixed_ip': None,
+ }
+
+ if parsed_args.tag:
+ if compute_client.api_version < api_versions.APIVersion("2.49"):
+ msg = _(
+ '--os-compute-api-version 2.49 or greater is required to '
+ 'support the --tag option'
+ )
+ raise exceptions.CommandError(msg)
+ kwargs['tag'] = parsed_args.tag
+
+ server.interface_attach(**kwargs)
class AddNetwork(command.Command):
@@ -374,6 +434,14 @@ class AddNetwork(command.Command):
metavar="<network>",
help=_("Network to add to the server (name or ID)"),
)
+ parser.add_argument(
+ '--tag',
+ metavar='<tag>',
+ help=_(
+ 'Tag for the attached interface. '
+ '(supported by --os-compute-api-version 2.49 or above)'
+ ),
+ )
return parser
def take_action(self, parsed_args):
@@ -389,7 +457,23 @@ class AddNetwork(command.Command):
else:
net_id = parsed_args.network
- server.interface_attach(port_id=None, net_id=net_id, fixed_ip=None)
+ kwargs = {
+ 'port_id': None,
+ 'net_id': net_id,
+ 'fixed_ip': None,
+ }
+
+ if parsed_args.tag:
+ if compute_client.api_version < api_versions.APIVersion('2.49'):
+ msg = _(
+ '--os-compute-api-version 2.49 or greater is required to '
+ 'support the --tag option'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs['tag'] = parsed_args.tag
+
+ server.interface_attach(**kwargs)
class AddServerSecurityGroup(command.Command):
@@ -446,20 +530,32 @@ class AddServerVolume(command.Command):
metavar='<device>',
help=_('Server internal device name for volume'),
)
+ parser.add_argument(
+ '--tag',
+ metavar='<tag>',
+ help=_(
+ "Tag for the attached volume. "
+ "(Supported by API versions '2.49' - '2.latest')"
+ ),
+ )
termination_group = parser.add_mutually_exclusive_group()
termination_group.add_argument(
'--enable-delete-on-termination',
action='store_true',
- help=_("Specify if the attached volume should be deleted when "
- "the server is destroyed. (Supported with "
- "``--os-compute-api-version`` 2.79 or greater.)"),
+ help=_(
+ "Specify if the attached volume should be deleted when the "
+ "server is destroyed. "
+ "(Supported by API versions '2.79' - '2.latest')"
+ ),
)
termination_group.add_argument(
'--disable-delete-on-termination',
action='store_true',
- help=_("Specify if the attached volume should not be deleted "
- "when the server is destroyed. (Supported with "
- "``--os-compute-api-version`` 2.79 or greater.)"),
+ help=_(
+ "Specify if the attached volume should not be deleted when "
+ "the server is destroyed. "
+ "(Supported by API versions '2.79' - '2.latest')"
+ ),
)
return parser
@@ -476,28 +572,38 @@ class AddServerVolume(command.Command):
parsed_args.volume,
)
- support_set_delete_on_termination = (compute_client.api_version >=
- api_versions.APIVersion('2.79'))
-
- if not support_set_delete_on_termination:
- if parsed_args.enable_delete_on_termination:
- msg = _('--os-compute-api-version 2.79 or greater '
- 'is required to support the '
- '--enable-delete-on-termination option.')
- raise exceptions.CommandError(msg)
- if parsed_args.disable_delete_on_termination:
- msg = _('--os-compute-api-version 2.79 or greater '
- 'is required to support the '
- '--disable-delete-on-termination option.')
- raise exceptions.CommandError(msg)
-
kwargs = {
"device": parsed_args.device
}
+ if parsed_args.tag:
+ if compute_client.api_version < api_versions.APIVersion('2.49'):
+ msg = _(
+ '--os-compute-api-version 2.49 or greater is required to '
+ 'support the --tag option'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs['tag'] = parsed_args.tag
+
if parsed_args.enable_delete_on_termination:
+ if compute_client.api_version < api_versions.APIVersion('2.79'):
+ msg = _(
+ '--os-compute-api-version 2.79 or greater is required to '
+ 'support the --enable-delete-on-termination option.'
+ )
+ raise exceptions.CommandError(msg)
+
kwargs['delete_on_termination'] = True
+
if parsed_args.disable_delete_on_termination:
+ if compute_client.api_version < api_versions.APIVersion('2.79'):
+ msg = _(
+ '--os-compute-api-version 2.79 or greater is required to '
+ 'support the --disable-delete-on-termination option.'
+ )
+ raise exceptions.CommandError(msg)
+
kwargs['delete_on_termination'] = False
compute_client.volumes.create_server_volume(
@@ -534,13 +640,20 @@ class CreateServer(command.ShowOne):
disk_group.add_argument(
'--volume',
metavar='<volume>',
- help=_('Create server using this volume as the boot disk (name '
- 'or ID).\n'
- 'This option automatically creates a block device mapping '
- 'with a boot index of 0. On many hypervisors (libvirt/kvm '
- 'for example) this will be device vda. Do not create a '
- 'duplicate mapping using --block-device-mapping for this '
- 'volume.'),
+ help=_(
+ 'Create server using this volume as the boot disk (name or ID)'
+ '\n'
+ 'This option automatically creates a block device mapping '
+ 'with a boot index of 0. On many hypervisors (libvirt/kvm '
+ 'for example) this will be device vda. Do not create a '
+ 'duplicate mapping using --block-device-mapping for this '
+ 'volume.'
+ ),
+ )
+ parser.add_argument(
+ '--password',
+ metavar='<password>',
+ help=_("Set the password to this server"),
)
parser.add_argument(
'--flavor',
@@ -553,28 +666,34 @@ class CreateServer(command.ShowOne):
metavar='<security-group>',
action='append',
default=[],
- help=_('Security group to assign to this server (name or ID) '
- '(repeat option to set multiple groups)'),
+ help=_(
+ 'Security group to assign to this server (name or ID) '
+ '(repeat option to set multiple groups)'
+ ),
)
parser.add_argument(
'--key-name',
metavar='<key-name>',
- help=_('Keypair to inject into this server (optional extension)'),
+ help=_('Keypair to inject into this server'),
)
parser.add_argument(
'--property',
metavar='<key=value>',
action=parseractions.KeyValueAction,
- help=_('Set a property on this server '
- '(repeat option to set multiple values)'),
+ help=_(
+ 'Set a property on this server '
+ '(repeat option to set multiple values)'
+ ),
)
parser.add_argument(
'--file',
metavar='<dest-filename=source-filename>',
action='append',
default=[],
- help=_('File to inject into image before boot '
- '(repeat option to set multiple files)'),
+ help=_(
+ 'File to inject into image before boot '
+ '(repeat option to set multiple files)'
+ ),
)
parser.add_argument(
'--user-data',
@@ -584,40 +703,53 @@ class CreateServer(command.ShowOne):
parser.add_argument(
'--description',
metavar='<description>',
- help=_('Set description for the server (supported by '
- '--os-compute-api-version 2.19 or above)'),
+ help=_(
+ 'Set description for the server '
+ '(supported by --os-compute-api-version 2.19 or above)'
+ ),
)
parser.add_argument(
'--availability-zone',
metavar='<zone-name>',
- help=_('Select an availability zone for the server'),
+ help=_('Select an availability zone for the server. '
+ 'Host and node are optional parameters. '
+ 'Availability zone in the format '
+ '<zone-name>:<host-name>:<node-name>, '
+ '<zone-name>::<node-name>, <zone-name>:<host-name> '
+ 'or <zone-name>'),
)
parser.add_argument(
'--host',
metavar='<host>',
- help=_('Requested host to create servers. Admin only '
- 'by default. (supported by --os-compute-api-version 2.74 '
- 'or above)'),
+ help=_(
+ 'Requested host to create servers. '
+ '(admin only) '
+ '(supported by --os-compute-api-version 2.74 or above)'
+ ),
)
parser.add_argument(
'--hypervisor-hostname',
metavar='<hypervisor-hostname>',
- help=_('Requested hypervisor hostname to create servers. Admin '
- 'only by default. (supported by --os-compute-api-version '
- '2.74 or above)'),
+ help=_(
+ 'Requested hypervisor hostname to create servers. '
+ '(admin only) '
+ '(supported by --os-compute-api-version 2.74 or above)'
+ ),
)
parser.add_argument(
'--boot-from-volume',
metavar='<volume-size>',
type=int,
- help=_('When used in conjunction with the ``--image`` or '
- '``--image-property`` option, this option automatically '
- 'creates a block device mapping with a boot index of 0 '
- 'and tells the compute service to create a volume of the '
- 'given size (in GB) from the specified image and use it '
- 'as the root disk of the server. The root volume will not '
- 'be deleted when the server is deleted. This option is '
- 'mutually exclusive with the ``--volume`` option.')
+ help=_(
+ 'When used in conjunction with the ``--image`` or '
+ '``--image-property`` option, this option automatically '
+ 'creates a block device mapping with a boot index of 0 '
+ 'and tells the compute service to create a volume of the '
+ 'given size (in GB) from the specified image and use it '
+ 'as the root disk of the server. The root volume will not '
+ 'be deleted when the server is deleted. This option is '
+ 'mutually exclusive with the ``--volume`` option.'
+ )
)
parser.add_argument(
'--block-device-mapping',
@@ -627,37 +759,40 @@ class CreateServer(command.ShowOne):
# NOTE(RuiChen): Add '\n' at the end of line to put each item in
# the separated line, avoid the help message looks
# messy, see _SmartHelpFormatter in cliff.
- help=_('Create a block device on the server.\n'
- 'Block device mapping in the format\n'
- '<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n'
- '<dev-name>: block device name, like: vdb, xvdc '
- '(required)\n'
- '<id>: Name or ID of the volume, volume snapshot or image '
- '(required)\n'
- '<type>: volume, snapshot or image; default: volume '
- '(optional)\n'
- '<size(GB)>: volume size if create from image or snapshot '
- '(optional)\n'
- '<delete-on-terminate>: true or false; default: false '
- '(optional)\n'
- '(optional extension)'),
+ help=_(
+ 'Create a block device on the server.\n'
+ 'Block device mapping in the format\n'
+ '<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n'
+ '<dev-name>: block device name, like: vdb, xvdc '
+ '(required)\n'
+ '<id>: Name or ID of the volume, volume snapshot or image '
+ '(required)\n'
+ '<type>: volume, snapshot or image; default: volume '
+ '(optional)\n'
+ '<size(GB)>: volume size if create from image or snapshot '
+ '(optional)\n'
+ '<delete-on-terminate>: true or false; default: false '
+ '(optional)\n'
+ ),
)
parser.add_argument(
'--nic',
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
"port-id=port-uuid,auto,none>",
action='append',
- help=_("Create a NIC on the server. "
- "Specify option multiple times to create multiple NICs. "
- "Either net-id or port-id must be provided, but not both. "
- "net-id: attach NIC to network with this UUID, "
- "port-id: attach NIC to port with this UUID, "
- "v4-fixed-ip: IPv4 fixed address for NIC (optional), "
- "v6-fixed-ip: IPv6 fixed address for NIC (optional), "
- "none: (v2.37+) no network is attached, "
- "auto: (v2.37+) the compute service will automatically "
- "allocate a network. Specifying a --nic of auto or none "
- "cannot be used with any other --nic value."),
+ help=_(
+ "Create a NIC on the server. "
+ "Specify option multiple times to create multiple NICs. "
+ "Either net-id or port-id must be provided, but not both. "
+ "net-id: attach NIC to network with this UUID, "
+ "port-id: attach NIC to port with this UUID, "
+ "v4-fixed-ip: IPv4 fixed address for NIC (optional), "
+ "v6-fixed-ip: IPv6 fixed address for NIC (optional), "
+ "none: (v2.37+) no network is attached, "
+ "auto: (v2.37+) the compute service will automatically "
+ "allocate a network. Specifying a --nic of auto or none "
+ "cannot be used with any other --nic value."
+ ),
)
parser.add_argument(
'--network',
@@ -665,13 +800,15 @@ class CreateServer(command.ShowOne):
action='append',
dest='nic',
type=_prefix_checked_value('net-id='),
- help=_("Create a NIC on the server and connect it to network. "
- "Specify option multiple times to create multiple NICs. "
- "This is a wrapper for the '--nic net-id=<network>' "
- "parameter that provides simple syntax for the standard "
- "use case of connecting a new server to a given network. "
- "For more advanced use cases, refer to the '--nic' "
- "parameter."),
+ help=_(
+ "Create a NIC on the server and connect it to network. "
+ "Specify option multiple times to create multiple NICs. "
+ "This is a wrapper for the '--nic net-id=<network>' "
+ "parameter that provides simple syntax for the standard "
+ "use case of connecting a new server to a given network. "
+ "For more advanced use cases, refer to the '--nic' "
+ "parameter."
+ ),
)
parser.add_argument(
'--port',
@@ -679,12 +816,14 @@ class CreateServer(command.ShowOne):
action='append',
dest='nic',
type=_prefix_checked_value('port-id='),
- help=_("Create a NIC on the server and connect it to port. "
- "Specify option multiple times to create multiple NICs. "
- "This is a wrapper for the '--nic port-id=<port>' "
- "parameter that provides simple syntax for the standard "
- "use case of connecting a new server to a given port. For "
- "more advanced use cases, refer to the '--nic' parameter."),
+ help=_(
+ "Create a NIC on the server and connect it to port. "
+ "Specify option multiple times to create multiple NICs. "
+ "This is a wrapper for the '--nic port-id=<port>' "
+ "parameter that provides simple syntax for the standard "
+ "use case of connecting a new server to a given port. For "
+ "more advanced use cases, refer to the '--nic' parameter."
+ ),
)
parser.add_argument(
'--hint',
@@ -693,12 +832,30 @@ class CreateServer(command.ShowOne):
default={},
help=_('Hints for the scheduler (optional extension)'),
)
- parser.add_argument(
+ config_drive_group = parser.add_mutually_exclusive_group()
+ config_drive_group.add_argument(
+ '--use-config-drive',
+ action='store_true',
+ dest='config_drive',
+ help=_("Enable config drive."),
+ )
+ config_drive_group.add_argument(
+ '--no-config-drive',
+ action='store_false',
+ dest='config_drive',
+ help=_("Disable config drive."),
+ )
+ # TODO(stephenfin): Drop support in the next major version bump after
+ # Victoria
+ config_drive_group.add_argument(
'--config-drive',
metavar='<config-drive-volume>|True',
default=False,
- help=_('Use specified volume as the config drive, '
- 'or \'True\' to use an ephemeral drive'),
+ help=_(
+ "**Deprecated** Use specified volume as the config drive, "
+ "or 'True' to use an ephemeral drive. Replaced by "
+ "'--use-config-drive'."
+ ),
)
parser.add_argument(
'--min',
@@ -719,6 +876,18 @@ class CreateServer(command.ShowOne):
action='store_true',
help=_('Wait for build to complete'),
)
+ parser.add_argument(
+ '--tag',
+ metavar='<tag>',
+ action='append',
+ default=[],
+ dest='tags',
+ help=_(
+ 'Tags for the server. '
+ 'Specify multiple times to add multiple tags. '
+ '(supported by --os-compute-api-version 2.52 or above)'
+ ),
+ )
return parser
def take_action(self, parsed_args):
@@ -741,57 +910,75 @@ class CreateServer(command.ShowOne):
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]})
+ 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.images()
images_matched = []
for img in image_list:
img_dict = {}
+
# exclude any unhashable entries
- for key, value in img.items():
+ img_dict_items = list(img.items())
+ if img.properties:
+ img_dict_items.extend(list(img.properties.items()))
+ for key, value in img_dict_items:
try:
set([key, value])
except TypeError:
+ if key != 'properties':
+ LOG.debug(
+ 'Skipped the \'%s\' attribute. '
+ 'That cannot be compared. '
+ '(image: %s, value: %s)',
+ key, img.id, value,
+ )
pass
else:
img_dict[key] = value
- if all(k in img_dict and img_dict[k] == v
- for k, v in wanted_properties.items()):
+
+ 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, parsed_args.image_property)
if len(images) > 1:
- emit_duplicated_warning(images,
- parsed_args.image_property)
+ 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"))
+ msg = _(
+ 'No images match the property expected by '
+ '--image-property'
+ )
+ raise exceptions.CommandError(msg)
# Lookup parsed_args.volume
volume = None
if parsed_args.volume:
# --volume and --boot-from-volume are mutually exclusive.
if parsed_args.boot_from_volume:
- raise exceptions.CommandError(
- _('--volume is not allowed with --boot-from-volume'))
+ msg = _('--volume is not allowed with --boot-from-volume')
+ raise exceptions.CommandError(msg)
+
volume = utils.find_resource(
volume_client.volumes,
parsed_args.volume,
).id
# Lookup parsed_args.flavor
- flavor = utils.find_resource(compute_client.flavors,
- parsed_args.flavor)
+ flavor = utils.find_resource(
+ compute_client.flavors, parsed_args.flavor)
files = {}
for f in parsed_args.file:
@@ -801,16 +988,17 @@ class CreateServer(command.ShowOne):
except IOError as e:
msg = _("Can't open '%(source)s': %(exception)s")
raise exceptions.CommandError(
- msg % {"source": src,
- "exception": e}
+ msg % {'source': src, 'exception': e}
)
if parsed_args.min > parsed_args.max:
msg = _("min instances should be <= max instances")
raise exceptions.CommandError(msg)
+
if parsed_args.min < 1:
msg = _("min instances should be > 0")
raise exceptions.CommandError(msg)
+
if parsed_args.max < 1:
msg = _("max instances should be > 0")
raise exceptions.CommandError(msg)
@@ -822,8 +1010,7 @@ class CreateServer(command.ShowOne):
except IOError as e:
msg = _("Can't open '%(data)s': %(exception)s")
raise exceptions.CommandError(
- msg % {"data": parsed_args.user_data,
- "exception": e}
+ msg % {'data': parsed_args.user_data, 'exception': e}
)
if parsed_args.description:
@@ -834,11 +1021,12 @@ class CreateServer(command.ShowOne):
block_device_mapping_v2 = []
if volume:
- block_device_mapping_v2 = [{'uuid': volume,
- 'boot_index': '0',
- 'source_type': 'volume',
- 'destination_type': 'volume'
- }]
+ block_device_mapping_v2 = [{
+ 'uuid': volume,
+ 'boot_index': '0',
+ 'source_type': 'volume',
+ 'destination_type': 'volume'
+ }]
elif parsed_args.boot_from_volume:
# Tell nova to create a root volume from the image provided.
block_device_mapping_v2 = [{
@@ -854,18 +1042,21 @@ class CreateServer(command.ShowOne):
boot_args = [parsed_args.server_name, image, flavor]
# Handle block device by device name order, like: vdb -> vdc -> vdd
- for dev_name in sorted(six.iterkeys(parsed_args.block_device_mapping)):
+ for dev_name in sorted(parsed_args.block_device_mapping):
dev_map = parsed_args.block_device_mapping[dev_name]
dev_map = dev_map.split(':')
if dev_map[0]:
mapping = {'device_name': dev_name}
+
# 1. decide source and destination type
if (len(dev_map) > 1 and
dev_map[1] in ('volume', 'snapshot', 'image')):
mapping['source_type'] = dev_map[1]
else:
mapping['source_type'] = 'volume'
+
mapping['destination_type'] = 'volume'
+
# 2. check target exist, update target uuid according by
# source type
if mapping['source_type'] == 'volume':
@@ -891,14 +1082,18 @@ class CreateServer(command.ShowOne):
image_id = image_client.find_image(dev_map[0],
ignore_missing=False).id
mapping['uuid'] = image_id
+
# 3. append size and delete_on_termination if exist
if len(dev_map) > 2 and dev_map[2]:
mapping['volume_size'] = dev_map[2]
+
if len(dev_map) > 3 and dev_map[3]:
mapping['delete_on_termination'] = dev_map[3]
else:
- msg = _("Volume, volume snapshot or image (name or ID) must "
- "be specified if --block-device-mapping is specified")
+ msg = _(
+ 'Volume, volume snapshot or image (name or ID) must '
+ 'be specified if --block-device-mapping is specified'
+ )
raise exceptions.CommandError(msg)
block_device_mapping_v2.append(mapping)
@@ -912,22 +1107,32 @@ class CreateServer(command.ShowOne):
auto_or_none = True
nics.append(nic_str)
else:
- nic_info = {"net-id": "", "v4-fixed-ip": "",
- "v6-fixed-ip": "", "port-id": ""}
+ nic_info = {
+ 'net-id': '',
+ 'v4-fixed-ip': '',
+ 'v6-fixed-ip': '',
+ 'port-id': '',
+ }
for kv_str in nic_str.split(","):
k, sep, v = kv_str.partition("=")
if k in nic_info and v:
nic_info[k] = v
else:
- msg = (_("Invalid nic argument '%s'. Nic arguments "
- "must be of the form --nic <net-id=net-uuid"
- ",v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
- "port-id=port-uuid>."))
+ msg = _(
+ "Invalid nic argument '%s'. Nic arguments "
+ "must be of the form --nic <net-id=net-uuid"
+ ",v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
+ "port-id=port-uuid>."
+ )
raise exceptions.CommandError(msg % k)
+
if bool(nic_info["net-id"]) == bool(nic_info["port-id"]):
- msg = _("either network or port should be specified "
- "but not both")
+ msg = _(
+ 'Either network or port should be specified '
+ 'but not both'
+ )
raise exceptions.CommandError(msg)
+
if self.app.client_manager.is_network_endpoint_enabled():
network_client = self.app.client_manager.network
if nic_info["net-id"]:
@@ -944,17 +1149,22 @@ class CreateServer(command.ShowOne):
nic_info["net-id"]
)['id']
if nic_info["port-id"]:
- msg = _("can't create server with port specified "
- "since network endpoint not enabled")
+ msg = _(
+ "Can't create server with port specified "
+ "since network endpoint not enabled"
+ )
raise exceptions.CommandError(msg)
+
nics.append(nic_info)
if nics:
if auto_or_none:
if len(nics) > 1:
- msg = _('Specifying a --nic of auto or none cannot '
- 'be used with any other --nic, --network '
- 'or --port value.')
+ msg = _(
+ 'Specifying a --nic of auto or none cannot '
+ 'be used with any other --nic, --network '
+ 'or --port value.'
+ )
raise exceptions.CommandError(msg)
nics = nics[0]
else:
@@ -991,16 +1201,19 @@ class CreateServer(command.ShowOne):
else:
hints[key] = values
- # What does a non-boolean value for config-drive do?
- # --config-drive argument is either a volume id or
- # 'True' (or '1') to use an ephemeral volume
- if str(parsed_args.config_drive).lower() in ("true", "1"):
- config_drive = True
- elif str(parsed_args.config_drive).lower() in ("false", "0",
- "", "none"):
- config_drive = None
+ if isinstance(parsed_args.config_drive, bool):
+ # NOTE(stephenfin): The API doesn't accept False as a value :'(
+ config_drive = parsed_args.config_drive or None
else:
- config_drive = parsed_args.config_drive
+ # TODO(stephenfin): Remove when we drop support for
+ # '--config-drive'
+ if str(parsed_args.config_drive).lower() in ("true", "1"):
+ config_drive = True
+ elif str(parsed_args.config_drive).lower() in ("false", "0",
+ "", "none"):
+ config_drive = None
+ else:
+ config_drive = parsed_args.config_drive
boot_kwargs = dict(
meta=parsed_args.property,
@@ -1012,6 +1225,7 @@ class CreateServer(command.ShowOne):
userdata=userdata,
key_name=parsed_args.key_name,
availability_zone=parsed_args.availability_zone,
+ admin_pass=parsed_args.password,
block_device_mapping_v2=block_device_mapping_v2,
nics=nics,
scheduler_hints=hints,
@@ -1020,18 +1234,34 @@ class CreateServer(command.ShowOne):
if parsed_args.description:
boot_kwargs['description'] = parsed_args.description
+ if parsed_args.tags:
+ if compute_client.api_version < api_versions.APIVersion('2.52'):
+ msg = _(
+ '--os-compute-api-version 2.52 or greater is required to '
+ 'support the --tag option'
+ )
+ raise exceptions.CommandError(msg)
+
+ boot_kwargs['tags'] = parsed_args.tags
+
if parsed_args.host:
if compute_client.api_version < api_versions.APIVersion("2.74"):
- msg = _("Specifying --host is not supported for "
- "--os-compute-api-version less than 2.74")
+ msg = _(
+ '--os-compute-api-version 2.74 or greater is required to '
+ 'support the --host option'
+ )
raise exceptions.CommandError(msg)
+
boot_kwargs['host'] = parsed_args.host
if parsed_args.hypervisor_hostname:
if compute_client.api_version < api_versions.APIVersion("2.74"):
- msg = _("Specifying --hypervisor-hostname is not supported "
- "for --os-compute-api-version less than 2.74")
+ msg = _(
+ '--os-compute-api-version 2.74 or greater is required to '
+ 'support the --hypervisor-hostname option'
+ )
raise exceptions.CommandError(msg)
+
boot_kwargs['hypervisor_hostname'] = (
parsed_args.hypervisor_hostname)
@@ -1057,8 +1287,7 @@ class CreateServer(command.ShowOne):
):
self.app.stdout.write('\n')
else:
- LOG.error(_('Error creating server: %s'),
- parsed_args.server_name)
+ LOG.error('Error creating server: %s', parsed_args.server_name)
self.app.stdout.write(_('Error creating server\n'))
raise SystemExit
@@ -1142,6 +1371,13 @@ class ListServer(command.Lister):
def get_parser(self, prog_name):
parser = super(ListServer, self).get_parser(prog_name)
parser.add_argument(
+ '--availability-zone',
+ metavar='<availability-zone>',
+ help=_('Only return instances that match the availability zone. '
+ 'Note that this option will be ignored for non-admin users '
+ 'when using ``--os-compute-api-version`` prior to 2.83.'),
+ )
+ parser.add_argument(
'--reservation-id',
metavar='<reservation-id>',
help=_('Only return instances that match the reservation'),
@@ -1287,6 +1523,30 @@ class ListServer(command.Lister):
help=_('Only display unlocked servers. '
'Requires ``--os-compute-api-version`` 2.73 or greater.'),
)
+ parser.add_argument(
+ '--tags',
+ metavar='<tag>',
+ action='append',
+ default=[],
+ dest='tags',
+ help=_(
+ 'Only list servers with the specified tag. '
+ 'Specify multiple times to filter on multiple tags. '
+ '(supported by --os-compute-api-version 2.26 or above)'
+ ),
+ )
+ parser.add_argument(
+ '--not-tags',
+ metavar='<tag>',
+ action='append',
+ default=[],
+ dest='not_tags',
+ help=_(
+ 'Only list servers without the specified tag. '
+ 'Specify multiple times to filter on multiple tags. '
+ '(supported by --os-compute-api-version 2.26 or above)'
+ ),
+ )
return parser
def take_action(self, parsed_args):
@@ -1326,6 +1586,7 @@ class ListServer(command.Lister):
ignore_missing=False).id
search_opts = {
+ 'availability_zone': parsed_args.availability_zone,
'reservation_id': parsed_args.reservation_id,
'ip': parsed_args.ip,
'ip6': parsed_args.ip6,
@@ -1342,6 +1603,27 @@ class ListServer(command.Lister):
'changes-before': parsed_args.changes_before,
'changes-since': parsed_args.changes_since,
}
+
+ if parsed_args.tags:
+ if compute_client.api_version < api_versions.APIVersion('2.26'):
+ msg = _(
+ '--os-compute-api-version 2.26 or greater is required to '
+ 'support the --tag option'
+ )
+ raise exceptions.CommandError(msg)
+
+ search_opts['tags'] = parsed_args.tags
+
+ if parsed_args.not_tags:
+ if compute_client.api_version < api_versions.APIVersion('2.26'):
+ msg = _(
+ '--os-compute-api-version 2.26 or greater is required to '
+ 'support the --not-tag option'
+ )
+ raise exceptions.CommandError(msg)
+
+ search_opts['not-tags'] = parsed_args.not_tags
+
support_locked = (compute_client.api_version >=
api_versions.APIVersion('2.73'))
if not support_locked and (parsed_args.locked or parsed_args.unlocked):
@@ -1362,8 +1644,8 @@ class ListServer(command.Lister):
raise exceptions.CommandError(msg)
try:
- timeutils.parse_isotime(search_opts['changes-before'])
- except ValueError:
+ iso8601.parse_date(search_opts['changes-before'])
+ except (TypeError, iso8601.ParseError):
raise exceptions.CommandError(
_('Invalid changes-before value: %s') %
search_opts['changes-before']
@@ -1371,8 +1653,8 @@ class ListServer(command.Lister):
if search_opts['changes-since']:
try:
- timeutils.parse_isotime(search_opts['changes-since'])
- except ValueError:
+ iso8601.parse_date(search_opts['changes-since'])
+ except (TypeError, iso8601.ParseError):
raise exceptions.CommandError(
_('Invalid changes-since value: %s') %
search_opts['changes-since']
@@ -1446,6 +1728,27 @@ class ListServer(command.Lister):
marker_id = None
+ # support for additional columns
+ if parsed_args.columns:
+ # convert tuple to list to edit them
+ column_headers = list(column_headers)
+ columns = list(columns)
+
+ for c in parsed_args.columns:
+ if c in ('Project ID', 'project_id'):
+ columns.append('tenant_id')
+ column_headers.append('Project ID')
+ if c in ('User ID', 'user_id'):
+ columns.append('user_id')
+ column_headers.append('User ID')
+ if c in ('Created At', 'created_at'):
+ columns.append('created_at')
+ column_headers.append('Created At')
+
+ # convert back to tuple
+ column_headers = tuple(column_headers)
+ columns = tuple(columns)
+
if parsed_args.marker:
# Check if both "--marker" and "--deleted" are used.
# In that scenario a lookup is not needed as the marker
@@ -1520,8 +1823,12 @@ class ListServer(command.Lister):
s.image_name = image.name
s.image_id = s.image['id']
else:
- s.image_name = ''
- s.image_id = ''
+ # NOTE(melwitt): An server booted from a volume will have no
+ # image associated with it. We fill in the Image Name and ID
+ # with "N/A (booted from volume)" to help users who want to be
+ # able to grep for boot-from-volume servers when using the CLI.
+ s.image_name = IMAGE_STRING_FOR_BFV
+ s.image_id = IMAGE_STRING_FOR_BFV
if 'id' in s.flavor:
flavor = flavors.get(s.flavor['id'])
if flavor:
@@ -1768,6 +2075,284 @@ revert to release the new server and restart the old one.""")
raise SystemExit
+class ListMigration(command.Lister):
+ _description = _("""List server migrations""")
+
+ def get_parser(self, prog_name):
+ parser = super(ListMigration, self).get_parser(prog_name)
+ parser.add_argument(
+ '--server',
+ metavar='<server>',
+ help=_(
+ 'Filter migrations by server (name or ID)'
+ )
+ )
+ parser.add_argument(
+ '--host',
+ metavar='<host>',
+ help=_(
+ 'Filter migrations by source or destination host'
+ ),
+ )
+ parser.add_argument(
+ '--status',
+ metavar='<status>',
+ help=_('Filter migrations by status')
+ )
+ parser.add_argument(
+ '--type',
+ metavar='<type>',
+ choices=[
+ 'evacuation', 'live-migration', 'cold-migration', 'resize',
+ ],
+ help=_('Filter migrations by type'),
+ )
+ parser.add_argument(
+ '--marker',
+ metavar='<marker>',
+ help=_(
+ "The last migration of the previous page; displays list "
+ "of migrations after 'marker'. Note that the marker is "
+ "the migration UUID. "
+ "(supported with --os-compute-api-version 2.59 or above)"
+ ),
+ )
+ parser.add_argument(
+ '--limit',
+ metavar='<limit>',
+ type=int,
+ help=_(
+ "Maximum number of migrations to display. Note that there "
+ "is a configurable max limit on the server, and the limit "
+ "that is used will be the minimum of what is requested "
+ "here and what is configured in the server. "
+ "(supported with --os-compute-api-version 2.59 or above)"
+ ),
+ )
+ parser.add_argument(
+ '--changes-since',
+ dest='changes_since',
+ metavar='<changes-since>',
+ help=_(
+ "List only migrations changed later or equal to a certain "
+ "point of time. The provided time should be an ISO 8061 "
+ "formatted time, e.g. ``2016-03-04T06:27:59Z``. "
+ "(supported with --os-compute-api-version 2.59 or above)"
+ ),
+ )
+ parser.add_argument(
+ '--changes-before',
+ dest='changes_before',
+ metavar='<changes-before>',
+ help=_(
+ "List only migrations changed earlier or equal to a "
+ "certain point of time. The provided time should be an ISO "
+ "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. "
+ "(supported with --os-compute-api-version 2.66 or above)"
+ ),
+ )
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ dest='project_id',
+ help=_(
+ "Filter migrations by project (ID) "
+ "(supported with --os-compute-api-version 2.80 or above)"
+ ),
+ )
+ parser.add_argument(
+ '--user',
+ metavar='<user>',
+ dest='user_id',
+ help=_(
+ "Filter migrations by user (ID) "
+ "(supported with --os-compute-api-version 2.80 or above)"
+ ),
+ )
+ return parser
+
+ def print_migrations(self, parsed_args, compute_client, migrations):
+ columns = [
+ 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute',
+ 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor',
+ 'Created At', 'Updated At',
+ ]
+
+ # Insert migrations UUID after ID
+ if compute_client.api_version >= api_versions.APIVersion("2.59"):
+ columns.insert(0, "UUID")
+
+ if compute_client.api_version >= api_versions.APIVersion("2.23"):
+ columns.insert(0, "Id")
+ columns.insert(len(columns) - 2, "Type")
+
+ if compute_client.api_version >= api_versions.APIVersion("2.80"):
+ if parsed_args.project_id:
+ columns.insert(len(columns) - 2, "Project")
+ if parsed_args.user_id:
+ columns.insert(len(columns) - 2, "User")
+
+ return (
+ columns,
+ (utils.get_item_properties(mig, columns) for mig in migrations),
+ )
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+
+ search_opts = {
+ 'host': parsed_args.host,
+ 'status': parsed_args.status,
+ }
+
+ if parsed_args.server:
+ search_opts['instance_uuid'] = utils.find_resource(
+ compute_client.servers,
+ parsed_args.server,
+ ).id
+
+ if parsed_args.type:
+ migration_type = parsed_args.type
+ # we're using an alias because the default value is confusing
+ if migration_type == 'cold-migration':
+ migration_type = 'migration'
+ search_opts['migration_type'] = migration_type
+
+ if parsed_args.marker:
+ if compute_client.api_version < api_versions.APIVersion('2.59'):
+ msg = _(
+ '--os-compute-api-version 2.59 or greater is required to '
+ 'support the --marker option'
+ )
+ raise exceptions.CommandError(msg)
+ search_opts['marker'] = parsed_args.marker
+
+ if parsed_args.limit:
+ if compute_client.api_version < api_versions.APIVersion('2.59'):
+ msg = _(
+ '--os-compute-api-version 2.59 or greater is required to '
+ 'support the --limit option'
+ )
+ raise exceptions.CommandError(msg)
+ search_opts['limit'] = parsed_args.limit
+
+ if parsed_args.changes_since:
+ if compute_client.api_version < api_versions.APIVersion('2.59'):
+ msg = _(
+ '--os-compute-api-version 2.59 or greater is required to '
+ 'support the --changes-since option'
+ )
+ raise exceptions.CommandError(msg)
+ search_opts['changes_since'] = parsed_args.changes_since
+
+ if parsed_args.changes_before:
+ if compute_client.api_version < api_versions.APIVersion('2.66'):
+ msg = _(
+ '--os-compute-api-version 2.66 or greater is required to '
+ 'support the --changes-before option'
+ )
+ raise exceptions.CommandError(msg)
+ search_opts['changes_before'] = parsed_args.changes_before
+
+ if parsed_args.project_id:
+ if compute_client.api_version < api_versions.APIVersion('2.80'):
+ msg = _(
+ '--os-compute-api-version 2.80 or greater is required to '
+ 'support the --project option'
+ )
+ raise exceptions.CommandError(msg)
+ search_opts['project_id'] = parsed_args.project_id
+
+ if parsed_args.user_id:
+ if compute_client.api_version < api_versions.APIVersion('2.80'):
+ msg = _(
+ '--os-compute-api-version 2.80 or greater is required to '
+ 'support the --user option'
+ )
+ raise exceptions.CommandError(msg)
+ search_opts['user_id'] = parsed_args.user_id
+
+ migrations = compute_client.migrations.list(**search_opts)
+
+ return self.print_migrations(parsed_args, compute_client, migrations)
+
+
+class AbortMigration(command.Command):
+ """Cancel an ongoing live migration.
+
+ This command requires ``--os-compute-api-version`` 2.24 or greater.
+ """
+
+ def get_parser(self, prog_name):
+ parser = super(AbortMigration, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ help=_('Server (name or ID)'),
+ )
+ parser.add_argument(
+ 'migration',
+ metavar='<migration>',
+ help=_("Migration (ID)"),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+
+ if compute_client.api_version < api_versions.APIVersion('2.24'):
+ msg = _(
+ '--os-compute-api-version 2.24 or greater is required to '
+ 'support the server migration abort command'
+ )
+ raise exceptions.CommandError(msg)
+
+ server = utils.find_resource(
+ compute_client.servers,
+ parsed_args.server,
+ )
+ compute_client.server_migrations.live_migration_abort(
+ server.id, parsed_args.migration)
+
+
+class ForceCompleteMigration(command.Command):
+ """Force an ongoing live migration to complete.
+
+ This command requires ``--os-compute-api-version`` 2.22 or greater.
+ """
+
+ def get_parser(self, prog_name):
+ parser = super(ForceCompleteMigration, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ help=_('Server (name or ID)'),
+ )
+ parser.add_argument(
+ 'migration',
+ metavar='<migration>',
+ help=_('Migration (ID)')
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+
+ if compute_client.api_version < api_versions.APIVersion('2.22'):
+ msg = _(
+ '--os-compute-api-version 2.22 or greater is required to '
+ 'support the server migration force complete command'
+ )
+ raise exceptions.CommandError(msg)
+
+ server = utils.find_resource(
+ compute_client.servers,
+ parsed_args.server,
+ )
+ compute_client.server_migrations.live_migrate_force_complete(
+ server.id, parsed_args.migration)
+
+
class PauseServer(command.Command):
_description = _("Pause server(s)")
@@ -1863,47 +2448,57 @@ class RebuildServer(command.ShowOne):
parser.add_argument(
'--image',
metavar='<image>',
- help=_('Recreate server from the specified image (name or ID).'
- ' Defaults to the currently used one.'),
+ help=_(
+ 'Recreate server from the specified image (name or ID).'
+ 'Defaults to the currently used one.'
+ ),
)
parser.add_argument(
'--password',
metavar='<password>',
- help=_("Set the password on the rebuilt instance"),
+ help=_('Set a password on the rebuilt server'),
)
parser.add_argument(
'--property',
metavar='<key=value>',
action=parseractions.KeyValueAction,
- help=_('Set a property on the rebuilt instance '
- '(repeat option to set multiple values)'),
+ help=_(
+ 'Set a new property on the rebuilt server '
+ '(repeat option to set multiple values)'
+ ),
)
parser.add_argument(
'--description',
metavar='<description>',
- help=_('New description for the server (supported by '
- '--os-compute-api-version 2.19 or above'),
- )
- parser.add_argument(
- '--wait',
- action='store_true',
- help=_('Wait for rebuild to complete'),
+ help=_(
+ 'Set a new description on the rebuilt server '
+ '(supported by --os-compute-api-version 2.19 or above)'
+ ),
)
key_group = parser.add_mutually_exclusive_group()
key_group.add_argument(
'--key-name',
metavar='<key-name>',
- help=_("Set the key name of key pair on the rebuilt instance."
- " Cannot be specified with the '--key-unset' option."
- " (Supported by API versions '2.54' - '2.latest')"),
+ help=_(
+ 'Set the key name of key pair on the rebuilt server. '
+ 'Cannot be specified with the --key-unset option. '
+ '(supported by --os-compute-api-version 2.54 or above)'
+ ),
)
key_group.add_argument(
'--key-unset',
action='store_true',
default=False,
- help=_("Unset the key name of key pair on the rebuilt instance."
- " Cannot be specified with the '--key-name' option."
- " (Supported by API versions '2.54' - '2.latest')"),
+ help=_(
+ 'Unset the key name of key pair on the rebuilt server. '
+ 'Cannot be specified with the --key-name option. '
+ '(supported by --os-compute-api-version 2.54 or above)'
+ ),
+ )
+ parser.add_argument(
+ '--wait',
+ action='store_true',
+ help=_('Wait for rebuild to complete'),
)
return parser
@@ -1929,24 +2524,38 @@ class RebuildServer(command.ShowOne):
image = image_client.get_image(image_id)
kwargs = {}
+
if parsed_args.property:
kwargs['meta'] = parsed_args.property
+
if parsed_args.description:
if server.api_version < api_versions.APIVersion("2.19"):
- msg = _("Description is not supported for "
- "--os-compute-api-version less than 2.19")
+ msg = _(
+ '--os-compute-api-version 2.19 or greater is required to '
+ 'support the --description option'
+ )
raise exceptions.CommandError(msg)
+
kwargs['description'] = parsed_args.description
- if parsed_args.key_name or parsed_args.key_unset:
+ if parsed_args.key_name:
if compute_client.api_version < api_versions.APIVersion('2.54'):
- msg = _('--os-compute-api-version 2.54 or later is required')
+ msg = _(
+ '--os-compute-api-version 2.54 or greater is required to '
+ 'support the --key-name option'
+ )
raise exceptions.CommandError(msg)
- if parsed_args.key_unset:
- kwargs['key_name'] = None
- if parsed_args.key_name:
kwargs['key_name'] = parsed_args.key_name
+ elif parsed_args.key_unset:
+ if compute_client.api_version < api_versions.APIVersion('2.54'):
+ msg = _(
+ '--os-compute-api-version 2.54 or greater is required to '
+ 'support the --no-key-name option'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs['key_name'] = None
server = server.rebuild(image, parsed_args.password, **kwargs)
if parsed_args.wait:
@@ -1957,13 +2566,124 @@ class RebuildServer(command.ShowOne):
):
self.app.stdout.write(_('Complete\n'))
else:
- LOG.error(_('Error rebuilding server: %s'),
- server.id)
+ LOG.error(_('Error rebuilding server: %s'), server.id)
self.app.stdout.write(_('Error rebuilding server\n'))
raise SystemExit
- details = _prep_server_detail(compute_client, image_client, server,
- refresh=False)
+ details = _prep_server_detail(
+ compute_client, image_client, server, refresh=False)
+ return zip(*sorted(details.items()))
+
+
+class EvacuateServer(command.ShowOne):
+ _description = _("""Evacuate a server to a different host.
+
+This command is used to recreate a server after the host it was on has failed.
+It can only be used if the compute service that manages the server is down.
+This command should only be used by an admin after they have confirmed that the
+instance is not running on the failed host.
+
+If the server instance was created with an ephemeral root disk on non-shared
+storage the server will be rebuilt using the original glance image preserving
+the ports and any attached data volumes.
+
+If the server uses boot for volume or has its root disk on shared storage the
+root disk will be preserved and reused for the evacuated instance on the new
+host.""")
+
+ def get_parser(self, prog_name):
+ parser = super(EvacuateServer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ help=_('Server (name or ID)'),
+ )
+
+ parser.add_argument(
+ '--wait', action='store_true',
+ help=_('Wait for evacuation to complete'),
+ )
+ parser.add_argument(
+ '--host', metavar='<host>', default=None,
+ help=_(
+ 'Set the preferred host on which to rebuild the evacuated '
+ 'server. The host will be validated by the scheduler. '
+ '(supported by --os-compute-api-version 2.29 or above)'
+ ),
+ )
+ shared_storage_group = parser.add_mutually_exclusive_group()
+ shared_storage_group.add_argument(
+ '--password', metavar='<password>', default=None,
+ help=_(
+ 'Set the password on the evacuated instance. This option is '
+ 'mutually exclusive with the --shared-storage option'
+ ),
+ )
+ shared_storage_group.add_argument(
+ '--shared-storage', action='store_true', dest='shared_storage',
+ help=_(
+ 'Indicate that the instance is on shared storage. '
+ 'This will be auto-calculated with '
+ '--os-compute-api-version 2.14 and greater and should not '
+ 'be used with later microversions. This option is mutually '
+ 'exclusive with the --password option'
+ ),
+ )
+ 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
+
+ if parsed_args.host:
+ if compute_client.api_version < api_versions.APIVersion('2.29'):
+ msg = _(
+ '--os-compute-api-version 2.29 or later is required '
+ 'to specify a preferred host.'
+ )
+ raise exceptions.CommandError(msg)
+
+ if parsed_args.shared_storage:
+ if compute_client.api_version > api_versions.APIVersion('2.13'):
+ msg = _(
+ '--os-compute-api-version 2.13 or earlier is required '
+ 'to specify shared-storage.'
+ )
+ raise exceptions.CommandError(msg)
+
+ kwargs = {
+ 'host': parsed_args.host,
+ 'password': parsed_args.password,
+ }
+
+ if compute_client.api_version <= api_versions.APIVersion('2.13'):
+ kwargs['on_shared_storage'] = parsed_args.shared_storage
+
+ server = utils.find_resource(
+ compute_client.servers, parsed_args.server)
+
+ server = server.evacuate(**kwargs)
+
+ if parsed_args.wait:
+ if utils.wait_for_status(
+ compute_client.servers.get,
+ server.id,
+ callback=_show_progress,
+ ):
+ self.app.stdout.write(_('Complete\n'))
+ else:
+ LOG.error(_('Error evacuating server: %s'), server.id)
+ self.app.stdout.write(_('Error evacuating server\n'))
+ raise SystemExit
+
+ details = _prep_server_detail(
+ compute_client, image_client, server, refresh=False)
return zip(*sorted(details.items()))
@@ -2433,6 +3153,18 @@ class SetServer(command.Command):
help=_('New server description (supported by '
'--os-compute-api-version 2.19 or above)'),
)
+ parser.add_argument(
+ '--tag',
+ metavar='<tag>',
+ action='append',
+ default=[],
+ dest='tags',
+ help=_(
+ 'Tag for the server. '
+ 'Specify multiple times to add multiple tags. '
+ '(supported by --os-compute-api-version 2.26 or above)'
+ ),
+ )
return parser
def take_action(self, parsed_args):
@@ -2471,6 +3203,17 @@ class SetServer(command.Command):
raise exceptions.CommandError(msg)
server.update(description=parsed_args.description)
+ if parsed_args.tags:
+ if server.api_version < api_versions.APIVersion('2.26'):
+ msg = _(
+ '--os-compute-api-version 2.26 or greater is required to '
+ 'support the --tag option'
+ )
+ raise exceptions.CommandError(msg)
+
+ for tag in parsed_args.tags:
+ server.add_tag(tag=tag)
+
class ShelveServer(command.Command):
_description = _("Shelve server(s)")
@@ -2530,7 +3273,6 @@ class ShowServer(command.ShowOne):
data = _prep_server_detail(compute_client,
self.app.client_manager.image, server,
refresh=False)
-
return zip(*sorted(data.items()))
@@ -2813,7 +3555,7 @@ class UnrescueServer(command.Command):
class UnsetServer(command.Command):
- _description = _("Unset server properties")
+ _description = _("Unset server properties and tags")
def get_parser(self, prog_name):
parser = super(UnsetServer, self).get_parser(prog_name)
@@ -2837,6 +3579,18 @@ class UnsetServer(command.Command):
help=_('Unset server description (supported by '
'--os-compute-api-version 2.19 or above)'),
)
+ parser.add_argument(
+ '--tag',
+ metavar='<tag>',
+ action='append',
+ default=[],
+ dest='tags',
+ help=_(
+ 'Tag to remove from the server. '
+ 'Specify multiple times to remove multiple tags. '
+ '(supported by --os-compute-api-version 2.26 or later'
+ ),
+ )
return parser
def take_action(self, parsed_args):
@@ -2862,6 +3616,17 @@ class UnsetServer(command.Command):
description="",
)
+ if parsed_args.tags:
+ if compute_client.api_version < api_versions.APIVersion('2.26'):
+ msg = _(
+ '--os-compute-api-version 2.26 or greater is required to '
+ 'support the --tag option'
+ )
+ raise exceptions.CommandError(msg)
+
+ for tag in parsed_args.tags:
+ compute_client.servers.delete_tag(server, tag=tag)
+
class UnshelveServer(command.Command):
_description = _("Unshelve server(s)")
diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py
index a5d43fc6..b1b821b2 100644
--- a/openstackclient/compute/v2/server_backup.py
+++ b/openstackclient/compute/v2/server_backup.py
@@ -15,10 +15,11 @@
"""Compute v2 Server action implementations"""
+import importlib
+
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
-from oslo_utils import importutils
from openstackclient.i18n import _
@@ -119,7 +120,7 @@ class CreateServerBackup(command.ShowOne):
info['properties'] = utils.format_dict(info.get('properties', {}))
else:
# Get the right image module to format the output
- image_module = importutils.import_module(
+ image_module = importlib.import_module(
self.IMAGE_API_VERSIONS[
self.app.client_manager._api_version['image']
]
diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py
index c49a552f..a3363244 100644
--- a/openstackclient/compute/v2/server_group.py
+++ b/openstackclient/compute/v2/server_group.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
@@ -55,21 +56,40 @@ class CreateServerGroup(command.ShowOne):
parser.add_argument(
'--policy',
metavar='<policy>',
+ choices=[
+ 'affinity',
+ 'anti-affinity',
+ 'soft-affinity',
+ 'soft-anti-affinity',
+ ],
default='affinity',
- help=_("Add a policy to <name> "
- "('affinity' or 'anti-affinity', "
- "defaults to 'affinity'). Specify --os-compute-api-version "
- "2.15 or higher for the 'soft-affinity' or "
- "'soft-anti-affinity' policy.")
+ help=_(
+ "Add a policy to <name> "
+ "Specify --os-compute-api-version 2.15 or higher for the "
+ "'soft-affinity' or 'soft-anti-affinity' policy."
+ )
)
return parser
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
info = {}
+
+ if parsed_args.policy in ('soft-affinity', 'soft-anti-affinity'):
+ if compute_client.api_version < api_versions.APIVersion('2.15'):
+ msg = _(
+ '--os-compute-api-version 2.15 or greater is required to '
+ 'support the %s policy'
+ )
+ raise exceptions.CommandError(msg % parsed_args.policy)
+
+ policy_arg = {'policies': [parsed_args.policy]}
+ if compute_client.api_version >= api_versions.APIVersion("2.64"):
+ policy_arg = {'policy': parsed_args.policy}
+
server_group = compute_client.server_groups.create(
- name=parsed_args.name,
- policies=[parsed_args.policy])
+ name=parsed_args.name, **policy_arg)
+
info.update(server_group._info)
columns = _get_columns(info)
@@ -136,11 +156,15 @@ class ListServerGroup(command.Lister):
compute_client = self.app.client_manager.compute
data = compute_client.server_groups.list(parsed_args.all_projects)
+ policy_key = 'Policies'
+ if compute_client.api_version >= api_versions.APIVersion("2.64"):
+ policy_key = 'Policy'
+
if parsed_args.long:
column_headers = columns = (
'ID',
'Name',
- 'Policies',
+ policy_key,
'Members',
'Project Id',
'User Id',
@@ -149,7 +173,7 @@ class ListServerGroup(command.Lister):
column_headers = columns = (
'ID',
'Name',
- 'Policies',
+ policy_key,
)
return (column_headers,
diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py
index fea87af8..c12bc2b3 100644
--- a/openstackclient/compute/v2/server_image.py
+++ b/openstackclient/compute/v2/server_image.py
@@ -15,12 +15,12 @@
"""Compute v2 Server action implementations"""
+import importlib
import logging
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
-from oslo_utils import importutils
from openstackclient.i18n import _
@@ -99,7 +99,7 @@ class CreateServerImage(command.ShowOne):
info['properties'] = utils.format_dict(info.get('properties', {}))
else:
# Get the right image module to format the output
- image_module = importutils.import_module(
+ image_module = importlib.import_module(
self.IMAGE_API_VERSIONS[
self.app.client_manager._api_version['image']
]
diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py
index e70d87d2..a75db4f8 100644
--- a/openstackclient/identity/common.py
+++ b/openstackclient/identity/common.py
@@ -207,7 +207,7 @@ def _find_identity_resource(identity_client_manager, name_or_id,
name_or_id, **kwargs)
if identity_resource is not None:
return identity_resource
- except exceptions.Forbidden:
+ except (exceptions.Forbidden, identity_exc.Forbidden):
pass
return resource_type(None, {'id': name_or_id, 'name': name_or_id})
diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py
index ccedbf33..05d0e9ae 100644
--- a/openstackclient/identity/v2_0/catalog.py
+++ b/openstackclient/identity/v2_0/catalog.py
@@ -91,7 +91,7 @@ class ShowCatalog(command.ShowOne):
for service in auth_ref.service_catalog.catalog:
if (service.get('name') == parsed_args.service or
service.get('type') == parsed_args.service):
- data = service
+ data = service.copy()
data['endpoints'] = EndpointsColumn(data['endpoints'])
if 'endpoints_links' in data:
data.pop('endpoints_links')
diff --git a/openstackclient/identity/v3/access_rule.py b/openstackclient/identity/v3/access_rule.py
index 65e78be1..ffda04f9 100644
--- a/openstackclient/identity/v3/access_rule.py
+++ b/openstackclient/identity/v3/access_rule.py
@@ -20,7 +20,6 @@ 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
@@ -115,4 +114,4 @@ class ShowAccessRule(command.ShowOne):
access_rule._info.pop('links', None)
- return zip(*sorted(six.iteritems(access_rule._info)))
+ return zip(*sorted(access_rule._info.items()))
diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py
index 2b2d9d11..7307cea0 100644
--- a/openstackclient/identity/v3/identity_provider.py
+++ b/openstackclient/identity/v3/identity_provider.py
@@ -143,10 +143,32 @@ class DeleteIdentityProvider(command.Command):
class ListIdentityProvider(command.Lister):
_description = _("List identity providers")
+ def get_parser(self, prog_name):
+ parser = super(ListIdentityProvider, self).get_parser(prog_name)
+ parser.add_argument(
+ '--id',
+ metavar='<id>',
+ help=_('The Identity Providers’ ID attribute'),
+ )
+ parser.add_argument(
+ '--enabled',
+ dest='enabled',
+ action='store_true',
+ help=_('The Identity Providers that are enabled will be returned'),
+ )
+ return parser
+
def take_action(self, parsed_args):
columns = ('ID', 'Enabled', 'Domain ID', 'Description')
identity_client = self.app.client_manager.identity
- data = identity_client.federation.identity_providers.list()
+
+ kwargs = {}
+ if parsed_args.id:
+ kwargs['id'] = parsed_args.id
+ if parsed_args.enabled:
+ kwargs['enabled'] = True
+
+ data = identity_client.federation.identity_providers.list(**kwargs)
return (columns,
(utils.get_item_properties(
s, columns,
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index 980ebf11..a674564f 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -64,59 +64,61 @@ def _add_identity_and_resource_options_to_parser(parser):
def _process_identity_and_resource_options(parsed_args,
- identity_client_manager):
+ identity_client_manager,
+ validate_actor_existence=True):
+
+ def _find_user():
+ try:
+ return common.find_user(
+ identity_client_manager,
+ parsed_args.user,
+ parsed_args.user_domain
+ ).id
+ except exceptions.CommandError:
+ if not validate_actor_existence:
+ return parsed_args.user
+ raise
+
+ def _find_group():
+ try:
+ return common.find_group(
+ identity_client_manager,
+ parsed_args.group,
+ parsed_args.group_domain
+ ).id
+ except exceptions.CommandError:
+ if not validate_actor_existence:
+ return parsed_args.group
+ raise
+
kwargs = {}
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['user'] = _find_user()
kwargs['system'] = parsed_args.system
elif parsed_args.user and parsed_args.domain:
- kwargs['user'] = common.find_user(
- identity_client_manager,
- parsed_args.user,
- parsed_args.user_domain,
- ).id
+ kwargs['user'] = _find_user()
kwargs['domain'] = common.find_domain(
identity_client_manager,
parsed_args.domain,
).id
elif parsed_args.user and parsed_args.project:
- kwargs['user'] = common.find_user(
- identity_client_manager,
- parsed_args.user,
- parsed_args.user_domain,
- ).id
+ kwargs['user'] = _find_user()
kwargs['project'] = common.find_project(
identity_client_manager,
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['group'] = _find_group()
kwargs['system'] = parsed_args.system
elif parsed_args.group and parsed_args.domain:
- kwargs['group'] = common.find_group(
- identity_client_manager,
- parsed_args.group,
- parsed_args.group_domain,
- ).id
+ kwargs['group'] = _find_group()
kwargs['domain'] = common.find_domain(
identity_client_manager,
parsed_args.domain,
).id
elif parsed_args.group and parsed_args.project:
- kwargs['group'] = common.find_group(
- identity_client_manager,
- parsed_args.group,
- parsed_args.group_domain,
- ).id
+ kwargs['group'] = _find_group()
kwargs['project'] = common.find_project(
identity_client_manager,
parsed_args.project,
@@ -340,7 +342,9 @@ class RemoveRole(command.Command):
)
kwargs = _process_identity_and_resource_options(
- parsed_args, self.app.client_manager.identity)
+ parsed_args, self.app.client_manager.identity,
+ validate_actor_existence=False
+ )
identity_client.roles.revoke(role.id, **kwargs)
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index cf1d6817..64aa3fcd 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -478,7 +478,11 @@ class SaveImage(command.Command):
image_client = self.app.client_manager.image
image = image_client.find_image(parsed_args.image)
- image_client.download_image(image.id, output=parsed_args.file)
+ output_file = parsed_args.file
+ if output_file is None:
+ output_file = getattr(sys.stdout, "buffer", sys.stdout)
+
+ image_client.download_image(image.id, stream=True, output=output_file)
class SetImage(command.Command):
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index 029f57a3..58d92f51 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -354,7 +354,7 @@ class CreateImage(command.ShowOne):
# Build an attribute dict from the parsed args, only include
# attributes that were actually set on the command line
- kwargs = {}
+ kwargs = {'allow_duplicates': True}
copy_attrs = ('name', 'id',
'container_format', 'disk_format',
'min_disk', 'min_ram', 'tags', 'visibility')
@@ -803,7 +803,11 @@ class SaveImage(command.Command):
image_client = self.app.client_manager.image
image = image_client.find_image(parsed_args.image)
- image_client.download_image(image.id, output=parsed_args.file)
+ output_file = parsed_args.file
+ if output_file is None:
+ output_file = getattr(sys.stdout, "buffer", sys.stdout)
+
+ image_client.download_image(image.id, stream=True, output=output_file)
class SetImage(command.Command):
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py
index e68628b3..47ffbe77 100644
--- a/openstackclient/network/common.py
+++ b/openstackclient/network/common.py
@@ -18,7 +18,6 @@ import logging
import openstack.exceptions
from osc_lib.command import command
from osc_lib import exceptions
-import six
from openstackclient.i18n import _
@@ -54,8 +53,7 @@ def check_missing_extension_if_error(client_manager, attrs):
raise
-@six.add_metaclass(abc.ABCMeta)
-class NetDetectionMixin(object):
+class NetDetectionMixin(metaclass=abc.ABCMeta):
"""Convenience methods for nova-network vs. neutron decisions.
A live environment detects which network type it is running and creates its
@@ -166,8 +164,8 @@ class NetDetectionMixin(object):
pass
-@six.add_metaclass(abc.ABCMeta)
-class NetworkAndComputeCommand(NetDetectionMixin, command.Command):
+class NetworkAndComputeCommand(NetDetectionMixin, command.Command,
+ metaclass=abc.ABCMeta):
"""Network and Compute Command
Command class for commands that support implementation via
@@ -178,8 +176,8 @@ class NetworkAndComputeCommand(NetDetectionMixin, command.Command):
pass
-@six.add_metaclass(abc.ABCMeta)
-class NetworkAndComputeDelete(NetworkAndComputeCommand):
+class NetworkAndComputeDelete(NetworkAndComputeCommand,
+ metaclass=abc.ABCMeta):
"""Network and Compute Delete
Delete class for commands that support implementation via
@@ -222,8 +220,8 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand):
raise exceptions.CommandError(msg)
-@six.add_metaclass(abc.ABCMeta)
-class NetworkAndComputeLister(NetDetectionMixin, command.Lister):
+class NetworkAndComputeLister(NetDetectionMixin, command.Lister,
+ metaclass=abc.ABCMeta):
"""Network and Compute Lister
Lister class for commands that support implementation via
@@ -234,8 +232,8 @@ class NetworkAndComputeLister(NetDetectionMixin, command.Lister):
pass
-@six.add_metaclass(abc.ABCMeta)
-class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne):
+class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne,
+ metaclass=abc.ABCMeta):
"""Network and Compute ShowOne
ShowOne class for commands that support implementation via
@@ -255,5 +253,5 @@ class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne):
except openstack.exceptions.HttpException as exc:
msg = _("Error while executing command: %s") % exc.message
if exc.details:
- msg += ", " + six.text_type(exc.details)
+ msg += ", " + str(exc.details)
raise exceptions.CommandError(msg)
diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py
index 49ff9e1b..1cf0395f 100644
--- a/openstackclient/network/v2/network_meter_rule.py
+++ b/openstackclient/network/v2/network_meter_rule.py
@@ -46,6 +46,10 @@ def _get_attrs(client_manager, parsed_args):
attrs['direction'] = 'egress'
if parsed_args.remote_ip_prefix is not None:
attrs['remote_ip_prefix'] = parsed_args.remote_ip_prefix
+ if parsed_args.source_ip_prefix is not None:
+ attrs['source_ip_prefix'] = parsed_args.source_ip_prefix
+ if parsed_args.destination_ip_prefix is not None:
+ attrs['destination_ip_prefix'] = parsed_args.destination_ip_prefix
if parsed_args.meter is not None:
attrs['metering_label_id'] = parsed_args.meter
if parsed_args.project is not None:
@@ -97,10 +101,22 @@ class CreateMeterRule(command.ShowOne):
parser.add_argument(
'--remote-ip-prefix',
metavar='<remote-ip-prefix>',
- required=True,
+ required=False,
help=_('The remote IP prefix to associate with this rule'),
)
parser.add_argument(
+ '--source-ip-prefix',
+ metavar='<remote-ip-prefix>',
+ required=False,
+ help=_('The source IP prefix to associate with this rule'),
+ )
+ parser.add_argument(
+ '--destination-ip-prefix',
+ metavar='<remote-ip-prefix>',
+ required=False,
+ help=_('The destination IP prefix to associate with this rule'),
+ )
+ parser.add_argument(
'meter',
metavar='<meter>',
help=_('Label to associate with this metering rule (name or ID)'),
@@ -168,12 +184,16 @@ class ListMeterRule(command.Lister):
'excluded',
'direction',
'remote_ip_prefix',
+ 'source_ip_prefix',
+ 'destination_ip_prefix',
)
column_headers = (
'ID',
'Excluded',
'Direction',
'Remote IP Prefix',
+ 'Source IP Prefix',
+ 'Destination IP Prefix',
)
data = client.metering_label_rules()
return (column_headers,
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index a21324ae..cb77759e 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -158,6 +158,16 @@ def _get_attrs(client_manager, parsed_args):
parsed_args.disable_uplink_status_propagation):
attrs['propagate_uplink_status'] = False
+ if ('numa_policy_required' in parsed_args and
+ parsed_args.numa_policy_required):
+ attrs['numa_affinity_policy'] = 'required'
+ elif ('numa_policy_preferred' in parsed_args and
+ parsed_args.numa_policy_preferred):
+ attrs['numa_affinity_policy'] = 'preferred'
+ elif ('numa_policy_legacy' in parsed_args and
+ parsed_args.numa_policy_legacy):
+ attrs['numa_affinity_policy'] = 'legacy'
+
return attrs
@@ -265,6 +275,22 @@ def _add_updatable_args(parser):
help=_("Set DNS name for this port "
"(requires DNS integration extension)")
)
+ numa_affinity_policy_group = parser.add_mutually_exclusive_group()
+ numa_affinity_policy_group.add_argument(
+ '--numa-policy-required',
+ action='store_true',
+ help=_("NUMA affinity policy required to schedule this port")
+ )
+ numa_affinity_policy_group.add_argument(
+ '--numa-policy-preferred',
+ action='store_true',
+ help=_("NUMA affinity policy preferred to schedule this port")
+ )
+ numa_affinity_policy_group.add_argument(
+ '--numa-policy-legacy',
+ action='store_true',
+ help=_("NUMA affinity policy using legacy mode to schedule this port")
+ )
# TODO(abhiraut): Use the SDK resource mapped attribute names once the
@@ -454,12 +480,23 @@ 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
+
+ set_tags_in_post = bool(
+ client.find_extension('tag-ports-during-bulk-creation'))
+ if set_tags_in_post:
+ if parsed_args.no_tag:
+ attrs['tags'] = []
+ if parsed_args.tags:
+ attrs['tags'] = list(set(parsed_args.tags))
+
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)
+ if not set_tags_in_post:
+ # 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)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
@@ -628,7 +665,7 @@ class ListPort(command.Lister):
_tag.get_tag_filtering_args(parsed_args, filters)
- data = network_client.ports(**filters)
+ data = network_client.ports(fields=columns, **filters)
headers, attrs = utils.calculate_header_and_attrs(
column_headers, columns, parsed_args)
@@ -904,6 +941,11 @@ class UnsetPort(command.Command):
action='store_true',
help=_("Clear existing information of data plane status")
)
+ parser.add_argument(
+ '--numa-policy',
+ action='store_true',
+ help=_("Clear existing NUMA affinity policy")
+ )
_tag.add_tag_option_to_parser_for_unset(parser, _('port'))
@@ -959,6 +1001,8 @@ class UnsetPort(command.Command):
attrs['qos_policy_id'] = None
if parsed_args.data_plane_status:
attrs['data_plane_status'] = None
+ if parsed_args.numa_policy:
+ attrs['numa_affinity_policy'] = None
if attrs:
client.update_port(obj, **attrs)
diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py
index 1fbd97ab..a7703933 100644
--- a/openstackclient/network/v2/security_group_rule.py
+++ b/openstackclient/network/v2/security_group_rule.py
@@ -474,7 +474,7 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
action='store_true',
default=False,
help=self.enhance_help_neutron(
- _("List additional fields in output"))
+ _("**Deprecated** This argument is no longer needed"))
)
return parser
@@ -504,15 +504,19 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
'Ethertype',
'IP Range',
'Port Range',
+ 'Direction',
+ 'Remote Security Group',
)
- if parsed_args.long:
- column_headers = column_headers + ('Direction',)
- column_headers = column_headers + ('Remote Security Group',)
if parsed_args.group is None:
column_headers = column_headers + ('Security Group',)
return column_headers
def take_action_network(self, client, parsed_args):
+ if parsed_args.long:
+ self.log.warning(_(
+ "The --long option has been deprecated and is no longer needed"
+ ))
+
column_headers = self._get_column_headers(parsed_args)
columns = (
'id',
@@ -520,10 +524,9 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister):
'ether_type',
'remote_ip_prefix',
'port_range',
+ 'direction',
+ 'remote_group_id',
)
- if parsed_args.long:
- columns = columns + ('direction',)
- columns = columns + ('remote_group_id',)
# Get the security group rules using the requested query.
query = {}
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index 755af24d..bc88e1f1 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -16,13 +16,11 @@
"""Command-line interface to the OpenStack APIs"""
-import locale
import sys
from osc_lib.api import auth
from osc_lib.command import commandmanager
from osc_lib import shell
-import six
import openstackclient
from openstackclient.common import clientmanager
@@ -143,12 +141,6 @@ class OpenStackShell(shell.OpenStackShell):
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
- if six.PY2:
- # Emulate Py3, decode argv into Unicode based on locale so that
- # commands always see arguments as text instead of binary data
- encoding = locale.getpreferredencoding()
- if encoding:
- argv = map(lambda arg: arg.decode(encoding), argv)
return OpenStackShell().run(argv)
diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py
index 08e9390e..3542a827 100644
--- a/openstackclient/tests/functional/base.py
+++ b/openstackclient/tests/functional/base.py
@@ -19,10 +19,6 @@ from tempest.lib import exceptions
import testtools
-COMMON_DIR = os.path.dirname(os.path.abspath(__file__))
-FUNCTIONAL_DIR = os.path.normpath(os.path.join(COMMON_DIR, '..'))
-ROOT_DIR = os.path.normpath(os.path.join(FUNCTIONAL_DIR, '..'))
-EXAMPLE_DIR = os.path.join(ROOT_DIR, 'examples')
ADMIN_CLOUD = os.environ.get('OS_ADMIN_CLOUD', 'devstack-admin')
diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py
index 4c2fc0e3..9c057460 100644
--- a/openstackclient/tests/functional/common/test_quota.py
+++ b/openstackclient/tests/functional/common/test_quota.py
@@ -165,47 +165,3 @@ class QuotaTests(base.TestCase):
# returned attributes
self.assertTrue(cmd_output["key-pairs"] >= 0)
self.assertTrue(cmd_output["snapshots"] >= 0)
-
- def test_quota_set_force(self):
- """Test to set instance value by force """
- json_output = json.loads(self.openstack(
- 'quota list -f json --detail --compute'
- ))
- in_use = limit = None
- for j in json_output:
- if j["Resource"] == "instances":
- in_use = j["In Use"]
- limit = j["Limit"]
-
- # Reduce count of in_use
- in_use = in_use - 1
- # cannot have negative instances limit
- if in_use < 0:
- in_use = 0
-
- # set the limit by force now
- self.openstack(
- 'quota set ' + self.PROJECT_NAME +
- '--instances ' + str(in_use) + ' --force'
- )
- cmd_output = json.loads(self.openstack(
- 'quota show -f json ' + self.PROJECT_NAME
- ))
- self.assertIsNotNone(cmd_output)
- self.assertEqual(
- in_use,
- cmd_output["instances"]
- )
-
- # Set instances limit to original limit now
- self.openstack(
- 'quota set ' + self.PROJECT_NAME + '--instances ' + str(limit)
- )
- cmd_output = json.loads(self.openstack(
- 'quota show -f json ' + self.PROJECT_NAME
- ))
- self.assertIsNotNone(cmd_output)
- self.assertEqual(
- limit,
- cmd_output["instances"]
- )
diff --git a/openstackclient/tests/functional/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py
deleted file mode 100644
index 25d8c868..00000000
--- a/openstackclient/tests/functional/compute/v2/test_agent.py
+++ /dev/null
@@ -1,196 +0,0 @@
-# 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 hashlib
-import json
-
-from openstackclient.tests.functional import base
-
-
-class ComputeAgentTests(base.TestCase):
- """Functional tests for compute agent."""
-
- # Generate two different md5hash
- MD5HASH1 = hashlib.md5()
- MD5HASH1.update('agent_1'.encode('utf-8'))
- MD5HASH1 = MD5HASH1.hexdigest()
- MD5HASH2 = hashlib.md5()
- MD5HASH2.update('agent_2'.encode('utf-8'))
- MD5HASH2 = MD5HASH2.hexdigest()
-
- def test_compute_agent_delete(self):
- """Test compute agent create, delete multiple"""
- os1 = "os_1"
- arch1 = "x86_64"
- ver1 = "v1"
- url1 = "http://localhost"
- md5hash1 = self.MD5HASH1
- hyper1 = "kvm"
- cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1))
-
- cmd_output = json.loads(self.openstack(
- 'compute agent create -f json ' +
- cmd1
- ))
- agent_id1 = str(cmd_output["agent_id"])
-
- os2 = "os_2"
- arch2 = "x86"
- ver2 = "v2"
- url2 = "http://openstack"
- md5hash2 = self.MD5HASH2
- hyper2 = "xen"
- cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2))
-
- cmd_output = json.loads(self.openstack(
- 'compute agent create -f json ' +
- cmd2
- ))
- agent_id2 = str(cmd_output["agent_id"])
-
- # Test compute agent delete
- del_output = self.openstack(
- 'compute agent delete ' +
- agent_id1 + ' ' + agent_id2
- )
- self.assertOutput('', del_output)
-
- def test_compute_agent_list(self):
- """Test compute agent create and list"""
- os1 = "os_1"
- arch1 = "x86_64"
- ver1 = "v1"
- url1 = "http://localhost"
- md5hash1 = self.MD5HASH1
- hyper1 = "kvm"
- cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1))
-
- cmd_output = json.loads(self.openstack(
- 'compute agent create -f json ' +
- cmd1
- ))
- agent_id1 = str(cmd_output["agent_id"])
- self.addCleanup(self.openstack, 'compute agent delete ' + agent_id1)
-
- os2 = "os_2"
- arch2 = "x86"
- ver2 = "v2"
- url2 = "http://openstack"
- md5hash2 = self.MD5HASH2
- hyper2 = "xen"
- cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2))
-
- cmd_output = json.loads(self.openstack(
- 'compute agent create -f json ' +
- cmd2
- ))
- agent_id2 = str(cmd_output["agent_id"])
- self.addCleanup(self.openstack, 'compute agent delete ' + agent_id2)
-
- # Test compute agent list
- cmd_output = json.loads(self.openstack(
- 'compute agent list -f json'
- ))
-
- hypervisors = [x["Hypervisor"] for x in cmd_output]
- self.assertIn(hyper1, hypervisors)
- self.assertIn(hyper2, hypervisors)
-
- os = [x['OS'] for x in cmd_output]
- self.assertIn(os1, os)
- self.assertIn(os2, os)
-
- archs = [x['Architecture'] for x in cmd_output]
- self.assertIn(arch1, archs)
- self.assertIn(arch2, archs)
-
- versions = [x['Version'] for x in cmd_output]
- self.assertIn(ver1, versions)
- self.assertIn(ver2, versions)
-
- md5hashes = [x['Md5Hash'] for x in cmd_output]
- self.assertIn(md5hash1, md5hashes)
- self.assertIn(md5hash2, md5hashes)
-
- urls = [x['URL'] for x in cmd_output]
- self.assertIn(url1, urls)
- self.assertIn(url2, urls)
-
- # Test compute agent list --hypervisor
- cmd_output = json.loads(self.openstack(
- 'compute agent list -f json ' +
- '--hypervisor kvm'
- ))
-
- hypervisors = [x["Hypervisor"] for x in cmd_output]
- self.assertIn(hyper1, hypervisors)
- self.assertNotIn(hyper2, hypervisors)
-
- os = [x['OS'] for x in cmd_output]
- self.assertIn(os1, os)
- self.assertNotIn(os2, os)
-
- archs = [x['Architecture'] for x in cmd_output]
- self.assertIn(arch1, archs)
- self.assertNotIn(arch2, archs)
-
- versions = [x['Version'] for x in cmd_output]
- self.assertIn(ver1, versions)
- self.assertNotIn(ver2, versions)
-
- md5hashes = [x['Md5Hash'] for x in cmd_output]
- self.assertIn(md5hash1, md5hashes)
- self.assertNotIn(md5hash2, md5hashes)
-
- urls = [x['URL'] for x in cmd_output]
- self.assertIn(url1, urls)
- self.assertNotIn(url2, urls)
-
- def test_compute_agent_set(self):
- """Test compute agent set"""
- os1 = "os_1"
- arch1 = "x86_64"
- ver1 = "v1"
- ver2 = "v2"
- url1 = "http://localhost"
- url2 = "http://openstack"
- md5hash1 = self.MD5HASH1
- md5hash2 = self.MD5HASH2
- hyper1 = "kvm"
- cmd = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1))
-
- cmd_output = json.loads(self.openstack(
- 'compute agent create -f json ' +
- cmd
- ))
- agent_id = str(cmd_output["agent_id"])
- self.assertEqual(ver1, cmd_output["version"])
- self.assertEqual(url1, cmd_output["url"])
- self.assertEqual(md5hash1, cmd_output["md5hash"])
-
- self.addCleanup(self.openstack, 'compute agent delete ' + agent_id)
-
- raw_output = self.openstack(
- 'compute agent set ' +
- agent_id + ' ' +
- '--agent-version ' + ver2 + ' ' +
- '--url ' + url2 + ' ' +
- '--md5hash ' + md5hash2
- )
- self.assertOutput('', raw_output)
-
- cmd_output = json.loads(self.openstack(
- 'compute agent list -f json'
- ))
- self.assertEqual(ver2, cmd_output[0]["Version"])
- self.assertEqual(url2, cmd_output[0]["URL"])
- self.assertEqual(md5hash2, cmd_output[0]["Md5Hash"])
diff --git a/openstackclient/tests/functional/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py
index c274adf2..162d4287 100644
--- a/openstackclient/tests/functional/compute/v2/test_flavor.py
+++ b/openstackclient/tests/functional/compute/v2/test_flavor.py
@@ -115,8 +115,8 @@ class FlavorTests(base.TestCase):
self.assertFalse(
cmd_output["os-flavor-access:is_public"],
)
- self.assertEqual(
- "a='b2', b='d2'",
+ self.assertDictEqual(
+ {"a": "b2", "b": "d2"},
cmd_output["properties"],
)
@@ -133,12 +133,18 @@ class FlavorTests(base.TestCase):
"flavor list -f json " +
"--long"
))
- col_name = [x["Name"] for x in cmd_output]
- col_properties = [x['Properties'] for x in cmd_output]
- self.assertIn(name1, col_name)
- self.assertIn("a='b', c='d'", col_properties)
- self.assertNotIn(name2, col_name)
- self.assertNotIn("b2', b='d2'", col_properties)
+ # We have list of complex json objects
+ # Iterate through the list setting flags
+ found_expected = False
+ for rec in cmd_output:
+ if rec['Name'] == name1:
+ found_expected = True
+ self.assertEqual('b', rec['Properties']['a'])
+ self.assertEqual('d', rec['Properties']['c'])
+ elif rec['Name'] == name2:
+ # We should have not seen private flavor
+ self.assertFalse(True)
+ self.assertTrue(found_expected)
# Test list --public
cmd_output = json.loads(self.openstack(
@@ -201,8 +207,8 @@ class FlavorTests(base.TestCase):
self.assertFalse(
cmd_output["os-flavor-access:is_public"],
)
- self.assertEqual(
- "a='first', b='second'",
+ self.assertDictEqual(
+ {"a": "first", "b": "second"},
cmd_output["properties"],
)
@@ -223,9 +229,14 @@ class FlavorTests(base.TestCase):
cmd_output["id"],
)
self.assertEqual(
- "a='third and 10', b='second', g='fourth'",
- cmd_output['properties'],
- )
+ 'third and 10',
+ cmd_output['properties']['a'])
+ self.assertEqual(
+ 'second',
+ cmd_output['properties']['b'])
+ self.assertEqual(
+ 'fourth',
+ cmd_output['properties']['g'])
raw_output = self.openstack(
"flavor unset " +
@@ -238,7 +249,5 @@ class FlavorTests(base.TestCase):
"flavor show -f json " +
name1
))
- self.assertEqual(
- "a='third and 10', g='fourth'",
- cmd_output["properties"],
- )
+
+ self.assertNotIn('b', cmd_output['properties'])
diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py
index 6e080e9b..44d9c61f 100644
--- a/openstackclient/tests/functional/compute/v2/test_server.py
+++ b/openstackclient/tests/functional/compute/v2/test_server.py
@@ -16,6 +16,7 @@ import uuid
from tempest.lib import exceptions
+from openstackclient.compute.v2 import server as v2_server
from openstackclient.tests.functional.compute.v2 import common
from openstackclient.tests.functional.volume.v2 import common as volume_common
@@ -230,7 +231,7 @@ class ServerTests(common.ComputeTestCase):
))
# Really, shouldn't this be a list?
self.assertEqual(
- "a='b', c='d'",
+ {'a': 'b', 'c': 'd'},
cmd_output['properties'],
)
@@ -244,7 +245,7 @@ class ServerTests(common.ComputeTestCase):
name
))
self.assertEqual(
- "c='d'",
+ {'c': 'd'},
cmd_output['properties'],
)
@@ -509,6 +510,20 @@ class ServerTests(common.ComputeTestCase):
server['name'],
)
+ # check that image indicates server "booted from volume"
+ self.assertEqual(
+ v2_server.IMAGE_STRING_FOR_BFV,
+ server['image'],
+ )
+ # check server list too
+ servers = json.loads(self.openstack(
+ 'server list -f json'
+ ))
+ self.assertEqual(
+ v2_server.IMAGE_STRING_FOR_BFV,
+ servers[0]['Image']
+ )
+
# check volumes
cmd_output = json.loads(self.openstack(
'volume show -f json ' +
@@ -619,8 +634,8 @@ class ServerTests(common.ComputeTestCase):
server_name
))
volumes_attached = cmd_output['volumes_attached']
- self.assertTrue(volumes_attached.startswith('id='))
- attached_volume_id = volumes_attached.replace('id=', '')
+ self.assertIsNotNone(volumes_attached)
+ attached_volume_id = volumes_attached[0]["id"]
# check the volume that attached on server
cmd_output = json.loads(self.openstack(
@@ -699,8 +714,8 @@ class ServerTests(common.ComputeTestCase):
server_name
))
volumes_attached = cmd_output['volumes_attached']
- self.assertTrue(volumes_attached.startswith('id='))
- attached_volume_id = volumes_attached.replace('id=', '')
+ self.assertIsNotNone(volumes_attached)
+ attached_volume_id = volumes_attached[0]["id"]
# check the volume that attached on server
cmd_output = json.loads(self.openstack(
@@ -773,19 +788,21 @@ class ServerTests(common.ComputeTestCase):
server_name
))
volumes_attached = cmd_output['volumes_attached']
- self.assertTrue(volumes_attached.startswith('id='))
- attached_volume_id = volumes_attached.replace('id=', '')
- # Don't leak the volume when the test exits.
- self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id)
+ self.assertIsNotNone(volumes_attached)
+ attached_volume_id = volumes_attached[0]["id"]
+ for vol in volumes_attached:
+ self.assertIsNotNone(vol['id'])
+ # Don't leak the volume when the test exits.
+ self.addCleanup(self.openstack, 'volume delete ' + vol['id'])
# Since the server is volume-backed the GET /servers/{server_id}
- # response will have image=''.
- self.assertEqual('', cmd_output['image'])
+ # response will have image='N/A (booted from volume)'.
+ self.assertEqual(v2_server.IMAGE_STRING_FOR_BFV, cmd_output['image'])
# check the volume that attached on server
cmd_output = json.loads(self.openstack(
'volume show -f json ' +
- attached_volume_id
+ volumes_attached[0]["id"]
))
# The volume size should be what we specified on the command line.
self.assertEqual(1, int(cmd_output['size']))
@@ -879,14 +896,21 @@ class ServerTests(common.ComputeTestCase):
self.assertIsNotNone(server['id'])
self.assertEqual(server_name, server['name'])
- self.assertIn(str(security_group1['id']), server['security_groups'])
- self.assertIn(str(security_group2['id']), server['security_groups'])
+ sec_grp = ""
+ for sec in server['security_groups']:
+ sec_grp += sec['name']
+ self.assertIn(str(security_group1['id']), sec_grp)
+ self.assertIn(str(security_group2['id']), sec_grp)
self.wait_for_status(server_name, 'ACTIVE')
server = json.loads(self.openstack(
'server show -f json ' + server_name
))
- self.assertIn(sg_name1, server['security_groups'])
- self.assertIn(sg_name2, server['security_groups'])
+ # check if security group exists in list
+ sec_grp = ""
+ for sec in server['security_groups']:
+ sec_grp += sec['name']
+ self.assertIn(sg_name1, sec_grp)
+ self.assertIn(sg_name2, sec_grp)
def test_server_create_with_empty_network_option_latest(self):
"""Test server create with empty network option in nova 2.latest."""
diff --git a/openstackclient/tests/functional/examples/__init__.py b/openstackclient/tests/functional/examples/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/openstackclient/tests/functional/examples/__init__.py
+++ /dev/null
diff --git a/openstackclient/tests/functional/examples/test_examples.py b/openstackclient/tests/functional/examples/test_examples.py
deleted file mode 100644
index 031f036a..00000000
--- a/openstackclient/tests/functional/examples/test_examples.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# 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 ExampleTests(base.TestCase):
- """Functional tests for running examples."""
-
- def test_common(self):
- # NOTE(stevemar): If an examples has a non-zero return
- # code, then execute will raise an error by default.
- base.execute('python', base.EXAMPLE_DIR + '/common.py --debug')
-
- def test_object_api(self):
- base.execute('python', base.EXAMPLE_DIR + '/object_api.py --debug')
-
- def test_osc_lib(self):
- base.execute('python', base.EXAMPLE_DIR + '/osc-lib.py --debug')
diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py
index 6504c5b0..8771359c 100644
--- a/openstackclient/tests/unit/common/test_quota.py
+++ b/openstackclient/tests/unit/common/test_quota.py
@@ -1087,3 +1087,26 @@ class TestQuotaShow(TestQuota):
identity_fakes.project_id, details=False
)
self.assertNotCalled(self.network.get_quota_default)
+
+ def test_network_quota_show_remove_empty(self):
+ arglist = [
+ self.projects[0].name,
+ ]
+ verifylist = [
+ ('project', self.projects[0].name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # First check that all regular values are returned
+ result = self.cmd.get_network_quota(parsed_args)
+ self.assertEqual(len(network_fakes.QUOTA), len(result))
+
+ # set 1 of the values to None, and verify it is not returned
+ orig_get_quota = self.network.get_quota
+ network_quotas = copy.copy(network_fakes.QUOTA)
+ network_quotas['healthmonitor'] = None
+ self.network.get_quota = mock.Mock(return_value=network_quotas)
+ result = self.cmd.get_network_quota(parsed_args)
+ self.assertEqual(len(network_fakes.QUOTA) - 1, len(result))
+ # Go back to default mock
+ self.network.get_quota = orig_get_quota
diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py
index 6e12f735..d3d037a9 100644
--- a/openstackclient/tests/unit/compute/v2/fakes.py
+++ b/openstackclient/tests/unit/compute/v2/fakes.py
@@ -14,10 +14,12 @@
#
import copy
+import random
from unittest import mock
import uuid
from novaclient import api_versions
+from openstack.compute.v2 import flavor as _flavor
from openstackclient.api import compute_v2
from openstackclient.tests.unit import fakes
@@ -163,7 +165,6 @@ class FakeComputev2Client(object):
self.extensions.resource_class = fakes.FakeResource(None, {})
self.flavors = mock.Mock()
- self.flavors.resource_class = fakes.FakeResource(None, {})
self.flavor_access = mock.Mock()
self.flavor_access.resource_class = fakes.FakeResource(None, {})
@@ -195,9 +196,15 @@ class FakeComputev2Client(object):
self.server_groups = mock.Mock()
self.server_groups.resource_class = fakes.FakeResource(None, {})
+ self.server_migrations = mock.Mock()
+ self.server_migrations.resource_class = fakes.FakeResource(None, {})
+
self.instance_action = mock.Mock()
self.instance_action.resource_class = fakes.FakeResource(None, {})
+ self.migrations = mock.Mock()
+ self.migrations.resource_class = fakes.FakeResource(None, {})
+
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
@@ -770,27 +777,13 @@ class FakeFlavor(object):
'os-flavor-access:is_public': True,
'description': 'description',
'OS-FLV-EXT-DATA:ephemeral': 0,
- 'properties': {'property': 'value'},
+ 'extra_specs': {'property': 'value'},
}
# Overwrite default attributes.
flavor_info.update(attrs)
- # Set default methods.
- flavor_methods = {
- 'set_keys': None,
- 'unset_keys': None,
- 'get_keys': {'property': 'value'},
- }
-
- flavor = fakes.FakeResource(info=copy.deepcopy(flavor_info),
- methods=flavor_methods,
- loaded=True)
-
- # Set attributes with special mappings in nova client.
- flavor.disabled = flavor_info['OS-FLV-DISABLED:disabled']
- flavor.is_public = flavor_info['os-flavor-access:is_public']
- flavor.ephemeral = flavor_info['OS-FLV-EXT-DATA:ephemeral']
+ flavor = _flavor.Flavor(**flavor_info)
return flavor
@@ -877,6 +870,7 @@ class FakeKeypair(object):
# Set default attributes.
keypair_info = {
'name': 'keypair-name-' + uuid.uuid4().hex,
+ 'type': 'ssh',
'fingerprint': 'dummy',
'public_key': 'dummy',
'user_id': 'user'
@@ -1244,7 +1238,7 @@ class FakeServerGroup(object):
"""Fake one server group"""
@staticmethod
- def create_one_server_group(attrs=None):
+ def _create_one_server_group(attrs=None):
"""Create a fake server group
:param Dictionary attrs:
@@ -1261,7 +1255,6 @@ class FakeServerGroup(object):
'members': [],
'metadata': {},
'name': 'server-group-name-' + uuid.uuid4().hex,
- 'policies': [],
'project_id': 'server-group-project-id-' + uuid.uuid4().hex,
'user_id': 'server-group-user-id-' + uuid.uuid4().hex,
}
@@ -1274,6 +1267,38 @@ class FakeServerGroup(object):
loaded=True)
return server_group
+ @staticmethod
+ def create_one_server_group(attrs=None):
+ """Create a fake server group
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id and other attributes
+ """
+ if attrs is None:
+ attrs = {}
+ attrs.setdefault('policies', ['policy1', 'policy2'])
+ return FakeServerGroup._create_one_server_group(attrs)
+
+
+class FakeServerGroupV264(object):
+ """Fake one server group fo API >= 2.64"""
+
+ @staticmethod
+ def create_one_server_group(attrs=None):
+ """Create a fake server group
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id and other attributes
+ """
+ if attrs is None:
+ attrs = {}
+ attrs.setdefault('policy', 'policy1')
+ return FakeServerGroup._create_one_server_group(attrs)
+
class FakeUsage(object):
"""Fake one or more usage."""
@@ -1539,3 +1564,89 @@ class FakeRateLimit(object):
self.remain = remain
self.unit = unit
self.next_available = next_available
+
+
+class FakeServerMigration(object):
+ """Fake one or more server migrations."""
+
+ @staticmethod
+ def create_one_server_migration(attrs=None, methods=None):
+ """Create a fake server migration.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :return:
+ A FakeResource object, with id, type, and so on
+ """
+ attrs = attrs or {}
+ methods = methods or {}
+
+ # Set default attributes.
+ migration_info = {
+ "dest_host": "10.0.2.15",
+ "status": "migrating",
+ "type": "migration",
+ "updated_at": "2017-01-31T08:03:25.000000",
+ "created_at": "2017-01-31T08:03:21.000000",
+ "dest_compute": "compute-" + uuid.uuid4().hex,
+ "id": random.randint(1, 999),
+ "source_node": "node-" + uuid.uuid4().hex,
+ "server": uuid.uuid4().hex,
+ "dest_node": "node-" + uuid.uuid4().hex,
+ "source_compute": "compute-" + uuid.uuid4().hex,
+ "uuid": uuid.uuid4().hex,
+ "old_instance_type_id": uuid.uuid4().hex,
+ "new_instance_type_id": uuid.uuid4().hex,
+ "project": uuid.uuid4().hex,
+ "user": uuid.uuid4().hex
+ }
+
+ # Overwrite default attributes.
+ migration_info.update(attrs)
+
+ migration = fakes.FakeResource(info=copy.deepcopy(migration_info),
+ methods=methods,
+ loaded=True)
+ return migration
+
+ @staticmethod
+ def create_server_migrations(attrs=None, methods=None, count=2):
+ """Create multiple fake server migrations.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :param int count:
+ The number of server migrations to fake
+ :return:
+ A list of FakeResource objects faking the server migrations
+ """
+ migrations = []
+ for i in range(0, count):
+ migrations.append(
+ FakeServerMigration.create_one_server_migration(
+ attrs, methods))
+
+ return migrations
+
+ @staticmethod
+ def get_server_migrations(migrations=None, count=2):
+ """Get an iterable MagicMock object with a list of faked migrations.
+
+ If server migrations list is provided, then initialize the Mock object
+ with the list. Otherwise create one.
+
+ :param List migrations:
+ A list of FakeResource objects faking server migrations
+ :param int count:
+ The number of server migrations to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ server migrations
+ """
+ if migrations is None:
+ migrations = FakeServerMigration.create_server_migrations(count)
+ return mock.Mock(side_effect=migrations)
diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py
index cd0c1525..e12edd0f 100644
--- a/openstackclient/tests/unit/compute/v2/test_aggregate.py
+++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py
@@ -16,12 +16,14 @@
from unittest import mock
from unittest.mock import call
+from openstack import exceptions as sdk_exceptions
+from openstack import utils as sdk_utils
from osc_lib.cli import format_columns
from osc_lib import exceptions
-from osc_lib import utils
from openstackclient.compute.v2 import aggregate
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit.image.v2 import fakes as image_fakes
class TestAggregate(compute_fakes.TestComputev2):
@@ -48,8 +50,17 @@ class TestAggregate(compute_fakes.TestComputev2):
super(TestAggregate, self).setUp()
# Get a shortcut to the AggregateManager Mock
- self.aggregate_mock = self.app.client_manager.compute.aggregates
- self.aggregate_mock.reset_mock()
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.app.client_manager.sdk_connection.compute = mock.Mock()
+ self.sdk_client = self.app.client_manager.sdk_connection.compute
+ self.sdk_client.aggregates = mock.Mock()
+ self.sdk_client.find_aggregate = mock.Mock()
+ self.sdk_client.create_aggregate = mock.Mock()
+ self.sdk_client.update_aggregate = mock.Mock()
+ self.sdk_client.update_aggregate = mock.Mock()
+ self.sdk_client.set_aggregate_metadata = mock.Mock()
+ self.sdk_client.add_host_to_aggregate = mock.Mock()
+ self.sdk_client.remove_host_from_aggregate = mock.Mock()
class TestAggregateAddHost(TestAggregate):
@@ -57,8 +68,8 @@ class TestAggregateAddHost(TestAggregate):
def setUp(self):
super(TestAggregateAddHost, self).setUp()
- self.aggregate_mock.get.return_value = self.fake_ag
- self.aggregate_mock.add_host.return_value = self.fake_ag
+ self.sdk_client.find_aggregate.return_value = self.fake_ag
+ self.sdk_client.add_host_to_aggregate.return_value = self.fake_ag
self.cmd = aggregate.AddAggregateHost(self.app, None)
def test_aggregate_add_host(self):
@@ -72,11 +83,12 @@ class TestAggregateAddHost(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.aggregate_mock.add_host.assert_called_once_with(self.fake_ag,
- parsed_args.host)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.sdk_client.add_host_to_aggregate.assert_called_once_with(
+ self.fake_ag.id, parsed_args.host)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestAggregateCreate(TestAggregate):
@@ -84,8 +96,8 @@ class TestAggregateCreate(TestAggregate):
def setUp(self):
super(TestAggregateCreate, self).setUp()
- self.aggregate_mock.create.return_value = self.fake_ag
- self.aggregate_mock.set_metadata.return_value = self.fake_ag
+ self.sdk_client.create_aggregate.return_value = self.fake_ag
+ self.sdk_client.set_aggregate_metadata.return_value = self.fake_ag
self.cmd = aggregate.CreateAggregate(self.app, None)
def test_aggregate_create(self):
@@ -97,10 +109,10 @@ class TestAggregateCreate(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.aggregate_mock.create.assert_called_once_with(parsed_args.name,
- None)
+ self.sdk_client.create_aggregate.assert_called_once_with(
+ name=parsed_args.name)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_aggregate_create_with_zone(self):
arglist = [
@@ -114,10 +126,10 @@ class TestAggregateCreate(TestAggregate):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.aggregate_mock.create.assert_called_once_with(parsed_args.name,
- parsed_args.zone)
+ self.sdk_client.create_aggregate.assert_called_once_with(
+ name=parsed_args.name, availability_zone=parsed_args.zone)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_aggregate_create_with_property(self):
arglist = [
@@ -131,12 +143,12 @@ class TestAggregateCreate(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.aggregate_mock.create.assert_called_once_with(parsed_args.name,
- None)
- self.aggregate_mock.set_metadata.assert_called_once_with(
- self.fake_ag, parsed_args.property)
+ self.sdk_client.create_aggregate.assert_called_once_with(
+ name=parsed_args.name)
+ self.sdk_client.set_aggregate_metadata.assert_called_once_with(
+ self.fake_ag.id, parsed_args.property)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestAggregateDelete(TestAggregate):
@@ -146,7 +158,7 @@ class TestAggregateDelete(TestAggregate):
def setUp(self):
super(TestAggregateDelete, self).setUp()
- self.aggregate_mock.get = (
+ self.sdk_client.find_aggregate = (
compute_fakes.FakeAggregate.get_aggregates(self.fake_ags))
self.cmd = aggregate.DeleteAggregate(self.app, None)
@@ -158,10 +170,11 @@ class TestAggregateDelete(TestAggregate):
('aggregate', [self.fake_ags[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(self.fake_ags[0].id)
- self.aggregate_mock.delete.assert_called_once_with(self.fake_ags[0].id)
- self.assertIsNone(result)
+ self.cmd.take_action(parsed_args)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ self.fake_ags[0].id, ignore_missing=False)
+ self.sdk_client.delete_aggregate.assert_called_once_with(
+ self.fake_ags[0].id, ignore_missing=False)
def test_delete_multiple_aggregates(self):
arglist = []
@@ -172,13 +185,13 @@ class TestAggregateDelete(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- result = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
calls = []
for a in self.fake_ags:
- calls.append(call(a.id))
- self.aggregate_mock.delete.assert_has_calls(calls)
- self.assertIsNone(result)
+ calls.append(call(a.id, ignore_missing=False))
+ self.sdk_client.find_aggregate.assert_has_calls(calls)
+ self.sdk_client.delete_aggregate.assert_has_calls(calls)
def test_delete_multiple_agggregates_with_exception(self):
arglist = [
@@ -191,23 +204,21 @@ class TestAggregateDelete(TestAggregate):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- find_mock_result = [self.fake_ags[0], exceptions.CommandError]
- with mock.patch.object(utils, 'find_resource',
- side_effect=find_mock_result) as find_mock:
- try:
- self.cmd.take_action(parsed_args)
- self.fail('CommandError should be raised.')
- except exceptions.CommandError as e:
- self.assertEqual('1 of 2 aggregates failed to delete.',
- str(e))
-
- find_mock.assert_any_call(self.aggregate_mock, self.fake_ags[0].id)
- find_mock.assert_any_call(self.aggregate_mock, 'unexist_aggregate')
+ self.sdk_client.find_aggregate.side_effect = [
+ self.fake_ags[0], sdk_exceptions.NotFoundException]
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 aggregates failed to delete.',
+ str(e))
- self.assertEqual(2, find_mock.call_count)
- self.aggregate_mock.delete.assert_called_once_with(
- self.fake_ags[0].id
- )
+ calls = []
+ for a in arglist:
+ calls.append(call(a, ignore_missing=False))
+ self.sdk_client.find_aggregate.assert_has_calls(calls)
+ self.sdk_client.delete_aggregate.assert_called_with(
+ self.fake_ags[0].id, ignore_missing=False)
class TestAggregateList(TestAggregate):
@@ -245,7 +256,7 @@ class TestAggregateList(TestAggregate):
def setUp(self):
super(TestAggregateList, self).setUp()
- self.aggregate_mock.list.return_value = [self.fake_ag]
+ self.sdk_client.aggregates.return_value = [self.fake_ag]
self.cmd = aggregate.ListAggregate(self.app, None)
def test_aggregate_list(self):
@@ -254,7 +265,7 @@ class TestAggregateList(TestAggregate):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.list_columns, columns)
- self.assertItemEqual(self.list_data, tuple(data))
+ self.assertItemsEqual(self.list_data, tuple(data))
def test_aggregate_list_with_long(self):
arglist = [
@@ -267,7 +278,7 @@ class TestAggregateList(TestAggregate):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.list_columns_long, columns)
- self.assertListItemEqual(self.list_data_long, tuple(data))
+ self.assertItemsEqual(self.list_data_long, tuple(data))
class TestAggregateRemoveHost(TestAggregate):
@@ -275,11 +286,11 @@ class TestAggregateRemoveHost(TestAggregate):
def setUp(self):
super(TestAggregateRemoveHost, self).setUp()
- self.aggregate_mock.get.return_value = self.fake_ag
- self.aggregate_mock.remove_host.return_value = self.fake_ag
+ self.sdk_client.find_aggregate.return_value = self.fake_ag
+ self.sdk_client.remove_host_from_aggregate.return_value = self.fake_ag
self.cmd = aggregate.RemoveAggregateHost(self.app, None)
- def test_aggregate_add_host(self):
+ def test_aggregate_remove_host(self):
arglist = [
'ag1',
'host1',
@@ -290,11 +301,12 @@ class TestAggregateRemoveHost(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.aggregate_mock.remove_host.assert_called_once_with(
- self.fake_ag, parsed_args.host)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.sdk_client.remove_host_from_aggregate.assert_called_once_with(
+ self.fake_ag.id, parsed_args.host)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestAggregateSet(TestAggregate):
@@ -302,7 +314,7 @@ class TestAggregateSet(TestAggregate):
def setUp(self):
super(TestAggregateSet, self).setUp()
- self.aggregate_mock.get.return_value = self.fake_ag
+ self.sdk_client.find_aggregate.return_value = self.fake_ag
self.cmd = aggregate.SetAggregate(self.app, None)
def test_aggregate_set_no_option(self):
@@ -315,9 +327,10 @@ class TestAggregateSet(TestAggregate):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.assertNotCalled(self.aggregate_mock.update)
- self.assertNotCalled(self.aggregate_mock.set_metadata)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.assertNotCalled(self.sdk_client.update_aggregate)
+ self.assertNotCalled(self.sdk_client.set_aggregate_metadata)
self.assertIsNone(result)
def test_aggregate_set_with_name(self):
@@ -332,10 +345,11 @@ class TestAggregateSet(TestAggregate):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.aggregate_mock.update.assert_called_once_with(
- self.fake_ag, {'name': parsed_args.name})
- self.assertNotCalled(self.aggregate_mock.set_metadata)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.sdk_client.update_aggregate.assert_called_once_with(
+ self.fake_ag.id, name=parsed_args.name)
+ self.assertNotCalled(self.sdk_client.set_aggregate_metadata)
self.assertIsNone(result)
def test_aggregate_set_with_zone(self):
@@ -350,10 +364,11 @@ class TestAggregateSet(TestAggregate):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.aggregate_mock.update.assert_called_once_with(
- self.fake_ag, {'availability_zone': parsed_args.zone})
- self.assertNotCalled(self.aggregate_mock.set_metadata)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.sdk_client.update_aggregate.assert_called_once_with(
+ self.fake_ag.id, availability_zone=parsed_args.zone)
+ self.assertNotCalled(self.sdk_client.set_aggregate_metadata)
self.assertIsNone(result)
def test_aggregate_set_with_property(self):
@@ -369,10 +384,11 @@ class TestAggregateSet(TestAggregate):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.assertNotCalled(self.aggregate_mock.update)
- self.aggregate_mock.set_metadata.assert_called_once_with(
- self.fake_ag, parsed_args.property)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.assertNotCalled(self.sdk_client.update_aggregate)
+ self.sdk_client.set_aggregate_metadata.assert_called_once_with(
+ self.fake_ag.id, parsed_args.property)
self.assertIsNone(result)
def test_aggregate_set_with_no_property_and_property(self):
@@ -388,10 +404,11 @@ class TestAggregateSet(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.assertNotCalled(self.aggregate_mock.update)
- self.aggregate_mock.set_metadata.assert_called_once_with(
- self.fake_ag, {'key1': None, 'key2': 'value2'})
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.assertNotCalled(self.sdk_client.update_aggregate)
+ self.sdk_client.set_aggregate_metadata.assert_called_once_with(
+ self.fake_ag.id, {'key1': None, 'key2': 'value2'})
self.assertIsNone(result)
def test_aggregate_set_with_no_property(self):
@@ -405,10 +422,11 @@ class TestAggregateSet(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.assertNotCalled(self.aggregate_mock.update)
- self.aggregate_mock.set_metadata.assert_called_once_with(
- self.fake_ag, {'key1': None})
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.assertNotCalled(self.sdk_client.update_aggregate)
+ self.sdk_client.set_aggregate_metadata.assert_called_once_with(
+ self.fake_ag.id, {'key1': None})
self.assertIsNone(result)
def test_aggregate_set_with_zone_and_no_property(self):
@@ -424,11 +442,12 @@ class TestAggregateSet(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
- self.aggregate_mock.update.assert_called_once_with(
- self.fake_ag, {'availability_zone': parsed_args.zone})
- self.aggregate_mock.set_metadata.assert_called_once_with(
- self.fake_ag, {'key1': None})
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.sdk_client.update_aggregate.assert_called_once_with(
+ self.fake_ag.id, availability_zone=parsed_args.zone)
+ self.sdk_client.set_aggregate_metadata.assert_called_once_with(
+ self.fake_ag.id, {'key1': None})
self.assertIsNone(result)
@@ -457,7 +476,7 @@ class TestAggregateShow(TestAggregate):
def setUp(self):
super(TestAggregateShow, self).setUp()
- self.aggregate_mock.get.return_value = self.fake_ag
+ self.sdk_client.find_aggregate.return_value = self.fake_ag
self.cmd = aggregate.ShowAggregate(self.app, None)
def test_aggregate_show(self):
@@ -469,10 +488,11 @@ class TestAggregateShow(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, tuple(data))
+ self.assertItemsEqual(self.data, tuple(data))
class TestAggregateUnset(TestAggregate):
@@ -480,7 +500,7 @@ class TestAggregateUnset(TestAggregate):
def setUp(self):
super(TestAggregateUnset, self).setUp()
- self.aggregate_mock.get.return_value = self.fake_ag
+ self.sdk_client.find_aggregate.return_value = self.fake_ag
self.cmd = aggregate.UnsetAggregate(self.app, None)
def test_aggregate_unset(self):
@@ -495,7 +515,7 @@ class TestAggregateUnset(TestAggregate):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.set_metadata.assert_called_once_with(
+ self.sdk_client.set_aggregate_metadata.assert_called_once_with(
self.fake_ag, {'unset_key': None})
self.assertIsNone(result)
@@ -512,7 +532,7 @@ class TestAggregateUnset(TestAggregate):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.aggregate_mock.set_metadata.assert_called_once_with(
+ self.sdk_client.set_aggregate_metadata.assert_called_once_with(
self.fake_ag, {'unset_key1': None, 'unset_key2': None})
self.assertIsNone(result)
@@ -526,5 +546,72 @@ class TestAggregateUnset(TestAggregate):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.assertNotCalled(self.aggregate_mock.set_metadata)
+ self.assertNotCalled(self.sdk_client.set_aggregate_metadata)
self.assertIsNone(result)
+
+
+class TestAggregateCacheImage(TestAggregate):
+
+ images = image_fakes.FakeImage.create_images(count=2)
+
+ def setUp(self):
+ super(TestAggregateCacheImage, self).setUp()
+
+ self.sdk_client.find_aggregate.return_value = self.fake_ag
+ self.find_image_mock = mock.Mock(side_effect=self.images)
+ self.app.client_manager.sdk_connection.image.find_image = \
+ self.find_image_mock
+
+ self.cmd = aggregate.CacheImageForAggregate(self.app, None)
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_aggregate_not_supported(self, sm_mock):
+ arglist = [
+ 'ag1',
+ 'im1'
+ ]
+ verifylist = [
+ ('aggregate', 'ag1'),
+ ('image', ['im1']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args
+ )
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_aggregate_add_single_image(self, sm_mock):
+ arglist = [
+ 'ag1',
+ 'im1'
+ ]
+ verifylist = [
+ ('aggregate', 'ag1'),
+ ('image', ['im1']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.sdk_client.aggregate_precache_images.assert_called_once_with(
+ self.fake_ag.id, [self.images[0].id])
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_aggregate_add_multiple_images(self, sm_mock):
+ arglist = [
+ 'ag1',
+ 'im1',
+ 'im2',
+ ]
+ verifylist = [
+ ('aggregate', 'ag1'),
+ ('image', ['im1', 'im2']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+ self.sdk_client.find_aggregate.assert_called_once_with(
+ parsed_args.aggregate, ignore_missing=False)
+ self.sdk_client.aggregate_precache_images.assert_called_once_with(
+ self.fake_ag.id, [self.images[0].id, self.images[1].id])
diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py
index 99a14f04..db9603c9 100644
--- a/openstackclient/tests/unit/compute/v2/test_console.py
+++ b/openstackclient/tests/unit/compute/v2/test_console.py
@@ -17,29 +17,103 @@ from unittest import mock
from openstackclient.compute.v2 import console
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit import utils
class TestConsole(compute_fakes.TestComputev2):
def setUp(self):
super(TestConsole, self).setUp()
- self.servers_mock = self.app.client_manager.compute.servers
- self.servers_mock.reset_mock()
+
+ # SDK mock
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.app.client_manager.sdk_connection.compute = mock.Mock()
+ self.sdk_client = self.app.client_manager.sdk_connection.compute
+ self.sdk_client.find_server = mock.Mock()
+ self.sdk_client.get_server_console_output = mock.Mock()
+
+
+class TestConsoleLog(TestConsole):
+ _server = compute_fakes.FakeServer.create_one_server()
+
+ def setUp(self):
+ super(TestConsoleLog, self).setUp()
+
+ self.sdk_client.find_server.return_value = self._server
+
+ self.cmd = console.ShowConsoleLog(self.app, None)
+
+ def test_show_no_args(self):
+ arglist = [
+ ]
+ verifylist = [
+ ]
+ self.assertRaises(utils.ParserException,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ verifylist)
+
+ def test_show(self):
+ arglist = [
+ 'fake_server'
+ ]
+ verifylist = [
+ ('server', 'fake_server')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ output = {
+ 'output': '1st line\n2nd line\n'
+ }
+ self.sdk_client.get_server_console_output.return_value = output
+ self.cmd.take_action(parsed_args)
+
+ self.sdk_client.find_server.assert_called_with(
+ name_or_id='fake_server', ignore_missing=False)
+ self.sdk_client.get_server_console_output.assert_called_with(
+ self._server.id,
+ length=None
+ )
+ stdout = self.app.stdout.content
+ self.assertEqual(stdout[0], output['output'])
+
+ def test_show_lines(self):
+ arglist = [
+ 'fake_server',
+ '--lines', '15'
+ ]
+ verifylist = [
+ ('server', 'fake_server'),
+ ('lines', 15)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ output = {
+ 'output': '1st line\n2nd line'
+ }
+ self.sdk_client.get_server_console_output.return_value = output
+ self.cmd.take_action(parsed_args)
+
+ self.sdk_client.find_server.assert_called_with(
+ name_or_id='fake_server', ignore_missing=False)
+ self.sdk_client.get_server_console_output.assert_called_with(
+ self._server.id,
+ length=15
+ )
class TestConsoleUrlShow(TestConsole):
+ _server = compute_fakes.FakeServer.create_one_server()
def setUp(self):
super(TestConsoleUrlShow, self).setUp()
- fake_console_data = {'remote_console': {'url': 'http://localhost',
- 'protocol': 'fake_protocol',
- 'type': 'fake_type'}}
- methods = {
- 'get_console_url': fake_console_data
- }
- self.fake_server = compute_fakes.FakeServer.create_one_server(
- methods=methods)
- self.servers_mock.get.return_value = self.fake_server
+ self.sdk_client.find_server.return_value = self._server
+ fake_console_data = {'url': 'http://localhost',
+ 'protocol': 'fake_protocol',
+ 'type': 'fake_type'}
+ self.sdk_client.create_console = mock.Mock(
+ return_value=fake_console_data)
self.columns = (
'protocol',
@@ -47,9 +121,9 @@ class TestConsoleUrlShow(TestConsole):
'url',
)
self.data = (
- fake_console_data['remote_console']['protocol'],
- fake_console_data['remote_console']['type'],
- fake_console_data['remote_console']['url']
+ fake_console_data['protocol'],
+ fake_console_data['type'],
+ fake_console_data['url']
)
self.cmd = console.ShowConsoleURL(self.app, None)
@@ -64,7 +138,9 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_console_url.assert_called_once_with('novnc')
+ self.sdk_client.create_console.assert_called_once_with(
+ self._server.id,
+ console_type='novnc')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -79,7 +155,9 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_console_url.assert_called_once_with('novnc')
+ self.sdk_client.create_console.assert_called_once_with(
+ self._server.id,
+ console_type='novnc')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -94,7 +172,9 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_console_url.assert_called_once_with('xvpvnc')
+ self.sdk_client.create_console.assert_called_once_with(
+ self._server.id,
+ console_type='xvpvnc')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -109,41 +189,12 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_console_url.assert_called_once_with(
- 'spice-html5')
+ self.sdk_client.create_console.assert_called_once_with(
+ self._server.id,
+ console_type='spice-html5')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
- def test_console_url_show_compatible(self):
- methods = {
- 'get_console_url': {'console': {'url': 'http://localhost',
- 'type': 'fake_type'}},
- }
- old_fake_server = compute_fakes.FakeServer.create_one_server(
- methods=methods)
- old_columns = (
- 'type',
- 'url',
- )
- old_data = (
- methods['get_console_url']['console']['type'],
- methods['get_console_url']['console']['url']
- )
- arglist = [
- 'foo_vm',
- ]
- verifylist = [
- ('url_type', 'novnc'),
- ('server', 'foo_vm'),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- with mock.patch.object(self.servers_mock, 'get',
- return_value=old_fake_server):
- columns, data = self.cmd.take_action(parsed_args)
- old_fake_server.get_console_url.assert_called_once_with('novnc')
- self.assertEqual(old_columns, columns)
- self.assertEqual(old_data, data)
-
def test_console_url_show_with_rdp(self):
arglist = [
'--rdp',
@@ -155,8 +206,9 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_console_url.assert_called_once_with(
- 'rdp-html5')
+ self.sdk_client.create_console.assert_called_once_with(
+ self._server.id,
+ console_type='rdp-html5')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -171,8 +223,9 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_console_url.assert_called_once_with(
- 'serial')
+ self.sdk_client.create_console.assert_called_once_with(
+ self._server.id,
+ console_type='serial')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -187,6 +240,8 @@ class TestConsoleUrlShow(TestConsole):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- self.fake_server.get_console_url.assert_called_once_with('webmks')
+ self.sdk_client.create_console.assert_called_once_with(
+ self._server.id,
+ console_type='webmks')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py
index fe7ce174..4c4882ec 100644
--- a/openstackclient/tests/unit/compute/v2/test_flavor.py
+++ b/openstackclient/tests/unit/compute/v2/test_flavor.py
@@ -12,13 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
#
-
from unittest import mock
-from unittest.mock import call
-import novaclient
+from openstack.compute.v2 import flavor as _flavor
+from openstack import exceptions as sdk_exceptions
+from openstack import utils as sdk_utils
+from osc_lib.cli import format_columns
from osc_lib import exceptions
-from osc_lib import utils
from openstackclient.compute.v2 import flavor
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
@@ -31,13 +31,19 @@ class TestFlavor(compute_fakes.TestComputev2):
def setUp(self):
super(TestFlavor, self).setUp()
- # Get a shortcut to the FlavorManager Mock
- self.flavors_mock = self.app.client_manager.compute.flavors
- self.flavors_mock.reset_mock()
-
- # Get a shortcut to the FlavorAccessManager Mock
- self.flavor_access_mock = self.app.client_manager.compute.flavor_access
- self.flavor_access_mock.reset_mock()
+ # SDK mock
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.app.client_manager.sdk_connection.compute = mock.Mock()
+ self.sdk_client = self.app.client_manager.sdk_connection.compute
+ self.sdk_client.flavors = mock.Mock()
+ self.sdk_client.find_flavor = mock.Mock()
+ self.sdk_client.delete_flavor = mock.Mock()
+ self.sdk_client.update_flavor = mock.Mock()
+ self.sdk_client.flavor_add_tenant_access = mock.Mock()
+ self.sdk_client.flavor_remove_tenant_access = mock.Mock()
+ self.sdk_client.create_flavor_extra_specs = mock.Mock()
+ self.sdk_client.update_flavor_extra_specs_property = mock.Mock()
+ self.sdk_client.delete_flavor_extra_specs_property = mock.Mock()
self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock()
@@ -48,6 +54,7 @@ class TestFlavorCreate(TestFlavor):
flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'links': 'flavor-links'})
project = identity_fakes.FakeProject.create_one_project()
+
columns = (
'OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral',
@@ -60,17 +67,32 @@ class TestFlavorCreate(TestFlavor):
'ram',
'rxtx_factor',
'swap',
- 'vcpus',
+ 'vcpus'
)
+
data = (
- flavor.disabled,
+ flavor.is_disabled,
flavor.ephemeral,
flavor.description,
flavor.disk,
flavor.id,
flavor.name,
flavor.is_public,
- utils.format_dict(flavor.properties),
+ format_columns.DictColumn(flavor.extra_specs),
+ flavor.ram,
+ flavor.rxtx_factor,
+ flavor.swap,
+ flavor.vcpus,
+ )
+ data_private = (
+ flavor.is_disabled,
+ flavor.ephemeral,
+ flavor.description,
+ flavor.disk,
+ flavor.id,
+ flavor.name,
+ False,
+ format_columns.DictColumn(flavor.extra_specs),
flavor.ram,
flavor.rxtx_factor,
flavor.swap,
@@ -82,7 +104,7 @@ class TestFlavorCreate(TestFlavor):
# Return a project
self.projects_mock.get.return_value = self.project
- self.flavors_mock.create.return_value = self.flavor
+ self.sdk_client.create_flavor.return_value = self.flavor
self.cmd = flavor.CreateFlavor(self.app, None)
def test_flavor_create_default_options(self):
@@ -95,23 +117,23 @@ class TestFlavorCreate(TestFlavor):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- default_args = (
- self.flavor.name,
- 256,
- 1,
- 0,
- 'auto',
- 0,
- 0,
- 1.0,
- True,
- None,
- )
+ default_args = {
+ 'name': self.flavor.name,
+ 'ram': 256,
+ 'vcpus': 1,
+ 'disk': 0,
+ 'id': None,
+ 'ephemeral': 0,
+ 'swap': 0,
+ 'rxtx_factor': 1.0,
+ 'is_public': True,
+ }
+
columns, data = self.cmd.take_action(parsed_args)
- self.flavors_mock.create.assert_called_once_with(*default_args)
+ self.sdk_client.create_flavor.assert_called_once_with(**default_args)
self.assertEqual(self.columns, columns)
- self.assertEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_flavor_create_all_options(self):
@@ -143,29 +165,44 @@ class TestFlavorCreate(TestFlavor):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- 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,
- self.flavor.is_public,
- self.flavor.description,
- )
- self.app.client_manager.compute.api_version = 2.55
- with mock.patch.object(novaclient.api_versions,
- 'APIVersion',
- return_value=2.55):
+ args = {
+ 'name': self.flavor.name,
+ 'ram': self.flavor.ram,
+ 'vcpus': self.flavor.vcpus,
+ 'disk': self.flavor.disk,
+ 'id': self.flavor.id,
+ 'ephemeral': self.flavor.ephemeral,
+ 'swap': self.flavor.swap,
+ 'rxtx_factor': self.flavor.rxtx_factor,
+ 'is_public': self.flavor.is_public,
+ 'description': self.flavor.description
+ }
+
+ props = {'property': 'value'}
+
+ # SDK updates the flavor object instance. In order to make the
+ # verification clear and preciese let's create new flavor and change
+ # expected props this way
+ create_flavor = _flavor.Flavor(**self.flavor)
+ expected_flavor = _flavor.Flavor(**self.flavor)
+ expected_flavor.extra_specs = props
+ # convert expected data tuple to list to be able to modify it
+ cmp_data = list(self.data)
+ cmp_data[7] = format_columns.DictColumn(props)
+ self.sdk_client.create_flavor.return_value = create_flavor
+ self.sdk_client.create_flavor_extra_specs.return_value = \
+ expected_flavor
+
+ with mock.patch.object(sdk_utils, 'supports_microversion',
+ return_value=True):
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()
+ self.sdk_client.create_flavor.assert_called_once_with(**args)
+ self.sdk_client.create_flavor_extra_specs.assert_called_once_with(
+ create_flavor, props)
+ self.sdk_client.get_flavor_access.assert_not_called()
self.assertEqual(self.columns, columns)
- self.assertEqual(self.data, data)
+ self.assertItemsEqual(tuple(cmp_data), data)
def test_flavor_create_other_options(self):
@@ -200,33 +237,47 @@ class TestFlavorCreate(TestFlavor):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- args = (
- self.flavor.name,
- self.flavor.ram,
- self.flavor.vcpus,
- self.flavor.disk,
- 'auto',
- self.flavor.ephemeral,
- self.flavor.swap,
- self.flavor.rxtx_factor,
- self.flavor.is_public,
- self.flavor.description,
- )
- self.app.client_manager.compute.api_version = 2.55
- with mock.patch.object(novaclient.api_versions,
- 'APIVersion',
- return_value=2.55):
+ args = {
+ 'name': self.flavor.name,
+ 'ram': self.flavor.ram,
+ 'vcpus': self.flavor.vcpus,
+ 'disk': self.flavor.disk,
+ 'id': 'auto',
+ 'ephemeral': self.flavor.ephemeral,
+ 'swap': self.flavor.swap,
+ 'rxtx_factor': self.flavor.rxtx_factor,
+ 'is_public': False,
+ 'description': self.flavor.description
+ }
+
+ props = {'key1': 'value1', 'key2': 'value2'}
+
+ # SDK updates the flavor object instance. In order to make the
+ # verification clear and preciese let's create new flavor and change
+ # expected props this way
+ create_flavor = _flavor.Flavor(**self.flavor)
+ expected_flavor = _flavor.Flavor(**self.flavor)
+ expected_flavor.extra_specs = props
+ expected_flavor.is_public = False
+ # convert expected data tuple to list to be able to modify it
+ cmp_data = list(self.data_private)
+ cmp_data[7] = format_columns.DictColumn(props)
+ self.sdk_client.create_flavor.return_value = create_flavor
+ self.sdk_client.create_flavor_extra_specs.return_value = \
+ expected_flavor
+
+ with mock.patch.object(sdk_utils, 'supports_microversion',
+ return_value=True):
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.sdk_client.create_flavor.assert_called_once_with(**args)
+ self.sdk_client.flavor_add_tenant_access.assert_called_with(
self.flavor.id,
self.project.id,
)
- self.flavor.set_keys.assert_called_with(
- {'key1': 'value1', 'key2': 'value2'})
- self.flavor.get_keys.assert_called_with()
+ self.sdk_client.create_flavor_extra_specs.assert_called_with(
+ create_flavor, props)
self.assertEqual(self.columns, columns)
- self.assertEqual(self.data, data)
+ self.assertItemsEqual(cmp_data, data)
def test_public_flavor_create_with_project(self):
arglist = [
@@ -278,29 +329,28 @@ class TestFlavorCreate(TestFlavor):
('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):
+ with mock.patch.object(sdk_utils, 'supports_microversion',
+ return_value=True):
+
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',
- )
+ args = {
+ 'name': self.flavor.name,
+ 'ram': self.flavor.ram,
+ 'vcpus': self.flavor.vcpus,
+ 'disk': self.flavor.disk,
+ 'id': self.flavor.id,
+ 'ephemeral': self.flavor.ephemeral,
+ 'swap': self.flavor.swap,
+ 'rxtx_factor': self.flavor.rxtx_factor,
+ 'is_public': self.flavor.is_public,
+ 'description': 'fake description'
+ }
- self.flavors_mock.create.assert_called_once_with(*args)
+ self.sdk_client.create_flavor.assert_called_once_with(**args)
self.assertEqual(self.columns, columns)
- self.assertEqual(self.data, data)
+ self.assertItemsEqual(self.data_private, data)
def test_flavor_create_with_description_api_older(self):
arglist = [
@@ -318,10 +368,8 @@ class TestFlavorCreate(TestFlavor):
]
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):
+ with mock.patch.object(sdk_utils, 'supports_microversion',
+ return_value=False):
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)
@@ -333,9 +381,7 @@ class TestFlavorDelete(TestFlavor):
def setUp(self):
super(TestFlavorDelete, self).setUp()
- self.flavors_mock.get = (
- compute_fakes.FakeFlavor.get_flavors(self.flavors))
- self.flavors_mock.delete.return_value = None
+ self.sdk_client.delete_flavor.return_value = None
self.cmd = flavor.DeleteFlavor(self.app, None)
@@ -348,9 +394,13 @@ class TestFlavorDelete(TestFlavor):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.sdk_client.find_flavor.return_value = self.flavors[0]
+
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.delete.assert_called_with(self.flavors[0].id)
+ self.sdk_client.find_flavor.assert_called_with(self.flavors[0].id,
+ ignore_missing=False)
+ self.sdk_client.delete_flavor.assert_called_with(self.flavors[0].id)
self.assertIsNone(result)
def test_delete_multiple_flavors(self):
@@ -362,12 +412,17 @@ class TestFlavorDelete(TestFlavor):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.sdk_client.find_flavor.side_effect = self.flavors
+
result = self.cmd.take_action(parsed_args)
- calls = []
- for f in self.flavors:
- calls.append(call(f.id))
- self.flavors_mock.delete.assert_has_calls(calls)
+ find_calls = [
+ mock.call(i.id, ignore_missing=False) for i in self.flavors
+ ]
+ delete_calls = [mock.call(i.id) for i in self.flavors]
+ self.sdk_client.find_flavor.assert_has_calls(find_calls)
+ self.sdk_client.delete_flavor.assert_has_calls(delete_calls)
self.assertIsNone(result)
def test_multi_flavors_delete_with_exception(self):
@@ -380,11 +435,10 @@ class TestFlavorDelete(TestFlavor):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- find_mock_result = [self.flavors[0], exceptions.CommandError]
- self.flavors_mock.get = (
- mock.Mock(side_effect=find_mock_result)
- )
- self.flavors_mock.find.side_effect = exceptions.NotFound(None)
+ self.sdk_client.find_flavor.side_effect = [
+ self.flavors[0],
+ sdk_exceptions.ResourceNotFound
+ ]
try:
self.cmd.take_action(parsed_args)
@@ -392,15 +446,18 @@ class TestFlavorDelete(TestFlavor):
except exceptions.CommandError as e:
self.assertEqual('1 of 2 flavors failed to delete.', str(e))
- self.flavors_mock.get.assert_any_call(self.flavors[0].id)
- self.flavors_mock.get.assert_any_call('unexist_flavor')
- self.flavors_mock.delete.assert_called_once_with(self.flavors[0].id)
+ find_calls = [
+ mock.call(self.flavors[0].id, ignore_missing=False),
+ mock.call('unexist_flavor', ignore_missing=False),
+ ]
+ delete_calls = [mock.call(self.flavors[0].id)]
+ self.sdk_client.find_flavor.assert_has_calls(find_calls)
+ self.sdk_client.delete_flavor.assert_has_calls(delete_calls)
class TestFlavorList(TestFlavor):
- # Return value of self.flavors_mock.list().
- flavors = compute_fakes.FakeFlavor.create_flavors(count=1)
+ _flavor = compute_fakes.FakeFlavor.create_one_flavor()
columns = (
'ID',
@@ -418,24 +475,27 @@ class TestFlavorList(TestFlavor):
)
data = ((
- flavors[0].id,
- flavors[0].name,
- flavors[0].ram,
- flavors[0].disk,
- flavors[0].ephemeral,
- flavors[0].vcpus,
- flavors[0].is_public,
- ), )
+ _flavor.id,
+ _flavor.name,
+ _flavor.ram,
+ _flavor.disk,
+ _flavor.ephemeral,
+ _flavor.vcpus,
+ _flavor.is_public,
+ ),)
data_long = (data[0] + (
- flavors[0].swap,
- flavors[0].rxtx_factor,
- u'property=\'value\''
+ _flavor.swap,
+ _flavor.rxtx_factor,
+ format_columns.DictColumn(_flavor.extra_specs)
), )
def setUp(self):
super(TestFlavorList, self).setUp()
- self.flavors_mock.list.return_value = self.flavors
+ self.api_mock = mock.Mock()
+ self.api_mock.side_effect = [[self._flavor], [], ]
+
+ self.sdk_client.flavors = self.api_mock
# Get the command object to test
self.cmd = flavor.ListFlavor(self.app, None)
@@ -458,16 +518,14 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
'is_public': True,
- 'limit': None,
- 'marker': None
}
- self.flavors_mock.list.assert_called_with(
+ self.sdk_client.flavors.assert_called_with(
**kwargs
)
self.assertEqual(self.columns, columns)
- self.assertEqual(tuple(self.data), tuple(data))
+ self.assertEqual(self.data, tuple(data))
def test_flavor_list_all_flavors(self):
arglist = [
@@ -487,16 +545,14 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
'is_public': None,
- 'limit': None,
- 'marker': None
}
- self.flavors_mock.list.assert_called_with(
+ self.sdk_client.flavors.assert_called_with(
**kwargs
)
self.assertEqual(self.columns, columns)
- self.assertEqual(tuple(self.data), tuple(data))
+ self.assertEqual(self.data, tuple(data))
def test_flavor_list_private_flavors(self):
arglist = [
@@ -516,16 +572,14 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
'is_public': False,
- 'limit': None,
- 'marker': None
}
- self.flavors_mock.list.assert_called_with(
+ self.sdk_client.flavors.assert_called_with(
**kwargs
)
self.assertEqual(self.columns, columns)
- self.assertEqual(tuple(self.data), tuple(data))
+ self.assertEqual(self.data, tuple(data))
def test_flavor_list_public_flavors(self):
arglist = [
@@ -545,16 +599,14 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
'is_public': True,
- 'limit': None,
- 'marker': None
}
- self.flavors_mock.list.assert_called_with(
+ self.sdk_client.flavors.assert_called_with(
**kwargs
)
self.assertEqual(self.columns, columns)
- self.assertEqual(tuple(self.data), tuple(data))
+ self.assertEqual(self.data, tuple(data))
def test_flavor_list_long(self):
arglist = [
@@ -574,21 +626,19 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
'is_public': True,
- 'limit': None,
- 'marker': None
}
- self.flavors_mock.list.assert_called_with(
+ self.sdk_client.flavors.assert_called_with(
**kwargs
)
self.assertEqual(self.columns_long, columns)
- self.assertEqual(tuple(self.data_long), tuple(data))
+ self.assertItemsEqual(self.data_long, tuple(data))
class TestFlavorSet(TestFlavor):
- # Return value of self.flavors_mock.find().
+ # Return value of self.sdk_client.find_flavor().
flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'os-flavor-access:is_public': False})
project = identity_fakes.FakeProject.create_one_project()
@@ -596,8 +646,7 @@ class TestFlavorSet(TestFlavor):
def setUp(self):
super(TestFlavorSet, self).setUp()
- self.flavors_mock.find.return_value = self.flavor
- self.flavors_mock.get.side_effect = exceptions.NotFound(None)
+ self.sdk_client.find_flavor.return_value = self.flavor
# Return a project
self.projects_mock.get.return_value = self.project
self.cmd = flavor.SetFlavor(self.app, None)
@@ -614,9 +663,14 @@ class TestFlavorSet(TestFlavor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
- is_public=None)
- self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'})
+ self.sdk_client.find_flavor.assert_called_with(
+ parsed_args.flavor,
+ get_extra_specs=True,
+ ignore_missing=False
+ )
+ self.sdk_client.create_flavor_extra_specs.assert_called_with(
+ self.flavor.id,
+ {'FOO': '"B A R"'})
self.assertIsNone(result)
def test_flavor_set_no_property(self):
@@ -631,9 +685,13 @@ class TestFlavorSet(TestFlavor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
- is_public=None)
- self.flavor.unset_keys.assert_called_with(['property'])
+ self.sdk_client.find_flavor.assert_called_with(
+ parsed_args.flavor,
+ get_extra_specs=True,
+ ignore_missing=False
+ )
+ self.sdk_client.delete_flavor_extra_specs_property.assert_called_with(
+ self.flavor.id, 'property')
self.assertIsNone(result)
def test_flavor_set_project(self):
@@ -649,13 +707,16 @@ class TestFlavorSet(TestFlavor):
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
- is_public=None)
- self.flavor_access_mock.add_tenant_access.assert_called_with(
+ self.sdk_client.find_flavor.assert_called_with(
+ parsed_args.flavor,
+ get_extra_specs=True,
+ ignore_missing=False
+ )
+ self.sdk_client.flavor_add_tenant_access.assert_called_with(
self.flavor.id,
self.project.id,
)
- self.flavor.set_keys.assert_not_called()
+ self.sdk_client.create_flavor_extra_specs.assert_not_called()
self.assertIsNone(result)
def test_flavor_set_no_project(self):
@@ -681,8 +742,9 @@ class TestFlavorSet(TestFlavor):
self.cmd, arglist, verifylist)
def test_flavor_set_with_unexist_flavor(self):
- self.flavors_mock.get.side_effect = exceptions.NotFound(None)
- self.flavors_mock.find.side_effect = exceptions.NotFound(None)
+ self.sdk_client.find_flavor.side_effect = [
+ sdk_exceptions.ResourceNotFound()
+ ]
arglist = [
'--project', self.project.id,
@@ -708,9 +770,12 @@ class TestFlavorSet(TestFlavor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
- is_public=None)
- self.flavor_access_mock.add_tenant_access.assert_not_called()
+ self.sdk_client.find_flavor.assert_called_with(
+ parsed_args.flavor,
+ get_extra_specs=True,
+ ignore_missing=False
+ )
+ self.sdk_client.flavor_add_tenant_access.assert_not_called()
self.assertIsNone(result)
def test_flavor_set_description_api_newer(self):
@@ -724,11 +789,11 @@ class TestFlavorSet(TestFlavor):
]
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):
+ with mock.patch.object(sdk_utils,
+ 'supports_microversion',
+ return_value=True):
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.update.assert_called_with(
+ self.sdk_client.update_flavor.assert_called_with(
flavor=self.flavor.id, description='description')
self.assertIsNone(result)
@@ -743,16 +808,54 @@ class TestFlavorSet(TestFlavor):
]
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):
+ with mock.patch.object(sdk_utils,
+ 'supports_microversion',
+ return_value=False):
+ self.assertRaises(exceptions.CommandError, self.cmd.take_action,
+ parsed_args)
+
+ def test_flavor_set_description_using_name_api_newer(self):
+ arglist = [
+ '--description', 'description',
+ self.flavor.name,
+ ]
+ verifylist = [
+ ('description', 'description'),
+ ('flavor', 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(sdk_utils,
+ 'supports_microversion',
+ return_value=True):
+ result = self.cmd.take_action(parsed_args)
+ self.sdk_client.update_flavor.assert_called_with(
+ flavor=self.flavor.id, description='description')
+ self.assertIsNone(result)
+
+ def test_flavor_set_description_using_name_api_older(self):
+ arglist = [
+ '--description', 'description',
+ self.flavor.name,
+ ]
+ verifylist = [
+ ('description', 'description'),
+ ('flavor', 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(sdk_utils,
+ 'supports_microversion',
+ return_value=False):
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)
class TestFlavorShow(TestFlavor):
- # Return value of self.flavors_mock.find().
+ # Return value of self.sdk_client.find_flavor().
flavor_access = compute_fakes.FakeFlavorAccess.create_one_flavor_access()
flavor = compute_fakes.FakeFlavor.create_one_flavor()
@@ -769,11 +872,11 @@ class TestFlavorShow(TestFlavor):
'ram',
'rxtx_factor',
'swap',
- 'vcpus',
+ 'vcpus'
)
data = (
- flavor.disabled,
+ flavor.is_disabled,
flavor.ephemeral,
None,
flavor.description,
@@ -781,7 +884,7 @@ class TestFlavorShow(TestFlavor):
flavor.id,
flavor.name,
flavor.is_public,
- utils.format_dict(flavor.get_keys()),
+ format_columns.DictColumn(flavor.extra_specs),
flavor.ram,
flavor.rxtx_factor,
flavor.swap,
@@ -792,9 +895,8 @@ class TestFlavorShow(TestFlavor):
super(TestFlavorShow, self).setUp()
# Return value of _find_resource()
- self.flavors_mock.find.return_value = self.flavor
- self.flavors_mock.get.side_effect = exceptions.NotFound(None)
- self.flavor_access_mock.list.return_value = [self.flavor_access]
+ self.sdk_client.find_flavor.return_value = self.flavor
+ self.sdk_client.get_flavor_access.return_value = [self.flavor_access]
self.cmd = flavor.ShowFlavor(self.app, None)
def test_show_no_options(self):
@@ -818,7 +920,7 @@ class TestFlavorShow(TestFlavor):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, columns)
- self.assertEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_private_flavor_show(self):
private_flavor = compute_fakes.FakeFlavor.create_one_flavor(
@@ -826,7 +928,7 @@ class TestFlavorShow(TestFlavor):
'os-flavor-access:is_public': False,
}
)
- self.flavors_mock.find.return_value = private_flavor
+ self.sdk_client.find_flavor.return_value = private_flavor
arglist = [
private_flavor.name,
@@ -836,15 +938,15 @@ class TestFlavorShow(TestFlavor):
]
data_with_project = (
- private_flavor.disabled,
+ private_flavor.is_disabled,
private_flavor.ephemeral,
- self.flavor_access.tenant_id,
+ [self.flavor_access.tenant_id],
private_flavor.description,
private_flavor.disk,
private_flavor.id,
private_flavor.name,
private_flavor.is_public,
- utils.format_dict(private_flavor.get_keys()),
+ format_columns.DictColumn(private_flavor.extra_specs),
private_flavor.ram,
private_flavor.rxtx_factor,
private_flavor.swap,
@@ -855,15 +957,15 @@ class TestFlavorShow(TestFlavor):
columns, data = self.cmd.take_action(parsed_args)
- self.flavor_access_mock.list.assert_called_with(
+ self.sdk_client.get_flavor_access.assert_called_with(
flavor=private_flavor.id)
self.assertEqual(self.columns, columns)
- self.assertEqual(data_with_project, data)
+ self.assertItemsEqual(data_with_project, data)
class TestFlavorUnset(TestFlavor):
- # Return value of self.flavors_mock.find().
+ # Return value of self.sdk_client.find_flavor().
flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'os-flavor-access:is_public': False})
project = identity_fakes.FakeProject.create_one_project()
@@ -871,12 +973,13 @@ class TestFlavorUnset(TestFlavor):
def setUp(self):
super(TestFlavorUnset, self).setUp()
- self.flavors_mock.find.return_value = self.flavor
- self.flavors_mock.get.side_effect = exceptions.NotFound(None)
+ self.sdk_client.find_flavor.return_value = self.flavor
# Return a project
self.projects_mock.get.return_value = self.project
self.cmd = flavor.UnsetFlavor(self.app, None)
+ self.mock_shortcut = self.sdk_client.delete_flavor_extra_specs_property
+
def test_flavor_unset_property(self):
arglist = [
'--property', 'property',
@@ -889,12 +992,49 @@ class TestFlavorUnset(TestFlavor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
- is_public=None)
- self.flavor.unset_keys.assert_called_with(['property'])
- self.flavor_access_mock.remove_tenant_access.assert_not_called()
+ self.sdk_client.find_flavor.assert_called_with(
+ parsed_args.flavor,
+ get_extra_specs=True,
+ ignore_missing=False)
+ self.mock_shortcut.assert_called_with(
+ self.flavor.id, 'property')
+ self.sdk_client.flavor_remove_tenant_access.assert_not_called()
self.assertIsNone(result)
+ def test_flavor_unset_properties(self):
+ arglist = [
+ '--property', 'property1',
+ '--property', 'property2',
+ 'baremetal'
+ ]
+ verifylist = [
+ ('property', ['property1', 'property2']),
+ ('flavor', 'baremetal'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+ self.sdk_client.find_flavor.assert_called_with(
+ parsed_args.flavor,
+ get_extra_specs=True,
+ ignore_missing=False)
+ calls = [
+ mock.call(self.flavor.id, 'property1'),
+ mock.call(self.flavor.id, 'property2')
+ ]
+ self.mock_shortcut.assert_has_calls(
+ calls)
+
+ # A bit tricky way to ensure we do not unset other properties
+ calls.append(mock.call(self.flavor.id, 'property'))
+ self.assertRaises(
+ AssertionError,
+ self.mock_shortcut.assert_has_calls,
+ calls
+ )
+
+ self.sdk_client.flavor_remove_tenant_access.assert_not_called()
+
def test_flavor_unset_project(self):
arglist = [
'--project', self.project.id,
@@ -909,13 +1049,14 @@ class TestFlavorUnset(TestFlavor):
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
- self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
- is_public=None)
- self.flavor_access_mock.remove_tenant_access.assert_called_with(
+ self.sdk_client.find_flavor.assert_called_with(
+ parsed_args.flavor, get_extra_specs=True,
+ ignore_missing=False)
+ self.sdk_client.flavor_remove_tenant_access.assert_called_with(
self.flavor.id,
self.project.id,
)
- self.flavor.unset_keys.assert_not_called()
+ self.sdk_client.delete_flavor_extra_specs_proerty.assert_not_called()
self.assertIsNone(result)
def test_flavor_unset_no_project(self):
@@ -941,8 +1082,9 @@ class TestFlavorUnset(TestFlavor):
self.cmd, arglist, verifylist)
def test_flavor_unset_with_unexist_flavor(self):
- self.flavors_mock.get.side_effect = exceptions.NotFound(None)
- self.flavors_mock.find.side_effect = exceptions.NotFound(None)
+ self.sdk_client.find_flavor.side_effect = [
+ sdk_exceptions.ResourceNotFound
+ ]
arglist = [
'--project', self.project.id,
@@ -968,4 +1110,4 @@ class TestFlavorUnset(TestFlavor):
result = self.cmd.take_action(parsed_args)
self.assertIsNone(result)
- self.flavor_access_mock.remove_tenant_access.assert_not_called()
+ self.sdk_client.flavor_remove_tenant_access.assert_not_called()
diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py
index 1f3f56f9..5a17808f 100644
--- a/openstackclient/tests/unit/compute/v2/test_keypair.py
+++ b/openstackclient/tests/unit/compute/v2/test_keypair.py
@@ -13,15 +13,19 @@
# under the License.
#
+import copy
from unittest import mock
from unittest.mock import call
import uuid
+from novaclient import api_versions
+from openstack import utils as sdk_utils
from osc_lib import exceptions
-from osc_lib import utils
from openstackclient.compute.v2 import keypair
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit import fakes
+from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests.unit import utils as tests_utils
@@ -30,9 +34,22 @@ class TestKeypair(compute_fakes.TestComputev2):
def setUp(self):
super(TestKeypair, self).setUp()
- # Get a shortcut to the KeypairManager Mock
- self.keypairs_mock = self.app.client_manager.compute.keypairs
- self.keypairs_mock.reset_mock()
+ # Initialize the user mock
+ self.users_mock = self.app.client_manager.identity.users
+ self.users_mock.reset_mock()
+ self.users_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.USER),
+ loaded=True,
+ )
+
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.app.client_manager.sdk_connection.compute = mock.Mock()
+ self.sdk_client = self.app.client_manager.sdk_connection.compute
+ self.sdk_client.keypairs = mock.Mock()
+ self.sdk_client.create_keypair = mock.Mock()
+ self.sdk_client.delete_keypair = mock.Mock()
+ self.sdk_client.find_keypair = mock.Mock()
class TestKeypairCreate(TestKeypair):
@@ -45,18 +62,20 @@ class TestKeypairCreate(TestKeypair):
self.columns = (
'fingerprint',
'name',
+ 'type',
'user_id'
)
self.data = (
self.keypair.fingerprint,
self.keypair.name,
+ self.keypair.type,
self.keypair.user_id
)
# Get the command object to test
self.cmd = keypair.CreateKeypair(self.app, None)
- self.keypairs_mock.create.return_value = self.keypair
+ self.sdk_client.create_keypair.return_value = self.keypair
def test_key_pair_create_no_options(self):
@@ -70,9 +89,8 @@ class TestKeypairCreate(TestKeypair):
columns, data = self.cmd.take_action(parsed_args)
- self.keypairs_mock.create.assert_called_with(
- self.keypair.name,
- public_key=None
+ self.sdk_client.create_keypair.assert_called_with(
+ name=self.keypair.name
)
self.assertEqual({}, columns)
@@ -82,11 +100,12 @@ class TestKeypairCreate(TestKeypair):
# overwrite the setup one because we want to omit private_key
self.keypair = compute_fakes.FakeKeypair.create_one_keypair(
no_pri=True)
- self.keypairs_mock.create.return_value = self.keypair
+ self.sdk_client.create_keypair.return_value = self.keypair
self.data = (
self.keypair.fingerprint,
self.keypair.name,
+ self.keypair.type,
self.keypair.user_id
)
@@ -96,7 +115,7 @@ class TestKeypairCreate(TestKeypair):
]
verifylist = [
('public_key', self.keypair.public_key),
- ('name', self.keypair.name)
+ ('name', self.keypair.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -108,9 +127,9 @@ class TestKeypairCreate(TestKeypair):
columns, data = self.cmd.take_action(parsed_args)
- self.keypairs_mock.create.assert_called_with(
- self.keypair.name,
- public_key=self.keypair.public_key
+ self.sdk_client.create_keypair.assert_called_with(
+ name=self.keypair.name,
+ public_key=self.keypair.public_key,
)
self.assertEqual(self.columns, columns)
@@ -124,7 +143,7 @@ class TestKeypairCreate(TestKeypair):
]
verifylist = [
('private_key', tmp_pk_file),
- ('name', self.keypair.name)
+ ('name', self.keypair.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -135,9 +154,8 @@ class TestKeypairCreate(TestKeypair):
columns, data = self.cmd.take_action(parsed_args)
- self.keypairs_mock.create.assert_called_with(
- self.keypair.name,
- public_key=None
+ self.sdk_client.create_keypair.assert_called_with(
+ name=self.keypair.name,
)
mock_open.assert_called_once_with(tmp_pk_file, 'w+')
@@ -146,6 +164,116 @@ class TestKeypairCreate(TestKeypair):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_keypair_create_with_key_type(self, sm_mock):
+ for key_type in ['x509', 'ssh']:
+ self.keypair = compute_fakes.FakeKeypair.create_one_keypair(
+ no_pri=True)
+ self.sdk_client.create_keypair.return_value = self.keypair
+
+ self.data = (
+ self.keypair.fingerprint,
+ self.keypair.name,
+ self.keypair.type,
+ self.keypair.user_id,
+ )
+ arglist = [
+ '--public-key', self.keypair.public_key,
+ self.keypair.name,
+ '--type', key_type,
+ ]
+ verifylist = [
+ ('public_key', self.keypair.public_key),
+ ('name', self.keypair.name),
+ ('type', key_type),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ with mock.patch('io.open') as mock_open:
+ mock_open.return_value = mock.MagicMock()
+ m_file = mock_open.return_value.__enter__.return_value
+ m_file.read.return_value = 'dummy'
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.sdk_client.create_keypair.assert_called_with(
+ name=self.keypair.name,
+ public_key=self.keypair.public_key,
+ key_type=key_type,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_keypair_create_with_key_type_pre_v22(self, sm_mock):
+ for key_type in ['x509', 'ssh']:
+ arglist = [
+ '--public-key', self.keypair.public_key,
+ self.keypair.name,
+ '--type', 'ssh',
+ ]
+ verifylist = [
+ ('public_key', self.keypair.public_key),
+ ('name', self.keypair.name),
+ ('type', 'ssh'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ with mock.patch('io.open') as mock_open:
+ mock_open.return_value = mock.MagicMock()
+ m_file = mock_open.return_value.__enter__.return_value
+ m_file.read.return_value = 'dummy'
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
+ self.assertIn(
+ '--os-compute-api-version 2.2 or greater is required',
+ str(ex))
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_key_pair_create_with_user(self, sm_mock):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ self.keypair.name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('name', self.keypair.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.sdk_client.create_keypair.assert_called_with(
+ name=self.keypair.name,
+ user_id=identity_fakes.user_id,
+ )
+
+ self.assertEqual({}, columns)
+ self.assertEqual({}, data)
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_key_pair_create_with_user_pre_v210(self, sm_mock):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ self.keypair.name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('name', self.keypair.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.10 or greater is required', str(ex))
+
class TestKeypairDelete(TestKeypair):
@@ -154,10 +282,6 @@ class TestKeypairDelete(TestKeypair):
def setUp(self):
super(TestKeypairDelete, self).setUp()
- self.keypairs_mock.get = compute_fakes.FakeKeypair.get_keypairs(
- self.keypairs)
- self.keypairs_mock.delete.return_value = None
-
self.cmd = keypair.DeleteKeypair(self.app, None)
def test_keypair_delete(self):
@@ -173,7 +297,8 @@ class TestKeypairDelete(TestKeypair):
ret = self.cmd.take_action(parsed_args)
self.assertIsNone(ret)
- self.keypairs_mock.delete.assert_called_with(self.keypairs[0].name)
+ self.sdk_client.delete_keypair.assert_called_with(
+ self.keypairs[0].name, ignore_missing=False)
def test_delete_multiple_keypairs(self):
arglist = []
@@ -188,8 +313,8 @@ class TestKeypairDelete(TestKeypair):
calls = []
for k in self.keypairs:
- calls.append(call(k.name))
- self.keypairs_mock.delete.assert_has_calls(calls)
+ calls.append(call(k.name, ignore_missing=False))
+ self.sdk_client.delete_keypair.assert_has_calls(calls)
self.assertIsNone(result)
def test_delete_multiple_keypairs_with_exception(self):
@@ -203,52 +328,81 @@ class TestKeypairDelete(TestKeypair):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- find_mock_result = [self.keypairs[0], exceptions.CommandError]
- with mock.patch.object(utils, 'find_resource',
- side_effect=find_mock_result) as find_mock:
- try:
- self.cmd.take_action(parsed_args)
- self.fail('CommandError should be raised.')
- except exceptions.CommandError as e:
- self.assertEqual('1 of 2 keys failed to delete.', str(e))
-
- find_mock.assert_any_call(
- self.keypairs_mock, self.keypairs[0].name)
- find_mock.assert_any_call(self.keypairs_mock, 'unexist_keypair')
-
- self.assertEqual(2, find_mock.call_count)
- self.keypairs_mock.delete.assert_called_once_with(
- self.keypairs[0].name
- )
+ self.sdk_client.delete_keypair.side_effect = [
+ None, exceptions.CommandError]
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual('1 of 2 keys failed to delete.', str(e))
+ calls = []
+ for k in arglist:
+ calls.append(call(k, ignore_missing=False))
+ self.sdk_client.delete_keypair.assert_has_calls(calls)
-class TestKeypairList(TestKeypair):
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_keypair_delete_with_user(self, sm_mock):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ self.keypairs[0].name
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('name', [self.keypairs[0].name]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- # Return value of self.keypairs_mock.list().
- keypairs = compute_fakes.FakeKeypair.create_keypairs(count=1)
+ ret = self.cmd.take_action(parsed_args)
+
+ self.assertIsNone(ret)
+ self.sdk_client.delete_keypair.assert_called_with(
+ self.keypairs[0].name,
+ user_id=identity_fakes.user_id,
+ ignore_missing=False
+ )
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_keypair_delete_with_user_pre_v210(self, sm_mock):
+
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.9')
+
+ arglist = [
+ '--user', identity_fakes.user_name,
+ self.keypairs[0].name
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('name', [self.keypairs[0].name]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- columns = (
- "Name",
- "Fingerprint"
- )
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.10 or greater is required', str(ex))
- data = ((
- keypairs[0].name,
- keypairs[0].fingerprint
- ), )
+
+class TestKeypairList(TestKeypair):
+
+ # Return value of self.sdk_client.keypairs().
+ keypairs = compute_fakes.FakeKeypair.create_keypairs(count=1)
def setUp(self):
super(TestKeypairList, self).setUp()
- self.keypairs_mock.list.return_value = self.keypairs
+ self.sdk_client.keypairs.return_value = self.keypairs
# Get the command object to test
self.cmd = keypair.ListKeypair(self.app, None)
- def test_keypair_list_no_options(self):
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_keypair_list_no_options(self, sm_mock):
arglist = []
- verifylist = [
- ]
+ verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -259,10 +413,161 @@ class TestKeypairList(TestKeypair):
# Set expected values
- self.keypairs_mock.list.assert_called_with()
+ self.sdk_client.keypairs.assert_called_with()
- self.assertEqual(self.columns, columns)
- self.assertEqual(tuple(self.data), tuple(data))
+ self.assertEqual(('Name', 'Fingerprint'), columns)
+ self.assertEqual(
+ ((self.keypairs[0].name, self.keypairs[0].fingerprint), ),
+ tuple(data)
+ )
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_keypair_list_v22(self, sm_mock):
+ 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)
+
+ # Set expected values
+
+ self.sdk_client.keypairs.assert_called_with()
+
+ self.assertEqual(('Name', 'Fingerprint', 'Type'), columns)
+ self.assertEqual(
+ ((
+ self.keypairs[0].name,
+ self.keypairs[0].fingerprint,
+ self.keypairs[0].type,
+ ), ),
+ tuple(data)
+ )
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_keypair_list_with_user(self, sm_mock):
+
+ users_mock = self.app.client_manager.identity.users
+ users_mock.reset_mock()
+ users_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.USER),
+ loaded=True,
+ )
+
+ arglist = [
+ '--user', identity_fakes.user_name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ users_mock.get.assert_called_with(identity_fakes.user_name)
+ self.sdk_client.keypairs.assert_called_with(
+ user_id=identity_fakes.user_id,
+ )
+
+ self.assertEqual(('Name', 'Fingerprint', 'Type'), columns)
+ self.assertEqual(
+ ((
+ self.keypairs[0].name,
+ self.keypairs[0].fingerprint,
+ self.keypairs[0].type,
+ ), ),
+ tuple(data)
+ )
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_keypair_list_with_user_pre_v210(self, sm_mock):
+
+ arglist = [
+ '--user', identity_fakes.user_name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.10 or greater is required', str(ex))
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_keypair_list_with_project(self, sm_mock):
+
+ projects_mock = self.app.client_manager.identity.tenants
+ projects_mock.reset_mock()
+ projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
+
+ users_mock = self.app.client_manager.identity.users
+ users_mock.reset_mock()
+ users_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.USER),
+ loaded=True,
+ ),
+ ]
+
+ arglist = ['--project', identity_fakes.project_name]
+ verifylist = [('project', identity_fakes.project_name)]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ projects_mock.get.assert_called_with(identity_fakes.project_name)
+ users_mock.list.assert_called_with(tenant_id=identity_fakes.project_id)
+ self.sdk_client.keypairs.assert_called_with(
+ user_id=identity_fakes.user_id,
+ )
+
+ self.assertEqual(('Name', 'Fingerprint', 'Type'), columns)
+ self.assertEqual(
+ ((
+ self.keypairs[0].name,
+ self.keypairs[0].fingerprint,
+ self.keypairs[0].type,
+ ), ),
+ tuple(data)
+ )
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_keypair_list_with_project_pre_v210(self, sm_mock):
+
+ arglist = ['--project', identity_fakes.project_name]
+ verifylist = [('project', identity_fakes.project_name)]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.10 or greater is required', str(ex))
+
+ def test_keypair_list_conflicting_user_options(self):
+
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--project', identity_fakes.project_name,
+ ]
+
+ self.assertRaises(
+ tests_utils.ParserException,
+ self.check_parser, self.cmd, arglist, None)
class TestKeypairShow(TestKeypair):
@@ -272,23 +577,25 @@ class TestKeypairShow(TestKeypair):
def setUp(self):
super(TestKeypairShow, self).setUp()
- self.keypairs_mock.get.return_value = self.keypair
+ self.sdk_client.find_keypair.return_value = self.keypair
self.cmd = keypair.ShowKeypair(self.app, None)
self.columns = (
"fingerprint",
"name",
+ "type",
"user_id"
)
self.data = (
self.keypair.fingerprint,
self.keypair.name,
+ self.keypair.type,
self.keypair.user_id
)
- def test_show_no_options(self):
+ def test_keypair_show_no_options(self):
arglist = []
verifylist = []
@@ -301,11 +608,12 @@ class TestKeypairShow(TestKeypair):
# overwrite the setup one because we want to omit private_key
self.keypair = compute_fakes.FakeKeypair.create_one_keypair(
no_pri=True)
- self.keypairs_mock.get.return_value = self.keypair
+ self.sdk_client.find_keypair.return_value = self.keypair
self.data = (
self.keypair.fingerprint,
self.keypair.name,
+ self.keypair.type,
self.keypair.user_id
)
@@ -315,11 +623,15 @@ class TestKeypairShow(TestKeypair):
verifylist = [
('name', self.keypair.name)
]
-
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
+ self.sdk_client.find_keypair.assert_called_with(
+ self.keypair.name,
+ ignore_missing=False
+ )
+
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
@@ -340,3 +652,60 @@ class TestKeypairShow(TestKeypair):
self.assertEqual({}, columns)
self.assertEqual({}, data)
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_keypair_show_with_user(self, sm_mock):
+
+ # overwrite the setup one because we want to omit private_key
+ self.keypair = compute_fakes.FakeKeypair.create_one_keypair(
+ no_pri=True)
+ self.sdk_client.find_keypair.return_value = self.keypair
+
+ self.data = (
+ self.keypair.fingerprint,
+ self.keypair.name,
+ self.keypair.type,
+ self.keypair.user_id
+ )
+
+ arglist = [
+ '--user', identity_fakes.user_name,
+ self.keypair.name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('name', self.keypair.name)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.users_mock.get.assert_called_with(identity_fakes.user_name)
+ self.sdk_client.find_keypair.assert_called_with(
+ self.keypair.name,
+ ignore_missing=False,
+ user_id=identity_fakes.user_id
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_keypair_show_with_user_pre_v210(self, sm_mock):
+
+ arglist = [
+ '--user', identity_fakes.user_name,
+ self.keypair.name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('name', self.keypair.name)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.10 or greater is required', str(ex))
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 7e4c71c5..dfb8df30 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -19,12 +19,11 @@ import getpass
from unittest import mock
from unittest.mock import call
+import iso8601
from novaclient import api_versions
from openstack import exceptions as sdk_exceptions
from osc_lib import exceptions
from osc_lib import utils as common_utils
-from oslo_utils import timeutils
-import six
from openstackclient.compute.v2 import server
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
@@ -43,10 +42,19 @@ class TestServer(compute_fakes.TestComputev2):
self.servers_mock = self.app.client_manager.compute.servers
self.servers_mock.reset_mock()
- # Get a shortcut to the compute client volumeManager Mock
+ # Get a shortcut to the compute client ServerMigrationsManager Mock
+ self.server_migrations_mock = \
+ self.app.client_manager.compute.server_migrations
+ self.server_migrations_mock.reset_mock()
+
+ # Get a shortcut to the compute client VolumeManager mock
self.servers_volumes_mock = self.app.client_manager.compute.volumes
self.servers_volumes_mock.reset_mock()
+ # Get a shortcut to the compute client MigrationManager mock
+ self.migrations_mock = self.app.client_manager.compute.migrations
+ self.migrations_mock.reset_mock()
+
# Get a shortcut to the compute client FlavorManager Mock
self.flavors_mock = self.app.client_manager.compute.flavors
self.flavors_mock.reset_mock()
@@ -178,6 +186,72 @@ class TestServerAddFixedIP(TestServer):
extralist = ['--fixed-ip-address', '5.6.7.8']
self._test_server_add_fixed_ip(extralist, '5.6.7.8')
+ def test_server_add_fixed_ip_with_tag(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.49')
+
+ servers = self.setup_servers_mock(count=1)
+ network = compute_fakes.FakeNetwork.create_one_network()
+ with mock.patch(
+ 'openstackclient.api.compute_v2.APIv2.network_find'
+ ) as net_mock:
+ net_mock.return_value = network
+
+ arglist = [
+ servers[0].id,
+ network['id'],
+ '--fixed-ip-address', '5.6.7.8',
+ '--tag', 'tag1',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('network', network['id']),
+ ('fixed_ip_address', '5.6.7.8'),
+ ('tag', 'tag1'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ servers[0].interface_attach.assert_called_once_with(
+ port_id=None,
+ net_id=network['id'],
+ fixed_ip='5.6.7.8',
+ tag='tag1'
+ )
+ self.assertIsNone(result)
+
+ def test_server_add_fixed_ip_with_tag_pre_v249(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.48')
+
+ servers = self.setup_servers_mock(count=1)
+ network = compute_fakes.FakeNetwork.create_one_network()
+ with mock.patch(
+ 'openstackclient.api.compute_v2.APIv2.network_find'
+ ) as net_mock:
+ net_mock.return_value = network
+
+ arglist = [
+ servers[0].id,
+ network['id'],
+ '--fixed-ip-address', '5.6.7.8',
+ '--tag', 'tag1',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('network', network['id']),
+ ('fixed_ip_address', '5.6.7.8'),
+ ('tag', 'tag1'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.49 or greater is required',
+ str(ex))
+
@mock.patch(
'openstackclient.api.compute_v2.APIv2.floating_ip_add'
@@ -250,7 +324,7 @@ class TestServerAddFloatingIPNetwork(
# Get the command object to test
self.cmd = server.AddFloatingIP(self.app, self.namespace)
- def test_server_add_floating_ip_default(self):
+ def test_server_add_floating_ip(self):
_server = compute_fakes.FakeServer.create_one_server()
self.servers_mock.get.return_value = _server
_port = network_fakes.FakePort.create_one_port()
@@ -285,8 +359,41 @@ class TestServerAddFloatingIPNetwork(
**attrs
)
- def test_server_add_floating_ip_default_no_external_gateway(self,
- success=False):
+ def test_server_add_floating_ip_no_ports(self):
+ server = compute_fakes.FakeServer.create_one_server()
+ floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip()
+
+ self.servers_mock.get.return_value = server
+ self.network.find_ip = mock.Mock(return_value=floating_ip)
+ self.network.ports = mock.Mock(return_value=[])
+
+ arglist = [
+ server.id,
+ floating_ip['floating_ip_address'],
+ ]
+ verifylist = [
+ ('server', server.id),
+ ('ip_address', floating_ip['floating_ip_address']),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ 'No attached ports found to associate floating IP with',
+ str(ex))
+
+ self.network.find_ip.assert_called_once_with(
+ floating_ip['floating_ip_address'],
+ ignore_missing=False,
+ )
+ self.network.ports.assert_called_once_with(
+ device_id=server.id,
+ )
+
+ def test_server_add_floating_ip_no_external_gateway(self, success=False):
_server = compute_fakes.FakeServer.create_one_server()
self.servers_mock.get.return_value = _server
_port = network_fakes.FakePort.create_one_port()
@@ -339,11 +446,10 @@ class TestServerAddFloatingIPNetwork(
**attrs
)
- def test_server_add_floating_ip_default_one_external_gateway(self):
- self.test_server_add_floating_ip_default_no_external_gateway(
- success=True)
+ def test_server_add_floating_ip_one_external_gateway(self):
+ self.test_server_add_floating_ip_no_external_gateway(success=True)
- def test_server_add_floating_ip_fixed(self):
+ def test_server_add_floating_ip_with_fixed_ip(self):
_server = compute_fakes.FakeServer.create_one_server()
self.servers_mock.get.return_value = _server
_port = network_fakes.FakePort.create_one_port()
@@ -385,7 +491,7 @@ class TestServerAddFloatingIPNetwork(
**attrs
)
- def test_server_add_floating_ip_fixed_no_port_found(self):
+ def test_server_add_floating_ip_with_fixed_ip_no_port_found(self):
_server = compute_fakes.FakeServer.create_one_server()
self.servers_mock.get.return_value = _server
_port = network_fakes.FakePort.create_one_port()
@@ -466,6 +572,59 @@ class TestServerAddPort(TestServer):
self._test_server_add_port('fake-port')
self.find_port.assert_not_called()
+ def test_server_add_port_with_tag(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.49')
+
+ servers = self.setup_servers_mock(count=1)
+ self.find_port.return_value.id = 'fake-port'
+ arglist = [
+ servers[0].id,
+ 'fake-port',
+ '--tag', 'tag1',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('port', 'fake-port'),
+ ('tag', 'tag1'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.assertIsNone(result)
+
+ servers[0].interface_attach.assert_called_once_with(
+ port_id='fake-port',
+ net_id=None,
+ fixed_ip=None,
+ tag='tag1')
+
+ def test_server_add_port_with_tag_pre_v249(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.48')
+
+ servers = self.setup_servers_mock(count=1)
+ self.find_port.return_value.id = 'fake-port'
+ arglist = [
+ servers[0].id,
+ 'fake-port',
+ '--tag', 'tag1',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('port', 'fake-port'),
+ ('tag', 'tag1'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.49 or greater is required',
+ str(ex))
+
class TestServerVolume(TestServer):
@@ -503,8 +662,57 @@ class TestServerVolume(TestServer):
servers[0].id, self.volume.id, device='/dev/sdb')
self.assertIsNone(result)
+ def test_server_add_volume_with_tag(self):
+ # requires API 2.49 or later
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.49')
+
+ servers = self.setup_servers_mock(count=1)
+ arglist = [
+ '--device', '/dev/sdb',
+ '--tag', 'foo',
+ servers[0].id,
+ self.volume.id,
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('volume', self.volume.id),
+ ('device', '/dev/sdb'),
+ ('tag', 'foo'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_volumes_mock.create_server_volume.assert_called_once_with(
+ servers[0].id, self.volume.id, device='/dev/sdb', tag='foo')
+ self.assertIsNone(result)
+
+ def test_server_add_volume_with_tag_pre_v249(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.48')
+
+ servers = self.setup_servers_mock(count=1)
+ arglist = [
+ servers[0].id,
+ self.volume.id,
+ '--tag', 'foo',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('volume', self.volume.id),
+ ('tag', 'foo'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-class TestServerVolumeV279(TestServerVolume):
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.49 or greater is required',
+ str(ex))
def test_server_add_volume_with_enable_delete_on_termination(self):
self.app.client_manager.compute.api_version = api_versions.APIVersion(
@@ -561,7 +769,8 @@ class TestServerVolumeV279(TestServerVolume):
self.assertIsNone(result)
def test_server_add_volume_with_enable_delete_on_termination_pre_v279(
- self):
+ self,
+ ):
self.app.client_manager.compute.api_version = api_versions.APIVersion(
'2.78')
@@ -585,7 +794,8 @@ class TestServerVolumeV279(TestServerVolume):
str(ex))
def test_server_add_volume_with_disable_delete_on_termination_pre_v279(
- self):
+ self,
+ ):
self.app.client_manager.compute.api_version = api_versions.APIVersion(
'2.78')
@@ -609,7 +819,8 @@ class TestServerVolumeV279(TestServerVolume):
str(ex))
def test_server_add_volume_with_disable_and_enable_delete_on_termination(
- self):
+ self,
+ ):
self.app.client_manager.compute.api_version = api_versions.APIVersion(
'2.79')
@@ -682,6 +893,62 @@ class TestServerAddNetwork(TestServer):
self._test_server_add_network('fake-network')
self.find_network.assert_not_called()
+ def test_server_add_network_with_tag(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.49')
+
+ servers = self.setup_servers_mock(count=1)
+ self.find_network.return_value.id = 'fake-network'
+
+ arglist = [
+ servers[0].id,
+ 'fake-network',
+ '--tag', 'tag1',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('network', 'fake-network'),
+ ('tag', 'tag1'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.assertIsNone(result)
+
+ servers[0].interface_attach.assert_called_once_with(
+ port_id=None,
+ net_id='fake-network',
+ fixed_ip=None,
+ tag='tag1'
+ )
+
+ def test_server_add_network_with_tag_pre_v249(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.48')
+
+ servers = self.setup_servers_mock(count=1)
+ self.find_network.return_value.id = 'fake-network'
+
+ arglist = [
+ servers[0].id,
+ 'fake-network',
+ '--tag', 'tag1',
+ ]
+ verifylist = [
+ ('server', servers[0].id),
+ ('network', 'fake-network'),
+ ('tag', 'tag1'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.49 or greater is required',
+ str(ex))
+
@mock.patch(
'openstackclient.api.compute_v2.APIv2.security_group_find'
@@ -832,6 +1099,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics=[],
scheduler_hints={},
@@ -857,6 +1125,8 @@ class TestServerCreate(TestServer):
'--key-name', 'keyname',
'--property', 'Beta=b',
'--security-group', 'securitygroup',
+ '--use-config-drive',
+ '--password', 'passw0rd',
'--hint', 'a=b',
'--hint', 'a=c',
self.new_server.name,
@@ -868,7 +1138,8 @@ class TestServerCreate(TestServer):
('property', {'Beta': 'b'}),
('security_group', ['securitygroup']),
('hint', {'a': ['b', 'c']}),
- ('config_drive', False),
+ ('config_drive', True),
+ ('password', 'passw0rd'),
('server_name', self.new_server.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -897,10 +1168,11 @@ class TestServerCreate(TestServer):
userdata=None,
key_name='keyname',
availability_zone=None,
+ admin_pass='passw0rd',
block_device_mapping_v2=[],
nics=[],
scheduler_hints={'a': ['b', 'c']},
- config_drive=None,
+ config_drive=True,
)
# ServerManager.create(name, image, flavor, **kwargs)
self.servers_mock.create.assert_called_with(
@@ -983,6 +1255,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name='keyname',
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics=[],
scheduler_hints={},
@@ -1069,6 +1342,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics=[{'net-id': 'net1_uuid',
'v4-fixed-ip': '',
@@ -1133,6 +1407,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='auto',
scheduler_hints={},
@@ -1182,6 +1457,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='auto',
scheduler_hints={},
@@ -1227,6 +1503,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='none',
scheduler_hints={},
@@ -1392,6 +1669,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics=[],
scheduler_hints={},
@@ -1442,6 +1720,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics=[],
scheduler_hints={},
@@ -1497,6 +1776,7 @@ class TestServerCreate(TestServer):
userdata=mock_file,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics=[],
scheduler_hints={},
@@ -1543,6 +1823,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[{
'device_name': 'vda',
'uuid': self.volume.id,
@@ -1595,6 +1876,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[{
'device_name': 'vdf',
'uuid': self.volume.id,
@@ -1646,6 +1928,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[{
'device_name': 'vdf',
'uuid': self.volume.id,
@@ -1699,6 +1982,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[{
'device_name': 'vde',
'uuid': self.volume.id,
@@ -1754,6 +2038,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[{
'device_name': 'vds',
'uuid': self.snapshot.id,
@@ -1809,6 +2094,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[
{
'device_name': 'vdb',
@@ -1907,7 +2193,7 @@ class TestServerCreate(TestServer):
self.cmd.take_action, parsed_args)
# Assert it is the error we expect.
self.assertIn('--volume is not allowed with --boot-from-volume',
- six.text_type(ex))
+ str(ex))
def test_server_create_image_property(self):
arglist = [
@@ -1945,6 +2231,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='none',
meta=None,
@@ -2000,6 +2287,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='none',
meta=None,
@@ -2048,6 +2336,66 @@ class TestServerCreate(TestServer):
self.cmd.take_action,
parsed_args)
+ def test_server_create_image_property_with_image_list(self):
+ arglist = [
+ '--image-property',
+ 'owner_specified.openstack.object=image/cirros',
+ '--flavor', 'flavor1',
+ '--nic', 'none',
+ self.new_server.name,
+ ]
+
+ verifylist = [
+ ('image_property',
+ {'owner_specified.openstack.object': 'image/cirros'}),
+ ('flavor', 'flavor1'),
+ ('nic', ['none']),
+ ('server_name', self.new_server.name),
+ ]
+ # create a image_info as the side_effect of the fake image_list()
+ image_info = {
+ 'properties': {
+ 'owner_specified.openstack.object': 'image/cirros'
+ }
+ }
+
+ target_image = image_fakes.FakeImage.create_one_image(image_info)
+ another_image = image_fakes.FakeImage.create_one_image({})
+ self.images_mock.return_value = [target_image, another_image]
+
+ 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,
+ admin_pass=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,
+ target_image,
+ self.flavor,
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist(), data)
+
def test_server_create_invalid_hint(self):
# Not a key-value pair
arglist = [
@@ -2110,6 +2458,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='auto',
scheduler_hints={},
@@ -2155,6 +2504,87 @@ class TestServerCreate(TestServer):
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)
+ def test_server_create_with_tag(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.52')
+
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--tag', 'tag1',
+ '--tag', 'tag2',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('tags', ['tag1', 'tag2']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'meta': None,
+ 'files': {},
+ 'reservation_id': None,
+ 'min_count': 1,
+ 'max_count': 1,
+ 'security_groups': [],
+ 'userdata': None,
+ 'key_name': None,
+ 'availability_zone': None,
+ 'block_device_mapping_v2': [],
+ 'admin_pass': None,
+ 'nics': 'auto',
+ 'scheduler_hints': {},
+ 'config_drive': None,
+ 'tags': ['tag1', 'tag2'],
+ }
+ # ServerManager.create(name, image, flavor, **kwargs)
+ self.servers_mock.create.assert_called_with(
+ self.new_server.name,
+ self.image,
+ self.flavor,
+ **kwargs
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.datalist(), data)
+ self.assertFalse(self.images_mock.called)
+ self.assertFalse(self.flavors_mock.called)
+
+ def test_server_create_with_tag_pre_v252(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.51')
+
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--tag', 'tag1',
+ '--tag', 'tag2',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('tags', ['tag1', 'tag2']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.52 or greater is required',
+ str(ex))
+
def test_server_create_with_host_v274(self):
# Explicit host is supported for nova api version 2.74 or above
@@ -2194,6 +2624,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='auto',
scheduler_hints={},
@@ -2279,6 +2710,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='auto',
scheduler_hints={},
@@ -2366,6 +2798,7 @@ class TestServerCreate(TestServer):
userdata=None,
key_name=None,
availability_zone=None,
+ admin_pass=None,
block_device_mapping_v2=[],
nics='auto',
scheduler_hints={},
@@ -2529,6 +2962,7 @@ class TestServerList(TestServer):
super(TestServerList, self).setUp()
self.search_opts = {
+ 'availability_zone': None,
'reservation_id': None,
'ip': None,
'ip6': None,
@@ -2609,7 +3043,7 @@ class TestServerList(TestServer):
s.status,
server._format_servers_list_networks(s.networks),
# Image will be an empty string if boot-from-volume
- self.image.name if s.image else s.image,
+ self.image.name if s.image else server.IMAGE_STRING_FOR_BFV,
self.flavor.name,
))
self.data_long.append((
@@ -2622,8 +3056,8 @@ class TestServerList(TestServer):
),
server._format_servers_list_networks(s.networks),
# Image will be an empty string if boot-from-volume
- self.image.name if s.image else s.image,
- s.image['id'] if s.image else s.image,
+ self.image.name if s.image else server.IMAGE_STRING_FOR_BFV,
+ s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV,
self.flavor.name,
s.flavor['id'],
getattr(s, 'OS-EXT-AZ:availability_zone'),
@@ -2636,7 +3070,7 @@ class TestServerList(TestServer):
s.status,
server._format_servers_list_networks(s.networks),
# Image will be an empty string if boot-from-volume
- s.image['id'] if s.image else s.image,
+ s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV,
s.flavor['id']
))
@@ -2696,6 +3130,25 @@ class TestServerList(TestServer):
self.assertEqual(self.columns_long, columns)
self.assertEqual(tuple(self.data_long), tuple(data))
+ def test_server_list_column_option(self):
+ arglist = [
+ '-c', 'Project ID',
+ '-c', 'User ID',
+ '-c', 'Created At',
+ '--long'
+ ]
+ verifylist = [
+ ('long', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.list.assert_called_with(**self.kwargs)
+ self.assertIn('Project ID', columns)
+ self.assertIn('User ID', columns)
+ self.assertIn('Created At', columns)
+
def test_server_list_no_name_lookup_option(self):
arglist = [
'--no-name-lookup',
@@ -2886,7 +3339,7 @@ class TestServerList(TestServer):
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
- @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError)
+ @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError)
def test_server_list_with_invalid_changes_since(self, mock_parse_isotime):
arglist = [
@@ -2924,12 +3377,13 @@ class TestServerList(TestServer):
self.search_opts['changes-before'] = '2016-03-05T06:27:59Z'
self.search_opts['deleted'] = True
+
self.servers_mock.list.assert_called_with(**self.kwargs)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
- @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError)
+ @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError)
def test_server_list_v266_with_invalid_changes_before(
self, mock_parse_isotime):
self.app.client_manager.compute.api_version = (
@@ -3016,6 +3470,92 @@ class TestServerList(TestServer):
'UNKNOWN', '', '', '')
self.assertEqual(expected_row, partial_server)
+ def test_server_list_with_tag(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.26')
+
+ arglist = [
+ '--tag', 'tag1',
+ '--tag', 'tag2',
+ ]
+ verifylist = [
+ ('tags', ['tag1', 'tag2']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.search_opts['tags'] = ['tag1', 'tag2']
+
+ self.servers_mock.list.assert_called_with(**self.kwargs)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+ def test_server_list_with_tag_pre_v225(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.25')
+
+ arglist = [
+ '--tag', 'tag1',
+ '--tag', 'tag2',
+ ]
+ verifylist = [
+ ('tags', ['tag1', 'tag2']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.26 or greater is required',
+ str(ex))
+
+ def test_server_list_with_not_tag(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.26')
+
+ arglist = [
+ '--not-tag', 'tag1',
+ '--not-tag', 'tag2',
+ ]
+ verifylist = [
+ ('not_tags', ['tag1', 'tag2']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.search_opts['not-tags'] = ['tag1', 'tag2']
+
+ self.servers_mock.list.assert_called_with(**self.kwargs)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+ def test_server_list_with_not_tag_pre_v226(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.25')
+
+ arglist = [
+ '--not-tag', 'tag1',
+ '--not-tag', 'tag2',
+ ]
+ verifylist = [
+ ('not_tags', ['tag1', 'tag2']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.26 or greater is required',
+ str(ex))
+
class TestServerLock(TestServer):
@@ -3229,7 +3769,7 @@ class TestServerMigrate(TestServer):
# Make sure it's the error we expect.
self.assertIn('--os-compute-api-version 2.56 or greater is required '
'to use --host without --live-migration.',
- six.text_type(ex))
+ str(ex))
self.servers_mock.get.assert_called_with(self.server.id)
self.assertNotCalled(self.servers_mock.live_migrate)
@@ -3264,7 +3804,7 @@ class TestServerMigrate(TestServer):
# A warning should have been logged for using --live.
mock_warning.assert_called_once()
self.assertIn('The --live option has been deprecated.',
- six.text_type(mock_warning.call_args[0][0]))
+ str(mock_warning.call_args[0][0]))
def test_server_live_migrate_host_pre_2_30(self):
# Tests that the --host option is not supported for --live-migration
@@ -3287,7 +3827,7 @@ class TestServerMigrate(TestServer):
# Make sure it's the error we expect.
self.assertIn('--os-compute-api-version 2.30 or greater is required '
- 'when using --host', six.text_type(ex))
+ 'when using --host', str(ex))
self.servers_mock.get.assert_called_with(self.server.id)
self.assertNotCalled(self.servers_mock.live_migrate)
@@ -3377,7 +3917,7 @@ class TestServerMigrate(TestServer):
# A warning should have been logged for using --live.
mock_warning.assert_called_once()
self.assertIn('The --live option has been deprecated.',
- six.text_type(mock_warning.call_args[0][0]))
+ str(mock_warning.call_args[0][0]))
def test_server_live_migrate_live_and_host_mutex(self):
# Tests specifying both the --live and --host options which are in a
@@ -3523,6 +4063,603 @@ class TestServerMigrate(TestServer):
self.assertNotCalled(self.servers_mock.live_migrate)
+class TestListMigration(TestServer):
+ """Test fetch all migrations."""
+
+ MIGRATION_COLUMNS = [
+ 'Source Node', 'Dest Node', 'Source Compute',
+ 'Dest Compute', 'Dest Host', 'Status', 'Server UUID',
+ 'Old Flavor', 'New Flavor', 'Created At', 'Updated At'
+ ]
+
+ def setUp(self):
+ super(TestListMigration, self).setUp()
+
+ self.server = compute_fakes.FakeServer.create_one_server()
+ self.servers_mock.get.return_value = self.server
+
+ self.migrations = compute_fakes.FakeServerMigration\
+ .create_server_migrations(count=3)
+ self.migrations_mock.list.return_value = self.migrations
+
+ self.data = (common_utils.get_item_properties(
+ s, self.MIGRATION_COLUMNS) for s in self.migrations)
+
+ # Get the command object to test
+ self.cmd = server.ListMigration(self.app, None)
+
+ def test_server_migration_list_no_options(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'status': None,
+ 'host': None,
+ }
+
+ self.migrations_mock.list.assert_called_with(**kwargs)
+
+ self.assertEqual(self.MIGRATION_COLUMNS, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+ def test_server_migration_list(self):
+ arglist = [
+ '--server', 'server1',
+ '--host', 'host1',
+ '--status', 'migrating',
+ '--type', 'cold-migration',
+ ]
+ verifylist = [
+ ('server', 'server1'),
+ ('host', 'host1'),
+ ('status', 'migrating'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'status': 'migrating',
+ 'host': 'host1',
+ 'instance_uuid': self.server.id,
+ 'migration_type': 'migration',
+ }
+
+ self.servers_mock.get.assert_called_with('server1')
+ self.migrations_mock.list.assert_called_with(**kwargs)
+
+ self.assertEqual(self.MIGRATION_COLUMNS, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+
+class TestListMigrationV223(TestListMigration):
+ """Test fetch all migrations. """
+
+ MIGRATION_COLUMNS = [
+ 'Source Node', 'Dest Node', 'Source Compute',
+ 'Dest Compute', 'Dest Host', 'Status', 'Server UUID',
+ 'Old Flavor', 'New Flavor', 'Created At', 'Updated At'
+ ]
+
+ def setUp(self):
+ super(TestListMigrationV223, self).setUp()
+
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.23')
+
+ def test_server_migration_list(self):
+ arglist = [
+ '--status', 'migrating'
+ ]
+ verifylist = [
+ ('status', 'migrating')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'status': 'migrating',
+ 'host': None,
+ }
+
+ self.migrations_mock.list.assert_called_with(**kwargs)
+
+ self.MIGRATION_COLUMNS.insert(0, "Id")
+ self.MIGRATION_COLUMNS.insert(
+ len(self.MIGRATION_COLUMNS) - 2, 'Type')
+ self.assertEqual(self.MIGRATION_COLUMNS, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+
+class TestListMigrationV259(TestListMigration):
+ """Test fetch all migrations. """
+
+ MIGRATION_COLUMNS = [
+ 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute',
+ 'Dest Compute', 'Dest Host', 'Status', 'Server UUID',
+ 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At'
+ ]
+
+ def setUp(self):
+ super(TestListMigrationV259, self).setUp()
+
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.59')
+
+ def test_server_migration_list(self):
+ arglist = [
+ '--status', 'migrating',
+ '--limit', '1',
+ '--marker', 'test_kp',
+ '--changes-since', '2019-08-09T08:03:25Z'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('limit', 1),
+ ('marker', 'test_kp'),
+ ('changes_since', '2019-08-09T08:03:25Z')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'status': 'migrating',
+ 'limit': 1,
+ 'marker': 'test_kp',
+ 'host': None,
+ 'changes_since': '2019-08-09T08:03:25Z',
+ }
+
+ self.migrations_mock.list.assert_called_with(**kwargs)
+
+ self.assertEqual(self.MIGRATION_COLUMNS, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+ def test_server_migration_list_with_limit_pre_v259(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.58')
+ arglist = [
+ '--status', 'migrating',
+ '--limit', '1'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('limit', 1)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.59 or greater is required',
+ str(ex))
+
+ def test_server_migration_list_with_marker_pre_v259(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.58')
+ arglist = [
+ '--status', 'migrating',
+ '--marker', 'test_kp'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('marker', 'test_kp')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.59 or greater is required',
+ str(ex))
+
+ def test_server_migration_list_with_changes_since_pre_v259(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.58')
+ arglist = [
+ '--status', 'migrating',
+ '--changes-since', '2019-08-09T08:03:25Z'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('changes_since', '2019-08-09T08:03:25Z')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.59 or greater is required',
+ str(ex))
+
+
+class TestListMigrationV266(TestListMigration):
+ """Test fetch all migrations by changes-before. """
+
+ MIGRATION_COLUMNS = [
+ 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute',
+ 'Dest Compute', 'Dest Host', 'Status', 'Server UUID',
+ 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At'
+ ]
+
+ def setUp(self):
+ super(TestListMigrationV266, self).setUp()
+
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.66')
+
+ def test_server_migration_list_with_changes_before(self):
+ arglist = [
+ '--status', 'migrating',
+ '--limit', '1',
+ '--marker', 'test_kp',
+ '--changes-since', '2019-08-07T08:03:25Z',
+ '--changes-before', '2019-08-09T08:03:25Z'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('limit', 1),
+ ('marker', 'test_kp'),
+ ('changes_since', '2019-08-07T08:03:25Z'),
+ ('changes_before', '2019-08-09T08:03:25Z')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'status': 'migrating',
+ 'limit': 1,
+ 'marker': 'test_kp',
+ 'host': None,
+ 'changes_since': '2019-08-07T08:03:25Z',
+ 'changes_before': '2019-08-09T08:03:25Z',
+ }
+
+ self.migrations_mock.list.assert_called_with(**kwargs)
+
+ self.assertEqual(self.MIGRATION_COLUMNS, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+ def test_server_migration_list_with_changes_before_pre_v266(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.65')
+ arglist = [
+ '--status', 'migrating',
+ '--changes-before', '2019-08-09T08:03:25Z'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('changes_before', '2019-08-09T08:03:25Z')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.66 or greater is required',
+ str(ex))
+
+
+class TestListMigrationV280(TestListMigration):
+ """Test fetch all migrations by user-id and/or project-id. """
+
+ MIGRATION_COLUMNS = [
+ 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute',
+ 'Dest Compute', 'Dest Host', 'Status', 'Server UUID',
+ 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At'
+ ]
+
+ def setUp(self):
+ super(TestListMigrationV280, self).setUp()
+
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.80')
+
+ def test_server_migration_list_with_project(self):
+ arglist = [
+ '--status', 'migrating',
+ '--limit', '1',
+ '--marker', 'test_kp',
+ '--changes-since', '2019-08-07T08:03:25Z',
+ '--changes-before', '2019-08-09T08:03:25Z',
+ '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('limit', 1),
+ ('marker', 'test_kp'),
+ ('changes_since', '2019-08-07T08:03:25Z'),
+ ('changes_before', '2019-08-09T08:03:25Z'),
+ ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'status': 'migrating',
+ 'limit': 1,
+ 'marker': 'test_kp',
+ 'host': None,
+ 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3',
+ 'changes_since': '2019-08-07T08:03:25Z',
+ 'changes_before': "2019-08-09T08:03:25Z",
+ }
+
+ self.migrations_mock.list.assert_called_with(**kwargs)
+
+ self.MIGRATION_COLUMNS.insert(
+ len(self.MIGRATION_COLUMNS) - 2, "Project")
+ self.assertEqual(self.MIGRATION_COLUMNS, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+ # Clean up global variables MIGRATION_COLUMNS
+ self.MIGRATION_COLUMNS.remove('Project')
+
+ def test_get_migrations_with_project_pre_v280(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.79')
+ arglist = [
+ '--status', 'migrating',
+ '--changes-before', '2019-08-09T08:03:25Z',
+ '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('changes_before', '2019-08-09T08:03:25Z'),
+ ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.80 or greater is required',
+ str(ex))
+
+ def test_server_migration_list_with_user(self):
+ arglist = [
+ '--status', 'migrating',
+ '--limit', '1',
+ '--marker', 'test_kp',
+ '--changes-since', '2019-08-07T08:03:25Z',
+ '--changes-before', '2019-08-09T08:03:25Z',
+ '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('limit', 1),
+ ('marker', 'test_kp'),
+ ('changes_since', '2019-08-07T08:03:25Z'),
+ ('changes_before', '2019-08-09T08:03:25Z'),
+ ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'status': 'migrating',
+ 'limit': 1,
+ 'marker': 'test_kp',
+ 'host': None,
+ 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6',
+ 'changes_since': '2019-08-07T08:03:25Z',
+ 'changes_before': "2019-08-09T08:03:25Z",
+ }
+
+ self.migrations_mock.list.assert_called_with(**kwargs)
+
+ self.MIGRATION_COLUMNS.insert(
+ len(self.MIGRATION_COLUMNS) - 2, "User")
+ self.assertEqual(self.MIGRATION_COLUMNS, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+ # Clean up global variables MIGRATION_COLUMNS
+ self.MIGRATION_COLUMNS.remove('User')
+
+ def test_get_migrations_with_user_pre_v280(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.79')
+ arglist = [
+ '--status', 'migrating',
+ '--changes-before', '2019-08-09T08:03:25Z',
+ '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('changes_before', '2019-08-09T08:03:25Z'),
+ ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.80 or greater is required',
+ str(ex))
+
+ def test_server_migration_list_with_project_and_user(self):
+ arglist = [
+ '--status', 'migrating',
+ '--limit', '1',
+ '--changes-since', '2019-08-07T08:03:25Z',
+ '--changes-before', '2019-08-09T08:03:25Z',
+ '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3',
+ '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('limit', 1),
+ ('changes_since', '2019-08-07T08:03:25Z'),
+ ('changes_before', '2019-08-09T08:03:25Z'),
+ ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3'),
+ ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'status': 'migrating',
+ 'limit': 1,
+ 'host': None,
+ 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3',
+ 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6',
+ 'changes_since': '2019-08-07T08:03:25Z',
+ 'changes_before': "2019-08-09T08:03:25Z",
+ }
+
+ self.migrations_mock.list.assert_called_with(**kwargs)
+
+ self.MIGRATION_COLUMNS.insert(
+ len(self.MIGRATION_COLUMNS) - 2, "Project")
+ self.MIGRATION_COLUMNS.insert(
+ len(self.MIGRATION_COLUMNS) - 2, "User")
+ self.assertEqual(self.MIGRATION_COLUMNS, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+ # Clean up global variables MIGRATION_COLUMNS
+ self.MIGRATION_COLUMNS.remove('Project')
+ self.MIGRATION_COLUMNS.remove('User')
+
+ def test_get_migrations_with_project_and_user_pre_v280(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.79')
+ arglist = [
+ '--status', 'migrating',
+ '--changes-before', '2019-08-09T08:03:25Z',
+ '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3',
+ '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6'
+ ]
+ verifylist = [
+ ('status', 'migrating'),
+ ('changes_before', '2019-08-09T08:03:25Z'),
+ ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3'),
+ ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6')
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.80 or greater is required',
+ str(ex))
+
+
+class TestServerMigrationAbort(TestServer):
+
+ def setUp(self):
+ super(TestServerMigrationAbort, self).setUp()
+
+ self.server = compute_fakes.FakeServer.create_one_server()
+
+ # Return value for utils.find_resource for server.
+ self.servers_mock.get.return_value = self.server
+
+ # Get the command object to test
+ self.cmd = server.AbortMigration(self.app, None)
+
+ def test_migration_abort(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.24')
+
+ arglist = [
+ self.server.id,
+ '2', # arbitrary migration ID
+ ]
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server_migrations_mock.live_migration_abort.assert_called_with(
+ self.server.id, '2',)
+ self.assertIsNone(result)
+
+ def test_migration_abort_pre_v224(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.23')
+
+ arglist = [
+ self.server.id,
+ '2', # arbitrary migration ID
+ ]
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.24 or greater is required',
+ str(ex))
+
+
+class TestServerMigrationForceComplete(TestServer):
+
+ def setUp(self):
+ super(TestServerMigrationForceComplete, self).setUp()
+
+ self.server = compute_fakes.FakeServer.create_one_server()
+
+ # Return value for utils.find_resource for server.
+ self.servers_mock.get.return_value = self.server
+
+ # Get the command object to test
+ self.cmd = server.ForceCompleteMigration(self.app, None)
+
+ def test_migration_force_complete(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.22')
+
+ arglist = [
+ self.server.id,
+ '2', # arbitrary migration ID
+ ]
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server_migrations_mock.live_migrate_force_complete\
+ .assert_called_with(self.server.id, '2',)
+ self.assertIsNone(result)
+
+ def test_migration_force_complete_pre_v222(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.21')
+
+ arglist = [
+ self.server.id,
+ '2', # arbitrary migration ID
+ ]
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.22 or greater is required',
+ str(ex))
+
+
class TestServerPause(TestServer):
def setUp(self):
@@ -3861,6 +4998,167 @@ class TestServerRebuild(TestServer):
self.cmd, arglist, verifylist)
+class TestEvacuateServer(TestServer):
+
+ def setUp(self):
+ super(TestEvacuateServer, self).setUp()
+ # Return value for utils.find_resource for image
+ self.image = image_fakes.FakeImage.create_one_image()
+ self.images_mock.get.return_value = self.image
+
+ # Fake the rebuilt new server.
+ attrs = {
+ '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 = {
+ 'evacuate': new_server,
+ }
+ self.server = compute_fakes.FakeServer.create_one_server(
+ attrs=attrs,
+ methods=methods
+ )
+
+ # Return value for utils.find_resource for server.
+ self.servers_mock.get.return_value = self.server
+
+ self.cmd = server.EvacuateServer(self.app, None)
+
+ def _test_evacuate(self, args, verify_args, evac_args):
+ parsed_args = self.check_parser(self.cmd, args, verify_args)
+
+ # Get the command object to test
+ self.cmd.take_action(parsed_args)
+
+ self.servers_mock.get.assert_called_with(self.server.id)
+ self.server.evacuate.assert_called_with(**evac_args)
+
+ def test_evacuate(self):
+ args = [
+ self.server.id,
+ ]
+ verify_args = [
+ ('server', self.server.id),
+ ]
+ evac_args = {
+ 'host': None, 'on_shared_storage': False, 'password': None,
+ }
+ self._test_evacuate(args, verify_args, evac_args)
+
+ def test_evacuate_with_password(self):
+ args = [
+ self.server.id,
+ '--password', 'password',
+ ]
+ verify_args = [
+ ('server', self.server.id),
+ ('password', 'password'),
+ ]
+ evac_args = {
+ 'host': None, 'on_shared_storage': False, 'password': 'password',
+ }
+ self._test_evacuate(args, verify_args, evac_args)
+
+ def test_evacuate_with_host(self):
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.29')
+
+ host = 'target-host'
+ args = [
+ self.server.id,
+ '--host', 'target-host',
+ ]
+ verify_args = [
+ ('server', self.server.id),
+ ('host', 'target-host'),
+ ]
+ evac_args = {'host': host, 'password': None}
+
+ self._test_evacuate(args, verify_args, evac_args)
+
+ def test_evacuate_with_host_pre_v229(self):
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.28')
+
+ args = [
+ self.server.id,
+ '--host', 'target-host',
+ ]
+ verify_args = [
+ ('server', self.server.id),
+ ('host', 'target-host'),
+ ]
+ parsed_args = self.check_parser(self.cmd, args, verify_args)
+
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
+ def test_evacuate_without_share_storage(self):
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.13')
+
+ args = [
+ self.server.id,
+ '--shared-storage'
+ ]
+ verify_args = [
+ ('server', self.server.id),
+ ('shared_storage', True),
+ ]
+ evac_args = {
+ 'host': None, 'on_shared_storage': True, 'password': None,
+ }
+ self._test_evacuate(args, verify_args, evac_args)
+
+ def test_evacuate_without_share_storage_post_v213(self):
+ self.app.client_manager.compute.api_version = \
+ api_versions.APIVersion('2.14')
+
+ args = [
+ self.server.id,
+ '--shared-storage'
+ ]
+ verify_args = [
+ ('server', self.server.id),
+ ('shared_storage', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, args, verify_args)
+
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
+ @mock.patch.object(common_utils, 'wait_for_status', return_value=True)
+ def test_evacuate_with_wait_ok(self, mock_wait_for_status):
+ args = [
+ self.server.id,
+ '--wait',
+ ]
+ verify_args = [
+ ('server', self.server.id),
+ ('wait', True),
+ ]
+ evac_args = {
+ 'host': None, 'on_shared_storage': False, 'password': None,
+ }
+ self._test_evacuate(args, verify_args, evac_args)
+ mock_wait_for_status.assert_called_once_with(
+ self.servers_mock.get,
+ self.server.id,
+ callback=mock.ANY,
+ )
+
+
class TestServerRemoveFixedIP(TestServer):
def setUp(self):
@@ -4293,7 +5591,7 @@ class TestServerResize(TestServer):
# A warning should have been logged for using --confirm.
mock_warning.assert_called_once()
self.assertIn('The --confirm option has been deprecated.',
- six.text_type(mock_warning.call_args[0][0]))
+ str(mock_warning.call_args[0][0]))
def test_server_resize_revert(self):
arglist = [
@@ -4318,7 +5616,7 @@ class TestServerResize(TestServer):
# A warning should have been logged for using --revert.
mock_warning.assert_called_once()
self.assertIn('The --revert option has been deprecated.',
- six.text_type(mock_warning.call_args[0][0]))
+ str(mock_warning.call_args[0][0]))
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
def test_server_resize_with_wait_ok(self, mock_wait_for_status):
@@ -4520,6 +5818,8 @@ class TestServerSet(TestServer):
'update': None,
'reset_state': None,
'change_password': None,
+ 'add_tag': None,
+ 'set_tags': None,
}
self.fake_servers = self.setup_servers_mock(2)
@@ -4660,6 +5960,50 @@ class TestServerSet(TestServer):
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)
+ def test_server_set_with_tag(self):
+ self.fake_servers[0].api_version = api_versions.APIVersion('2.26')
+
+ arglist = [
+ '--tag', 'tag1',
+ '--tag', 'tag2',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('tags', ['tag1', 'tag2']),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.fake_servers[0].add_tag.assert_has_calls([
+ mock.call(tag='tag1'),
+ mock.call(tag='tag2'),
+ ])
+ self.assertIsNone(result)
+
+ def test_server_set_with_tag_pre_v226(self):
+ self.fake_servers[0].api_version = api_versions.APIVersion('2.25')
+
+ arglist = [
+ '--tag', 'tag1',
+ '--tag', 'tag2',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('tags', ['tag1', 'tag2']),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.26 or greater is required',
+ str(ex))
+
class TestServerShelve(TestServer):
@@ -4985,6 +6329,52 @@ class TestServerUnset(TestServer):
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args)
+ def test_server_unset_with_tag(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.26')
+
+ arglist = [
+ '--tag', 'tag1',
+ '--tag', 'tag2',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('tags', ['tag1', 'tag2']),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+ self.assertIsNone(result)
+
+ self.servers_mock.delete_tag.assert_has_calls([
+ mock.call(self.fake_server, tag='tag1'),
+ mock.call(self.fake_server, tag='tag2'),
+ ])
+
+ def test_server_unset_with_tag_pre_v226(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.25')
+
+ arglist = [
+ '--tag', 'tag1',
+ '--tag', 'tag2',
+ 'foo_vm',
+ ]
+ verifylist = [
+ ('tags', ['tag1', 'tag2']),
+ ('server', 'foo_vm'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.26 or greater is required',
+ str(ex))
+
class TestServerUnshelve(TestServer):
@@ -5166,6 +6556,8 @@ class TestServerGeneral(TestServer):
'tenant_id': u'tenant-id-xxx',
'networks': {u'public': [u'10.20.30.40', u'2001:db8::f']},
'links': u'http://xxx.yyy.com',
+ 'properties': '',
+ 'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}],
}
_server = compute_fakes.FakeServer.create_one_server(attrs=server_info)
find_resource.side_effect = [_server, _flavor]
@@ -5182,6 +6574,7 @@ class TestServerGeneral(TestServer):
'properties': '',
'OS-EXT-STS:power_state': server._format_servers_list_power_state(
getattr(_server, 'OS-EXT-STS:power_state')),
+ 'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}],
}
# Call _prep_server_detail().
diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py
index 5cdc2080..753db9cd 100644
--- a/openstackclient/tests/unit/compute/v2/test_server_backup.py
+++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py
@@ -139,7 +139,7 @@ class TestServerBackupCreate(TestServerBackup):
)
self.assertEqual(self.image_columns(images[0]), columns)
- self.assertItemEqual(self.image_data(images[0]), data)
+ self.assertItemsEqual(self.image_data(images[0]), data)
def test_server_backup_create_options(self):
servers = self.setup_servers_mock(count=1)
@@ -173,7 +173,7 @@ class TestServerBackupCreate(TestServerBackup):
)
self.assertEqual(self.image_columns(images[0]), columns)
- self.assertItemEqual(self.image_data(images[0]), data)
+ self.assertItemsEqual(self.image_data(images[0]), data)
@mock.patch.object(common_utils, 'wait_for_status', return_value=False)
def test_server_backup_wait_fail(self, mock_wait_for_status):
@@ -269,4 +269,4 @@ class TestServerBackupCreate(TestServerBackup):
)
self.assertEqual(self.image_columns(images[0]), columns)
- self.assertItemEqual(self.image_data(images[0]), data)
+ self.assertItemsEqual(self.image_data(images[0]), data)
diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py
index 9cd876ea..bf0ea0ba 100644
--- a/openstackclient/tests/unit/compute/v2/test_server_group.py
+++ b/openstackclient/tests/unit/compute/v2/test_server_group.py
@@ -15,6 +15,7 @@
from unittest import mock
+from novaclient import api_versions
from osc_lib import exceptions
from osc_lib import utils
@@ -53,6 +54,33 @@ class TestServerGroup(compute_fakes.TestComputev2):
self.server_groups_mock.reset_mock()
+class TestServerGroupV264(TestServerGroup):
+
+ fake_server_group = \
+ compute_fakes.FakeServerGroupV264.create_one_server_group()
+
+ columns = (
+ 'id',
+ 'members',
+ 'name',
+ 'policy',
+ 'project_id',
+ 'user_id',
+ )
+
+ data = (
+ fake_server_group.id,
+ utils.format_list(fake_server_group.members),
+ fake_server_group.name,
+ fake_server_group.policy,
+ fake_server_group.project_id,
+ fake_server_group.user_id,
+ )
+
+ def setUp(self):
+ super(TestServerGroupV264, self).setUp()
+
+
class TestServerGroupCreate(TestServerGroup):
def setUp(self):
@@ -63,6 +91,28 @@ class TestServerGroupCreate(TestServerGroup):
def test_server_group_create(self):
arglist = [
+ '--policy', 'anti-affinity',
+ 'affinity_group',
+ ]
+ verifylist = [
+ ('policy', 'anti-affinity'),
+ ('name', 'affinity_group'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.server_groups_mock.create.assert_called_once_with(
+ name=parsed_args.name,
+ policies=[parsed_args.policy],
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_server_group_create_with_soft_policies(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.15')
+
+ arglist = [
'--policy', 'soft-anti-affinity',
'affinity_group',
]
@@ -80,6 +130,49 @@ class TestServerGroupCreate(TestServerGroup):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_server_group_create_with_soft_policies_pre_v215(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.14')
+
+ arglist = [
+ '--policy', 'soft-anti-affinity',
+ 'affinity_group',
+ ]
+ verifylist = [
+ ('policy', 'soft-anti-affinity'),
+ ('name', 'affinity_group'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ ex = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-compute-api-version 2.15 or greater is required',
+ str(ex))
+
+ def test_server_group_create_v264(self):
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.64')
+
+ arglist = [
+ '--policy', 'soft-anti-affinity',
+ 'affinity_group',
+ ]
+ verifylist = [
+ ('policy', 'soft-anti-affinity'),
+ ('name', 'affinity_group'),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.server_groups_mock.create.assert_called_once_with(
+ name=parsed_args.name,
+ policy=parsed_args.policy,
+ )
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
class TestServerGroupDelete(TestServerGroup):
@@ -230,6 +323,76 @@ class TestServerGroupList(TestServerGroup):
self.assertEqual(self.list_data_long, tuple(data))
+class TestServerGroupListV264(TestServerGroupV264):
+
+ list_columns = (
+ 'ID',
+ 'Name',
+ 'Policy',
+ )
+
+ list_columns_long = (
+ 'ID',
+ 'Name',
+ 'Policy',
+ 'Members',
+ 'Project Id',
+ 'User Id',
+ )
+
+ list_data = ((
+ TestServerGroupV264.fake_server_group.id,
+ TestServerGroupV264.fake_server_group.name,
+ TestServerGroupV264.fake_server_group.policy,
+ ),)
+
+ list_data_long = ((
+ TestServerGroupV264.fake_server_group.id,
+ TestServerGroupV264.fake_server_group.name,
+ TestServerGroupV264.fake_server_group.policy,
+ utils.format_list(TestServerGroupV264.fake_server_group.members),
+ TestServerGroupV264.fake_server_group.project_id,
+ TestServerGroupV264.fake_server_group.user_id,
+ ),)
+
+ def setUp(self):
+ super(TestServerGroupListV264, self).setUp()
+
+ self.server_groups_mock.list.return_value = [self.fake_server_group]
+ self.cmd = server_group.ListServerGroup(self.app, None)
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.64')
+
+ def test_server_group_list(self):
+ arglist = []
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.server_groups_mock.list.assert_called_once_with(False)
+
+ self.assertEqual(self.list_columns, columns)
+ self.assertEqual(self.list_data, tuple(data))
+
+ def test_server_group_list_with_all_projects_and_long(self):
+ arglist = [
+ '--all-projects',
+ '--long',
+ ]
+ verifylist = [
+ ('all_projects', True),
+ ('long', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+ self.server_groups_mock.list.assert_called_once_with(True)
+
+ self.assertEqual(self.list_columns_long, columns)
+ self.assertEqual(self.list_data_long, tuple(data))
+
+
class TestServerGroupShow(TestServerGroup):
def setUp(self):
diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py
index 1cec5b68..06f6017c 100644
--- a/openstackclient/tests/unit/compute/v2/test_server_image.py
+++ b/openstackclient/tests/unit/compute/v2/test_server_image.py
@@ -133,7 +133,7 @@ class TestServerImageCreate(TestServerImage):
)
self.assertEqual(self.image_columns(images[0]), columns)
- self.assertItemEqual(self.image_data(images[0]), data)
+ self.assertItemsEqual(self.image_data(images[0]), data)
def test_server_image_create_options(self):
servers = self.setup_servers_mock(count=1)
@@ -161,7 +161,7 @@ class TestServerImageCreate(TestServerImage):
)
self.assertEqual(self.image_columns(images[0]), columns)
- self.assertItemEqual(self.image_data(images[0]), data)
+ self.assertItemsEqual(self.image_data(images[0]), data)
@mock.patch.object(common_utils, 'wait_for_status', return_value=False)
def test_server_create_image_wait_fail(self, mock_wait_for_status):
@@ -229,4 +229,4 @@ class TestServerImageCreate(TestServerImage):
)
self.assertEqual(self.image_columns(images[0]), columns)
- self.assertItemEqual(self.image_data(images[0]), data)
+ self.assertItemsEqual(self.image_data(images[0]), data)
diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py
index 7a036833..87e54747 100644
--- a/openstackclient/tests/unit/compute/v2/test_service.py
+++ b/openstackclient/tests/unit/compute/v2/test_service.py
@@ -18,7 +18,6 @@ from unittest.mock import call
from novaclient import api_versions
from osc_lib import exceptions
-import six
from openstackclient.compute.v2 import service
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
@@ -502,7 +501,7 @@ class TestServiceSet(TestService):
self.cmd._find_service_by_host_and_binary,
self.service_mock, 'fake-host', 'nova-compute')
self.assertIn('Compute service for host "fake-host" and binary '
- '"nova-compute" not found.', six.text_type(ex))
+ '"nova-compute" not found.', str(ex))
def test_service_set_find_service_by_host_and_binary_many_results(self):
# Tests that more than one compute service is found by host and binary.
@@ -512,4 +511,4 @@ class TestServiceSet(TestService):
self.service_mock, 'fake-host', 'nova-compute')
self.assertIn('Multiple compute services found for host "fake-host" '
'and binary "nova-compute". Unable to proceed.',
- six.text_type(ex))
+ str(ex))
diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py
index e5476f06..00e0c129 100644
--- a/openstackclient/tests/unit/fakes.py
+++ b/openstackclient/tests/unit/fakes.py
@@ -19,7 +19,6 @@ from unittest import mock
from keystoneauth1 import fixture
import requests
-import six
AUTH_TOKEN = "foobar"
@@ -253,7 +252,7 @@ class FakeResponse(requests.Response):
self.headers.update(headers)
self._content = json.dumps(data)
- if not isinstance(self._content, six.binary_type):
+ if not isinstance(self._content, bytes):
self._content = self._content.encode()
diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py
index 17355074..e2c56ba1 100644
--- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py
+++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py
@@ -71,9 +71,10 @@ class TestCatalogList(TestCatalog):
datalist = ((
'supernova',
'compute',
- catalog.EndpointsColumn(self.service_catalog['endpoints']),
+ catalog.EndpointsColumn(
+ auth_ref.service_catalog.catalog[0]['endpoints']),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_catalog_list_with_endpoint_url(self):
attr = {
@@ -113,9 +114,10 @@ class TestCatalogList(TestCatalog):
datalist = ((
'supernova',
'compute',
- catalog.EndpointsColumn(service_catalog['endpoints']),
+ catalog.EndpointsColumn(
+ auth_ref.service_catalog.catalog[0]['endpoints']),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
class TestCatalogShow(TestCatalog):
@@ -150,16 +152,17 @@ class TestCatalogShow(TestCatalog):
collist = ('endpoints', 'id', 'name', 'type')
self.assertEqual(collist, columns)
datalist = (
- catalog.EndpointsColumn(self.service_catalog['endpoints']),
+ catalog.EndpointsColumn(
+ auth_ref.service_catalog.catalog[0]['endpoints']),
self.service_catalog.id,
'supernova',
'compute',
)
- self.assertItemEqual(datalist, data)
+ self.assertItemsEqual(datalist, data)
class TestFormatColumns(TestCatalog):
- def test_endpoints_column_human_readabale(self):
+ def test_endpoints_column_human_readable(self):
col = catalog.EndpointsColumn(self.service_catalog['endpoints'])
self.assertEqual(
'one\n publicURL: https://public.one.example.com\n '
diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py
index cd8c825d..766d5dab 100644
--- a/openstackclient/tests/unit/identity/v2_0/test_project.py
+++ b/openstackclient/tests/unit/identity/v2_0/test_project.py
@@ -643,7 +643,7 @@ class TestProjectShow(TestProject):
self.fake_proj_show.name,
format_columns.DictColumn({}),
)
- self.assertItemEqual(datalist, data)
+ self.assertItemsEqual(datalist, data)
class TestProjectUnset(TestProject):
diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py
index 4308b05d..dd300478 100644
--- a/openstackclient/tests/unit/identity/v2_0/test_user.py
+++ b/openstackclient/tests/unit/identity/v2_0/test_user.py
@@ -482,7 +482,7 @@ class TestUserList(TestUser):
self.users_mock.list.assert_called_with(tenant_id=None)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_user_list_project(self):
arglist = [
@@ -502,7 +502,7 @@ class TestUserList(TestUser):
self.users_mock.list.assert_called_with(tenant_id=project_id)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_user_list_long(self):
arglist = [
@@ -531,7 +531,7 @@ class TestUserList(TestUser):
self.fake_user_l.email,
True,
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
class TestUserSet(TestUser):
@@ -819,4 +819,4 @@ class TestUserShow(TestUser):
self.fake_user.name,
self.fake_project.id,
)
- self.assertItemEqual(datalist, data)
+ self.assertItemsEqual(datalist, data)
diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py
index 3630ccb6..97ce48f6 100644
--- a/openstackclient/tests/unit/identity/v3/test_catalog.py
+++ b/openstackclient/tests/unit/identity/v3/test_catalog.py
@@ -91,9 +91,10 @@ class TestCatalogList(TestCatalog):
datalist = ((
'supernova',
'compute',
- catalog.EndpointsColumn(self.fake_service['endpoints']),
+ catalog.EndpointsColumn(
+ auth_ref.service_catalog.catalog[0]['endpoints']),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
class TestCatalogShow(TestCatalog):
@@ -128,12 +129,13 @@ class TestCatalogShow(TestCatalog):
collist = ('endpoints', 'id', 'name', 'type')
self.assertEqual(collist, columns)
datalist = (
- catalog.EndpointsColumn(self.fake_service['endpoints']),
+ catalog.EndpointsColumn(
+ auth_ref.service_catalog.catalog[0]['endpoints']),
'qwertyuiop',
'supernova',
'compute',
)
- self.assertItemEqual(datalist, data)
+ self.assertItemsEqual(datalist, data)
class TestFormatColumns(TestCatalog):
diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py
index a419a9bc..5aff2b1b 100644
--- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py
+++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py
@@ -89,7 +89,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_create_identity_provider_description(self):
arglist = [
@@ -117,7 +117,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_create_identity_provider_remote_id(self):
arglist = [
@@ -145,7 +145,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_create_identity_provider_remote_ids_multiple(self):
arglist = [
@@ -174,7 +174,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_create_identity_provider_remote_ids_file(self):
arglist = [
@@ -207,7 +207,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_create_identity_provider_disabled(self):
@@ -250,7 +250,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_id,
identity_fakes.formatted_idp_remote_ids
)
- self.assertItemEqual(datalist, data)
+ self.assertItemsEqual(datalist, data)
def test_create_identity_provider_domain_name(self):
arglist = [
@@ -278,7 +278,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_create_identity_provider_domain_id(self):
arglist = [
@@ -306,7 +306,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
class TestIdentityProviderDelete(TestIdentityProvider):
@@ -382,7 +382,62 @@ class TestIdentityProviderList(TestIdentityProvider):
identity_fakes.domain_id,
identity_fakes.idp_description,
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
+
+ def test_identity_provider_list_ID_option(self):
+ arglist = ['--id',
+ identity_fakes.idp_id]
+ verifylist = [
+ ('id', identity_fakes.idp_id)
+ ]
+ 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)
+
+ kwargs = {
+ 'id': identity_fakes.idp_id
+ }
+ self.identity_providers_mock.list.assert_called_with(**kwargs)
+
+ collist = ('ID', 'Enabled', 'Domain ID', 'Description')
+ self.assertEqual(collist, columns)
+ datalist = ((
+ identity_fakes.idp_id,
+ True,
+ identity_fakes.domain_id,
+ identity_fakes.idp_description,
+ ), )
+ self.assertItemsEqual(datalist, tuple(data))
+
+ def test_identity_provider_list_enabled_option(self):
+ arglist = ['--enabled']
+ verifylist = [
+ ('enabled', 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)
+
+ kwargs = {
+ 'enabled': True
+ }
+ self.identity_providers_mock.list.assert_called_with(**kwargs)
+
+ collist = ('ID', 'Enabled', 'Domain ID', 'Description')
+ self.assertEqual(collist, columns)
+ datalist = ((
+ identity_fakes.idp_id,
+ True,
+ identity_fakes.domain_id,
+ identity_fakes.idp_description,
+ ), )
+ self.assertItemsEqual(datalist, tuple(data))
class TestIdentityProviderSet(TestIdentityProvider):
@@ -667,4 +722,4 @@ class TestIdentityProviderShow(TestIdentityProvider):
identity_fakes.idp_id,
identity_fakes.formatted_idp_remote_ids
)
- self.assertItemEqual(datalist, data)
+ self.assertItemsEqual(datalist, data)
diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py
index 544da7c1..774b2c2b 100644
--- a/openstackclient/tests/unit/identity/v3/test_role.py
+++ b/openstackclient/tests/unit/identity/v3/test_role.py
@@ -19,6 +19,7 @@ from unittest import mock
from osc_lib import exceptions
from osc_lib import utils
+from openstackclient.identity import common
from openstackclient.identity.v3 import role
from openstackclient.tests.unit import fakes
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
@@ -102,6 +103,40 @@ class TestRoleAdd(TestRole):
# Get the command object to test
self.cmd = role.AddRole(self.app, None)
+ def test_role_add_user_system(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--system', 'all',
+ identity_fakes.role_name,
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('group', None),
+ ('system', 'all'),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'user': identity_fakes.user_id,
+ 'system': 'all',
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.grant(role, user=, group=, domain=, project=)
+ self.roles_mock.grant.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_role_add_user_domain(self):
arglist = [
'--user', identity_fakes.user_name,
@@ -168,6 +203,40 @@ class TestRoleAdd(TestRole):
)
self.assertIsNone(result)
+ def test_role_add_group_system(self):
+ arglist = [
+ '--group', identity_fakes.group_name,
+ '--system', 'all',
+ identity_fakes.role_name,
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', None),
+ ('group', identity_fakes.group_name),
+ ('system', 'all'),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'group': identity_fakes.group_id,
+ 'system': 'all',
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.grant(role, user=, group=, domain=, project=)
+ self.roles_mock.grant.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_role_add_group_domain(self):
arglist = [
'--group', identity_fakes.group_name,
@@ -744,6 +813,81 @@ class TestRoleRemove(TestRole):
# Get the command object to test
self.cmd = role.RemoveRole(self.app, None)
+ def test_role_remove_user_system(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--system', 'all',
+ identity_fakes.role_name
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('group', None),
+ ('system', 'all'),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'user': identity_fakes.user_id,
+ 'system': 'all',
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+ @mock.patch.object(common, 'find_user')
+ def test_role_remove_non_existent_user_system(self, find_mock):
+ # Simulate the user not being in keystone, the client should gracefully
+ # handle this exception and send the request to remove the role since
+ # keystone supports removing role assignments with non-existent actors
+ # (e.g., users or groups).
+ find_mock.side_effect = exceptions.CommandError
+
+ arglist = [
+ '--user', identity_fakes.user_id,
+ '--system', 'all',
+ identity_fakes.role_name
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', identity_fakes.user_id),
+ ('group', None),
+ ('system', 'all'),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'user': identity_fakes.user_id,
+ 'system': 'all',
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_role_remove_user_domain(self):
arglist = [
'--user', identity_fakes.user_name,
@@ -777,6 +921,46 @@ class TestRoleRemove(TestRole):
)
self.assertIsNone(result)
+ @mock.patch.object(common, 'find_user')
+ def test_role_remove_non_existent_user_domain(self, find_mock):
+ # Simulate the user not being in keystone, the client the gracefully
+ # handle this exception and send the request to remove the role since
+ # keystone will validate.
+ find_mock.side_effect = exceptions.CommandError
+
+ arglist = [
+ '--user', identity_fakes.user_id,
+ '--domain', identity_fakes.domain_name,
+ identity_fakes.role_name
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', identity_fakes.user_id),
+ ('group', None),
+ ('system', None),
+ ('domain', identity_fakes.domain_name),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'user': identity_fakes.user_id,
+ 'domain': identity_fakes.domain_id,
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_role_remove_user_project(self):
arglist = [
'--user', identity_fakes.user_name,
@@ -810,6 +994,121 @@ class TestRoleRemove(TestRole):
)
self.assertIsNone(result)
+ @mock.patch.object(common, 'find_user')
+ def test_role_remove_non_existent_user_project(self, find_mock):
+ # Simulate the user not being in keystone, the client the gracefully
+ # handle this exception and send the request to remove the role since
+ # keystone will validate.
+ find_mock.side_effect = exceptions.CommandError
+
+ arglist = [
+ '--user', identity_fakes.user_id,
+ '--project', identity_fakes.project_name,
+ identity_fakes.role_name
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', identity_fakes.user_id),
+ ('group', None),
+ ('system', None),
+ ('domain', None),
+ ('project', identity_fakes.project_name),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'user': identity_fakes.user_id,
+ 'project': identity_fakes.project_id,
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+ def test_role_remove_group_system(self):
+ arglist = [
+ '--group', identity_fakes.group_name,
+ '--system', 'all',
+ identity_fakes.role_name,
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', None),
+ ('group', identity_fakes.group_name),
+ ('system', 'all'),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'group': identity_fakes.group_id,
+ 'system': 'all',
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
+ @mock.patch.object(common, 'find_group')
+ def test_role_remove_non_existent_group_system(self, find_mock):
+ # Simulate the user not being in keystone, the client the gracefully
+ # handle this exception and send the request to remove the role since
+ # keystone will validate.
+ find_mock.side_effect = exceptions.CommandError
+
+ arglist = [
+ '--group', identity_fakes.group_id,
+ '--system', 'all',
+ identity_fakes.role_name
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', None),
+ ('group', identity_fakes.group_id),
+ ('system', 'all'),
+ ('domain', None),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'group': identity_fakes.group_id,
+ 'system': 'all',
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_role_remove_group_domain(self):
arglist = [
'--group', identity_fakes.group_name,
@@ -844,6 +1143,46 @@ class TestRoleRemove(TestRole):
)
self.assertIsNone(result)
+ @mock.patch.object(common, 'find_group')
+ def test_role_remove_non_existent_group_domain(self, find_mock):
+ # Simulate the user not being in keystone, the client the gracefully
+ # handle this exception and send the request to remove the role since
+ # keystone will validate.
+ find_mock.side_effect = exceptions.CommandError
+
+ arglist = [
+ '--group', identity_fakes.group_id,
+ '--domain', identity_fakes.domain_name,
+ identity_fakes.role_name
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', None),
+ ('group', identity_fakes.group_id),
+ ('system', None),
+ ('domain', identity_fakes.domain_name),
+ ('project', None),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'group': identity_fakes.group_id,
+ 'domain': identity_fakes.domain_id,
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_role_remove_group_project(self):
arglist = [
'--group', identity_fakes.group_name,
@@ -877,6 +1216,46 @@ class TestRoleRemove(TestRole):
)
self.assertIsNone(result)
+ @mock.patch.object(common, 'find_group')
+ def test_role_remove_non_existent_group_project(self, find_mock):
+ # Simulate the user not being in keystone, the client the gracefully
+ # handle this exception and send the request to remove the role since
+ # keystone will validate.
+ find_mock.side_effect = exceptions.CommandError
+
+ arglist = [
+ '--group', identity_fakes.group_id,
+ '--project', identity_fakes.project_name,
+ identity_fakes.role_name
+ ]
+ if self._is_inheritance_testcase():
+ arglist.append('--inherited')
+ verifylist = [
+ ('user', None),
+ ('group', identity_fakes.group_id),
+ ('system', None),
+ ('domain', None),
+ ('project', identity_fakes.project_name),
+ ('role', identity_fakes.role_name),
+ ('inherited', self._is_inheritance_testcase()),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'group': identity_fakes.group_id,
+ 'project': identity_fakes.project_id,
+ 'os_inherit_extension_inherited': self._is_inheritance_testcase(),
+ }
+ # RoleManager.revoke(role, user=, group=, domain=, project=)
+ self.roles_mock.revoke.assert_called_with(
+ identity_fakes.role_id,
+ **kwargs
+ )
+ self.assertIsNone(result)
+
def test_role_remove_domain_role_on_group_domain(self):
self.roles_mock.get.return_value = fakes.FakeResource(
None,
diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py
index 2f190a7a..db64983c 100644
--- a/openstackclient/tests/unit/image/v1/test_image.py
+++ b/openstackclient/tests/unit/image/v1/test_image.py
@@ -100,7 +100,7 @@ class TestImageCreate(TestImage):
self.assertEqual(self.client.update_image.call_args_list, [])
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
@mock.patch('sys.stdin', side_effect=[None])
def test_image_reserve_options(self, raw_input):
@@ -149,7 +149,7 @@ class TestImageCreate(TestImage):
self.assertEqual(self.client.update_image.call_args_list, [])
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
@mock.patch('openstackclient.image.v1.image.io.open', name='Open')
def test_image_create_file(self, mock_open):
@@ -205,7 +205,7 @@ class TestImageCreate(TestImage):
self.assertEqual(self.client.update_image.call_args_list, [])
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestImageDelete(TestImage):
@@ -386,7 +386,7 @@ class TestImageList(TestImage):
format_columns.DictColumn(
{'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
@mock.patch('osc_lib.api.utils.simple_filter')
def test_image_list_property_option(self, sf_mock):
@@ -737,7 +737,7 @@ class TestImageShow(TestImage):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_image_show_human_readable(self):
arglist = [
diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py
index 310f6b76..b72e9835 100644
--- a/openstackclient/tests/unit/image/v2/test_image.py
+++ b/openstackclient/tests/unit/image/v2/test_image.py
@@ -100,6 +100,7 @@ class TestImageCreate(TestImage):
# ImageManager.create(name=, **)
self.client.create_image.assert_called_with(
name=self.new_image.name,
+ allow_duplicates=True,
container_format=image.DEFAULT_CONTAINER_FORMAT,
disk_format=image.DEFAULT_DISK_FORMAT,
)
@@ -110,7 +111,7 @@ class TestImageCreate(TestImage):
self.assertEqual(
self.expected_columns,
columns)
- self.assertItemEqual(
+ self.assertItemsEqual(
self.expected_data,
data)
@@ -152,6 +153,7 @@ class TestImageCreate(TestImage):
# ImageManager.create(name=, **)
self.client.create_image.assert_called_with(
name=self.new_image.name,
+ allow_duplicates=True,
container_format='ovf',
disk_format='ami',
min_disk=10,
@@ -164,7 +166,7 @@ class TestImageCreate(TestImage):
self.assertEqual(
self.expected_columns,
columns)
- self.assertItemEqual(
+ self.assertItemsEqual(
self.expected_data,
data)
@@ -239,6 +241,7 @@ class TestImageCreate(TestImage):
# ImageManager.create(name=, **)
self.client.create_image.assert_called_with(
name=self.new_image.name,
+ allow_duplicates=True,
container_format=image.DEFAULT_CONTAINER_FORMAT,
disk_format=image.DEFAULT_DISK_FORMAT,
is_protected=self.new_image.is_protected,
@@ -246,13 +249,13 @@ class TestImageCreate(TestImage):
Alpha='1',
Beta='2',
tags=self.new_image.tags,
- filename=imagefile.name
+ filename=imagefile.name,
)
self.assertEqual(
self.expected_columns,
columns)
- self.assertItemEqual(
+ self.assertItemsEqual(
self.expected_data,
data)
@@ -288,6 +291,7 @@ class TestImageCreate(TestImage):
# ImageManager.create(name=, **)
self.client.create_image.assert_called_with(
name=self.new_image.name,
+ allow_duplicates=True,
container_format=image.DEFAULT_CONTAINER_FORMAT,
disk_format=image.DEFAULT_DISK_FORMAT,
use_import=True
@@ -509,7 +513,7 @@ class TestImageList(TestImage):
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_image_list_public_option(self):
arglist = [
@@ -533,7 +537,7 @@ class TestImageList(TestImage):
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_image_list_private_option(self):
arglist = [
@@ -557,7 +561,7 @@ class TestImageList(TestImage):
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_image_list_community_option(self):
arglist = [
@@ -605,7 +609,7 @@ class TestImageList(TestImage):
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_image_list_shared_member_status_option(self):
arglist = [
@@ -693,7 +697,7 @@ class TestImageList(TestImage):
self._image.owner_id,
format_columns.ListColumn(self._image.tags),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
@mock.patch('osc_lib.api.utils.simple_filter')
def test_image_list_property_option(self, sf_mock):
@@ -721,7 +725,7 @@ class TestImageList(TestImage):
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
@mock.patch('osc_lib.utils.sort_items')
def test_image_list_sort_option(self, si_mock):
@@ -743,7 +747,7 @@ class TestImageList(TestImage):
str,
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_image_list_limit_option(self):
ret_limit = 1
@@ -1468,7 +1472,7 @@ class TestImageShow(TestImage):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_image_show_human_readable(self):
self.client.find_image.return_value = self.new_image
@@ -1614,7 +1618,7 @@ class TestImageSave(TestImage):
verifylist = [
('file', '/path/to/file'),
- ('image', self.image.id)
+ ('image', self.image.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -1622,6 +1626,7 @@ class TestImageSave(TestImage):
self.client.download_image.assert_called_once_with(
self.image.id,
+ stream=True,
output='/path/to/file')
diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py
index cef0a11c..2db83d3b 100644
--- a/openstackclient/tests/unit/network/v2/fakes.py
+++ b/openstackclient/tests/unit/network/v2/fakes.py
@@ -634,6 +634,7 @@ class FakePort(object):
'mac_address': 'fa:16:3e:a9:4e:72',
'name': 'port-name-' + uuid.uuid4().hex,
'network_id': 'network-id-' + uuid.uuid4().hex,
+ 'numa_affinity_policy': 'required',
'port_security_enabled': True,
'security_group_ids': [],
'status': 'ACTIVE',
@@ -641,7 +642,7 @@ class FakePort(object):
'qos_network_policy_id': 'qos-policy-id-' + uuid.uuid4().hex,
'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex,
'tags': [],
- 'uplink_status_propagation': False,
+ 'propagate_uplink_status': False,
}
# Overwrite default attributes.
@@ -661,8 +662,8 @@ class FakePort(object):
port.project_id = port_attrs['tenant_id']
port.security_group_ids = port_attrs['security_group_ids']
port.qos_policy_id = port_attrs['qos_policy_id']
- port.uplink_status_propagation = port_attrs[
- 'uplink_status_propagation']
+ port.propagate_uplink_status = port_attrs[
+ 'propagate_uplink_status']
return port
@@ -1590,6 +1591,8 @@ class FakeNetworkMeterRule(object):
'excluded': False,
'metering_label_id': 'meter-label-id-' + uuid.uuid4().hex,
'remote_ip_prefix': '10.0.0.0/24',
+ 'source_ip_prefix': '8.8.8.8/32',
+ 'destination_ip_prefix': '10.0.0.0/24',
'tenant_id': 'project-id-' + uuid.uuid4().hex,
}
diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py
index 9a712704..ade57837 100644
--- a/openstackclient/tests/unit/network/v2/test_ip_availability.py
+++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py
@@ -75,7 +75,7 @@ class TestListIPAvailability(TestIPAvailability):
self.network.network_ip_availabilities.assert_called_once_with(
**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_ip_version(self):
arglist = [
@@ -93,7 +93,7 @@ class TestListIPAvailability(TestIPAvailability):
self.network.network_ip_availabilities.assert_called_once_with(
**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_project(self):
arglist = [
@@ -113,7 +113,7 @@ class TestListIPAvailability(TestIPAvailability):
self.network.network_ip_availabilities.assert_called_once_with(
**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
class TestShowIPAvailability(TestIPAvailability):
@@ -176,4 +176,4 @@ class TestShowIPAvailability(TestIPAvailability):
self._ip_availability.network_name,
ignore_missing=False)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py
index 5f8eed67..e29b72c7 100644
--- a/openstackclient/tests/unit/network/v2/test_network.py
+++ b/openstackclient/tests/unit/network/v2/test_network.py
@@ -146,7 +146,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
})
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_all_options(self):
arglist = [
@@ -211,7 +211,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'dns_domain': 'example.org.',
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_other_options(self):
arglist = [
@@ -238,7 +238,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
'port_security_enabled': False,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def _test_create_with_tag(self, add_tags=True):
arglist = [self._network.name]
@@ -270,7 +270,7 @@ class TestCreateNetworkIdentityV3(TestNetwork):
else:
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_tags(self):
self._test_create_with_tag(add_tags=True)
@@ -385,7 +385,7 @@ class TestCreateNetworkIdentityV2(TestNetwork):
})
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_domain_identityv2(self):
arglist = [
@@ -577,7 +577,7 @@ class TestListNetwork(TestNetwork):
self.network.networks.assert_called_once_with()
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_external(self):
arglist = [
@@ -598,7 +598,7 @@ class TestListNetwork(TestNetwork):
**{'router:external': True, 'is_router_external': True}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_internal(self):
arglist = [
@@ -615,7 +615,7 @@ class TestListNetwork(TestNetwork):
**{'router:external': False, 'is_router_external': False}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_long(self):
arglist = [
@@ -634,7 +634,7 @@ class TestListNetwork(TestNetwork):
self.network.networks.assert_called_once_with()
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
def test_list_name(self):
test_name = "fakename"
@@ -653,7 +653,7 @@ class TestListNetwork(TestNetwork):
**{'name': test_name}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_enable(self):
arglist = [
@@ -671,7 +671,7 @@ class TestListNetwork(TestNetwork):
**{'admin_state_up': True, 'is_admin_state_up': True}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_disable(self):
arglist = [
@@ -689,7 +689,7 @@ class TestListNetwork(TestNetwork):
**{'admin_state_up': False, 'is_admin_state_up': False}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_project(self):
project = identity_fakes_v3.FakeProject.create_one_project()
@@ -708,7 +708,7 @@ class TestListNetwork(TestNetwork):
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_project_domain(self):
project = identity_fakes_v3.FakeProject.create_one_project()
@@ -727,7 +727,7 @@ class TestListNetwork(TestNetwork):
self.network.networks.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_share(self):
arglist = [
@@ -744,7 +744,7 @@ class TestListNetwork(TestNetwork):
**{'shared': True, 'is_shared': True}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_no_share(self):
arglist = [
@@ -761,7 +761,7 @@ class TestListNetwork(TestNetwork):
**{'shared': False, 'is_shared': False}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_status(self):
choices = ['ACTIVE', 'BUILD', 'DOWN', 'ERROR']
@@ -780,7 +780,7 @@ class TestListNetwork(TestNetwork):
**{'status': test_status}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_provider_network_type(self):
network_type = self._network[0].provider_network_type
@@ -798,7 +798,7 @@ class TestListNetwork(TestNetwork):
'provider_network_type': network_type}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_provider_physical_network(self):
physical_network = self._network[0].provider_physical_network
@@ -816,7 +816,7 @@ class TestListNetwork(TestNetwork):
'provider_physical_network': physical_network}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_provider_segment(self):
segmentation_id = self._network[0].provider_segmentation_id
@@ -834,7 +834,7 @@ class TestListNetwork(TestNetwork):
'provider_segmentation_id': segmentation_id}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_list_dhcp_agent(self):
arglist = [
@@ -853,7 +853,7 @@ class TestListNetwork(TestNetwork):
*attrs)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(list(data), list(self.data))
+ self.assertItemsEqual(list(data), list(self.data))
def test_list_with_tag_options(self):
arglist = [
@@ -878,7 +878,7 @@ class TestListNetwork(TestNetwork):
'not_any_tags': 'black,white'}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
class TestSetNetwork(TestNetwork):
@@ -1111,7 +1111,7 @@ class TestShowNetwork(TestNetwork):
self._network.name, ignore_missing=False)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestUnsetNetwork(TestNetwork):
diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py
index 3181ee78..fceac68e 100644
--- a/openstackclient/tests/unit/network/v2/test_network_agent.py
+++ b/openstackclient/tests/unit/network/v2/test_network_agent.py
@@ -246,7 +246,7 @@ class TestListNetworkAgent(TestNetworkAgent):
self.network.agents.assert_called_once_with(**{})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_agents_list_agent_type(self):
arglist = [
@@ -263,7 +263,7 @@ class TestListNetworkAgent(TestNetworkAgent):
'agent_type': 'DHCP agent',
})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_agents_list_host(self):
arglist = [
@@ -280,7 +280,7 @@ class TestListNetworkAgent(TestNetworkAgent):
'host': self.network_agents[0].host,
})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_agents_list_networks(self):
arglist = [
@@ -298,7 +298,7 @@ class TestListNetworkAgent(TestNetworkAgent):
self.network.network_hosting_dhcp_agents.assert_called_once_with(
*attrs)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_agents_list_routers(self):
arglist = [
@@ -318,7 +318,7 @@ class TestListNetworkAgent(TestNetworkAgent):
*attrs)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_network_agents_list_routers_with_long_option(self):
arglist = [
@@ -343,7 +343,7 @@ class TestListNetworkAgent(TestNetworkAgent):
router_agent_data = [d + ('',) for d in self.data]
self.assertEqual(router_agent_columns, columns)
- self.assertListItemEqual(router_agent_data, list(data))
+ self.assertItemsEqual(router_agent_data, list(data))
class TestRemoveNetworkFromAgent(TestNetworkAgent):
@@ -571,4 +571,4 @@ class TestShowNetworkAgent(TestNetworkAgent):
self.network.get_agent.assert_called_once_with(
self._network_agent.id)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(list(self.data), list(data))
+ self.assertItemsEqual(list(self.data), list(data))
diff --git a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py
index 8f8922c0..e9224fa6 100644
--- a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py
+++ b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py
@@ -42,20 +42,24 @@ class TestCreateMeterRule(TestMeterRule):
)
columns = (
+ 'destination_ip_prefix',
'direction',
'excluded',
'id',
'metering_label_id',
'project_id',
'remote_ip_prefix',
+ 'source_ip_prefix',
)
data = (
+ new_rule.destination_ip_prefix,
new_rule.direction,
new_rule.excluded,
new_rule.id,
new_rule.metering_label_id,
new_rule.project_id,
new_rule.remote_ip_prefix,
+ new_rule.source_ip_prefix,
)
def setUp(self):
@@ -228,6 +232,8 @@ class TestListMeterRule(TestMeterRule):
'Excluded',
'Direction',
'Remote IP Prefix',
+ 'Source IP Prefix',
+ 'Destination IP Prefix'
)
data = []
@@ -238,6 +244,8 @@ class TestListMeterRule(TestMeterRule):
rule.excluded,
rule.direction,
rule.remote_ip_prefix,
+ rule.source_ip_prefix,
+ rule.destination_ip_prefix
))
def setUp(self):
@@ -270,21 +278,25 @@ class TestShowMeterRule(TestMeterRule):
)
columns = (
+ 'destination_ip_prefix',
'direction',
'excluded',
'id',
'metering_label_id',
'project_id',
'remote_ip_prefix',
+ 'source_ip_prefix',
)
data = (
+ new_rule.destination_ip_prefix,
new_rule.direction,
new_rule.excluded,
new_rule.id,
new_rule.metering_label_id,
new_rule.project_id,
new_rule.remote_ip_prefix,
+ new_rule.source_ip_prefix,
)
def setUp(self):
diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py
index 87aea61f..e21f9d01 100644
--- a/openstackclient/tests/unit/network/v2/test_port.py
+++ b/openstackclient/tests/unit/network/v2/test_port.py
@@ -26,6 +26,10 @@ from openstackclient.tests.unit.network.v2 import fakes as network_fakes
from openstackclient.tests.unit import utils as tests_utils
+LIST_FIELDS_TO_RETRIEVE = ('id', 'name', 'mac_address', 'fixed_ips', 'status')
+LIST_FIELDS_TO_RETRIEVE_LONG = ('security_group_ids', 'device_owner', 'tags')
+
+
class TestPort(network_fakes.TestNetworkV2):
def setUp(self):
@@ -59,14 +63,15 @@ class TestPort(network_fakes.TestNetworkV2):
'mac_address',
'name',
'network_id',
+ 'numa_affinity_policy',
'port_security_enabled',
'project_id',
+ 'propagate_uplink_status',
'qos_network_policy_id',
'qos_policy_id',
'security_group_ids',
'status',
'tags',
- 'uplink_status_propagation',
)
data = (
@@ -90,14 +95,15 @@ class TestPort(network_fakes.TestNetworkV2):
fake_port.mac_address,
fake_port.name,
fake_port.network_id,
+ fake_port.numa_affinity_policy,
fake_port.port_security_enabled,
fake_port.project_id,
+ fake_port.propagate_uplink_status,
fake_port.qos_network_policy_id,
fake_port.qos_policy_id,
format_columns.ListColumn(fake_port.security_group_ids),
fake_port.status,
format_columns.ListColumn(fake_port.tags),
- fake_port.uplink_status_propagation,
)
return columns, data
@@ -119,6 +125,7 @@ class TestCreatePort(TestPort):
self.network.find_network = mock.Mock(return_value=fake_net)
self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet()
self.network.find_subnet = mock.Mock(return_value=self.fake_subnet)
+ self.network.find_extension = mock.Mock(return_value=[])
# Get the command object to test
self.cmd = port.CreatePort(self.app, self.namespace)
@@ -144,7 +151,7 @@ class TestCreatePort(TestPort):
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_full_options(self):
arglist = [
@@ -202,7 +209,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_invalid_json_binding_profile(self):
arglist = [
@@ -253,7 +260,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_security_group(self):
secgroup = network_fakes.FakeSecurityGroup.create_one_security_group()
@@ -282,7 +289,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_port_with_dns_name(self):
arglist = [
@@ -308,7 +315,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_security_groups(self):
sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group()
@@ -338,7 +345,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_no_security_groups(self):
arglist = [
@@ -364,7 +371,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_no_fixed_ips(self):
arglist = [
@@ -390,7 +397,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_port_with_allowed_address_pair_ipaddr(self):
pairs = [{'ip_address': '192.168.1.123'},
@@ -420,7 +427,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_port_with_allowed_address_pair(self):
pairs = [{'ip_address': '192.168.1.123',
@@ -456,7 +463,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_port_with_qos(self):
qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()
@@ -484,7 +491,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_port_security_enabled(self):
arglist = [
@@ -534,7 +541,7 @@ class TestCreatePort(TestPort):
'name': 'test-port',
})
- def _test_create_with_tag(self, add_tags=True):
+ def _test_create_with_tag(self, add_tags=True, add_tags_in_post=True):
arglist = [
'--network', self._port.network_id,
'test-port',
@@ -553,28 +560,59 @@ class TestCreatePort(TestPort):
else:
verifylist.append(('no_tag', True))
+ self.network.find_extension = mock.Mock(return_value=add_tags_in_post)
+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = (self.cmd.take_action(parsed_args))
- self.network.create_port.assert_called_once_with(
- admin_state_up=True,
- network_id=self._port.network_id,
- name='test-port'
- )
- if add_tags:
- self.network.set_tags.assert_called_once_with(
- self._port,
- tests_utils.CompareBySet(['red', 'blue']))
+ args = {
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'name': 'test-port',
+ }
+ if add_tags_in_post:
+ if add_tags:
+ args['tags'] = sorted(['red', 'blue'])
+ else:
+ args['tags'] = []
+ self.network.create_port.assert_called_once()
+ # Now we need to verify if arguments to call create_port are as
+ # expected,
+ # But we can't simply use assert_called_once_with() method because
+ # duplicates from 'tags' are removed with
+ # list(set(parsed_args.tags)) and that don't quarantee order of
+ # tags list which is used to call create_port().
+ create_port_call_kwargs = self.network.create_port.call_args[1]
+ create_port_call_kwargs['tags'] = sorted(
+ create_port_call_kwargs['tags'])
+ self.assertDictEqual(args, create_port_call_kwargs)
else:
- self.assertFalse(self.network.set_tags.called)
+ self.network.create_port.assert_called_once_with(
+ admin_state_up=True,
+ network_id=self._port.network_id,
+ name='test-port'
+ )
+ if add_tags:
+ self.network.set_tags.assert_called_once_with(
+ self._port,
+ tests_utils.CompareBySet(['red', 'blue']))
+ else:
+ self.assertFalse(self.network.set_tags.called)
+
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_tags(self):
- self._test_create_with_tag(add_tags=True)
+ self._test_create_with_tag(add_tags=True, add_tags_in_post=True)
def test_create_with_no_tag(self):
- self._test_create_with_tag(add_tags=False)
+ self._test_create_with_tag(add_tags=False, add_tags_in_post=True)
+
+ def test_create_with_tags_using_put(self):
+ self._test_create_with_tag(add_tags=True, add_tags_in_post=False)
+
+ def test_create_with_no_tag_using_put(self):
+ self._test_create_with_tag(add_tags=False, add_tags_in_post=False)
def _test_create_with_uplink_status_propagation(self, enable=True):
arglist = [
@@ -605,7 +643,7 @@ class TestCreatePort(TestPort):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_uplink_status_propagation_enabled(self):
self._test_create_with_uplink_status_propagation(enable=True)
@@ -655,6 +693,50 @@ class TestCreatePort(TestPort):
'name': 'test-port',
})
+ def _test_create_with_numa_affinity_policy(self, policy=None):
+ arglist = [
+ '--network', self._port.network_id,
+ 'test-port',
+ ]
+ if policy:
+ arglist += ['--numa-policy-%s' % policy]
+
+ numa_affinity_policy = None if not policy else policy
+ verifylist = [
+ ('network', self._port.network_id,),
+ ('name', 'test-port'),
+ ]
+ if policy:
+ verifylist.append(('numa_policy_%s' % policy, True))
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = (self.cmd.take_action(parsed_args))
+
+ create_args = {
+ 'admin_state_up': True,
+ 'network_id': self._port.network_id,
+ 'name': 'test-port',
+ }
+ if numa_affinity_policy:
+ create_args['numa_affinity_policy'] = numa_affinity_policy
+ self.network.create_port.assert_called_once_with(**create_args)
+
+ self.assertEqual(self.columns, columns)
+ self.assertItemsEqual(self.data, data)
+
+ def test_create_with_numa_affinity_policy_required(self):
+ self._test_create_with_numa_affinity_policy(policy='required')
+
+ def test_create_with_numa_affinity_policy_preferred(self):
+ self._test_create_with_numa_affinity_policy(policy='preferred')
+
+ def test_create_with_numa_affinity_policy_legacy(self):
+ self._test_create_with_numa_affinity_policy(policy='legacy')
+
+ def test_create_with_numa_affinity_policy_null(self):
+ self._test_create_with_numa_affinity_policy()
+
class TestDeletePort(TestPort):
@@ -805,9 +887,10 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
- self.network.ports.assert_called_once_with()
+ self.network.ports.assert_called_once_with(
+ fields=LIST_FIELDS_TO_RETRIEVE)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_router_opt(self):
arglist = [
@@ -823,10 +906,11 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
self.network.ports.assert_called_once_with(**{
- 'device_id': 'fake-router-id'
+ 'device_id': 'fake-router-id',
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
@mock.patch.object(utils, 'find_resource')
def test_port_list_with_server_option(self, mock_find):
@@ -843,10 +927,11 @@ class TestListPort(TestPort):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.network.ports.assert_called_once_with(
- device_id=fake_server.id)
+ device_id=fake_server.id,
+ fields=LIST_FIELDS_TO_RETRIEVE)
mock_find.assert_called_once_with(mock.ANY, 'fake-server-name')
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_device_id_opt(self):
arglist = [
@@ -862,10 +947,11 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
self.network.ports.assert_called_once_with(**{
- 'device_id': self._ports[0].device_id
+ 'device_id': self._ports[0].device_id,
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_device_owner_opt(self):
arglist = [
@@ -881,10 +967,11 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
self.network.ports.assert_called_once_with(**{
- 'device_owner': self._ports[0].device_owner
+ 'device_owner': self._ports[0].device_owner,
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_all_opt(self):
arglist = [
@@ -909,10 +996,11 @@ class TestListPort(TestPort):
'device_owner': self._ports[0].device_owner,
'device_id': 'fake-router-id',
'network_id': 'fake-network-id',
- 'mac_address': self._ports[0].mac_address
+ 'mac_address': self._ports[0].mac_address,
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_mac_address_opt(self):
arglist = [
@@ -928,10 +1016,11 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
self.network.ports.assert_called_once_with(**{
- 'mac_address': self._ports[0].mac_address
+ 'mac_address': self._ports[0].mac_address,
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_fixed_ip_opt_ip_address(self):
ip_address = self._ports[0].fixed_ips[0]['ip_address']
@@ -947,9 +1036,11 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
self.network.ports.assert_called_once_with(**{
- 'fixed_ips': ['ip_address=%s' % ip_address]})
+ 'fixed_ips': ['ip_address=%s' % ip_address],
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ })
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(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]
@@ -965,9 +1056,11 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
self.network.ports.assert_called_once_with(**{
- 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss]})
+ 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss],
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ })
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_fixed_ip_opt_subnet_id(self):
subnet_id = self._ports[0].fixed_ips[0]['subnet_id']
@@ -985,9 +1078,11 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
self.network.ports.assert_called_once_with(**{
- 'fixed_ips': ['subnet_id=%s' % subnet_id]})
+ 'fixed_ips': ['subnet_id=%s' % subnet_id],
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ })
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_fixed_ip_opts(self):
subnet_id = self._ports[0].fixed_ips[0]['subnet_id']
@@ -1009,9 +1104,11 @@ class TestListPort(TestPort):
self.network.ports.assert_called_once_with(**{
'fixed_ips': ['subnet_id=%s' % subnet_id,
- 'ip_address=%s' % ip_address]})
+ 'ip_address=%s' % ip_address],
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ })
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_fixed_ips(self):
subnet_id = self._ports[0].fixed_ips[0]['subnet_id']
@@ -1025,17 +1122,21 @@ class TestListPort(TestPort):
{'ip-address': ip_address}])
]
- self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet(
- {'id': subnet_id})
+ self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet({
+ 'id': subnet_id,
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ })
self.network.find_subnet = mock.Mock(return_value=self.fake_subnet)
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': ['subnet_id=%s' % subnet_id,
- 'ip_address=%s' % ip_address]})
+ 'ip_address=%s' % ip_address],
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ })
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_port_with_long(self):
arglist = [
@@ -1050,9 +1151,10 @@ class TestListPort(TestPort):
columns, data = self.cmd.take_action(parsed_args)
- self.network.ports.assert_called_once_with()
+ self.network.ports.assert_called_once_with(
+ fields=LIST_FIELDS_TO_RETRIEVE + LIST_FIELDS_TO_RETRIEVE_LONG)
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
def test_port_list_host(self):
arglist = [
@@ -1064,11 +1166,14 @@ class TestListPort(TestPort):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'binding:host_id': 'foobar'}
+ filters = {
+ 'binding:host_id': 'foobar',
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ }
self.network.ports.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_project(self):
project = identity_fakes.FakeProject.create_one_project()
@@ -1082,11 +1187,15 @@ class TestListPort(TestPort):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'tenant_id': project.id, 'project_id': project.id}
+ filters = {
+ 'tenant_id': project.id,
+ 'project_id': project.id,
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ }
self.network.ports.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_port_list_project_domain(self):
project = identity_fakes.FakeProject.create_one_project()
@@ -1102,11 +1211,15 @@ class TestListPort(TestPort):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- filters = {'tenant_id': project.id, 'project_id': project.id}
+ filters = {
+ 'tenant_id': project.id,
+ 'project_id': project.id,
+ 'fields': LIST_FIELDS_TO_RETRIEVE,
+ }
self.network.ports.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_with_tag_options(self):
arglist = [
@@ -1128,10 +1241,11 @@ class TestListPort(TestPort):
**{'tags': 'red,blue',
'any_tags': 'red,green',
'not_tags': 'orange,yellow',
- 'not_any_tags': 'black,white'}
+ 'not_any_tags': 'black,white',
+ 'fields': LIST_FIELDS_TO_RETRIEVE}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
class TestSetPort(TestPort):
@@ -1668,6 +1782,32 @@ class TestSetPort(TestPort):
def test_set_with_no_tag(self):
self._test_set_tags(with_tags=False)
+ def _test_create_with_numa_affinity_policy(self, policy):
+ arglist = [
+ '--numa-policy-%s' % policy,
+ self._port.id,
+ ]
+ verifylist = [
+ ('numa_policy_%s' % policy, True),
+ ('port', self._port.id,)
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.network.update_port.assert_called_once_with(
+ self._port, **{'numa_affinity_policy': policy})
+
+ def test_create_with_numa_affinity_policy_required(self):
+ self._test_create_with_numa_affinity_policy('required')
+
+ def test_create_with_numa_affinity_policy_preferred(self):
+ self._test_create_with_numa_affinity_policy('preferred')
+
+ def test_create_with_numa_affinity_policy_legacy(self):
+ self._test_create_with_numa_affinity_policy('legacy')
+
class TestShowPort(TestPort):
@@ -1705,7 +1845,7 @@ class TestShowPort(TestPort):
self._port.name, ignore_missing=False)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestUnsetPort(TestPort):
@@ -1923,3 +2063,26 @@ class TestUnsetPort(TestPort):
def test_unset_with_all_tag(self):
self._test_unset_tags(with_tags=False)
+
+ def test_unset_numa_affinity_policy(self):
+ _fake_port = network_fakes.FakePort.create_one_port(
+ {'numa_affinity_policy': 'required'})
+ self.network.find_port = mock.Mock(return_value=_fake_port)
+ arglist = [
+ '--numa-policy',
+ _fake_port.name,
+ ]
+ verifylist = [
+ ('numa_policy', True),
+ ('port', _fake_port.name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ attrs = {
+ 'numa_affinity_policy': None,
+ }
+
+ self.network.update_port.assert_called_once_with(_fake_port, **attrs)
+ self.assertIsNone(result)
diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py
index 09b4957c..323c9198 100644
--- a/openstackclient/tests/unit/network/v2/test_router.py
+++ b/openstackclient/tests/unit/network/v2/test_router.py
@@ -184,7 +184,7 @@ class TestCreateRouter(TestRouter):
})
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def _test_create_with_ha_options(self, option, ha):
arglist = [
@@ -208,7 +208,7 @@ class TestCreateRouter(TestRouter):
'ha': ha,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_ha_option(self):
self._test_create_with_ha_options('--ha', True)
@@ -237,7 +237,7 @@ class TestCreateRouter(TestRouter):
'distributed': distributed,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_distributed_option(self):
self._test_create_with_distributed_options('--distributed', True)
@@ -268,7 +268,7 @@ class TestCreateRouter(TestRouter):
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def _test_create_with_tag(self, add_tags=True):
arglist = [self.new_router.name]
@@ -301,7 +301,7 @@ class TestCreateRouter(TestRouter):
else:
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_tags(self):
self._test_create_with_tag(add_tags=True)
@@ -494,7 +494,7 @@ class TestListRouter(TestRouter):
self.network.routers.assert_called_once_with()
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_router_list_no_ha_no_distributed(self):
_routers = network_fakes.FakeRouter.create_routers({
@@ -531,7 +531,7 @@ class TestListRouter(TestRouter):
self.network.routers.assert_called_once_with()
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
def test_router_list_long_no_az(self):
arglist = [
@@ -552,7 +552,7 @@ class TestListRouter(TestRouter):
self.network.routers.assert_called_once_with()
self.assertEqual(self.columns_long_no_az, columns)
- self.assertListItemEqual(self.data_long_no_az, list(data))
+ self.assertItemsEqual(self.data_long_no_az, list(data))
def test_list_name(self):
test_name = "fakename"
@@ -570,7 +570,7 @@ class TestListRouter(TestRouter):
**{'name': test_name}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_router_list_enable(self):
arglist = [
@@ -587,7 +587,7 @@ class TestListRouter(TestRouter):
**{'admin_state_up': True, 'is_admin_state_up': True}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_router_list_disable(self):
arglist = [
@@ -605,7 +605,7 @@ class TestListRouter(TestRouter):
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_router_list_project(self):
project = identity_fakes_v3.FakeProject.create_one_project()
@@ -623,7 +623,7 @@ class TestListRouter(TestRouter):
self.network.routers.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_router_list_project_domain(self):
project = identity_fakes_v3.FakeProject.create_one_project()
@@ -643,7 +643,7 @@ class TestListRouter(TestRouter):
self.network.routers.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_router_list_agents_no_args(self):
arglist = [
@@ -671,7 +671,7 @@ class TestListRouter(TestRouter):
self.network.agent_hosted_routers(
*attrs)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_with_tag_options(self):
arglist = [
@@ -696,7 +696,7 @@ class TestListRouter(TestRouter):
'not_any_tags': 'black,white'}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
class TestRemovePortFromRouter(TestRouter):
@@ -1403,7 +1403,7 @@ class TestShowRouter(TestRouter):
'device_id': self._router.id
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_show_no_ha_no_distributed(self):
_router = network_fakes.FakeRouter.create_one_router({
diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py
index b4ddcf80..837c9b21 100644
--- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py
+++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py
@@ -88,7 +88,7 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute):
self._security_group['name'],
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_security_group_create_all_options(self, sg_mock):
sg_mock.return_value = self._security_group
@@ -109,7 +109,7 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute):
self._security_group['description'],
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
@mock.patch(
@@ -255,7 +255,7 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute):
kwargs = {'search_opts': {'all_tenants': False}}
sg_mock.assert_called_once_with(**kwargs)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_security_group_list_all_projects(self, sg_mock):
sg_mock.return_value = self._security_groups
@@ -272,7 +272,7 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute):
kwargs = {'search_opts': {'all_tenants': True}}
sg_mock.assert_called_once_with(**kwargs)
self.assertEqual(self.columns_all_projects, columns)
- self.assertListItemEqual(self.data_all_projects, list(data))
+ self.assertItemsEqual(self.data_all_projects, list(data))
@mock.patch(
@@ -401,4 +401,4 @@ class TestShowSecurityGroupCompute(TestSecurityGroupCompute):
sg_mock.assert_called_once_with(self._security_group['id'])
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
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 7c1d7fb6..fe377785 100644
--- a/openstackclient/tests/unit/network/v2/test_security_group_network.py
+++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py
@@ -96,7 +96,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
'name': self._security_group.name,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_all_options(self):
arglist = [
@@ -124,7 +124,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
'tenant_id': self.project.id,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def _test_create_with_tag(self, add_tags=True):
arglist = [self._security_group.name]
@@ -155,7 +155,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork):
else:
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_tags(self):
self._test_create_with_tag(add_tags=True)
@@ -293,7 +293,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
self.network.security_groups.assert_called_once_with(
fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_security_group_list_all_projects(self):
arglist = [
@@ -309,7 +309,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
self.network.security_groups.assert_called_once_with(
fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_security_group_list_project(self):
project = identity_fakes.FakeProject.create_one_project()
@@ -329,7 +329,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
self.network.security_groups.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_security_group_list_project_domain(self):
project = identity_fakes.FakeProject.create_one_project()
@@ -351,7 +351,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork):
self.network.security_groups.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_with_tag_options(self):
arglist = [
@@ -539,7 +539,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork):
self.network.find_security_group.assert_called_once_with(
self._security_group.id, ignore_missing=False)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork):
diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py
index 5720e305..b7e38afb 100644
--- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py
+++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py
@@ -362,6 +362,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute):
'Ethertype',
'IP Range',
'Port Range',
+ 'Direction',
'Remote Security Group',
)
expected_columns_no_group = \
diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py
index 0a9522b0..01411611 100644
--- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py
+++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py
@@ -870,7 +870,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
_security_group_rules = [_security_group_rule_tcp,
_security_group_rule_icmp]
- expected_columns_with_group_and_long = (
+ expected_columns_with_group = (
'ID',
'IP Protocol',
'Ethertype',
@@ -885,14 +885,15 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
'Ethertype',
'IP Range',
'Port Range',
+ 'Direction',
'Remote Security Group',
'Security Group',
)
- expected_data_with_group_and_long = []
+ expected_data_with_group = []
expected_data_no_group = []
for _security_group_rule in _security_group_rules:
- expected_data_with_group_and_long.append((
+ expected_data_with_group.append((
_security_group_rule.id,
_security_group_rule.protocol,
_security_group_rule.ether_type,
@@ -909,6 +910,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
_security_group_rule.remote_ip_prefix,
security_group_rule._format_network_port_range(
_security_group_rule),
+ _security_group_rule.direction,
_security_group_rule.remote_group_id,
_security_group_rule.security_group_id,
))
@@ -935,14 +937,12 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.assertEqual(self.expected_columns_no_group, columns)
self.assertEqual(self.expected_data_no_group, list(data))
- def test_list_with_group_and_long(self):
+ def test_list_with_group(self):
self._security_group_rule_tcp.port_range_min = 80
arglist = [
- '--long',
self._security_group.id,
]
verifylist = [
- ('long', True),
('group', self._security_group.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -952,8 +952,8 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork):
self.network.security_group_rules.assert_called_once_with(**{
'security_group_id': self._security_group.id,
})
- self.assertEqual(self.expected_columns_with_group_and_long, columns)
- self.assertEqual(self.expected_data_with_group_and_long, list(data))
+ self.assertEqual(self.expected_columns_with_group, columns)
+ self.assertEqual(self.expected_data_with_group, list(data))
def test_list_with_ignored_options(self):
self._security_group_rule_tcp.port_range_min = 80
diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py
index 47d0c6b4..1b4bfdad 100644
--- a/openstackclient/tests/unit/network/v2/test_subnet.py
+++ b/openstackclient/tests/unit/network/v2/test_subnet.py
@@ -255,7 +255,7 @@ class TestCreateSubnet(TestSubnet):
})
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_from_subnet_pool_options(self):
# Mock SDK calls for this test.
@@ -317,7 +317,7 @@ class TestCreateSubnet(TestSubnet):
'service_types': self._subnet_from_pool.service_types,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data_subnet_pool, data)
+ self.assertItemsEqual(self.data_subnet_pool, data)
def test_create_options_subnet_range_ipv6(self):
# Mock SDK calls for this test.
@@ -390,7 +390,7 @@ class TestCreateSubnet(TestSubnet):
})
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data_ipv6, data)
+ self.assertItemsEqual(self.data_ipv6, data)
def test_create_with_network_segment(self):
# Mock SDK calls for this test.
@@ -424,7 +424,7 @@ class TestCreateSubnet(TestSubnet):
})
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_description(self):
# Mock SDK calls for this test.
@@ -458,7 +458,7 @@ class TestCreateSubnet(TestSubnet):
})
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def _test_create_with_dns(self, publish_dns=True):
arglist = [
@@ -490,7 +490,7 @@ class TestCreateSubnet(TestSubnet):
dns_publish_fixed_ip=publish_dns,
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_dns(self):
self._test_create_with_dns(publish_dns=True)
@@ -535,7 +535,7 @@ class TestCreateSubnet(TestSubnet):
else:
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_tags(self):
self._test_create_with_tag(add_tags=True)
@@ -691,7 +691,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with()
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_long(self):
arglist = [
@@ -706,7 +706,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with()
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
def test_subnet_list_ip_version(self):
arglist = [
@@ -722,7 +722,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_dhcp(self):
arglist = [
@@ -738,7 +738,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_no_dhcp(self):
arglist = [
@@ -754,7 +754,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_service_type(self):
arglist = [
@@ -769,7 +769,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_project(self):
project = identity_fakes_v3.FakeProject.create_one_project()
@@ -787,7 +787,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_service_type_multiple(self):
arglist = [
@@ -805,7 +805,7 @@ class TestListSubnet(TestSubnet):
'network:floatingip_agent_gateway']}
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_project_domain(self):
project = identity_fakes_v3.FakeProject.create_one_project()
@@ -825,7 +825,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_network(self):
network = network_fakes.FakeNetwork.create_one_network()
@@ -843,7 +843,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_gateway(self):
subnet = network_fakes.FakeSubnet.create_one_subnet()
@@ -861,7 +861,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_name(self):
subnet = network_fakes.FakeSubnet.create_one_subnet()
@@ -879,7 +879,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_list_subnet_range(self):
subnet = network_fakes.FakeSubnet.create_one_subnet()
@@ -897,7 +897,7 @@ class TestListSubnet(TestSubnet):
self.network.subnets.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_with_tag_options(self):
arglist = [
@@ -1244,7 +1244,7 @@ class TestShowSubnet(TestSubnet):
self._subnet.name, ignore_missing=False)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestUnsetSubnet(TestSubnet):
diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py
index eb454646..243fc76d 100644
--- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py
+++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py
@@ -133,7 +133,7 @@ class TestCreateSubnetPool(TestSubnetPool):
})
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_prefixlen_options(self):
arglist = [
@@ -163,7 +163,7 @@ class TestCreateSubnetPool(TestSubnetPool):
'name': self._subnet_pool.name,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_len_negative(self):
arglist = [
@@ -201,7 +201,7 @@ class TestCreateSubnetPool(TestSubnetPool):
'name': self._subnet_pool.name,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_address_scope_option(self):
arglist = [
@@ -224,7 +224,7 @@ class TestCreateSubnetPool(TestSubnetPool):
'name': self._subnet_pool.name,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_default_and_shared_options(self):
arglist = [
@@ -250,7 +250,7 @@ class TestCreateSubnetPool(TestSubnetPool):
'shared': True,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_description(self):
arglist = [
@@ -273,7 +273,7 @@ class TestCreateSubnetPool(TestSubnetPool):
'description': self._subnet_pool.description,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_default_quota(self):
arglist = [
@@ -294,7 +294,7 @@ class TestCreateSubnetPool(TestSubnetPool):
'default_quota': 10,
})
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def _test_create_with_tag(self, add_tags=True):
arglist = [
@@ -328,7 +328,7 @@ class TestCreateSubnetPool(TestSubnetPool):
else:
self.assertFalse(self.network.set_tags.called)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_create_with_tags(self):
self._test_create_with_tag(add_tags=True)
@@ -476,7 +476,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with()
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_pool_list_long(self):
arglist = [
@@ -491,7 +491,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with()
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
def test_subnet_pool_list_no_share(self):
arglist = [
@@ -507,7 +507,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_pool_list_share(self):
arglist = [
@@ -523,7 +523,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_pool_list_no_default(self):
arglist = [
@@ -539,7 +539,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_pool_list_default(self):
arglist = [
@@ -555,7 +555,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_pool_list_project(self):
project = identity_fakes_v3.FakeProject.create_one_project()
@@ -573,7 +573,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_pool_list_project_domain(self):
project = identity_fakes_v3.FakeProject.create_one_project()
@@ -593,7 +593,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_pool_list_name(self):
subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool()
@@ -611,7 +611,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_subnet_pool_list_address_scope(self):
addr_scope = network_fakes.FakeAddressScope.create_one_address_scope()
@@ -629,7 +629,7 @@ class TestListSubnetPool(TestSubnetPool):
self.network.subnet_pools.assert_called_once_with(**filters)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_list_with_tag_options(self):
arglist = [
@@ -654,7 +654,7 @@ class TestListSubnetPool(TestSubnetPool):
'not_any_tags': 'black,white'}
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
class TestSetSubnetPool(TestSubnetPool):
@@ -1008,7 +1008,7 @@ class TestShowSubnetPool(TestSubnetPool):
ignore_missing=False
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestUnsetSubnetPool(TestSubnetPool):
diff --git a/openstackclient/tests/unit/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py
index 0ed791a5..1808d5b7 100644
--- a/openstackclient/tests/unit/object/v1/fakes.py
+++ b/openstackclient/tests/unit/object/v1/fakes.py
@@ -14,7 +14,6 @@
#
from keystoneauth1 import session
-import six
from openstackclient.api import object_store_v1 as object_store
from openstackclient.tests.unit import utils
@@ -68,7 +67,7 @@ OBJECT = {
'last_modified': object_modified_1,
}
-object_1_content = six.b('object 1 content')
+object_1_content = b'object 1 content'
OBJECT_2 = {
'name': object_name_2,
diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py
index dd587142..7e88409f 100644
--- a/openstackclient/tests/unit/object/v1/test_object_all.py
+++ b/openstackclient/tests/unit/object/v1/test_object_all.py
@@ -12,11 +12,11 @@
#
import copy
+import io
from unittest import mock
from osc_lib import exceptions
from requests_mock.contrib import fixture
-import six
from openstackclient.object.v1 import object as object_cmds
from openstackclient.tests.unit.object.v1 import fakes as object_fakes
@@ -241,9 +241,9 @@ class TestObjectSave(TestObjectAll):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- class FakeStdout(six.BytesIO):
+ class FakeStdout(io.BytesIO):
def __init__(self):
- six.BytesIO.__init__(self)
+ io.BytesIO.__init__(self)
self.context_manager_calls = []
def __enter__(self):
diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py
index 94f4f44d..366c364e 100644
--- a/openstackclient/tests/unit/test_shell.py
+++ b/openstackclient/tests/unit/test_shell.py
@@ -13,12 +13,12 @@
# under the License.
#
+import importlib
import os
import sys
from unittest import mock
from osc_lib.tests import utils as osc_lib_test_utils
-from oslo_utils import importutils
import wrapt
from openstackclient import shell
@@ -151,12 +151,13 @@ class TestShell(osc_lib_test_utils.TestShell):
super(TestShell, self).setUp()
# TODO(dtroyer): remove this once the shell_class_patch patch is
# released in osc-lib
- self.shell_class = importutils.import_class(self.shell_class_name)
+ mod_str, _sep, class_str = self.shell_class_name.rpartition('.')
+ self.shell_class = getattr(importlib.import_module(mod_str), class_str)
def _assert_admin_token_auth(self, cmd_options, default_args):
with mock.patch(
- self.shell_class_name + ".initialize_app",
- self.app,
+ self.shell_class_name + ".initialize_app",
+ self.app,
):
_shell = osc_lib_test_utils.make_shell(
shell_class=self.shell_class,
diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py
index 4f1bc46a..39cb5614 100644
--- a/openstackclient/tests/unit/utils.py
+++ b/openstackclient/tests/unit/utils.py
@@ -14,11 +14,10 @@
# under the License.
#
+from io import StringIO
import os
-from cliff import columns as cliff_columns
import fixtures
-from six.moves import StringIO
import testtools
from openstackclient.tests.unit import fakes
@@ -85,18 +84,3 @@ class TestCommand(TestCase):
self.assertIn(attr, parsed_args)
self.assertEqual(value, getattr(parsed_args, attr))
return parsed_args
-
- def assertListItemEqual(self, expected, actual):
- self.assertEqual(len(expected), len(actual))
- for item_expected, item_actual in zip(expected, actual):
- self.assertItemEqual(item_expected, item_actual)
-
- def assertItemEqual(self, expected, actual):
- self.assertEqual(len(expected), len(actual))
- for col_expected, col_actual in zip(expected, actual):
- if isinstance(col_expected, cliff_columns.FormattableColumn):
- self.assertIsInstance(col_actual, col_expected.__class__)
- self.assertEqual(col_expected.human_readable(),
- col_actual.human_readable())
- else:
- self.assertEqual(col_expected, col_actual)
diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py
index 83c533b6..5500438b 100644
--- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py
+++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py
@@ -109,7 +109,7 @@ class TestQosCreate(TestQos):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_qos_create_with_consumer(self):
arglist = [
@@ -129,7 +129,7 @@ class TestQosCreate(TestQos):
{'consumer': self.new_qos_spec.consumer}
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_qos_create_with_properties(self):
arglist = [
@@ -155,7 +155,7 @@ class TestQosCreate(TestQos):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
class TestQosDelete(TestQos):
@@ -350,7 +350,7 @@ class TestQosList(TestQos):
self.qos_mock.list.assert_called_with()
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_qos_list_no_association(self):
self.qos_mock.reset_mock()
@@ -377,7 +377,7 @@ class TestQosList(TestQos):
format_columns.ListColumn(None),
format_columns.DictColumn(self.qos_specs[1].specs),
)
- self.assertListItemEqual(ex_data, list(data))
+ self.assertItemsEqual(ex_data, list(data))
class TestQosSet(TestQos):
@@ -454,7 +454,7 @@ class TestQosShow(TestQos):
self.qos_spec.name,
format_columns.DictColumn(self.qos_spec.specs),
)
- self.assertItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
class TestQosUnset(TestQos):
diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py
index 8bee5747..f1d46914 100644
--- a/openstackclient/tests/unit/volume/v1/test_type.py
+++ b/openstackclient/tests/unit/volume/v1/test_type.py
@@ -78,7 +78,7 @@ class TestTypeCreate(TestType):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_type_create_with_encryption(self):
encryption_info = {
@@ -139,7 +139,7 @@ class TestTypeCreate(TestType):
body,
)
self.assertEqual(encryption_columns, columns)
- self.assertItemEqual(encryption_data, data)
+ self.assertItemsEqual(encryption_data, data)
class TestTypeDelete(TestType):
@@ -270,7 +270,7 @@ class TestTypeList(TestType):
columns, data = self.cmd.take_action(parsed_args)
self.types_mock.list.assert_called_once_with()
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_type_list_with_options(self):
arglist = [
@@ -284,7 +284,7 @@ class TestTypeList(TestType):
columns, data = self.cmd.take_action(parsed_args)
self.types_mock.list.assert_called_once_with()
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
def test_type_list_with_encryption(self):
encryption_type = volume_fakes.FakeType.create_one_encryption_type(
@@ -328,7 +328,7 @@ class TestTypeList(TestType):
self.encryption_types_mock.list.assert_called_once_with()
self.types_mock.list.assert_called_once_with()
self.assertEqual(encryption_columns, columns)
- self.assertListItemEqual(encryption_data, list(data))
+ self.assertItemsEqual(encryption_data, list(data))
class TestTypeSet(TestType):
@@ -469,7 +469,7 @@ class TestTypeShow(TestType):
self.types_mock.get.assert_called_with(self.volume_type.id)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_type_show_with_encryption(self):
encryption_type = volume_fakes.FakeType.create_one_encryption_type()
@@ -513,7 +513,7 @@ class TestTypeShow(TestType):
self.types_mock.get.assert_called_with(self.volume_type.id)
self.encryption_types_mock.get.assert_called_with(self.volume_type.id)
self.assertEqual(encryption_columns, columns)
- self.assertItemEqual(encryption_data, data)
+ self.assertItemsEqual(encryption_data, data)
class TestTypeUnset(TestType):
diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py
index 25cdf92a..704a66da 100644
--- a/openstackclient/tests/unit/volume/v1/test_volume.py
+++ b/openstackclient/tests/unit/volume/v1/test_volume.py
@@ -135,7 +135,7 @@ class TestVolumeCreate(TestVolume):
None,
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_options(self):
arglist = [
@@ -179,7 +179,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_user_project_id(self):
# Return a project
@@ -226,7 +226,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_user_project_name(self):
# Return a project
@@ -273,7 +273,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_properties(self):
arglist = [
@@ -314,7 +314,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_image_id(self):
image = image_fakes.FakeImage.create_one_image()
@@ -357,7 +357,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_image_name(self):
image = image_fakes.FakeImage.create_one_image()
@@ -400,7 +400,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_with_source(self):
self.volumes_mock.get.return_value = self.new_volume
@@ -430,7 +430,7 @@ class TestVolumeCreate(TestVolume):
None,
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_with_bootable_and_readonly(self):
arglist = [
@@ -468,7 +468,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
self.volumes_mock.set_bootable.assert_called_with(
self.new_volume.id, True)
self.volumes_mock.update_readonly_flag.assert_called_with(
@@ -510,7 +510,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
self.volumes_mock.set_bootable.assert_called_with(
self.new_volume.id, False)
self.volumes_mock.update_readonly_flag.assert_called_with(
@@ -562,7 +562,7 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(2, mock_error.call_count)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
self.volumes_mock.set_bootable.assert_called_with(
self.new_volume.id, True)
self.volumes_mock.update_readonly_flag.assert_called_with(
@@ -765,7 +765,7 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_volume_list_name(self):
arglist = [
@@ -782,7 +782,7 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, tuple(columns))
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_volume_list_status(self):
arglist = [
@@ -799,7 +799,7 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, tuple(columns))
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_volume_list_all_projects(self):
arglist = [
@@ -816,7 +816,7 @@ class TestVolumeList(TestVolume):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, tuple(columns))
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_volume_list_long(self):
arglist = [
@@ -856,7 +856,7 @@ class TestVolumeList(TestVolume):
volume.AttachmentsColumn(self._volume.attachments),
format_columns.DictColumn(self._volume.metadata),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_with_limit(self):
arglist = [
@@ -881,7 +881,7 @@ class TestVolumeList(TestVolume):
'all_tenants': False, }
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.datalist, tuple(data))
+ self.assertItemsEqual(self.datalist, tuple(data))
def test_volume_list_negative_limit(self):
arglist = [
@@ -1272,7 +1272,7 @@ class TestVolumeShow(TestVolume):
self.volumes_mock.get.assert_called_with(self._volume.id)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_show_backward_compatibility(self):
arglist = [
diff --git a/openstackclient/tests/unit/volume/v1/test_volume_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py
index 20aadcd3..a7131550 100644
--- a/openstackclient/tests/unit/volume/v1/test_volume_backup.py
+++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py
@@ -100,7 +100,7 @@ class TestBackupCreate(TestBackup):
self.new_backup.description,
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_backup_create_without_name(self):
arglist = [
@@ -124,7 +124,7 @@ class TestBackupCreate(TestBackup):
self.new_backup.description,
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestBackupDelete(TestBackup):
@@ -277,7 +277,7 @@ class TestBackupList(TestBackup):
search_opts=search_opts,
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_backup_list_with_options(self):
arglist = [
@@ -309,7 +309,7 @@ class TestBackupList(TestBackup):
search_opts=search_opts,
)
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
class TestBackupRestore(TestBackup):
@@ -391,4 +391,4 @@ class TestBackupShow(TestBackup):
self.backups_mock.get.assert_called_with(self.backup.id)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py
index c3bd71e3..6bb6c029 100644
--- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py
+++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py
@@ -251,7 +251,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_consistency_group_create_from_source(self):
arglist = [
@@ -279,7 +279,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_consistency_group_create_from_snapshot(self):
arglist = [
@@ -307,7 +307,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestConsistencyGroupDelete(TestConsistencyGroup):
@@ -463,7 +463,7 @@ class TestConsistencyGroupList(TestConsistencyGroup):
self.consistencygroups_mock.list.assert_called_once_with(
detailed=True, search_opts={'all_tenants': False})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_consistency_group_list_with_all_project(self):
arglist = [
@@ -480,7 +480,7 @@ class TestConsistencyGroupList(TestConsistencyGroup):
self.consistencygroups_mock.list.assert_called_once_with(
detailed=True, search_opts={'all_tenants': True})
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_consistency_group_list_with_long(self):
arglist = [
@@ -497,7 +497,7 @@ class TestConsistencyGroupList(TestConsistencyGroup):
self.consistencygroups_mock.list.assert_called_once_with(
detailed=True, search_opts={'all_tenants': False})
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
class TestConsistencyGroupRemoveVolume(TestConsistencyGroup):
@@ -705,4 +705,4 @@ class TestConsistencyGroupShow(TestConsistencyGroup):
self.consistencygroups_mock.get.assert_called_once_with(
self.consistency_group.id)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py
index 073ec570..bc4cee8b 100644
--- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py
+++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py
@@ -112,7 +112,7 @@ class TestQosCreate(TestQos):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_qos_create_with_consumer(self):
arglist = [
@@ -133,7 +133,7 @@ class TestQosCreate(TestQos):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_qos_create_with_properties(self):
arglist = [
@@ -159,7 +159,7 @@ class TestQosCreate(TestQos):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
class TestQosDelete(TestQos):
@@ -342,7 +342,7 @@ class TestQosList(TestQos):
self.qos_mock.list.assert_called_with()
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_qos_list_no_association(self):
self.qos_mock.reset_mock()
@@ -369,7 +369,7 @@ class TestQosList(TestQos):
format_columns.ListColumn(None),
format_columns.DictColumn(self.qos_specs[1].specs),
)
- self.assertListItemEqual(ex_data, list(data))
+ self.assertItemsEqual(ex_data, list(data))
class TestQosSet(TestQos):
@@ -449,7 +449,7 @@ class TestQosShow(TestQos):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, tuple(data))
+ self.assertItemsEqual(self.data, tuple(data))
class TestQosUnset(TestQos):
diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py
index f13d0851..000464c5 100644
--- a/openstackclient/tests/unit/volume/v2/test_type.py
+++ b/openstackclient/tests/unit/volume/v2/test_type.py
@@ -93,7 +93,7 @@ class TestTypeCreate(TestType):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_type_create_private(self):
arglist = [
@@ -119,7 +119,7 @@ class TestTypeCreate(TestType):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_public_type_create_with_project(self):
arglist = [
@@ -196,7 +196,7 @@ class TestTypeCreate(TestType):
body,
)
self.assertEqual(encryption_columns, columns)
- self.assertItemEqual(encryption_data, data)
+ self.assertItemsEqual(encryption_data, data)
class TestTypeDelete(TestType):
@@ -330,7 +330,7 @@ class TestTypeList(TestType):
columns, data = self.cmd.take_action(parsed_args)
self.types_mock.list.assert_called_once_with(is_public=None)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_type_list_with_options(self):
arglist = [
@@ -348,7 +348,7 @@ class TestTypeList(TestType):
columns, data = self.cmd.take_action(parsed_args)
self.types_mock.list.assert_called_once_with(is_public=True)
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
def test_type_list_with_private_option(self):
arglist = [
@@ -365,7 +365,7 @@ class TestTypeList(TestType):
columns, data = self.cmd.take_action(parsed_args)
self.types_mock.list.assert_called_once_with(is_public=False)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_type_list_with_default_option(self):
arglist = [
@@ -383,7 +383,7 @@ class TestTypeList(TestType):
columns, data = self.cmd.take_action(parsed_args)
self.types_mock.default.assert_called_once_with()
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data_with_default_type, list(data))
+ self.assertItemsEqual(self.data_with_default_type, list(data))
def test_type_list_with_encryption(self):
encryption_type = volume_fakes.FakeType.create_one_encryption_type(
@@ -427,7 +427,7 @@ class TestTypeList(TestType):
self.encryption_types_mock.list.assert_called_once_with()
self.types_mock.list.assert_called_once_with(is_public=None)
self.assertEqual(encryption_columns, columns)
- self.assertListItemEqual(encryption_data, list(data))
+ self.assertItemsEqual(encryption_data, list(data))
class TestTypeSet(TestType):
@@ -713,7 +713,7 @@ class TestTypeShow(TestType):
self.types_mock.get.assert_called_with(self.volume_type.id)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.data, data)
+ self.assertItemsEqual(self.data, data)
def test_type_show_with_access(self):
arglist = [
@@ -746,7 +746,7 @@ class TestTypeShow(TestType):
private_type.name,
format_columns.DictColumn(private_type.extra_specs)
)
- self.assertItemEqual(private_type_data, data)
+ self.assertItemsEqual(private_type_data, data)
def test_type_show_with_list_access_exec(self):
arglist = [
@@ -778,7 +778,7 @@ class TestTypeShow(TestType):
private_type.name,
format_columns.DictColumn(private_type.extra_specs)
)
- self.assertItemEqual(private_type_data, data)
+ self.assertItemsEqual(private_type_data, data)
def test_type_show_with_encryption(self):
encryption_type = volume_fakes.FakeType.create_one_encryption_type()
@@ -824,7 +824,7 @@ class TestTypeShow(TestType):
self.types_mock.get.assert_called_with(self.volume_type.id)
self.encryption_types_mock.get.assert_called_with(self.volume_type.id)
self.assertEqual(encryption_columns, columns)
- self.assertItemEqual(encryption_data, data)
+ self.assertItemsEqual(encryption_data, data)
class TestTypeUnset(TestType):
diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py
index 4e204ad1..b9fe4e83 100644
--- a/openstackclient/tests/unit/volume/v2/test_volume.py
+++ b/openstackclient/tests/unit/volume/v2/test_volume.py
@@ -136,7 +136,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_options(self):
consistency_group = (
@@ -182,7 +182,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_properties(self):
arglist = [
@@ -218,7 +218,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_image_id(self):
image = image_fakes.FakeImage.create_one_image()
@@ -256,7 +256,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_image_name(self):
image = image_fakes.FakeImage.create_one_image()
@@ -294,7 +294,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_with_snapshot(self):
snapshot = volume_fakes.FakeSnapshot.create_one_snapshot()
@@ -331,7 +331,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
def test_volume_create_with_bootable_and_readonly(self):
arglist = [
@@ -369,7 +369,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
self.volumes_mock.set_bootable.assert_called_with(
self.new_volume.id, True)
self.volumes_mock.update_readonly_flag.assert_called_with(
@@ -411,7 +411,7 @@ class TestVolumeCreate(TestVolume):
)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
self.volumes_mock.set_bootable.assert_called_with(
self.new_volume.id, False)
self.volumes_mock.update_readonly_flag.assert_called_with(
@@ -463,7 +463,7 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(2, mock_error.call_count)
self.assertEqual(self.columns, columns)
- self.assertItemEqual(self.datalist, data)
+ self.assertItemsEqual(self.datalist, data)
self.volumes_mock.set_bootable.assert_called_with(
self.new_volume.id, True)
self.volumes_mock.update_readonly_flag.assert_called_with(
@@ -680,7 +680,7 @@ class TestVolumeList(TestVolume):
self.mock_volume.size,
volume.AttachmentsColumn(self.mock_volume.attachments),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_project(self):
arglist = [
@@ -720,7 +720,7 @@ class TestVolumeList(TestVolume):
self.mock_volume.size,
volume.AttachmentsColumn(self.mock_volume.attachments),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_project_domain(self):
arglist = [
@@ -762,7 +762,7 @@ class TestVolumeList(TestVolume):
self.mock_volume.size,
volume.AttachmentsColumn(self.mock_volume.attachments),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_user(self):
arglist = [
@@ -801,7 +801,7 @@ class TestVolumeList(TestVolume):
self.mock_volume.size,
volume.AttachmentsColumn(self.mock_volume.attachments),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_user_domain(self):
arglist = [
@@ -843,7 +843,7 @@ class TestVolumeList(TestVolume):
self.mock_volume.size,
volume.AttachmentsColumn(self.mock_volume.attachments),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_name(self):
arglist = [
@@ -883,7 +883,7 @@ class TestVolumeList(TestVolume):
self.mock_volume.size,
volume.AttachmentsColumn(self.mock_volume.attachments),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_status(self):
arglist = [
@@ -923,7 +923,7 @@ class TestVolumeList(TestVolume):
self.mock_volume.size,
volume.AttachmentsColumn(self.mock_volume.attachments),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_all_projects(self):
arglist = [
@@ -963,7 +963,7 @@ class TestVolumeList(TestVolume):
self.mock_volume.size,
volume.AttachmentsColumn(self.mock_volume.attachments),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_long(self):
arglist = [
@@ -1017,7 +1017,7 @@ class TestVolumeList(TestVolume):
volume.AttachmentsColumn(self.mock_volume.attachments),
format_columns.DictColumn(self.mock_volume.metadata),
), )
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_with_marker_and_limit(self):
arglist = [
@@ -1056,7 +1056,7 @@ class TestVolumeList(TestVolume):
'name': None,
'all_tenants': False, }
)
- self.assertListItemEqual(datalist, tuple(data))
+ self.assertItemsEqual(datalist, tuple(data))
def test_volume_list_negative_limit(self):
arglist = [
@@ -1450,7 +1450,7 @@ class TestVolumeShow(TestVolume):
volume_fakes.FakeVolume.get_volume_columns(self._volume),
columns)
- self.assertItemEqual(
+ self.assertItemsEqual(
volume_fakes.FakeVolume.get_volume_data(self._volume),
data)
diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backend.py b/openstackclient/tests/unit/volume/v2/test_volume_backend.py
index db188660..d9ac2c96 100644
--- a/openstackclient/tests/unit/volume/v2/test_volume_backend.py
+++ b/openstackclient/tests/unit/volume/v2/test_volume_backend.py
@@ -65,7 +65,7 @@ class TestShowVolumeCapability(volume_fakes.TestVolume):
# confirming if all expected values are present in the result.
for cap in data:
- self.assertTrue(cap[0] in capabilities)
+ self.assertIn(cap[0], capabilities)
# checking if proper call was made to get capabilities
self.capability_mock.get.assert_called_with(
diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py
index 4e1f7ee1..13513ed8 100644
--- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py
+++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py
@@ -314,7 +314,7 @@ class TestBackupList(TestBackup):
limit=None,
)
self.assertEqual(self.columns, columns)
- self.assertListItemEqual(self.data, list(data))
+ self.assertItemsEqual(self.data, list(data))
def test_backup_list_with_options(self):
arglist = [
@@ -353,7 +353,7 @@ class TestBackupList(TestBackup):
limit=3,
)
self.assertEqual(self.columns_long, columns)
- self.assertListItemEqual(self.data_long, list(data))
+ self.assertItemsEqual(self.data_long, list(data))
class TestBackupRestore(TestBackup):
diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py
index 1fbfaaee..64e8b9f3 100644
--- a/openstackclient/volume/client.py
+++ b/openstackclient/volume/client.py
@@ -29,6 +29,7 @@ API_VERSIONS = {
"1": "cinderclient.v1.client.Client",
"2": "cinderclient.v2.client.Client",
"3": "cinderclient.v3.client.Client",
+ "3.42": "cinderclient.v3.client.Client",
}
@@ -47,14 +48,19 @@ def make_client(instance):
except Exception:
del API_VERSIONS['1']
- if instance._api_version[API_NAME] == '1':
+ version = instance._api_version[API_NAME]
+ from cinderclient import api_versions
+ # convert to APIVersion object
+ version = api_versions.get_api_version(version)
+
+ if version.ver_major == '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,
- instance._api_version[API_NAME],
+ version.ver_major,
API_VERSIONS
)
LOG.debug('Instantiating volume client: %s', volume_client)
@@ -76,6 +82,7 @@ def make_client(instance):
http_log_debug=http_log_debug,
region_name=instance.region_name,
endpoint_override=endpoint_override,
+ api_version=version,
**kwargs
)
diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py
index 1e0cb183..cab0b2f4 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -605,14 +605,16 @@ class SetVolume(command.Command):
result = 0
if parsed_args.size:
try:
- if volume.status != 'available':
- msg = (_("Volume is in %s state, it must be available "
- "before size can be extended") % volume.status)
- raise exceptions.CommandError(msg)
if parsed_args.size <= volume.size:
msg = (_("New size must be greater than %s GB")
% volume.size)
raise exceptions.CommandError(msg)
+ if volume.status != 'available' and \
+ not volume_client.api_version.matches('3.42'):
+
+ msg = (_("Volume is in %s state, it must be available "
+ "before size can be extended") % volume.status)
+ raise exceptions.CommandError(msg)
volume_client.volumes.extend(volume.id, parsed_args.size)
except Exception as e:
LOG.error(_("Failed to set volume size: %s"), e)