Skip to content

Commit d469282

Browse files
author
Christoph Büscher
committed
Rest: Adding support of multi-index query parameters for _cluster/state
Adding missing support for the multi-index query parameters 'ignore_unavailable', 'allow_no_indices' and 'expand_wildcards' to '_cluster/state' API. These parameters are supposed to be supported for APIs that work across multiple indices. So far overwriting the default settings per REST call was not possible which is fixed here. Closes #5229 Closes #9295
1 parent fc7ade3 commit d469282

File tree

8 files changed

+307
-2
lines changed

8 files changed

+307
-2
lines changed

rest-api-spec/api/cluster.state.json

+12
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@
3232
"flat_settings": {
3333
"type": "boolean",
3434
"description": "Return settings in flat format (default: false)"
35+
},
36+
"ignore_unavailable": {
37+
"type" : "boolean",
38+
"description" : "Whether specified concrete indices should be ignored when unavailable (missing or closed)"
39+
},
40+
"allow_no_indices": {
41+
"type" : "boolean",
42+
"description" : "Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)"
43+
},
44+
"expand_wildcards":{
45+
"type":"list",
46+
"description":"Whether wildcard expressions should get expanded to open or closed indices (default: open)"
3547
}
3648
}
3749
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
setup:
2+
3+
- do:
4+
indices.create:
5+
index: test_close_index
6+
body:
7+
settings:
8+
number_of_shards: "1"
9+
number_of_replicas: "0"
10+
11+
- do:
12+
indices.create:
13+
index: test_open_index
14+
body:
15+
settings:
16+
number_of_shards: "1"
17+
number_of_replicas: "0"
18+
19+
- do:
20+
cluster.health:
21+
wait_for_status: green
22+
23+
# close one index, keep other open for later test
24+
25+
- do:
26+
indices.close:
27+
index: test_close_index
28+
29+
---
30+
"Test expand_wildcards parameter on closed, open indices and both":
31+
32+
- do:
33+
cluster.state:
34+
metric: [ metadata ]
35+
index: test*
36+
expand_wildcards: [ closed ]
37+
38+
- is_false: metadata.indices.test_open_index
39+
- match: {metadata.indices.test_close_index.state: "close"}
40+
41+
- do:
42+
cluster.state:
43+
metric: [ metadata ]
44+
index: test*
45+
expand_wildcards: [ open ]
46+
47+
- match: {metadata.indices.test_open_index.state: "open"}
48+
- is_false: metadata.indices.test_close_index
49+
50+
- do:
51+
cluster.state:
52+
metric: [ metadata ]
53+
index: test*
54+
expand_wildcards: [ open,closed ]
55+
56+
- match: {metadata.indices.test_open_index.state: "open"}
57+
- match: {metadata.indices.test_close_index.state: "close"}
58+
59+
---
60+
"Test ignore_unavailable parameter":
61+
62+
- do:
63+
cluster.state:
64+
metric: [ metadata ]
65+
index: foobla
66+
ignore_unavailable: true
67+
68+
- match: {metadata.indices: {}}
69+
70+
- do:
71+
catch: missing
72+
cluster.state:
73+
metric: [ metadata ]
74+
index: foobla
75+
ignore_unavailable: false
76+
77+
---
78+
"Test allow_no_indices parameter":
79+
80+
- do:
81+
cluster.state:
82+
metric: [ metadata ]
83+
index: not_there*
84+
85+
- match: {metadata.indices: {}}
86+
87+
- do:
88+
catch: missing
89+
cluster.state:
90+
metric: [ metadata ]
91+
index: not_there*
92+
allow_no_indices: false

src/main/java/org/elasticsearch/action/admin/cluster/state/ClusterStateRequest.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class ClusterStateRequest extends MasterNodeReadOperationRequest<ClusterS
4040
private boolean metaData = true;
4141
private boolean blocks = true;
4242
private String[] indices = Strings.EMPTY_ARRAY;
43+
private IndicesOptions indicesOptions = IndicesOptions.lenientExpandOpen();
4344

4445
public ClusterStateRequest() {
4546
}
@@ -57,7 +58,7 @@ public ClusterStateRequest all() {
5758
indices = Strings.EMPTY_ARRAY;
5859
return this;
5960
}
60-
61+
6162
public ClusterStateRequest clear() {
6263
routingTable = false;
6364
nodes = false;
@@ -116,7 +117,12 @@ public ClusterStateRequest indices(String... indices) {
116117

117118
@Override
118119
public IndicesOptions indicesOptions() {
119-
return IndicesOptions.lenientExpandOpen();
120+
return this.indicesOptions;
121+
}
122+
123+
public final ClusterStateRequest indicesOptions(IndicesOptions indicesOptions) {
124+
this.indicesOptions = indicesOptions;
125+
return this;
120126
}
121127

122128
@Override
@@ -132,6 +138,9 @@ public void readFrom(StreamInput in) throws IOException {
132138
in.readStringArray();
133139
}
134140
readLocal(in);
141+
if (in.getVersion().onOrAfter(Version.V_1_5_0)) {
142+
indicesOptions = IndicesOptions.readIndicesOptions(in);
143+
}
135144
}
136145

137146
@Override
@@ -147,5 +156,8 @@ public void writeTo(StreamOutput out) throws IOException {
147156
out.writeStringArray(Strings.EMPTY_ARRAY);
148157
}
149158
writeLocal(out);
159+
if (out.getVersion().onOrAfter(Version.V_1_5_0)) {
160+
indicesOptions.writeIndicesOptions(out);
161+
}
150162
}
151163
}

src/main/java/org/elasticsearch/action/admin/cluster/state/ClusterStateRequestBuilder.java

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.action.admin.cluster.state;
2121

2222
import org.elasticsearch.action.ActionListener;
23+
import org.elasticsearch.action.support.IndicesOptions;
2324
import org.elasticsearch.action.support.master.MasterNodeReadOperationRequestBuilder;
2425
import org.elasticsearch.client.ClusterAdminClient;
2526

@@ -89,6 +90,11 @@ public ClusterStateRequestBuilder setIndices(String... indices) {
8990
return this;
9091
}
9192

93+
public ClusterStateRequestBuilder setIndicesOptions(IndicesOptions indicesOptions) {
94+
request.indicesOptions(indicesOptions);
95+
return this;
96+
}
97+
9298
@Override
9399
protected void doExecute(ActionListener<ClusterStateResponse> listener) {
94100
client.state(request, listener);

src/main/java/org/elasticsearch/rest/action/admin/cluster/state/RestClusterStateAction.java

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
2323
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
24+
import org.elasticsearch.action.support.IndicesOptions;
2425
import org.elasticsearch.client.Client;
2526
import org.elasticsearch.client.Requests;
2627
import org.elasticsearch.cluster.ClusterState;
@@ -57,6 +58,7 @@ public RestClusterStateAction(Settings settings, RestController controller, Clie
5758
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
5859
final ClusterStateRequest clusterStateRequest = Requests.clusterStateRequest();
5960
clusterStateRequest.listenerThreaded(false);
61+
clusterStateRequest.indicesOptions(IndicesOptions.fromRequest(request, clusterStateRequest.indicesOptions()));
6062
clusterStateRequest.local(request.paramAsBoolean("local", clusterStateRequest.local()));
6163
clusterStateRequest.masterNodeTimeout(request.paramAsTime("master_timeout", clusterStateRequest.masterNodeTimeout()));
6264

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.admin.cluster.state;
21+
22+
import org.elasticsearch.Version;
23+
import org.elasticsearch.action.support.IndicesOptions;
24+
import org.elasticsearch.common.io.stream.BytesStreamInput;
25+
import org.elasticsearch.common.io.stream.BytesStreamOutput;
26+
import org.elasticsearch.test.ElasticsearchTestCase;
27+
import org.junit.Test;
28+
29+
import static org.hamcrest.CoreMatchers.equalTo;
30+
31+
/**
32+
* Unit tests for the {@link ClusterStateRequest}.
33+
*/
34+
public class ClusterStateRequestTest extends ElasticsearchTestCase {
35+
36+
@Test
37+
public void testSerialization() throws Exception {
38+
int iterations = randomIntBetween(5, 20);
39+
for (int i = 0; i < iterations; i++) {
40+
41+
IndicesOptions indicesOptions = IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean());
42+
ClusterStateRequest clusterStateRequest = new ClusterStateRequest().routingTable(randomBoolean()).metaData(randomBoolean())
43+
.nodes(randomBoolean()).blocks(randomBoolean()).indices("testindex", "testindex2").indicesOptions(indicesOptions);
44+
45+
Version testVersion = randomVersionBetween(Version.CURRENT.minimumCompatibilityVersion(), Version.CURRENT);
46+
BytesStreamOutput output = new BytesStreamOutput();
47+
output.setVersion(testVersion);
48+
clusterStateRequest.writeTo(output);
49+
50+
BytesStreamInput bytesStreamInput = new BytesStreamInput(output.bytes());
51+
bytesStreamInput.setVersion(testVersion);
52+
ClusterStateRequest deserializedCSRequest = new ClusterStateRequest();
53+
deserializedCSRequest.readFrom(bytesStreamInput);
54+
55+
assertThat(deserializedCSRequest.routingTable(), equalTo(clusterStateRequest.routingTable()));
56+
assertThat(deserializedCSRequest.metaData(), equalTo(clusterStateRequest.metaData()));
57+
assertThat(deserializedCSRequest.nodes(), equalTo(clusterStateRequest.nodes()));
58+
assertThat(deserializedCSRequest.blocks(), equalTo(clusterStateRequest.blocks()));
59+
assertThat(deserializedCSRequest.indices(), equalTo(clusterStateRequest.indices()));
60+
61+
if (testVersion.onOrAfter(Version.V_1_5_0)) {
62+
assertOptionsMatch(deserializedCSRequest.indicesOptions(), clusterStateRequest.indicesOptions());
63+
} else {
64+
// versions before V_1_5_0 use IndicesOptions.lenientExpandOpen()
65+
assertOptionsMatch(deserializedCSRequest.indicesOptions(), IndicesOptions.lenientExpandOpen());
66+
}
67+
}
68+
}
69+
70+
private static void assertOptionsMatch(IndicesOptions in, IndicesOptions out) {
71+
assertThat(in.ignoreUnavailable(), equalTo(out.ignoreUnavailable()));
72+
assertThat(in.expandWildcardsClosed(), equalTo(out.expandWildcardsClosed()));
73+
assertThat(in.expandWildcardsOpen(), equalTo(out.expandWildcardsOpen()));
74+
assertThat(in.allowNoIndices(), equalTo(out.allowNoIndices()));
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.bwcompat;
21+
22+
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
23+
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
24+
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
25+
import org.elasticsearch.client.transport.TransportClient;
26+
import org.elasticsearch.cluster.ClusterState;
27+
import org.elasticsearch.common.settings.ImmutableSettings;
28+
import org.elasticsearch.common.settings.Settings;
29+
import org.elasticsearch.test.ElasticsearchBackwardsCompatIntegrationTest;
30+
import org.junit.Test;
31+
import static org.hamcrest.Matchers.*;
32+
33+
public class ClusterStateBackwardsCompat extends ElasticsearchBackwardsCompatIntegrationTest {
34+
35+
@Test
36+
public void testClusterState() throws Exception {
37+
createIndex("test");
38+
39+
NodesInfoResponse nodesInfo = client().admin().cluster().prepareNodesInfo().execute().actionGet();
40+
Settings settings = ImmutableSettings.settingsBuilder().put("client.transport.ignore_cluster_name", true)
41+
.put("node.name", "transport_client_" + getTestName()).build();
42+
43+
// connect to each node with a custom TransportClient, issue a ClusterStateRequest to test serialization
44+
for (NodeInfo n : nodesInfo.getNodes()) {
45+
try (TransportClient tc = new TransportClient(settings)) {
46+
tc.addTransportAddress(n.getNode().address());
47+
ClusterStateResponse response = tc.admin().cluster().prepareState().execute().actionGet();
48+
49+
assertThat(response.getState().status(), equalTo(ClusterState.ClusterStateStatus.UNKNOWN));
50+
assertNotNull(response.getClusterName());
51+
assertTrue(response.getState().getMetaData().hasIndex("test"));
52+
}
53+
}
54+
}
55+
}

src/test/java/org/elasticsearch/cluster/SimpleClusterStateTests.java

+50
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@
2121

2222
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
2323
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
24+
import org.elasticsearch.action.support.IndicesOptions;
2425
import org.elasticsearch.client.Client;
26+
import org.elasticsearch.client.Requests;
2527
import org.elasticsearch.cluster.metadata.IndexMetaData;
2628
import org.elasticsearch.cluster.metadata.MappingMetaData;
2729
import org.elasticsearch.common.Strings;
2830
import org.elasticsearch.common.unit.ByteSizeValue;
2931
import org.elasticsearch.common.xcontent.XContentBuilder;
3032
import org.elasticsearch.common.xcontent.XContentFactory;
33+
import org.elasticsearch.indices.IndexMissingException;
3134
import org.elasticsearch.test.ElasticsearchIntegrationTest;
3235
import org.elasticsearch.test.hamcrest.CollectionAssertions;
3336
import org.junit.Before;
@@ -157,4 +160,51 @@ public void testLargeClusterStatePublishing() throws Exception {
157160
assertThat(mappingMetadata, equalTo(masterMappingMetaData));
158161
}
159162
}
163+
164+
@Test
165+
public void testIndicesOptions() throws Exception {
166+
ClusterStateResponse clusterStateResponse = client().admin().cluster().prepareState().clear().setMetaData(true).setIndices("f*")
167+
.get();
168+
assertThat(clusterStateResponse.getState().metaData().indices().size(), is(2));
169+
170+
// close one index
171+
client().admin().indices().close(Requests.closeIndexRequest("fuu")).get();
172+
clusterStateResponse = client().admin().cluster().prepareState().clear().setMetaData(true).setIndices("f*").get();
173+
assertThat(clusterStateResponse.getState().metaData().indices().size(), is(1));
174+
assertThat(clusterStateResponse.getState().metaData().index("foo").state(), equalTo(IndexMetaData.State.OPEN));
175+
176+
// expand_wildcards_closed should toggle return only closed index fuu
177+
IndicesOptions expandCloseOptions = IndicesOptions.fromOptions(false, true, false, true);
178+
clusterStateResponse = client().admin().cluster().prepareState().clear().setMetaData(true).setIndices("f*")
179+
.setIndicesOptions(expandCloseOptions).get();
180+
assertThat(clusterStateResponse.getState().metaData().indices().size(), is(1));
181+
assertThat(clusterStateResponse.getState().metaData().index("fuu").state(), equalTo(IndexMetaData.State.CLOSE));
182+
183+
// ignore_unavailable set to true should not raise exception on fzzbzz
184+
IndicesOptions ignoreUnavailabe = IndicesOptions.fromOptions(true, true, true, false);
185+
clusterStateResponse = client().admin().cluster().prepareState().clear().setMetaData(true).setIndices("fzzbzz")
186+
.setIndicesOptions(ignoreUnavailabe).get();
187+
assertThat(clusterStateResponse.getState().metaData().indices().isEmpty(), is(true));
188+
189+
// empty wildcard expansion result should work when allowNoIndices is
190+
// turned on
191+
IndicesOptions allowNoIndices = IndicesOptions.fromOptions(false, true, true, false);
192+
clusterStateResponse = client().admin().cluster().prepareState().clear().setMetaData(true).setIndices("a*")
193+
.setIndicesOptions(allowNoIndices).get();
194+
assertThat(clusterStateResponse.getState().metaData().indices().isEmpty(), is(true));
195+
}
196+
197+
@Test(expected=IndexMissingException.class)
198+
public void testIndicesOptionsOnAllowNoIndicesFalse() throws Exception {
199+
// empty wildcard expansion throws exception when allowNoIndices is turned off
200+
IndicesOptions allowNoIndices = IndicesOptions.fromOptions(false, false, true, false);
201+
client().admin().cluster().prepareState().clear().setMetaData(true).setIndices("a*").setIndicesOptions(allowNoIndices).get();
202+
}
203+
204+
@Test(expected=IndexMissingException.class)
205+
public void testIndicesIgnoreUnavailableFalse() throws Exception {
206+
// ignore_unavailable set to false throws exception when allowNoIndices is turned off
207+
IndicesOptions allowNoIndices = IndicesOptions.fromOptions(false, true, true, false);
208+
client().admin().cluster().prepareState().clear().setMetaData(true).setIndices("fzzbzz").setIndicesOptions(allowNoIndices).get();
209+
}
160210
}

0 commit comments

Comments
 (0)