Add new APIs and deprecate old API for migrations

This patch does two things:
1. Add two APIs /servers/migrations:index/show for server migrations.
Two new novaclient commands server-migration-list and
server-migration-show will also be added.
ref: I071198fa9ba0699383bdebf4fab54714a435e6c3

2. Add ref link for /os-migrations
The old top-level resource `/os-migrations` won't be extended anymore.
It is deprecated.
Adding migration_type for it, also add ref link to
/servers/{uuid}/migrations/{id} for it when the migration is an
in progress migration.

Partially implements blueprint live-migration-progress-report

Change-Id: Ia92ecbe3c99082e3a34adf4fd29041b1a95ef21e
Co-authored-by: ShaoHe Feng <shaohe.feng@intel.com>
This commit is contained in:
ShaoHe Feng 2015-12-24 19:51:43 +08:00
parent 058e21ec98
commit 98e4a64ad3
29 changed files with 1040 additions and 42 deletions

View File

@ -0,0 +1,74 @@
{
"migrations": [
{
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"instance_uuid": "8600d31b-d1a1-4632-b2ff-45c2be1a70ff",
"links": [
{
"href": "http://openstack.example.com/v2.1/openstack/servers/8600d31b-d1a1-4632-b2ff-45c2be1a70ff/migrations/1",
"rel": "self"
},
{
"href": "http://openstack.example.com/openstack/servers/8600d31b-d1a1-4632-b2ff-45c2be1a70ff/migrations/1",
"rel": "bookmark"
}
],
"new_instance_type_id": 2,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"migration_type": "live-migration",
"updated_at": "2016-01-29T13:42:02.000000"
},
{
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 2,
"instance_uuid": "8600d31b-d1a1-4632-b2ff-45c2be1a70ff",
"new_instance_type_id": 2,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"status": "error",
"migration_type": "live-migration",
"updated_at": "2016-01-29T13:42:02.000000"
},
{
"created_at": "2016-01-22T13:42:02.000000",
"dest_compute": "compute20",
"dest_host": "5.6.7.8",
"dest_node": "node20",
"id": 3,
"instance_uuid": "9128d044-7b61-403e-b766-7547076ff6c1",
"new_instance_type_id": 6,
"old_instance_type_id": 5,
"source_compute": "compute10",
"source_node": "node10",
"status": "error",
"migration_type": "resize",
"updated_at": "2016-01-22T13:42:02.000000"
},
{
"created_at": "2016-01-22T13:42:02.000000",
"dest_compute": "compute20",
"dest_host": "5.6.7.8",
"dest_node": "node20",
"id": 4,
"instance_uuid": "9128d044-7b61-403e-b766-7547076ff6c1",
"new_instance_type_id": 6,
"old_instance_type_id": 5,
"source_compute": "compute10",
"source_node": "node10",
"status": "migrating",
"migration_type": "resize",
"updated_at": "2016-01-22T13:42:02.000000"
}
]
}

View File

@ -0,0 +1,20 @@
{
"migration": {
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}
}

View File

@ -0,0 +1,22 @@
{
"migrations": [
{
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}
]
}

View File

@ -19,7 +19,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.22", "version": "2.23",
"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.22", "version": "2.23",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@ -272,6 +272,8 @@
"os_compute_api:servers:trigger_crash_dump": "rule:admin_or_owner", "os_compute_api:servers:trigger_crash_dump": "rule:admin_or_owner",
"os_compute_api:servers:migrations:force_complete": "rule:admin_api", "os_compute_api:servers:migrations:force_complete": "rule:admin_api",
"os_compute_api:servers:discoverable": "@", "os_compute_api:servers:discoverable": "@",
"os_compute_api:servers:migrations:index": "rule:admin_api",
"os_compute_api:servers:migrations:show": "rule:admin_api",
"os_compute_api:os-access-ips:discoverable": "@", "os_compute_api:os-access-ips:discoverable": "@",
"os_compute_api:os-access-ips": "rule:admin_or_owner", "os_compute_api:os-access-ips": "rule:admin_or_owner",
"os_compute_api:os-admin-actions": "rule:admin_api", "os_compute_api:os-admin-actions": "rule:admin_api",

View File

@ -65,6 +65,9 @@ REST_API_VERSION_HISTORY = """REST API Version History:
and shelved_offloaded state and shelved_offloaded state
* 2.21 - Make os-instance-actions read deleted instances * 2.21 - Make os-instance-actions read deleted instances
* 2.22 - Add API to force live migration to complete * 2.22 - Add API to force live migration to complete
* 2.23 - Add index/show API for server migrations.
Also add migration_type for /os-migrations and add ref link for it
when the migration is an in progress live migration.
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
@ -73,7 +76,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.22" _MAX_API_VERSION = "2.23"
DEFAULT_API_VERSION = _MIN_API_VERSION DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -10,6 +10,8 @@
# 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 nova.api.openstack import api_version_request
from nova.api.openstack import common
from nova.api.openstack import extensions from nova.api.openstack import extensions
from nova.api.openstack import wsgi from nova.api.openstack import wsgi
from nova import compute from nova import compute
@ -23,42 +25,64 @@ def authorize(context, action_name):
extensions.os_compute_authorizer(ALIAS)(context, action=action_name) extensions.os_compute_authorizer(ALIAS)(context, action=action_name)
def output(migrations_obj):
"""Returns the desired output of the API from an object.
From a MigrationsList's object this method returns a list of
primitive objects with the only necessary fields.
"""
detail_keys = ['memory_total', 'memory_processed', 'memory_remaining',
'disk_total', 'disk_processed', 'disk_remaining']
# Note(Shaohe Feng): We need to leverage the oslo.versionedobjects.
# Then we can pass the target version to it's obj_to_primitive.
objects = obj_base.obj_to_primitive(migrations_obj)
objects = [x for x in objects if not x['hidden']]
for obj in objects:
del obj['deleted']
del obj['deleted_at']
del obj['migration_type']
del obj['hidden']
if 'memory_total' in obj:
for key in detail_keys:
del obj[key]
return objects
class MigrationsController(wsgi.Controller): class MigrationsController(wsgi.Controller):
"""Controller for accessing migrations in OpenStack API.""" """Controller for accessing migrations in OpenStack API."""
_view_builder_class = common.ViewBuilder
_collection_name = "servers/%s/migrations"
def __init__(self): def __init__(self):
super(MigrationsController, self).__init__()
self.compute_api = compute.API() self.compute_api = compute.API()
def _output(self, req, migrations_obj, add_link=False):
"""Returns the desired output of the API from an object.
From a MigrationsList's object this method returns a list of
primitive objects with the only necessary fields.
"""
detail_keys = ['memory_total', 'memory_processed', 'memory_remaining',
'disk_total', 'disk_processed', 'disk_remaining']
# TODO(Shaohe Feng) we should share the in-progress list.
live_migration_in_progress = ['queued', 'preparing',
'running', 'post-migrating']
# Note(Shaohe Feng): We need to leverage the oslo.versionedobjects.
# Then we can pass the target version to it's obj_to_primitive.
objects = obj_base.obj_to_primitive(migrations_obj)
objects = [x for x in objects if not x['hidden']]
for obj in objects:
del obj['deleted']
del obj['deleted_at']
del obj['hidden']
if 'memory_total' in obj:
for key in detail_keys:
del obj[key]
# NOTE(Shaohe Feng) above version 2.23, add migration_type for all
# kinds of migration, but we only add links just for in-progress
# live-migration.
if add_link and obj['migration_type'] == "live-migration" and (
obj["status"] in live_migration_in_progress):
obj["links"] = self._view_builder._get_links(
req, obj["id"],
self._collection_name % obj['instance_uuid'])
elif add_link is False:
del obj['migration_type']
return objects
@extensions.expected_errors(()) @extensions.expected_errors(())
def index(self, req): def index(self, req):
"""Return all migrations in progress.""" """Return all migrations in progress."""
context = req.environ['nova.context'] context = req.environ['nova.context']
authorize(context, "index") authorize(context, "index")
migrations = self.compute_api.get_migrations(context, req.GET) migrations = self.compute_api.get_migrations(context, req.GET)
return {'migrations': output(migrations)}
if api_version_request.is_supported(req, min_version='2.23'):
return {'migrations': self._output(req, migrations, True)}
return {'migrations': self._output(req, migrations)}
class Migrations(extensions.V21APIExtensionBase): class Migrations(extensions.V21APIExtensionBase):

View File

@ -22,11 +22,39 @@ from nova.api.openstack import wsgi
from nova.api import validation from nova.api import validation
from nova import compute from nova import compute
from nova import exception from nova import exception
from nova.i18n import _
ALIAS = 'servers:migrations' ALIAS = 'servers:migrations'
authorize = extensions.os_compute_authorizer(ALIAS) authorize = extensions.os_compute_authorizer(ALIAS)
def output(migration):
"""Returns the desired output of the API from an object.
From a Migrations's object this method returns the primitive
object with the only necessary and expected fields.
"""
return {
"created_at": migration.created_at,
"dest_compute": migration.dest_compute,
"dest_host": migration.dest_host,
"dest_node": migration.dest_node,
"disk_processed_bytes": migration.disk_processed,
"disk_remaining_bytes": migration.disk_remaining,
"disk_total_bytes": migration.disk_total,
"id": migration.id,
"memory_processed_bytes": migration.memory_processed,
"memory_remaining_bytes": migration.memory_remaining,
"memory_total_bytes": migration.memory_total,
"server_uuid": migration.instance_uuid,
"source_compute": migration.source_compute,
"source_node": migration.source_node,
"status": migration.status,
"updated_at": migration.updated_at
}
class ServerMigrationsController(wsgi.Controller): class ServerMigrationsController(wsgi.Controller):
"""The server migrations API controller for the OpenStack API.""" """The server migrations API controller for the OpenStack API."""
@ -58,6 +86,55 @@ class ServerMigrationsController(wsgi.Controller):
common.raise_http_conflict_for_instance_invalid_state( common.raise_http_conflict_for_instance_invalid_state(
state_error, 'force_complete', server_id) state_error, 'force_complete', server_id)
@wsgi.Controller.api_version("2.23")
@extensions.expected_errors(404)
def index(self, req, server_id):
"""Return all migrations of an instance in progress."""
context = req.environ['nova.context']
authorize(context, action="index")
# NOTE(Shaohe Feng) just check the instance is available. To keep
# consistency with other API, check it before get migrations.
common.get_instance(self.compute_api, context, server_id)
migrations = self.compute_api.get_migrations_in_progress_by_instance(
context, server_id, 'live-migration')
return {'migrations': [output(migration) for migration in migrations]}
@wsgi.Controller.api_version("2.23")
@extensions.expected_errors(404)
def show(self, req, server_id, id):
"""Return the migration of an instance in progress by id."""
context = req.environ['nova.context']
authorize(context, action="show")
# NOTE(Shaohe Feng) just check the instance is available. To keep
# consistency with other API, check it before get migrations.
common.get_instance(self.compute_api, context, server_id)
try:
migration = self.compute_api.get_migration_by_id_and_instance(
context, id, server_id)
except exception.MigrationNotFoundForInstance:
msg = _("In-progress live migration %(id)s is not found for"
" server %(uuid)s.") % {"id": id, "uuid": server_id}
raise exc.HTTPNotFound(explanation=msg)
if migration.get("migration_type") != "live-migration":
msg = _("Migration %(id)s for server %(uuid)s is not"
" live-migration.") % {"id": id, "uuid": server_id}
raise exc.HTTPNotFound(explanation=msg)
# TODO(Shaohe Feng) we should share the in-progress list.
in_progress = ['queued', 'preparing', 'running', 'post-migrating']
if migration.get("status") not in in_progress:
msg = _("Live migration %(id)s for server %(uuid)s is not in"
" progress.") % {"id": id, "uuid": server_id}
raise exc.HTTPNotFound(explanation=msg)
return {'migration': output(migration)}
class ServerMigrations(extensions.V21APIExtensionBase): class ServerMigrations(extensions.V21APIExtensionBase):
"""Server Migrations API.""" """Server Migrations API."""

View File

@ -195,3 +195,13 @@ user documentation.
{ {
"force_complete": null "force_complete": null
} }
2.23
----
From this version of the API users can get the migration summary list by
index API or the information of a specific migration by get API.
And the old top-level resource `/os-migrations` won't be extended anymore.
Add migration_type for old /os-migrations API, also add ref link to the
/servers/{uuid}/migrations/{id} for it when the migration is an in-progress
live-migration.

View File

@ -3395,6 +3395,18 @@ class API(base.Base):
"""Get all migrations for the given filters.""" """Get all migrations for the given filters."""
return objects.MigrationList.get_by_filters(context, filters) return objects.MigrationList.get_by_filters(context, filters)
def get_migrations_in_progress_by_instance(self, context, instance_uuid,
migration_type=None):
"""Get all migrations of an instance in progress."""
return objects.MigrationList.get_in_progress_by_instance(
context, instance_uuid, migration_type)
def get_migration_by_id_and_instance(self, context,
migration_id, instance_uuid):
"""Get the migration of an instance by id."""
return objects.Migration.get_by_id_and_instance(
context, migration_id, instance_uuid)
@wrap_check_policy @wrap_check_policy
def volume_snapshot_create(self, context, volume_id, create_info): def volume_snapshot_create(self, context, volume_id, create_info):
bdm = objects.BlockDeviceMapping.get_by_volume( bdm = objects.BlockDeviceMapping.get_by_volume(

View File

@ -511,6 +511,13 @@ def migration_get_all_by_filters(context, filters):
return IMPL.migration_get_all_by_filters(context, filters) return IMPL.migration_get_all_by_filters(context, filters)
def migration_get_in_progress_by_instance(context, instance_uuid,
migration_type=None):
"""Finds all migrations of an instance in progress."""
return IMPL.migration_get_in_progress_by_instance(context, instance_uuid,
migration_type)
#################### ####################

View File

@ -4547,6 +4547,23 @@ def migration_get_in_progress_by_host_and_node(context, host, node):
all() all()
@main_context_manager.reader
def migration_get_in_progress_by_instance(context, instance_uuid,
migration_type=None):
# TODO(Shaohe Feng) we should share the in-progress list.
# TODO(Shaohe Feng) will also summarize all status to a new
# MigrationStatus class.
query = model_query(context, models.Migration).\
filter_by(instance_uuid=instance_uuid).\
filter(models.Migration.status.in_(['queued', 'preparing',
'running',
'post-migrating']))
if migration_type:
query = query.filter(models.Migration.migration_type == migration_type)
return query.all()
@main_context_manager.reader @main_context_manager.reader
def migration_get_all_by_filters(context, filters): def migration_get_all_by_filters(context, filters):
query = model_query(context, models.Migration) query = model_query(context, models.Migration)

View File

@ -157,7 +157,9 @@ class MigrationList(base.ObjectListBase, base.NovaObject):
# Migration <= 1.1 # Migration <= 1.1
# Version 1.1: Added use_slave to get_unconfirmed_by_dest_compute # Version 1.1: Added use_slave to get_unconfirmed_by_dest_compute
# Version 1.2: Migration version 1.2 # Version 1.2: Migration version 1.2
VERSION = '1.2' # Version 1.3: Added a new function to get in progress migrations
# for an instance.
VERSION = '1.3'
fields = { fields = {
'objects': fields.ListOfObjectsField('Migration'), 'objects': fields.ListOfObjectsField('Migration'),
@ -190,3 +192,11 @@ class MigrationList(base.ObjectListBase, base.NovaObject):
db_migrations = db.migration_get_all_by_filters(context, filters) db_migrations = db.migration_get_all_by_filters(context, filters)
return base.obj_make_list(context, cls(context), objects.Migration, return base.obj_make_list(context, cls(context), objects.Migration,
db_migrations) db_migrations)
@base.remotable_classmethod
def get_in_progress_by_instance(cls, context, instance_uuid,
migration_type=None):
db_migrations = db.migration_get_in_progress_by_instance(
context, instance_uuid, migration_type)
return base.obj_make_list(context, cls(context), objects.Migration,
db_migrations)

View File

@ -0,0 +1,74 @@
{
"migrations": [
{
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"instance_uuid": "%(instance_1)s",
"links": [
{
"href": "%(host)s/v2.1/openstack/servers/%(instance_1)s/migrations/1",
"rel": "self"
},
{
"href": "%(host)s/openstack/servers/%(instance_1)s/migrations/1",
"rel": "bookmark"
}
],
"new_instance_type_id": 2,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"migration_type": "live-migration",
"status": "running",
"updated_at": "2016-01-29T13:42:02.000000"
},
{
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 2,
"instance_uuid": "%(instance_1)s",
"new_instance_type_id": 2,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"migration_type": "live-migration",
"status": "error",
"updated_at": "2016-01-29T13:42:02.000000"
},
{
"created_at": "2016-01-22T13:42:02.000000",
"dest_compute": "compute20",
"dest_host": "5.6.7.8",
"dest_node": "node20",
"id": 3,
"instance_uuid": "%(instance_2)s",
"new_instance_type_id": 6,
"old_instance_type_id": 5,
"source_compute": "compute10",
"source_node": "node10",
"migration_type": "resize",
"status": "error",
"updated_at": "2016-01-22T13:42:02.000000"
},
{
"created_at": "2016-01-22T13:42:02.000000",
"dest_compute": "compute20",
"dest_host": "5.6.7.8",
"dest_node": "node20",
"id": 4,
"instance_uuid": "%(instance_2)s",
"new_instance_type_id": 6,
"old_instance_type_id": 5,
"source_compute": "compute10",
"source_node": "node10",
"migration_type": "resize",
"status": "migrating",
"updated_at": "2016-01-22T13:42:02.000000"
}
]
}

View File

@ -0,0 +1,20 @@
{
"migration": {
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "%(server_uuid)s",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}
}

View File

@ -0,0 +1,22 @@
{
"migrations": [
{
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "%(server_uuid_1)s",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}
]
}

View File

@ -17,6 +17,8 @@ import datetime
from oslo_config import cfg from oslo_config import cfg
from nova import context
from nova import objects
from nova.tests.functional.api_sample_tests import api_sample_base from nova.tests.functional.api_sample_tests import api_sample_base
CONF = cfg.CONF CONF = cfg.CONF
@ -24,6 +26,12 @@ CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.legacy_v2.extensions') 'nova.api.openstack.compute.legacy_v2.extensions')
# NOTE(ShaoHe Feng) here I can not use uuidsentinel, it generate a random
# UUID. The uuid in doc/api_samples files is fixed.
INSTANCE_UUID_1 = "8600d31b-d1a1-4632-b2ff-45c2be1a70ff"
INSTANCE_UUID_2 = "9128d044-7b61-403e-b766-7547076ff6c1"
class MigrationsSamplesJsonTest(api_sample_base.ApiSampleTestBaseV21): class MigrationsSamplesJsonTest(api_sample_base.ApiSampleTestBaseV21):
ADMIN_API = True ADMIN_API = True
extension_name = "os-migrations" extension_name = "os-migrations"
@ -86,3 +94,101 @@ class MigrationsSamplesJsonTest(api_sample_base.ApiSampleTestBaseV21):
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
self._verify_response('migrations-get', {}, response, 200) self._verify_response('migrations-get', {}, response, 200)
class MigrationsSamplesJsonTestV2_23(api_sample_base.ApiSampleTestBaseV21):
ADMIN_API = True
extension_name = "os-migrations"
microversion = '2.23'
scenarios = [('v2_23', {'api_major_version': 'v2.1'})]
fake_migrations = [
# in-progress live-migration.
{
'source_node': 'node1',
'dest_node': 'node2',
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'running',
'instance_uuid': INSTANCE_UUID_1,
'old_instance_type_id': 1,
'new_instance_type_id': 2,
'migration_type': 'live-migration',
'hidden': False,
'created_at': datetime.datetime(2016, 0o1, 29, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 29, 13, 42, 2),
'deleted_at': None,
'deleted': False
},
# non in-progress live-migration.
{
'source_node': 'node1',
'dest_node': 'node2',
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'error',
'instance_uuid': INSTANCE_UUID_1,
'old_instance_type_id': 1,
'new_instance_type_id': 2,
'migration_type': 'live-migration',
'hidden': False,
'created_at': datetime.datetime(2016, 0o1, 29, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 29, 13, 42, 2),
'deleted_at': None,
'deleted': False
},
# non in-progress resize.
{
'source_node': 'node10',
'dest_node': 'node20',
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'error',
'instance_uuid': INSTANCE_UUID_2,
'old_instance_type_id': 5,
'new_instance_type_id': 6,
'migration_type': 'resize',
'hidden': False,
'created_at': datetime.datetime(2016, 0o1, 22, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 22, 13, 42, 2),
'deleted_at': None,
'deleted': False
},
# in-progress resize.
{
'source_node': 'node10',
'dest_node': 'node20',
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'migrating',
'instance_uuid': INSTANCE_UUID_2,
'old_instance_type_id': 5,
'new_instance_type_id': 6,
'migration_type': 'resize',
'hidden': False,
'created_at': datetime.datetime(2016, 0o1, 22, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 22, 13, 42, 2),
'deleted_at': None,
'deleted': False
}
]
def setUp(self):
super(MigrationsSamplesJsonTestV2_23, self).setUp()
fake_context = context.RequestContext('fake', 'fake')
for mig in self.fake_migrations:
mig_obj = objects.Migration(context=fake_context, **mig)
mig_obj.create()
def test_get_migrations_v2_23(self):
response = self._do_get('os-migrations')
self.assertEqual(200, response.status_code)
self._verify_response(
'migrations-get',
{"instance_1": INSTANCE_UUID_1, "instance_2": INSTANCE_UUID_2},
response, 200)

View File

@ -13,12 +13,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import datetime
import mock import mock
from nova.conductor import manager as conductor_manager from nova.conductor import manager as conductor_manager
from nova import context
from nova import db from nova import db
from nova import objects from nova import objects
from nova.tests.functional.api_sample_tests import test_servers from nova.tests.functional.api_sample_tests import test_servers
from nova.tests.unit import fake_instance
class ServerMigrationsSampleJsonTest(test_servers.ServersSampleBase): class ServerMigrationsSampleJsonTest(test_servers.ServersSampleBase):
@ -50,3 +53,108 @@ class ServerMigrationsSampleJsonTest(test_servers.ServersSampleBase):
response = self._do_post('servers/%s/migrations/%s/action' response = self._do_post('servers/%s/migrations/%s/action'
% (self.uuid, '3'), 'force_complete', {}) % (self.uuid, '3'), 'force_complete', {})
self.assertEqual(202, response.status_code) self.assertEqual(202, response.status_code)
def test_get_migration(self):
response = self._do_get('servers/fake_id/migrations/1234')
self.assertEqual(404, response.status_code)
def test_list_migrations(self):
response = self._do_get('servers/fake_id/migrations')
self.assertEqual(404, response.status_code)
class ServerMigrationsSamplesJsonTestV2_23(test_servers.ServersSampleBase):
ADMIN_API = True
extension_name = "server-migrations"
microversion = '2.23'
scenarios = [('v2_23', {'api_major_version': 'v2.1'})]
UUID_1 = '4cfba335-03d8-49b2-8c52-e69043d1e8fe'
UUID_2 = '058fc419-a8a8-4e08-b62c-a9841ef9cd3f'
fake_migrations = [
{
'source_node': 'node1',
'dest_node': 'node2',
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'running',
'instance_uuid': UUID_1,
'migration_type': 'live-migration',
'hidden': False,
'memory_total': 123456,
'memory_processed': 12345,
'memory_remaining': 120000,
'disk_total': 234567,
'disk_processed': 23456,
'disk_remaining': 230000,
'created_at': datetime.datetime(2016, 0o1, 29, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 29, 13, 42, 2),
'deleted_at': None,
'deleted': False
},
{
'source_node': 'node10',
'dest_node': 'node20',
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'migrating',
'instance_uuid': UUID_2,
'migration_type': 'resize',
'hidden': False,
'memory_total': 456789,
'memory_processed': 56789,
'memory_remaining': 45000,
'disk_total': 96789,
'disk_processed': 6789,
'disk_remaining': 96000,
'created_at': datetime.datetime(2016, 0o1, 22, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 22, 13, 42, 2),
'deleted_at': None,
'deleted': False
}
]
def setUp(self):
super(ServerMigrationsSamplesJsonTestV2_23, self).setUp()
fake_context = context.RequestContext('fake', 'fake')
self.mig1 = objects.Migration(
context=fake_context, **self.fake_migrations[0])
self.mig1.create()
self.mig2 = objects.Migration(
context=fake_context, **self.fake_migrations[1])
self.mig2.create()
fake_ins = fake_instance.fake_db_instance(uuid=self.UUID_1)
fake_ins.pop("pci_devices")
fake_ins.pop("security_groups")
fake_ins.pop("services")
fake_ins.pop("tags")
fake_ins.pop("info_cache")
fake_ins.pop("id")
self.instance = objects.Instance(
context=fake_context,
**fake_ins)
self.instance.create()
def test_get_migration(self):
response = self._do_get('servers/%s/migrations/%s' %
(self.fake_migrations[0]["instance_uuid"],
self.mig1.id))
self.assertEqual(200, response.status_code)
self._verify_response('migrations-get',
{"server_uuid": self.UUID_1},
response, 200)
def test_list_migrations(self):
response = self._do_get('servers/%s/migrations' %
self.fake_migrations[0]["instance_uuid"])
self.assertEqual(200, response.status_code)
self._verify_response('migrations-index',
{"server_uuid_1": self.UUID_1},
response, 200)

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
import datetime import datetime
import mock
from oslotest import moxstubout from oslotest import moxstubout
@ -25,21 +26,23 @@ from nova import objects
from nova.objects import base from nova.objects import base
from nova import test from nova import test
from nova.tests.unit.api.openstack import fakes from nova.tests.unit.api.openstack import fakes
from nova.tests import uuidsentinel as uuids
fake_migrations = [ fake_migrations = [
# in-progress live migration
{ {
'id': 1234, 'id': 1,
'source_node': 'node1', 'source_node': 'node1',
'dest_node': 'node2', 'dest_node': 'node2',
'source_compute': 'compute1', 'source_compute': 'compute1',
'dest_compute': 'compute2', 'dest_compute': 'compute2',
'dest_host': '1.2.3.4', 'dest_host': '1.2.3.4',
'status': 'Done', 'status': 'running',
'instance_uuid': 'instance_id_123', 'instance_uuid': uuids.instance1,
'old_instance_type_id': 1, 'old_instance_type_id': 1,
'new_instance_type_id': 2, 'new_instance_type_id': 2,
'migration_type': 'resize', 'migration_type': 'live-migration',
'hidden': False, 'hidden': False,
'memory_total': 123456, 'memory_total': 123456,
'memory_processed': 12345, 'memory_processed': 12345,
@ -52,15 +55,66 @@ fake_migrations = [
'deleted_at': None, 'deleted_at': None,
'deleted': False 'deleted': False
}, },
# non in-progress live migration
{ {
'id': 5678, 'id': 2,
'source_node': 'node1',
'dest_node': 'node2',
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'error',
'instance_uuid': uuids.instance1,
'old_instance_type_id': 1,
'new_instance_type_id': 2,
'migration_type': 'live-migration',
'hidden': False,
'memory_total': 123456,
'memory_processed': 12345,
'memory_remaining': 120000,
'disk_total': 234567,
'disk_processed': 23456,
'disk_remaining': 230000,
'created_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
'deleted_at': None,
'deleted': False
},
# in-progress resize
{
'id': 4,
'source_node': 'node10', 'source_node': 'node10',
'dest_node': 'node20', 'dest_node': 'node20',
'source_compute': 'compute10', 'source_compute': 'compute10',
'dest_compute': 'compute20', 'dest_compute': 'compute20',
'dest_host': '5.6.7.8', 'dest_host': '5.6.7.8',
'status': 'Done', 'status': 'migrating',
'instance_uuid': 'instance_id_456', 'instance_uuid': uuids.instance2,
'old_instance_type_id': 5,
'new_instance_type_id': 6,
'migration_type': 'resize',
'hidden': False,
'memory_total': 456789,
'memory_processed': 56789,
'memory_remaining': 45000,
'disk_total': 96789,
'disk_processed': 6789,
'disk_remaining': 96000,
'created_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
'updated_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
'deleted_at': None,
'deleted': False
},
# non in-progress resize
{
'id': 5,
'source_node': 'node10',
'dest_node': 'node20',
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'error',
'instance_uuid': uuids.instance2,
'old_instance_type_id': 5, 'old_instance_type_id': 5,
'new_instance_type_id': 6, 'new_instance_type_id': 6,
'migration_type': 'resize', 'migration_type': 'resize',
@ -94,23 +148,26 @@ class FakeRequest(object):
class MigrationsTestCaseV21(test.NoDBTestCase): class MigrationsTestCaseV21(test.NoDBTestCase):
migrations = migrations_v21 migrations = migrations_v21
def _migrations_output(self):
return self.controller._output(self.req, migrations_obj)
def setUp(self): def setUp(self):
"""Run before each test.""" """Run before each test."""
super(MigrationsTestCaseV21, self).setUp() super(MigrationsTestCaseV21, self).setUp()
self.controller = self.migrations.MigrationsController() self.controller = self.migrations.MigrationsController()
self.req = FakeRequest() self.req = fakes.HTTPRequest.blank('', use_admin_context=True)
self.context = self.req.environ['nova.context'] self.context = self.req.environ['nova.context']
mox_fixture = self.useFixture(moxstubout.MoxStubout()) mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.mox = mox_fixture.mox self.mox = mox_fixture.mox
def test_index(self): def test_index(self):
migrations_in_progress = { migrations_in_progress = {'migrations': self._migrations_output()}
'migrations': self.migrations.output(migrations_obj)}
for mig in migrations_in_progress['migrations']: for mig in migrations_in_progress['migrations']:
self.assertIn('id', mig) self.assertIn('id', mig)
self.assertNotIn('deleted', mig) self.assertNotIn('deleted', mig)
self.assertNotIn('deleted_at', mig) self.assertNotIn('deleted_at', mig)
self.assertNotIn('links', mig)
filters = {'host': 'host1', 'status': 'migrating', filters = {'host': 'host1', 'status': 'migrating',
'cell_name': 'ChildCell'} 'cell_name': 'ChildCell'}
@ -129,9 +186,11 @@ class MigrationsTestCaseV21(test.NoDBTestCase):
class MigrationsTestCaseV2(MigrationsTestCaseV21): class MigrationsTestCaseV2(MigrationsTestCaseV21):
migrations = migrations_v2 migrations = migrations_v2
def _migrations_output(self):
return self.migrations.output(migrations_obj)
def setUp(self): def setUp(self):
super(MigrationsTestCaseV2, self).setUp() super(MigrationsTestCaseV2, self).setUp()
self.req = fakes.HTTPRequest.blank('', use_admin_context=True)
self.context = self.req.environ['nova.context'] self.context = self.req.environ['nova.context']
def test_index_needs_authorization(self): def test_index_needs_authorization(self):
@ -146,6 +205,40 @@ class MigrationsTestCaseV2(MigrationsTestCaseV21):
self.req) self.req)
class MigrationsTestCaseV223(MigrationsTestCaseV21):
wsgi_api_version = '2.23'
def setUp(self):
"""Run before each test."""
super(MigrationsTestCaseV223, self).setUp()
self.req = fakes.HTTPRequest.blank(
'', version=self.wsgi_api_version, use_admin_context=True)
def test_index(self):
migrations = {'migrations': self.controller._output(
self.req, migrations_obj, True)}
for i, mig in enumerate(migrations['migrations']):
# first item is in-progress live migration
if i == 0:
self.assertIn('links', mig)
else:
self.assertNotIn('links', mig)
self.assertIn('migration_type', mig)
self.assertIn('id', mig)
self.assertNotIn('deleted', mig)
self.assertNotIn('deleted_at', mig)
with mock.patch.object(self.controller.compute_api,
'get_migrations') as m_get:
m_get.return_value = migrations_obj
response = self.controller.index(self.req)
self.assertEqual(migrations, response)
self.assertIn('links', response['migrations'][0])
self.assertIn('migration_type', response['migrations'][0])
class MigrationsPolicyEnforcement(test.NoDBTestCase): class MigrationsPolicyEnforcement(test.NoDBTestCase):
def setUp(self): def setUp(self):
super(MigrationsPolicyEnforcement, self).setUp() super(MigrationsPolicyEnforcement, self).setUp()
@ -161,3 +254,11 @@ class MigrationsPolicyEnforcement(test.NoDBTestCase):
self.assertEqual( self.assertEqual(
"Policy doesn't allow %s to be performed." % rule_name, "Policy doesn't allow %s to be performed." % rule_name,
exc.format_message()) exc.format_message())
class MigrationsPolicyEnforcementV223(MigrationsPolicyEnforcement):
wsgi_api_version = '2.23'
def setUp(self):
super(MigrationsPolicyEnforcementV223, self).setUp()
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)

View File

@ -13,13 +13,77 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
import datetime
import mock import mock
import webob import webob
from nova.api.openstack.compute import server_migrations from nova.api.openstack.compute import server_migrations
from nova import exception from nova import exception
from nova import objects
from nova.objects import base
from nova import test from nova import test
from nova.tests.unit.api.openstack import fakes from nova.tests.unit.api.openstack import fakes
from nova.tests import uuidsentinel as uuids
SERVER_UUID = uuids.server_uuid
fake_migrations = [
{
'id': 1234,
'source_node': 'node1',
'dest_node': 'node2',
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'running',
'instance_uuid': SERVER_UUID,
'old_instance_type_id': 1,
'new_instance_type_id': 2,
'migration_type': 'live-migration',
'hidden': False,
'memory_total': 123456,
'memory_processed': 12345,
'memory_remaining': 120000,
'disk_total': 234567,
'disk_processed': 23456,
'disk_remaining': 230000,
'created_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
'deleted_at': None,
'deleted': False
},
{
'id': 5678,
'source_node': 'node10',
'dest_node': 'node20',
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'running',
'instance_uuid': SERVER_UUID,
'old_instance_type_id': 5,
'new_instance_type_id': 6,
'migration_type': 'live-migration',
'hidden': False,
'memory_total': 456789,
'memory_processed': 56789,
'memory_remaining': 45000,
'disk_total': 96789,
'disk_processed': 6789,
'disk_remaining': 96000,
'created_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
'updated_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
'deleted_at': None,
'deleted': False
}
]
migrations_obj = base.obj_make_list(
'fake-context',
objects.MigrationList(),
objects.Migration,
fake_migrations)
class ServerMigrationsTestsV21(test.NoDBTestCase): class ServerMigrationsTestsV21(test.NoDBTestCase):
@ -86,6 +150,117 @@ class ServerMigrationsTestsV21(test.NoDBTestCase):
exception.NovaException(), webob.exc.HTTPInternalServerError) exception.NovaException(), webob.exc.HTTPInternalServerError)
class ServerMigrationsTestsV223(ServerMigrationsTestsV21):
wsgi_api_version = '2.23'
def setUp(self):
super(ServerMigrationsTestsV223, self).setUp()
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version,
use_admin_context=True)
self.context = self.req.environ['nova.context']
@mock.patch('nova.compute.api.API.get_migrations_in_progress_by_instance')
@mock.patch('nova.compute.api.API.get')
def test_index(self, m_get_instance, m_get_mig):
migrations = [server_migrations.output(mig) for mig in migrations_obj]
migrations_in_progress = {'migrations': migrations}
for mig in migrations_in_progress['migrations']:
self.assertIn('id', mig)
self.assertNotIn('deleted', mig)
self.assertNotIn('deleted_at', mig)
m_get_mig.return_value = migrations_obj
response = self.controller.index(self.req, SERVER_UUID)
self.assertEqual(migrations_in_progress, response)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
want_objects=True)
@mock.patch('nova.compute.api.API.get')
def test_index_invalid_instance(self, m_get_instance):
m_get_instance.side_effect = exception.InstanceNotFound(instance_id=1)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.index,
self.req, SERVER_UUID)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
want_objects=True)
@mock.patch('nova.compute.api.API.get_migration_by_id_and_instance')
@mock.patch('nova.compute.api.API.get')
def test_show(self, m_get_instance, m_get_mig):
migrations = [server_migrations.output(mig) for mig in migrations_obj]
m_get_mig.return_value = migrations_obj[0]
response = self.controller.show(self.req, SERVER_UUID,
migrations_obj[0].id)
self.assertEqual(migrations[0], response['migration'])
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
want_objects=True)
@mock.patch('nova.compute.api.API.get_migration_by_id_and_instance')
@mock.patch('nova.compute.api.API.get')
def test_show_migration_non_progress(self, m_get_instance, m_get_mig):
non_progress_mig = copy.deepcopy(migrations_obj[0])
non_progress_mig.status = "reverted"
m_get_mig.return_value = non_progress_mig
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show,
self.req, SERVER_UUID,
non_progress_mig.id)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
want_objects=True)
@mock.patch('nova.compute.api.API.get_migration_by_id_and_instance')
@mock.patch('nova.compute.api.API.get')
def test_show_migration_not_live_migration(self, m_get_instance,
m_get_mig):
non_progress_mig = copy.deepcopy(migrations_obj[0])
non_progress_mig.migration_type = "resize"
m_get_mig.return_value = non_progress_mig
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show,
self.req, SERVER_UUID,
non_progress_mig.id)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
want_objects=True)
@mock.patch('nova.compute.api.API.get_migration_by_id_and_instance')
@mock.patch('nova.compute.api.API.get')
def test_show_migration_not_exist(self, m_get_instance, m_get_mig):
m_get_mig.side_effect = exception.MigrationNotFoundForInstance(
migration_id=migrations_obj[0].id,
instance_id=SERVER_UUID)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show,
self.req, SERVER_UUID,
migrations_obj[0].id)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
want_objects=True)
@mock.patch('nova.compute.api.API.get')
def test_show_migration_invalid_instance(self, m_get_instance):
m_get_instance.side_effect = exception.InstanceNotFound(instance_id=1)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show,
self.req, SERVER_UUID,
migrations_obj[0].id)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
want_objects=True)
class ServerMigrationsPolicyEnforcementV21(test.NoDBTestCase): class ServerMigrationsPolicyEnforcementV21(test.NoDBTestCase):
wsgi_api_version = '2.22' wsgi_api_version = '2.22'
@ -106,3 +281,30 @@ class ServerMigrationsPolicyEnforcementV21(test.NoDBTestCase):
self.assertEqual( self.assertEqual(
"Policy doesn't allow %s to be performed." % rule_name, "Policy doesn't allow %s to be performed." % rule_name,
exc.format_message()) exc.format_message())
class ServerMigrationsPolicyEnforcementV223(
ServerMigrationsPolicyEnforcementV21):
wsgi_api_version = '2.23'
def setUp(self):
super(ServerMigrationsPolicyEnforcementV223, self).setUp()
def test_migration_index_failed(self):
rule_name = "os_compute_api:servers:migrations:index"
self.policy.set_rules({rule_name: "project:non_fake"})
exc = self.assertRaises(exception.PolicyNotAuthorized,
self.controller.index, self.req,
fakes.FAKE_UUID)
self.assertEqual("Policy doesn't allow %s to be performed." %
rule_name, exc.format_message())
def test_migration_show_failed(self):
rule_name = "os_compute_api:servers:migrations:show"
self.policy.set_rules({rule_name: "project:non_fake"})
exc = self.assertRaises(exception.PolicyNotAuthorized,
self.controller.show, self.req,
fakes.FAKE_UUID, 1)
self.assertEqual("Policy doesn't allow %s to be performed." %
rule_name, exc.format_message())

View File

@ -10179,6 +10179,26 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(1, len(migrations)) self.assertEqual(1, len(migrations))
self.assertEqual(migrations[0].id, migration['id']) self.assertEqual(migrations[0].id, migration['id'])
@mock.patch("nova.db.migration_get_in_progress_by_instance")
def test_get_migrations_in_progress_by_instance(self, mock_get):
migration = test_migration.fake_db_migration(instance_uuid="1234")
mock_get.return_value = [migration]
db.migration_get_in_progress_by_instance(self.context, "1234")
migrations = self.compute_api.get_migrations_in_progress_by_instance(
self.context, "1234")
self.assertEqual(1, len(migrations))
self.assertEqual(migrations[0].id, migration['id'])
@mock.patch("nova.db.migration_get_by_id_and_instance")
def test_get_migration_by_id_and_instance(self, mock_get):
migration = test_migration.fake_db_migration(instance_uuid="1234")
mock_get.return_value = migration
db.migration_get_by_id_and_instance(
self.context, migration['id'], uuid)
res = self.compute_api.get_migration_by_id_and_instance(
self.context, migration['id'], "1234")
self.assertEqual(res.id, migration['id'])
class ComputeAPIIpFilterTestCase(test.NoDBTestCase): class ComputeAPIIpFilterTestCase(test.NoDBTestCase):
'''Verifies the IP filtering in the compute API.''' '''Verifies the IP filtering in the compute API.'''

View File

@ -1355,6 +1355,7 @@ class MigrationTestCase(test.TestCase):
'dest_node': dest_node, 'instance_uuid': instance['uuid'], 'dest_node': dest_node, 'instance_uuid': instance['uuid'],
'migration_type': migration_type} 'migration_type': migration_type}
db.migration_create(self.ctxt, values) db.migration_create(self.ctxt, values)
return values
def _assert_in_progress(self, migrations): def _assert_in_progress(self, migrations):
for migration in migrations: for migration in migrations:
@ -1475,6 +1476,37 @@ class MigrationTestCase(test.TestCase):
db.migration_update(self.ctxt, migration['id'], db.migration_update(self.ctxt, migration['id'],
{"status": "CONFIRMED"}) {"status": "CONFIRMED"})
def test_migration_get_in_progress_by_instance(self):
values = self._create(status='running',
migration_type="live-migration")
results = db.migration_get_in_progress_by_instance(
self.ctxt, values["instance_uuid"], "live-migration")
self.assertEqual(1, len(results))
for key in values:
self.assertEqual(values[key], results[0][key])
self.assertEqual("running", results[0]["status"])
def test_migration_get_in_progress_by_instance_not_in_progress(self):
values = self._create(migration_type="live-migration")
results = db.migration_get_in_progress_by_instance(
self.ctxt, values["instance_uuid"], "live-migration")
self.assertEqual(0, len(results))
def test_migration_get_in_progress_by_instance_not_live_migration(self):
values = self._create(migration_type="resize")
results = db.migration_get_in_progress_by_instance(
self.ctxt, values["instance_uuid"], "live-migration")
self.assertEqual(0, len(results))
results = db.migration_get_in_progress_by_instance(
self.ctxt, values["instance_uuid"])
self.assertEqual(0, len(results))
def test_migration_update_not_found(self): def test_migration_update_not_found(self):
self.assertRaises(exception.MigrationNotFound, self.assertRaises(exception.MigrationNotFound,
db.migration_update, self.ctxt, 42, {}) db.migration_update, self.ctxt, 42, {})

View File

@ -126,6 +126,8 @@ policy_data = """
"os_compute_api:servers:stop": "", "os_compute_api:servers:stop": "",
"os_compute_api:servers:trigger_crash_dump": "", "os_compute_api:servers:trigger_crash_dump": "",
"os_compute_api:servers:migrations:force_complete": "", "os_compute_api:servers:migrations:force_complete": "",
"os_compute_api:servers:migrations:index": "rule:admin_api",
"os_compute_api:servers:migrations:show": "rule:admin_api",
"os_compute_api:os-access-ips": "", "os_compute_api:os-access-ips": "",
"compute_extension:accounts": "", "compute_extension:accounts": "",
"compute_extension:admin_actions:pause": "", "compute_extension:admin_actions:pause": "",

View File

@ -82,6 +82,20 @@ class _TestMigrationObject(object):
ctxt, fake_migration['id'], 'migrating') ctxt, fake_migration['id'], 'migrating')
self.compare_obj(mig, fake_migration) self.compare_obj(mig, fake_migration)
@mock.patch('nova.db.migration_get_in_progress_by_instance')
def test_get_in_progress_by_instance(self, m_get_mig):
ctxt = context.get_admin_context()
fake_migration = fake_db_migration()
db_migrations = [fake_migration, dict(fake_migration, id=456)]
m_get_mig.return_value = db_migrations
migrations = migration.MigrationList.get_in_progress_by_instance(
ctxt, fake_migration['instance_uuid'])
self.assertEqual(2, len(migrations))
for index, db_migration in enumerate(db_migrations):
self.compare_obj(migrations[index], db_migration)
def test_create(self): def test_create(self):
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
fake_migration = fake_db_migration() fake_migration = fake_db_migration()

View File

@ -1151,7 +1151,7 @@ object_data = {
'KeyPairList': '1.2-58b94f96e776bedaf1e192ddb2a24c4e', 'KeyPairList': '1.2-58b94f96e776bedaf1e192ddb2a24c4e',
'Migration': '1.4-17979b9f2ae7f28d97043a220b2a8350', 'Migration': '1.4-17979b9f2ae7f28d97043a220b2a8350',
'MigrationContext': '1.0-d8c2f10069e410f639c49082b5932c92', 'MigrationContext': '1.0-d8c2f10069e410f639c49082b5932c92',
'MigrationList': '1.2-02c0ec0c50b75ca86a2a74c5e8c911cc', 'MigrationList': '1.3-55595bfc1a299a5962614d0821a3567e',
'MonitorMetric': '1.1-53b1db7c4ae2c531db79761e7acc52ba', 'MonitorMetric': '1.1-53b1db7c4ae2c531db79761e7acc52ba',
'MonitorMetricList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'MonitorMetricList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e',
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545', 'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',

View File

@ -362,7 +362,10 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
"os_compute_api:os-assisted-volume-snapshots:delete", "os_compute_api:os-assisted-volume-snapshots:delete",
"os_compute_api:os-console-auth-tokens", "os_compute_api:os-console-auth-tokens",
"os_compute_api:os-quota-class-sets:update", "os_compute_api:os-quota-class-sets:update",
"os_compute_api:os-server-external-events:create") "os_compute_api:os-server-external-events:create",
"os_compute_api:servers:migrations:index",
"os_compute_api:servers:migrations:show",
)
self.admin_or_owner_rules = ( self.admin_or_owner_rules = (
"default", "default",

View File

@ -0,0 +1,8 @@
---
deprecations:
-
The old top-level resource `/os-migrations` is deprecated, it won't be
extended anymore. And migration_type for /os-migrations, also add ref
link to the /servers/{uuid}/migrations/{id} for it when the migration
is an in-progress live-migration. This has been added in microversion
2.23.

View File

@ -0,0 +1,8 @@
---
features:
- |
Add two new list/show API for server-migration.
The list API will return the in progress live migratons
information of a server. The show API will return
a specified in progress live migration of a server.
This has been added in microversion 2.23.