Add pagination and changes-since for instance-actions

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

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

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

Implement: blueprint pagination-add-changes-since-for-instance-action-list

Change-Id: I1a1b39803e8d0449f21d2ab5ef96d4060e638aa8
This commit is contained in:
Kevin_Zheng 2016-06-07 17:24:27 +08:00 committed by Yikun Jiang (Kero)
parent d110ec5961
commit 0c480d795f
27 changed files with 580 additions and 18 deletions

View File

@ -22,7 +22,7 @@ through the ``policy.json`` file.
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404)
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
Request
-------
@ -31,6 +31,9 @@ Request
- server_id: server_id_path
- limit: instance_action_limit
- marker: instance_action_marker
- changes-since: changes_since_instance_action
Response
--------
@ -46,12 +49,18 @@ Response
- request_id: request_id_body
- start_time: start_time
- user_id: user_id
- updated_at: updated_instance_action
- instance_actions_links: instance_actions_next_links
**Example List Actions For Server: JSON response**
.. literalinclude:: ../../doc/api_samples/os-instance-actions/instance-actions-list-resp.json
:language: javascript
**Example List Actions For Server With Links (v2.58):**
.. literalinclude:: ../../doc/api_samples/os-instance-actions/v2.58/instance-actions-list-with-limit-resp.json
:language: javascript
Show Server Action Details
==========================
@ -102,6 +111,7 @@ Response
- events.finish_time: event_finish_time
- events.result: event_result
- events.traceback: event_traceback
- updated_at: updated_instance_action
**Example Show Server Action Details For Admin (v2.1)**

View File

@ -429,6 +429,23 @@ changes-since:
in: query
required: false
type: string
changes_since_instance_action:
description: |
Filters the response by a date and time stamp when the instance action last
changed.
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.58
changes_since_server:
description: |
Filters the response by a date and time stamp when the server last
@ -679,6 +696,26 @@ include:
in: query
required: false
type: string
instance_action_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.58
instance_action_marker:
description: |
The ``request_id`` of the last-seen instance action. 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.58
ip6_query:
description: |
An IPv6 address to filter results by.
@ -3398,6 +3435,17 @@ instance_action_events_2_51:
required: true
type: array
min_version: 2.51
instance_actions_next_links:
description: |
Links pertaining to the instance action.
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.58
instance_id_body:
description: |
The UUID of the server.
@ -5794,6 +5842,23 @@ updated_consider_null:
in: body
required: true
type: string
updated_instance_action:
description: |
The date and time when the instance action or the action event of
instance action was updated. The date and time stamp format is
`ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_
::
CCYY-MM-DDThh:mm:ss±hh:mm
For example, ``2015-08-27T09:49:58-05:00``. The ``±hh:mm``
value, if included, is the time zone as an offset from UTC. In
the previous example, the offset value is ``-05:00``.
in: body
required: true
type: string
min_version: 2.58
updated_version:
description: |
This is a fixed string. It is ``2011-01-21T11:33:21Z`` in version 2.0,

View File

@ -0,0 +1,20 @@
{
"instanceAction": {
"action": "stop",
"events": [
{
"event": "compute_stop_instance",
"finish_time": "2017-12-07T11:07:06.431902",
"result": "Success",
"start_time": "2017-12-07T11:07:06.251280"
}
],
"instance_uuid": "b48316c5-71e8-45e4-9884-6c78055b9b13",
"message": null,
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-3293a3f1-b44c-4609-b8d2-d81b105636b8",
"start_time": "2017-12-07T11:07:06.088644",
"updated_at": "2017-12-07T11:07:06.431902",
"user_id": "fake"
}
}

View File

@ -0,0 +1,21 @@
{
"instanceAction": {
"action": "stop",
"events": [
{
"event": "compute_stop_instance",
"finish_time": "2017-12-07T11:07:06.431902",
"result": "Success",
"start_time": "2017-12-07T11:07:06.251280",
"traceback": null
}
],
"instance_uuid": "b48316c5-71e8-45e4-9884-6c78055b9b13",
"message": "",
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-3293a3f1-b44c-4609-b8d2-d81b105636b8",
"start_time": "2017-12-07T11:07:06.088644",
"updated_at": "2017-12-07T11:07:06.431902",
"user_id": "fake"
}
}

View File

@ -0,0 +1,24 @@
{
"instanceActions": [
{
"instance_uuid": "e357e6d8-952e-4d1d-b74f-c8519e937706",
"user_id": "fake",
"start_time": "2017-12-07T11:07:06.088644",
"updated_at": "2017-12-07T11:07:06.431902",
"request_id": "req-e80018f1-c5bd-45ee-aaa9-290f2f5ef7bc",
"action": "stop",
"message": null,
"project_id": "6f70656e737461636b20342065766572"
},
{
"instance_uuid": "e357e6d8-952e-4d1d-b74f-c8519e937706",
"user_id": "fake",
"start_time": "2017-12-07T11:07:04.313653",
"updated_at": "2017-12-07T11:07:06.058351",
"request_id": "req-c8fd339d-d2bf-43c2-a98a-84328281f83e",
"action": "create",
"message": null,
"project_id": "6f70656e737461636b20342065766572"
}
]
}

View File

@ -0,0 +1,20 @@
{
"instanceActions": [
{
"instance_uuid": "e357e6d8-952e-4d1d-b74f-c8519e937706",
"user_id": "fake",
"start_time": "2017-12-07T11:07:06.088644",
"updated_at": "2017-12-07T11:07:06.431902",
"request_id": "req-e80018f1-c5bd-45ee-aaa9-290f2f5ef7bc",
"action": "stop",
"message": null,
"project_id": "6f70656e737461636b20342065766572"
}
],
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/e357e6d8-952e-4d1d-b74f-c8519e937706/os-instance-actions?limit=1&marker=req-e80018f1-c5bd-45ee-aaa9-290f2f5ef7bc",
"rel": "next"
}
]
}

View File

@ -0,0 +1,14 @@
{
"instanceActions": [
{
"instance_uuid": "e357e6d8-952e-4d1d-b74f-c8519e937706",
"user_id": "fake",
"start_time": "2017-12-07T11:07:04.313653",
"updated_at": "2017-12-07T11:07:06.058351",
"request_id": "req-c8fd339d-d2bf-43c2-a98a-84328281f83e",
"action": "create",
"message": null,
"project_id": "6f70656e737461636b20342065766572"
}
]
}

View File

@ -0,0 +1,14 @@
{
"instanceActions": [
{
"instance_uuid": "e357e6d8-952e-4d1d-b74f-c8519e937706",
"user_id": "fake",
"start_time": "2017-12-07T11:07:06.088644",
"updated_at": "2017-12-07T11:07:06.431902",
"request_id": "req-e80018f1-c5bd-45ee-aaa9-290f2f5ef7bc",
"action": "stop",
"message": null,
"project_id": "6f70656e737461636b20342065766572"
}
]
}

View File

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

View File

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

View File

@ -137,6 +137,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
server action APIs. Added the ability to pass new user_data to
the rebuild server action API. Personality / file injection
related limits and quota resources are also removed.
* 2.58 - Add pagination support and changes-since filter for
os-instance-actions API.
"""
# The minimum and maximum versions of the API supported
@ -145,7 +147,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.57"
_MAX_API_VERSION = "2.58"
DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal

View File

@ -15,30 +15,42 @@
from webob import exc
from oslo_utils import timeutils
from nova.api.openstack import api_version_request
from nova.api.openstack import common
from nova.api.openstack.compute.schemas \
import instance_actions as schema_instance_actions
from nova.api.openstack.compute.views \
import instance_actions as instance_actions_view
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api import validation
from nova import compute
from nova import exception
from nova.i18n import _
from nova.policies import instance_actions as ia_policies
from nova import utils
ACTION_KEYS = ['action', 'instance_uuid', 'request_id', 'user_id',
'project_id', 'start_time', 'message']
ACTION_KEYS_V258 = ['action', 'instance_uuid', 'request_id', 'user_id',
'project_id', 'start_time', 'message', 'updated_at']
EVENT_KEYS = ['event', 'start_time', 'finish_time', 'result', 'traceback']
class InstanceActionsController(wsgi.Controller):
_view_builder_class = instance_actions_view.ViewBuilder
def __init__(self):
super(InstanceActionsController, self).__init__()
self.compute_api = compute.API()
self.action_api = compute.InstanceActionAPI()
def _format_action(self, action_raw):
def _format_action(self, action_raw, action_keys):
action = {}
for key in ACTION_KEYS:
for key in action_keys:
action[key] = action_raw.get(key)
return action
@ -60,6 +72,7 @@ class InstanceActionsController(wsgi.Controller):
with utils.temporary_mutation(context, read_deleted='yes'):
return common.get_instance(self.compute_api, context, server_id)
@wsgi.Controller.api_version("2.1", "2.57")
@extensions.expected_errors(404)
def index(self, req, server_id):
"""Returns the list of actions recorded for a given instance."""
@ -67,9 +80,41 @@ class InstanceActionsController(wsgi.Controller):
instance = self._get_instance(req, context, server_id)
context.can(ia_policies.BASE_POLICY_NAME, instance)
actions_raw = self.action_api.actions_get(context, instance)
actions = [self._format_action(action) for action in actions_raw]
actions = [self._format_action(action, ACTION_KEYS)
for action in actions_raw]
return {'instanceActions': actions}
@wsgi.Controller.api_version("2.58") # noqa
@extensions.expected_errors((400, 404))
@validation.query_schema(schema_instance_actions.list_query_params_v258,
"2.58")
def index(self, req, server_id):
"""Returns the list of actions recorded for a given instance."""
context = req.environ["nova.context"]
instance = self._get_instance(req, context, server_id)
context.can(ia_policies.BASE_POLICY_NAME, instance)
search_opts = {}
search_opts.update(req.GET)
if 'changes-since' in search_opts:
search_opts['changes-since'] = timeutils.parse_isotime(
search_opts['changes-since'])
limit, marker = common.get_limit_and_marker(req)
try:
actions_raw = self.action_api.actions_get(context, instance,
limit=limit,
marker=marker,
filters=search_opts)
except exception.MarkerNotFound as e:
raise exc.HTTPBadRequest(explanation=e.format_message())
actions = [self._format_action(action, ACTION_KEYS_V258)
for action in actions_raw]
actions_dict = {'instanceActions': actions}
actions_links = self._view_builder.get_links(req, server_id, actions)
if actions_links:
actions_dict['links'] = actions_links
return actions_dict
@extensions.expected_errors(404)
def show(self, req, server_id, id):
"""Return data about the given instance action."""
@ -83,7 +128,10 @@ class InstanceActionsController(wsgi.Controller):
raise exc.HTTPNotFound(explanation=msg)
action_id = action['id']
action = self._format_action(action)
if api_version_request.is_supported(req, min_version="2.58"):
action = self._format_action(action, ACTION_KEYS_V258)
else:
action = self._format_action(action, ACTION_KEYS)
# Prior to microversion 2.51, events would only be returned in the
# response for admins by default policy rules. Starting in
# microversion 2.51, events are returned for admin_or_owner (of the

View File

@ -729,3 +729,12 @@ The 2.57 microversion makes the following changes:
* The ``injected_files``, ``injected_file_content_bytes`` and
``injected_file_path_bytes`` quotas are removed from the ``os-quota-sets``
and ``os-quota-class-sets`` APIs.
2.58
----
Add pagination support and ``changes-since`` filter for os-instance-actions
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.

View File

@ -0,0 +1,29 @@
# 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.validation import parameter_types
list_query_params_v258 = {
'type': 'object',
'properties': {
# The 2.58 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'}),
},
'additionalProperties': False
}

View File

@ -0,0 +1,23 @@
# 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):
def get_links(self, request, server_id, instance_actions):
collection_name = 'servers/%s/os-instance-actions' % server_id
return self._get_collection_links(request, instance_actions,
collection_name, 'request_id')

View File

@ -4757,9 +4757,10 @@ class HostAPI(base.Base):
class InstanceActionAPI(base.Base):
"""Sub-set of the Compute Manager API for managing instance actions."""
def actions_get(self, context, instance):
def actions_get(self, context, instance, limit=None, marker=None,
filters=None):
return objects.InstanceActionList.get_by_instance_uuid(
context, instance.uuid)
context, instance.uuid, limit, marker, filters)
def action_get_by_request_id(self, context, instance, request_id):
return objects.InstanceAction.get_by_request_id(

View File

@ -676,7 +676,9 @@ class InstanceActionAPI(compute_api.InstanceActionAPI):
super(InstanceActionAPI, self).__init__()
self.cells_rpcapi = cells_rpcapi.CellsAPI()
def actions_get(self, context, instance):
def actions_get(self, context, instance, limit=None, marker=None,
filters=None):
# Paging and filtering isn't supported in cells v1.
return self.cells_rpcapi.actions_get(context, instance)
def action_get_by_request_id(self, context, instance, request_id):

View File

@ -0,0 +1,20 @@
{
"instanceAction": {
"action": "stop",
"instance_uuid": "%(uuid)s",
"request_id": "%(request_id)s",
"user_id": "%(user_id)s",
"project_id": "%(project_id)s",
"start_time": "%(strtime)s",
"updated_at": "%(strtime)s",
"message": "",
"events": [
{
"event": "compute_stop_instance",
"start_time": "%(strtime)s",
"finish_time": "%(strtime)s",
"result": "Success"
}
]
}
}

View File

@ -0,0 +1,21 @@
{
"instanceAction": {
"action": "stop",
"instance_uuid": "%(uuid)s",
"request_id": "%(request_id)s",
"user_id": "%(user_id)s",
"project_id": "%(project_id)s",
"start_time": "%(strtime)s",
"updated_at": "%(strtime)s",
"message": "",
"events": [
{
"event": "compute_stop_instance",
"start_time": "%(strtime)s",
"finish_time": "%(strtime)s",
"result": "Success",
"traceback": ""
}
]
}
}

View File

@ -0,0 +1,24 @@
{
"instanceActions": [
{
"action": "stop",
"instance_uuid": "%(uuid)s",
"request_id": "%(request_id)s",
"user_id": "%(user_id)s",
"project_id": "%(project_id)s",
"start_time": "%(strtime)s",
"updated_at": "%(strtime)s",
"message": ""
},
{
"action": "create",
"instance_uuid": "%(uuid)s",
"request_id": "%(request_id)s",
"user_id": "%(user_id)s",
"project_id": "%(project_id)s",
"start_time": "%(strtime)s",
"updated_at": "%(strtime)s",
"message": ""
}
]
}

View File

@ -0,0 +1,20 @@
{
"instanceActions": [
{
"action": "stop",
"instance_uuid": "%(uuid)s",
"request_id": "%(request_id)s",
"user_id": "%(user_id)s",
"project_id": "%(project_id)s",
"start_time": "%(strtime)s",
"updated_at": "%(strtime)s",
"message": ""
}
],
"links": [
{
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s/os-instance-actions?limit=1&marker=%(request_id)s",
"rel": "next"
}
]
}

View File

@ -0,0 +1,14 @@
{
"instanceActions": [
{
"action": "create",
"instance_uuid": "%(uuid)s",
"request_id": "%(request_id)s",
"user_id": "%(user_id)s",
"project_id": "%(project_id)s",
"start_time": "%(strtime)s",
"updated_at": "%(strtime)s",
"message": ""
}
]
}

View File

@ -0,0 +1,14 @@
{
"instanceActions": [
{
"action": "stop",
"instance_uuid": "%(uuid)s",
"request_id": "%(request_id)s",
"user_id": "%(user_id)s",
"project_id": "%(project_id)s",
"start_time": "%(strtime)s",
"updated_at": "%(strtime)s",
"message": ""
}
]
}

View File

@ -18,6 +18,9 @@ import copy
import six
from nova.tests.functional.api_sample_tests import api_sample_base
from nova.tests.functional.api_sample_tests import test_servers
from nova.tests.functional import api_samples_test_base
from nova.tests.functional import integrated_helpers
from nova.tests.unit import fake_instance
from nova.tests.unit import fake_server_actions
from nova.tests.unit import utils as test_utils
@ -127,3 +130,73 @@ class ServerActionsV251NonAdminSampleJsonTest(ServerActionsSampleJsonTest):
ADMIN_API = False
microversion = '2.51'
scenarios = [('v2_51', {'api_major_version': 'v2.1'})]
class ServerActionsV258SampleJsonTest(test_servers.ServersSampleBase,
integrated_helpers.InstanceHelperMixin):
microversion = '2.58'
scenarios = [('v2_58', {'api_major_version': 'v2.1'})]
sample_dir = 'os-instance-actions'
ADMIN_API = True
def setUp(self):
super(ServerActionsV258SampleJsonTest, self).setUp()
# Create and stop a server
self.uuid = self._post_server()
self._get_response('servers/%s/action' % self.uuid, 'POST',
'{"os-stop": null}')
response = self._do_get('servers/%s/os-instance-actions' % self.uuid)
response_data = api_samples_test_base.pretty_data(response.content)
actions = api_samples_test_base.objectify(response_data)
self.action_stop = actions['instanceActions'][0]
self._wait_for_state_change(self.api, {'id': self.uuid}, 'SHUTOFF')
def _get_subs(self):
return {
'uuid': self.uuid,
'project_id': self.action_stop['project_id']
}
def test_instance_action_get(self):
req_id = self.action_stop['request_id']
response = self._do_get('servers/%s/os-instance-actions/%s' %
(self.uuid, req_id))
# Non-admins can see event details except for the "traceback" field
# starting in the 2.51 microversion.
if self.ADMIN_API:
name = 'instance-action-get-resp'
else:
name = 'instance-action-get-non-admin-resp'
self._verify_response(name, self._get_subs(), response, 200)
def test_instance_actions_list(self):
response = self._do_get('servers/%s/os-instance-actions' % self.uuid)
self._verify_response('instance-actions-list-resp', self._get_subs(),
response, 200)
def test_instance_actions_list_with_limit(self):
response = self._do_get('servers/%s/os-instance-actions'
'?limit=1' % self.uuid)
self._verify_response('instance-actions-list-with-limit-resp',
self._get_subs(), response, 200)
def test_instance_actions_list_with_marker(self):
marker = self.action_stop['request_id']
response = self._do_get('servers/%s/os-instance-actions'
'?marker=%s' % (self.uuid, marker))
self._verify_response('instance-actions-list-with-marker-resp',
self._get_subs(), response, 200)
def test_instance_actions_with_timestamp_filter(self):
stop_action_time = self.action_stop['start_time']
response = self._do_get(
'servers/%s/os-instance-actions'
'?changes-since=%s' % (self.uuid, stop_action_time))
self._verify_response(
'instance-actions-list-with-timestamp-filter',
self._get_subs(), response, 200)
class ServerActionsV258NonAdminSampleJsonTest(ServerActionsV258SampleJsonTest):
ADMIN_API = False

View File

@ -434,6 +434,8 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase):
'-[0-9a-f]{4}-[0-9a-f]{12})',
'uuid': '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}'
'-[0-9a-f]{4}-[0-9a-f]{12}',
'request_id': 'req-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}'
'-[0-9a-f]{4}-[0-9a-f]{12}',
'reservation_id': 'r-[0-9a-zA-Z]{8}',
'private_key': '(-----BEGIN RSA PRIVATE KEY-----|)'
'[a-zA-Z0-9\n/+=]*'

View File

@ -34,9 +34,11 @@ from nova.tests import uuidsentinel as uuids
FAKE_UUID = fake_server_actions.FAKE_UUID
FAKE_REQUEST_ID = fake_server_actions.FAKE_REQUEST_ID1
FAKE_EVENT_ID = fake_server_actions.FAKE_ACTION_ID1
FAKE_REQUEST_NOTFOUND_ID = 'req-' + uuids.req_not_found
def format_action(action):
def format_action(action, expect_traceback=True):
'''Remove keys that aren't serialized.'''
to_delete = ('id', 'finish_time', 'created_at', 'updated_at', 'deleted_at',
'deleted')
@ -47,14 +49,16 @@ def format_action(action):
# NOTE(danms): Without WSGI above us, these will be just stringified
action['start_time'] = str(action['start_time'].replace(tzinfo=None))
for event in action.get('events', []):
format_event(event)
format_event(event, expect_traceback)
return action
def format_event(event):
def format_event(event, expect_traceback=True):
'''Remove keys that aren't serialized.'''
to_delete = ('id', 'created_at', 'updated_at', 'deleted_at', 'deleted',
'action_id')
to_delete = ['id', 'created_at', 'updated_at', 'deleted_at', 'deleted',
'action_id']
if not expect_traceback:
to_delete.append('traceback')
for key in to_delete:
if key in event:
del(event[key])
@ -109,6 +113,7 @@ class InstanceActionsPolicyTestV21(test.NoDBTestCase):
class InstanceActionsTestV21(test.NoDBTestCase):
instance_actions = instance_actions_v21
wsgi_api_version = os_wsgi.DEFAULT_API_VERSION
expect_events_non_admin = False
def fake_get(self, context, instance_uuid, expected_attrs=None):
return objects.Instance(uuid=instance_uuid)
@ -188,8 +193,13 @@ class InstanceActionsTestV21(test.NoDBTestCase):
req = self._get_http_req('os-instance-actions/1')
res_dict = self.controller.show(req, FAKE_UUID, FAKE_REQUEST_ID)
fake_action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID]
self.assertEqual(format_action(fake_action),
format_action(res_dict['instanceAction']))
if self.expect_events_non_admin:
fake_event = fake_server_actions.FAKE_EVENTS[FAKE_EVENT_ID]
fake_action['events'] = copy.deepcopy(fake_event)
# By default, non-admins are not allowed to see traceback details.
self.assertEqual(format_action(fake_action, expect_traceback=False),
format_action(res_dict['instanceAction'],
expect_traceback=False))
def test_action_not_found(self):
def fake_no_action(context, uuid, action_id):
@ -223,3 +233,57 @@ class InstanceActionsTestV221(InstanceActionsTestV21):
def fake_get(self, context, instance_uuid, expected_attrs=None):
self.assertEqual('yes', context.read_deleted)
return objects.Instance(uuid=instance_uuid)
class InstanceActionsTestV251(InstanceActionsTestV221):
wsgi_api_version = "2.51"
expect_events_non_admin = True
class InstanceActionsTestV258(InstanceActionsTestV251):
wsgi_api_version = "2.58"
@mock.patch('nova.objects.InstanceActionList.get_by_instance_uuid')
def test_get_action_with_invalid_marker(self, mock_actions_get):
"""Tests detail paging with an invalid marker (not found)."""
mock_actions_get.side_effect = exception.MarkerNotFound(
marker=FAKE_REQUEST_NOTFOUND_ID)
req = self._get_http_req('os-instance-actions?'
'marker=%s' % FAKE_REQUEST_NOTFOUND_ID)
self.assertRaises(exc.HTTPBadRequest,
self.controller.index, req, FAKE_UUID)
def test_get_action_with_invalid_limit(self):
"""Tests get paging with an invalid limit."""
req = self._get_http_req('os-instance-actions?limit=x')
self.assertRaises(exception.ValidationError,
self.controller.index, req)
req = self._get_http_req('os-instance-actions?limit=-1')
self.assertRaises(exception.ValidationError,
self.controller.index, req)
def test_get_action_with_invalid_change_since(self):
"""Tests get paging with a invalid change_since."""
req = self._get_http_req('os-instance-actions?'
'changes-since=wrong_time')
ex = self.assertRaises(exception.ValidationError,
self.controller.index, req)
self.assertIn('Invalid input for query parameters changes-since',
six.text_type(ex))
def test_get_action_with_invalid_params(self):
"""Tests get paging with a invalid change_since."""
req = self._get_http_req('os-instance-actions?'
'wrong_params=xxx')
ex = self.assertRaises(exception.ValidationError,
self.controller.index, req)
self.assertIn('Additional properties are not allowed',
six.text_type(ex))
def test_get_action_with_multi_params(self):
"""Tests get paging with multi markers."""
req = self._get_http_req('os-instance-actions?marker=A&marker=B')
ex = self.assertRaises(exception.ValidationError,
self.controller.index, req)
self.assertIn('Invalid input for query parameters marker',
six.text_type(ex))

View File

@ -0,0 +1,8 @@
---
features:
- |
Add pagination support and ``changes-since`` filter for os-instance-actions
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.