diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5da1984d0f..47c9c82629 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,7 +21,6 @@ import io import json import logging import os -import uuid from cliff import columns as cliff_columns import iso8601 @@ -2812,468 +2811,6 @@ revert to release the new server and restart the old one.""") raise SystemExit -class ListMigration(command.Lister): - _description = _("""List server migrations""") - - def get_parser(self, prog_name): - parser = super(ListMigration, self).get_parser(prog_name) - parser.add_argument( - '--server', - metavar='', - help=_( - 'Filter migrations by server (name or ID)' - ) - ) - parser.add_argument( - '--host', - metavar='', - help=_( - 'Filter migrations by source or destination host' - ), - ) - parser.add_argument( - '--status', - metavar='', - help=_('Filter migrations by status') - ) - parser.add_argument( - '--type', - metavar='', - choices=[ - 'evacuation', 'live-migration', 'cold-migration', 'resize', - ], - help=_('Filter migrations by type'), - ) - parser.add_argument( - '--marker', - metavar='', - help=_( - "The last migration of the previous page; displays list " - "of migrations after 'marker'. Note that the marker is " - "the migration UUID. " - "(supported with --os-compute-api-version 2.59 or above)" - ), - ) - parser.add_argument( - '--limit', - metavar='', - type=int, - help=_( - "Maximum number of migrations to display. Note that there " - "is a configurable max limit on the server, and the limit " - "that is used will be the minimum of what is requested " - "here and what is configured in the server. " - "(supported with --os-compute-api-version 2.59 or above)" - ), - ) - parser.add_argument( - '--changes-since', - dest='changes_since', - metavar='', - help=_( - "List only migrations changed later or equal to a certain " - "point of time. The provided time should be an ISO 8061 " - "formatted time, e.g. ``2016-03-04T06:27:59Z``. " - "(supported with --os-compute-api-version 2.59 or above)" - ), - ) - parser.add_argument( - '--changes-before', - dest='changes_before', - metavar='', - help=_( - "List only migrations changed earlier or equal to a " - "certain point of time. The provided time should be an ISO " - "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " - "(supported with --os-compute-api-version 2.66 or above)" - ), - ) - parser.add_argument( - '--project', - metavar='', - help=_( - "Filter migrations by project (name or ID) " - "(supported with --os-compute-api-version 2.80 or above)" - ), - ) - identity_common.add_project_domain_option_to_parser(parser) - parser.add_argument( - '--user', - metavar='', - help=_( - "Filter migrations by user (name or ID) " - "(supported with --os-compute-api-version 2.80 or above)" - ), - ) - identity_common.add_user_domain_option_to_parser(parser) - return parser - - def print_migrations(self, parsed_args, compute_client, migrations): - column_headers = [ - 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', - 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', - 'Created At', 'Updated At', - ] - - # Response fields coming back from the REST API are not always exactly - # the same as the column header names. - columns = [ - 'source_node', 'dest_node', 'source_compute', 'dest_compute', - 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', - 'new_instance_type_id', 'created_at', 'updated_at', - ] - - # Insert migrations UUID after ID - if compute_client.api_version >= api_versions.APIVersion("2.59"): - column_headers.insert(0, "UUID") - columns.insert(0, "uuid") - - if compute_client.api_version >= api_versions.APIVersion("2.23"): - column_headers.insert(0, "Id") - columns.insert(0, "id") - column_headers.insert(len(column_headers) - 2, "Type") - columns.insert(len(columns) - 2, "migration_type") - - if compute_client.api_version >= api_versions.APIVersion("2.80"): - if parsed_args.project: - column_headers.insert(len(column_headers) - 2, "Project") - columns.insert(len(columns) - 2, "project_id") - if parsed_args.user: - column_headers.insert(len(column_headers) - 2, "User") - columns.insert(len(columns) - 2, "user_id") - - return ( - column_headers, - (utils.get_item_properties(mig, columns) for mig in migrations), - ) - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - identity_client = self.app.client_manager.identity - - search_opts = { - 'host': parsed_args.host, - 'status': parsed_args.status, - } - - if parsed_args.server: - search_opts['instance_uuid'] = utils.find_resource( - compute_client.servers, - parsed_args.server, - ).id - - if parsed_args.type: - migration_type = parsed_args.type - # we're using an alias because the default value is confusing - if migration_type == 'cold-migration': - migration_type = 'migration' - search_opts['migration_type'] = migration_type - - if parsed_args.marker: - if compute_client.api_version < api_versions.APIVersion('2.59'): - msg = _( - '--os-compute-api-version 2.59 or greater is required to ' - 'support the --marker option' - ) - raise exceptions.CommandError(msg) - search_opts['marker'] = parsed_args.marker - - if parsed_args.limit: - if compute_client.api_version < api_versions.APIVersion('2.59'): - msg = _( - '--os-compute-api-version 2.59 or greater is required to ' - 'support the --limit option' - ) - raise exceptions.CommandError(msg) - search_opts['limit'] = parsed_args.limit - - if parsed_args.changes_since: - if compute_client.api_version < api_versions.APIVersion('2.59'): - msg = _( - '--os-compute-api-version 2.59 or greater is required to ' - 'support the --changes-since option' - ) - raise exceptions.CommandError(msg) - search_opts['changes_since'] = parsed_args.changes_since - - if parsed_args.changes_before: - if compute_client.api_version < api_versions.APIVersion('2.66'): - msg = _( - '--os-compute-api-version 2.66 or greater is required to ' - 'support the --changes-before option' - ) - raise exceptions.CommandError(msg) - search_opts['changes_before'] = parsed_args.changes_before - - if parsed_args.project: - if compute_client.api_version < api_versions.APIVersion('2.80'): - msg = _( - '--os-compute-api-version 2.80 or greater is required to ' - 'support the --project option' - ) - raise exceptions.CommandError(msg) - - search_opts['project_id'] = identity_common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id - - if parsed_args.user: - if compute_client.api_version < api_versions.APIVersion('2.80'): - msg = _( - '--os-compute-api-version 2.80 or greater is required to ' - 'support the --user option' - ) - raise exceptions.CommandError(msg) - - search_opts['user_id'] = identity_common.find_user( - identity_client, - parsed_args.user, - parsed_args.user_domain, - ).id - - migrations = compute_client.migrations.list(**search_opts) - - return self.print_migrations(parsed_args, compute_client, migrations) - - -def _get_migration_by_uuid(compute_client, server_id, migration_uuid): - for migration in compute_client.server_migrations.list(server_id): - if migration.uuid == migration_uuid: - return migration - break - else: - msg = _( - 'In-progress live migration %s is not found for server %s.' - ) - raise exceptions.CommandError(msg % (migration_uuid, server_id)) - - -class ShowMigration(command.ShowOne): - """Show an in-progress live migration for a given server. - - Note that it is not possible to show cold migrations or completed - live-migrations. Use 'openstack server migration list' to get details for - these. - """ - - def get_parser(self, prog_name): - parser = super().get_parser(prog_name) - parser.add_argument( - 'server', - metavar='', - help=_('Server (name or ID)'), - ) - parser.add_argument( - 'migration', - metavar='', - help=_("Migration (ID)"), - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - if compute_client.api_version < api_versions.APIVersion('2.24'): - msg = _( - '--os-compute-api-version 2.24 or greater is required to ' - 'support the server migration show command' - ) - raise exceptions.CommandError(msg) - - if not parsed_args.migration.isdigit(): - try: - uuid.UUID(parsed_args.migration) - except ValueError: - msg = _( - 'The argument must be an ID or UUID' - ) - raise exceptions.CommandError(msg) - - if compute_client.api_version < api_versions.APIVersion('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, - parsed_args.server, - ) - - # the nova API doesn't currently allow retrieval by UUID but it's a - # reasonably common operation so emulate this behavior by listing - # migrations - the responses are identical - if not parsed_args.migration.isdigit(): - server_migration = _get_migration_by_uuid( - compute_client, server.id, parsed_args.migration, - ) - else: - server_migration = compute_client.server_migrations.get( - server.id, parsed_args.migration, - ) - - columns = ( - 'ID', - 'Server UUID', - '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.59'): - columns += ('UUID',) - - if compute_client.api_version >= api_versions.APIVersion('2.80'): - columns += ('User ID', 'Project ID') - - data = utils.get_item_properties(server_migration, columns) - return columns, data - - -class AbortMigration(command.Command): - """Cancel an ongoing live migration. - - This command requires ``--os-compute-api-version`` 2.24 or greater. - """ - - def get_parser(self, prog_name): - parser = super(AbortMigration, self).get_parser(prog_name) - parser.add_argument( - 'server', - metavar='', - help=_('Server (name or ID)'), - ) - parser.add_argument( - 'migration', - metavar='', - help=_("Migration (ID)"), - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - if compute_client.api_version < api_versions.APIVersion('2.24'): - msg = _( - '--os-compute-api-version 2.24 or greater is required to ' - 'support the server migration abort command' - ) - raise exceptions.CommandError(msg) - - if not parsed_args.migration.isdigit(): - try: - uuid.UUID(parsed_args.migration) - except ValueError: - msg = _( - 'The argument must be an ID or UUID' - ) - raise exceptions.CommandError(msg) - - if compute_client.api_version < api_versions.APIVersion('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, - parsed_args.server, - ) - - # the nova API doesn't currently allow retrieval by UUID but it's a - # reasonably common operation so emulate this behavior by listing - # migrations - the responses are identical - migration_id = parsed_args.migration - if not parsed_args.migration.isdigit(): - migration_id = _get_migration_by_uuid( - compute_client, server.id, parsed_args.migration, - ).id - - compute_client.server_migrations.live_migration_abort( - server.id, migration_id, - ) - - -class ForceCompleteMigration(command.Command): - """Force an ongoing live migration to complete. - - This command requires ``--os-compute-api-version`` 2.22 or greater. - """ - - def get_parser(self, prog_name): - parser = super(ForceCompleteMigration, self).get_parser(prog_name) - parser.add_argument( - 'server', - metavar='', - help=_('Server (name or ID)'), - ) - parser.add_argument( - 'migration', - metavar='', - help=_('Migration (ID)') - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - if compute_client.api_version < api_versions.APIVersion('2.22'): - msg = _( - '--os-compute-api-version 2.22 or greater is required to ' - 'support the server migration force complete command' - ) - raise exceptions.CommandError(msg) - - if not parsed_args.migration.isdigit(): - try: - uuid.UUID(parsed_args.migration) - except ValueError: - msg = _( - 'The argument must be an ID or UUID' - ) - raise exceptions.CommandError(msg) - - if compute_client.api_version < api_versions.APIVersion('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, - parsed_args.server, - ) - - # the nova API doesn't currently allow retrieval by UUID but it's a - # reasonably common operation so emulate this behavior by listing - # migrations - the responses are identical - migration_id = parsed_args.migration - if not parsed_args.migration.isdigit(): - migration_id = _get_migration_by_uuid( - compute_client, server.id, parsed_args.migration, - ).id - - compute_client.server_migrations.live_migrate_force_complete( - server.id, migration_id, - ) - - class PauseServer(command.Command): _description = _("Pause server(s)") diff --git a/openstackclient/compute/v2/server_migration.py b/openstackclient/compute/v2/server_migration.py new file mode 100644 index 0000000000..919b67bdcb --- /dev/null +++ b/openstackclient/compute/v2/server_migration.py @@ -0,0 +1,485 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Compute v2 Server Migration action implementations""" + +import uuid + +from novaclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +class ListMigration(command.Lister): + _description = _("""List server migrations""") + + def get_parser(self, prog_name): + parser = super(ListMigration, self).get_parser(prog_name) + parser.add_argument( + '--server', + metavar='', + help=_( + 'Filter migrations by server (name or ID)' + ) + ) + parser.add_argument( + '--host', + metavar='', + help=_( + 'Filter migrations by source or destination host' + ), + ) + parser.add_argument( + '--status', + metavar='', + help=_('Filter migrations by status') + ) + parser.add_argument( + '--type', + metavar='', + choices=[ + 'evacuation', 'live-migration', 'cold-migration', 'resize', + ], + help=_('Filter migrations by type'), + ) + parser.add_argument( + '--marker', + metavar='', + help=_( + "The last migration of the previous page; displays list " + "of migrations after 'marker'. Note that the marker is " + "the migration UUID. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_( + "Maximum number of migrations to display. Note that there " + "is a configurable max limit on the server, and the limit " + "that is used will be the minimum of what is requested " + "here and what is configured in the server. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--changes-since', + dest='changes_since', + metavar='', + help=_( + "List only migrations changed later or equal to a certain " + "point of time. The provided time should be an ISO 8061 " + "formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--changes-before', + dest='changes_before', + metavar='', + help=_( + "List only migrations changed earlier or equal to a " + "certain point of time. The provided time should be an ISO " + "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.66 or above)" + ), + ) + parser.add_argument( + '--project', + metavar='', + help=_( + "Filter migrations by project (name or ID) " + "(supported with --os-compute-api-version 2.80 or above)" + ), + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--user', + metavar='', + help=_( + "Filter migrations by user (name or ID) " + "(supported with --os-compute-api-version 2.80 or above)" + ), + ) + identity_common.add_user_domain_option_to_parser(parser) + return parser + + def print_migrations(self, parsed_args, compute_client, migrations): + column_headers = [ + 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', + 'Created At', 'Updated At', + ] + + # Response fields coming back from the REST API are not always exactly + # the same as the column header names. + columns = [ + 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'created_at', 'updated_at', + ] + + # Insert migrations UUID after ID + if compute_client.api_version >= api_versions.APIVersion("2.59"): + column_headers.insert(0, "UUID") + columns.insert(0, "uuid") + + if compute_client.api_version >= api_versions.APIVersion("2.23"): + column_headers.insert(0, "Id") + columns.insert(0, "id") + column_headers.insert(len(column_headers) - 2, "Type") + columns.insert(len(columns) - 2, "migration_type") + + if compute_client.api_version >= api_versions.APIVersion("2.80"): + if parsed_args.project: + column_headers.insert(len(column_headers) - 2, "Project") + columns.insert(len(columns) - 2, "project_id") + if parsed_args.user: + column_headers.insert(len(column_headers) - 2, "User") + columns.insert(len(columns) - 2, "user_id") + + return ( + column_headers, + (utils.get_item_properties(mig, columns) for mig in migrations), + ) + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + + search_opts = { + 'host': parsed_args.host, + 'status': parsed_args.status, + } + + if parsed_args.server: + search_opts['instance_uuid'] = utils.find_resource( + compute_client.servers, + parsed_args.server, + ).id + + if parsed_args.type: + migration_type = parsed_args.type + # we're using an alias because the default value is confusing + if migration_type == 'cold-migration': + migration_type = 'migration' + search_opts['migration_type'] = migration_type + + if parsed_args.marker: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --marker option' + ) + raise exceptions.CommandError(msg) + search_opts['marker'] = parsed_args.marker + + if parsed_args.limit: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --limit option' + ) + raise exceptions.CommandError(msg) + search_opts['limit'] = parsed_args.limit + + if parsed_args.changes_since: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --changes-since option' + ) + raise exceptions.CommandError(msg) + search_opts['changes_since'] = parsed_args.changes_since + + if parsed_args.changes_before: + if compute_client.api_version < api_versions.APIVersion('2.66'): + msg = _( + '--os-compute-api-version 2.66 or greater is required to ' + 'support the --changes-before option' + ) + raise exceptions.CommandError(msg) + search_opts['changes_before'] = parsed_args.changes_before + + if parsed_args.project: + if compute_client.api_version < api_versions.APIVersion('2.80'): + msg = _( + '--os-compute-api-version 2.80 or greater is required to ' + 'support the --project option' + ) + raise exceptions.CommandError(msg) + + search_opts['project_id'] = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + + if parsed_args.user: + if compute_client.api_version < api_versions.APIVersion('2.80'): + msg = _( + '--os-compute-api-version 2.80 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + search_opts['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + + migrations = compute_client.migrations.list(**search_opts) + + return self.print_migrations(parsed_args, compute_client, migrations) + + +def _get_migration_by_uuid(compute_client, server_id, migration_uuid): + for migration in compute_client.server_migrations.list(server_id): + if migration.uuid == migration_uuid: + return migration + break + else: + msg = _( + 'In-progress live migration %s is not found for server %s.' + ) + raise exceptions.CommandError(msg % (migration_uuid, server_id)) + + +class ShowMigration(command.ShowOne): + """Show an in-progress live migration for a given server. + + Note that it is not possible to show cold migrations or completed + live-migrations. Use 'openstack server migration list' to get details for + these. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='', + help=_("Migration (ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.24'): + msg = _( + '--os-compute-api-version 2.24 or greater is required to ' + 'support the server migration show command' + ) + raise exceptions.CommandError(msg) + + if not parsed_args.migration.isdigit(): + try: + uuid.UUID(parsed_args.migration) + except ValueError: + msg = _( + 'The argument must be an ID or UUID' + ) + raise exceptions.CommandError(msg) + + if compute_client.api_version < api_versions.APIVersion('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, + parsed_args.server, + ) + + # the nova API doesn't currently allow retrieval by UUID but it's a + # reasonably common operation so emulate this behavior by listing + # migrations - the responses are identical + if not parsed_args.migration.isdigit(): + server_migration = _get_migration_by_uuid( + compute_client, server.id, parsed_args.migration, + ) + else: + server_migration = compute_client.server_migrations.get( + server.id, parsed_args.migration, + ) + + columns = ( + 'ID', + 'Server UUID', + '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.59'): + columns += ('UUID',) + + if compute_client.api_version >= api_versions.APIVersion('2.80'): + columns += ('User ID', 'Project ID') + + data = utils.get_item_properties(server_migration, columns) + return columns, data + + +class AbortMigration(command.Command): + """Cancel an ongoing live migration. + + This command requires ``--os-compute-api-version`` 2.24 or greater. + """ + + def get_parser(self, prog_name): + parser = super(AbortMigration, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='', + help=_("Migration (ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.24'): + msg = _( + '--os-compute-api-version 2.24 or greater is required to ' + 'support the server migration abort command' + ) + raise exceptions.CommandError(msg) + + if not parsed_args.migration.isdigit(): + try: + uuid.UUID(parsed_args.migration) + except ValueError: + msg = _( + 'The argument must be an ID or UUID' + ) + raise exceptions.CommandError(msg) + + if compute_client.api_version < api_versions.APIVersion('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, + parsed_args.server, + ) + + # the nova API doesn't currently allow retrieval by UUID but it's a + # reasonably common operation so emulate this behavior by listing + # migrations - the responses are identical + migration_id = parsed_args.migration + if not parsed_args.migration.isdigit(): + migration_id = _get_migration_by_uuid( + compute_client, server.id, parsed_args.migration, + ).id + + compute_client.server_migrations.live_migration_abort( + server.id, migration_id, + ) + + +class ForceCompleteMigration(command.Command): + """Force an ongoing live migration to complete. + + This command requires ``--os-compute-api-version`` 2.22 or greater. + """ + + def get_parser(self, prog_name): + parser = super(ForceCompleteMigration, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='', + help=_('Migration (ID)') + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.22'): + msg = _( + '--os-compute-api-version 2.22 or greater is required to ' + 'support the server migration force complete command' + ) + raise exceptions.CommandError(msg) + + if not parsed_args.migration.isdigit(): + try: + uuid.UUID(parsed_args.migration) + except ValueError: + msg = _( + 'The argument must be an ID or UUID' + ) + raise exceptions.CommandError(msg) + + if compute_client.api_version < api_versions.APIVersion('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, + parsed_args.server, + ) + + # the nova API doesn't currently allow retrieval by UUID but it's a + # reasonably common operation so emulate this behavior by listing + # migrations - the responses are identical + migration_id = parsed_args.migration + if not parsed_args.migration.isdigit(): + migration_id = _get_migration_by_uuid( + compute_client, server.id, parsed_args.migration, + ).id + + compute_client.server_migrations.live_migrate_force_complete( + server.id, migration_id, + ) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index e1002d2806..188781097b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -31,7 +31,6 @@ from osc_lib import utils as common_utils from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes -from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit.image.v2 import fakes as image_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils @@ -5559,990 +5558,6 @@ class TestServerMigrate(TestServer): self.assertNotCalled(self.servers_mock.live_migrate) -class TestListMigration(TestServer): - """Test fetch all migrations.""" - - MIGRATION_COLUMNS = [ - 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'source_node', 'dest_node', 'source_compute', 'dest_compute', - 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', - 'new_instance_type_id', 'created_at', 'updated_at' - ] - - def setUp(self): - super(TestListMigration, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - self.servers_mock.get.return_value = self.server - - self.migrations = compute_fakes.FakeMigration.create_migrations( - count=3) - self.migrations_mock.list.return_value = self.migrations - - self.data = (common_utils.get_item_properties( - s, self.MIGRATION_FIELDS) for s in self.migrations) - - # Get the command object to test - self.cmd = server.ListMigration(self.app, None) - - def test_server_migration_list_no_options(self): - arglist = [] - verifylist = [] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': None, - 'host': None, - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_migration_list(self): - arglist = [ - '--server', 'server1', - '--host', 'host1', - '--status', 'migrating', - '--type', 'cold-migration', - ] - verifylist = [ - ('server', 'server1'), - ('host', 'host1'), - ('status', 'migrating'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'host': 'host1', - 'instance_uuid': self.server.id, - 'migration_type': 'migration', - } - - self.servers_mock.get.assert_called_with('server1') - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - -class TestListMigrationV223(TestListMigration): - """Test fetch all migrations. """ - - MIGRATION_COLUMNS = [ - 'Id', 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', - 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', - 'Type', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'id', 'source_node', 'dest_node', 'source_compute', 'dest_compute', - 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', - 'new_instance_type_id', 'migration_type', 'created_at', 'updated_at' - ] - - def setUp(self): - super(TestListMigrationV223, self).setUp() - - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.23') - - def test_server_migration_list(self): - arglist = [ - '--status', 'migrating' - ] - verifylist = [ - ('status', 'migrating') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'host': None, - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - -class TestListMigrationV259(TestListMigration): - """Test fetch all migrations. """ - - MIGRATION_COLUMNS = [ - 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', - 'dest_compute', 'dest_host', 'status', 'instance_uuid', - 'old_instance_type_id', 'new_instance_type_id', 'migration_type', - 'created_at', 'updated_at' - ] - - def setUp(self): - super(TestListMigrationV259, self).setUp() - - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.59') - - def test_server_migration_list(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--marker', 'test_kp', - '--changes-since', '2019-08-09T08:03:25Z' - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('marker', 'test_kp'), - ('changes_since', '2019-08-09T08:03:25Z') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'marker': 'test_kp', - 'host': None, - 'changes_since': '2019-08-09T08:03:25Z', - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_migration_list_with_limit_pre_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.58') - arglist = [ - '--status', 'migrating', - '--limit', '1' - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - def test_server_migration_list_with_marker_pre_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.58') - arglist = [ - '--status', 'migrating', - '--marker', 'test_kp' - ] - verifylist = [ - ('status', 'migrating'), - ('marker', 'test_kp') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - def test_server_migration_list_with_changes_since_pre_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.58') - arglist = [ - '--status', 'migrating', - '--changes-since', '2019-08-09T08:03:25Z' - ] - verifylist = [ - ('status', 'migrating'), - ('changes_since', '2019-08-09T08:03:25Z') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - -class TestListMigrationV266(TestListMigration): - """Test fetch all migrations by changes-before. """ - - MIGRATION_COLUMNS = [ - 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', - 'dest_compute', 'dest_host', 'status', 'instance_uuid', - 'old_instance_type_id', 'new_instance_type_id', 'migration_type', - 'created_at', 'updated_at' - ] - - def setUp(self): - super(TestListMigrationV266, self).setUp() - - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.66') - - def test_server_migration_list_with_changes_before(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--marker', 'test_kp', - '--changes-since', '2019-08-07T08:03:25Z', - '--changes-before', '2019-08-09T08:03:25Z' - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('marker', 'test_kp'), - ('changes_since', '2019-08-07T08:03:25Z'), - ('changes_before', '2019-08-09T08:03:25Z') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'marker': 'test_kp', - 'host': None, - 'changes_since': '2019-08-07T08:03:25Z', - 'changes_before': '2019-08-09T08:03:25Z', - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_migration_list_with_changes_before_pre_v266(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.65') - arglist = [ - '--status', 'migrating', - '--changes-before', '2019-08-09T08:03:25Z' - ] - verifylist = [ - ('status', 'migrating'), - ('changes_before', '2019-08-09T08:03:25Z') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.66 or greater is required', - str(ex)) - - -class TestListMigrationV280(TestListMigration): - """Test fetch all migrations by user-id and/or project-id. """ - - MIGRATION_COLUMNS = [ - 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', - 'dest_compute', 'dest_host', 'status', 'instance_uuid', - 'old_instance_type_id', 'new_instance_type_id', 'migration_type', - 'created_at', 'updated_at' - ] - - project = identity_fakes.FakeProject.create_one_project() - user = identity_fakes.FakeUser.create_one_user() - - def setUp(self): - super(TestListMigrationV280, self).setUp() - - self.projects_mock = self.app.client_manager.identity.projects - self.projects_mock.reset_mock() - - self.users_mock = self.app.client_manager.identity.users - self.users_mock.reset_mock() - - self.projects_mock.get.return_value = self.project - self.users_mock.get.return_value = self.user - - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.80') - - def test_server_migration_list_with_project(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--marker', 'test_kp', - '--changes-since', '2019-08-07T08:03:25Z', - '--changes-before', '2019-08-09T08:03:25Z', - '--project', self.project.id - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('marker', 'test_kp'), - ('changes_since', '2019-08-07T08:03:25Z'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('project', self.project.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'marker': 'test_kp', - 'host': None, - 'project_id': self.project.id, - 'changes_since': '2019-08-07T08:03:25Z', - 'changes_before': "2019-08-09T08:03:25Z", - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, "Project") - self.MIGRATION_FIELDS.insert( - len(self.MIGRATION_FIELDS) - 2, "project_id") - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - # Clean up global variables MIGRATION_COLUMNS - self.MIGRATION_COLUMNS.remove('Project') - # Clean up global variables MIGRATION_FIELDS - self.MIGRATION_FIELDS.remove('project_id') - - def test_get_migrations_with_project_pre_v280(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - arglist = [ - '--status', 'migrating', - '--changes-before', '2019-08-09T08:03:25Z', - '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' - ] - verifylist = [ - ('status', 'migrating'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('project', '0c2accde-644a-45fa-8c10-e76debc7fbc3') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.80 or greater is required', - str(ex)) - - def test_server_migration_list_with_user(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--marker', 'test_kp', - '--changes-since', '2019-08-07T08:03:25Z', - '--changes-before', '2019-08-09T08:03:25Z', - '--user', self.user.id, - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('marker', 'test_kp'), - ('changes_since', '2019-08-07T08:03:25Z'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('user', self.user.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'marker': 'test_kp', - 'host': None, - 'user_id': self.user.id, - 'changes_since': '2019-08-07T08:03:25Z', - 'changes_before': "2019-08-09T08:03:25Z", - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, "User") - self.MIGRATION_FIELDS.insert( - len(self.MIGRATION_FIELDS) - 2, "user_id") - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - # Clean up global variables MIGRATION_COLUMNS - self.MIGRATION_COLUMNS.remove('User') - # Clean up global variables MIGRATION_FIELDS - self.MIGRATION_FIELDS.remove('user_id') - - def test_get_migrations_with_user_pre_v280(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - arglist = [ - '--status', 'migrating', - '--changes-before', '2019-08-09T08:03:25Z', - '--user', self.user.id, - ] - verifylist = [ - ('status', 'migrating'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('user', self.user.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.80 or greater is required', - str(ex)) - - def test_server_migration_list_with_project_and_user(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--changes-since', '2019-08-07T08:03:25Z', - '--changes-before', '2019-08-09T08:03:25Z', - '--project', self.project.id, - '--user', self.user.id, - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('changes_since', '2019-08-07T08:03:25Z'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('project', self.project.id), - ('user', self.user.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'host': None, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'changes_since': '2019-08-07T08:03:25Z', - 'changes_before': "2019-08-09T08:03:25Z", - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, "Project") - self.MIGRATION_FIELDS.insert( - len(self.MIGRATION_FIELDS) - 2, "project_id") - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, "User") - self.MIGRATION_FIELDS.insert( - len(self.MIGRATION_FIELDS) - 2, "user_id") - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - # Clean up global variables MIGRATION_COLUMNS - self.MIGRATION_COLUMNS.remove('Project') - self.MIGRATION_FIELDS.remove('project_id') - self.MIGRATION_COLUMNS.remove('User') - self.MIGRATION_FIELDS.remove('user_id') - - def test_get_migrations_with_project_and_user_pre_v280(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - arglist = [ - '--status', 'migrating', - '--changes-before', '2019-08-09T08:03:25Z', - '--project', self.project.id, - '--user', self.user.id, - ] - verifylist = [ - ('status', 'migrating'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('project', self.project.id), - ('user', self.user.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.80 or greater is required', - str(ex)) - - -class TestServerMigrationShow(TestServer): - - def setUp(self): - super().setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - self.servers_mock.get.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.columns = ( - 'ID', - 'Server UUID', - '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', - ) - - self.data = ( - self.server_migration.id, - self.server_migration.server_uuid, - self.server_migration.status, - self.server_migration.source_compute, - self.server_migration.source_node, - self.server_migration.dest_compute, - self.server_migration.dest_host, - self.server_migration.dest_node, - self.server_migration.memory_total_bytes, - self.server_migration.memory_processed_bytes, - self.server_migration.memory_remaining_bytes, - self.server_migration.disk_total_bytes, - self.server_migration.disk_processed_bytes, - self.server_migration.disk_remaining_bytes, - self.server_migration.created_at, - self.server_migration.updated_at, - ) - - # Get the command object to test - self.cmd = server.ShowMigration(self.app, None) - - def _test_server_migration_show(self): - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - 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',) - - def test_server_migration_show(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '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.columns += ('UUID',) - self.data += (self.server_migration.uuid,) - - 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.columns += ('UUID', 'User ID', 'Project ID') - self.data += ( - self.server_migration.uuid, - self.server_migration.user_id, - self.server_migration.project_id, - ) - - 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') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.24 or greater is required', - str(ex)) - - 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.columns += ('UUID',) - self.data += (self.server_migration.uuid,) - - arglist = [ - self.server.id, - self.server_migration.uuid, # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - 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() - - 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 = [] - - arglist = [ - self.server.id, - '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', - str(ex)) - - def test_server_migration_show_by_uuid_pre_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.58') - - arglist = [ - self.server.id, - '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - def test_server_migration_show_invalid_id(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.24') - - arglist = [ - self.server.id, - 'foo', # invalid migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - 'The argument must be an ID or UUID', - str(ex)) - - -class TestServerMigrationAbort(TestServer): - - def setUp(self): - super(TestServerMigrationAbort, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - - # Return value for utils.find_resource for server. - self.servers_mock.get.return_value = self.server - - # Get the command object to test - self.cmd = server.AbortMigration(self.app, None) - - def test_migration_abort(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.24') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server_migrations_mock.live_migration_abort.assert_called_with( - self.server.id, '2',) - self.assertIsNone(result) - - def test_migration_abort_pre_v224(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.23') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.24 or greater is required', - str(ex)) - - def test_server_migration_abort_by_uuid(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.59') - - self.server_migration = compute_fakes.FakeServerMigration\ - .create_one_server_migration() - self.server_migrations_mock.list.return_value = [self.server_migration] - - arglist = [ - self.server.id, - self.server_migration.uuid, # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server_migrations_mock.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.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.server_migrations_mock.list.return_value = [] - - arglist = [ - self.server.id, - '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', - str(ex)) - - def test_server_migration_abort_by_uuid_pre_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.58') - - arglist = [ - self.server.id, - '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - -class TestServerMigrationForceComplete(TestServer): - - def setUp(self): - super(TestServerMigrationForceComplete, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - - # Return value for utils.find_resource for server. - self.servers_mock.get.return_value = self.server - - # Get the command object to test - self.cmd = server.ForceCompleteMigration(self.app, None) - - def test_migration_force_complete(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.22') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server_migrations_mock.live_migrate_force_complete\ - .assert_called_with(self.server.id, '2',) - self.assertIsNone(result) - - def test_migration_force_complete_pre_v222(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.21') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.22 or greater is required', - str(ex)) - - def test_server_migration_force_complete_by_uuid(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.59') - - self.server_migration = compute_fakes.FakeServerMigration\ - .create_one_server_migration() - self.server_migrations_mock.list.return_value = [self.server_migration] - - arglist = [ - self.server.id, - self.server_migration.uuid, # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server_migrations_mock.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.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.server_migrations_mock.list.return_value = [] - - arglist = [ - self.server.id, - '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', - 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') - - arglist = [ - self.server.id, - '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - class TestServerPause(TestServer): def setUp(self): diff --git a/openstackclient/tests/unit/compute/v2/test_server_migration.py b/openstackclient/tests/unit/compute/v2/test_server_migration.py new file mode 100644 index 0000000000..c4cbac47e2 --- /dev/null +++ b/openstackclient/tests/unit/compute/v2/test_server_migration.py @@ -0,0 +1,1028 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from novaclient import api_versions +from osc_lib import exceptions +from osc_lib import utils as common_utils + +from openstackclient.compute.v2 import server_migration +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestServerMigration(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 ServerMigrationsManager Mock + self.server_migrations_mock = \ + self.app.client_manager.compute.server_migrations + self.server_migrations_mock.reset_mock() + + # Get a shortcut to the compute client MigrationManager mock + self.migrations_mock = self.app.client_manager.compute.migrations + self.migrations_mock.reset_mock() + + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute + + +class TestListMigration(TestServerMigration): + """Test fetch all migrations.""" + + MIGRATION_COLUMNS = [ + 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'created_at', 'updated_at' + ] + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = self.server + + self.migrations = compute_fakes.FakeMigration.create_migrations( + count=3) + self.migrations_mock.list.return_value = self.migrations + + self.data = (common_utils.get_item_properties( + s, self.MIGRATION_FIELDS) for s in self.migrations) + + # Get the command object to test + self.cmd = server_migration.ListMigration(self.app, None) + + def test_server_migration_list_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': None, + 'host': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list(self): + arglist = [ + '--server', 'server1', + '--host', 'host1', + '--status', 'migrating', + '--type', 'cold-migration', + ] + verifylist = [ + ('server', 'server1'), + ('host', 'host1'), + ('status', 'migrating'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'host': 'host1', + 'instance_uuid': self.server.id, + 'migration_type': 'migration', + } + + self.servers_mock.get.assert_called_with('server1') + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestListMigrationV223(TestListMigration): + """Test fetch all migrations. """ + + MIGRATION_COLUMNS = [ + 'Id', 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', + 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'migration_type', 'created_at', 'updated_at' + ] + + def setUp(self): + super().setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + def test_server_migration_list(self): + arglist = [ + '--status', 'migrating' + ] + verifylist = [ + ('status', 'migrating') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'host': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestListMigrationV259(TestListMigration): + """Test fetch all migrations. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + + def setUp(self): + super().setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + def test_server_migration_list(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'changes_since': '2019-08-09T08:03:25Z', + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list_with_limit_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--limit', '1' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + def test_server_migration_list_with_marker_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--marker', 'test_kp' + ] + verifylist = [ + ('status', 'migrating'), + ('marker', 'test_kp') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + def test_server_migration_list_with_changes_since_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--changes-since', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_since', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + +class TestListMigrationV266(TestListMigration): + """Test fetch all migrations by changes-before. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + + def setUp(self): + super().setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.66') + + def test_server_migration_list_with_changes_before(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': '2019-08-09T08:03:25Z', + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list_with_changes_before_pre_v266(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.65') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.66 or greater is required', + str(ex)) + + +class TestListMigrationV280(TestListMigration): + """Test fetch all migrations by user-id and/or project-id. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user() + + def setUp(self): + super().setUp() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + self.projects_mock.get.return_value = self.project + self.users_mock.get.return_value = self.user + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.80') + + def test_server_migration_list_with_project(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', self.project.id + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project', self.project.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'project_id': self.project.id, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "project_id") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('Project') + # Clean up global variables MIGRATION_FIELDS + self.MIGRATION_FIELDS.remove('project_id') + + def test_get_migrations_with_project_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project', '0c2accde-644a-45fa-8c10-e76debc7fbc3') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + def test_server_migration_list_with_user(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--user', self.user.id, + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'user_id': self.user.id, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "User") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "user_id") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('User') + # Clean up global variables MIGRATION_FIELDS + self.MIGRATION_FIELDS.remove('user_id') + + def test_get_migrations_with_user_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--user', self.user.id, + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + def test_server_migration_list_with_project_and_user(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', self.project.id, + '--user', self.user.id, + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project', self.project.id), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'host': None, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "project_id") + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "User") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "user_id") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('Project') + self.MIGRATION_FIELDS.remove('project_id') + self.MIGRATION_COLUMNS.remove('User') + self.MIGRATION_FIELDS.remove('user_id') + + def test_get_migrations_with_project_and_user_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', self.project.id, + '--user', self.user.id, + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project', self.project.id), + ('user', self.user.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + +class 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_migration = compute_fakes.FakeServerMigration\ + .create_one_server_migration() + self.server_migrations_mock.get.return_value = self.server_migration + + self.columns = ( + 'ID', + 'Server UUID', + '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', + ) + + self.data = ( + self.server_migration.id, + self.server_migration.server_uuid, + self.server_migration.status, + self.server_migration.source_compute, + self.server_migration.source_node, + self.server_migration.dest_compute, + self.server_migration.dest_host, + self.server_migration.dest_node, + self.server_migration.memory_total_bytes, + self.server_migration.memory_processed_bytes, + self.server_migration.memory_remaining_bytes, + self.server_migration.disk_total_bytes, + self.server_migration.disk_processed_bytes, + self.server_migration.disk_remaining_bytes, + self.server_migration.created_at, + self.server_migration.updated_at, + ) + + # Get the command object to test + self.cmd = server_migration.ShowMigration(self.app, None) + + def _test_server_migration_show(self): + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + 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',) + + def test_server_migration_show(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '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.columns += ('UUID',) + self.data += (self.server_migration.uuid,) + + 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.columns += ('UUID', 'User ID', 'Project ID') + self.data += ( + self.server_migration.uuid, + self.server_migration.user_id, + self.server_migration.project_id, + ) + + 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') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.24 or greater is required', + str(ex)) + + 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.columns += ('UUID',) + self.data += (self.server_migration.uuid,) + + arglist = [ + self.server.id, + self.server_migration.uuid, # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + 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() + + 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 = [] + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', + str(ex)) + + def test_server_migration_show_by_uuid_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + def test_server_migration_show_invalid_id(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.24') + + arglist = [ + self.server.id, + 'foo', # invalid migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'The argument must be an ID or UUID', + str(ex)) + + +class TestServerMigrationAbort(TestServerMigration): + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server_migration.AbortMigration(self.app, None) + + def test_migration_abort(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.24') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.live_migration_abort.assert_called_with( + self.server.id, '2',) + self.assertIsNone(result) + + def test_migration_abort_pre_v224(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.24 or greater is required', + str(ex)) + + def test_server_migration_abort_by_uuid(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + self.server_migration = compute_fakes.FakeServerMigration\ + .create_one_server_migration() + self.server_migrations_mock.list.return_value = [self.server_migration] + + arglist = [ + self.server.id, + self.server_migration.uuid, # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.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.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.server_migrations_mock.list.return_value = [] + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', + str(ex)) + + def test_server_migration_abort_by_uuid_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + +class TestServerMigrationForceComplete(TestServerMigration): + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server_migration.ForceCompleteMigration(self.app, None) + + def test_migration_force_complete(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.22') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.live_migrate_force_complete\ + .assert_called_with(self.server.id, '2',) + self.assertIsNone(result) + + def test_migration_force_complete_pre_v222(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.21') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.22 or greater is required', + str(ex)) + + def test_server_migration_force_complete_by_uuid(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + self.server_migration = compute_fakes.FakeServerMigration\ + .create_one_server_migration() + self.server_migrations_mock.list.return_value = [self.server_migration] + + arglist = [ + self.server.id, + self.server_migration.uuid, # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.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.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.server_migrations_mock.list.return_value = [] + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', + 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') + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) diff --git a/setup.cfg b/setup.cfg index b4718b1fb5..79e805f04e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -111,12 +111,8 @@ openstack.compute.v2 = server_migrate = openstackclient.compute.v2.server:MigrateServer server_migrate_confirm = openstackclient.compute.v2.server:MigrateConfirm server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert - server_migration_abort = openstackclient.compute.v2.server:AbortMigration server_migration_confirm = openstackclient.compute.v2.server:ConfirmMigration - server_migration_force_complete = openstackclient.compute.v2.server:ForceCompleteMigration - server_migration_list = openstackclient.compute.v2.server:ListMigration server_migration_revert = openstackclient.compute.v2.server:RevertMigration - server_migration_show = openstackclient.compute.v2.server:ShowMigration server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer @@ -157,6 +153,11 @@ openstack.compute.v2 = server_image_create = openstackclient.compute.v2.server_image:CreateServerImage + server_migration_abort = openstackclient.compute.v2.server_migration:AbortMigration + server_migration_force_complete = openstackclient.compute.v2.server_migration:ForceCompleteMigration + server_migration_list = openstackclient.compute.v2.server_migration:ListMigration + server_migration_show = openstackclient.compute.v2.server_migration:ShowMigration + server_volume_list = openstackclient.compute.v2.server_volume:ListServerVolume server_volume_update = openstackclient.compute.v2.server_volume:UpdateServerVolume