summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/compute/v2/hypervisor.py137
-rw-r--r--openstackclient/compute/v2/server.py19
-rw-r--r--openstackclient/compute/v2/usage.py69
-rw-r--r--openstackclient/tests/functional/base.py62
-rw-r--r--openstackclient/tests/functional/common/test_args.py25
-rw-r--r--openstackclient/tests/functional/common/test_availability_zone.py8
-rw-r--r--openstackclient/tests/functional/common/test_configuration.py24
-rw-r--r--openstackclient/tests/functional/common/test_extension.py56
-rw-r--r--openstackclient/tests/functional/common/test_module.py16
-rw-r--r--openstackclient/tests/functional/common/test_quota.py99
-rw-r--r--openstackclient/tests/functional/common/test_versions.py6
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py134
-rw-r--r--openstackclient/tests/unit/compute/v2/test_hypervisor.py230
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py149
-rw-r--r--openstackclient/tests/unit/compute/v2/test_usage.py45
-rw-r--r--openstackclient/volume/v3/volume_attachment.py2
16 files changed, 636 insertions, 445 deletions
diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py
index 5f7497b5..d4b4003b 100644
--- a/openstackclient/compute/v2/hypervisor.py
+++ b/openstackclient/compute/v2/hypervisor.py
@@ -18,8 +18,8 @@
import json
import re
-from novaclient import api_versions
from novaclient import exceptions as nova_exceptions
+from openstack import utils as sdk_utils
from osc_lib.cli import format_columns
from osc_lib.command import command
from osc_lib import exceptions
@@ -28,11 +28,44 @@ from osc_lib import utils
from openstackclient.i18n import _
+def _get_hypervisor_columns(item, client):
+ column_map = {'name': 'hypervisor_hostname'}
+ hidden_columns = ['location', 'servers']
+
+ if sdk_utils.supports_microversion(client, '2.88'):
+ hidden_columns.extend([
+ 'current_workload',
+ 'disk_available',
+ 'local_disk_free',
+ 'local_disk_size',
+ 'local_disk_used',
+ 'memory_free',
+ 'memory_size',
+ 'memory_used',
+ 'running_vms',
+ 'vcpus_used',
+ 'vcpus',
+ ])
+ else:
+ column_map.update({
+ 'disk_available': 'disk_available_least',
+ 'local_disk_free': 'free_disk_gb',
+ 'local_disk_size': 'local_gb',
+ 'local_disk_used': 'local_gb_used',
+ 'memory_free': 'free_ram_mb',
+ 'memory_used': 'memory_mb_used',
+ 'memory_size': 'memory_mb',
+ })
+
+ return utils.get_osc_show_columns_for_sdk_resource(
+ item, column_map, hidden_columns)
+
+
class ListHypervisor(command.Lister):
_description = _("List hypervisors")
def get_parser(self, prog_name):
- parser = super(ListHypervisor, self).get_parser(prog_name)
+ parser = super().get_parser(prog_name)
parser.add_argument(
'--matching',
metavar='<hostname>',
@@ -67,7 +100,7 @@ class ListHypervisor(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
list_opts = {}
@@ -78,7 +111,7 @@ class ListHypervisor(command.Lister):
raise exceptions.CommandError(msg)
if parsed_args.marker:
- if compute_client.api_version < api_versions.APIVersion('2.33'):
+ if not sdk_utils.supports_microversion(compute_client, '2.33'):
msg = _(
'--os-compute-api-version 2.33 or greater is required to '
'support the --marker option'
@@ -87,7 +120,7 @@ class ListHypervisor(command.Lister):
list_opts['marker'] = parsed_args.marker
if parsed_args.limit:
- if compute_client.api_version < api_versions.APIVersion('2.33'):
+ if not sdk_utils.supports_microversion(compute_client, '2.33'):
msg = _(
'--os-compute-api-version 2.33 or greater is required to '
'support the --limit option'
@@ -95,23 +128,43 @@ class ListHypervisor(command.Lister):
raise exceptions.CommandError(msg)
list_opts['limit'] = parsed_args.limit
- columns = (
+ column_headers = (
"ID",
"Hypervisor Hostname",
"Hypervisor Type",
"Host IP",
"State"
)
+ columns = (
+ 'id',
+ 'name',
+ 'hypervisor_type',
+ 'host_ip',
+ 'state'
+ )
if parsed_args.long:
- columns += ("vCPUs Used", "vCPUs", "Memory MB Used", "Memory MB")
+ if not sdk_utils.supports_microversion(compute_client, '2.88'):
+ column_headers += (
+ 'vCPUs Used',
+ 'vCPUs',
+ 'Memory MB Used',
+ 'Memory MB'
+ )
+ columns += (
+ 'vcpus_used',
+ 'vcpus',
+ 'memory_used',
+ 'memory_size'
+ )
if parsed_args.matching:
- data = compute_client.hypervisors.search(parsed_args.matching)
+ data = compute_client.find_hypervisor(
+ parsed_args.matching, ignore_missing=False)
else:
- data = compute_client.hypervisors.list(**list_opts)
+ data = compute_client.hypervisors(**list_opts, details=True)
return (
- columns,
+ column_headers,
(utils.get_item_properties(s, columns) for s in data),
)
@@ -120,7 +173,7 @@ class ShowHypervisor(command.ShowOne):
_description = _("Display hypervisor details")
def get_parser(self, prog_name):
- parser = super(ShowHypervisor, self).get_parser(prog_name)
+ parser = super().get_parser(prog_name)
parser.add_argument(
"hypervisor",
metavar="<hypervisor>",
@@ -129,20 +182,25 @@ class ShowHypervisor(command.ShowOne):
return parser
def take_action(self, parsed_args):
- compute_client = self.app.client_manager.compute
- hypervisor = utils.find_resource(compute_client.hypervisors,
- parsed_args.hypervisor)._info.copy()
+ compute_client = self.app.client_manager.sdk_connection.compute
+ hypervisor = compute_client.find_hypervisor(
+ parsed_args.hypervisor, ignore_missing=False).copy()
+
+ # Some of the properties in the hypervisor object need to be processed
+ # before they get reported to the user. We spend this section
+ # extracting the relevant details to be reported by modifying our
+ # copy of the hypervisor object.
+ aggregates = compute_client.aggregates()
+ hypervisor['aggregates'] = list()
+ service_details = hypervisor['service_details']
- aggregates = compute_client.aggregates.list()
- hypervisor["aggregates"] = list()
if aggregates:
# Hypervisors in nova cells are prefixed by "<cell>@"
- if "@" in hypervisor['service']['host']:
- cell, service_host = hypervisor['service']['host'].split(
- '@', 1)
+ if "@" in service_details['host']:
+ cell, service_host = service_details['host'].split('@', 1)
else:
cell = None
- service_host = hypervisor['service']['host']
+ service_host = service_details['host']
if cell:
# The host aggregates are also prefixed by "<cell>@"
@@ -154,42 +212,45 @@ class ShowHypervisor(command.ShowOne):
member_of = [aggregate.name
for aggregate in aggregates
if service_host in aggregate.hosts]
- hypervisor["aggregates"] = member_of
+ hypervisor['aggregates'] = member_of
try:
- uptime = compute_client.hypervisors.uptime(hypervisor['id'])._info
+ if sdk_utils.supports_microversion(compute_client, '2.88'):
+ uptime = hypervisor['uptime'] or ''
+ del hypervisor['uptime']
+ else:
+ del hypervisor['uptime']
+ uptime = compute_client.get_hypervisor_uptime(
+ hypervisor['id'])['uptime']
# Extract data from uptime value
# format: 0 up 0, 0 users, load average: 0, 0, 0
# example: 17:37:14 up 2:33, 3 users,
# load average: 0.33, 0.36, 0.34
m = re.match(
r"\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)",
- uptime['uptime'])
+ uptime)
if m:
- hypervisor["host_time"] = m.group(1)
- hypervisor["uptime"] = m.group(2)
- hypervisor["users"] = m.group(3)
- hypervisor["load_average"] = m.group(4)
+ hypervisor['host_time'] = m.group(1)
+ hypervisor['uptime'] = m.group(2)
+ hypervisor['users'] = m.group(3)
+ hypervisor['load_average'] = m.group(4)
except nova_exceptions.HTTPNotImplemented:
pass
- hypervisor["service_id"] = hypervisor["service"]["id"]
- hypervisor["service_host"] = hypervisor["service"]["host"]
- del hypervisor["service"]
+ hypervisor['service_id'] = service_details['id']
+ hypervisor['service_host'] = service_details['host']
+ del hypervisor['service_details']
- if compute_client.api_version < api_versions.APIVersion('2.28'):
+ if not sdk_utils.supports_microversion(compute_client, '2.28'):
# microversion 2.28 transformed this to a JSON blob rather than a
# string; on earlier fields, do this manually
- if hypervisor['cpu_info']:
- hypervisor['cpu_info'] = json.loads(hypervisor['cpu_info'])
- else:
- hypervisor['cpu_info'] = {}
-
- columns = tuple(sorted(hypervisor))
+ hypervisor['cpu_info'] = json.loads(hypervisor['cpu_info'] or '{}')
+ display_columns, columns = _get_hypervisor_columns(
+ hypervisor, compute_client)
data = utils.get_dict_properties(
hypervisor, columns,
formatters={
'cpu_info': format_columns.DictColumn,
})
- return (columns, data)
+ return display_columns, data
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 39b2bdc8..609faf5a 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -721,11 +721,6 @@ class NICAction(argparse.Action):
if getattr(namespace, self.dest, None) is None:
setattr(namespace, self.dest, [])
- # Handle the special auto/none cases
- if values in ('auto', 'none'):
- getattr(namespace, self.dest).append(values)
- return
-
if self.key:
if ',' in values or '=' in values:
msg = _(
@@ -735,6 +730,12 @@ class NICAction(argparse.Action):
raise argparse.ArgumentTypeError(msg % values)
values = '='.join([self.key, values])
+ else:
+ # Handle the special auto/none cases but only when a key isn't set
+ # (otherwise those could be valid values for the key)
+ if values in ('auto', 'none'):
+ getattr(namespace, self.dest).append(values)
+ return
# We don't include 'tag' here by default since that requires a
# particular microversion
@@ -1614,6 +1615,14 @@ class CreateServer(command.ShowOne):
)
raise exceptions.CommandError(msg)
+ if compute_client.api_version < api_versions.APIVersion('2.37'):
+ msg = _(
+ '--os-compute-api-version 2.37 or greater is '
+ 'required to support explicit auto-allocation of a '
+ 'network or to disable network allocation'
+ )
+ raise exceptions.CommandError(msg)
+
nics = nics[0]
else:
for nic in nics:
diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py
index 69fa04e8..86f538a7 100644
--- a/openstackclient/compute/v2/usage.py
+++ b/openstackclient/compute/v2/usage.py
@@ -15,12 +15,10 @@
"""Usage action implementations"""
-import collections
import datetime
import functools
from cliff import columns as cliff_columns
-from novaclient import api_versions
from osc_lib.command import command
from osc_lib import utils
@@ -58,7 +56,7 @@ class ProjectColumn(cliff_columns.FormattableColumn):
class CountColumn(cliff_columns.FormattableColumn):
def human_readable(self):
- return len(self._value)
+ return len(self._value) if self._value is not None else None
class FloatColumn(cliff_columns.FormattableColumn):
@@ -69,7 +67,7 @@ class FloatColumn(cliff_columns.FormattableColumn):
def _formatters(project_cache):
return {
- 'tenant_id': functools.partial(
+ 'project_id': functools.partial(
ProjectColumn, project_cache=project_cache),
'server_usages': CountColumn,
'total_memory_mb_usage': FloatColumn,
@@ -102,10 +100,10 @@ def _merge_usage(usage, next_usage):
def _merge_usage_list(usages, next_usage_list):
for next_usage in next_usage_list:
- if next_usage.tenant_id in usages:
- _merge_usage(usages[next_usage.tenant_id], next_usage)
+ if next_usage.project_id in usages:
+ _merge_usage(usages[next_usage.project_id], next_usage)
else:
- usages[next_usage.tenant_id] = next_usage
+ usages[next_usage.project_id] = next_usage
class ListUsage(command.Lister):
@@ -138,9 +136,9 @@ class ListUsage(command.Lister):
else:
return project
- compute_client = self.app.client_manager.compute
+ compute_client = self.app.client_manager.sdk_connection.compute
columns = (
- "tenant_id",
+ "project_id",
"server_usages",
"total_memory_mb_usage",
"total_vcpus_usage",
@@ -154,36 +152,25 @@ class ListUsage(command.Lister):
"Disk GB-Hours"
)
- dateformat = "%Y-%m-%d"
+ date_cli_format = "%Y-%m-%d"
+ date_api_format = "%Y-%m-%dT%H:%M:%S"
now = datetime.datetime.utcnow()
if parsed_args.start:
- start = datetime.datetime.strptime(parsed_args.start, dateformat)
+ start = datetime.datetime.strptime(
+ parsed_args.start, date_cli_format)
else:
start = now - datetime.timedelta(weeks=4)
if parsed_args.end:
- end = datetime.datetime.strptime(parsed_args.end, dateformat)
+ end = datetime.datetime.strptime(parsed_args.end, date_cli_format)
else:
end = now + datetime.timedelta(days=1)
- if compute_client.api_version < api_versions.APIVersion("2.40"):
- usage_list = compute_client.usage.list(start, end, detailed=True)
- else:
- # If the number of instances used to calculate the usage is greater
- # than CONF.api.max_limit, the usage will be split across multiple
- # requests and the responses will need to be merged back together.
- usages = collections.OrderedDict()
- usage_list = compute_client.usage.list(start, end, detailed=True)
- _merge_usage_list(usages, usage_list)
- marker = _get_usage_list_marker(usage_list)
- while marker:
- next_usage_list = compute_client.usage.list(
- start, end, detailed=True, marker=marker)
- marker = _get_usage_list_marker(next_usage_list)
- if marker:
- _merge_usage_list(usages, next_usage_list)
- usage_list = list(usages.values())
+ usage_list = list(compute_client.usages(
+ start=start.strftime(date_api_format),
+ end=end.strftime(date_api_format),
+ detailed=True))
# Cache the project list
project_cache = {}
@@ -196,8 +183,8 @@ class ListUsage(command.Lister):
if parsed_args.formatter == 'table' and len(usage_list) > 0:
self.app.stdout.write(_("Usage from %(start)s to %(end)s: \n") % {
- "start": start.strftime(dateformat),
- "end": end.strftime(dateformat),
+ "start": start.strftime(date_cli_format),
+ "end": end.strftime(date_cli_format),
})
return (
@@ -239,17 +226,19 @@ class ShowUsage(command.ShowOne):
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- compute_client = self.app.client_manager.compute
- dateformat = "%Y-%m-%d"
+ compute_client = self.app.client_manager.sdk_connection.compute
+ date_cli_format = "%Y-%m-%d"
+ date_api_format = "%Y-%m-%dT%H:%M:%S"
now = datetime.datetime.utcnow()
if parsed_args.start:
- start = datetime.datetime.strptime(parsed_args.start, dateformat)
+ start = datetime.datetime.strptime(
+ parsed_args.start, date_cli_format)
else:
start = now - datetime.timedelta(weeks=4)
if parsed_args.end:
- end = datetime.datetime.strptime(parsed_args.end, dateformat)
+ end = datetime.datetime.strptime(parsed_args.end, date_cli_format)
else:
end = now + datetime.timedelta(days=1)
@@ -262,19 +251,21 @@ class ShowUsage(command.ShowOne):
# Get the project from the current auth
project = self.app.client_manager.auth_ref.project_id
- usage = compute_client.usage.get(project, start, end)
+ usage = compute_client.get_usage(
+ project=project, start=start.strftime(date_api_format),
+ end=end.strftime(date_api_format))
if parsed_args.formatter == 'table':
self.app.stdout.write(_(
"Usage from %(start)s to %(end)s on project %(project)s: \n"
) % {
- "start": start.strftime(dateformat),
- "end": end.strftime(dateformat),
+ "start": start.strftime(date_cli_format),
+ "end": end.strftime(date_cli_format),
"project": project,
})
columns = (
- "tenant_id",
+ "project_id",
"server_usages",
"total_memory_mb_usage",
"total_vcpus_usage",
diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py
index b6867f81..0c430267 100644
--- a/openstackclient/tests/functional/base.py
+++ b/openstackclient/tests/functional/base.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import logging
import os
import shlex
@@ -48,33 +49,52 @@ def execute(cmd, fail_ok=False, merge_stderr=False):
class TestCase(testtools.TestCase):
@classmethod
- def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False):
+ def openstack(
+ cls,
+ cmd,
+ *,
+ cloud=ADMIN_CLOUD,
+ fail_ok=False,
+ parse_output=False,
+ ):
"""Executes openstackclient command for the given action
- NOTE(dtroyer): There is a subtle distinction between passing
- cloud=None and cloud='': for compatibility reasons passing
- cloud=None continues to include the option '--os-auth-type none'
- in the command while passing cloud='' omits the '--os-auth-type'
- option completely to let the default handlers be invoked.
+ :param cmd: A string representation of the command to execute.
+ :param cloud: The cloud to execute against. This can be a string, empty
+ string, or None. A string results in '--os-auth-type $cloud', an
+ empty string results in the '--os-auth-type' option being
+ omitted, and None resuts in '--os-auth-type none' for legacy
+ reasons.
+ :param fail_ok: If failure is permitted. If False (default), a command
+ failure will result in `~tempest.lib.exceptions.CommandFailed`
+ being raised.
+ :param parse_output: If true, pass the '-f json' parameter and decode
+ the output.
+ :returns: The output from the command.
+ :raises: `~tempest.lib.exceptions.CommandFailed` if the command failed
+ and ``fail_ok`` was ``False``.
"""
+ auth_args = []
if cloud is None:
# Execute command with no auth
- return execute(
- 'openstack --os-auth-type none ' + cmd,
- fail_ok=fail_ok
- )
- elif cloud == '':
- # Execute command with no auth options at all
- return execute(
- 'openstack ' + cmd,
- fail_ok=fail_ok
- )
- else:
+ auth_args.append('--os-auth-type none')
+ elif cloud != '':
# Execute command with an explicit cloud specified
- return execute(
- 'openstack --os-cloud=' + cloud + ' ' + cmd,
- fail_ok=fail_ok
- )
+ auth_args.append(f'--os-cloud {cloud}')
+
+ format_args = []
+ if parse_output:
+ format_args.append('-f json')
+
+ output = execute(
+ ' '.join(['openstack'] + auth_args + [cmd] + format_args),
+ fail_ok=fail_ok,
+ )
+
+ if parse_output:
+ return json.loads(output)
+ else:
+ return output
@classmethod
def is_service_enabled(cls, service, version=None):
diff --git a/openstackclient/tests/functional/common/test_args.py b/openstackclient/tests/functional/common/test_args.py
index 02cad6c1..1f5ecc1c 100644
--- a/openstackclient/tests/functional/common/test_args.py
+++ b/openstackclient/tests/functional/common/test_args.py
@@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
-
from tempest.lib import exceptions as tempest_exc
from openstackclient.tests.functional import base
@@ -21,10 +19,11 @@ class ArgumentTests(base.TestCase):
"""Functional tests for command line arguments"""
def test_default_auth_type(self):
- cmd_output = json.loads(self.openstack(
- 'configuration show -f json',
+ cmd_output = self.openstack(
+ 'configuration show',
cloud='',
- ))
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertIn(
'auth_type',
@@ -36,10 +35,11 @@ class ArgumentTests(base.TestCase):
)
def test_auth_type_none(self):
- cmd_output = json.loads(self.openstack(
- 'configuration show -f json',
+ cmd_output = self.openstack(
+ 'configuration show',
cloud=None,
- ))
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertIn(
'auth_type',
@@ -54,7 +54,7 @@ class ArgumentTests(base.TestCase):
# Make sure token_endpoint is really gone
try:
self.openstack(
- 'configuration show -f json --os-auth-type token_endpoint',
+ 'configuration show --os-auth-type token_endpoint',
cloud=None,
)
except tempest_exc.CommandFailed as e:
@@ -64,10 +64,11 @@ class ArgumentTests(base.TestCase):
self.fail('CommandFailed should be raised')
def test_auth_type_password_opt(self):
- cmd_output = json.loads(self.openstack(
- 'configuration show -f json --os-auth-type password',
+ cmd_output = self.openstack(
+ 'configuration show --os-auth-type password',
cloud=None,
- ))
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertIn(
'auth_type',
diff --git a/openstackclient/tests/functional/common/test_availability_zone.py b/openstackclient/tests/functional/common/test_availability_zone.py
index 025da95c..f319ffc5 100644
--- a/openstackclient/tests/functional/common/test_availability_zone.py
+++ b/openstackclient/tests/functional/common/test_availability_zone.py
@@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
-
from openstackclient.tests.functional import base
@@ -19,8 +17,10 @@ class AvailabilityZoneTests(base.TestCase):
"""Functional tests for availability zone. """
def test_availability_zone_list(self):
- cmd_output = json.loads(self.openstack(
- 'availability zone list -f json'))
+ cmd_output = self.openstack(
+ 'availability zone list',
+ parse_output=True,
+ )
zones = [x['Zone Name'] for x in cmd_output]
self.assertIn(
'internal',
diff --git a/openstackclient/tests/functional/common/test_configuration.py b/openstackclient/tests/functional/common/test_configuration.py
index 17e0f45d..614b3e46 100644
--- a/openstackclient/tests/functional/common/test_configuration.py
+++ b/openstackclient/tests/functional/common/test_configuration.py
@@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
import os
from openstackclient.common import configuration
@@ -30,9 +29,7 @@ class ConfigurationTests(base.TestCase):
items = self.parse_listing(raw_output)
self.assert_table_structure(items, BASIC_CONFIG_HEADERS)
- cmd_output = json.loads(self.openstack(
- 'configuration show -f json'
- ))
+ cmd_output = self.openstack('configuration show', parse_output=True)
self.assertEqual(
configuration.REDACTED,
cmd_output['auth.password']
@@ -43,18 +40,18 @@ class ConfigurationTests(base.TestCase):
)
# Test show --mask
- cmd_output = json.loads(self.openstack(
- 'configuration show --mask -f json'
- ))
+ cmd_output = self.openstack(
+ 'configuration show --mask', parse_output=True,
+ )
self.assertEqual(
configuration.REDACTED,
cmd_output['auth.password']
)
# Test show --unmask
- cmd_output = json.loads(self.openstack(
- 'configuration show --unmask -f json'
- ))
+ cmd_output = self.openstack(
+ 'configuration show --unmask', parse_output=True,
+ )
# If we are using os-client-config, this will not be set. Rather than
# parse clouds.yaml to get the right value, just make sure
# we are not getting redacted.
@@ -84,10 +81,11 @@ class ConfigurationTestsNoAuth(base.TestCase):
items = self.parse_listing(raw_output)
self.assert_table_structure(items, BASIC_CONFIG_HEADERS)
- cmd_output = json.loads(self.openstack(
- 'configuration show -f json',
+ cmd_output = self.openstack(
+ 'configuration show',
cloud=None,
- ))
+ parse_output=True,
+ )
self.assertNotIn(
'auth.password',
cmd_output,
diff --git a/openstackclient/tests/functional/common/test_extension.py b/openstackclient/tests/functional/common/test_extension.py
index 92efabef..8784c55b 100644
--- a/openstackclient/tests/functional/common/test_extension.py
+++ b/openstackclient/tests/functional/common/test_extension.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
-
from tempest.lib import exceptions as tempest_exc
from openstackclient.tests.functional import base
@@ -30,11 +28,11 @@ class ExtensionTests(base.TestCase):
def test_extension_list_compute(self):
"""Test compute extension list"""
- json_output = json.loads(self.openstack(
- 'extension list -f json ' +
- '--compute'
- ))
- name_list = [item.get('Name') for item in json_output]
+ output = self.openstack(
+ 'extension list --compute',
+ parse_output=True,
+ )
+ name_list = [item.get('Name') for item in output]
self.assertIn(
'ImageSize',
name_list,
@@ -42,11 +40,11 @@ class ExtensionTests(base.TestCase):
def test_extension_list_volume(self):
"""Test volume extension list"""
- json_output = json.loads(self.openstack(
- 'extension list -f json ' +
- '--volume'
- ))
- name_list = [item.get('Name') for item in json_output]
+ output = self.openstack(
+ 'extension list --volume',
+ parse_output=True,
+ )
+ name_list = [item.get('Name') for item in output]
self.assertIn(
'TypesManage',
name_list,
@@ -57,43 +55,29 @@ class ExtensionTests(base.TestCase):
if not self.haz_network:
self.skipTest("No Network service present")
- json_output = json.loads(self.openstack(
- 'extension list -f json ' +
- '--network'
- ))
- name_list = [item.get('Name') for item in json_output]
+ output = self.openstack(
+ 'extension list --network',
+ parse_output=True,
+ )
+ name_list = [item.get('Name') for item in output]
self.assertIn(
'Default Subnetpools',
name_list,
)
- # NOTE(dtroyer): Only network extensions are currently supported but
- # I am going to leave this here anyway as a reminder
- # fix that.
- # def test_extension_show_compute(self):
- # """Test compute extension show"""
- # json_output = json.loads(self.openstack(
- # 'extension show -f json ' +
- # 'ImageSize'
- # ))
- # self.assertEqual(
- # 'OS-EXT-IMG-SIZE',
- # json_output.get('Alias'),
- # )
-
def test_extension_show_network(self):
"""Test network extension show"""
if not self.haz_network:
self.skipTest("No Network service present")
name = 'agent'
- json_output = json.loads(self.openstack(
- 'extension show -f json ' +
- name
- ))
+ output = self.openstack(
+ 'extension show ' + name,
+ parse_output=True,
+ )
self.assertEqual(
name,
- json_output.get('alias'),
+ output.get('alias'),
)
def test_extension_show_not_exist(self):
diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py
index 41aabb7f..967d3b49 100644
--- a/openstackclient/tests/functional/common/test_module.py
+++ b/openstackclient/tests/functional/common/test_module.py
@@ -11,9 +11,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-#
-
-import json
from openstackclient.tests.functional import base
@@ -31,14 +28,14 @@ class ModuleTest(base.TestCase):
def test_module_list(self):
# Test module list
- cmd_output = json.loads(self.openstack('module list -f json'))
+ cmd_output = self.openstack('module list', parse_output=True)
for one_module in self.CLIENTS:
self.assertIn(one_module, cmd_output.keys())
for one_module in self.LIBS:
self.assertNotIn(one_module, cmd_output.keys())
# Test module list --all
- cmd_output = json.loads(self.openstack('module list --all -f json'))
+ cmd_output = self.openstack('module list --all', parse_output=True)
for one_module in self.CLIENTS + self.LIBS:
self.assertIn(one_module, cmd_output.keys())
@@ -56,7 +53,7 @@ class CommandTest(base.TestCase):
]
def test_command_list_no_option(self):
- cmd_output = json.loads(self.openstack('command list -f json'))
+ cmd_output = self.openstack('command list', parse_output=True)
group_names = [each.get('Command Group') for each in cmd_output]
for one_group in self.GROUPS:
self.assertIn(one_group, group_names)
@@ -70,9 +67,10 @@ class CommandTest(base.TestCase):
'compute.v2'
]
for each_input in input_groups:
- cmd_output = json.loads(self.openstack(
- 'command list --group %s -f json' % each_input
- ))
+ cmd_output = self.openstack(
+ 'command list --group %s' % each_input,
+ parse_output=True,
+ )
group_names = [each.get('Command Group') for each in cmd_output]
for each_name in group_names:
self.assertIn(each_input, each_name)
diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py
index 9089cba5..6e48df1d 100644
--- a/openstackclient/tests/functional/common/test_quota.py
+++ b/openstackclient/tests/functional/common/test_quota.py
@@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
import uuid
from tempest.lib import exceptions
@@ -36,9 +35,10 @@ class QuotaTests(base.TestCase):
def test_quota_list_details_compute(self):
expected_headers = ["Resource", "In Use", "Reserved", "Limit"]
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --detail --compute'
- ))
+ cmd_output = self.openstack(
+ 'quota list --detail --compute',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
resources = []
for row in cmd_output:
@@ -52,9 +52,10 @@ class QuotaTests(base.TestCase):
def test_quota_list_details_network(self):
expected_headers = ["Resource", "In Use", "Reserved", "Limit"]
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --detail --network'
- ))
+ cmd_output = self.openstack(
+ 'quota list --detail --network',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
resources = []
for row in cmd_output:
@@ -70,9 +71,10 @@ class QuotaTests(base.TestCase):
if not self.haz_network:
self.skipTest("No Network service present")
self.openstack('quota set --networks 40 ' + self.PROJECT_NAME)
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --network'
- ))
+ cmd_output = self.openstack(
+ 'quota list --network',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertEqual(
40,
@@ -81,9 +83,10 @@ class QuotaTests(base.TestCase):
def test_quota_list_compute_option(self):
self.openstack('quota set --instances 30 ' + self.PROJECT_NAME)
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --compute'
- ))
+ cmd_output = self.openstack(
+ 'quota list --compute',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertEqual(
30,
@@ -92,9 +95,10 @@ class QuotaTests(base.TestCase):
def test_quota_list_volume_option(self):
self.openstack('quota set --volumes 20 ' + self.PROJECT_NAME)
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --volume'
- ))
+ cmd_output = self.openstack(
+ 'quota list --volume',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertEqual(
20,
@@ -111,9 +115,10 @@ class QuotaTests(base.TestCase):
network_option +
self.PROJECT_NAME
)
- cmd_output = json.loads(self.openstack(
- 'quota show -f json ' + self.PROJECT_NAME
- ))
+ cmd_output = self.openstack(
+ 'quota show ' + self.PROJECT_NAME,
+ parse_output=True,
+ )
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
self.assertIsNotNone(cmd_output)
self.assertEqual(
@@ -131,9 +136,10 @@ class QuotaTests(base.TestCase):
)
# Check default quotas
- cmd_output = json.loads(self.openstack(
- 'quota show -f json --default'
- ))
+ cmd_output = self.openstack(
+ 'quota show --default',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
# We don't necessarily know the default quotas, we're checking the
# returned attributes
@@ -148,9 +154,10 @@ class QuotaTests(base.TestCase):
'quota set --key-pairs 33 --snapshots 43 ' +
'--class default'
)
- cmd_output = json.loads(self.openstack(
- 'quota show -f json --class default'
- ))
+ cmd_output = self.openstack(
+ 'quota show --class default',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
self.assertEqual(
@@ -163,9 +170,10 @@ class QuotaTests(base.TestCase):
)
# Check default quota class
- cmd_output = json.loads(self.openstack(
- 'quota show -f json --class'
- ))
+ cmd_output = self.openstack(
+ 'quota show --class',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
# We don't necessarily know the default quotas, we're checking the
# returned attributes
@@ -182,16 +190,18 @@ class QuotaTests(base.TestCase):
if not self.is_extension_enabled('quota-check-limit'):
self.skipTest('No "quota-check-limit" extension present')
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --network'
- ))
+ cmd_output = self.openstack(
+ 'quota list --network',
+ parse_output=True,
+ )
self.addCleanup(self._restore_quota_limit, 'network',
cmd_output[0]['Networks'], self.PROJECT_NAME)
self.openstack('quota set --networks 40 ' + self.PROJECT_NAME)
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --network'
- ))
+ cmd_output = self.openstack(
+ 'quota list --network',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertEqual(40, cmd_output[0]['Networks'])
@@ -218,16 +228,18 @@ class QuotaTests(base.TestCase):
if not self.is_extension_enabled('quota-check-limit'):
self.skipTest('No "quota-check-limit" extension present')
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --network'
- ))
+ cmd_output = self.openstack(
+ 'quota list --network',
+ parse_output=True,
+ )
self.addCleanup(self._restore_quota_limit, 'network',
cmd_output[0]['Networks'], self.PROJECT_NAME)
self.openstack('quota set --networks 40 ' + self.PROJECT_NAME)
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --network'
- ))
+ cmd_output = self.openstack(
+ 'quota list --network',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertEqual(40, cmd_output[0]['Networks'])
@@ -237,8 +249,9 @@ class QuotaTests(base.TestCase):
(self.PROJECT_NAME, uuid.uuid4().hex))
self.openstack('quota set --networks 1 --force ' + self.PROJECT_NAME)
- cmd_output = json.loads(self.openstack(
- 'quota list -f json --network'
- ))
+ cmd_output = self.openstack(
+ 'quota list --network',
+ parse_output=True,
+ )
self.assertIsNotNone(cmd_output)
self.assertEqual(1, cmd_output[0]['Networks'])
diff --git a/openstackclient/tests/functional/common/test_versions.py b/openstackclient/tests/functional/common/test_versions.py
index adc74ebc..6575671a 100644
--- a/openstackclient/tests/functional/common/test_versions.py
+++ b/openstackclient/tests/functional/common/test_versions.py
@@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
-
from openstackclient.tests.functional import base
@@ -21,9 +19,7 @@ class VersionsTests(base.TestCase):
def test_versions_show(self):
# TODO(mordred) Make this better. The trick is knowing what in the
# payload to test for.
- cmd_output = json.loads(self.openstack(
- 'versions show -f json'
- ))
+ cmd_output = self.openstack('versions show', parse_output=True)
self.assertIsNotNone(cmd_output)
self.assertIn(
"Region Name",
diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py
index d77797ab..7d8c29ad 100644
--- a/openstackclient/tests/unit/compute/v2/fakes.py
+++ b/openstackclient/tests/unit/compute/v2/fakes.py
@@ -20,6 +20,7 @@ import uuid
from novaclient import api_versions
from openstack.compute.v2 import flavor as _flavor
+from openstack.compute.v2 import hypervisor as _hypervisor
from openstack.compute.v2 import server
from openstack.compute.v2 import server_group as _server_group
from openstack.compute.v2 import server_interface as _server_interface
@@ -340,75 +341,6 @@ class FakeExtension(object):
return extension
-class FakeHypervisor(object):
- """Fake one or more hypervisor."""
-
- @staticmethod
- def create_one_hypervisor(attrs=None):
- """Create a fake hypervisor.
-
- :param dict attrs:
- A dictionary with all attributes
- :return:
- A FakeResource object, with id, hypervisor_hostname, and so on
- """
- attrs = attrs or {}
-
- # Set default attributes.
- hypervisor_info = {
- 'id': 'hypervisor-id-' + uuid.uuid4().hex,
- 'hypervisor_hostname': 'hypervisor-hostname-' + uuid.uuid4().hex,
- 'status': 'enabled',
- 'host_ip': '192.168.0.10',
- 'cpu_info': {
- 'aaa': 'aaa',
- },
- 'free_disk_gb': 50,
- 'hypervisor_version': 2004001,
- 'disk_available_least': 50,
- 'local_gb': 50,
- 'free_ram_mb': 1024,
- 'service': {
- 'host': 'aaa',
- 'disabled_reason': None,
- 'id': 1,
- },
- 'vcpus_used': 0,
- 'hypervisor_type': 'QEMU',
- 'local_gb_used': 0,
- 'vcpus': 4,
- 'memory_mb_used': 512,
- 'memory_mb': 1024,
- 'current_workload': 0,
- 'state': 'up',
- 'running_vms': 0,
- }
-
- # Overwrite default attributes.
- hypervisor_info.update(attrs)
-
- hypervisor = fakes.FakeResource(info=copy.deepcopy(hypervisor_info),
- loaded=True)
- return hypervisor
-
- @staticmethod
- def create_hypervisors(attrs=None, count=2):
- """Create multiple fake hypervisors.
-
- :param dict attrs:
- A dictionary with all attributes
- :param int count:
- The number of hypervisors to fake
- :return:
- A list of FakeResource objects faking the hypervisors
- """
- hypervisors = []
- for i in range(0, count):
- hypervisors.append(FakeHypervisor.create_one_hypervisor(attrs))
-
- return hypervisors
-
-
class FakeHypervisorStats(object):
"""Fake one or more hypervisor stats."""
@@ -1795,6 +1727,70 @@ class FakeVolumeAttachment(object):
return volume_attachments
+def create_one_hypervisor(attrs=None):
+ """Create a fake hypervisor.
+
+ :param dict attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, hypervisor_hostname, and so on
+ """
+ attrs = attrs or {}
+
+ # Set default attributes.
+ hypervisor_info = {
+ 'id': 'hypervisor-id-' + uuid.uuid4().hex,
+ 'hypervisor_hostname': 'hypervisor-hostname-' + uuid.uuid4().hex,
+ 'status': 'enabled',
+ 'host_ip': '192.168.0.10',
+ 'cpu_info': {
+ 'aaa': 'aaa',
+ },
+ 'free_disk_gb': 50,
+ 'hypervisor_version': 2004001,
+ 'disk_available_least': 50,
+ 'local_gb': 50,
+ 'free_ram_mb': 1024,
+ 'service': {
+ 'host': 'aaa',
+ 'disabled_reason': None,
+ 'id': 1,
+ },
+ 'vcpus_used': 0,
+ 'hypervisor_type': 'QEMU',
+ 'local_gb_used': 0,
+ 'vcpus': 4,
+ 'memory_mb_used': 512,
+ 'memory_mb': 1024,
+ 'current_workload': 0,
+ 'state': 'up',
+ 'running_vms': 0,
+ }
+
+ # Overwrite default attributes.
+ hypervisor_info.update(attrs)
+
+ hypervisor = _hypervisor.Hypervisor(**hypervisor_info, loaded=True)
+ return hypervisor
+
+
+def create_hypervisors(attrs=None, count=2):
+ """Create multiple fake hypervisors.
+
+ :param dict attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of hypervisors to fake
+ :return:
+ A list of FakeResource objects faking the hypervisors
+ """
+ hypervisors = []
+ for i in range(0, count):
+ hypervisors.append(create_one_hypervisor(attrs))
+
+ return hypervisors
+
+
def create_one_server_group(attrs=None):
"""Create a fake server group
diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py
index 7dbd6e19..e5804665 100644
--- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py
+++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py
@@ -13,41 +13,37 @@
# under the License.
#
-import copy
import json
+from unittest import mock
-from novaclient import api_versions
from novaclient import exceptions as nova_exceptions
+from openstack import utils as sdk_utils
from osc_lib.cli import format_columns
from osc_lib import exceptions
from openstackclient.compute.v2 import hypervisor
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
-from openstackclient.tests.unit import fakes
class TestHypervisor(compute_fakes.TestComputev2):
def setUp(self):
- super(TestHypervisor, self).setUp()
+ super().setUp()
- # Get a shortcut to the compute client hypervisors mock
- self.hypervisors_mock = self.app.client_manager.compute.hypervisors
- self.hypervisors_mock.reset_mock()
-
- # Get a shortcut to the compute client aggregates mock
- self.aggregates_mock = self.app.client_manager.compute.aggregates
- self.aggregates_mock.reset_mock()
+ # Create and get a shortcut to the compute client mock
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.sdk_client = self.app.client_manager.sdk_connection.compute
+ self.sdk_client.reset_mock()
class TestHypervisorList(TestHypervisor):
def setUp(self):
- super(TestHypervisorList, self).setUp()
+ super().setUp()
# Fake hypervisors to be listed up
- self.hypervisors = compute_fakes.FakeHypervisor.create_hypervisors()
- self.hypervisors_mock.list.return_value = self.hypervisors
+ self.hypervisors = compute_fakes.create_hypervisors()
+ self.sdk_client.hypervisors.return_value = self.hypervisors
self.columns = (
"ID",
@@ -70,14 +66,14 @@ class TestHypervisorList(TestHypervisor):
self.data = (
(
self.hypervisors[0].id,
- self.hypervisors[0].hypervisor_hostname,
+ self.hypervisors[0].name,
self.hypervisors[0].hypervisor_type,
self.hypervisors[0].host_ip,
self.hypervisors[0].state
),
(
self.hypervisors[1].id,
- self.hypervisors[1].hypervisor_hostname,
+ self.hypervisors[1].name,
self.hypervisors[1].hypervisor_type,
self.hypervisors[1].host_ip,
self.hypervisors[1].state
@@ -87,25 +83,25 @@ class TestHypervisorList(TestHypervisor):
self.data_long = (
(
self.hypervisors[0].id,
- self.hypervisors[0].hypervisor_hostname,
+ self.hypervisors[0].name,
self.hypervisors[0].hypervisor_type,
self.hypervisors[0].host_ip,
self.hypervisors[0].state,
self.hypervisors[0].vcpus_used,
self.hypervisors[0].vcpus,
- self.hypervisors[0].memory_mb_used,
- self.hypervisors[0].memory_mb
+ self.hypervisors[0].memory_used,
+ self.hypervisors[0].memory_size
),
(
self.hypervisors[1].id,
- self.hypervisors[1].hypervisor_hostname,
+ self.hypervisors[1].name,
self.hypervisors[1].hypervisor_type,
self.hypervisors[1].host_ip,
self.hypervisors[1].state,
self.hypervisors[1].vcpus_used,
self.hypervisors[1].vcpus,
- self.hypervisors[1].memory_mb_used,
- self.hypervisors[1].memory_mb
+ self.hypervisors[1].memory_used,
+ self.hypervisors[1].memory_size
),
)
# Get the command object to test
@@ -121,25 +117,25 @@ class TestHypervisorList(TestHypervisor):
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.hypervisors_mock.list.assert_called_with()
+ self.sdk_client.hypervisors.assert_called_with(details=True)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, tuple(data))
def test_hypervisor_list_matching_option_found(self):
arglist = [
- '--matching', self.hypervisors[0].hypervisor_hostname,
+ '--matching', self.hypervisors[0].name,
]
verifylist = [
- ('matching', self.hypervisors[0].hypervisor_hostname),
+ ('matching', self.hypervisors[0].name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# Fake the return value of search()
- self.hypervisors_mock.search.return_value = [self.hypervisors[0]]
+ self.sdk_client.find_hypervisor.return_value = [self.hypervisors[0]]
self.data = (
(
self.hypervisors[0].id,
- self.hypervisors[0].hypervisor_hostname,
+ self.hypervisors[0].name,
self.hypervisors[1].hypervisor_type,
self.hypervisors[1].host_ip,
self.hypervisors[1].state,
@@ -151,8 +147,9 @@ class TestHypervisorList(TestHypervisor):
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.hypervisors_mock.search.assert_called_with(
- self.hypervisors[0].hypervisor_hostname
+ self.sdk_client.find_hypervisor.assert_called_with(
+ self.hypervisors[0].name,
+ ignore_missing=False
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, tuple(data))
@@ -167,25 +164,25 @@ class TestHypervisorList(TestHypervisor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# Fake exception raised from search()
- self.hypervisors_mock.search.side_effect = exceptions.NotFound(None)
+ self.sdk_client.find_hypervisor.side_effect = \
+ exceptions.NotFound(None)
self.assertRaises(exceptions.NotFound,
self.cmd.take_action,
parsed_args)
- def test_hypervisor_list_with_matching_and_pagination_options(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.32')
-
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_hypervisor_list_with_matching_and_pagination_options(
+ self, sm_mock):
arglist = [
- '--matching', self.hypervisors[0].hypervisor_hostname,
+ '--matching', self.hypervisors[0].name,
'--limit', '1',
- '--marker', self.hypervisors[0].hypervisor_hostname,
+ '--marker', self.hypervisors[0].name,
]
verifylist = [
- ('matching', self.hypervisors[0].hypervisor_hostname),
+ ('matching', self.hypervisors[0].name),
('limit', 1),
- ('marker', self.hypervisors[0].hypervisor_hostname),
+ ('marker', self.hypervisors[0].name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -197,7 +194,8 @@ class TestHypervisorList(TestHypervisor):
self.assertIn(
'--matching is not compatible with --marker or --limit', str(ex))
- def test_hypervisor_list_long_option(self):
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_hypervisor_list_long_option(self, sm_mock):
arglist = [
'--long',
]
@@ -211,14 +209,12 @@ class TestHypervisorList(TestHypervisor):
# containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args)
- self.hypervisors_mock.list.assert_called_with()
+ self.sdk_client.hypervisors.assert_called_with(details=True)
self.assertEqual(self.columns_long, columns)
self.assertEqual(self.data_long, tuple(data))
- def test_hypervisor_list_with_limit(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.33')
-
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_hypervisor_list_with_limit(self, sm_mock):
arglist = [
'--limit', '1',
]
@@ -229,12 +225,10 @@ class TestHypervisorList(TestHypervisor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
- self.hypervisors_mock.list.assert_called_with(limit=1)
-
- def test_hypervisor_list_with_limit_pre_v233(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.32')
+ self.sdk_client.hypervisors.assert_called_with(limit=1, details=True)
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_hypervisor_list_with_limit_pre_v233(self, sm_mock):
arglist = [
'--limit', '1',
]
@@ -251,10 +245,8 @@ class TestHypervisorList(TestHypervisor):
self.assertIn(
'--os-compute-api-version 2.33 or greater is required', str(ex))
- def test_hypervisor_list_with_marker(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.33')
-
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_hypervisor_list_with_marker(self, sm_mock):
arglist = [
'--marker', 'test_hyp',
]
@@ -265,12 +257,11 @@ class TestHypervisorList(TestHypervisor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
- self.hypervisors_mock.list.assert_called_with(marker='test_hyp')
-
- def test_hypervisor_list_with_marker_pre_v233(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.32')
+ self.sdk_client.hypervisors.assert_called_with(
+ marker='test_hyp', details=True)
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_hypervisor_list_with_marker_pre_v233(self, sm_mock):
arglist = [
'--marker', 'test_hyp',
]
@@ -291,29 +282,66 @@ class TestHypervisorList(TestHypervisor):
class TestHypervisorShow(TestHypervisor):
def setUp(self):
- super(TestHypervisorShow, self).setUp()
+ super().setUp()
+
+ uptime_string = (' 01:28:24 up 3 days, 11:15, 1 user, '
+ ' load average: 0.94, 0.62, 0.50\n')
# Fake hypervisors to be listed up
- self.hypervisor = compute_fakes.FakeHypervisor.create_one_hypervisor()
+ self.hypervisor = compute_fakes.create_one_hypervisor(attrs={
+ 'uptime': uptime_string,
+ })
- # Return value of utils.find_resource()
- self.hypervisors_mock.get.return_value = self.hypervisor
+ # Return value of compute_client.find_hypervisor
+ self.sdk_client.find_hypervisor.return_value = self.hypervisor
- # Return value of compute_client.aggregates.list()
- self.aggregates_mock.list.return_value = []
+ # Return value of compute_client.aggregates()
+ self.sdk_client.aggregates.return_value = []
- # Return value of compute_client.hypervisors.uptime()
+ # Return value of compute_client.get_hypervisor_uptime()
uptime_info = {
'status': self.hypervisor.status,
'state': self.hypervisor.state,
'id': self.hypervisor.id,
- 'hypervisor_hostname': self.hypervisor.hypervisor_hostname,
- 'uptime': ' 01:28:24 up 3 days, 11:15, 1 user, '
- ' load average: 0.94, 0.62, 0.50\n',
+ 'hypervisor_hostname': self.hypervisor.name,
+ 'uptime': uptime_string,
}
- self.hypervisors_mock.uptime.return_value = fakes.FakeResource(
- info=copy.deepcopy(uptime_info),
- loaded=True
+ self.sdk_client.get_hypervisor_uptime.return_value = uptime_info
+
+ self.columns_v288 = (
+ 'aggregates',
+ 'cpu_info',
+ 'host_ip',
+ 'host_time',
+ 'hypervisor_hostname',
+ 'hypervisor_type',
+ 'hypervisor_version',
+ 'id',
+ 'load_average',
+ 'service_host',
+ 'service_id',
+ 'state',
+ 'status',
+ 'uptime',
+ 'users',
+ )
+
+ self.data_v288 = (
+ [],
+ format_columns.DictColumn({'aaa': 'aaa'}),
+ '192.168.0.10',
+ '01:28:24',
+ self.hypervisor.name,
+ 'QEMU',
+ 2004001,
+ self.hypervisor.id,
+ '0.94, 0.62, 0.50',
+ 'aaa',
+ 1,
+ 'up',
+ 'enabled',
+ '3 days, 11:15',
+ '1',
)
self.columns = (
@@ -353,7 +381,7 @@ class TestHypervisorShow(TestHypervisor):
1024,
'192.168.0.10',
'01:28:24',
- self.hypervisor.hypervisor_hostname,
+ self.hypervisor.name,
'QEMU',
2004001,
self.hypervisor.id,
@@ -376,15 +404,32 @@ class TestHypervisorShow(TestHypervisor):
# Get the command object to test
self.cmd = hypervisor.ShowHypervisor(self.app, None)
- def test_hypervisor_show(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.28')
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True)
+ def test_hypervisor_show(self, sm_mock):
+ arglist = [
+ self.hypervisor.name,
+ ]
+ verifylist = [
+ ('hypervisor', self.hypervisor.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ # In base command class ShowOne in cliff, abstract method take_action()
+ # returns a two-part tuple with a tuple of column names and a tuple of
+ # data to be shown.
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.assertEqual(self.columns_v288, columns)
+ self.assertCountEqual(self.data_v288, data)
+
+ @mock.patch.object(sdk_utils, 'supports_microversion',
+ side_effect=[False, True, False])
+ def test_hypervisor_show_pre_v288(self, sm_mock):
arglist = [
- self.hypervisor.hypervisor_hostname,
+ self.hypervisor.name,
]
verifylist = [
- ('hypervisor', self.hypervisor.hypervisor_hostname),
+ ('hypervisor', self.hypervisor.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -396,21 +441,19 @@ class TestHypervisorShow(TestHypervisor):
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.data, data)
- def test_hypervisor_show_pre_v228(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.27')
-
+ @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False)
+ def test_hypervisor_show_pre_v228(self, sm_mock):
# before microversion 2.28, nova returned a stringified version of this
# field
- self.hypervisor._info['cpu_info'] = json.dumps(
- self.hypervisor._info['cpu_info'])
- self.hypervisors_mock.get.return_value = self.hypervisor
+ self.hypervisor.cpu_info = json.dumps(
+ self.hypervisor.cpu_info)
+ self.sdk_client.find_hypervisor.return_value = self.hypervisor
arglist = [
- self.hypervisor.hypervisor_hostname,
+ self.hypervisor.name,
]
verifylist = [
- ('hypervisor', self.hypervisor.hypervisor_hostname),
+ ('hypervisor', self.hypervisor.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -422,19 +465,18 @@ class TestHypervisorShow(TestHypervisor):
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.data, data)
- def test_hypervisor_show_uptime_not_implemented(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.28')
-
+ @mock.patch.object(sdk_utils, 'supports_microversion',
+ side_effect=[False, True, False])
+ def test_hypervisor_show_uptime_not_implemented(self, sm_mock):
arglist = [
- self.hypervisor.hypervisor_hostname,
+ self.hypervisor.name,
]
verifylist = [
- ('hypervisor', self.hypervisor.hypervisor_hostname),
+ ('hypervisor', self.hypervisor.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- self.hypervisors_mock.uptime.side_effect = (
+ self.sdk_client.get_hypervisor_uptime.side_effect = (
nova_exceptions.HTTPNotImplemented(501))
# In base command class ShowOne in cliff, abstract method take_action()
@@ -474,7 +516,7 @@ class TestHypervisorShow(TestHypervisor):
50,
1024,
'192.168.0.10',
- self.hypervisor.hypervisor_hostname,
+ self.hypervisor.name,
'QEMU',
2004001,
self.hypervisor.id,
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 858c9a2a..f59c954a 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -1675,6 +1675,7 @@ class TestServerCreate(TestServer):
'--nic', 'net-id=net1,v4-fixed-ip=10.0.0.2',
'--port', 'port1',
'--network', 'net1',
+ '--network', 'auto', # this is a network called 'auto'
'--nic', 'port-id=port2',
self.new_server.name,
]
@@ -1683,24 +1684,40 @@ class TestServerCreate(TestServer):
('flavor', 'flavor1'),
('nics', [
{
- 'net-id': 'net1', 'port-id': '',
- 'v4-fixed-ip': '', 'v6-fixed-ip': '',
+ 'net-id': 'net1',
+ 'port-id': '',
+ 'v4-fixed-ip': '',
+ 'v6-fixed-ip': '',
},
{
- 'net-id': 'net1', 'port-id': '',
- 'v4-fixed-ip': '10.0.0.2', 'v6-fixed-ip': '',
+ 'net-id': 'net1',
+ 'port-id': '',
+ 'v4-fixed-ip': '10.0.0.2',
+ 'v6-fixed-ip': '',
},
{
- 'net-id': '', 'port-id': 'port1',
- 'v4-fixed-ip': '', 'v6-fixed-ip': '',
+ 'net-id': '',
+ 'port-id': 'port1',
+ 'v4-fixed-ip': '',
+ 'v6-fixed-ip': '',
},
{
- 'net-id': 'net1', 'port-id': '',
- 'v4-fixed-ip': '', 'v6-fixed-ip': '',
+ 'net-id': 'net1',
+ 'port-id': '',
+ 'v4-fixed-ip': '',
+ 'v6-fixed-ip': '',
},
{
- 'net-id': '', 'port-id': 'port2',
- 'v4-fixed-ip': '', 'v6-fixed-ip': '',
+ 'net-id': 'auto',
+ 'port-id': '',
+ 'v4-fixed-ip': '',
+ 'v6-fixed-ip': '',
+ },
+ {
+ 'net-id': '',
+ 'port-id': 'port2',
+ 'v4-fixed-ip': '',
+ 'v6-fixed-ip': '',
},
]),
('config_drive', False),
@@ -1729,12 +1746,16 @@ class TestServerCreate(TestServer):
"port2": port2_resource}[port_id])
# Mock sdk APIs.
- _network = mock.Mock(id='net1_uuid')
+ _network_1 = mock.Mock(id='net1_uuid')
+ _network_auto = mock.Mock(id='auto_uuid')
_port1 = mock.Mock(id='port1_uuid')
_port2 = mock.Mock(id='port2_uuid')
find_network = mock.Mock()
find_port = mock.Mock()
- find_network.return_value = _network
+ find_network.side_effect = lambda net_id, ignore_missing: {
+ "net1": _network_1,
+ "auto": _network_auto,
+ }[net_id]
find_port.side_effect = (lambda port_id, ignore_missing:
{"port1": _port1,
"port2": _port2}[port_id])
@@ -1775,6 +1796,10 @@ class TestServerCreate(TestServer):
'v4-fixed-ip': '',
'v6-fixed-ip': '',
'port-id': ''},
+ {'net-id': 'auto_uuid',
+ 'v4-fixed-ip': '',
+ 'v6-fixed-ip': '',
+ 'port-id': ''},
{'net-id': '',
'v4-fixed-ip': '',
'v6-fixed-ip': '',
@@ -1901,6 +1926,10 @@ class TestServerCreate(TestServer):
exceptions.CommandError, self.cmd.take_action, parsed_args)
def _test_server_create_with_auto_network(self, arglist):
+ # requires API microversion 2.37 or later
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.37')
+
verifylist = [
('image', 'image1'),
('flavor', 'flavor1'),
@@ -1961,8 +1990,45 @@ class TestServerCreate(TestServer):
]
self._test_server_create_with_auto_network(arglist)
+ def test_server_create_with_auto_network_pre_v237(self):
+ # use an API microversion that's too old
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.36')
+
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--nic', 'auto',
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('nics', ['auto']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ exc = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args,
+ )
+ self.assertIn(
+ '--os-compute-api-version 2.37 or greater is required to support '
+ 'explicit auto-allocation of a network or to disable network '
+ 'allocation',
+ str(exc),
+ )
+ self.assertNotCalled(self.servers_mock.create)
+
def test_server_create_with_auto_network_default_v2_37(self):
"""Tests creating a server without specifying --nic using 2.37."""
+ # requires API microversion 2.37 or later
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.37')
+
arglist = [
'--image', 'image1',
'--flavor', 'flavor1',
@@ -1976,12 +2042,7 @@ class TestServerCreate(TestServer):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- # Since check_parser doesn't handle compute global options like
- # --os-compute-api-version, we have to mock the construction of
- # the novaclient client object with our own APIVersion.
- with mock.patch.object(self.app.client_manager.compute, 'api_version',
- api_versions.APIVersion('2.37')):
- columns, data = self.cmd.take_action(parsed_args)
+ columns, data = self.cmd.take_action(parsed_args)
# Set expected values
kwargs = dict(
@@ -2012,6 +2073,10 @@ class TestServerCreate(TestServer):
self.assertEqual(self.datalist(), data)
def _test_server_create_with_none_network(self, arglist):
+ # requires API microversion 2.37 or later
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.37')
+
verifylist = [
('image', 'image1'),
('flavor', 'flavor1'),
@@ -2072,6 +2137,40 @@ class TestServerCreate(TestServer):
]
self._test_server_create_with_none_network(arglist)
+ def test_server_create_with_none_network_pre_v237(self):
+ # use an API microversion that's too old
+ self.app.client_manager.compute.api_version = api_versions.APIVersion(
+ '2.36')
+
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--nic', 'none',
+ self.new_server.name,
+ ]
+
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', 'flavor1'),
+ ('nics', ['none']),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ exc = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args,
+ )
+ self.assertIn(
+ '--os-compute-api-version 2.37 or greater is required to support '
+ 'explicit auto-allocation of a network or to disable network '
+ 'allocation',
+ str(exc),
+ )
+ self.assertNotCalled(self.servers_mock.create)
+
def test_server_create_with_conflict_network_options(self):
arglist = [
'--image', 'image1',
@@ -3202,13 +3301,11 @@ class TestServerCreate(TestServer):
arglist = [
'--image-property', 'hypervisor_type=qemu',
'--flavor', 'flavor1',
- '--nic', 'none',
self.new_server.name,
]
verifylist = [
('image_properties', {'hypervisor_type': 'qemu'}),
('flavor', 'flavor1'),
- ('nics', ['none']),
('config_drive', False),
('server_name', self.new_server.name),
]
@@ -3236,7 +3333,7 @@ class TestServerCreate(TestServer):
availability_zone=None,
admin_pass=None,
block_device_mapping_v2=[],
- nics='none',
+ nics=[],
meta=None,
scheduler_hints={},
config_drive=None,
@@ -3257,14 +3354,12 @@ class TestServerCreate(TestServer):
'--image-property', 'hypervisor_type=qemu',
'--image-property', 'hw_disk_bus=ide',
'--flavor', 'flavor1',
- '--nic', 'none',
self.new_server.name,
]
verifylist = [
('image_properties', {'hypervisor_type': 'qemu',
'hw_disk_bus': 'ide'}),
('flavor', 'flavor1'),
- ('nics', ['none']),
('config_drive', False),
('server_name', self.new_server.name),
]
@@ -3292,7 +3387,7 @@ class TestServerCreate(TestServer):
availability_zone=None,
admin_pass=None,
block_device_mapping_v2=[],
- nics='none',
+ nics=[],
meta=None,
scheduler_hints={},
config_drive=None,
@@ -3313,14 +3408,12 @@ class TestServerCreate(TestServer):
'--image-property', 'hypervisor_type=qemu',
'--image-property', 'hw_disk_bus=virtio',
'--flavor', 'flavor1',
- '--nic', 'none',
self.new_server.name,
]
verifylist = [
('image_properties', {'hypervisor_type': 'qemu',
'hw_disk_bus': 'virtio'}),
('flavor', 'flavor1'),
- ('nics', ['none']),
('config_drive', False),
('server_name', self.new_server.name),
]
@@ -3344,7 +3437,6 @@ class TestServerCreate(TestServer):
'--image-property',
'owner_specified.openstack.object=image/cirros',
'--flavor', 'flavor1',
- '--nic', 'none',
self.new_server.name,
]
@@ -3352,7 +3444,6 @@ class TestServerCreate(TestServer):
('image_properties',
{'owner_specified.openstack.object': 'image/cirros'}),
('flavor', 'flavor1'),
- ('nics', ['none']),
('server_name', self.new_server.name),
]
# create a image_info as the side_effect of the fake image_list()
@@ -3382,7 +3473,7 @@ class TestServerCreate(TestServer):
availability_zone=None,
admin_pass=None,
block_device_mapping_v2=[],
- nics='none',
+ nics=[],
meta=None,
scheduler_hints={},
config_drive=None,
diff --git a/openstackclient/tests/unit/compute/v2/test_usage.py b/openstackclient/tests/unit/compute/v2/test_usage.py
index bbccb9bd..85b45e1b 100644
--- a/openstackclient/tests/unit/compute/v2/test_usage.py
+++ b/openstackclient/tests/unit/compute/v2/test_usage.py
@@ -11,11 +11,8 @@
# under the License.
#
-import datetime
from unittest import mock
-from novaclient import api_versions
-
from openstackclient.compute.v2 import usage as usage_cmds
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
@@ -26,8 +23,9 @@ class TestUsage(compute_fakes.TestComputev2):
def setUp(self):
super(TestUsage, self).setUp()
- self.usage_mock = self.app.client_manager.compute.usage
- self.usage_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.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock()
@@ -38,7 +36,7 @@ class TestUsageList(TestUsage):
project = identity_fakes.FakeProject.create_one_project()
# Return value of self.usage_mock.list().
usages = compute_fakes.FakeUsage.create_usages(
- attrs={'tenant_id': project.name}, count=1)
+ attrs={'project_id': project.name}, count=1)
columns = (
"Project",
@@ -49,7 +47,7 @@ class TestUsageList(TestUsage):
)
data = [(
- usage_cmds.ProjectColumn(usages[0].tenant_id),
+ usage_cmds.ProjectColumn(usages[0].project_id),
usage_cmds.CountColumn(usages[0].server_usages),
usage_cmds.FloatColumn(usages[0].total_memory_mb_usage),
usage_cmds.FloatColumn(usages[0].total_vcpus_usage),
@@ -59,7 +57,7 @@ class TestUsageList(TestUsage):
def setUp(self):
super(TestUsageList, self).setUp()
- self.usage_mock.list.return_value = self.usages
+ self.sdk_client.usages.return_value = self.usages
self.projects_mock.list.return_value = [self.project]
# Get the command object to test
@@ -97,9 +95,9 @@ class TestUsageList(TestUsage):
columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.list.assert_called_with()
- self.usage_mock.list.assert_called_with(
- datetime.datetime(2016, 11, 11, 0, 0),
- datetime.datetime(2016, 12, 20, 0, 0),
+ self.sdk_client.usages.assert_called_with(
+ start='2016-11-11T00:00:00',
+ end='2016-12-20T00:00:00',
detailed=True)
self.assertCountEqual(self.columns, columns)
@@ -112,20 +110,13 @@ class TestUsageList(TestUsage):
('end', None),
]
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.40')
- self.usage_mock.list.reset_mock()
- self.usage_mock.list.side_effect = [self.usages, []]
-
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.projects_mock.list.assert_called_with()
- self.usage_mock.list.assert_has_calls([
- mock.call(mock.ANY, mock.ANY, detailed=True),
- mock.call(mock.ANY, mock.ANY, detailed=True,
- marker=self.usages[0]['server_usages'][0]['instance_id'])
+ self.sdk_client.usages.assert_has_calls([
+ mock.call(start=mock.ANY, end=mock.ANY, detailed=True)
])
self.assertCountEqual(self.columns, columns)
self.assertCountEqual(tuple(self.data), tuple(data))
@@ -136,7 +127,7 @@ class TestUsageShow(TestUsage):
project = identity_fakes.FakeProject.create_one_project()
# Return value of self.usage_mock.list().
usage = compute_fakes.FakeUsage.create_one_usage(
- attrs={'tenant_id': project.name})
+ attrs={'project_id': project.name})
columns = (
'Project',
@@ -147,7 +138,7 @@ class TestUsageShow(TestUsage):
)
data = (
- usage_cmds.ProjectColumn(usage.tenant_id),
+ usage_cmds.ProjectColumn(usage.project_id),
usage_cmds.CountColumn(usage.server_usages),
usage_cmds.FloatColumn(usage.total_memory_mb_usage),
usage_cmds.FloatColumn(usage.total_vcpus_usage),
@@ -157,7 +148,7 @@ class TestUsageShow(TestUsage):
def setUp(self):
super(TestUsageShow, self).setUp()
- self.usage_mock.get.return_value = self.usage
+ self.sdk_client.get_usage.return_value = self.usage
self.projects_mock.get.return_value = self.project
# Get the command object to test
@@ -199,10 +190,10 @@ class TestUsageShow(TestUsage):
columns, data = self.cmd.take_action(parsed_args)
- self.usage_mock.get.assert_called_with(
- self.project.id,
- datetime.datetime(2016, 11, 11, 0, 0),
- datetime.datetime(2016, 12, 20, 0, 0))
+ self.sdk_client.get_usage.assert_called_with(
+ project=self.project.id,
+ start='2016-11-11T00:00:00',
+ end='2016-12-20T00:00:00')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
diff --git a/openstackclient/volume/v3/volume_attachment.py b/openstackclient/volume/v3/volume_attachment.py
index c7401837..57a6da73 100644
--- a/openstackclient/volume/v3/volume_attachment.py
+++ b/openstackclient/volume/v3/volume_attachment.py
@@ -82,7 +82,7 @@ class CreateVolumeAttachment(command.ShowOne):
the volume to the server at the hypervisor level. As a result, it should
typically only be used for troubleshooting issues with an existing server
in combination with other tooling. For all other use cases, the 'server
- volume add' command should be preferred.
+ add volume' command should be preferred.
"""
def get_parser(self, prog_name):