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:: 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
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
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**

View File

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

View File

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

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.64",
"version": "2.65",
"min_version": "2.1",
"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
GET, POST /os-server-groups APIs and GET
/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
@ -164,7 +166,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.64"
_MAX_API_VERSION = "2.65"
DEFAULT_API_VERSION = _MIN_API_VERSION
# 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.
* The ``policies`` and ``metadata`` fields have been removed from the response
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.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)
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:
common.raise_http_conflict_for_instance_invalid_state(
state_error, "abort live migration", server_id)
@ -156,3 +160,5 @@ class ServerMigrationsController(wsgi.Controller):
raise exc.HTTPNotFound(explanation=e.format_message())
except exception.InvalidMigrationState as e:
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
MIN_COMPUTE_MULTIATTACH = 27
MIN_COMPUTE_TRUSTED_CERTS = 31
MIN_COMPUTE_ABORT_QUEUED_LIVE_MIGRATION = 34
# 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
@ -4411,12 +4412,15 @@ class API(base.Base):
@check_instance_lock
@check_instance_cell
@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.
:param context: Security context
:param instance: The instance that is being migrated
: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,
@ -4424,7 +4428,30 @@ class API(base.Base):
LOG.debug("Going to cancel live migration %s",
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,
instance_uuid=instance.uuid,
state=migration.status,

View File

@ -1162,6 +1162,12 @@ class InvalidMigrationState(Invalid):
"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):
msg_fmt = _("Console log output could not be retrieved for instance "
"%(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
# under the License.
from concurrent import futures
import datetime
import mock
@ -226,3 +227,21 @@ class ServerMigrationsSamplesJsonTestV2_59(
self.fake_migrations[1][
'uuid'] = '22341d4b-346a-40d0-83c6-5f4f6892b650'
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 mock
import six
import webob
from nova.api.openstack.compute import server_migrations
@ -273,7 +274,8 @@ class ServerMigrationsTestsV224(ServerMigrationsTestsV21):
self.controller.delete(self.req, 'server-id', 'migration-id')
mock_abort.assert_called_once_with(self.context,
mock_get(),
'migration-id')
'migration-id',
support_abort_in_queue=False)
_do_test()
def _test_cancel_live_migration_failed(self, fake_exc, expected_exc):
@ -318,6 +320,39 @@ class ServerMigrationsTestsV224(ServerMigrationsTestsV21):
'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):
wsgi_api_version = '2.22'

View File

@ -5364,6 +5364,65 @@ class _ComputeAPIUnitTestMixIn(object):
instance_actions.LIVE_MIGRATION_CANCEL)
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')
def test_live_migration_abort_wrong_migration_status(self,
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.