API: add support to abort queued live migration in microversion 2.65

This patch bumped API microversion to 2.65 to add support for
abort live migrations in ``queued`` and ``preparing`` status.

Part of blueprint abort-live-migration-in-queued-status

Change-Id: I4636a8d270ce01c1831bc951c4497ad472bc9aa8
This commit is contained in:
Kevin_Zheng 2018-06-07 16:43:21 +08:00 committed by Matt Riedemann
parent e8192177e8
commit 4cae503767
14 changed files with 191 additions and 9 deletions

View File

@ -184,6 +184,9 @@ Abort an in-progress live migration.
.. note:: Microversion 2.24 or greater is required for this API. .. note:: Microversion 2.24 or greater is required for this API.
.. note:: With microversion 2.65 or greater, you can abort live migrations
also in ``queued`` and ``preparing`` status.
.. note:: Not all compute back ends support aborting an in-progress live .. note:: Not all compute back ends support aborting an in-progress live
migration. migration.
@ -198,7 +201,9 @@ The server OS-EXT-STS:task_state value must be ``migrating``.
If the server is locked, you must have administrator privileges to force the If the server is locked, you must have administrator privileges to force the
completion of the server migration. completion of the server migration.
The migration status must be ``running``. For microversions from 2.24 to 2.64 the migration status must be ``running``,
for microversion 2.65 and greater, the migration status can also be ``queued``
and ``preparing``.
**Asynchronous Postconditions** **Asynchronous Postconditions**

View File

@ -0,0 +1,6 @@
{
"os-migrateLive": {
"host": null,
"block_migration": "auto"
}
}

View File

@ -19,7 +19,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.64", "version": "2.65",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@ -22,7 +22,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.64", "version": "2.65",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@ -156,6 +156,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
and ``rules`` (optional) fields are added in response body of and ``rules`` (optional) fields are added in response body of
GET, POST /os-server-groups APIs and GET GET, POST /os-server-groups APIs and GET
/os-server-groups/{group_id} API. /os-server-groups/{group_id} API.
* 2.65 - Add support for abort live migrations in ``queued`` and
``preparing`` status.
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
@ -164,7 +166,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions # Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API. # support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1" _MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.64" _MAX_API_VERSION = "2.65"
DEFAULT_API_VERSION = _MIN_API_VERSION DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal # Almost all proxy APIs which are related to network, images and baremetal

View File

@ -835,4 +835,10 @@ in server group APIs:
``/os-server-groups/{server_group_id}`` API. ``/os-server-groups/{server_group_id}`` API.
* The ``policies`` and ``metadata`` fields have been removed from the response * The ``policies`` and ``metadata`` fields have been removed from the response
body of POST, GET ``/os-server-groups`` API and GET body of POST, GET ``/os-server-groups`` API and GET
``/os-server-groups/{server_group_id}`` API. ``/os-server-groups/{server_group_id}`` API.
2.65
----
Add support for abort live migrations in ``queued`` and ``preparing`` status
for API ``DELETE /servers/{server_id}/migrations/{migration_id}``.

View File

@ -146,9 +146,13 @@ class ServerMigrationsController(wsgi.Controller):
context = req.environ['nova.context'] context = req.environ['nova.context']
context.can(sm_policies.POLICY_ROOT % 'delete') context.can(sm_policies.POLICY_ROOT % 'delete')
support_abort_in_queue = api_version_request.is_supported(req, '2.65')
instance = common.get_instance(self.compute_api, context, server_id) instance = common.get_instance(self.compute_api, context, server_id)
try: try:
self.compute_api.live_migrate_abort(context, instance, id) self.compute_api.live_migrate_abort(
context, instance, id,
support_abort_in_queue=support_abort_in_queue)
except exception.InstanceInvalidState as state_error: except exception.InstanceInvalidState as state_error:
common.raise_http_conflict_for_instance_invalid_state( common.raise_http_conflict_for_instance_invalid_state(
state_error, "abort live migration", server_id) state_error, "abort live migration", server_id)
@ -156,3 +160,5 @@ class ServerMigrationsController(wsgi.Controller):
raise exc.HTTPNotFound(explanation=e.format_message()) raise exc.HTTPNotFound(explanation=e.format_message())
except exception.InvalidMigrationState as e: except exception.InvalidMigrationState as e:
raise exc.HTTPBadRequest(explanation=e.format_message()) raise exc.HTTPBadRequest(explanation=e.format_message())
except exception.AbortQueuedLiveMigrationNotYetSupported as e:
raise exc.HTTPConflict(explanation=e.format_message())

View File

@ -106,6 +106,7 @@ BFV_RESERVE_MIN_COMPUTE_VERSION = 17
CINDER_V3_ATTACH_MIN_COMPUTE_VERSION = 24 CINDER_V3_ATTACH_MIN_COMPUTE_VERSION = 24
MIN_COMPUTE_MULTIATTACH = 27 MIN_COMPUTE_MULTIATTACH = 27
MIN_COMPUTE_TRUSTED_CERTS = 31 MIN_COMPUTE_TRUSTED_CERTS = 31
MIN_COMPUTE_ABORT_QUEUED_LIVE_MIGRATION = 34
# FIXME(danms): Keep a global cache of the cells we find the # FIXME(danms): Keep a global cache of the cells we find the
# first time we look. This needs to be refreshed on a timer or # first time we look. This needs to be refreshed on a timer or
@ -4411,12 +4412,15 @@ class API(base.Base):
@check_instance_lock @check_instance_lock
@check_instance_cell @check_instance_cell
@check_instance_state(task_state=[task_states.MIGRATING]) @check_instance_state(task_state=[task_states.MIGRATING])
def live_migrate_abort(self, context, instance, migration_id): def live_migrate_abort(self, context, instance, migration_id,
support_abort_in_queue=False):
"""Abort an in-progress live migration. """Abort an in-progress live migration.
:param context: Security context :param context: Security context
:param instance: The instance that is being migrated :param instance: The instance that is being migrated
:param migration_id: ID of in-progress live migration :param migration_id: ID of in-progress live migration
:param support_abort_in_queue: Flag indicating whether we can support
abort migrations in "queued" or "preparing" status.
""" """
migration = objects.Migration.get_by_id_and_instance(context, migration = objects.Migration.get_by_id_and_instance(context,
@ -4424,7 +4428,30 @@ class API(base.Base):
LOG.debug("Going to cancel live migration %s", LOG.debug("Going to cancel live migration %s",
migration.id, instance=instance) migration.id, instance=instance)
if migration.status != 'running': # If the microversion does not support abort migration in queue,
# we are only be able to abort migrations with `running` status;
# if it is supported, we are able to also abort migrations in
# `queued` and `preparing` status.
allowed_states = ['running']
queued_states = ['queued', 'preparing']
if support_abort_in_queue:
# The user requested a microversion that supports aborting a queued
# or preparing live migration. But we need to check that the
# compute service hosting the instance is new enough to support
# aborting a queued/preparing live migration, so we check the
# service version here.
# TODO(Kevin_Zheng): This service version check can be removed in
# Stein (at the earliest) when the API only supports Rocky or
# newer computes.
if migration.status in queued_states:
service = objects.Service.get_by_compute_host(
context, instance.host)
if service.version < MIN_COMPUTE_ABORT_QUEUED_LIVE_MIGRATION:
raise exception.AbortQueuedLiveMigrationNotYetSupported(
migration_id=migration_id, status=migration.status)
allowed_states.extend(queued_states)
if migration.status not in allowed_states:
raise exception.InvalidMigrationState(migration_id=migration_id, raise exception.InvalidMigrationState(migration_id=migration_id,
instance_uuid=instance.uuid, instance_uuid=instance.uuid,
state=migration.status, state=migration.status,

View File

@ -1162,6 +1162,12 @@ class InvalidMigrationState(Invalid):
"migration is in this state.") "migration is in this state.")
class AbortQueuedLiveMigrationNotYetSupported(NovaException):
msg_fmt = _("Aborting live migration %(migration_id)s with status "
"%(status)s is not yet supported for this instance.")
code = 409
class ConsoleLogOutputException(NovaException): class ConsoleLogOutputException(NovaException):
msg_fmt = _("Console log output could not be retrieved for instance " msg_fmt = _("Console log output could not be retrieved for instance "
"%(instance_id)s. Reason: %(reason)s") "%(instance_id)s. Reason: %(reason)s")

View File

@ -0,0 +1,6 @@
{
"os-migrateLive": {
"host": null,
"block_migration": "auto"
}
}

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from concurrent import futures
import datetime import datetime
import mock import mock
@ -226,3 +227,21 @@ class ServerMigrationsSamplesJsonTestV2_59(
self.fake_migrations[1][ self.fake_migrations[1][
'uuid'] = '22341d4b-346a-40d0-83c6-5f4f6892b650' 'uuid'] = '22341d4b-346a-40d0-83c6-5f4f6892b650'
super(ServerMigrationsSamplesJsonTestV2_59, self).setUp() super(ServerMigrationsSamplesJsonTestV2_59, self).setUp()
class ServerMigrationsSampleJsonTestV2_65(ServerMigrationsSampleJsonTestV2_24):
ADMIN_API = True
microversion = '2.65'
scenarios = [('v2_65', {'api_major_version': 'v2.1'})]
@mock.patch.object(conductor_manager.ComputeTaskManager, '_live_migrate')
def test_live_migrate_abort_migration_queued(self, _live_migrate):
self.migration.status = 'queued'
self.migration.save()
self._do_post('servers/%s/action' % self.uuid, 'live-migrate-server',
{'hostname': self.compute.host})
self.compute._waiting_live_migrations[self.uuid] = (
self.migration, futures.Future())
uri = 'servers/%s/migrations/%s' % (self.uuid, self.migration.id)
response = self._do_delete(uri)
self.assertEqual(202, response.status_code)

View File

@ -17,6 +17,7 @@ import copy
import datetime import datetime
import mock import mock
import six
import webob import webob
from nova.api.openstack.compute import server_migrations from nova.api.openstack.compute import server_migrations
@ -273,7 +274,8 @@ class ServerMigrationsTestsV224(ServerMigrationsTestsV21):
self.controller.delete(self.req, 'server-id', 'migration-id') self.controller.delete(self.req, 'server-id', 'migration-id')
mock_abort.assert_called_once_with(self.context, mock_abort.assert_called_once_with(self.context,
mock_get(), mock_get(),
'migration-id') 'migration-id',
support_abort_in_queue=False)
_do_test() _do_test()
def _test_cancel_live_migration_failed(self, fake_exc, expected_exc): def _test_cancel_live_migration_failed(self, fake_exc, expected_exc):
@ -318,6 +320,39 @@ class ServerMigrationsTestsV224(ServerMigrationsTestsV21):
'migration-id') 'migration-id')
class ServerMigrationsTestsV265(ServerMigrationsTestsV224):
wsgi_api_version = '2.65'
def test_cancel_live_migration_succeeded(self):
@mock.patch.object(self.compute_api, 'live_migrate_abort')
@mock.patch.object(self.compute_api, 'get')
def _do_test(mock_get, mock_abort):
self.controller.delete(self.req, 'server-id', 1)
mock_abort.assert_called_once_with(self.context,
mock_get.return_value, 1,
support_abort_in_queue=True)
_do_test()
def test_cancel_live_migration_in_queue_not_yet_available(self):
exc = exception.AbortQueuedLiveMigrationNotYetSupported(
migration_id=1, status='queued')
@mock.patch.object(self.compute_api, 'live_migrate_abort',
side_effect=exc)
@mock.patch.object(self.compute_api, 'get')
def _do_test(mock_get, mock_abort):
error = self.assertRaises(webob.exc.HTTPConflict,
self.controller.delete,
self.req, 'server-id', 1)
self.assertIn("Aborting live migration 1 with status queued is "
"not yet supported for this instance.",
six.text_type(error))
mock_abort.assert_called_once_with(self.context,
mock_get.return_value, 1,
support_abort_in_queue=True)
_do_test()
class ServerMigrationsPolicyEnforcementV21(test.NoDBTestCase): class ServerMigrationsPolicyEnforcementV21(test.NoDBTestCase):
wsgi_api_version = '2.22' wsgi_api_version = '2.22'

View File

@ -5364,6 +5364,65 @@ class _ComputeAPIUnitTestMixIn(object):
instance_actions.LIVE_MIGRATION_CANCEL) instance_actions.LIVE_MIGRATION_CANCEL)
mock_lm_abort.called_once_with(self.context, instance, migration.id) mock_lm_abort.called_once_with(self.context, instance, migration.id)
@mock.patch('nova.compute.api.API._record_action_start')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'live_migration_abort')
@mock.patch.object(objects.Migration, 'get_by_id_and_instance')
@mock.patch.object(objects.Service, 'get_by_compute_host')
def test_live_migrate_abort_in_queue_succeeded(self,
mock_get_service,
mock_get_migration,
mock_lm_abort,
mock_rec_action):
service_obj = objects.Service()
service_obj.version = (
compute_api.MIN_COMPUTE_ABORT_QUEUED_LIVE_MIGRATION)
mock_get_service.return_value = service_obj
instance = self._create_instance_obj()
instance.task_state = task_states.MIGRATING
for migration_status in ('queued', 'preparing'):
migration = self._get_migration(
21, migration_status, 'live-migration')
mock_get_migration.return_value = migration
self.compute_api.live_migrate_abort(self.context,
instance,
migration.id,
support_abort_in_queue=True)
mock_rec_action.assert_called_once_with(
self.context, instance, instance_actions.LIVE_MIGRATION_CANCEL)
mock_lm_abort.called_once_with(self.context, instance, migration)
mock_get_migration.reset_mock()
mock_rec_action.reset_mock()
mock_lm_abort.reset_mock()
@mock.patch.object(objects.Migration, 'get_by_id_and_instance')
def test_live_migration_abort_in_queue_old_microversion_fails(
self, mock_get_migration):
instance = self._create_instance_obj()
instance.task_state = task_states.MIGRATING
migration = self._get_migration(21, 'queued', 'live-migration')
mock_get_migration.return_value = migration
self.assertRaises(exception.InvalidMigrationState,
self.compute_api.live_migrate_abort, self.context,
instance, migration.id,
support_abort_in_queue=False)
@mock.patch.object(objects.Migration, 'get_by_id_and_instance')
@mock.patch.object(objects.Service, 'get_by_compute_host')
def test_live_migration_abort_in_queue_old_compute_conflict(
self, mock_get_service, mock_get_migration):
service_obj = objects.Service()
service_obj.version = (
compute_api.MIN_COMPUTE_ABORT_QUEUED_LIVE_MIGRATION - 1)
mock_get_service.return_value = service_obj
instance = self._create_instance_obj()
instance.task_state = task_states.MIGRATING
migration = self._get_migration(21, 'queued', 'live-migration')
mock_get_migration.return_value = migration
self.assertRaises(exception.AbortQueuedLiveMigrationNotYetSupported,
self.compute_api.live_migrate_abort, self.context,
instance, migration.id,
support_abort_in_queue=True)
@mock.patch.object(objects.Migration, 'get_by_id_and_instance') @mock.patch.object(objects.Migration, 'get_by_id_and_instance')
def test_live_migration_abort_wrong_migration_status(self, def test_live_migration_abort_wrong_migration_status(self,
mock_get_migration): mock_get_migration):

View File

@ -0,0 +1,5 @@
---
features:
- The support to abort live migrations with ``queued`` and ``preparing``
status using ``DELETE /servers/{server_id}/migrations/{migration_id}``
API has been added in microversion 2.65.