Merge "Allow status_message updates for actions in SKIPPED state"

This commit is contained in:
Zuul
2025-08-28 20:04:34 +00:00
committed by Gerrit Code Review
8 changed files with 141 additions and 10 deletions

View File

@@ -192,6 +192,9 @@ action_state:
action_status_message:
description: |
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
required: false
type: string

View File

@@ -0,0 +1,7 @@
[
{
"op": "replace",
"value": "Action skipped due to scheduled maintenance window",
"path": "/status_message"
}
]

View File

@@ -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"
}

View File

@@ -210,4 +210,53 @@ Response
**Example JSON representation of a skipped Action:**
.. 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

View File

@@ -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

View File

@@ -44,4 +44,7 @@ with ``event`` type.
---
Added support for SKIPPED actions status via PATCH support for Actions API.
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.

View File

@@ -439,15 +439,18 @@ class ActionsController(rest.RestController):
ap_state=action_plan.state))
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.
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 = _(
"status_message update only allowed with state change")
raise exception.PatchError(
"status_message update only allowed when action state "
"is SKIPPED")
raise exception.Conflict(
patch=patch,
reason=error_message)
message=error_message)
else:
status_message = (_("%(status_message)s Reason: %(reason)s")
% dict(status_message=status_message,

View File

@@ -656,17 +656,17 @@ class TestPatchAction(api_base.FunctionalTest):
response.json['error_message'])
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(
'/actions/%s' % self.action.uuid,
[{'path': '/status_message', 'value': 'test message',
'op': 'replace'}],
headers={'OpenStack-API-Version': 'infra-optim 1.5'},
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.assertIn("status_message update only allowed with state change",
response.json['error_message'])
self.assertIn("status_message update only allowed when action state "
"is SKIPPED", response.json['error_message'])
self.assertIsNone(self.action.status_message)
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'])
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):