summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/cli/data/nova.csv14
-rw-r--r--openstackclient/common/project_cleanup.py8
-rw-r--r--openstackclient/common/quota.py10
-rw-r--r--openstackclient/compute/v2/host.py60
-rw-r--r--openstackclient/compute/v2/server.py42
-rw-r--r--openstackclient/compute/v2/server_migration.py82
-rw-r--r--openstackclient/compute/v2/server_volume.py73
-rw-r--r--openstackclient/compute/v2/service.py10
-rw-r--r--openstackclient/network/v2/floating_ip_port_forwarding.py102
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server.py3
-rw-r--r--openstackclient/tests/unit/compute/v2/fakes.py329
-rw-r--r--openstackclient/tests/unit/compute/v2/test_host.py105
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py3
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_migration.py187
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_volume.py186
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py38
-rw-r--r--openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py231
-rw-r--r--openstackclient/tests/unit/volume/v1/test_volume.py51
-rw-r--r--openstackclient/tests/unit/volume/v2/test_consistency_group.py4
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py53
-rw-r--r--openstackclient/tests/unit/volume/v3/test_volume_group.py55
-rw-r--r--openstackclient/volume/v1/volume.py37
-rw-r--r--openstackclient/volume/v2/backup_record.py14
-rw-r--r--openstackclient/volume/v2/consistency_group.py45
-rw-r--r--openstackclient/volume/v2/volume.py37
-rw-r--r--openstackclient/volume/v3/volume_group.py127
-rw-r--r--releasenotes/notes/add-port-ranges-in-port-forwarding-command-8c6ee05cf625578a.yaml4
-rw-r--r--releasenotes/notes/consistency-group-create-opts-aliases-e1c2f1498e9b1d3d.yaml7
-rw-r--r--releasenotes/notes/deprecate-volume-group-create-positional-arguments-89f6b886c0f1f2b5.yaml10
-rw-r--r--releasenotes/notes/migrate-host-list-show-to-sdk-9b80cd9b4196ab01.yaml4
-rw-r--r--releasenotes/notes/migrate-server-volume-list-update-to-sdk-95b1d3063e46f813.yaml3
-rw-r--r--releasenotes/notes/rename-server-volume-update-to-server-volume-set-833f1730a9bf6169.yaml6
-rw-r--r--releasenotes/notes/switch-server-migration-show-to-sdk-4adb88a0f1f03f3b.yaml3
-rw-r--r--setup.cfg1
34 files changed, 1246 insertions, 698 deletions
diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv
index e494ce28..ff691a51 100644
--- a/doc/source/cli/data/nova.csv
+++ b/doc/source/cli/data/nova.csv
@@ -4,7 +4,7 @@ agent-delete,compute agent delete,Delete existing agent build.
agent-list,compute agent list,List all builds.
agent-modify,compute agent set,Modify existing agent build.
aggregate-add-host,aggregate add host,Add the host to the specified aggregate.
-aggregate-cache-images,WONTFIX,Request images be cached. (Supported by API versions '2.81' - '2.latest') [hint: use '-- os-compute-api-version' flag to show help message for proper version]
+aggregate-cache-images,aggregate cache image,Request images be cached. (Supported by API versions '2.81' - '2.latest') [hint: use '-- os-compute-api-version' flag to show help message for proper version]
aggregate-create,aggregate create,Create a new aggregate with the specified details.
aggregate-delete,aggregate delete,Delete the aggregate.
aggregate-list,aggregate list,Print a list of all aggregates.
@@ -36,19 +36,19 @@ get-rdp-console,console url show --rdp,Get a rdp console to a server.
get-serial-console,console url show --serial,Get a serial console to a server.
get-spice-console,console url show --spice,Get a spice console to a server.
get-vnc-console,console url show --novnc,Get a vnc console to a server.
-host-evacuate,,Evacuate all instances from failed host.
-host-evacuate-live,,Live migrate all instances off the specified host to other available hosts.
-host-meta,,Set or Delete metadata on all instances of a host.
-host-servers-migrate,,Cold migrate all instances off the specified host to other available hosts.
+host-evacuate,WONTFIX,Evacuate all instances from failed host.
+host-evacuate-live,WONTFIX,Live migrate all instances off the specified host to other available hosts.
+host-meta,WONTFIX,Set or Delete metadata on all instances of a host.
+host-servers-migrate,WONTFIX,Cold migrate all instances off the specified host to other available hosts.
hypervisor-list,hypervisor list,List hypervisors. (Supported by API versions '2.0' - '2.latest')
-hypervisor-servers,,List servers belonging to specific hypervisors.
+hypervisor-servers,server list --host,List servers belonging to specific hypervisors.
hypervisor-show,hypervisor show,Display the details of the specified hypervisor.
hypervisor-stats,hypervisor stats show,Get hypervisor statistics over all compute nodes.
hypervisor-uptime,hypervisor show,Display the uptime of the specified hypervisor.
image-create,server image create,Create a new image by taking a snapshot of a running server.
instance-action,server event show,Show an action.
instance-action-list,server event list,List actions on a server.
-instance-usage-audit-log,,List/Get server usage audits.
+instance-usage-audit-log,WONTFIX,List/Get server usage audits.
interface-attach,server add port / server add floating ip / server add fixed ip,Attach a network interface to a server.
interface-detach,server remove port,Detach a network interface from a server.
interface-list,port list --server,List interfaces attached to a server.
diff --git a/openstackclient/common/project_cleanup.py b/openstackclient/common/project_cleanup.py
index f2536354..1479f1a4 100644
--- a/openstackclient/common/project_cleanup.py
+++ b/openstackclient/common/project_cleanup.py
@@ -28,16 +28,15 @@ from openstackclient.identity import common as identity_common
LOG = logging.getLogger(__name__)
-def ask_user_yesno(msg, default=True):
+def ask_user_yesno(msg):
"""Ask user Y/N question
:param str msg: question text
- :param bool default: default value
:return bool: User choice
"""
while True:
answer = getpass._raw_input(
- '{} [{}]: '.format(msg, 'y/N' if not default else 'Y/n'))
+ '{} [{}]: '.format(msg, 'y/n'))
if answer in ('y', 'Y', 'yes'):
return True
elif answer in ('n', 'N', 'no'):
@@ -129,8 +128,7 @@ class ProjectCleanup(command.Command):
return
confirm = ask_user_yesno(
- _("These resources will be deleted. Are you sure"),
- default=False)
+ _("These resources will be deleted. Are you sure"))
if confirm:
self.log.warning(_('Deleting resources'))
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index 246e44b3..670451e2 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -749,12 +749,10 @@ class SetQuota(common.NetDetectionMixin, command.Command):
class ShowQuota(command.Lister):
- _description = _(
- "Show quotas for project or class. "
- "Specify ``--os-compute-api-version 2.50`` or higher to see "
- "``server-groups`` and ``server-group-members`` output for a given "
- "quota class."
- )
+ _description = _("""Show quotas for project or class.
+
+Specify ``--os-compute-api-version 2.50`` or higher to see ``server-groups``
+and ``server-group-members`` output for a given quota class.""")
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py
index 07c92a8c..e6dd3a6f 100644
--- a/openstackclient/compute/v2/host.py
+++ b/openstackclient/compute/v2/host.py
@@ -22,10 +22,10 @@ from openstackclient.i18n import _
class ListHost(command.Lister):
- _description = _("List hosts")
+ _description = _("DEPRECATED: List hosts")
def get_parser(self, prog_name):
- parser = super(ListHost, self).get_parser(prog_name)
+ parser = super().get_parser(prog_name)
parser.add_argument(
"--zone",
metavar="<zone>",
@@ -34,17 +34,33 @@ class ListHost(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
columns = (
"Host Name",
"Service",
"Zone"
)
- data = compute_client.api.host_list(parsed_args.zone)
- return (columns,
- (utils.get_dict_properties(
- s, columns,
- ) for s in data))
+
+ self.log.warning(
+ "API has been deprecated. "
+ "Please consider using 'hypervisor list' instead."
+ )
+
+ # doing this since openstacksdk has decided not to support this
+ # deprecated command
+ hosts = compute_client.get(
+ '/os-hosts', microversion='2.1'
+ ).json().get('hosts')
+
+ if parsed_args.zone is not None:
+ filtered_hosts = []
+ for host in hosts:
+ if host['zone'] == parsed_args.zone:
+ filtered_hosts.append(host)
+
+ hosts = filtered_hosts
+
+ return columns, (utils.get_dict_properties(s, columns) for s in hosts)
class SetHost(command.Command):
@@ -102,10 +118,10 @@ class SetHost(command.Command):
class ShowHost(command.Lister):
- _description = _("Display host details")
+ _description = _("DEPRECATED: Display host details")
def get_parser(self, prog_name):
- parser = super(ShowHost, self).get_parser(prog_name)
+ parser = super().get_parser(prog_name)
parser.add_argument(
"host",
metavar="<host>",
@@ -114,7 +130,7 @@ class ShowHost(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
columns = (
"Host",
"Project",
@@ -123,9 +139,21 @@ class ShowHost(command.Lister):
"Disk GB"
)
- data = compute_client.api.host_show(parsed_args.host)
+ self.log.warning(
+ "API has been deprecated. "
+ "Please consider using 'hypervisor show' instead."
+ )
+
+ # doing this since openstacksdk has decided not to support this
+ # deprecated command
+ resources = compute_client.get(
+ '/os-hosts/' + parsed_args.host,
+ microversion='2.1'
+ ).json().get('host')
+
+ data = []
+ if resources is not None:
+ for resource in resources:
+ data.append(resource['resource'])
- return (columns,
- (utils.get_dict_properties(
- s, columns,
- ) for s in data))
+ return columns, (utils.get_dict_properties(s, columns) for s in data)
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 85693e17..23bd5e6f 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -608,10 +608,10 @@ class AddServerSecurityGroup(command.Command):
class AddServerVolume(command.ShowOne):
- _description = _(
- "Add volume to server. "
- "Specify ``--os-compute-api-version 2.20`` or higher to add a volume "
- "to a server with status ``SHELVED`` or ``SHELVED_OFFLOADED``.")
+ _description = _("""Add volume to server.
+
+Specify ``--os-compute-api-version 2.20`` or higher to add a volume to a server
+with status ``SHELVED`` or ``SHELVED_OFFLOADED``.""")
def get_parser(self, prog_name):
parser = super(AddServerVolume, self).get_parser(prog_name)
@@ -3757,11 +3757,10 @@ class RemoveServerSecurityGroup(command.Command):
class RemoveServerVolume(command.Command):
- _description = _(
- "Remove volume from server. "
- "Specify ``--os-compute-api-version 2.20`` or higher to remove a "
- "volume from a server with status ``SHELVED`` or "
- "``SHELVED_OFFLOADED``.")
+ _description = _("""Remove volume from server.
+
+Specify ``--os-compute-api-version 2.20`` or higher to remove a
+volume from a server with status ``SHELVED`` or ``SHELVED_OFFLOADED``.""")
def get_parser(self, prog_name):
parser = super(RemoveServerVolume, self).get_parser(prog_name)
@@ -3798,11 +3797,10 @@ class RemoveServerVolume(command.Command):
class RescueServer(command.Command):
- _description = _(
- "Put server in rescue mode. "
- "Specify ``--os-compute-api-version 2.87`` or higher to rescue a "
- "server booted from a volume."
- )
+ _description = _("""Put server in rescue mode.
+
+Specify ``--os-compute-api-version 2.87`` or higher to rescue a
+server booted from a volume.""")
def get_parser(self, prog_name):
parser = super(RescueServer, self).get_parser(prog_name)
@@ -3958,9 +3956,7 @@ Confirm (verify) success of resize operation and release the old server.""")
# TODO(stephenfin): Remove in OSC 7.0
class MigrateConfirm(ResizeConfirm):
- _description = _("""DEPRECATED: Confirm server migration.
-
-Use 'server migration confirm' instead.""")
+ _description = _("DEPRECATED: Use 'server migration confirm' instead.")
def take_action(self, parsed_args):
msg = _(
@@ -4006,9 +4002,7 @@ one.""")
# TODO(stephenfin): Remove in OSC 7.0
class MigrateRevert(ResizeRevert):
- _description = _("""Revert server migration.
-
-Use 'server migration revert' instead.""")
+ _description = _("DEPRECATED: Use 'server migration revert' instead.")
def take_action(self, parsed_args):
msg = _(
@@ -4346,10 +4340,10 @@ class ShelveServer(command.Command):
class ShowServer(command.ShowOne):
- _description = _(
- "Show server details. Specify ``--os-compute-api-version 2.47`` "
- "or higher to see the embedded flavor information for the server."
- )
+ _description = _("""Show server details.
+
+Specify ``--os-compute-api-version 2.47`` or higher to see the embedded flavor
+information for the server.""")
def get_parser(self, prog_name):
parser = super(ShowServer, self).get_parser(prog_name)
diff --git a/openstackclient/compute/v2/server_migration.py b/openstackclient/compute/v2/server_migration.py
index 016d15d7..91575c1e 100644
--- a/openstackclient/compute/v2/server_migration.py
+++ b/openstackclient/compute/v2/server_migration.py
@@ -14,7 +14,6 @@
import uuid
-from novaclient import api_versions
from openstack import utils as sdk_utils
from osc_lib.command import command
from osc_lib import exceptions
@@ -256,7 +255,7 @@ class ListMigration(command.Lister):
def _get_migration_by_uuid(compute_client, server_id, migration_uuid):
- for migration in compute_client.server_migrations.list(server_id):
+ for migration in compute_client.server_migrations(server_id):
if migration.uuid == migration_uuid:
return migration
break
@@ -290,9 +289,9 @@ class ShowMigration(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
- if compute_client.api_version < api_versions.APIVersion('2.24'):
+ if not sdk_utils.supports_microversion(compute_client, '2.24'):
msg = _(
'--os-compute-api-version 2.24 or greater is required to '
'support the server migration show command'
@@ -308,16 +307,16 @@ class ShowMigration(command.ShowOne):
)
raise exceptions.CommandError(msg)
- if compute_client.api_version < api_versions.APIVersion('2.59'):
+ if not sdk_utils.supports_microversion(compute_client, '2.59'):
msg = _(
'--os-compute-api-version 2.59 or greater is required to '
'retrieve server migrations by UUID'
)
raise exceptions.CommandError(msg)
- server = utils.find_resource(
- compute_client.servers,
+ server = compute_client.find_server(
parsed_args.server,
+ ignore_missing=False,
)
# the nova API doesn't currently allow retrieval by UUID but it's a
@@ -328,11 +327,13 @@ class ShowMigration(command.ShowOne):
compute_client, server.id, parsed_args.migration,
)
else:
- server_migration = compute_client.server_migrations.get(
- server.id, parsed_args.migration,
+ server_migration = compute_client.get_server_migration(
+ server.id,
+ parsed_args.migration,
+ ignore_missing=False,
)
- columns = (
+ column_headers = (
'ID',
'Server UUID',
'Status',
@@ -351,14 +352,35 @@ class ShowMigration(command.ShowOne):
'Updated At',
)
- if compute_client.api_version >= api_versions.APIVersion('2.59'):
- columns += ('UUID',)
+ columns = (
+ 'id',
+ 'server_id',
+ 'status',
+ 'source_compute',
+ 'source_node',
+ 'dest_compute',
+ 'dest_host',
+ 'dest_node',
+ 'memory_total_bytes',
+ 'memory_processed_bytes',
+ 'memory_remaining_bytes',
+ 'disk_total_bytes',
+ 'disk_processed_bytes',
+ 'disk_remaining_bytes',
+ 'created_at',
+ 'updated_at',
+ )
- if compute_client.api_version >= api_versions.APIVersion('2.80'):
- columns += ('User ID', 'Project ID')
+ if sdk_utils.supports_microversion(compute_client, '2.59'):
+ column_headers += ('UUID',)
+ columns += ('uuid',)
+
+ if sdk_utils.supports_microversion(compute_client, '2.80'):
+ column_headers += ('User ID', 'Project ID')
+ columns += ('user_id', 'project_id')
data = utils.get_item_properties(server_migration, columns)
- return columns, data
+ return column_headers, data
class AbortMigration(command.Command):
@@ -382,9 +404,9 @@ class AbortMigration(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
- if compute_client.api_version < api_versions.APIVersion('2.24'):
+ if not sdk_utils.supports_microversion(compute_client, '2.24'):
msg = _(
'--os-compute-api-version 2.24 or greater is required to '
'support the server migration abort command'
@@ -400,16 +422,16 @@ class AbortMigration(command.Command):
)
raise exceptions.CommandError(msg)
- if compute_client.api_version < api_versions.APIVersion('2.59'):
+ if not sdk_utils.supports_microversion(compute_client, '2.59'):
msg = _(
'--os-compute-api-version 2.59 or greater is required to '
'abort server migrations by UUID'
)
raise exceptions.CommandError(msg)
- server = utils.find_resource(
- compute_client.servers,
+ server = compute_client.find_server(
parsed_args.server,
+ ignore_missing=False,
)
# the nova API doesn't currently allow retrieval by UUID but it's a
@@ -421,8 +443,10 @@ class AbortMigration(command.Command):
compute_client, server.id, parsed_args.migration,
).id
- compute_client.server_migrations.live_migration_abort(
- server.id, migration_id,
+ compute_client.abort_server_migration(
+ migration_id,
+ server.id,
+ ignore_missing=False,
)
@@ -447,9 +471,9 @@ class ForceCompleteMigration(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
- if compute_client.api_version < api_versions.APIVersion('2.22'):
+ if not sdk_utils.supports_microversion(compute_client, '2.22'):
msg = _(
'--os-compute-api-version 2.22 or greater is required to '
'support the server migration force complete command'
@@ -465,16 +489,16 @@ class ForceCompleteMigration(command.Command):
)
raise exceptions.CommandError(msg)
- if compute_client.api_version < api_versions.APIVersion('2.59'):
+ if not sdk_utils.supports_microversion(compute_client, '2.59'):
msg = _(
'--os-compute-api-version 2.59 or greater is required to '
'abort server migrations by UUID'
)
raise exceptions.CommandError(msg)
- server = utils.find_resource(
- compute_client.servers,
+ server = compute_client.find_server(
parsed_args.server,
+ ignore_missing=False,
)
# the nova API doesn't currently allow retrieval by UUID but it's a
@@ -486,6 +510,6 @@ class ForceCompleteMigration(command.Command):
compute_client, server.id, parsed_args.migration,
).id
- compute_client.server_migrations.live_migrate_force_complete(
- server.id, migration_id,
+ compute_client.force_complete_server_migration(
+ migration_id, server.id
)
diff --git a/openstackclient/compute/v2/server_volume.py b/openstackclient/compute/v2/server_volume.py
index d53cec93..b4322c0b 100644
--- a/openstackclient/compute/v2/server_volume.py
+++ b/openstackclient/compute/v2/server_volume.py
@@ -14,7 +14,7 @@
"""Compute v2 Server action implementations"""
-from novaclient import api_versions
+from openstack import utils as sdk_utils
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@@ -34,27 +34,25 @@ class ListServerVolume(command.Lister):
return parser
def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.sdk_connection.compute
- compute_client = self.app.client_manager.compute
-
- server = utils.find_resource(
- compute_client.servers,
+ server = compute_client.find_server(
parsed_args.server,
+ ignore_missing=False,
)
-
- volumes = compute_client.volumes.get_server_volumes(server.id)
+ volumes = compute_client.volume_attachments(server)
columns = ()
column_headers = ()
- if compute_client.api_version < api_versions.APIVersion('2.89'):
+ if not sdk_utils.supports_microversion(compute_client, '2.89'):
columns += ('id',)
column_headers += ('ID',)
columns += (
'device',
- 'serverId',
- 'volumeId',
+ 'server_id',
+ 'volume_id',
)
column_headers += (
'Device',
@@ -62,40 +60,36 @@ class ListServerVolume(command.Lister):
'Volume ID',
)
- if compute_client.api_version >= api_versions.APIVersion('2.70'):
+ if sdk_utils.supports_microversion(compute_client, '2.70'):
columns += ('tag',)
column_headers += ('Tag',)
- if compute_client.api_version >= api_versions.APIVersion('2.79'):
+ if sdk_utils.supports_microversion(compute_client, '2.79'):
columns += ('delete_on_termination',)
column_headers += ('Delete On Termination?',)
- if compute_client.api_version >= api_versions.APIVersion('2.89'):
- columns += ('attachment_id', 'bdm_uuid')
+ if sdk_utils.supports_microversion(compute_client, '2.89'):
+ columns += ('attachment_id', 'bdm_id')
column_headers += ('Attachment ID', 'BlockDeviceMapping UUID')
return (
column_headers,
- (
- utils.get_item_properties(
- s, columns, mixed_case_fields=('serverId', 'volumeId')
- ) for s in volumes
- ),
+ (utils.get_item_properties(s, columns) for s in volumes),
)
-class UpdateServerVolume(command.Command):
+class SetServerVolume(command.Command):
"""Update a volume attachment on the server."""
def get_parser(self, prog_name):
- parser = super(UpdateServerVolume, self).get_parser(prog_name)
+ parser = super().get_parser(prog_name)
parser.add_argument(
'server',
help=_('Server to update volume for (name or ID)'),
)
parser.add_argument(
'volume',
- help=_('Volume (ID)'),
+ help=_('Volume to update attachment for (name or ID)'),
)
termination_group = parser.add_mutually_exclusive_group()
termination_group.add_argument(
@@ -120,31 +114,34 @@ class UpdateServerVolume(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
+ volume_client = self.app.client_manager.sdk_connection.volume
if parsed_args.delete_on_termination is not None:
- if compute_client.api_version < api_versions.APIVersion('2.85'):
+ if not sdk_utils.supports_microversion(compute_client, '2.85'):
msg = _(
'--os-compute-api-version 2.85 or greater is required to '
- 'support the --(no-)delete-on-termination option'
+ 'support the -delete-on-termination or '
+ '--preserve-on-termination option'
)
raise exceptions.CommandError(msg)
- server = utils.find_resource(
- compute_client.servers,
+ server = compute_client.find_server(
parsed_args.server,
+ ignore_missing=False,
)
-
- # NOTE(stephenfin): This may look silly, and that's because it is.
- # This API was originally used only for the swapping volumes, which
- # is an internal operation that should only be done by
- # orchestration software rather than a human. We're not going to
- # expose that, but we are going to expose the ability to change the
- # delete on termination behavior.
- compute_client.volumes.update_server_volume(
- server.id,
- parsed_args.volume,
+ volume = volume_client.find_volume(
parsed_args.volume,
+ ignore_missing=False,
+ )
+
+ compute_client.update_volume_attachment(
+ server,
+ volume,
delete_on_termination=parsed_args.delete_on_termination,
)
+
+
+# Legacy alias
+class UpdateServerVolume(SetServerVolume):
+ """DEPRECATED: Use 'server volume set' instead."""
diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py
index 8605156c..fad717c9 100644
--- a/openstackclient/compute/v2/service.py
+++ b/openstackclient/compute/v2/service.py
@@ -71,11 +71,11 @@ class DeleteService(command.Command):
class ListService(command.Lister):
- _description = _("List compute services. Using "
- "``--os-compute-api-version`` 2.53 or greater will "
- "return the ID as a UUID value which can be used to "
- "uniquely identify the service in a multi-cell "
- "deployment.")
+ _description = _("""List compute services.
+
+Using ``--os-compute-api-version`` 2.53 or greater will return the ID as a UUID
+value which can be used to uniquely identify the service in a multi-cell
+deployment.""")
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py
index bcd5add4..0156af8e 100644
--- a/openstackclient/network/v2/floating_ip_port_forwarding.py
+++ b/openstackclient/network/v2/floating_ip_port_forwarding.py
@@ -25,6 +25,61 @@ from openstackclient.network import common
LOG = logging.getLogger(__name__)
+def validate_ports_diff(ports):
+ if len(ports) == 0:
+ return 0
+
+ ports_diff = ports[-1] - ports[0]
+ if ports_diff < 0:
+ msg = _("The last number in port range must be"
+ " greater or equal to the first")
+ raise exceptions.CommandError(msg)
+ return ports_diff
+
+
+def validate_ports_match(internal_ports, external_ports):
+ internal_ports_diff = validate_ports_diff(internal_ports)
+ external_ports_diff = validate_ports_diff(external_ports)
+
+ if internal_ports_diff != 0 and internal_ports_diff != external_ports_diff:
+ msg = _("The relation between internal and external ports does not "
+ "match the pattern 1:N and N:N")
+ raise exceptions.CommandError(msg)
+
+
+def validate_and_assign_port_ranges(parsed_args, attrs):
+ internal_port_range = parsed_args.internal_protocol_port
+ external_port_range = parsed_args.external_protocol_port
+ external_ports = internal_ports = []
+ if external_port_range:
+ external_ports = list(map(int, str(external_port_range).split(':')))
+ if internal_port_range:
+ internal_ports = list(map(int, str(internal_port_range).split(':')))
+
+ validate_ports_match(internal_ports, external_ports)
+
+ for port in external_ports + internal_ports:
+ validate_port(port)
+
+ if internal_port_range:
+ if ':' in internal_port_range:
+ attrs['internal_port_range'] = internal_port_range
+ else:
+ attrs['internal_port'] = int(internal_port_range)
+
+ if external_port_range:
+ if ':' in external_port_range:
+ attrs['external_port_range'] = external_port_range
+ else:
+ attrs['external_port'] = int(external_port_range)
+
+
+def validate_port(port):
+ if port <= 0 or port > 65535:
+ msg = _("The port number range is <1-65535>")
+ raise exceptions.CommandError(msg)
+
+
def _get_columns(item):
column_map = {}
hidden_columns = ['location', 'tenant_id']
@@ -58,7 +113,6 @@ class CreateFloatingIPPortForwarding(command.ShowOne,
)
parser.add_argument(
'--internal-protocol-port',
- type=int,
metavar='<port-number>',
required=True,
help=_("The protocol port number "
@@ -67,7 +121,6 @@ class CreateFloatingIPPortForwarding(command.ShowOne,
)
parser.add_argument(
'--external-protocol-port',
- type=int,
metavar='<port-number>',
required=True,
help=_("The protocol port number of "
@@ -92,6 +145,7 @@ class CreateFloatingIPPortForwarding(command.ShowOne,
help=_("Floating IP that the port forwarding belongs to "
"(IP address or ID)")
)
+
return parser
def take_action(self, parsed_args):
@@ -102,19 +156,7 @@ class CreateFloatingIPPortForwarding(command.ShowOne,
ignore_missing=False,
)
- if parsed_args.internal_protocol_port is not None:
- if (parsed_args.internal_protocol_port <= 0 or
- parsed_args.internal_protocol_port > 65535):
- msg = _("The port number range is <1-65535>")
- raise exceptions.CommandError(msg)
- attrs['internal_port'] = parsed_args.internal_protocol_port
-
- if parsed_args.external_protocol_port is not None:
- if (parsed_args.external_protocol_port <= 0 or
- parsed_args.external_protocol_port > 65535):
- msg = _("The port number range is <1-65535>")
- raise exceptions.CommandError(msg)
- attrs['external_port'] = parsed_args.external_protocol_port
+ validate_and_assign_port_ranges(parsed_args, attrs)
if parsed_args.port:
port = client.find_port(parsed_args.port,
@@ -226,7 +268,9 @@ class ListFloatingIPPortForwarding(command.Lister):
'internal_port_id',
'internal_ip_address',
'internal_port',
+ 'internal_port_range',
'external_port',
+ 'external_port_range',
'protocol',
'description',
)
@@ -235,7 +279,9 @@ class ListFloatingIPPortForwarding(command.Lister):
'Internal Port ID',
'Internal IP Address',
'Internal Port',
+ 'Internal Port Range',
'External Port',
+ 'External Port Range',
'Protocol',
'Description',
)
@@ -246,8 +292,13 @@ class ListFloatingIPPortForwarding(command.Lister):
port = client.find_port(parsed_args.port,
ignore_missing=False)
query['internal_port_id'] = port.id
- if parsed_args.external_protocol_port is not None:
- query['external_port'] = parsed_args.external_protocol_port
+ external_port = parsed_args.external_protocol_port
+ if external_port:
+ if ':' in external_port:
+ query['external_port_range'] = external_port
+ else:
+ query['external_port'] = int(
+ parsed_args.external_protocol_port)
if parsed_args.protocol is not None:
query['protocol'] = parsed_args.protocol
@@ -297,14 +348,12 @@ class SetFloatingIPPortForwarding(common.NeutronCommandWithExtraArgs):
parser.add_argument(
'--internal-protocol-port',
metavar='<port-number>',
- type=int,
help=_("The TCP/UDP/other protocol port number of the "
"network port fixed IPv4 address associated to "
"the floating IP port forwarding")
)
parser.add_argument(
'--external-protocol-port',
- type=int,
metavar='<port-number>',
help=_("The TCP/UDP/other protocol port number of the "
"port forwarding's floating IP address")
@@ -339,19 +388,8 @@ class SetFloatingIPPortForwarding(common.NeutronCommandWithExtraArgs):
if parsed_args.internal_ip_address:
attrs['internal_ip_address'] = parsed_args.internal_ip_address
- if parsed_args.internal_protocol_port is not None:
- if (parsed_args.internal_protocol_port <= 0 or
- parsed_args.internal_protocol_port > 65535):
- msg = _("The port number range is <1-65535>")
- raise exceptions.CommandError(msg)
- attrs['internal_port'] = parsed_args.internal_protocol_port
-
- if parsed_args.external_protocol_port is not None:
- if (parsed_args.external_protocol_port <= 0 or
- parsed_args.external_protocol_port > 65535):
- msg = _("The port number range is <1-65535>")
- raise exceptions.CommandError(msg)
- attrs['external_port'] = parsed_args.external_protocol_port
+
+ validate_and_assign_port_ranges(parsed_args, attrs)
if parsed_args.protocol:
attrs['protocol'] = parsed_args.protocol
diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py
index 37183a79..05945a02 100644
--- a/openstackclient/tests/functional/compute/v2/test_server.py
+++ b/openstackclient/tests/functional/compute/v2/test_server.py
@@ -1375,10 +1375,8 @@ class ServerTests(common.ComputeTestCase):
parse_output=True,
)
- self.assertIsNotNone(cmd_output['ID'])
self.assertEqual(server_id, cmd_output['Server ID'])
self.assertEqual(volume_id, cmd_output['Volume ID'])
- volume_attachment_id = cmd_output['ID']
cmd_output = self.openstack(
'server volume list ' +
@@ -1386,7 +1384,6 @@ class ServerTests(common.ComputeTestCase):
parse_output=True,
)
- self.assertEqual(volume_attachment_id, cmd_output[0]['ID'])
self.assertEqual(server_id, cmd_output[0]['Server ID'])
self.assertEqual(volume_id, cmd_output[0]['Volume ID'])
diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py
index b2702128..f7f07509 100644
--- a/openstackclient/tests/unit/compute/v2/fakes.py
+++ b/openstackclient/tests/unit/compute/v2/fakes.py
@@ -21,9 +21,11 @@ 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 migration as _migration
from openstack.compute.v2 import server as _server
from openstack.compute.v2 import server_group as _server_group
from openstack.compute.v2 import server_interface as _server_interface
+from openstack.compute.v2 import server_migration as _server_migration
from openstack.compute.v2 import service
from openstack.compute.v2 import volume_attachment
@@ -1433,242 +1435,155 @@ class FakeRateLimit(object):
self.next_available = next_available
-class FakeMigration(object):
- """Fake one or more migrations."""
+def create_one_migration(attrs=None):
+ """Create a fake migration.
- @staticmethod
- def create_one_migration(attrs=None, methods=None):
- """Create a fake migration.
-
- :param dict attrs:
- A dictionary with all attributes
- :param dict methods:
- A dictionary with all methods
- :return:
- A FakeResource object, with id, type, and so on
- """
- attrs = attrs or {}
- methods = methods or {}
+ :param dict attrs: A dictionary with all attributes
+ :return: A fake openstack.compute.v2.migration.Migration object
+ """
+ attrs = attrs or {}
- # Set default attributes.
- migration_info = {
- "dest_host": "10.0.2.15",
- "status": "migrating",
- "migration_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,
- "instance_uuid": 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_id": uuid.uuid4().hex,
- "user_id": uuid.uuid4().hex
- }
+ # Set default attributes.
+ migration_info = {
+ "created_at": "2017-01-31T08:03:21.000000",
+ "dest_compute": "compute-" + uuid.uuid4().hex,
+ "dest_host": "10.0.2.15",
+ "dest_node": "node-" + uuid.uuid4().hex,
+ "id": random.randint(1, 999),
+ "migration_type": "migration",
+ "new_flavor_id": uuid.uuid4().hex,
+ "old_flavor_id": uuid.uuid4().hex,
+ "project_id": uuid.uuid4().hex,
+ "server_id": uuid.uuid4().hex,
+ "source_compute": "compute-" + uuid.uuid4().hex,
+ "source_node": "node-" + uuid.uuid4().hex,
+ "status": "migrating",
+ "updated_at": "2017-01-31T08:03:25.000000",
+ "user_id": uuid.uuid4().hex,
+ "uuid": uuid.uuid4().hex,
+ }
- # Overwrite default attributes.
- migration_info.update(attrs)
+ # Overwrite default attributes.
+ migration_info.update(attrs)
- migration = fakes.FakeResource(info=copy.deepcopy(migration_info),
- methods=methods,
- loaded=True)
- return migration
+ migration = _migration.Migration(**migration_info)
+ return migration
- @staticmethod
- def create_migrations(attrs=None, methods=None, count=2):
- """Create multiple fake migrations.
- :param dict attrs:
- A dictionary with all attributes
- :param dict methods:
- A dictionary with all methods
- :param int count:
- The number of migrations to fake
- :return:
- A list of FakeResource objects faking the migrations
- """
- migrations = []
- for i in range(0, count):
- migrations.append(
- FakeMigration.create_one_migration(
- attrs, methods))
+def create_migrations(attrs=None, count=2):
+ """Create multiple fake migrations.
- return migrations
+ :param dict attrs: A dictionary with all attributes
+ :param int count: The number of migrations to fake
+ :return: A list of fake openstack.compute.v2.migration.Migration objects
+ """
+ migrations = []
+ for i in range(0, count):
+ migrations.append(create_one_migration(attrs))
+ return migrations
-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 dict attrs:
- A dictionary with all attributes
- :param dict methods:
- A dictionary with all methods
- :return:
- A FakeResource object, with id, type, and so on
- """
- attrs = attrs or {}
- methods = methods or {}
+def create_one_server_migration(attrs=None):
+ """Create a fake server migration.
- # Set default attributes.
+ :param dict attrs: A dictionary with all attributes
+ :return A fake openstack.compute.v2.server_migration.ServerMigration object
+ """
+ attrs = attrs or {}
- migration_info = {
- "created_at": "2016-01-29T13:42:02.000000",
- "dest_compute": "compute2",
- "dest_host": "1.2.3.4",
- "dest_node": "node2",
- "id": random.randint(1, 999),
- "server_uuid": uuid.uuid4().hex,
- "source_compute": "compute1",
- "source_node": "node1",
- "status": "running",
- "memory_total_bytes": random.randint(1, 99999),
- "memory_processed_bytes": random.randint(1, 99999),
- "memory_remaining_bytes": random.randint(1, 99999),
- "disk_total_bytes": random.randint(1, 99999),
- "disk_processed_bytes": random.randint(1, 99999),
- "disk_remaining_bytes": random.randint(1, 99999),
- "updated_at": "2016-01-29T13:42:02.000000",
- # added in 2.59
- "uuid": uuid.uuid4().hex,
- # added in 2.80
- "user_id": uuid.uuid4().hex,
- "project_id": uuid.uuid4().hex,
- }
+ # Set default attributes.
- # Overwrite default attributes.
- migration_info.update(attrs)
+ migration_info = {
+ "created_at": "2016-01-29T13:42:02.000000",
+ "dest_compute": "compute2",
+ "dest_host": "1.2.3.4",
+ "dest_node": "node2",
+ "id": random.randint(1, 999),
+ "server_uuid": uuid.uuid4().hex,
+ "source_compute": "compute1",
+ "source_node": "node1",
+ "status": "running",
+ "memory_total_bytes": random.randint(1, 99999),
+ "memory_processed_bytes": random.randint(1, 99999),
+ "memory_remaining_bytes": random.randint(1, 99999),
+ "disk_total_bytes": random.randint(1, 99999),
+ "disk_processed_bytes": random.randint(1, 99999),
+ "disk_remaining_bytes": random.randint(1, 99999),
+ "updated_at": "2016-01-29T13:42:02.000000",
+ # added in 2.59
+ "uuid": uuid.uuid4().hex,
+ # added in 2.80
+ "user_id": uuid.uuid4().hex,
+ "project_id": uuid.uuid4().hex,
+ }
- migration = fakes.FakeResource(
- info=copy.deepcopy(migration_info),
- methods=methods,
- loaded=True)
- return migration
+ # Overwrite default attributes.
+ migration_info.update(attrs)
+ migration = _server_migration.ServerMigration(**migration_info)
+ return migration
-class FakeVolumeAttachment(object):
- """Fake one or more volume attachments (BDMs)."""
- @staticmethod
- def create_one_volume_attachment(attrs=None, methods=None):
- """Create a fake volume attachment.
+def create_server_migrations(attrs=None, methods=None, count=2):
+ """Create multiple server migrations.
- :param dict attrs:
- A dictionary with all attributes
- :param dict methods:
- A dictionary with all methods
- :return:
- A FakeResource object, with id, device, and so on
- """
- attrs = attrs or {}
- methods = methods or {}
-
- # Set default attributes.
- volume_attachment_info = {
- "id": uuid.uuid4().hex,
- "device": "/dev/sdb",
- "serverId": uuid.uuid4().hex,
- "volumeId": uuid.uuid4().hex,
- # introduced in API microversion 2.70
- "tag": "foo",
- # introduced in API microversion 2.79
- "delete_on_termination": True,
- # introduced in API microversion 2.89
- "attachment_id": uuid.uuid4().hex,
- "bdm_uuid": uuid.uuid4().hex
- }
-
- # Overwrite default attributes.
- volume_attachment_info.update(attrs)
-
- volume_attachment = fakes.FakeResource(
- info=copy.deepcopy(volume_attachment_info),
- methods=methods,
- loaded=True)
- return volume_attachment
+ :param dict attrs: A dictionary with all attributes
+ :param int count: The number of server migrations to fake
+ :return A list of fake
+ openstack.compute.v2.server_migration.ServerMigration objects
+ """
+ migrations = []
+ for i in range(0, count):
+ migrations.append(
+ create_one_server_migration(attrs, methods))
- @staticmethod
- def create_volume_attachments(attrs=None, methods=None, count=2):
- """Create multiple fake volume attachments (BDMs).
+ return migrations
- :param dict attrs:
- A dictionary with all attributes
- :param dict methods:
- A dictionary with all methods
- :param int count:
- The number of volume attachments to fake
- :return:
- A list of FakeResource objects faking the volume attachments.
- """
- volume_attachments = []
- for i in range(0, count):
- volume_attachments.append(
- FakeVolumeAttachment.create_one_volume_attachment(
- attrs, methods))
- return volume_attachments
+def create_one_volume_attachment(attrs=None):
+ """Create a fake volume attachment.
- @staticmethod
- def create_one_sdk_volume_attachment(attrs=None, methods=None):
- """Create a fake sdk VolumeAttachment.
+ :param dict attrs: A dictionary with all attributes
+ :return: A fake openstack.compute.v2.volume_attachment.VolumeAttachment
+ object
+ """
+ attrs = attrs or {}
- :param dict attrs:
- A dictionary with all attributes
- :param dict methods:
- A dictionary with all methods
- :return:
- A fake VolumeAttachment object, with id, device, and so on
- """
- attrs = attrs or {}
- methods = methods or {}
+ # Set default attributes.
+ volume_attachment_info = {
+ "id": uuid.uuid4().hex,
+ "device": "/dev/sdb",
+ "server_id": uuid.uuid4().hex,
+ "volume_id": uuid.uuid4().hex,
+ # introduced in API microversion 2.70
+ "tag": "foo",
+ # introduced in API microversion 2.79
+ "delete_on_termination": True,
+ # introduced in API microversion 2.89
+ "attachment_id": uuid.uuid4().hex,
+ "bdm_id": uuid.uuid4().hex,
+ }
- # Set default attributes.
- volume_attachment_info = {
- "id": uuid.uuid4().hex,
- "device": "/dev/sdb",
- "server_id": uuid.uuid4().hex,
- "volume_id": uuid.uuid4().hex,
- # introduced in API microversion 2.70
- "tag": "foo",
- # introduced in API microversion 2.79
- "delete_on_termination": True,
- # introduced in API microversion 2.89
- "attachment_id": uuid.uuid4().hex,
- "bdm_uuid": uuid.uuid4().hex
- }
+ # Overwrite default attributes.
+ volume_attachment_info.update(attrs)
- # Overwrite default attributes.
- volume_attachment_info.update(attrs)
+ return volume_attachment.VolumeAttachment(**volume_attachment_info)
- return volume_attachment.VolumeAttachment(**volume_attachment_info)
- @staticmethod
- def create_sdk_volume_attachments(attrs=None, methods=None, count=2):
- """Create multiple fake VolumeAttachment objects (BDMs).
+def create_volume_attachments(attrs=None, count=2):
+ """Create multiple fake volume attachments.
- :param dict attrs:
- A dictionary with all attributes
- :param dict methods:
- A dictionary with all methods
- :param int count:
- The number of volume attachments to fake
- :return:
- A list of VolumeAttachment objects faking the volume attachments.
- """
- volume_attachments = []
- for i in range(0, count):
- volume_attachments.append(
- FakeVolumeAttachment.create_one_sdk_volume_attachment(
- attrs, methods))
+ :param dict attrs: A dictionary with all attributes
+ :param int count: The number of volume attachments to fake
+ :return: A list of fake
+ openstack.compute.v2.volume_attachment.VolumeAttachment objects
+ """
+ volume_attachments = []
+ for i in range(0, count):
+ volume_attachments.append(create_one_volume_attachment(attrs))
- return volume_attachments
+ return volume_attachments
def create_one_hypervisor(attrs=None):
diff --git a/openstackclient/tests/unit/compute/v2/test_host.py b/openstackclient/tests/unit/compute/v2/test_host.py
index 4e1b5ad1..ec91b37a 100644
--- a/openstackclient/tests/unit/compute/v2/test_host.py
+++ b/openstackclient/tests/unit/compute/v2/test_host.py
@@ -17,6 +17,7 @@ from unittest import mock
from openstackclient.compute.v2 import host
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit import fakes
from openstackclient.tests.unit import utils as tests_utils
@@ -26,7 +27,10 @@ class TestHost(compute_fakes.TestComputev2):
super(TestHost, self).setUp()
# Get a shortcut to the compute client
- self.compute = self.app.client_manager.compute
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.app.client_manager.sdk_connection.compute = mock.Mock()
+ self.sdk_client = self.app.client_manager.sdk_connection.compute
+ self.sdk_client.get = mock.Mock()
@mock.patch(
@@ -34,27 +38,29 @@ class TestHost(compute_fakes.TestComputev2):
)
class TestHostList(TestHost):
- host = compute_fakes.FakeHost.create_one_host()
-
- columns = (
- 'Host Name',
- 'Service',
- 'Zone',
- )
-
- data = [(
- host['host_name'],
- host['service'],
- host['zone'],
- )]
+ _host = compute_fakes.FakeHost.create_one_host()
def setUp(self):
super(TestHostList, self).setUp()
+ self.sdk_client.get.return_value = fakes.FakeResponse(
+ data={'hosts': [self._host]}
+ )
+
+ self.columns = (
+ 'Host Name', 'Service', 'Zone'
+ )
+
+ self.data = [(
+ self._host['host_name'],
+ self._host['service'],
+ self._host['zone'],
+ )]
+
self.cmd = host.ListHost(self.app, None)
def test_host_list_no_option(self, h_mock):
- h_mock.return_value = [self.host]
+ h_mock.return_value = [self._host]
arglist = []
verifylist = []
@@ -62,24 +68,24 @@ class TestHostList(TestHost):
columns, data = self.cmd.take_action(parsed_args)
- h_mock.assert_called_with(None)
+ self.sdk_client.get.assert_called_with('/os-hosts', microversion='2.1')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
def test_host_list_with_option(self, h_mock):
- h_mock.return_value = [self.host]
+ h_mock.return_value = [self._host]
arglist = [
- '--zone', self.host['zone'],
+ '--zone', self._host['zone'],
]
verifylist = [
- ('zone', self.host['zone']),
+ ('zone', self._host['zone']),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- h_mock.assert_called_with(self.host['zone'])
+ self.sdk_client.get.assert_called_with('/os-hosts', microversion='2.1')
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
@@ -141,31 +147,43 @@ class TestHostSet(TestHost):
)
class TestHostShow(TestHost):
- host = compute_fakes.FakeHost.create_one_host()
-
- columns = (
- 'Host',
- 'Project',
- 'CPU',
- 'Memory MB',
- 'Disk GB',
- )
-
- data = [(
- host['host'],
- host['project'],
- host['cpu'],
- host['memory_mb'],
- host['disk_gb'],
- )]
+ _host = compute_fakes.FakeHost.create_one_host()
def setUp(self):
super(TestHostShow, self).setUp()
+ output_data = {"resource": {
+ "host": self._host['host'],
+ "project": self._host['project'],
+ "cpu": self._host['cpu'],
+ "memory_mb": self._host['memory_mb'],
+ "disk_gb": self._host['disk_gb']
+ }}
+
+ self.sdk_client.get.return_value = fakes.FakeResponse(
+ data={'host': [output_data]}
+ )
+
+ self.columns = (
+ 'Host',
+ 'Project',
+ 'CPU',
+ 'Memory MB',
+ 'Disk GB',
+ )
+
+ self.data = [(
+ self._host['host'],
+ self._host['project'],
+ self._host['cpu'],
+ self._host['memory_mb'],
+ self._host['disk_gb'],
+ )]
+
self.cmd = host.ShowHost(self.app, None)
def test_host_show_no_option(self, h_mock):
- h_mock.host_show.return_value = [self.host]
+ h_mock.host_show.return_value = [self._host]
arglist = []
verifylist = []
@@ -174,18 +192,21 @@ class TestHostShow(TestHost):
self.cmd, arglist, verifylist)
def test_host_show_with_option(self, h_mock):
- h_mock.return_value = [self.host]
+ h_mock.return_value = [self._host]
arglist = [
- self.host['host_name'],
+ self._host['host_name'],
]
verifylist = [
- ('host', self.host['host_name']),
+ ('host', self._host['host_name']),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- h_mock.assert_called_with(self.host['host_name'])
+ self.sdk_client.get.assert_called_with(
+ '/os-hosts/' + self._host['host_name'],
+ microversion='2.1'
+ )
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, list(data))
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 00f9b3a7..a5d5a43f 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -931,8 +931,7 @@ class TestServerVolume(TestServer):
'volume_id': self.volumes[0].id,
}
self.volume_attachment = \
- compute_fakes.FakeVolumeAttachment.\
- create_one_sdk_volume_attachment(attrs=attrs)
+ compute_fakes.create_one_volume_attachment(attrs=attrs)
self.sdk_client.create_volume_attachment.return_value = \
self.volume_attachment
diff --git a/openstackclient/tests/unit/compute/v2/test_server_migration.py b/openstackclient/tests/unit/compute/v2/test_server_migration.py
index 93c1865a..afe868d9 100644
--- a/openstackclient/tests/unit/compute/v2/test_server_migration.py
+++ b/openstackclient/tests/unit/compute/v2/test_server_migration.py
@@ -40,6 +40,18 @@ class TestServerMigration(compute_fakes.TestComputev2):
self.app.client_manager.sdk_connection.compute = mock.Mock()
self.sdk_client = self.app.client_manager.sdk_connection.compute
+ patcher = mock.patch.object(
+ sdk_utils, 'supports_microversion', return_value=True)
+ self.addCleanup(patcher.stop)
+ self.supports_microversion_mock = patcher.start()
+
+ def _set_mock_microversion(self, mock_v):
+ """Set a specific microversion for the mock supports_microversion()."""
+ self.supports_microversion_mock.reset_mock(return_value=True)
+ self.supports_microversion_mock.side_effect = (
+ lambda _, v:
+ api_versions.APIVersion(v) <= api_versions.APIVersion(mock_v))
+
class TestListMigration(TestServerMigration):
"""Test fetch all migrations."""
@@ -51,19 +63,20 @@ class TestListMigration(TestServerMigration):
]
MIGRATION_FIELDS = [
- 'source_node', 'dest_node', 'source_compute', 'dest_compute',
- 'dest_host', 'status', 'server_id', 'old_flavor_id',
+ 'source_node', 'dest_node', 'source_compute',
+ 'dest_compute', 'dest_host', 'status', 'server_id', 'old_flavor_id',
'new_flavor_id', 'created_at', 'updated_at'
]
def setUp(self):
super().setUp()
- self.server = compute_fakes.FakeServer.create_one_server()
+ self._set_mock_microversion('2.1')
+
+ self.server = compute_fakes.FakeServer.create_one_sdk_server()
self.sdk_client.find_server.return_value = self.server
- self.migrations = compute_fakes.FakeMigration.create_migrations(
- count=3)
+ self.migrations = compute_fakes.create_migrations(count=3)
self.sdk_client.migrations.return_value = self.migrations
self.data = (common_utils.get_item_properties(
@@ -72,20 +85,6 @@ class TestListMigration(TestServerMigration):
# Get the command object to test
self.cmd = server_migration.ListMigration(self.app, None)
- patcher = mock.patch.object(
- sdk_utils, 'supports_microversion', return_value=True)
- self.addCleanup(patcher.stop)
- self.supports_microversion_mock = patcher.start()
- self._set_mock_microversion(
- self.app.client_manager.compute.api_version.get_string())
-
- def _set_mock_microversion(self, mock_v):
- """Set a specific microversion for the mock supports_microversion()."""
- self.supports_microversion_mock.reset_mock(return_value=True)
- self.supports_microversion_mock.side_effect = (
- lambda _, v:
- api_versions.APIVersion(v) <= api_versions.APIVersion(mock_v))
-
def test_server_migration_list_no_options(self):
arglist = []
verifylist = []
@@ -601,12 +600,15 @@ class TestServerMigrationShow(TestServerMigration):
def setUp(self):
super().setUp()
- self.server = compute_fakes.FakeServer.create_one_server()
- self.servers_mock.get.return_value = self.server
+ self.server = compute_fakes.FakeServer.create_one_sdk_server()
+ self.sdk_client.find_server.return_value = self.server
- self.server_migration = compute_fakes.FakeServerMigration\
- .create_one_server_migration()
- self.server_migrations_mock.get.return_value = self.server_migration
+ self.server_migration = compute_fakes.create_one_server_migration()
+ self.sdk_client.get_server_migration.return_value =\
+ self.server_migration
+ self.sdk_client.server_migrations.return_value = iter(
+ [self.server_migration]
+ )
self.columns = (
'ID',
@@ -629,7 +631,7 @@ class TestServerMigrationShow(TestServerMigration):
self.data = (
self.server_migration.id,
- self.server_migration.server_uuid,
+ self.server_migration.server_id,
self.server_migration.status,
self.server_migration.source_compute,
self.server_migration.source_node,
@@ -662,19 +664,18 @@ class TestServerMigrationShow(TestServerMigration):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
- self.servers_mock.get.assert_called_with(self.server.id)
- self.server_migrations_mock.get.assert_called_with(
- self.server.id, '2',)
+ self.sdk_client.find_server.assert_called_with(
+ self.server.id, ignore_missing=False)
+ self.sdk_client.get_server_migration.assert_called_with(
+ self.server.id, '2', ignore_missing=False)
def test_server_migration_show(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.24')
+ self._set_mock_microversion('2.24')
self._test_server_migration_show()
def test_server_migration_show_v259(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.59')
+ self._set_mock_microversion('2.59')
self.columns += ('UUID',)
self.data += (self.server_migration.uuid,)
@@ -682,8 +683,7 @@ class TestServerMigrationShow(TestServerMigration):
self._test_server_migration_show()
def test_server_migration_show_v280(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.80')
+ self._set_mock_microversion('2.80')
self.columns += ('UUID', 'User ID', 'Project ID')
self.data += (
@@ -695,8 +695,7 @@ class TestServerMigrationShow(TestServerMigration):
self._test_server_migration_show()
def test_server_migration_show_pre_v224(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.23')
+ self._set_mock_microversion('2.23')
arglist = [
self.server.id,
@@ -714,9 +713,11 @@ class TestServerMigrationShow(TestServerMigration):
str(ex))
def test_server_migration_show_by_uuid(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.59')
- self.server_migrations_mock.list.return_value = [self.server_migration]
+ self._set_mock_microversion('2.59')
+
+ self.sdk_client.server_migrations.return_value = iter(
+ [self.server_migration]
+ )
self.columns += ('UUID',)
self.data += (self.server_migration.uuid,)
@@ -733,14 +734,14 @@ class TestServerMigrationShow(TestServerMigration):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
- self.servers_mock.get.assert_called_with(self.server.id)
- self.server_migrations_mock.list.assert_called_with(self.server.id)
- self.server_migrations_mock.get.assert_not_called()
+ self.sdk_client.find_server.assert_called_with(
+ self.server.id, ignore_missing=False)
+ self.sdk_client.server_migrations.assert_called_with(self.server.id)
+ self.sdk_client.get_server_migration.assert_not_called()
def test_server_migration_show_by_uuid_no_matches(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.59')
- self.server_migrations_mock.list.return_value = []
+ self._set_mock_microversion('2.59')
+ self.sdk_client.server_migrations.return_value = iter([])
arglist = [
self.server.id,
@@ -758,8 +759,7 @@ class TestServerMigrationShow(TestServerMigration):
str(ex))
def test_server_migration_show_by_uuid_pre_v259(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.58')
+ self._set_mock_microversion('2.58')
arglist = [
self.server.id,
@@ -777,8 +777,7 @@ class TestServerMigrationShow(TestServerMigration):
str(ex))
def test_server_migration_show_invalid_id(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.24')
+ self._set_mock_microversion('2.24')
arglist = [
self.server.id,
@@ -801,17 +800,16 @@ class TestServerMigrationAbort(TestServerMigration):
def setUp(self):
super().setUp()
- self.server = compute_fakes.FakeServer.create_one_server()
+ self.server = compute_fakes.FakeServer.create_one_sdk_server()
# Return value for utils.find_resource for server.
- self.servers_mock.get.return_value = self.server
+ self.sdk_client.find_server.return_value = self.server
# Get the command object to test
self.cmd = server_migration.AbortMigration(self.app, None)
def test_migration_abort(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.24')
+ self._set_mock_microversion('2.24')
arglist = [
self.server.id,
@@ -822,14 +820,14 @@ class TestServerMigrationAbort(TestServerMigration):
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.sdk_client.find_server.assert_called_with(
+ self.server.id, ignore_missing=False)
+ self.sdk_client.abort_server_migration.assert_called_with(
+ '2', self.server.id, ignore_missing=False)
self.assertIsNone(result)
def test_migration_abort_pre_v224(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.23')
+ self._set_mock_microversion('2.23')
arglist = [
self.server.id,
@@ -847,12 +845,12 @@ class TestServerMigrationAbort(TestServerMigration):
str(ex))
def test_server_migration_abort_by_uuid(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.59')
+ self._set_mock_microversion('2.59')
- self.server_migration = compute_fakes.FakeServerMigration\
- .create_one_server_migration()
- self.server_migrations_mock.list.return_value = [self.server_migration]
+ self.server_migration = compute_fakes.create_one_server_migration()
+ self.sdk_client.server_migrations.return_value = iter(
+ [self.server_migration]
+ )
arglist = [
self.server.id,
@@ -863,17 +861,19 @@ class TestServerMigrationAbort(TestServerMigration):
result = self.cmd.take_action(parsed_args)
- self.servers_mock.get.assert_called_with(self.server.id)
- self.server_migrations_mock.list.assert_called_with(self.server.id)
- self.server_migrations_mock.live_migration_abort.assert_called_with(
- self.server.id, self.server_migration.id)
+ self.sdk_client.find_server.assert_called_with(
+ self.server.id, ignore_missing=False)
+ self.sdk_client.server_migrations.assert_called_with(self.server.id)
+ self.sdk_client.abort_server_migration.assert_called_with(
+ self.server_migration.id, self.server.id, ignore_missing=False)
self.assertIsNone(result)
def test_server_migration_abort_by_uuid_no_matches(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.59')
+ self._set_mock_microversion('2.59')
- self.server_migrations_mock.list.return_value = []
+ self.sdk_client.server_migrations.return_value = iter(
+ []
+ )
arglist = [
self.server.id,
@@ -891,8 +891,7 @@ class TestServerMigrationAbort(TestServerMigration):
str(ex))
def test_server_migration_abort_by_uuid_pre_v259(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.58')
+ self._set_mock_microversion('2.58')
arglist = [
self.server.id,
@@ -915,17 +914,16 @@ class TestServerMigrationForceComplete(TestServerMigration):
def setUp(self):
super().setUp()
- self.server = compute_fakes.FakeServer.create_one_server()
+ self.server = compute_fakes.FakeServer.create_one_sdk_server()
# Return value for utils.find_resource for server.
- self.servers_mock.get.return_value = self.server
+ self.sdk_client.find_server.return_value = self.server
# Get the command object to test
self.cmd = server_migration.ForceCompleteMigration(self.app, None)
def test_migration_force_complete(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.22')
+ self._set_mock_microversion('2.22')
arglist = [
self.server.id,
@@ -936,14 +934,14 @@ class TestServerMigrationForceComplete(TestServerMigration):
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.sdk_client.find_server.assert_called_with(
+ self.server.id, ignore_missing=False)
+ self.sdk_client.force_complete_server_migration\
+ .assert_called_with('2', self.server.id)
self.assertIsNone(result)
def test_migration_force_complete_pre_v222(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.21')
+ self._set_mock_microversion('2.21')
arglist = [
self.server.id,
@@ -961,12 +959,12 @@ class TestServerMigrationForceComplete(TestServerMigration):
str(ex))
def test_server_migration_force_complete_by_uuid(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.59')
+ self._set_mock_microversion('2.59')
- self.server_migration = compute_fakes.FakeServerMigration\
- .create_one_server_migration()
- self.server_migrations_mock.list.return_value = [self.server_migration]
+ self.server_migration = compute_fakes.create_one_server_migration()
+ self.sdk_client.server_migrations.return_value = iter(
+ [self.server_migration]
+ )
arglist = [
self.server.id,
@@ -977,17 +975,17 @@ class TestServerMigrationForceComplete(TestServerMigration):
result = self.cmd.take_action(parsed_args)
- self.servers_mock.get.assert_called_with(self.server.id)
- self.server_migrations_mock.list.assert_called_with(self.server.id)
- self.server_migrations_mock.live_migrate_force_complete\
- .assert_called_with(self.server.id, self.server_migration.id)
+ self.sdk_client.find_server.assert_called_with(
+ self.server.id, ignore_missing=False)
+ self.sdk_client.server_migrations.assert_called_with(self.server.id)
+ self.sdk_client.force_complete_server_migration.\
+ assert_called_with(self.server_migration.id, self.server.id)
self.assertIsNone(result)
def test_server_migration_force_complete_by_uuid_no_matches(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.59')
+ self._set_mock_microversion('2.59')
- self.server_migrations_mock.list.return_value = []
+ self.sdk_client.server_migrations.return_value = iter([])
arglist = [
self.server.id,
@@ -1005,8 +1003,7 @@ class TestServerMigrationForceComplete(TestServerMigration):
str(ex))
def test_server_migration_force_complete_by_uuid_pre_v259(self):
- self.app.client_manager.compute.api_version = api_versions.APIVersion(
- '2.58')
+ self._set_mock_microversion('2.58')
arglist = [
self.server.id,
diff --git a/openstackclient/tests/unit/compute/v2/test_server_volume.py b/openstackclient/tests/unit/compute/v2/test_server_volume.py
index 02d378f8..f86bc7dd 100644
--- a/openstackclient/tests/unit/compute/v2/test_server_volume.py
+++ b/openstackclient/tests/unit/compute/v2/test_server_volume.py
@@ -11,11 +11,15 @@
# under the License.
#
+from unittest import mock
+
from novaclient import api_versions
+from openstack import utils as sdk_utils
from osc_lib import exceptions
from openstackclient.compute.v2 import server_volume
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
+from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
class TestServerVolume(compute_fakes.TestComputev2):
@@ -23,13 +27,11 @@ class TestServerVolume(compute_fakes.TestComputev2):
def setUp(self):
super().setUp()
- # Get a shortcut to the compute client ServerManager Mock
- self.servers_mock = self.app.client_manager.compute.servers
- self.servers_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()
+ self.app.client_manager.sdk_connection = mock.Mock()
+ self.app.client_manager.sdk_connection.compute = mock.Mock()
+ self.app.client_manager.sdk_connection.volume = mock.Mock()
+ self.compute_client = self.app.client_manager.sdk_connection.compute
+ self.volume_client = self.app.client_manager.sdk_connection.volume
class TestServerVolumeList(TestServerVolume):
@@ -37,20 +39,21 @@ class TestServerVolumeList(TestServerVolume):
def setUp(self):
super().setUp()
- self.server = compute_fakes.FakeServer.create_one_server()
- self.volume_attachments = (
- compute_fakes.FakeVolumeAttachment.create_volume_attachments())
+ self.server = compute_fakes.FakeServer.create_one_sdk_server()
+ self.volume_attachments = compute_fakes.create_volume_attachments()
- self.servers_mock.get.return_value = self.server
- self.servers_volumes_mock.get_server_volumes.return_value = (
+ self.compute_client.find_server.return_value = self.server
+ self.compute_client.volume_attachments.return_value = (
self.volume_attachments)
# Get the command object to test
self.cmd = server_volume.ListServerVolume(self.app, None)
- def test_server_volume_list(self):
+ @mock.patch.object(sdk_utils, 'supports_microversion')
+ def test_server_volume_list(self, sm_mock):
self.app.client_manager.compute.api_version = \
api_versions.APIVersion('2.1')
+ sm_mock.side_effect = [False, False, False, False]
arglist = [
self.server.id,
@@ -68,24 +71,25 @@ class TestServerVolumeList(TestServerVolume):
(
self.volume_attachments[0].id,
self.volume_attachments[0].device,
- self.volume_attachments[0].serverId,
- self.volume_attachments[0].volumeId,
+ self.volume_attachments[0].server_id,
+ self.volume_attachments[0].volume_id,
),
(
self.volume_attachments[1].id,
self.volume_attachments[1].device,
- self.volume_attachments[1].serverId,
- self.volume_attachments[1].volumeId,
+ self.volume_attachments[1].server_id,
+ self.volume_attachments[1].volume_id,
),
),
tuple(data),
)
- self.servers_volumes_mock.get_server_volumes.assert_called_once_with(
- self.server.id)
+ self.compute_client.volume_attachments.assert_called_once_with(
+ self.server,
+ )
- def test_server_volume_list_with_tags(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.70')
+ @mock.patch.object(sdk_utils, 'supports_microversion')
+ def test_server_volume_list_with_tags(self, sm_mock):
+ sm_mock.side_effect = [False, True, False, False]
arglist = [
self.server.id,
@@ -105,27 +109,27 @@ class TestServerVolumeList(TestServerVolume):
(
self.volume_attachments[0].id,
self.volume_attachments[0].device,
- self.volume_attachments[0].serverId,
- self.volume_attachments[0].volumeId,
+ self.volume_attachments[0].server_id,
+ self.volume_attachments[0].volume_id,
self.volume_attachments[0].tag,
),
(
self.volume_attachments[1].id,
self.volume_attachments[1].device,
- self.volume_attachments[1].serverId,
- self.volume_attachments[1].volumeId,
+ self.volume_attachments[1].server_id,
+ self.volume_attachments[1].volume_id,
self.volume_attachments[1].tag,
),
),
tuple(data),
)
- self.servers_volumes_mock.get_server_volumes.assert_called_once_with(
- self.server.id)
-
- def test_server_volume_list_with_delete_on_attachment(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.79')
+ self.compute_client.volume_attachments.assert_called_once_with(
+ self.server,
+ )
+ @mock.patch.object(sdk_utils, 'supports_microversion')
+ def test_server_volume_list_with_delete_on_attachment(self, sm_mock):
+ sm_mock.side_effect = [False, True, True, False]
arglist = [
self.server.id,
]
@@ -148,29 +152,30 @@ class TestServerVolumeList(TestServerVolume):
(
self.volume_attachments[0].id,
self.volume_attachments[0].device,
- self.volume_attachments[0].serverId,
- self.volume_attachments[0].volumeId,
+ self.volume_attachments[0].server_id,
+ self.volume_attachments[0].volume_id,
self.volume_attachments[0].tag,
self.volume_attachments[0].delete_on_termination,
),
(
self.volume_attachments[1].id,
self.volume_attachments[1].device,
- self.volume_attachments[1].serverId,
- self.volume_attachments[1].volumeId,
+ self.volume_attachments[1].server_id,
+ self.volume_attachments[1].volume_id,
self.volume_attachments[1].tag,
self.volume_attachments[1].delete_on_termination,
),
),
tuple(data),
)
- self.servers_volumes_mock.get_server_volumes.assert_called_once_with(
- self.server.id)
+ self.compute_client.volume_attachments.assert_called_once_with(
+ self.server,
+ )
- def test_server_volume_list_with_attachment_ids(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.89')
+ @mock.patch.object(sdk_utils, 'supports_microversion')
+ def test_server_volume_list_with_attachment_ids(self, sm_mock):
+ sm_mock.side_effect = [True, True, True, True]
arglist = [
self.server.id,
]
@@ -193,28 +198,29 @@ class TestServerVolumeList(TestServerVolume):
(
(
self.volume_attachments[0].device,
- self.volume_attachments[0].serverId,
- self.volume_attachments[0].volumeId,
+ self.volume_attachments[0].server_id,
+ self.volume_attachments[0].volume_id,
self.volume_attachments[0].tag,
self.volume_attachments[0].delete_on_termination,
self.volume_attachments[0].attachment_id,
- self.volume_attachments[0].bdm_uuid
+ self.volume_attachments[0].bdm_id
),
(
self.volume_attachments[1].device,
- self.volume_attachments[1].serverId,
- self.volume_attachments[1].volumeId,
+ self.volume_attachments[1].server_id,
+ self.volume_attachments[1].volume_id,
self.volume_attachments[1].tag,
self.volume_attachments[1].delete_on_termination,
self.volume_attachments[1].attachment_id,
- self.volume_attachments[1].bdm_uuid
+ self.volume_attachments[1].bdm_id
),
),
tuple(data),
)
- self.servers_volumes_mock.get_server_volumes.assert_called_once_with(
- self.server.id)
+ self.compute_client.volume_attachments.assert_called_once_with(
+ self.server,
+ )
class TestServerVolumeUpdate(TestServerVolume):
@@ -222,21 +228,23 @@ class TestServerVolumeUpdate(TestServerVolume):
def setUp(self):
super().setUp()
- self.server = compute_fakes.FakeServer.create_one_server()
- self.servers_mock.get.return_value = self.server
+ self.server = compute_fakes.FakeServer.create_one_sdk_server()
+ self.compute_client.find_server.return_value = self.server
+
+ self.volume = volume_fakes.create_one_sdk_volume()
+ self.volume_client.find_volume.return_value = self.volume
# Get the command object to test
self.cmd = server_volume.UpdateServerVolume(self.app, None)
def test_server_volume_update(self):
-
arglist = [
self.server.id,
- 'foo',
+ self.volume.id,
]
verifylist = [
('server', self.server.id),
- ('volume', 'foo'),
+ ('volume', self.volume.id),
('delete_on_termination', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -244,67 +252,73 @@ class TestServerVolumeUpdate(TestServerVolume):
result = self.cmd.take_action(parsed_args)
# This is a no-op
- self.servers_volumes_mock.update_server_volume.assert_not_called()
+ self.compute_client.update_volume_attachment.assert_not_called()
self.assertIsNone(result)
- def test_server_volume_update_with_delete_on_termination(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.85')
+ @mock.patch.object(sdk_utils, 'supports_microversion')
+ def test_server_volume_update_with_delete_on_termination(self, sm_mock):
+ sm_mock.return_value = True
arglist = [
self.server.id,
- 'foo',
+ self.volume.id,
'--delete-on-termination',
]
verifylist = [
('server', self.server.id),
- ('volume', 'foo'),
+ ('volume', self.volume.id),
('delete_on_termination', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.servers_volumes_mock.update_server_volume.assert_called_once_with(
- self.server.id, 'foo', 'foo',
- delete_on_termination=True)
+ self.compute_client.update_volume_attachment.assert_called_once_with(
+ self.server,
+ self.volume,
+ delete_on_termination=True,
+ )
self.assertIsNone(result)
- def test_server_volume_update_with_preserve_on_termination(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.85')
+ @mock.patch.object(sdk_utils, 'supports_microversion')
+ def test_server_volume_update_with_preserve_on_termination(self, sm_mock):
+ sm_mock.return_value = True
arglist = [
self.server.id,
- 'foo',
+ self.volume.id,
'--preserve-on-termination',
]
verifylist = [
('server', self.server.id),
- ('volume', 'foo'),
+ ('volume', self.volume.id),
('delete_on_termination', False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
- self.servers_volumes_mock.update_server_volume.assert_called_once_with(
- self.server.id, 'foo', 'foo',
- delete_on_termination=False)
+ self.compute_client.update_volume_attachment.assert_called_once_with(
+ self.server,
+ self.volume,
+ delete_on_termination=False
+ )
self.assertIsNone(result)
- def test_server_volume_update_with_delete_on_termination_pre_v285(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.84')
+ @mock.patch.object(sdk_utils, 'supports_microversion')
+ def test_server_volume_update_with_delete_on_termination_pre_v285(
+ self, sm_mock,
+ ):
+ sm_mock.return_value = False
arglist = [
self.server.id,
- 'foo',
+ self.volume.id,
'--delete-on-termination',
]
verifylist = [
('server', self.server.id),
- ('volume', 'foo'),
+ ('volume', self.volume.id),
('delete_on_termination', True),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -312,20 +326,24 @@ class TestServerVolumeUpdate(TestServerVolume):
self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
- parsed_args)
+ parsed_args,
+ )
+ self.compute_client.update_volume_attachment.assert_not_called()
- def test_server_volume_update_with_preserve_on_termination_pre_v285(self):
- self.app.client_manager.compute.api_version = \
- api_versions.APIVersion('2.84')
+ @mock.patch.object(sdk_utils, 'supports_microversion')
+ def test_server_volume_update_with_preserve_on_termination_pre_v285(
+ self, sm_mock,
+ ):
+ sm_mock.return_value = False
arglist = [
self.server.id,
- 'foo',
+ self.volume.id,
'--preserve-on-termination',
]
verifylist = [
('server', self.server.id),
- ('volume', 'foo'),
+ ('volume', self.volume.id),
('delete_on_termination', False),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -333,4 +351,6 @@ class TestServerVolumeUpdate(TestServerVolume):
self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
- parsed_args)
+ parsed_args,
+ )
+ self.compute_client.update_volume_attachment.assert_not_called()
diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py
index 5d68d95d..6d922008 100644
--- a/openstackclient/tests/unit/network/v2/fakes.py
+++ b/openstackclient/tests/unit/network/v2/fakes.py
@@ -1066,11 +1066,13 @@ class FakeFloatingIPPortForwarding(object):
""""Fake one or more Port forwarding"""
@staticmethod
- def create_one_port_forwarding(attrs=None):
+ def create_one_port_forwarding(attrs=None, use_range=False):
"""Create a fake Port Forwarding.
:param Dictionary attrs:
A dictionary with all attributes
+ :param Boolean use_range:
+ A boolean which defines if we will use ranges or not
:return:
A FakeResource object with name, id, etc.
"""
@@ -1084,13 +1086,29 @@ class FakeFloatingIPPortForwarding(object):
'floatingip_id': floatingip_id,
'internal_port_id': 'internal-port-id-' + uuid.uuid4().hex,
'internal_ip_address': '192.168.1.2',
- 'internal_port': randint(1, 65535),
- 'external_port': randint(1, 65535),
'protocol': 'tcp',
'description': 'some description',
'location': 'MUNCHMUNCHMUNCH',
}
+ if use_range:
+ port_range = randint(0, 100)
+ internal_start = randint(1, 65535 - port_range)
+ internal_end = internal_start + port_range
+ internal_range = ':'.join(map(str, [internal_start, internal_end]))
+ external_start = randint(1, 65535 - port_range)
+ external_end = external_start + port_range
+ external_range = ':'.join(map(str, [external_start, external_end]))
+ port_forwarding_attrs['internal_port_range'] = internal_range
+ port_forwarding_attrs['external_port_range'] = external_range
+ port_forwarding_attrs['internal_port'] = None
+ port_forwarding_attrs['external_port'] = None
+ else:
+ port_forwarding_attrs['internal_port'] = randint(1, 65535)
+ port_forwarding_attrs['external_port'] = randint(1, 65535)
+ port_forwarding_attrs['internal_port_range'] = ''
+ port_forwarding_attrs['external_port_range'] = ''
+
# Overwrite default attributes.
port_forwarding_attrs.update(attrs)
@@ -1101,25 +1119,28 @@ class FakeFloatingIPPortForwarding(object):
return port_forwarding
@staticmethod
- def create_port_forwardings(attrs=None, count=2):
+ def create_port_forwardings(attrs=None, count=2, use_range=False):
"""Create multiple fake Port Forwarding.
:param Dictionary attrs:
A dictionary with all attributes
:param int count:
The number of Port Forwarding rule to fake
+ :param Boolean use_range:
+ A boolean which defines if we will use ranges or not
:return:
A list of FakeResource objects faking the Port Forwardings
"""
port_forwardings = []
for i in range(0, count):
port_forwardings.append(
- FakeFloatingIPPortForwarding.create_one_port_forwarding(attrs)
+ FakeFloatingIPPortForwarding.create_one_port_forwarding(
+ attrs, use_range=use_range)
)
return port_forwardings
@staticmethod
- def get_port_forwardings(port_forwardings=None, count=2):
+ def get_port_forwardings(port_forwardings=None, count=2, use_range=False):
"""Get a list of faked Port Forwardings.
If port forwardings list is provided, then initialize the Mock object
@@ -1129,13 +1150,16 @@ class FakeFloatingIPPortForwarding(object):
A list of FakeResource objects faking port forwardings
:param int count:
The number of Port Forwardings to fake
+ :param Boolean use_range:
+ A boolean which defines if we will use ranges or not
:return:
An iterable Mock object with side_effect set to a list of faked
Port Forwardings
"""
if port_forwardings is None:
port_forwardings = (
- FakeFloatingIPPortForwarding.create_port_forwardings(count)
+ FakeFloatingIPPortForwarding.create_port_forwardings(
+ count, use_range=use_range)
)
return mock.Mock(side_effect=port_forwardings)
diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py
index 97399f43..d0f5af8c 100644
--- a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py
+++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py
@@ -49,6 +49,18 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding):
}
)
)
+
+ self.new_port_forwarding_with_ranges = (
+ network_fakes.FakeFloatingIPPortForwarding.
+ create_one_port_forwarding(
+ use_range=True,
+ attrs={
+ 'internal_port_id': self.port.id,
+ 'floatingip_id': self.floating_ip.id,
+ }
+ )
+ )
+
self.network.create_floating_ip_port_forwarding = mock.Mock(
return_value=self.new_port_forwarding)
@@ -63,22 +75,26 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding):
self.columns = (
'description',
'external_port',
+ 'external_port_range',
'floatingip_id',
'id',
'internal_ip_address',
'internal_port',
'internal_port_id',
+ 'internal_port_range',
'protocol'
)
self.data = (
self.new_port_forwarding.description,
self.new_port_forwarding.external_port,
+ self.new_port_forwarding.external_port_range,
self.new_port_forwarding.floatingip_id,
self.new_port_forwarding.id,
self.new_port_forwarding.internal_ip_address,
self.new_port_forwarding.internal_port,
self.new_port_forwarding.internal_port_id,
+ self.new_port_forwarding.internal_port_range,
self.new_port_forwarding.protocol,
)
@@ -90,6 +106,160 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding):
self.assertRaises(tests_utils.ParserException, self.check_parser,
self.cmd, arglist, verifylist)
+ def test_create_all_options_with_range(self):
+ arglist = [
+ '--port', self.new_port_forwarding_with_ranges.internal_port_id,
+ '--internal-protocol-port',
+ self.new_port_forwarding_with_ranges.internal_port_range,
+ '--external-protocol-port',
+ self.new_port_forwarding_with_ranges.external_port_range,
+ '--protocol', self.new_port_forwarding_with_ranges.protocol,
+ self.new_port_forwarding_with_ranges.floatingip_id,
+ '--internal-ip-address',
+ self.new_port_forwarding_with_ranges.internal_ip_address,
+ '--description',
+ self.new_port_forwarding_with_ranges.description,
+ ]
+ verifylist = [
+ ('port', self.new_port_forwarding_with_ranges.internal_port_id),
+ ('internal_protocol_port',
+ self.new_port_forwarding_with_ranges.internal_port_range),
+ ('external_protocol_port',
+ self.new_port_forwarding_with_ranges.external_port_range),
+ ('protocol', self.new_port_forwarding_with_ranges.protocol),
+ ('floating_ip',
+ self.new_port_forwarding_with_ranges.floatingip_id),
+ ('internal_ip_address', self.new_port_forwarding_with_ranges.
+ internal_ip_address),
+ ('description', self.new_port_forwarding_with_ranges.description),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.create_floating_ip_port_forwarding.\
+ assert_called_once_with(
+ self.new_port_forwarding.floatingip_id,
+ **{
+ 'external_port_range':
+ self.new_port_forwarding_with_ranges.
+ external_port_range,
+ 'internal_ip_address':
+ self.new_port_forwarding_with_ranges.
+ internal_ip_address,
+ 'internal_port_range':
+ self.new_port_forwarding_with_ranges.
+ internal_port_range,
+ 'internal_port_id':
+ self.new_port_forwarding_with_ranges.internal_port_id,
+ 'protocol': self.new_port_forwarding_with_ranges.protocol,
+ 'description':
+ self.new_port_forwarding_with_ranges.description,
+ })
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_create_all_options_with_range_invalid_port_exception(self):
+ invalid_port_range = '999999:999999'
+ arglist = [
+ '--port', self.new_port_forwarding_with_ranges.internal_port_id,
+ '--internal-protocol-port', invalid_port_range,
+ '--external-protocol-port', invalid_port_range,
+ '--protocol', self.new_port_forwarding_with_ranges.protocol,
+ self.new_port_forwarding_with_ranges.floatingip_id,
+ '--internal-ip-address',
+ self.new_port_forwarding_with_ranges.internal_ip_address,
+ '--description',
+ self.new_port_forwarding_with_ranges.description,
+ ]
+ verifylist = [
+ ('port', self.new_port_forwarding_with_ranges.internal_port_id),
+ ('internal_protocol_port', invalid_port_range),
+ ('external_protocol_port', invalid_port_range),
+ ('protocol', self.new_port_forwarding_with_ranges.protocol),
+ ('floating_ip',
+ self.new_port_forwarding_with_ranges.floatingip_id),
+ ('internal_ip_address', self.new_port_forwarding_with_ranges.
+ internal_ip_address),
+ ('description', self.new_port_forwarding_with_ranges.description),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ msg = 'The port number range is <1-65535>'
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(msg, str(e))
+ self.network.create_floating_ip_port_forwarding.assert_not_called()
+
+ def test_create_all_options_with_invalid_range_exception(self):
+ invalid_port_range = '80:70'
+ arglist = [
+ '--port', self.new_port_forwarding_with_ranges.internal_port_id,
+ '--internal-protocol-port', invalid_port_range,
+ '--external-protocol-port', invalid_port_range,
+ '--protocol', self.new_port_forwarding_with_ranges.protocol,
+ self.new_port_forwarding_with_ranges.floatingip_id,
+ '--internal-ip-address',
+ self.new_port_forwarding_with_ranges.internal_ip_address,
+ '--description',
+ self.new_port_forwarding_with_ranges.description,
+ ]
+ verifylist = [
+ ('port', self.new_port_forwarding_with_ranges.internal_port_id),
+ ('internal_protocol_port', invalid_port_range),
+ ('external_protocol_port', invalid_port_range),
+ ('protocol', self.new_port_forwarding_with_ranges.protocol),
+ ('floating_ip',
+ self.new_port_forwarding_with_ranges.floatingip_id),
+ ('internal_ip_address', self.new_port_forwarding_with_ranges.
+ internal_ip_address),
+ ('description', self.new_port_forwarding_with_ranges.description),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ msg = 'The last number in port range must be greater or equal to ' \
+ 'the first'
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(msg, str(e))
+ self.network.create_floating_ip_port_forwarding.assert_not_called()
+
+ def test_create_all_options_with_unmatch_ranges_exception(self):
+ internal_range = '80:90'
+ external_range = '8080:8100'
+ arglist = [
+ '--port', self.new_port_forwarding_with_ranges.internal_port_id,
+ '--internal-protocol-port', internal_range,
+ '--external-protocol-port', external_range,
+ '--protocol', self.new_port_forwarding_with_ranges.protocol,
+ self.new_port_forwarding_with_ranges.floatingip_id,
+ '--internal-ip-address',
+ self.new_port_forwarding_with_ranges.internal_ip_address,
+ '--description',
+ self.new_port_forwarding_with_ranges.description,
+ ]
+ verifylist = [
+ ('port', self.new_port_forwarding_with_ranges.internal_port_id),
+ ('internal_protocol_port', internal_range),
+ ('external_protocol_port', external_range),
+ ('protocol', self.new_port_forwarding_with_ranges.protocol),
+ ('floating_ip',
+ self.new_port_forwarding_with_ranges.floatingip_id),
+ ('internal_ip_address', self.new_port_forwarding_with_ranges.
+ internal_ip_address),
+ ('description', self.new_port_forwarding_with_ranges.description),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ msg = "The relation between internal and external ports does not " \
+ "match the pattern 1:N and N:N"
+ try:
+ self.cmd.take_action(parsed_args)
+ self.fail('CommandError should be raised.')
+ except exceptions.CommandError as e:
+ self.assertEqual(msg, str(e))
+ self.network.create_floating_ip_port_forwarding.assert_not_called()
+
def test_create_all_options(self):
arglist = [
'--port', self.new_port_forwarding.internal_port_id,
@@ -106,8 +276,10 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding):
]
verifylist = [
('port', self.new_port_forwarding.internal_port_id),
- ('internal_protocol_port', self.new_port_forwarding.internal_port),
- ('external_protocol_port', self.new_port_forwarding.external_port),
+ ('internal_protocol_port',
+ str(self.new_port_forwarding.internal_port)),
+ ('external_protocol_port',
+ str(self.new_port_forwarding.external_port)),
('protocol', self.new_port_forwarding.protocol),
('floating_ip', self.new_port_forwarding.floatingip_id),
('internal_ip_address', self.new_port_forwarding.
@@ -253,7 +425,9 @@ class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding):
'Internal Port ID',
'Internal IP Address',
'Internal Port',
+ 'Internal Port Range',
'External Port',
+ 'External Port Range',
'Protocol',
'Description',
)
@@ -275,7 +449,9 @@ class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding):
port_forwarding.internal_port_id,
port_forwarding.internal_ip_address,
port_forwarding.internal_port,
+ port_forwarding.internal_port_range,
port_forwarding.external_port,
+ port_forwarding.external_port_range,
port_forwarding.protocol,
port_forwarding.description,
))
@@ -330,7 +506,7 @@ class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding):
query = {
'internal_port_id': self.port_forwardings[0].internal_port_id,
- 'external_port': str(self.port_forwardings[0].external_port),
+ 'external_port': self.port_forwardings[0].external_port,
'protocol': self.port_forwardings[0].protocol,
}
@@ -392,7 +568,7 @@ class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding):
self.assertIsNone(result)
def test_set_all_thing(self):
- arglist = [
+ arglist_single = [
'--port', self.port.id,
'--internal-ip-address', 'new_internal_ip_address',
'--internal-protocol-port', '100',
@@ -402,21 +578,23 @@ class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding):
self._port_forwarding.floatingip_id,
self._port_forwarding.id,
]
- verifylist = [
+ arglist_range = list(arglist_single)
+ arglist_range[5] = '100:110'
+ arglist_range[7] = '200:210'
+ verifylist_single = [
('port', self.port.id),
('internal_ip_address', 'new_internal_ip_address'),
- ('internal_protocol_port', 100),
- ('external_protocol_port', 200),
+ ('internal_protocol_port', '100'),
+ ('external_protocol_port', '200'),
('protocol', 'tcp'),
('description', 'some description'),
('floating_ip', self._port_forwarding.floatingip_id),
('port_forwarding_id', self._port_forwarding.id),
]
-
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- result = self.cmd.take_action(parsed_args)
- attrs = {
+ verifylist_range = list(verifylist_single)
+ verifylist_range[2] = ('internal_protocol_port', '100:110')
+ verifylist_range[3] = ('external_protocol_port', '200:210')
+ attrs_single = {
'internal_port_id': self.port.id,
'internal_ip_address': 'new_internal_ip_address',
'internal_port': 100,
@@ -424,12 +602,25 @@ class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding):
'protocol': 'tcp',
'description': 'some description',
}
- self.network.update_floating_ip_port_forwarding.assert_called_with(
- self._port_forwarding.floatingip_id,
- self._port_forwarding.id,
- **attrs
- )
- self.assertIsNone(result)
+ attrs_range = dict(attrs_single, internal_port_range='100:110',
+ external_port_range='200:210')
+ attrs_range.pop('internal_port')
+ attrs_range.pop('external_port')
+
+ def run_and_validate(arglist, verifylist, attrs):
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.network.update_floating_ip_port_forwarding.assert_called_with(
+ self._port_forwarding.floatingip_id,
+ self._port_forwarding.id,
+ **attrs
+ )
+ self.assertIsNone(result)
+
+ run_and_validate(arglist_single, verifylist_single, attrs_single)
+ run_and_validate(arglist_range, verifylist_range, attrs_range)
class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding):
@@ -438,11 +629,13 @@ class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding):
columns = (
'description',
'external_port',
+ 'external_port_range',
'floatingip_id',
'id',
'internal_ip_address',
'internal_port',
'internal_port_id',
+ 'internal_port_range',
'protocol',
)
@@ -459,11 +652,13 @@ class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding):
self.data = (
self._port_forwarding.description,
self._port_forwarding.external_port,
+ self._port_forwarding.external_port_range,
self._port_forwarding.floatingip_id,
self._port_forwarding.id,
self._port_forwarding.internal_ip_address,
self._port_forwarding.internal_port,
self._port_forwarding.internal_port_id,
+ self._port_forwarding.internal_port_range,
self._port_forwarding.protocol,
)
self.network.find_floating_ip_port_forwarding = mock.Mock(
diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py
index 9f16b398..b46a608d 100644
--- a/openstackclient/tests/unit/volume/v1/test_volume.py
+++ b/openstackclient/tests/unit/volume/v1/test_volume.py
@@ -430,7 +430,8 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.datalist, data)
- def test_volume_create_with_bootable_and_readonly(self):
+ @mock.patch.object(utils, 'wait_for_status', return_value=True)
+ def test_volume_create_with_bootable_and_readonly(self, mock_wait):
arglist = [
'--bootable',
'--read-only',
@@ -472,7 +473,8 @@ class TestVolumeCreate(TestVolume):
self.volumes_mock.update_readonly_flag.assert_called_with(
self.new_volume.id, True)
- def test_volume_create_with_nonbootable_and_readwrite(self):
+ @mock.patch.object(utils, 'wait_for_status', return_value=True)
+ def test_volume_create_with_nonbootable_and_readwrite(self, mock_wait):
arglist = [
'--non-bootable',
'--read-write',
@@ -515,8 +517,9 @@ class TestVolumeCreate(TestVolume):
self.new_volume.id, False)
@mock.patch.object(volume.LOG, 'error')
+ @mock.patch.object(utils, 'wait_for_status', return_value=True)
def test_volume_create_with_bootable_and_readonly_fail(
- self, mock_error):
+ self, mock_wait, mock_error):
self.volumes_mock.set_bootable.side_effect = (
exceptions.CommandError())
@@ -566,6 +569,48 @@ class TestVolumeCreate(TestVolume):
self.volumes_mock.update_readonly_flag.assert_called_with(
self.new_volume.id, True)
+ @mock.patch.object(volume.LOG, 'error')
+ @mock.patch.object(utils, 'wait_for_status', return_value=False)
+ def test_volume_create_non_available_with_readonly(
+ self, mock_wait, mock_error):
+ arglist = [
+ '--non-bootable',
+ '--read-only',
+ '--size', str(self.new_volume.size),
+ self.new_volume.display_name,
+ ]
+ verifylist = [
+ ('bootable', False),
+ ('non_bootable', True),
+ ('read_only', True),
+ ('read_write', False),
+ ('size', self.new_volume.size),
+ ('name', self.new_volume.display_name),
+ ]
+
+ parsed_args = self.check_parser(
+ self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.create.assert_called_with(
+ self.new_volume.size,
+ None,
+ None,
+ self.new_volume.display_name,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ )
+
+ self.assertEqual(2, mock_error.call_count)
+ self.assertEqual(self.columns, columns)
+ self.assertCountEqual(self.datalist, data)
+
def test_volume_create_without_size(self):
arglist = [
self.new_volume.display_name,
diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py
index 7ef4a08e..c5537ed8 100644
--- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py
+++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py
@@ -257,7 +257,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup):
self.new_consistency_group.name,
]
verifylist = [
- ('consistency_group_source', self.new_consistency_group.id),
+ ('source', self.new_consistency_group.id),
('description', self.new_consistency_group.description),
('name', self.new_consistency_group.name),
]
@@ -285,7 +285,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup):
self.new_consistency_group.name,
]
verifylist = [
- ('consistency_group_snapshot', self.consistency_group_snapshot.id),
+ ('snapshot', self.consistency_group_snapshot.id),
('description', self.new_consistency_group.description),
('name', self.new_consistency_group.name),
]
diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py
index c930002f..0419acef 100644
--- a/openstackclient/tests/unit/volume/v2/test_volume.py
+++ b/openstackclient/tests/unit/volume/v2/test_volume.py
@@ -435,7 +435,8 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.datalist, data)
- def test_volume_create_with_bootable_and_readonly(self):
+ @mock.patch.object(utils, 'wait_for_status', return_value=True)
+ def test_volume_create_with_bootable_and_readonly(self, mock_wait):
arglist = [
'--bootable',
'--read-only',
@@ -478,7 +479,8 @@ class TestVolumeCreate(TestVolume):
self.volumes_mock.update_readonly_flag.assert_called_with(
self.new_volume.id, True)
- def test_volume_create_with_nonbootable_and_readwrite(self):
+ @mock.patch.object(utils, 'wait_for_status', return_value=True)
+ def test_volume_create_with_nonbootable_and_readwrite(self, mock_wait):
arglist = [
'--non-bootable',
'--read-write',
@@ -522,8 +524,9 @@ class TestVolumeCreate(TestVolume):
self.new_volume.id, False)
@mock.patch.object(volume.LOG, 'error')
+ @mock.patch.object(utils, 'wait_for_status', return_value=True)
def test_volume_create_with_bootable_and_readonly_fail(
- self, mock_error):
+ self, mock_wait, mock_error):
self.volumes_mock.set_bootable.side_effect = (
exceptions.CommandError())
@@ -574,6 +577,50 @@ class TestVolumeCreate(TestVolume):
self.volumes_mock.update_readonly_flag.assert_called_with(
self.new_volume.id, True)
+ @mock.patch.object(volume.LOG, 'error')
+ @mock.patch.object(utils, 'wait_for_status', return_value=False)
+ def test_volume_create_non_available_with_readonly(
+ self, mock_wait, mock_error,
+ ):
+ arglist = [
+ '--non-bootable',
+ '--read-only',
+ '--size', str(self.new_volume.size),
+ self.new_volume.name,
+ ]
+ verifylist = [
+ ('bootable', False),
+ ('non_bootable', True),
+ ('read_only', True),
+ ('read_write', False),
+ ('size', self.new_volume.size),
+ ('name', self.new_volume.name),
+ ]
+
+ parsed_args = self.check_parser(
+ self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volumes_mock.create.assert_called_with(
+ size=self.new_volume.size,
+ snapshot_id=None,
+ name=self.new_volume.name,
+ description=None,
+ volume_type=None,
+ availability_zone=None,
+ metadata=None,
+ imageRef=None,
+ source_volid=None,
+ consistencygroup_id=None,
+ scheduler_hints=None,
+ backup_id=None,
+ )
+
+ self.assertEqual(2, mock_error.call_count)
+ self.assertEqual(self.columns, columns)
+ self.assertCountEqual(self.datalist, data)
+
def test_volume_create_without_size(self):
arglist = [
self.new_volume.name,
diff --git a/openstackclient/tests/unit/volume/v3/test_volume_group.py b/openstackclient/tests/unit/volume/v3/test_volume_group.py
index a8338a80..78717de8 100644
--- a/openstackclient/tests/unit/volume/v3/test_volume_group.py
+++ b/openstackclient/tests/unit/volume/v3/test_volume_group.py
@@ -100,8 +100,8 @@ class TestVolumeGroupCreate(TestVolumeGroup):
api_versions.APIVersion('3.13')
arglist = [
- self.fake_volume_group_type.id,
- self.fake_volume_type.id,
+ '--volume-group-type', self.fake_volume_group_type.id,
+ '--volume-type', self.fake_volume_type.id,
]
verifylist = [
('volume_group_type', self.fake_volume_group_type.id),
@@ -128,12 +128,51 @@ class TestVolumeGroupCreate(TestVolumeGroup):
self.assertEqual(self.columns, columns)
self.assertCountEqual(self.data, data)
+ def test_volume_group_create__legacy(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.13')
+
+ arglist = [
+ self.fake_volume_group_type.id,
+ self.fake_volume_type.id,
+ ]
+ verifylist = [
+ ('volume_group_type_legacy', self.fake_volume_group_type.id),
+ ('volume_types_legacy', [self.fake_volume_type.id]),
+ ('name', None),
+ ('description', None),
+ ('availability_zone', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.volume_group_types_mock.get.assert_called_once_with(
+ self.fake_volume_group_type.id)
+ self.volume_types_mock.get.assert_called_once_with(
+ self.fake_volume_type.id)
+ self.volume_groups_mock.create.assert_called_once_with(
+ self.fake_volume_group_type.id,
+ self.fake_volume_type.id,
+ None,
+ None,
+ availability_zone=None,
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertCountEqual(self.data, data)
+ mock_warning.assert_called_once()
+ self.assertIn(
+ 'Passing volume group type and volume types as positional ',
+ str(mock_warning.call_args[0][0]),
+ )
+
def test_volume_group_create_no_volume_type(self):
self.app.client_manager.volume.api_version = \
api_versions.APIVersion('3.13')
arglist = [
- self.fake_volume_group_type.id
+ '--volume-group-type', self.fake_volume_group_type.id,
]
verifylist = [
('volume_group_type', self.fake_volume_group_type.id),
@@ -148,7 +187,7 @@ class TestVolumeGroupCreate(TestVolumeGroup):
self.cmd.take_action,
parsed_args)
self.assertIn(
- '<volume_types> is a required argument',
+ '--volume-types is a required argument when creating ',
str(exc))
def test_volume_group_create_with_options(self):
@@ -156,8 +195,8 @@ class TestVolumeGroupCreate(TestVolumeGroup):
api_versions.APIVersion('3.13')
arglist = [
- self.fake_volume_group_type.id,
- self.fake_volume_type.id,
+ '--volume-group-type', self.fake_volume_group_type.id,
+ '--volume-type', self.fake_volume_type.id,
'--name', 'foo',
'--description', 'hello, world',
'--availability-zone', 'bar',
@@ -192,8 +231,8 @@ class TestVolumeGroupCreate(TestVolumeGroup):
api_versions.APIVersion('3.12')
arglist = [
- self.fake_volume_group_type.id,
- self.fake_volume_type.id,
+ '--volume-group-type', self.fake_volume_group_type.id,
+ '--volume-type', self.fake_volume_type.id,
]
verifylist = [
('volume_group_type', self.fake_volume_group_type.id),
diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py
index dfbb0c54..198b890f 100644
--- a/openstackclient/volume/v1/volume.py
+++ b/openstackclient/volume/v1/volume.py
@@ -224,15 +224,44 @@ class CreateVolume(command.ShowOne):
if parsed_args.bootable or parsed_args.non_bootable:
try:
- volume_client.volumes.set_bootable(
- volume.id, parsed_args.bootable)
+ if utils.wait_for_status(
+ volume_client.volumes.get,
+ volume.id,
+ success_status=['available'],
+ error_status=['error'],
+ sleep_time=1
+ ):
+ volume_client.volumes.set_bootable(
+ volume.id,
+ parsed_args.bootable
+ )
+ else:
+ msg = _(
+ "Volume status is not available for setting boot "
+ "state"
+ )
+ raise exceptions.CommandError(msg)
except Exception as e:
LOG.error(_("Failed to set volume bootable property: %s"), e)
if parsed_args.read_only or parsed_args.read_write:
try:
- volume_client.volumes.update_readonly_flag(
+ if utils.wait_for_status(
+ volume_client.volumes.get,
volume.id,
- parsed_args.read_only)
+ success_status=['available'],
+ error_status=['error'],
+ sleep_time=1
+ ):
+ volume_client.volumes.update_readonly_flag(
+ volume.id,
+ parsed_args.read_only
+ )
+ else:
+ msg = _(
+ "Volume status is not available for setting it"
+ "read only."
+ )
+ raise exceptions.CommandError(msg)
except Exception as e:
LOG.error(_("Failed to set volume read-only access "
"mode flag: %s"), e)
diff --git a/openstackclient/volume/v2/backup_record.py b/openstackclient/volume/v2/backup_record.py
index 64ff4f67..0d3af641 100644
--- a/openstackclient/volume/v2/backup_record.py
+++ b/openstackclient/volume/v2/backup_record.py
@@ -26,9 +26,10 @@ LOG = logging.getLogger(__name__)
class ExportBackupRecord(command.ShowOne):
- _description = _('Export volume backup details. Backup information can be '
- 'imported into a new service instance to be able to '
- 'restore.')
+ _description = _("""Export volume backup details.
+
+Backup information can be imported into a new service instance to be able to
+restore.""")
def get_parser(self, prog_name):
parser = super(ExportBackupRecord, self).get_parser(prog_name)
@@ -54,9 +55,10 @@ class ExportBackupRecord(command.ShowOne):
class ImportBackupRecord(command.ShowOne):
- _description = _('Import volume backup details. Exported backup details '
- 'contain the metadata necessary to restore to a new or '
- 'rebuilt service instance')
+ _description = _("""Import volume backup details.
+
+Exported backup details contain the metadata necessary to restore to a new or
+rebuilt service instance""")
def get_parser(self, prog_name):
parser = super(ImportBackupRecord, self).get_parser(prog_name)
diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py
index c50a1b5b..77da6f64 100644
--- a/openstackclient/volume/v2/consistency_group.py
+++ b/openstackclient/volume/v2/consistency_group.py
@@ -14,6 +14,7 @@
"""Volume v2 consistency group action implementations"""
+import argparse
import logging
from osc_lib.cli import format_columns
@@ -90,35 +91,51 @@ class CreateConsistencyGroup(command.ShowOne):
"name",
metavar="<name>",
nargs="?",
- help=_("Name of new consistency group (default to None)")
+ help=_("Name of new consistency group (default to None)"),
)
exclusive_group = parser.add_mutually_exclusive_group(required=True)
exclusive_group.add_argument(
"--volume-type",
metavar="<volume-type>",
- help=_("Volume type of this consistency group (name or ID)")
+ help=_("Volume type of this consistency group (name or ID)"),
)
exclusive_group.add_argument(
+ "--source",
+ metavar="<consistency-group>",
+ help=_("Existing consistency group (name or ID)"),
+ )
+ # NOTE(stephenfin): Legacy alias
+ exclusive_group.add_argument(
"--consistency-group-source",
metavar="<consistency-group>",
- help=_("Existing consistency group (name or ID)")
+ dest='source',
+ help=argparse.SUPPRESS,
+ )
+ exclusive_group.add_argument(
+ "--snapshot",
+ metavar="<consistency-group-snapshot>",
+ help=_("Existing consistency group snapshot (name or ID)"),
)
+ # NOTE(stephenfin): Legacy alias
exclusive_group.add_argument(
"--consistency-group-snapshot",
metavar="<consistency-group-snapshot>",
- help=_("Existing consistency group snapshot (name or ID)")
+ dest='snapshot',
+ help=argparse.SUPPRESS,
)
parser.add_argument(
"--description",
metavar="<description>",
- help=_("Description of this consistency group")
+ help=_("Description of this consistency group"),
)
parser.add_argument(
"--availability-zone",
metavar="<availability-zone>",
- help=_("Availability zone for this consistency group "
- "(not available if creating consistency group "
- "from source)"),
+ help=_(
+ "Availability zone for this consistency group "
+ "(not available if creating consistency group "
+ "from source)"
+ ),
)
return parser
@@ -142,21 +159,23 @@ class CreateConsistencyGroup(command.ShowOne):
consistency_group_id = None
consistency_group_snapshot = None
- if parsed_args.consistency_group_source:
+ if parsed_args.source:
consistency_group_id = utils.find_resource(
volume_client.consistencygroups,
- parsed_args.consistency_group_source).id
- elif parsed_args.consistency_group_snapshot:
+ parsed_args.source,
+ ).id
+ elif parsed_args.snapshot:
consistency_group_snapshot = utils.find_resource(
volume_client.cgsnapshots,
- parsed_args.consistency_group_snapshot).id
+ parsed_args.snapshot,
+ ).id
consistency_group = (
volume_client.consistencygroups.create_from_src(
consistency_group_snapshot,
consistency_group_id,
name=parsed_args.name,
- description=parsed_args.description
+ description=parsed_args.description,
)
)
diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py
index 7905e097..a5e5a670 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -257,15 +257,44 @@ class CreateVolume(command.ShowOne):
if parsed_args.bootable or parsed_args.non_bootable:
try:
- volume_client.volumes.set_bootable(
- volume.id, parsed_args.bootable)
+ if utils.wait_for_status(
+ volume_client.volumes.get,
+ volume.id,
+ success_status=['available'],
+ error_status=['error'],
+ sleep_time=1
+ ):
+ volume_client.volumes.set_bootable(
+ volume.id,
+ parsed_args.bootable
+ )
+ else:
+ msg = _(
+ "Volume status is not available for setting boot "
+ "state"
+ )
+ raise exceptions.CommandError(msg)
except Exception as e:
LOG.error(_("Failed to set volume bootable property: %s"), e)
if parsed_args.read_only or parsed_args.read_write:
try:
- volume_client.volumes.update_readonly_flag(
+ if utils.wait_for_status(
+ volume_client.volumes.get,
volume.id,
- parsed_args.read_only)
+ success_status=['available'],
+ error_status=['error'],
+ sleep_time=1
+ ):
+ volume_client.volumes.update_readonly_flag(
+ volume.id,
+ parsed_args.read_only
+ )
+ else:
+ msg = _(
+ "Volume status is not available for setting it"
+ "read only."
+ )
+ raise exceptions.CommandError(msg)
except Exception as e:
LOG.error(_("Failed to set volume read-only access "
"mode flag: %s"), e)
diff --git a/openstackclient/volume/v3/volume_group.py b/openstackclient/volume/v3/volume_group.py
index 69b18ceb..242ffcd4 100644
--- a/openstackclient/volume/v3/volume_group.py
+++ b/openstackclient/volume/v3/volume_group.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
+import argparse
from cinderclient import api_versions
from osc_lib.command import command
@@ -19,8 +19,6 @@ from osc_lib import utils
from openstackclient.i18n import _
-LOG = logging.getLogger(__name__)
-
def _format_group(group):
columns = (
@@ -82,19 +80,72 @@ class CreateVolumeGroup(command.ShowOne):
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
+ # This is a bit complicated. We accept two patterns: a legacy pattern
+ #
+ # volume group create \
+ # <volume-group-type> <volume-type> [<volume-type>...]
+ #
+ # and the modern approach
+ #
+ # volume group create \
+ # --volume-group-type <volume-group-type>
+ # --volume-type <volume-type>
+ # [--volume-type <volume-type> ...]
+ #
+ # Because argparse doesn't properly support nested exclusive groups, we
+ # use two groups: one to ensure users don't pass <volume-group-type> as
+ # both a positional and an option argument and another to ensure users
+ # don't pass <volume-type> this way. It's a bit weird but it catches
+ # everything we care about.
source_parser = parser.add_mutually_exclusive_group()
+ # we use a different name purely so we can issue a deprecation warning
source_parser.add_argument(
- 'volume_group_type',
+ 'volume_group_type_legacy',
metavar='<volume_group_type>',
nargs='?',
- help=_('Name or ID of volume group type to use.'),
+ help=argparse.SUPPRESS,
)
- parser.add_argument(
- 'volume_types',
+ volume_types_parser = parser.add_mutually_exclusive_group()
+ # We need to use a separate dest
+ # https://github.com/python/cpython/issues/101990
+ volume_types_parser.add_argument(
+ 'volume_types_legacy',
metavar='<volume_type>',
nargs='*',
default=[],
- help=_('Name or ID of volume type(s) to use.'),
+ help=argparse.SUPPRESS,
+ )
+ source_parser.add_argument(
+ '--volume-group-type',
+ metavar='<volume_group_type>',
+ help=_('Volume group type to use (name or ID)'),
+ )
+ volume_types_parser.add_argument(
+ '--volume-type',
+ metavar='<volume_type>',
+ dest='volume_types',
+ action='append',
+ default=[],
+ help=_(
+ 'Volume type(s) to use (name or ID) '
+ '(required with --volume-group-type)'
+ ),
+ )
+ source_parser.add_argument(
+ '--source-group',
+ metavar='<source-group>',
+ help=_(
+ 'Existing volume group to use (name or ID) '
+ '(supported by --os-volume-api-version 3.14 or later)'
+ ),
+ )
+ source_parser.add_argument(
+ '--group-snapshot',
+ metavar='<group-snapshot>',
+ help=_(
+ 'Existing group snapshot to use (name or ID) '
+ '(supported by --os-volume-api-version 3.14 or later)'
+ ),
)
parser.add_argument(
'--name',
@@ -109,59 +160,63 @@ class CreateVolumeGroup(command.ShowOne):
parser.add_argument(
'--availability-zone',
metavar='<availability-zone>',
- help=_('Availability zone for volume group. '
- '(not available if creating group from source)'),
- )
- source_parser.add_argument(
- '--source-group',
- metavar='<source-group>',
- help=_('Existing volume group (name or ID) '
- '(supported by --os-volume-api-version 3.14 or later)'),
- )
- source_parser.add_argument(
- '--group-snapshot',
- metavar='<group-snapshot>',
- help=_('Existing group snapshot (name or ID) '
- '(supported by --os-volume-api-version 3.14 or later)'),
+ help=_(
+ 'Availability zone for volume group. '
+ '(not available if creating group from source)'
+ ),
)
return parser
def take_action(self, parsed_args):
volume_client = self.app.client_manager.volume
- if parsed_args.volume_group_type:
+ if parsed_args.volume_group_type_legacy:
+ msg = _(
+ "Passing volume group type and volume types as positional "
+ "arguments is deprecated. Use the --volume-group-type and "
+ "--volume-type option arguments instead."
+ )
+ self.log.warning(msg)
+
+ volume_group_type = parsed_args.volume_group_type or \
+ parsed_args.volume_group_type_legacy
+ volume_types = parsed_args.volume_types[:]
+ volume_types.extend(parsed_args.volume_types_legacy)
+
+ if volume_group_type:
if volume_client.api_version < api_versions.APIVersion('3.13'):
msg = _(
"--os-volume-api-version 3.13 or greater is required to "
"support the 'volume group create' command"
)
raise exceptions.CommandError(msg)
- if not parsed_args.volume_types:
+ if not volume_types:
msg = _(
- "<volume_types> is a required argument when creating a "
+ "--volume-types is a required argument when creating a "
"group from group type."
)
raise exceptions.CommandError(msg)
- volume_group_type = utils.find_resource(
+ volume_group_type_id = utils.find_resource(
volume_client.group_types,
- parsed_args.volume_group_type,
- )
- volume_types = []
- for volume_type in parsed_args.volume_types:
- volume_types.append(
+ volume_group_type,
+ ).id
+ volume_types_ids = []
+ for volume_type in volume_types:
+ volume_types_ids.append(
utils.find_resource(
volume_client.volume_types,
volume_type,
- )
+ ).id
)
group = volume_client.groups.create(
- volume_group_type.id,
- ','.join(x.id for x in volume_types),
+ volume_group_type_id,
+ ','.join(volume_types_ids),
parsed_args.name,
parsed_args.description,
- availability_zone=parsed_args.availability_zone)
+ availability_zone=parsed_args.availability_zone,
+ )
group = volume_client.groups.get(group.id)
return _format_group(group)
@@ -186,7 +241,7 @@ class CreateVolumeGroup(command.ShowOne):
if parsed_args.availability_zone:
msg = _("'--availability-zone' option will not work "
"if creating group from source.")
- LOG.warning(msg)
+ self.log.warning(msg)
source_group = None
if parsed_args.source_group:
diff --git a/releasenotes/notes/add-port-ranges-in-port-forwarding-command-8c6ee05cf625578a.yaml b/releasenotes/notes/add-port-ranges-in-port-forwarding-command-8c6ee05cf625578a.yaml
new file mode 100644
index 00000000..80e4445e
--- /dev/null
+++ b/releasenotes/notes/add-port-ranges-in-port-forwarding-command-8c6ee05cf625578a.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add port ranges support to the ``floating ip port forwarding`` commands.
diff --git a/releasenotes/notes/consistency-group-create-opts-aliases-e1c2f1498e9b1d3d.yaml b/releasenotes/notes/consistency-group-create-opts-aliases-e1c2f1498e9b1d3d.yaml
new file mode 100644
index 00000000..191f020f
--- /dev/null
+++ b/releasenotes/notes/consistency-group-create-opts-aliases-e1c2f1498e9b1d3d.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+ - |
+ The ``--consistency-group-source`` and ``--consistency-group-snapshot``
+ options for the ``consistency group create`` command have been renamed to
+ ``--source`` and ``--snapshot``, respectively. Aliases are provided for the
+ older variants.
diff --git a/releasenotes/notes/deprecate-volume-group-create-positional-arguments-89f6b886c0f1f2b5.yaml b/releasenotes/notes/deprecate-volume-group-create-positional-arguments-89f6b886c0f1f2b5.yaml
new file mode 100644
index 00000000..ee3a6843
--- /dev/null
+++ b/releasenotes/notes/deprecate-volume-group-create-positional-arguments-89f6b886c0f1f2b5.yaml
@@ -0,0 +1,10 @@
+---
+deprecations:
+ - |
+ The ``<volume-group-type>`` and ``<volume-type> [<volume-type>...]``
+ positional arguments for the ``volume group create`` command have been
+ deprecated in favour of option arguments. For example::
+
+ openstack volume group create \
+ --volume-group-type <volume-group-type>
+ --volume-type <volume-type> [--volume-type <volume-type> ...]
diff --git a/releasenotes/notes/migrate-host-list-show-to-sdk-9b80cd9b4196ab01.yaml b/releasenotes/notes/migrate-host-list-show-to-sdk-9b80cd9b4196ab01.yaml
new file mode 100644
index 00000000..085670c9
--- /dev/null
+++ b/releasenotes/notes/migrate-host-list-show-to-sdk-9b80cd9b4196ab01.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ The ``host list`` and ``host show`` commands have been migrated to SDK.
diff --git a/releasenotes/notes/migrate-server-volume-list-update-to-sdk-95b1d3063e46f813.yaml b/releasenotes/notes/migrate-server-volume-list-update-to-sdk-95b1d3063e46f813.yaml
new file mode 100644
index 00000000..6194fb1b
--- /dev/null
+++ b/releasenotes/notes/migrate-server-volume-list-update-to-sdk-95b1d3063e46f813.yaml
@@ -0,0 +1,3 @@
+features:
+ - |
+ Switch the server volume list and server volume update command from novaclient to SDK.
diff --git a/releasenotes/notes/rename-server-volume-update-to-server-volume-set-833f1730a9bf6169.yaml b/releasenotes/notes/rename-server-volume-update-to-server-volume-set-833f1730a9bf6169.yaml
new file mode 100644
index 00000000..c1e9251b
--- /dev/null
+++ b/releasenotes/notes/rename-server-volume-update-to-server-volume-set-833f1730a9bf6169.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ The ``server volume update`` command has been renamed to ``server volume
+ set`` to better match other commands in OSC. An alias is provided for
+ backwards compatibility.
diff --git a/releasenotes/notes/switch-server-migration-show-to-sdk-4adb88a0f1f03f3b.yaml b/releasenotes/notes/switch-server-migration-show-to-sdk-4adb88a0f1f03f3b.yaml
new file mode 100644
index 00000000..ec47cd13
--- /dev/null
+++ b/releasenotes/notes/switch-server-migration-show-to-sdk-4adb88a0f1f03f3b.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - Finish switching server migration to the OpenStackSDK
diff --git a/setup.cfg b/setup.cfg
index c3c99ccd..aee5e99b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -159,6 +159,7 @@ openstack.compute.v2 =
server_migration_show = openstackclient.compute.v2.server_migration:ShowMigration
server_volume_list = openstackclient.compute.v2.server_volume:ListServerVolume
+ server_volume_set = openstackclient.compute.v2.server_volume:SetServerVolume
server_volume_update = openstackclient.compute.v2.server_volume:UpdateServerVolume
usage_list = openstackclient.compute.v2.usage:ListUsage