Merge "Allow status_message updates for actions in SKIPPED state"
This commit is contained in:
@@ -192,6 +192,9 @@ action_state:
|
|||||||
action_status_message:
|
action_status_message:
|
||||||
description: |
|
description: |
|
||||||
Message with additional information about the Action state.
|
Message with additional information about the Action state.
|
||||||
|
This field can be set when transitioning an action to SKIPPED state,
|
||||||
|
or updated for actions that are already in SKIPPED state to provide
|
||||||
|
more detailed explanations, fix typos, or expand on initial reasons.
|
||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "replace",
|
||||||
|
"value": "Action skipped due to scheduled maintenance window",
|
||||||
|
"path": "/status_message"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"state": "SKIPPED",
|
||||||
|
"description": "Migrate instance to another compute node",
|
||||||
|
"parents": [
|
||||||
|
"b4529294-1de6-4302-b57a-9b5d5dc363c6"
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"rel": "self",
|
||||||
|
"href": "http://controller:9322/v1/actions/54acc7a0-91b0-46ea-a5f7-4ae2b9df0b0a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rel": "bookmark",
|
||||||
|
"href": "http://controller:9322/actions/54acc7a0-91b0-46ea-a5f7-4ae2b9df0b0a"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"action_plan_uuid": "4cbc4ede-0d25-481b-b86e-998dbbd4f8bf",
|
||||||
|
"uuid": "54acc7a0-91b0-46ea-a5f7-4ae2b9df0b0a",
|
||||||
|
"deleted_at": null,
|
||||||
|
"updated_at": "2018-04-10T12:20:15.123456+00:00",
|
||||||
|
"input_parameters": {
|
||||||
|
"migration_type": "live",
|
||||||
|
"destination_node": "compute-2",
|
||||||
|
"resource_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef"
|
||||||
|
},
|
||||||
|
"action_type": "migrate",
|
||||||
|
"created_at": "2018-04-10T11:59:12.725147+00:00",
|
||||||
|
"status_message": "Action skipped by user. Reason: Action skipped due to scheduled maintenance window"
|
||||||
|
}
|
||||||
@@ -210,4 +210,53 @@ Response
|
|||||||
**Example JSON representation of a skipped Action:**
|
**Example JSON representation of a skipped Action:**
|
||||||
|
|
||||||
.. literalinclude:: samples/action-skip-response.json
|
.. literalinclude:: samples/action-skip-response.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
Update Action Status Message
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. rest_method:: PATCH /v1/actions/{action_ident}
|
||||||
|
|
||||||
|
Updates the status_message of an Action that is already in SKIPPED state.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The status_message field can only be updated for Actions that are currently
|
||||||
|
in SKIPPED state. This allows administrators to fix typos, provide more
|
||||||
|
detailed explanations, or expand on reasons that were initially omitted.
|
||||||
|
This operation requires API microversion 1.5 or later.
|
||||||
|
|
||||||
|
Normal response codes: 200
|
||||||
|
|
||||||
|
Error codes: 400,404,403,409
|
||||||
|
|
||||||
|
Request
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- action_ident: action_ident
|
||||||
|
|
||||||
|
**Example status_message update request for a SKIPPED action:**
|
||||||
|
|
||||||
|
.. literalinclude:: samples/action-update-status-message-request.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
Response
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- uuid: uuid
|
||||||
|
- action_type: action_type
|
||||||
|
- state: action_state
|
||||||
|
- action_plan_uuid: action_action_plan_uuid
|
||||||
|
- parents: action_parents
|
||||||
|
- description: action_description
|
||||||
|
- input_parameters: action_input_parameters
|
||||||
|
- links: links
|
||||||
|
- status_message: action_status_message
|
||||||
|
|
||||||
|
**Example JSON representation of an Action with updated status_message:**
|
||||||
|
|
||||||
|
.. literalinclude:: samples/action-update-status-message-response.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixed action status_message update restrictions to allow updates when
|
||||||
|
action is in SKIPPED state. Previously, users could only update the
|
||||||
|
status_message when initially changing the action state to SKIPPED.
|
||||||
|
Now users can update the status_message field at any time while the
|
||||||
|
action remains in SKIPPED state, enabling them to fix typos, provide
|
||||||
|
more detailed explanations, or expand on reasons that were initially
|
||||||
|
omitted. For more details, see the bug report:
|
||||||
|
https://bugs.launchpad.net/watcher/+bug/2121601
|
||||||
@@ -44,4 +44,7 @@ with ``event`` type.
|
|||||||
---
|
---
|
||||||
Added support for SKIPPED actions status via PATCH support for Actions API.
|
Added support for SKIPPED actions status via PATCH support for Actions API.
|
||||||
This feature also introduces the ``status_message`` field to audits, actions
|
This feature also introduces the ``status_message`` field to audits, actions
|
||||||
and action plans.
|
and action plans. The ``status_message`` field can be set when transitioning
|
||||||
|
an action to SKIPPED state, and can also be updated for actions that are
|
||||||
|
already in SKIPPED state, allowing administrators to fix typos, provide more
|
||||||
|
detailed explanations, or expand on reasons that were initially omitted.
|
||||||
|
|||||||
@@ -439,15 +439,18 @@ class ActionsController(rest.RestController):
|
|||||||
ap_state=action_plan.state))
|
ap_state=action_plan.state))
|
||||||
|
|
||||||
status_message = _("Action skipped by user.")
|
status_message = _("Action skipped by user.")
|
||||||
# status_message update only allowed with status update
|
# status_message update only allowed with status update or when
|
||||||
|
# already SKIPPED
|
||||||
# NOTE(dviroel): status_message is an exposed field.
|
# NOTE(dviroel): status_message is an exposed field.
|
||||||
if action.status_message != action_to_update.status_message:
|
if action.status_message != action_to_update.status_message:
|
||||||
if action.state == action_to_update.state:
|
if (action.state == action_to_update.state and
|
||||||
|
action_to_update.state != objects.action.State.SKIPPED):
|
||||||
error_message = _(
|
error_message = _(
|
||||||
"status_message update only allowed with state change")
|
"status_message update only allowed when action state "
|
||||||
raise exception.PatchError(
|
"is SKIPPED")
|
||||||
|
raise exception.Conflict(
|
||||||
patch=patch,
|
patch=patch,
|
||||||
reason=error_message)
|
message=error_message)
|
||||||
else:
|
else:
|
||||||
status_message = (_("%(status_message)s Reason: %(reason)s")
|
status_message = (_("%(status_message)s Reason: %(reason)s")
|
||||||
% dict(status_message=status_message,
|
% dict(status_message=status_message,
|
||||||
|
|||||||
@@ -656,17 +656,17 @@ class TestPatchAction(api_base.FunctionalTest):
|
|||||||
response.json['error_message'])
|
response.json['error_message'])
|
||||||
|
|
||||||
def test_patch_action_status_message_not_allowed(self):
|
def test_patch_action_status_message_not_allowed(self):
|
||||||
"""Test that status_message cannot be patched directly"""
|
"""Test status_message cannot be patched directly when not SKIPPED"""
|
||||||
response = self.patch_json(
|
response = self.patch_json(
|
||||||
'/actions/%s' % self.action.uuid,
|
'/actions/%s' % self.action.uuid,
|
||||||
[{'path': '/status_message', 'value': 'test message',
|
[{'path': '/status_message', 'value': 'test message',
|
||||||
'op': 'replace'}],
|
'op': 'replace'}],
|
||||||
headers={'OpenStack-API-Version': 'infra-optim 1.5'},
|
headers={'OpenStack-API-Version': 'infra-optim 1.5'},
|
||||||
expect_errors=True)
|
expect_errors=True)
|
||||||
self.assertEqual(HTTPStatus.BAD_REQUEST, response.status_int)
|
self.assertEqual(HTTPStatus.CONFLICT, response.status_int)
|
||||||
self.assertEqual('application/json', response.content_type)
|
self.assertEqual('application/json', response.content_type)
|
||||||
self.assertIn("status_message update only allowed with state change",
|
self.assertIn("status_message update only allowed when action state "
|
||||||
response.json['error_message'])
|
"is SKIPPED", response.json['error_message'])
|
||||||
self.assertIsNone(self.action.status_message)
|
self.assertIsNone(self.action.status_message)
|
||||||
|
|
||||||
def test_patch_action_one_allowed_one_not_allowed(self):
|
def test_patch_action_one_allowed_one_not_allowed(self):
|
||||||
@@ -685,6 +685,32 @@ class TestPatchAction(api_base.FunctionalTest):
|
|||||||
"can not be updated", response.json['error_message'])
|
"can not be updated", response.json['error_message'])
|
||||||
self.assertIsNone(self.action.status_message)
|
self.assertIsNone(self.action.status_message)
|
||||||
|
|
||||||
|
def test_patch_action_status_message_allowed_when_skipped(self):
|
||||||
|
"""Test that status_message can be updated when action is SKIPPED"""
|
||||||
|
# First transition to SKIPPED state
|
||||||
|
new_state = objects.action.State.SKIPPED
|
||||||
|
response = self.patch_json(
|
||||||
|
'/actions/%s' % self.action.uuid,
|
||||||
|
[{'path': '/state', 'value': new_state, 'op': 'replace'},
|
||||||
|
{'path': '/status_message', 'value': 'initial message',
|
||||||
|
'op': 'replace'}],
|
||||||
|
headers={'OpenStack-API-Version': 'infra-optim 1.5'})
|
||||||
|
self.assertEqual(HTTPStatus.OK, response.status_int)
|
||||||
|
self.assertEqual(new_state, response.json['state'])
|
||||||
|
|
||||||
|
# Now update status_message while in SKIPPED state
|
||||||
|
response = self.patch_json(
|
||||||
|
'/actions/%s' % self.action.uuid,
|
||||||
|
[{'path': '/status_message', 'value': 'updated message',
|
||||||
|
'op': 'replace'}],
|
||||||
|
headers={'OpenStack-API-Version': 'infra-optim 1.5'})
|
||||||
|
self.assertEqual(HTTPStatus.OK, response.status_int)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertEqual(new_state, response.json['state'])
|
||||||
|
self.assertEqual(
|
||||||
|
'Action skipped by user. Reason: updated message',
|
||||||
|
response.json['status_message'])
|
||||||
|
|
||||||
|
|
||||||
class TestActionPolicyEnforcement(api_base.FunctionalTest):
|
class TestActionPolicyEnforcement(api_base.FunctionalTest):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user