Add pagination and Changes-since filter support for os-migrations.

This patch adds pagination support and changes-since filter
for os-migrations API.

Users can now use 'limit' and 'marker' to perform paginate
query of running migrations list. Users can also filter the
results according to the migrations' updated time.

The ``GET /os-migrations`` and server migrations APIs will now
return a uuid value in addition to the migrations id in the response,
and the query parameter schema of the ``GET /os-migrations`` API no
longer allows additional properties.

Co-Authored-By: Yikun Jiang <yikunkero@gmail.com>

Implement: blueprint add-pagination-and-change-since-for-migration-list
Change-Id: I7e01f95d7173d9217f76e838b3ea71555151ef56
This commit is contained in:
Kevin_Zheng 2016-06-16 17:28:33 +08:00 committed by Matt Riedemann
parent 55f59172ee
commit 92a0fc0b9f
27 changed files with 883 additions and 32 deletions

View File

@ -1,14 +1,11 @@
.. -*- rst -*-
=========================================
Migrations (os-migrations) (frozen)
Migrations (os-migrations)
=========================================
Shows data on migrations.
.. warning:: The old top-level resource `/os-migrations` is frozen,
it won't be extended anymore. Use /servers/{uuid}/migrations instead.
List Migrations
===============
@ -22,7 +19,7 @@ this operation. Cloud providers can change these permissions through the
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403)
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
Request
-------
@ -35,6 +32,9 @@ Request
- migration_type: migration_type
- source_compute: migration_source_compute
- status: migration_status
- limit: migration_limit
- marker: migration_marker
- changes-since: changes_since_migration
Response
--------
@ -55,9 +55,21 @@ Response
- updated_at: updated
- migration_type: migration_type_2_23
- links: migration_links_2_23
- uuid: migration_uuid
- migrations_links: migration_next_links_2_59
**Example List Migrations: JSON response**
.. literalinclude:: ../../doc/api_samples/os-migrations/migrations-get.json
:language: javascript
**Example List Migrations (v2.59):**
.. literalinclude:: ../../doc/api_samples/os-migrations/v2.59/migrations-get.json
:language: javascript
**Example List Migrations With Links (v2.59):**
.. literalinclude:: ../../doc/api_samples/os-migrations/v2.59/migrations-get-with-limit.json
:language: javascript

View File

@ -446,6 +446,23 @@ changes_since_instance_action:
required: false
type: string
min_version: 2.58
changes_since_migration:
description: |
Filters the response by a date and time stamp when the migration last
changed. Those changed after the specified date and time stamp are returned.
The date and time stamp format is `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_:
::
CCYY-MM-DDThh:mm:ss±hh:mm
The ``±hh:mm`` value, if included, returns the time zone as an offset from UTC.
For example, ``2015-08-27T09:49:58-05:00``.
If you omit the time zone, the UTC time zone is assumed.
in: query
required: false
type: string
min_version: 2.59
changes_since_server:
description: |
Filters the response by a date and time stamp when the server last
@ -845,6 +862,25 @@ migration_instance_uuid:
in: query
required: false
type: string
migration_limit:
description: |
Requests a page size of items. Returns a number of items up to a limit value.
Use the ``limit`` parameter to make an initial limited request and use the
last-seen item from the response as the ``marker`` parameter value in a
subsequent limited request.
in: query
required: false
type: integer
min_version: 2.59
migration_marker:
description: |
The UUID of the last-seen migration. Use the ``limit`` parameter to make an
initial limited request and use the last-seen item from the response as
the ``marker`` parameter value in a subsequent limited request.
in: query
required: false
type: string
min_version: 2.59
migration_source_compute:
description: |
The source compute node of migration to filter.
@ -4084,6 +4120,17 @@ migration_new_flavor_id:
in: body
required: true
type: string
migration_next_links_2_59:
description: |
Links pertaining to the migration.
This parameter is returned when paging and more data is available.
See `API Guide / Links and References
<http://developer.openstack.org/api-guide/compute/links_and_references.html>`_
for more info.
in: body
required: false
type: array
min_version: 2.59
migration_old_flavor_id:
description: |
The flavor ID of the server when the migration was started.
@ -4098,6 +4145,13 @@ migration_type_2_23:
required: true
type: string
min_version: 2.23
migration_uuid:
description: |
The UUID of the migration.
in: body
required: true
type: string
min_version: 2.59
migrations:
description: |
The list of server migration objects.

View File

@ -52,10 +52,11 @@ Response
- source_node: migrate_source_node
- status: migrate_status
- updated_at: updated
- uuid: migration_uuid
**Example List Migrations**
**Example List Migrations (2.59)**
.. literalinclude:: ../../doc/api_samples/server-migrations/v2.23/migrations-index.json
.. literalinclude:: ../../doc/api_samples/server-migrations/v2.59/migrations-index.json
:language: javascript
Show Migration Details
@ -105,10 +106,11 @@ Response
- source_node: migrate_source_node
- status: migrate_status
- updated_at: updated
- uuid: migration_uuid
**Example Show Migration Details**
**Example Show Migration Details (2.59)**
.. literalinclude:: ../../doc/api_samples/server-migrations/v2.23/migrations-get.json
.. literalinclude:: ../../doc/api_samples/server-migrations/v2.59/migrations-get.json
:language: javascript
Force Migration Complete Action (force_complete Action)

View File

@ -0,0 +1,24 @@
{
"migrations": [
{
"created_at": "2016-06-23T14: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-06-23T14:42:02.000000",
"uuid": "42341d4b-346a-40d0-83c6-5f4f6892b650"
}
],
"migrations_links": [{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/os-migrations?limit=1&marker=42341d4b-346a-40d0-83c6-5f4f6892b650",
"rel": "next"
}]
}

View File

@ -0,0 +1,30 @@
{
"migrations": [
{
"created_at": "2016-01-29T11: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/6f70656e737461636b20342065766572/servers/8600d31b-d1a1-4632-b2ff-45c2be1a70ff/migrations/1",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/8600d31b-d1a1-4632-b2ff-45c2be1a70ff/migrations/1",
"rel": "bookmark"
}
],
"new_instance_type_id": 1,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"migration_type": "live-migration",
"updated_at": "2016-01-29T11:42:02.000000",
"uuid": "12341d4b-346a-40d0-83c6-5f4f6892b650"
}
]
}

View File

@ -0,0 +1,36 @@
{
"migrations": [
{
"created_at": "2016-06-23T14: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-06-23T14:42:02.000000",
"uuid": "42341d4b-346a-40d0-83c6-5f4f6892b650"
},
{
"created_at": "2016-06-23T13: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-06-23T13:42:02.000000",
"uuid": "32341d4b-346a-40d0-83c6-5f4f6892b650"
}
]
}

View File

@ -0,0 +1,78 @@
{
"migrations": [
{
"created_at": "2016-06-23T14: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-06-23T14:42:02.000000",
"uuid": "42341d4b-346a-40d0-83c6-5f4f6892b650"
},
{
"created_at": "2016-06-23T13: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-06-23T13:42:02.000000",
"uuid": "32341d4b-346a-40d0-83c6-5f4f6892b650"
},
{
"created_at": "2016-01-29T12: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": 1,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"status": "error",
"migration_type": "live-migration",
"updated_at": "2016-01-29T12:42:02.000000",
"uuid": "22341d4b-346a-40d0-83c6-5f4f6892b650"
},
{
"created_at": "2016-01-29T11: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/6f70656e737461636b20342065766572/servers/8600d31b-d1a1-4632-b2ff-45c2be1a70ff/migrations/1",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/8600d31b-d1a1-4632-b2ff-45c2be1a70ff/migrations/1",
"rel": "bookmark"
}
],
"new_instance_type_id": 1,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"migration_type": "live-migration",
"updated_at": "2016-01-29T11:42:02.000000",
"uuid": "12341d4b-346a-40d0-83c6-5f4f6892b650"
}
]
}

View File

@ -0,0 +1,21 @@
{
"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": 111111,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 211111,
"updated_at": "2016-01-29T13:42:02.000000",
"uuid": "12341d4b-346a-40d0-83c6-5f4f6892b650"
}
}

View File

@ -0,0 +1,23 @@
{
"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": 111111,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 211111,
"updated_at": "2016-01-29T13:42:02.000000",
"uuid": "12341d4b-346a-40d0-83c6-5f4f6892b650"
}
]
}

View File

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

View File

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

View File

@ -139,6 +139,9 @@ REST_API_VERSION_HISTORY = """REST API Version History:
related limits and quota resources are also removed.
* 2.58 - Add pagination support and changes-since filter for
os-instance-actions API.
* 2.59 - Add pagination support and changes-since filter for os-migrations
API. And the os-migrations API now returns both the id and the
uuid in response.
"""
# The minimum and maximum versions of the API supported
@ -147,7 +150,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.58"
_MAX_API_VERSION = "2.59"
DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal

View File

@ -10,12 +10,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from nova.api.openstack import api_version_request
from oslo_utils import timeutils
from webob import exc
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import migrations as schema_migrations
from nova.api.openstack.compute.views import migrations as migrations_view
from nova.api.openstack import wsgi
from nova.api import validation
from nova import compute
from nova import exception
from nova.objects import base as obj_base
from nova.policies import migrations as migrations_policies
@ -23,14 +27,14 @@ from nova.policies import migrations as migrations_policies
class MigrationsController(wsgi.Controller):
"""Controller for accessing migrations in OpenStack API."""
_view_builder_class = common.ViewBuilder
_view_builder_class = migrations_view.ViewBuilder
_collection_name = "servers/%s/migrations"
def __init__(self):
super(MigrationsController, self).__init__()
self.compute_api = compute.API()
def _output(self, req, migrations_obj, add_link=False):
def _output(self, req, migrations_obj, add_link=False, add_uuid=False):
"""Returns the desired output of the API from an object.
From a MigrationsList's object this method returns a list of
@ -51,7 +55,8 @@ class MigrationsController(wsgi.Controller):
del obj['deleted']
del obj['deleted_at']
del obj['hidden']
del obj['uuid']
if not add_uuid:
del obj['uuid']
if 'memory_total' in obj:
for key in detail_keys:
del obj[key]
@ -68,15 +73,71 @@ class MigrationsController(wsgi.Controller):
return objects
@wsgi.expected_errors(())
@validation.query_schema(schema_migrations.list_query_schema_v20)
def index(self, req):
"""Return all migrations using the query parameters as filters."""
def _index(self, req, add_link=False, next_link=False, add_uuid=False,
sort_dirs=None, sort_keys=None, limit=None, marker=None,
allow_changes_since=False):
context = req.environ['nova.context']
context.can(migrations_policies.POLICY_ROOT % 'index')
migrations = self.compute_api.get_migrations(context, req.GET)
search_opts = {}
search_opts.update(req.GET)
if 'changes-since' in search_opts:
if allow_changes_since:
search_opts['changes-since'] = timeutils.parse_isotime(
search_opts['changes-since'])
else:
# Before microversion 2.59, the changes-since filter was not
# supported in the DB API. However, the schema allowed
# additionalProperties=True, so a user could pass it before
# 2.59 and filter by the updated_at field if we don't remove
# it from search_opts.
del search_opts['changes-since']
if api_version_request.is_supported(req, min_version='2.23'):
return {'migrations': self._output(req, migrations, True)}
if sort_keys:
try:
migrations = self.compute_api.get_migrations_sorted(
context, search_opts,
sort_dirs=sort_dirs, sort_keys=sort_keys,
limit=limit, marker=marker)
except exception.MarkerNotFound as e:
raise exc.HTTPBadRequest(explanation=e.format_message())
else:
migrations = self.compute_api.get_migrations(
context, search_opts)
return {'migrations': self._output(req, migrations)}
migrations = self._output(req, migrations, add_link, add_uuid)
migrations_dict = {'migrations': migrations}
if next_link:
migrations_links = self._view_builder.get_links(req, migrations)
if migrations_links:
migrations_dict['migrations_links'] = migrations_links
return migrations_dict
@wsgi.Controller.api_version("2.1", "2.22") # noqa
@wsgi.expected_errors(())
@validation.query_schema(schema_migrations.list_query_schema_v20,
"2.1", "2.22")
def index(self, req):
"""Return all migrations using the query parameters as filters."""
return self._index(req)
@wsgi.Controller.api_version("2.23", "2.58") # noqa
@wsgi.expected_errors(())
@validation.query_schema(schema_migrations.list_query_schema_v20,
"2.23", "2.58")
def index(self, req):
"""Return all migrations using the query parameters as filters."""
return self._index(req, add_link=True)
@wsgi.Controller.api_version("2.59") # noqa
@wsgi.expected_errors(400)
@validation.query_schema(schema_migrations.list_query_params_v259,
"2.59")
def index(self, req):
"""Return all migrations using the query parameters as filters."""
limit, marker = common.get_limit_and_marker(req)
return self._index(req, add_link=True, next_link=True, add_uuid=True,
sort_keys=['created_at', 'id'],
sort_dirs=['desc', 'desc'],
limit=limit, marker=marker,
allow_changes_since=True)

View File

@ -740,3 +740,20 @@ API. Users can now use ``limit`` and ``marker`` to perform paginated query
when listing instance actions. Users can also use ``changes-since`` filter
to filter the results based on the last time the instance action was
updated.
2.59
----
Added pagination support for migrations, there are four changes:
* Add pagination support and ``changes-since`` filter for os-migrations
API. Users can now use ``limit`` and ``marker`` to perform paginate query
when listing migrations.
* Users can also use ``changes-since`` filter to filter the results based
on the last time the migration record was updated.
* ``GET /os-migrations``,
``GET /servers/{server_id}/migrations/{migration_id}`` and
``GET /servers/{server_id}/migrations`` will now return a uuid value in
addition to the migrations id in the response.
* The query parameter schema of the ``GET /os-migrations`` API no longer
allows additional properties.

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from nova.api.validation import parameter_types
list_query_schema_v20 = {
@ -25,9 +27,17 @@ list_query_schema_v20 = {
'migration_type': parameter_types.common_query_param,
},
# For backward compatible changes
# TODO(mriedem): In a future microversion, consider changing
# additionalProperties to False, remove the 'hidden' filter since
# it's vestigial, and enforce type and enum checks for the filters
# where that makes sense, e.g. instance_uuid, status and migration_type.
'additionalProperties': True
}
list_query_params_v259 = copy.deepcopy(list_query_schema_v20)
list_query_params_v259['properties'].update({
# The 2.59 microversion added support for paging by limit and marker
# and filtering by changes-since.
'limit': parameter_types.single_param(
parameter_types.non_negative_integer),
'marker': parameter_types.single_param({'type': 'string'}),
'changes-since': parameter_types.single_param(
{'type': 'string', 'format': 'date-time'}),
})
list_query_params_v259['additionalProperties'] = False

View File

@ -15,6 +15,7 @@
from webob import exc
from nova.api.openstack import api_version_request
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import server_migrations
from nova.api.openstack import wsgi
@ -25,13 +26,13 @@ from nova.i18n import _
from nova.policies import servers_migrations as sm_policies
def output(migration):
def output(migration, include_uuid=False):
"""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 {
result = {
"created_at": migration.created_at,
"dest_compute": migration.dest_compute,
"dest_host": migration.dest_host,
@ -49,6 +50,9 @@ def output(migration):
"status": migration.status,
"updated_at": migration.updated_at
}
if include_uuid:
result['uuid'] = migration.uuid
return result
class ServerMigrationsController(wsgi.Controller):
@ -96,7 +100,9 @@ class ServerMigrationsController(wsgi.Controller):
migrations = self.compute_api.get_migrations_in_progress_by_instance(
context, server_id, 'live-migration')
return {'migrations': [output(migration) for migration in migrations]}
include_uuid = api_version_request.is_supported(req, '2.59')
return {'migrations': [output(
migration, include_uuid) for migration in migrations]}
@wsgi.Controller.api_version("2.23")
@wsgi.expected_errors(404)
@ -129,7 +135,8 @@ class ServerMigrationsController(wsgi.Controller):
" progress.") % {"id": id, "uuid": server_id}
raise exc.HTTPNotFound(explanation=msg)
return {'migration': output(migration)}
include_uuid = api_version_request.is_supported(req, '2.59')
return {'migration': output(migration, include_uuid)}
@wsgi.Controller.api_version("2.24")
@wsgi.response(202)

View File

@ -0,0 +1,24 @@
# Copyright 2017 Huawei Technologies Co.,LTD.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova.api.openstack import common
class ViewBuilder(common.ViewBuilder):
_collection_name = "os-migrations"
def get_links(self, request, migrations):
return self._get_collection_links(request, migrations,
self._collection_name, 'uuid')

View File

@ -0,0 +1,24 @@
{
"migrations": [
{
"created_at": "2016-06-23T14: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",
"status": "migrating",
"migration_type": "resize",
"updated_at": "2016-06-23T14:42:02.000000",
"uuid": "42341d4b-346a-40d0-83c6-5f4f6892b650"
}
],
"migrations_links": [{
"href": "%(host)s/v2.1/6f70656e737461636b20342065766572/os-migrations?limit=1&marker=42341d4b-346a-40d0-83c6-5f4f6892b650",
"rel": "next"
}]
}

View File

@ -0,0 +1,30 @@
{
"migrations": [
{
"created_at": "2016-01-29T11: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/6f70656e737461636b20342065766572/servers/%(instance_1)s/migrations/1",
"rel": "self"
},
{
"href": "%(host)s/6f70656e737461636b20342065766572/servers/%(instance_1)s/migrations/1",
"rel": "bookmark"
}
],
"new_instance_type_id": 1,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"migration_type": "live-migration",
"status": "running",
"updated_at": "2016-01-29T11:42:02.000000",
"uuid": "12341d4b-346a-40d0-83c6-5f4f6892b650"
}
]
}

View File

@ -0,0 +1,36 @@
{
"migrations": [
{
"created_at": "2016-06-23T14: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-06-23T14:42:02.000000",
"uuid": "42341d4b-346a-40d0-83c6-5f4f6892b650"
},
{
"created_at": "2016-06-23T13: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-06-23T13:42:02.000000",
"uuid": "32341d4b-346a-40d0-83c6-5f4f6892b650"
}
]
}

View File

@ -0,0 +1,78 @@
{
"migrations": [
{
"created_at": "2016-06-23T14: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-06-23T14:42:02.000000",
"uuid": "42341d4b-346a-40d0-83c6-5f4f6892b650"
},
{
"created_at": "2016-06-23T13: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-06-23T13:42:02.000000",
"uuid": "32341d4b-346a-40d0-83c6-5f4f6892b650"
},
{
"created_at": "2016-01-29T12: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": 1,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"migration_type": "live-migration",
"status": "error",
"updated_at": "2016-01-29T12:42:02.000000",
"uuid": "22341d4b-346a-40d0-83c6-5f4f6892b650"
},
{
"created_at": "2016-01-29T11: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/6f70656e737461636b20342065766572/servers/%(instance_1)s/migrations/1",
"rel": "self"
},
{
"href": "%(host)s/6f70656e737461636b20342065766572/servers/%(instance_1)s/migrations/1",
"rel": "bookmark"
}
],
"new_instance_type_id": 1,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"migration_type": "live-migration",
"status": "running",
"updated_at": "2016-01-29T11:42:02.000000",
"uuid": "12341d4b-346a-40d0-83c6-5f4f6892b650"
}
]
}

View File

@ -0,0 +1,21 @@
{
"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": 111111,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 211111,
"updated_at": "2016-01-29T13:42:02.000000",
"uuid": "12341d4b-346a-40d0-83c6-5f4f6892b650"
}
}

View File

@ -0,0 +1,23 @@
{
"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": 111111,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 211111,
"updated_at": "2016-01-29T13:42:02.000000",
"uuid": "12341d4b-346a-40d0-83c6-5f4f6892b650"
}
]
}

View File

@ -184,3 +184,110 @@ class MigrationsSamplesJsonTestV2_23(api_sample_base.ApiSampleTestBaseV21):
'migrations-get',
{"instance_1": INSTANCE_UUID_1, "instance_2": INSTANCE_UUID_2},
response, 200)
class MigrationsSamplesJsonTestV2_59(MigrationsSamplesJsonTestV2_23):
microversion = '2.59'
scenarios = [('v2_59', {'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': 1,
'migration_type': 'live-migration',
'hidden': False,
'created_at': datetime.datetime(2016, 0o1, 29, 11, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 29, 11, 42, 2),
'deleted_at': None,
'deleted': False,
'uuid': '12341d4b-346a-40d0-83c6-5f4f6892b650'
},
# 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': 1,
'migration_type': 'live-migration',
'hidden': False,
'created_at': datetime.datetime(2016, 0o1, 29, 12, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 29, 12, 42, 2),
'deleted_at': None,
'deleted': False,
'uuid': '22341d4b-346a-40d0-83c6-5f4f6892b650'
},
# 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, 0o6, 23, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o6, 23, 13, 42, 2),
'deleted_at': None,
'deleted': False,
'uuid': '32341d4b-346a-40d0-83c6-5f4f6892b650'
},
# 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, 0o6, 23, 14, 42, 2),
'updated_at': datetime.datetime(2016, 0o6, 23, 14, 42, 2),
'deleted_at': None,
'deleted': False,
'uuid': '42341d4b-346a-40d0-83c6-5f4f6892b650'
}
]
def test_get_migrations_with_limit(self):
response = self._do_get('os-migrations?limit=1')
self.assertEqual(200, response.status_code)
self._verify_response(
'migrations-get-with-limit',
{"instance_2": INSTANCE_UUID_2}, response, 200)
def test_get_migrations_with_marker(self):
response = self._do_get(
'os-migrations?marker=22341d4b-346a-40d0-83c6-5f4f6892b650')
self.assertEqual(200, response.status_code)
self._verify_response(
'migrations-get-with-marker',
{"instance_1": INSTANCE_UUID_1, "instance_2": INSTANCE_UUID_2},
response, 200)
def test_get_migrations_with_timestamp_filter(self):
response = self._do_get(
'os-migrations?changes-since=2016-06-23T13:42:01.000000')
self.assertEqual(200, response.status_code)
self._verify_response(
'migrations-get-with-timestamp-filter',
{"instance_2": INSTANCE_UUID_2}, response, 200)

View File

@ -210,3 +210,19 @@ class ServerMigrationsSampleJsonTestV2_24(test_servers.ServersSampleBase):
uri = 'servers/%s/migrations/%s' % (self.uuid, self.migration.id)
response = self._do_delete(uri)
self.assertEqual(400, response.status_code)
class ServerMigrationsSamplesJsonTestV2_59(
ServerMigrationsSamplesJsonTestV2_23
):
ADMIN_API = True
microversion = '2.59'
scenarios = [('v2_59', {'api_major_version': 'v2.1'})]
def setUp(self):
# Add UUIDs to the fake migrations used in the tests.
self.fake_migrations[0][
'uuid'] = '12341d4b-346a-40d0-83c6-5f4f6892b650'
self.fake_migrations[1][
'uuid'] = '22341d4b-346a-40d0-83c6-5f4f6892b650'
super(ServerMigrationsSamplesJsonTestV2_59, self).setUp()

View File

@ -15,6 +15,8 @@
import datetime
import mock
import six
from webob import exc
from nova.api.openstack.compute import migrations as migrations_v21
from nova import context
@ -258,6 +260,98 @@ class MigrationsTestCaseV223(MigrationsTestCaseV21):
self.assertIn('migration_type', response['migrations'][0])
class MigrationsTestCaseV259(MigrationsTestCaseV223):
wsgi_api_version = '2.59'
def test_index(self):
migrations = {'migrations': self.controller._output(
self.req, migrations_obj, True, 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.assertIn('uuid', mig)
self.assertNotIn('deleted', mig)
self.assertNotIn('deleted_at', mig)
with mock.patch.object(self.controller.compute_api,
'get_migrations_sorted') 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])
@mock.patch('nova.compute.api.API.get_migrations_sorted')
def test_index_with_invalid_marker(self, mock_migrations_get):
"""Tests detail paging with an invalid marker (not found)."""
mock_migrations_get.side_effect = exception.MarkerNotFound(
marker=uuids.invalid_marker)
req = fakes.HTTPRequest.blank(
'/os-migrations?marker=%s' % uuids.invalid_marker,
version=self.wsgi_api_version, use_admin_context=True)
e = self.assertRaises(exc.HTTPBadRequest,
self.controller.index, req)
self.assertEqual(
"Marker %s could not be found." % uuids.invalid_marker,
six.text_type(e))
def test_index_with_invalid_limit(self):
"""Tests detail paging with an invalid limit."""
req = fakes.HTTPRequest.blank(
'/os-migrations?limit=x', version=self.wsgi_api_version,
use_admin_context=True)
self.assertRaises(exception.ValidationError,
self.controller.index, req)
req = fakes.HTTPRequest.blank(
'/os-migrations?limit=-1', version=self.wsgi_api_version,
use_admin_context=True)
self.assertRaises(exception.ValidationError,
self.controller.index, req)
def test_index_with_invalid_changes_since(self):
"""Tests detail paging with an invalid changes-since value."""
req = fakes.HTTPRequest.blank(
'/os-migrations?changes-since=wrong_time',
version=self.wsgi_api_version, use_admin_context=True)
self.assertRaises(exception.ValidationError,
self.controller.index, req)
def test_index_with_unknown_query_param(self):
"""Tests detail paging with an unknown query parameter."""
req = fakes.HTTPRequest.blank(
'/os-migrations?foo=bar',
version=self.wsgi_api_version, use_admin_context=True)
ex = self.assertRaises(exception.ValidationError,
self.controller.index, req)
self.assertIn('Additional properties are not allowed',
six.text_type(ex))
@mock.patch('nova.compute.api.API.get_migrations',
return_value=objects.MigrationList())
def test_index_with_changes_since_old_microversion(self, get_migrations):
"""Tests that the changes-since query parameteris ignored before
microversion 2.59.
"""
# Also use a valid filter (instance_uuid) to make sure only
# changes-since is removed.
req = fakes.HTTPRequest.blank(
'/os-migrations?changes-since=2018-01-10T16:59:24.138939&'
'instance_uuid=%s' % uuids.instance_uuid,
version='2.58', use_admin_context=True)
result = self.controller.index(req)
self.assertEqual({'migrations': []}, result)
get_migrations.assert_called_once_with(
req.environ['nova.context'],
{'instance_uuid': uuids.instance_uuid})
class MigrationsPolicyEnforcement(test.NoDBTestCase):
def setUp(self):
super(MigrationsPolicyEnforcement, self).setUp()
@ -281,3 +375,7 @@ class MigrationsPolicyEnforcementV223(MigrationsPolicyEnforcement):
def setUp(self):
super(MigrationsPolicyEnforcementV223, self).setUp()
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
class MigrationsPolicyEnforcementV259(MigrationsPolicyEnforcementV223):
wsgi_api_version = '2.59'

View File

@ -0,0 +1,16 @@
---
features:
- |
Added pagination support for migrations, there are four changes:
- Add pagination support and ``changes-since`` filter for os-migrations
API. Users can now use ``limit`` and ``marker`` to perform paginate query
when listing migrations.
- Users can also use ``changes-since`` filter to filter the results based
on the last time the migration record was updated.
- ``GET /os-migrations``,
``GET /servers/{server_id}/migrations/{migration_id}`` and
``GET /servers/{server_id}/migrations`` will now return a uuid value in
addition to the migrations id in the response.
- The query parameter schema of the ``GET /os-migrations`` API no longer
allows additional properties.