Add host/hostId to instance action events API

This patch adds a new microversion to
``GET /servers/{server_id}/os-instance-actions/{req_id}`` API to
include the ``host`` field for admin and an ``hostId`` for all users
by default. And the display of newly added ``host`` field will be
controlled by the same policy as the ``traceback`` field.

The newly added fields can be used to determine on which host a
given action event occurred.

Part of blueprint: add-host-to-instance-action-events

Change-Id: I2f8b4a12a088b9ed96b428eafde2e0c478fb1db5
This commit is contained in:
Yikun Jiang 2018-03-28 11:59:09 +08:00 committed by Eric Fried
parent 577cb4b2f4
commit c2f7d65858
24 changed files with 366 additions and 22 deletions

View File

@ -111,14 +111,16 @@ Response
- events.finish_time: event_finish_time
- events.result: event_result
- events.traceback: event_traceback
- events.hostId: event_hostId
- events.host: event_host
- updated_at: updated_instance_action
**Example Show Server Action Details For Admin (v2.1)**
**Example Show Server Action Details For Admin (v2.62)**
.. literalinclude:: ../../doc/api_samples/os-instance-actions/instance-action-get-resp.json
.. literalinclude:: ../../doc/api_samples/os-instance-actions/v2.62/instance-action-get-resp.json
:language: javascript
**Example Show Server Action Details For Non-Admin (v2.51)**
**Example Show Server Action Details For Non-Admin (v2.62)**
.. literalinclude:: ../../doc/api_samples/os-instance-actions/v2.51/instance-action-get-non-admin-resp.json
.. literalinclude:: ../../doc/api_samples/os-instance-actions/v2.62/instance-action-get-non-admin-resp.json
:language: javascript

View File

@ -2357,6 +2357,29 @@ event_finish_time:
in: body
required: true
type: string
event_host:
min_version: 2.62
description: |
The name of the host on which the event occurred.
Policy defaults enable only users with the administrative role to see
an instance action event host. Cloud providers can change these
permissions through the ``policy.json`` file.
in: body
required: false
type: string
event_hostId:
min_version: 2.62
description: |
An obfuscated hashed host ID string, or the empty string if there is no
host for the event. This is a hashed value so will not actually look like
a hostname, and is hashed with data from the project_id, so the same
physical host as seen by two different project_ids will be different.
This is useful when within the same project you need to determine if two
events occurred on the same or different physical hosts.
in: body
required: true
type: string
event_name:
description: |
The event name. A valid value is ``network-changed``, ``network-vif-plugged``,

View File

@ -0,0 +1,21 @@
{
"instanceAction": {
"action": "stop",
"events": [
{
"event": "compute_stop_instance",
"finish_time": "2018-04-25T01:26:34.784165",
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
"result": "Success",
"start_time": "2018-04-25T01:26:34.612020"
}
],
"instance_uuid": "79edaa44-ad4f-4af7-b994-154518c2b927",
"message": null,
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-8eb28d4a-db6c-4337-bab8-ce154e9c620e",
"start_time": "2018-04-25T01:26:34.388280",
"updated_at": "2018-04-25T01:26:34.784165",
"user_id": "fake"
}
}

View File

@ -0,0 +1,23 @@
{
"instanceAction": {
"action": "stop",
"events": [
{
"event": "compute_stop_instance",
"finish_time": "2018-04-25T01:26:36.790544",
"host": "compute",
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
"result": "Success",
"start_time": "2018-04-25T01:26:36.539271",
"traceback": null
}
],
"instance_uuid": "4bf3473b-d550-4b65-9409-292d44ab14a2",
"message": null,
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-0d819d5c-1527-4669-bdf0-ffad31b5105b",
"start_time": "2018-04-25T01:26:36.341290",
"updated_at": "2018-04-25T01:26:36.790544",
"user_id": "admin"
}
}

View File

@ -0,0 +1,24 @@
{
"instanceActions": [
{
"action": "stop",
"instance_uuid": "15835b6f-1e14-4cfa-9f66-1abea1a1c0d5",
"message": null,
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-f04d4b92-6241-42da-b82d-2cedb225c58d",
"start_time": "2018-04-25T01:26:36.036697",
"updated_at": "2018-04-25T01:26:36.525308",
"user_id": "admin"
},
{
"action": "create",
"instance_uuid": "15835b6f-1e14-4cfa-9f66-1abea1a1c0d5",
"message": null,
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-d8790618-9bbf-4df0-8af8-fc9e24de29c0",
"start_time": "2018-04-25T01:26:33.692125",
"updated_at": "2018-04-25T01:26:35.993821",
"user_id": "admin"
}
]
}

View File

@ -0,0 +1,20 @@
{
"instanceActions": [
{
"action": "stop",
"instance_uuid": "7a580cc0-3469-441a-9736-d5fce91003f9",
"message": null,
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-b8ffb713-61a2-4e7c-a705-37052cba9d6e",
"start_time": "2018-04-25T01:26:28.955571",
"updated_at": "2018-04-25T01:26:29.414973",
"user_id": "fake"
}
],
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/7a580cc0-3469-441a-9736-d5fce91003f9/os-instance-actions?limit=1&marker=req-b8ffb713-61a2-4e7c-a705-37052cba9d6e",
"rel": "next"
}
]
}

View File

@ -0,0 +1,14 @@
{
"instanceActions": [
{
"action": "create",
"instance_uuid": "9bde1fd5-8435-45c5-afc1-bedd0605275b",
"message": null,
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-4510fb10-447f-4572-a64d-c2324547d86c",
"start_time": "2018-04-25T01:26:33.710291",
"updated_at": "2018-04-25T01:26:35.374936",
"user_id": "fake"
}
]
}

View File

@ -0,0 +1,14 @@
{
"instanceActions": [
{
"action": "stop",
"instance_uuid": "2150964c-30fe-4214-9547-8822375aa7d0",
"message": null,
"project_id": "6f70656e737461636b20342065766572",
"request_id": "req-0c3b2079-0a44-474d-a5b2-7466d4b4c642",
"start_time": "2018-04-25T01:26:29.594237",
"updated_at": "2018-04-25T01:26:30.065061",
"user_id": "admin"
}
]
}

View File

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

View File

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

View File

@ -146,6 +146,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 2.61 - Exposes flavor extra_specs in the flavor representation. Flavor
extra_specs will be included in Response body of GET, POST, PUT
/flavors APIs.
* 2.62 - Add ``host`` and ``hostId`` fields to instance action detail API
responses.
"""
# The minimum and maximum versions of the API supported
@ -154,7 +156,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.61"
_MAX_API_VERSION = "2.62"
DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal

View File

@ -53,13 +53,20 @@ class InstanceActionsController(wsgi.Controller):
action[key] = action_raw.get(key)
return action
def _format_event(self, event_raw, show_traceback=False):
def _format_event(self, event_raw, project_id, show_traceback=False,
show_host=False, show_hostid=False):
event = {}
for key in EVENT_KEYS:
# By default, non-admins are not allowed to see traceback details.
if key == 'traceback' and not show_traceback:
continue
event[key] = event_raw.get(key)
# By default, non-admins are not allowed to see host.
if show_host:
event['host'] = event_raw['host']
if show_hostid:
event['hostId'] = utils.generate_hostid(event_raw['host'],
project_id)
return event
@wsgi.Controller.api_version("2.1", "2.20")
@ -138,18 +145,26 @@ class InstanceActionsController(wsgi.Controller):
# by default.
show_events = False
show_traceback = False
show_host = False
if context.can(ia_policies.POLICY_ROOT % 'events', fatal=False):
# For all microversions, the user can see all event details
# including the traceback.
show_events = show_traceback = True
show_host = api_version_request.is_supported(req, '2.62')
elif api_version_request.is_supported(req, '2.51'):
# The user is not able to see all event details, but they can at
# least see the non-traceback event details.
show_events = True
# An obfuscated hashed host id is returned since microversion 2.62
# for all users.
show_hostid = api_version_request.is_supported(req, '2.62')
if show_events:
events_raw = self.action_api.action_events_get(context, instance,
action_id)
action['events'] = [self._format_event(evt, show_traceback)
for evt in events_raw]
action['events'] = [self._format_event(
evt, action['project_id'], show_traceback=show_traceback,
show_host=show_host, show_hostid=show_hostid
) for evt in events_raw]
return {'instanceAction': action}

View File

@ -780,3 +780,14 @@ Response body of the following APIs:
* ``GET /flavors/{flavor_id}``
* ``POST /flavors``
* ``PUT /flavors/{flavor_id}``
2.62
----
Adds ``host`` (hostname) and ``hostId`` (an obfuscated hashed host id string)
fields to the instance action
``GET /servers/{server_id}/os-instance-actions/{req_id}`` API. The display of
the newly added ``host`` field will be controlled via policy rule
``os_compute_api:os-instance-actions:events``, which is the same policy used
for the ``events.traceback`` field. If the user is prevented by policy, only
``hostId`` will be displayed.

View File

@ -31,7 +31,10 @@ instance_actions_policies = [
This check is performed only after the check
os_compute_api:os-instance-actions passes. Beginning with
Microversion 2.51, events details are always included; traceback
information is provided per event if policy enforcement passes.""",
information is provided per event if policy enforcement passes.
Beginning with Microversion 2.62, each event includes a hashed
host identifier and, if policy enforcement passes, the name of
the host.""",
[
{
'method': 'GET',

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": null,
"events": [
{
"event": "compute_stop_instance",
"start_time": "%(strtime)s",
"finish_time": "%(strtime)s",
"result": "Success",
"hostId": "%(event_hostId)s"
}
]
}
}

View File

@ -0,0 +1,23 @@
{
"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": null,
"events": [
{
"event": "compute_stop_instance",
"start_time": "%(strtime)s",
"finish_time": "%(strtime)s",
"result": "Success",
"traceback": null,
"host": "%(event_host)s",
"hostId": "%(event_hostId)s"
}
]
}
}

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": null
},
{
"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": null
}
]
}

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": null
}
],
"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": null
}
]
}

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": null
}
]
}

View File

@ -122,3 +122,20 @@ class ServerActionsV258SampleJsonTest(ServerActionsV251AdminSampleJsonTest):
class ServerActionsV258NonAdminSampleJsonTest(ServerActionsV258SampleJsonTest):
ADMIN_API = False
class ServerActionsV262SampleJsonTest(ServerActionsV258SampleJsonTest):
microversion = '2.62'
scenarios = [('v2_62', {'api_major_version': 'v2.1'})]
def _get_subs(self):
return {
'uuid': self.uuid,
'project_id': self.action_stop['project_id'],
'event_host': r'\w+',
'event_hostId': '[a-f0-9]+'
}
class ServerActionsV262NonAdminSampleJsonTest(ServerActionsV262SampleJsonTest):
ADMIN_API = False

View File

@ -38,7 +38,8 @@ FAKE_EVENT_ID = fake_server_actions.FAKE_ACTION_ID1
FAKE_REQUEST_NOTFOUND_ID = 'req-' + uuids.req_not_found
def format_action(action, expect_traceback=True):
def format_action(action, expect_traceback=True, expect_host=False,
expect_hostId=False):
'''Remove keys that aren't serialized.'''
to_delete = ('id', 'finish_time', 'created_at', 'updated_at', 'deleted_at',
'deleted')
@ -49,11 +50,14 @@ def format_action(action, expect_traceback=True):
# 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, expect_traceback)
format_event(event, action.get('project_id'),
expect_traceback=expect_traceback,
expect_host=expect_host, expect_hostId=expect_hostId)
return action
def format_event(event, expect_traceback=True, expect_host=False):
def format_event(event, project_id, expect_traceback=True, expect_host=False,
expect_hostId=False):
'''Remove keys that aren't serialized.'''
to_delete = ['id', 'created_at', 'updated_at', 'deleted_at', 'deleted',
'action_id']
@ -61,6 +65,8 @@ def format_event(event, expect_traceback=True, expect_host=False):
to_delete.append('traceback')
if not expect_host:
to_delete.append('host')
if not expect_hostId:
to_delete.append('hostId')
for key in to_delete:
if key in event:
del(event[key])
@ -116,6 +122,8 @@ class InstanceActionsTestV21(test.NoDBTestCase):
instance_actions = instance_actions_v21
wsgi_api_version = os_wsgi.DEFAULT_API_VERSION
expect_events_non_admin = False
expect_event_hostId = False
expect_event_host = False
def fake_get(self, context, instance_uuid, expected_attrs=None):
return objects.Instance(uuid=instance_uuid)
@ -181,8 +189,12 @@ class InstanceActionsTestV21(test.NoDBTestCase):
fake_action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID]
fake_events = self.fake_events[fake_action['id']]
fake_action['events'] = fake_events
self.assertEqual(format_action(fake_action),
format_action(res_dict['instanceAction']))
self.assertEqual(format_action(fake_action,
expect_host=self.expect_event_host,
expect_hostId=self.expect_event_hostId),
format_action(res_dict['instanceAction'],
expect_host=self.expect_event_host,
expect_hostId=self.expect_event_hostId))
def test_get_action_with_events_not_allowed(self):
def fake_get_action(context, uuid, request_id):
@ -201,10 +213,16 @@ class InstanceActionsTestV21(test.NoDBTestCase):
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),
# By default, non-admins are not allowed to see traceback details
# and event host.
self.assertEqual(format_action(fake_action,
expect_traceback=False,
expect_host=False,
expect_hostId=self.expect_event_hostId),
format_action(res_dict['instanceAction'],
expect_traceback=False))
expect_traceback=False,
expect_host=False,
expect_hostId=self.expect_event_hostId))
def test_action_not_found(self):
def fake_no_action(context, uuid, action_id):
@ -294,3 +312,9 @@ class InstanceActionsTestV258(InstanceActionsTestV251):
self.controller.index, req)
self.assertIn('Invalid input for query parameters marker',
six.text_type(ex))
class InstanceActionsTestV262(InstanceActionsTestV251):
wsgi_api_version = "2.62"
expect_event_hostId = True
expect_event_host = True

View File

@ -20,6 +20,8 @@ FAKE_REQUEST_ID1 = 'req-3293a3f1-b44c-4609-b8d2-d81b105636b8'
FAKE_REQUEST_ID2 = 'req-25517360-b757-47d3-be45-0e8d2a01b36a'
FAKE_ACTION_ID1 = 123
FAKE_ACTION_ID2 = 456
FAKE_HOST_ID1 = '74824069503a752aaa3abf194f73200fcdd117ef70ab28b576e5bf7a'
FAKE_HOST_ID2 = '858f5ed465b4967dd1306a38078e9b83b8705bdedfa7f16f898119b4'
FAKE_ACTIONS = {
FAKE_UUID: {
@ -70,7 +72,8 @@ FAKE_EVENTS = {
'updated_at': None,
'deleted_at': None,
'deleted': False,
'host': 'host1'
'host': 'host1',
'hostId': FAKE_HOST_ID1
},
{'id': 2,
'action_id': FAKE_ACTION_ID1,
@ -85,7 +88,8 @@ FAKE_EVENTS = {
'updated_at': None,
'deleted_at': None,
'deleted': False,
'host': 'host1'
'host': 'host1',
'hostId': FAKE_HOST_ID1
}
],
FAKE_ACTION_ID2: [{'id': 3,
@ -101,7 +105,8 @@ FAKE_EVENTS = {
'updated_at': None,
'deleted_at': None,
'deleted': False,
'host': 'host2'
'host': 'host2',
'hostId': FAKE_HOST_ID2
}
]
}

View File

@ -0,0 +1,10 @@
---
features:
- |
The microversion 2.62 adds ``host`` (hostname) and ``hostId`` (an
obfuscated hashed host id string) fields to the instance action
``GET /servers/{server_id}/os-instance-actions/{req_id}`` API. The display
of the newly added ``host`` field will be controlled via policy rule
``os_compute_api:os-instance-actions:events``, which is the same policy
used for the ``events.traceback`` field. If the user is prevented by
policy, only ``hostId`` will be displayed.