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:
parent
d110ec5961
commit
0c480d795f
@ -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)**
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.57",
|
||||
"version": "2.58",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.57",
|
||||
"version": "2.58",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
29
nova/api/openstack/compute/schemas/instance_actions.py
Normal file
29
nova/api/openstack/compute/schemas/instance_actions.py
Normal 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
|
||||
}
|
23
nova/api/openstack/compute/views/instance_actions.py
Normal file
23
nova/api/openstack/compute/views/instance_actions.py
Normal 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')
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
|
@ -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/+=]*'
|
||||
|
@ -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))
|
||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user