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:
parent
e8192177e8
commit
4cae503767
@ -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**
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"os-migrateLive": {
|
||||||
|
"host": null,
|
||||||
|
"block_migration": "auto"
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -836,3 +836,9 @@ in server group APIs:
|
|||||||
* 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}``.
|
||||||
|
@ -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())
|
||||||
|
@ -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,
|
||||||
|
@ -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")
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"os-migrateLive": {
|
||||||
|
"host": null,
|
||||||
|
"block_migration": "auto"
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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'
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user