API endpoints to get node history
Adds API for retrieving node history events via a node. Includes pagination and limitation of the response set. Story: 2002980 Tas: 42961 Change-Id: I22a92fa6c30d721f6a5dd0670b2e0a9cf76ad7b1
This commit is contained in:
parent
20503d94e5
commit
fb9eae7412
76
api-ref/source/baremetal-api-v1-nodes-history.inc
Normal file
76
api-ref/source/baremetal-api-v1-nodes-history.inc
Normal file
@ -0,0 +1,76 @@
|
||||
.. -*- rst -*-
|
||||
|
||||
================
|
||||
History of nodes
|
||||
================
|
||||
|
||||
.. versionadded:: 1.78
|
||||
|
||||
Identifying history of events from nodes is available via API version 1.78 via
|
||||
the ``v1/nodes/{node_ident}/history`` endpoint. In default policy
|
||||
configuration, only "System" scoped users as well as owners who are listed
|
||||
owners of associated nodes can list and retrieve nodes.
|
||||
|
||||
List history entries for a node
|
||||
===============================
|
||||
|
||||
.. rest_method:: GET /v1/nodes/{node_ident}/history
|
||||
|
||||
Normal response code: 200
|
||||
|
||||
Error codes: 400,401,403,404
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- node_ident: node_ident
|
||||
- detail: detail
|
||||
- marker: marker
|
||||
- limit: limit
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- history: n_history
|
||||
|
||||
**Example list of history events from a node:**
|
||||
|
||||
.. literalinclude:: samples/node-history-list-response.json
|
||||
:language: javascript
|
||||
|
||||
Retrieve a specific history entry
|
||||
=================================
|
||||
|
||||
.. rest_method:: GET /v1/nodes/{node_ident}/history/{history_event_uuid}
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- node_ident: node_ident
|
||||
- history_event_uuid: history_event_ident
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- uuid: uuid
|
||||
- created_at: created_at
|
||||
- user: history_user_ident
|
||||
- severity: history_severity
|
||||
- event: history_event
|
||||
- event_type: history_event_type
|
||||
- conductor: hostname
|
||||
|
||||
Deleting history entries for a node
|
||||
===================================
|
||||
|
||||
Due to the nature of an immutable history record, records cannot be deleted
|
||||
via the REST API. Records and ultimately expired history records are managed
|
||||
by the conductor.
|
@ -27,6 +27,7 @@
|
||||
.. include:: baremetal-api-v1-allocation.inc
|
||||
.. include:: baremetal-api-v1-node-allocation.inc
|
||||
.. include:: baremetal-api-v1-deploy-templates.inc
|
||||
.. include:: baremetal-api-v1-nodes-history.inc
|
||||
.. NOTE(dtantsur): keep chassis close to the end since it's semi-deprecated
|
||||
.. include:: baremetal-api-v1-chassis.inc
|
||||
.. NOTE(dtantsur): keep misc last, since it covers internal API
|
||||
|
@ -74,6 +74,12 @@ driver_ident:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
history_event_ident:
|
||||
description: |
|
||||
The UUID of a history event.
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
hostname_ident:
|
||||
description: |
|
||||
The hostname of the conductor.
|
||||
@ -971,6 +977,36 @@ fault:
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
history_event:
|
||||
description: |
|
||||
The event message body which has been logged related to the node for
|
||||
this error.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
history_event_type:
|
||||
description: |
|
||||
Short descriptive string to indicate where the error occurred at to
|
||||
enable API users/System Operators to be able to identify repeated
|
||||
issues in a particular area of operation, such as 'deployment',
|
||||
'console', 'cleaning', 'monitoring'.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
history_severity:
|
||||
description: |
|
||||
Severity indicator for the event being returned. Typically this will
|
||||
indicate if this was an Error or Informational entry.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
history_user_ident:
|
||||
description: |
|
||||
The UUID value representing the user whom appears to have caused
|
||||
the recorded event.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
hostname:
|
||||
description: |
|
||||
The hostname of this conductor.
|
||||
@ -1122,6 +1158,12 @@ n_description:
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
n_history:
|
||||
description: |
|
||||
History events attached to this node.
|
||||
in: body
|
||||
required: true
|
||||
type: array
|
||||
n_ind_state:
|
||||
description: |
|
||||
The state of an indicator of the component of the node. Possible values
|
||||
|
16
api-ref/source/samples/node-history-list-response.json
Normal file
16
api-ref/source/samples/node-history-list-response.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"history": [
|
||||
{
|
||||
"uuid": "e5840e39-b4ba-4a93-8071-cff9aa2c9633",
|
||||
"created_at": "2021-09-15T17:45:04.686541+00:00",
|
||||
"severity": "ERROR",
|
||||
"event": "Something is wrong",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://localhost/v1/nodes/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/history/e5840e39-b4ba-4a93-8071-cff9aa2c9633",
|
||||
"rel": "self"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -2,8 +2,17 @@
|
||||
REST API Version History
|
||||
========================
|
||||
|
||||
1.77
|
||||
1.78 (Xena, ?)
|
||||
----------------------
|
||||
Add endpoints to allow history events for nodes to be retrieved via
|
||||
the REST API.
|
||||
|
||||
* ``GET /v1/nodes/{node_ident}/history/``
|
||||
* ``GET /v1/nodes/{node_ident}/history/{event_uuid}``
|
||||
|
||||
1.77 (Xena, ?)
|
||||
----------------------
|
||||
|
||||
Add a fields selector to the the Drivers list:
|
||||
* ``GET /v1/drivers?fields=``
|
||||
Also add a fields selector to the the Driver detail:
|
||||
|
@ -1840,6 +1840,107 @@ class NodeVIFController(rest.RestController):
|
||||
vif_id=vif_id, topic=topic)
|
||||
|
||||
|
||||
class NodeHistoryController(rest.RestController):
|
||||
|
||||
detail_fields = ['uuid', 'created_at', 'severity', 'event_type',
|
||||
'event', 'conductor', 'user']
|
||||
|
||||
standard_fields = ['uuid', 'created_at', 'severity', 'event']
|
||||
|
||||
def __init__(self, node_ident):
|
||||
super(NodeHistoryController).__init__()
|
||||
self.node_ident = node_ident
|
||||
|
||||
def _history_event_convert_with_links(self, node_uuid, event,
|
||||
detail=False):
|
||||
"""Add link and convert history event"""
|
||||
url = api.request.public_url
|
||||
if not detail:
|
||||
fields = self.standard_fields
|
||||
else:
|
||||
fields = self.detail_fields
|
||||
|
||||
event_entry = api_utils.object_to_dict(
|
||||
event,
|
||||
link_resource='nodes',
|
||||
fields=fields)
|
||||
if not detail:
|
||||
# The spec for this feature calls to truncate the event
|
||||
# field if not detailed, which makes sense in some environments
|
||||
# with many events, espescialy if the event text is particullarlly
|
||||
# long.
|
||||
entry_len = len(event_entry['event'])
|
||||
if entry_len > 255:
|
||||
event_entry['event'] = event_entry['event'][0:251] + '...'
|
||||
else:
|
||||
event_entry['event'] = event_entry['event'][0:entry_len]
|
||||
# These records cannot be changed by the API consumer,
|
||||
# and updated_at gets handed up from the db model
|
||||
# regardless if we want it or not. As such, strip from
|
||||
# the reply.
|
||||
event_entry.pop('updated_at')
|
||||
event_entry['links'] = [
|
||||
link.make_link(
|
||||
'self', url,
|
||||
'nodes',
|
||||
'%s/history/%s' % (node_uuid, event.uuid)
|
||||
)
|
||||
]
|
||||
return event_entry
|
||||
|
||||
@METRICS.timer('NodeHistoryController.get_all')
|
||||
@method.expose()
|
||||
@args.validate(details=args.boolean, marker=args.uuid, limit=args.integer)
|
||||
def get_all(self, **kwargs):
|
||||
"""List node history."""
|
||||
node = api_utils.check_node_policy_and_retrieve(
|
||||
'baremetal:node:history:get', self.node_ident)
|
||||
|
||||
if kwargs.get('detail'):
|
||||
detail = True
|
||||
fields = self.detail_fields
|
||||
else:
|
||||
detail = False
|
||||
fields = self.standard_fields
|
||||
|
||||
marker_obj = None
|
||||
marker = kwargs.get('marker')
|
||||
if marker:
|
||||
marker_obj = objects.NodeHistory.get_by_uuid(api.request.context,
|
||||
marker)
|
||||
limit = kwargs.get('limit')
|
||||
|
||||
events = objects.NodeHistory.list_by_node_id(api.request.context,
|
||||
node.id,
|
||||
marker=marker_obj,
|
||||
limit=limit)
|
||||
|
||||
return collection.list_convert_with_links(
|
||||
items=[
|
||||
self._history_event_convert_with_links(
|
||||
node.uuid, event, detail=detail) for event in events
|
||||
],
|
||||
item_name='history',
|
||||
fields=fields,
|
||||
marker=marker_obj,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
@METRICS.timer('NodeHistoryController.get_one')
|
||||
@method.expose()
|
||||
@args.validate(event=args.uuid_or_name)
|
||||
def get_one(self, event):
|
||||
"""Get a node history entry"""
|
||||
node = api_utils.check_node_policy_and_retrieve(
|
||||
'baremetal:node:history:get', self.node_ident)
|
||||
# TODO(TheJulia): Need to check policy to make sure if policy
|
||||
# check fails, that the entry cannot be found.
|
||||
event = objects.NodeHistory.get_by_uuid(api.request.context,
|
||||
event)
|
||||
return self._history_event_convert_with_links(
|
||||
node.uuid, event, detail=True)
|
||||
|
||||
|
||||
class NodesController(rest.RestController):
|
||||
"""REST controller for Nodes."""
|
||||
|
||||
@ -1885,6 +1986,7 @@ class NodesController(rest.RestController):
|
||||
'traits': NodeTraitsController,
|
||||
'bios': bios.NodeBiosController,
|
||||
'allocation': allocation.NodeAllocationController,
|
||||
'history': NodeHistoryController,
|
||||
}
|
||||
|
||||
@pecan.expose()
|
||||
@ -1906,7 +2008,9 @@ class NodesController(rest.RestController):
|
||||
or (remainder[0] == 'bios'
|
||||
and not api_utils.allow_bios_interface())
|
||||
or (remainder[0] == 'allocation'
|
||||
and not api_utils.allow_allocations())):
|
||||
and not api_utils.allow_allocations())
|
||||
or (remainder[0] == 'history'
|
||||
and not api_utils.allow_node_history())):
|
||||
pecan.abort(http_client.NOT_FOUND)
|
||||
if remainder[0] == 'traits' and not api_utils.allow_traits():
|
||||
# NOTE(mgoddard): Returning here will ensure we exhibit the
|
||||
|
@ -1334,6 +1334,11 @@ def allow_reset_interfaces():
|
||||
return api.request.version.minor >= versions.MINOR_45_RESET_INTERFACES
|
||||
|
||||
|
||||
def allow_node_history():
|
||||
"""Check if node history access is permitted by API version."""
|
||||
return api.request.version.minor >= versions.MINOR_78_NODE_HISTORY
|
||||
|
||||
|
||||
def get_request_return_fields(fields, detail, default_fields,
|
||||
check_detail_version=allow_detail_query,
|
||||
check_fields_version=None):
|
||||
|
@ -115,6 +115,7 @@ BASE_VERSION = 1
|
||||
# v1.75: Add boot_mode, secure_boot fields to node object.
|
||||
# v1.76: Add support for changing boot_mode and secure_boot state
|
||||
# v1.77: Add fields selector to drivers list and driver detail.
|
||||
# v1.78: Add node history endpoint
|
||||
|
||||
MINOR_0_JUNO = 0
|
||||
MINOR_1_INITIAL_VERSION = 1
|
||||
@ -194,6 +195,7 @@ MINOR_74_BIOS_REGISTRY = 74
|
||||
MINOR_75_NODE_BOOT_MODE = 75
|
||||
MINOR_76_NODE_CHANGE_BOOT_MODE = 76
|
||||
MINOR_77_DRIVER_FIELDS_SELECTOR = 77
|
||||
MINOR_78_NODE_HISTORY = 78
|
||||
|
||||
# When adding another version, update:
|
||||
# - MINOR_MAX_VERSION
|
||||
@ -201,7 +203,7 @@ MINOR_77_DRIVER_FIELDS_SELECTOR = 77
|
||||
# explanation of what changed in the new version
|
||||
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
||||
|
||||
MINOR_MAX_VERSION = MINOR_77_DRIVER_FIELDS_SELECTOR
|
||||
MINOR_MAX_VERSION = MINOR_78_NODE_HISTORY
|
||||
|
||||
# String representations of the minor and maximum versions
|
||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||
|
@ -420,7 +420,6 @@ deprecated_bios_disable_cleaning = policy.DeprecatedRule(
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
)
|
||||
|
||||
|
||||
node_policies = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:create',
|
||||
@ -911,6 +910,24 @@ node_policies = [
|
||||
],
|
||||
deprecated_rule=deprecated_bios_disable_cleaning
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:history:get',
|
||||
check_str=SYSTEM_OR_OWNER_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Filter to allow operators to retreive history records '
|
||||
'for a node.',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/history', 'method': 'GET'},
|
||||
{'path': '/nodes/{node_ident}/history/{event_ident}',
|
||||
'method': 'GET'}
|
||||
],
|
||||
# This rule fallsback to deprecated_node_get in order to provide a
|
||||
# mechanism so the additional policies only engage in an updated
|
||||
# operating context.
|
||||
deprecated_rule=deprecated_node_get
|
||||
),
|
||||
|
||||
|
||||
]
|
||||
|
||||
deprecated_port_reason = """
|
||||
|
@ -371,7 +371,7 @@ RELEASE_MAPPING = {
|
||||
}
|
||||
},
|
||||
'master': {
|
||||
'api': '1.77',
|
||||
'api': '1.78',
|
||||
'rpc': '1.55',
|
||||
'objects': {
|
||||
'Allocation': ['1.1'],
|
||||
|
@ -2319,7 +2319,7 @@ class Connection(api.Connection):
|
||||
raise exception.NodeHistoryNotFound(history=history_uuid)
|
||||
|
||||
def get_node_history_list(self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
sort_key='created_at', sort_dir='asc'):
|
||||
return _paginate_query(models.NodeHistory, limit, marker, sort_key,
|
||||
sort_dir)
|
||||
|
||||
|
@ -7720,3 +7720,132 @@ class TestTraits(test_api_base.BaseApiTest):
|
||||
headers={api_base.Version.string: "1.36"},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
|
||||
|
||||
|
||||
class TestNodeHistory(test_api_base.BaseApiTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNodeHistory, self).setUp()
|
||||
self.version = "1.78"
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
provision_state=states.AVAILABLE, name='node-54')
|
||||
self.node.save()
|
||||
self.node.obj_reset_changes()
|
||||
|
||||
def _add_history_entries(self):
|
||||
self.event1 = objects.NodeHistory(node_id=self.node.id, event='meow',
|
||||
conductor='cat-tree1',
|
||||
user='peaches')
|
||||
self.event1.create()
|
||||
self.event2 = objects.NodeHistory(node_id=self.node.id, event='purr',
|
||||
conductor='cat-tree2',
|
||||
user='sage')
|
||||
self.event2.create()
|
||||
self.event3 = objects.NodeHistory(node_id=self.node.id,
|
||||
event='g' + 'rrrr' * 64 + '!',
|
||||
conductor='cat-tree3',
|
||||
user='bella')
|
||||
self.event3.create()
|
||||
|
||||
def test_get_all_history(self):
|
||||
ret = self.get_json('/nodes/%s/history' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version})
|
||||
self.assertEqual({'history': []}, ret)
|
||||
|
||||
def test_get_all_old_version(self):
|
||||
ret = self.get_json('/nodes/%s/history' % self.node.uuid,
|
||||
headers={api_base.Version.string: "1.77"},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
|
||||
|
||||
def test_get_all_history_returns_entries(self):
|
||||
self._add_history_entries()
|
||||
ret = self.get_json('/nodes/%s/history' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version})
|
||||
self.assertIn('history', ret)
|
||||
entries = ret['history']
|
||||
self.assertEqual(3, len(entries))
|
||||
self.assertEqual('meow', entries[0]['event'])
|
||||
self.assertEqual('purr', entries[1]['event'])
|
||||
self.assertIn('grr', entries[2]['event'])
|
||||
self.assertNotIn('r!', entries[2]['event'])
|
||||
self.assertIn('...', entries[2]['event'])
|
||||
for entry in [0, 1, 2]:
|
||||
for field in ['conductor', 'user']:
|
||||
self.assertNotIn(field, entries[entry])
|
||||
self.assertIn('severity', entries[entry])
|
||||
|
||||
def test_get_all_history_returns_detail(self):
|
||||
self._add_history_entries()
|
||||
ret = self.get_json('/nodes/%s/history?detail=true' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version})
|
||||
self.assertIn('history', ret)
|
||||
entries = ret['history']
|
||||
self.assertEqual(3, len(entries))
|
||||
self.assertEqual('meow', entries[0]['event'])
|
||||
self.assertEqual('purr', entries[1]['event'])
|
||||
self.assertIn('grr', entries[2]['event'])
|
||||
self.assertIn('r!', entries[2]['event'])
|
||||
for entry in [0, 1, 2]:
|
||||
for field in ['conductor', 'user', 'severity', 'event_type']:
|
||||
self.assertIn(field, entries[entry])
|
||||
|
||||
def test_get_history_item(self):
|
||||
self._add_history_entries()
|
||||
record = self.get_json('/nodes/%s/history/%s' % (self.node.uuid,
|
||||
self.event1.uuid),
|
||||
headers={api_base.Version.string: self.version})
|
||||
self.assertEqual(8, len(record))
|
||||
expected_keys = ['created_at', 'links', 'event',
|
||||
'event_type', 'severity', 'user', 'uuid']
|
||||
for key in expected_keys:
|
||||
self.assertIn(key, record)
|
||||
self.assertNotIn('updated_at', record)
|
||||
self.assertEqual('cat-tree1', record['conductor'])
|
||||
self.assertEqual('meow', record['event'])
|
||||
self.assertEqual('peaches', record['user'])
|
||||
self.assertEqual(self.event1.uuid, record['uuid'])
|
||||
|
||||
def test_get_history_item_not_found(self):
|
||||
self._add_history_entries()
|
||||
ret = self.get_json('/nodes/%s/history/52949728-59fc-'
|
||||
'4651-84c8-b0a16b469372' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
|
||||
|
||||
def test_get_history_item_old_version(self):
|
||||
ret = self.get_json('/nodes/%s/history/1234' % self.node.uuid,
|
||||
headers={api_base.Version.string: "1.77"},
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, ret.status_code)
|
||||
|
||||
def test_get_all_pagination(self):
|
||||
self._add_history_entries()
|
||||
# First request, initial request with a limit of 1.
|
||||
ret = self.get_json('/nodes/%s/history?limit=1' % self.node.uuid,
|
||||
headers={api_base.Version.string: self.version})
|
||||
self.assertIn('history', ret)
|
||||
entries = ret['history']
|
||||
self.assertEqual(1, len(entries))
|
||||
result_uuid = entries[0]['uuid']
|
||||
self.assertEqual(self.event1.uuid, result_uuid)
|
||||
# Second request
|
||||
ret = self.get_json('/nodes/%s/history?limit=1&marker=%s' %
|
||||
(self.node.uuid, result_uuid),
|
||||
headers={api_base.Version.string: self.version})
|
||||
self.assertIn('history', ret)
|
||||
entries = ret['history']
|
||||
self.assertEqual(1, len(entries))
|
||||
result_uuid = entries[0]['uuid']
|
||||
self.assertEqual(self.event2.uuid, result_uuid)
|
||||
# Third request
|
||||
ret = self.get_json('/nodes/%s/history?limit=1&marker=%s' %
|
||||
(self.node.uuid, result_uuid),
|
||||
headers={api_base.Version.string: self.version})
|
||||
self.assertIn('history', ret)
|
||||
entries = ret['history']
|
||||
self.assertEqual(1, len(entries))
|
||||
result_uuid = entries[0]['uuid']
|
||||
self.assertEqual(self.event3.uuid, result_uuid)
|
||||
|
@ -275,7 +275,7 @@ class TestRBACModelBeforeScopesBase(TestACLBase):
|
||||
value=fake_setting)
|
||||
db_utils.create_test_node_trait(
|
||||
node_id=fake_db_node['id'])
|
||||
|
||||
fake_history = db_utils.create_test_history(node_id=fake_db_node.id)
|
||||
# dedicated node for portgroup addition test to avoid
|
||||
# false positives with test runners.
|
||||
db_utils.create_test_node(
|
||||
@ -298,6 +298,7 @@ class TestRBACModelBeforeScopesBase(TestACLBase):
|
||||
'trait': fake_trait,
|
||||
'volume_target_ident': fake_db_volume_target['uuid'],
|
||||
'volume_connector_ident': fake_db_volume_connector['uuid'],
|
||||
'history_ident': fake_history['uuid'],
|
||||
})
|
||||
|
||||
|
||||
@ -402,6 +403,8 @@ class TestRBACProjectScoped(TestACLBase):
|
||||
node_id=owned_node['id'],
|
||||
owner=owner_project_id,
|
||||
resource_class="CUSTOM_TEST")
|
||||
owned_node_history = db_utils.create_test_history(
|
||||
node_id=owned_node.id)
|
||||
|
||||
# Leased nodes
|
||||
fake_allocation_id = 61
|
||||
@ -428,6 +431,9 @@ class TestRBACProjectScoped(TestACLBase):
|
||||
owner=lessee_project_id,
|
||||
resource_class="CUSTOM_LEASED")
|
||||
|
||||
leased_node_history = db_utils.create_test_history(
|
||||
node_id=leased_node.id)
|
||||
|
||||
# Random objects that shouldn't be project visible
|
||||
other_port = db_utils.create_test_port(
|
||||
uuid='abfd8dbb-1732-449a-b760-2224035c6b99',
|
||||
@ -460,7 +466,9 @@ class TestRBACProjectScoped(TestACLBase):
|
||||
'other_portgroup_ident': other_pgroup['uuid'],
|
||||
'driver_name': 'fake-driverz',
|
||||
'owner_allocation': fake_owner_allocation['uuid'],
|
||||
'lessee_allocation': fake_leased_allocation['uuid']})
|
||||
'lessee_allocation': fake_leased_allocation['uuid'],
|
||||
'owned_history_ident': owned_node_history['uuid'],
|
||||
'lessee_history_ident': leased_node_history['uuid']})
|
||||
|
||||
@ddt.file_data('test_rbac_project_scoped.yaml')
|
||||
@ddt.unpack
|
||||
|
@ -2349,3 +2349,49 @@ chassis_chassis_id_delete_observer:
|
||||
headers: *observer_headers
|
||||
assert_status: 403
|
||||
deprecated: true
|
||||
|
||||
node_history_get_admin:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *admin_headers
|
||||
assert_status: 200
|
||||
deprecated: true
|
||||
assert_list_length:
|
||||
history: 1
|
||||
|
||||
node_history_get_member:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
node_history_get_observer:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *observer_headers
|
||||
assert_status: 200
|
||||
deprecated: true
|
||||
assert_list_length:
|
||||
history: 1
|
||||
|
||||
node_history_get_entry_admin:
|
||||
path: '/v1/nodes/{node_ident}/history/{history_ident}'
|
||||
method: get
|
||||
headers: *admin_headers
|
||||
assert_status: 200
|
||||
deprecated: true
|
||||
|
||||
node_history_get_entry_member:
|
||||
path: '/v1/nodes/{node_ident}/history/{history_ident}'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
node_history_get_entry_observer:
|
||||
path: '/v1/nodes/{node_ident}/history/{history_ident}'
|
||||
method: get
|
||||
headers: *observer_headers
|
||||
assert_status: 200
|
||||
deprecated: true
|
||||
|
@ -2629,3 +2629,95 @@ third_party_admin_cannot_create_chassis:
|
||||
body:
|
||||
description: 'test-chassis'
|
||||
assert_status: 500
|
||||
|
||||
# Node history entries
|
||||
|
||||
node_history_get_admin:
|
||||
path: '/v1/nodes/{owner_node_ident}/history'
|
||||
method: get
|
||||
headers: *owner_admin_headers
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
history: 1
|
||||
|
||||
node_history_get_member:
|
||||
path: '/v1/nodes/{owner_node_ident}/history'
|
||||
method: get
|
||||
headers: *owner_member_headers
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
history: 1
|
||||
|
||||
node_history_get_reader:
|
||||
path: '/v1/nodes/{owner_node_ident}/history'
|
||||
method: get
|
||||
headers: *owner_reader_headers
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
history: 1
|
||||
|
||||
node_history_get_entry_admin:
|
||||
path: '/v1/nodes/{owner_node_ident}/history/{owned_history_ident}'
|
||||
method: get
|
||||
headers: *owner_admin_headers
|
||||
assert_status: 200
|
||||
|
||||
node_history_get_entry_member:
|
||||
path: '/v1/nodes/{owner_node_ident}/history/{owned_history_ident}'
|
||||
method: get
|
||||
headers: *owner_member_headers
|
||||
assert_status: 200
|
||||
|
||||
node_history_get_entry_reader:
|
||||
path: '/v1/nodes/{owner_node_ident}/history/{owned_history_ident}'
|
||||
method: get
|
||||
headers: *owner_reader_headers
|
||||
assert_status: 200
|
||||
|
||||
lessee_node_history_get_admin:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *lessee_admin_headers
|
||||
assert_status: 404
|
||||
|
||||
lessee_node_history_get_member:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *lessee_member_headers
|
||||
assert_status: 404
|
||||
|
||||
lessee_node_history_get_reader:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *lessee_reader_headers
|
||||
assert_status: 404
|
||||
|
||||
lessee_node_history_get_entry_admin:
|
||||
path: '/v1/nodes/{node_ident}/history/{lessee_history_ident}'
|
||||
method: get
|
||||
headers: *lessee_admin_headers
|
||||
assert_status: 404
|
||||
|
||||
lessee_history_get_entry_member:
|
||||
path: '/v1/nodes/{node_ident}/history/{lessee_history_ident}'
|
||||
method: get
|
||||
headers: *lessee_member_headers
|
||||
assert_status: 404
|
||||
|
||||
lessee_node_history_get_entry_reader:
|
||||
path: '/v1/nodes/{node_ident}/history/{lessee_history_ident}'
|
||||
method: get
|
||||
headers: *lessee_reader_headers
|
||||
assert_status: 404
|
||||
|
||||
third_party_admin_cannot_get_node_history:
|
||||
path: '/v1/nodes/{owner_node_ident}'
|
||||
method: get
|
||||
headers: *third_party_admin_headers
|
||||
assert_status: 404
|
||||
|
||||
node_history_get_entry_admin:
|
||||
path: '/v1/nodes/{owner_node_ident}/history/{owned_history_ident}'
|
||||
method: get
|
||||
headers: *third_party_admin_headers
|
||||
assert_status: 404
|
||||
|
@ -2079,3 +2079,47 @@ chassis_chassis_id_delete_reader:
|
||||
method: delete
|
||||
headers: *reader_headers
|
||||
assert_status: 403
|
||||
|
||||
# Node history entries
|
||||
|
||||
node_history_get_admin:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *admin_headers
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
history: 1
|
||||
|
||||
node_history_get_member:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *scoped_member_headers
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
history: 1
|
||||
|
||||
node_history_get_reader:
|
||||
path: '/v1/nodes/{node_ident}/history'
|
||||
method: get
|
||||
headers: *reader_headers
|
||||
assert_status: 200
|
||||
assert_list_length:
|
||||
history: 1
|
||||
|
||||
node_history_get_entry_admin:
|
||||
path: '/v1/nodes/{node_ident}/history/{history_ident}'
|
||||
method: get
|
||||
headers: *admin_headers
|
||||
assert_status: 200
|
||||
|
||||
node_history_get_entry_member:
|
||||
path: '/v1/nodes/{node_ident}/history/{history_ident}'
|
||||
method: get
|
||||
headers: *scoped_member_headers
|
||||
assert_status: 200
|
||||
|
||||
node_history_get_entry_reader:
|
||||
path: '/v1/nodes/{node_ident}/history/{history_ident}'
|
||||
method: get
|
||||
headers: *reader_headers
|
||||
assert_status: 200
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds API version ``1.78`` which provides the capability to retrieve
|
||||
node history events which may have been recorded in the process of
|
||||
management of the node, which may be aid in troubleshooting or identifying
|
||||
a problem area with a specific node or configuration which has been
|
||||
supplied.
|
Loading…
Reference in New Issue
Block a user