Skip to content

Commit

Permalink
Add granular error list to alias action response (#106514)
Browse files Browse the repository at this point in the history
When an alias action list is posted with must_exist==false, and succeeds only partially, a list of results for each action are now returned. The results contain information about the requested action, indices, and aliases. If must_exist==true, or all actions fail, the call will return a 400 status along with the associated exception.
  • Loading branch information
parkertimmins committed Apr 9, 2024
1 parent 24aed5c commit 75228df
Show file tree
Hide file tree
Showing 26 changed files with 766 additions and 58 deletions.
6 changes: 6 additions & 0 deletions docs/changelog/106514.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 106514
summary: Add granular error list to alias action response
area: Indices APIs
type: feature
issues:
- 94478
71 changes: 71 additions & 0 deletions docs/reference/alias.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,77 @@ POST _aliases
// TEST[s/^/PUT _data_stream\/logs-nginx.access-prod\nPUT _data_stream\/logs-my_app-default\n/]
// end::alias-multiple-actions-example[]

[discrete]
[[multiple-action-results]]
=== Multiple action results

When using multiple actions, if some succeed and some fail, a list of per-action results will be returned.

Consider a similar action list to the previous example, but now with an alias `log-non-existing`, which does not yet exist.
In this case, the `remove` action will fail, but the `add` action will succeed.
The response will contain the list `action_results`, with a result for every requested action.

[source,console]
----
POST _aliases
{
"actions": [
{
"remove": {
"index": "index1",
"alias": "logs-non-existing"
}
},
{
"add": {
"index": "index2",
"alias": "logs-non-existing"
}
}
]
}
----
// TEST[s/^/PUT \/index1\nPUT \/index2\n/]

The API returns the following result:

[source,console-result]
--------------------------------------------------
{
"acknowledged": true,
"errors": true,
"action_results": [
{
"action": {
"type": "remove",
"indices": [ "index1" ],
"aliases": [ "logs-non-existing" ],
},
"status": 404,
"error": {
"type": "aliases_not_found_exception",
"reason": "aliases [logs-non-existing] missing",
"resource.type": "aliases",
"resource.id": "logs-non-existing"
}
},
{
"action": {
"type": "add",
"indices": [ "index2" ],
"aliases": [ "logs-non-existing" ],
},
"status": 200
}
]
}
--------------------------------------------------

Allowing the action list to succeed partially may not provide the desired result.
It may be more appropriate to set `must_exist` to `true`, which will cause the entire action
list to fail if a single action fails.


[discrete]
[[add-alias-at-creation]]
=== Add an alias at index creation
Expand Down
58 changes: 56 additions & 2 deletions docs/reference/indices/aliases.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,16 @@ the alias points to one data stream.
+
Only the `add` action supports this parameter.

// tag::alias-options[]
`must_exist`::
(Optional, Boolean)
If `true`, the alias must exist to perform the action. Defaults to `false`. Only
the `remove` action supports this parameter.
Affects the behavior when attempting to remove an alias which does not exist.
If `true`, removing an alias which does not exist will cause all actions to fail.
If `false`, removing an alias which does not exist will only cause that removal to fail.
Defaults to `false`.
// end::alias-options[]
+
Only the `remove` action supports this parameter.

// tag::alias-options[]
`routing`::
Expand All @@ -168,3 +174,51 @@ stream aliases don't support this parameter.
Only the `add` action supports this parameter.
=====
====



[role="child_attributes"]
[[indices-aliases-api-response-body]]
==== {api-response-body-title}

`acknowledged`::
(Boolean)
If `true`, the request received a response from the master node within the
`timeout` period.

`errors`::
(Boolean)
If `true`, at least one of the requested actions failed.

`action_results`::
(Optional, array of objects) Results for each requested action.
+
.Properties of `action_results` objects
[%collapsible%open]
====
`action`::
(object)
Description of the associated action request.
+
.Properties of `action` object
[%collapsible%open]
=====
`type`::
(string) The type of the associated action, one of `add`, `remove`, or `remove_index`.

`indices`::
(array of strings) List of indices in the associated action.

`aliases`::
(array of strings) List of aliases in the associated action.
=====
`status`::
(integer) HTTP status code returned for the action.
`error`::
(Optional, object) Contains additional information about the failed action.
+
Only present if the action failed.
====
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,86 @@
indices.get_alias:
name: this-does-not-exist*
- is_false: ds-first.aliases.my-alias
---
"Action Results with multiple matching aliases":
- skip:
version: " - 8.13.99"
reason: "alias action results do not work until 8.14"
features: allowed_warnings
- do:
allowed_warnings:
- "index template [my-template] has index patterns [log-*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [my-template] will take precedence during new index creation"
indices.put_index_template:
name: my-template
body:
index_patterns: [ log-* ]
template:
settings:
index.number_of_replicas: 0
data_stream: { }
- do:
indices.create_data_stream:
name: log-foobar
- is_true: acknowledged
- do:
indices.update_aliases:
body:
actions:
- add:
index: log-foobar
aliases: test_alias1
- remove:
index: log-foobar
aliases: test_non_existing
must_exist: false
- is_true: errors
- length: { action_results: 2 }
- match: { action_results.0.status: 200 }
- match: { action_results.0.action: { 'type': 'add', 'indices': ['log-foobar'], 'aliases': ['test_alias1'] } }
- match: { action_results.0.error: null }
- match: { action_results.1.status: 404 }
- match: { action_results.1.action: { 'type': 'remove', 'indices': ['log-foobar'], 'aliases': ['test_non_existing'] } }
- match: { action_results.1.error.type: aliases_not_found_exception }
---
"Single action result per action":
- skip:
version: " - 8.13.99"
reason: "alias action results do not work until 8.14"
features: allowed_warnings
- do:
allowed_warnings:
- "index template [my-template] has index patterns [log-*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [my-template] will take precedence during new index creation"
indices.put_index_template:
name: my-template
body:
index_patterns: [ log-* ]
template:
settings:
index.number_of_replicas: 0
data_stream: { }
- do:
indices.create_data_stream:
name: log-test-1
- do:
indices.create_data_stream:
name: log-test-2
- is_true: acknowledged
- do:
indices.update_aliases:
body:
actions:
- add:
index: log-test-*
aliases: test_alias1
- remove:
index: log-test-*
aliases: test_non_existing
must_exist: false
- is_true: errors
- length: { action_results: 2 }
- match: { action_results.0.status: 200}
- match: { action_results.0.action: { 'type': 'add', 'indices': ['log-test-1', 'log-test-2'], 'aliases': ['test_alias1'] } }
- match: { action_results.0.error: null }
- match: { action_results.1.status: 404 }
- match: { action_results.1.action: { 'type': 'remove', 'indices': ['log-test-1', 'log-test-2'], 'aliases': ['test_non_existing'] } }
- match: { action_results.1.error.type: aliases_not_found_exception }
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,100 @@
- remove_index:
index: test_index
must_exist: true
---
"Partial success with must_exist == false":
- skip:
version: " - 8.13.99"
reason: "alias action results do not work until 8.14"
- do:
indices.create:
index: test_index
- do:
indices.update_aliases:
body:
actions:
- add:
index: test_index
aliases: test_alias1
- remove:
index: test_index
aliases: test_non_existing
must_exist: false
- is_true: errors
- match: { action_results.0.status: 200 }
- match: { action_results.0.action: { 'type': 'add', 'indices': ['test_index'], 'aliases': ['test_alias1'] } }
- match: { action_results.0.error: null }
- match: { action_results.1.status: 404 }
- match: { action_results.1.action: { 'type': 'remove', 'indices': ['test_index'], 'aliases': ['test_non_existing'] } }
- match: { action_results.1.error.type: aliases_not_found_exception }
---
"Partial success with must_exist == null (default)":
- skip:
version: " - 8.13.99"
reason: "alias action results do not work until 8.14"
- do:
indices.create:
index: test_index
- do:
indices.update_aliases:
body:
actions:
- add:
index: test_index
aliases: test_alias1
- remove:
index: test_index
aliases: test_non_existing
- is_true: errors
- match: { action_results.0.status: 200}
- match: { action_results.0.action: { 'type': 'add', 'indices': ['test_index'], 'aliases': ['test_alias1'] } }
- match: { action_results.0.error: null }
- match: { action_results.1.status: 404}
- match: { action_results.1.action: { 'type': 'remove', 'indices': ['test_index'], 'aliases': ['test_non_existing'] } }
- match: { action_results.1.error.type: aliases_not_found_exception }
---
"No action_results field if all actions successful":
- skip:
version: " - 8.13.99"
reason: "alias action results do not work until 8.14"
- do:
indices.create:
index: test_index
- do:
indices.update_aliases:
body:
actions:
- add:
index: test_index
aliases: test_alias1
- is_false: errors
- match: { action_results: null }
---
"Single result per input action":
- skip:
version: " - 8.13.99"
reason: "alias action results do not work until 8.14"
- do:
indices.create:
index: test_index1
- do:
indices.create:
index: test_index2
- do:
indices.update_aliases:
body:
actions:
- add:
index: test_index*
aliases: test_alias1
- remove:
index: test_index*
aliases: test_non_existing
- length: { action_results: 2 }
- is_true: errors
- match: { action_results.0.status: 200}
- match: { action_results.0.action: { 'type': 'add', 'indices': ['test_index1', 'test_index2'], 'aliases': ['test_alias1'] } }
- match: { action_results.0.error: null }
- match: { action_results.1.status: 404}
- match: { action_results.1.action: { 'type': 'remove', 'indices': ['test_index1', 'test_index2'], 'aliases': ['test_non_existing'] } }
- match: { action_results.1.error.type: aliases_not_found_exception }
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ static TransportVersion def(int id) {
public static final TransportVersion ESQL_ORDINAL_BLOCK = def(8_623_00_0);
public static final TransportVersion ML_INFERENCE_COHERE_RERANK = def(8_624_00_0);
public static final TransportVersion INDEXING_PRESSURE_DOCUMENT_REJECTIONS_COUNT = def(8_625_00_0);
public static final TransportVersion ALIAS_ACTION_RESULTS = def(8_626_00_0);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
package org.elasticsearch.action.admin.indices.alias;

import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse.AliasActionResult;
import org.elasticsearch.cluster.ack.ClusterStateUpdateRequest;
import org.elasticsearch.cluster.metadata.AliasAction;

Expand All @@ -18,8 +19,11 @@
public class IndicesAliasesClusterStateUpdateRequest extends ClusterStateUpdateRequest<IndicesAliasesClusterStateUpdateRequest> {
private final List<AliasAction> actions;

public IndicesAliasesClusterStateUpdateRequest(List<AliasAction> actions) {
private final List<IndicesAliasesResponse.AliasActionResult> actionResults;

public IndicesAliasesClusterStateUpdateRequest(List<AliasAction> actions, List<AliasActionResult> actionResults) {
this.actions = actions;
this.actionResults = actionResults;
}

/**
Expand All @@ -28,4 +32,8 @@ public IndicesAliasesClusterStateUpdateRequest(List<AliasAction> actions) {
public List<AliasAction> actions() {
return actions;
}

public List<AliasActionResult> getActionResults() {
return actionResults;
}
}

0 comments on commit 75228df

Please sign in to comment.