diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java index 6968839d1ee42..6f67557d5fea7 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java @@ -37,6 +37,8 @@ import org.elasticsearch.action.admin.indices.open.OpenIndexResponse; import org.elasticsearch.action.admin.indices.rollover.RolloverRequest; import org.elasticsearch.action.admin.indices.rollover.RolloverResponse; +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; +import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; import org.elasticsearch.action.admin.indices.shrink.ResizeRequest; import org.elasticsearch.action.admin.indices.shrink.ResizeResponse; @@ -215,6 +217,26 @@ public void existsAliasAsync(GetAliasesRequest getAliasesRequest, ActionListener listener, emptySet(), headers); } + /** + * Refresh one or more indices using the Refresh API + *

+ * See Refresh API on elastic.co + */ + public RefreshResponse refresh(RefreshRequest refreshRequest, Header... headers) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(refreshRequest, Request::refresh, RefreshResponse::fromXContent, + emptySet(), headers); + } + + /** + * Asynchronously refresh one or more indices using the Refresh API + *

+ * See Refresh API on elastic.co + */ + public void refreshAsync(RefreshRequest refreshRequest, ActionListener listener, Header... headers) { + restHighLevelClient.performRequestAsyncAndParseEntity(refreshRequest, Request::refresh, RefreshResponse::fromXContent, + listener, emptySet(), headers); + } + /** * Checks if the index (indices) exists or not. *

diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java index fd849f5e47883..d05c4e6ffe70b 100755 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java @@ -38,6 +38,7 @@ import org.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.rollover.RolloverRequest; import org.elasticsearch.action.admin.indices.shrink.ResizeRequest; import org.elasticsearch.action.admin.indices.shrink.ResizeType; @@ -216,6 +217,15 @@ static Request putMapping(PutMappingRequest putMappingRequest) throws IOExceptio return new Request(HttpPut.METHOD_NAME, endpoint, parameters.getParams(), entity); } + static Request refresh(RefreshRequest refreshRequest) { + String endpoint = endpoint(refreshRequest.indices(), "_refresh"); + + Params parameters = Params.builder(); + parameters.withIndicesOptions(refreshRequest.indicesOptions()); + + return new Request(HttpPost.METHOD_NAME, endpoint, parameters.getParams(), null); + } + static Request info() { return new Request(HttpGet.METHOD_NAME, "/", Collections.emptyMap(), null); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java index fbc15b10a7488..7799171b45b56 100755 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java @@ -39,6 +39,8 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexResponse; +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; +import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; import org.elasticsearch.action.admin.indices.rollover.RolloverRequest; import org.elasticsearch.action.admin.indices.rollover.RolloverResponse; import org.elasticsearch.action.admin.indices.shrink.ResizeRequest; @@ -47,6 +49,7 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.action.support.broadcast.BroadcastResponse; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.ByteSizeValue; @@ -381,6 +384,32 @@ public void testCloseNonExistentIndex() throws IOException { assertEquals(RestStatus.NOT_FOUND, exception.status()); } + public void testRefresh() throws IOException { + { + String index = "index"; + Settings settings = Settings.builder() + .put("number_of_shards", 1) + .put("number_of_replicas", 0) + .build(); + createIndex(index, settings); + RefreshRequest refreshRequest = new RefreshRequest(index); + RefreshResponse refreshResponse = + execute(refreshRequest, highLevelClient().indices()::refresh, highLevelClient().indices()::refreshAsync); + assertThat(refreshResponse.getTotalShards(), equalTo(1)); + assertThat(refreshResponse.getSuccessfulShards(), equalTo(1)); + assertThat(refreshResponse.getFailedShards(), equalTo(0)); + assertThat(refreshResponse.getShardFailures(), equalTo(BroadcastResponse.EMPTY)); + } + { + String nonExistentIndex = "non_existent_index"; + assertFalse(indexExists(nonExistentIndex)); + RefreshRequest refreshRequest = new RefreshRequest(nonExistentIndex); + ElasticsearchException exception = expectThrows(ElasticsearchException.class, + () -> execute(refreshRequest, highLevelClient().indices()::refresh, highLevelClient().indices()::refreshAsync)); + assertEquals(RestStatus.NOT_FOUND, exception.status()); + } + } + public void testExistsAlias() throws IOException { GetAliasesRequest getAliasesRequest = new GetAliasesRequest("alias"); assertFalse(execute(getAliasesRequest, highLevelClient().indices()::existsAlias, highLevelClient().indices()::existsAliasAsync)); @@ -495,4 +524,4 @@ public void testRollover() throws IOException { assertEquals("test_new", rolloverResponse.getNewIndex()); } } -} \ No newline at end of file +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java index 71724cab82ec7..2c46249ea217c 100755 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java @@ -40,6 +40,7 @@ import org.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.rollover.RolloverRequest; import org.elasticsearch.action.admin.indices.shrink.ResizeRequest; import org.elasticsearch.action.admin.indices.shrink.ResizeType; @@ -535,6 +536,21 @@ public void testIndex() throws IOException { } } + public void testRefresh() { + String[] indices = randomIndicesNames(1, 5); + RefreshRequest refreshRequest = new RefreshRequest(indices); + + Map expectedParams = new HashMap<>(); + setRandomIndicesOptions(refreshRequest::indicesOptions, refreshRequest::indicesOptions, expectedParams); + + Request request = Request.refresh(refreshRequest); + StringJoiner endpoint = new StringJoiner("/", "/", "").add(String.join(",", indices)).add("_refresh"); + assertThat(endpoint.toString(), equalTo(request.getEndpoint())); + assertThat(request.getParameters(), equalTo(expectedParams)); + assertThat(request.getEntity(), nullValue()); + assertThat(request.getMethod(), equalTo(HttpPost.METHOD_NAME)); + } + public void testUpdate() throws IOException { XContentType xContentType = randomFrom(XContentType.values()); @@ -1058,7 +1074,7 @@ public void testExistsAliasNoAliasNoIndex() { IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> Request.existsAlias(getAliasesRequest)); assertEquals("existsAlias requires at least an alias or an index", iae.getMessage()); } - + public void testRankEval() throws Exception { RankEvalSpec spec = new RankEvalSpec( Collections.singletonList(new RatedRequest("queryId", Collections.emptyList(), new SearchSourceBuilder())), @@ -1130,7 +1146,7 @@ private static void resizeTest(ResizeType resizeType, CheckedFunction expectedParams = new HashMap<>(); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java index 843c5c42fffa1..a16d5e2f5fc74 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java @@ -38,12 +38,15 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexResponse; +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; +import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; import org.elasticsearch.action.admin.indices.rollover.RolloverRequest; import org.elasticsearch.action.admin.indices.rollover.RolloverResponse; import org.elasticsearch.action.admin.indices.shrink.ResizeRequest; import org.elasticsearch.action.admin.indices.shrink.ResizeResponse; import org.elasticsearch.action.admin.indices.shrink.ResizeType; import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.action.support.DefaultShardOperationFailedException; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.ESRestHighLevelClientTestCase; import org.elasticsearch.client.RestHighLevelClient; @@ -612,6 +615,74 @@ public void onFailure(Exception e) { } } + public void testRefreshIndex() throws Exception { + RestHighLevelClient client = highLevelClient(); + + { + createIndex("index1", Settings.EMPTY); + } + + { + // tag::refresh-request + RefreshRequest request = new RefreshRequest("index1"); // <1> + RefreshRequest requestMultiple = new RefreshRequest("index1", "index2"); // <2> + RefreshRequest requestAll = new RefreshRequest(); // <3> + // end::refresh-request + + // tag::refresh-request-indicesOptions + request.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1> + // end::refresh-request-indicesOptions + + // tag::refresh-execute + RefreshResponse refreshResponse = client.indices().refresh(request); + // end::refresh-execute + + // tag::refresh-response + int totalShards = refreshResponse.getTotalShards(); // <1> + int successfulShards = refreshResponse.getSuccessfulShards(); // <2> + int failedShards = refreshResponse.getFailedShards(); // <3> + DefaultShardOperationFailedException[] failures = refreshResponse.getShardFailures(); // <4> + // end::refresh-response + + // tag::refresh-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(RefreshResponse refreshResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::refresh-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::refresh-execute-async + client.indices().refreshAsync(request, listener); // <1> + // end::refresh-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + + { + // tag::refresh-notfound + try { + RefreshRequest request = new RefreshRequest("does_not_exist"); + client.indices().refresh(request); + } catch (ElasticsearchException exception) { + if (exception.status() == RestStatus.NOT_FOUND) { + // <1> + } + } + // end::refresh-notfound + } + } + public void testCloseIndex() throws Exception { RestHighLevelClient client = highLevelClient(); diff --git a/docs/java-rest/high-level/indices/refresh.asciidoc b/docs/java-rest/high-level/indices/refresh.asciidoc new file mode 100644 index 0000000000000..f61c1c37d4ee1 --- /dev/null +++ b/docs/java-rest/high-level/indices/refresh.asciidoc @@ -0,0 +1,84 @@ +[[java-rest-high-refresh]] +=== Refresh API + +[[java-rest-high-refresh-request]] +==== Refresh Request + +A `RefreshRequest` can be applied to one or more indices, or even on `_all` the indices: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[refresh-request] +-------------------------------------------------- +<1> Refresh one index +<2> Refresh multiple indices +<3> Refresh all the indices + +==== Optional arguments + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[refresh-request-indicesOptions] +-------------------------------------------------- +<1> Setting `IndicesOptions` controls how unavailable indices are resolved and +how wildcard expressions are expanded + +[[java-rest-high-refresh-sync]] +==== Synchronous Execution + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[refresh-execute] +-------------------------------------------------- + +[[java-rest-high-refresh-async]] +==== Asynchronous Execution + +The asynchronous execution of a refresh request requires both the `RefreshRequest` +instance and an `ActionListener` instance to be passed to the asynchronous +method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[refresh-execute-async] +-------------------------------------------------- +<1> The `RefreshRequest` to execute and the `ActionListener` to use when +the execution completes + +The asynchronous method does not block and returns immediately. Once it is +completed the `ActionListener` is called back using the `onResponse` method +if the execution successfully completed or using the `onFailure` method if +it failed. + +A typical listener for `RefreshResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[refresh-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of failure. The raised exception is provided as an argument + +[[java-rest-high-refresh-response]] +==== Refresh Response + +The returned `RefreshResponse` allows to retrieve information about the +executed operation as follows: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[refresh-response] +-------------------------------------------------- +<1> Total number of shards hit by the refresh request +<2> Number of shards where the refresh has succeeded +<3> Number of shards where the refresh has failed +<4> A list of failures if the operation failed on one or more shards + +By default, if the indices were not found, an `ElasticsearchException` will be thrown: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[refresh-notfound] +-------------------------------------------------- +<1> Do something if the indices to be refreshed were not found \ No newline at end of file diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 9269da0923a56..634409b235669 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -52,6 +52,7 @@ Index Management:: * <> * <> * <> +* <> * <> Mapping Management:: @@ -68,6 +69,7 @@ include::indices/open_index.asciidoc[] include::indices/close_index.asciidoc[] include::indices/shrink_index.asciidoc[] include::indices/split_index.asciidoc[] +include::indices/refresh.asciidoc[] include::indices/rollover.asciidoc[] include::indices/put_mapping.asciidoc[] include::indices/update_aliases.asciidoc[] diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/RefreshRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/RefreshRequest.java index b5bce3c85ccd5..20687b8e53418 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/RefreshRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/RefreshRequest.java @@ -19,7 +19,6 @@ package org.elasticsearch.action.admin.indices.refresh; -import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.support.broadcast.BroadcastRequest; /** diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/RefreshResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/RefreshResponse.java index b629ac22b89a9..20165d078c5c9 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/RefreshResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/RefreshResponse.java @@ -21,7 +21,10 @@ import org.elasticsearch.action.support.DefaultShardOperationFailedException; import org.elasticsearch.action.support.broadcast.BroadcastResponse; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; +import java.util.Arrays; import java.util.List; /** @@ -29,10 +32,25 @@ */ public class RefreshResponse extends BroadcastResponse { + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("refresh", true, + arg -> { + BroadcastResponse response = (BroadcastResponse) arg[0]; + return new RefreshResponse(response.getTotalShards(), response.getSuccessfulShards(), response.getFailedShards(), + Arrays.asList(response.getShardFailures())); + }); + + static { + declareBroadcastFields(PARSER); + } + RefreshResponse() { } RefreshResponse(int totalShards, int successfulShards, int failedShards, List shardFailures) { super(totalShards, successfulShards, failedShards, shardFailures); } + + public static RefreshResponse fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } } diff --git a/server/src/main/java/org/elasticsearch/action/support/DefaultShardOperationFailedException.java b/server/src/main/java/org/elasticsearch/action/support/DefaultShardOperationFailedException.java index 2ced9145674a2..8a4a787fbe5f2 100644 --- a/server/src/main/java/org/elasticsearch/action/support/DefaultShardOperationFailedException.java +++ b/server/src/main/java/org/elasticsearch/action/support/DefaultShardOperationFailedException.java @@ -22,17 +22,36 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ShardOperationFailedException; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.rest.RestStatus; import java.io.IOException; import static org.elasticsearch.ExceptionsHelper.detailedMessage; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; public class DefaultShardOperationFailedException implements ShardOperationFailedException { + private static final String INDEX = "index"; + private static final String SHARD_ID = "shard"; + private static final String REASON = "reason"; + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "failures", true, arg -> new DefaultShardOperationFailedException((String) arg[0], (int) arg[1] ,(Throwable) arg[2])); + + static { + PARSER.declareString(constructorArg(), new ParseField(INDEX)); + PARSER.declareInt(constructorArg(), new ParseField(SHARD_ID)); + PARSER.declareObject(constructorArg(), (p, c) -> ElasticsearchException.fromXContent(p), new ParseField(REASON)); + } + private String index; private int shardId; @@ -45,8 +64,10 @@ protected DefaultShardOperationFailedException() { } public DefaultShardOperationFailedException(ElasticsearchException e) { - this.index = e.getIndex() == null ? null : e.getIndex().getName(); - this.shardId = e.getShardId().id(); + Index index = e.getIndex(); + this.index = index == null ? null : index.getName(); + ShardId shardId = e.getShardId(); + this.shardId = shardId == null ? -1 : shardId.id(); this.reason = e; this.status = e.status(); } @@ -123,12 +144,14 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.field("index", index()); builder.field("status", status.name()); if (reason != null) { - builder.field("reason"); - builder.startObject(); + builder.startObject("reason"); ElasticsearchException.generateThrowableXContent(builder, params, reason); builder.endObject(); } return builder; + } + public static DefaultShardOperationFailedException fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); } } diff --git a/server/src/main/java/org/elasticsearch/action/support/broadcast/BroadcastResponse.java b/server/src/main/java/org/elasticsearch/action/support/broadcast/BroadcastResponse.java index 2baf5a1d50ec1..ce812644faea6 100644 --- a/server/src/main/java/org/elasticsearch/action/support/broadcast/BroadcastResponse.java +++ b/server/src/main/java/org/elasticsearch/action/support/broadcast/BroadcastResponse.java @@ -21,25 +21,51 @@ import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.support.DefaultShardOperationFailedException; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentFragment; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.rest.action.RestActions; import java.io.IOException; import java.util.List; import static org.elasticsearch.action.support.DefaultShardOperationFailedException.readShardOperationFailed; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; /** * Base class for all broadcast operation based responses. */ -public class BroadcastResponse extends ActionResponse { - private static final DefaultShardOperationFailedException[] EMPTY = new DefaultShardOperationFailedException[0]; +public class BroadcastResponse extends ActionResponse implements ToXContentFragment { + + public static final DefaultShardOperationFailedException[] EMPTY = new DefaultShardOperationFailedException[0]; + + private static final ParseField _SHARDS_FIELD = new ParseField("_shards"); + private static final ParseField TOTAL_FIELD = new ParseField("total"); + private static final ParseField SUCCESSFUL_FIELD = new ParseField("successful"); + private static final ParseField FAILED_FIELD = new ParseField("failed"); + private static final ParseField FAILURES_FIELD = new ParseField("failures"); + private int totalShards; private int successfulShards; private int failedShards; private DefaultShardOperationFailedException[] shardFailures = EMPTY; + protected static void declareBroadcastFields(ConstructingObjectParser PARSER) { + ConstructingObjectParser shardsParser = new ConstructingObjectParser<>("_shards", true, + arg -> new BroadcastResponse((int) arg[0], (int) arg[1], (int) arg[2], (List) arg[3])); + shardsParser.declareInt(constructorArg(), TOTAL_FIELD); + shardsParser.declareInt(constructorArg(), SUCCESSFUL_FIELD); + shardsParser.declareInt(constructorArg(), FAILED_FIELD); + shardsParser.declareObjectArray(optionalConstructorArg(), + (p, c) -> DefaultShardOperationFailedException.fromXContent(p), FAILURES_FIELD); + PARSER.declareObject(constructorArg(), shardsParser, _SHARDS_FIELD); + } + public BroadcastResponse() { } @@ -120,4 +146,10 @@ public void writeTo(StreamOutput out) throws IOException { exp.writeTo(out); } } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + RestActions.buildBroadcastShardsHeader(builder, params, this); + return builder; + } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestRefreshAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestRefreshAction.java index 46dadf9040cd5..486d8664a49d2 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestRefreshAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestRefreshAction.java @@ -37,7 +37,6 @@ import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.POST; -import static org.elasticsearch.rest.action.RestActions.buildBroadcastShardsHeader; public class RestRefreshAction extends BaseRestHandler { public RestRefreshAction(Settings settings, RestController controller) { @@ -62,7 +61,7 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC @Override public RestResponse buildResponse(RefreshResponse response, XContentBuilder builder) throws Exception { builder.startObject(); - buildBroadcastShardsHeader(builder, request, response); + response.toXContent(builder, request); builder.endObject(); return new BytesRestResponse(response.getStatus(), builder); } diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/refresh/RefreshResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/refresh/RefreshResponseTests.java new file mode 100644 index 0000000000000..dbdb807fb5723 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/refresh/RefreshResponseTests.java @@ -0,0 +1,171 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.admin.indices.refresh; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.support.DefaultShardOperationFailedException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.elasticsearch.test.XContentTestUtils.insertRandomFields; +import static org.hamcrest.CoreMatchers.anyOf; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; + +public class RefreshResponseTests extends ESTestCase { + + public void testToXContent() { + RefreshResponse response = new RefreshResponse(10, 10, 0, null); + String output = Strings.toString(response); + assertEquals("{\"_shards\":{\"total\":10,\"successful\":10,\"failed\":0}}", output); + } + + public void testToAndFromXContent() throws IOException { + doFromXContentTestWithRandomFields(false); + } + + public void testFromXContentWithRandomFields() throws IOException { + doFromXContentTestWithRandomFields(true); + } + + public void testFailuresDeduplication() throws IOException { + List failures = new ArrayList<>(); + Index index = new Index("test", "_na_"); + ElasticsearchException exception1 = new ElasticsearchException("foo", new IllegalArgumentException("bar")); + exception1.setIndex(index); + exception1.setShard(new ShardId(index, 0)); + ElasticsearchException exception2 = new ElasticsearchException("foo", new IllegalArgumentException("bar")); + exception2.setIndex(index); + exception2.setShard(new ShardId(index, 1)); + ElasticsearchException exception3 = new ElasticsearchException("fizz", new IllegalStateException("buzz")); + exception3.setIndex(index); + exception3.setShard(new ShardId(index, 2)); + failures.add(new DefaultShardOperationFailedException(exception1)); + failures.add(new DefaultShardOperationFailedException(exception2)); + failures.add(new DefaultShardOperationFailedException(exception3)); + + RefreshResponse response = new RefreshResponse(10, 7, 3, failures); + boolean humanReadable = randomBoolean(); + XContentType xContentType = randomFrom(XContentType.values()); + BytesReference bytesReference = toShuffledXContent(response, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + RefreshResponse parsedResponse; + try(XContentParser parser = createParser(xContentType.xContent(), bytesReference)) { + parsedResponse = RefreshResponse.fromXContent(parser); + assertNull(parser.nextToken()); + } + + assertThat(parsedResponse.getShardFailures().length, equalTo(2)); + DefaultShardOperationFailedException[] parsedFailures = parsedResponse.getShardFailures(); + assertThat(parsedFailures[0].index(), equalTo("test")); + assertThat(parsedFailures[0].shardId(), anyOf(equalTo(0), equalTo(1))); + assertThat(parsedFailures[0].status(), equalTo(RestStatus.INTERNAL_SERVER_ERROR)); + assertThat(parsedFailures[0].getCause().getMessage(), containsString("foo")); + assertThat(parsedFailures[1].index(), equalTo("test")); + assertThat(parsedFailures[1].shardId(), equalTo(2)); + assertThat(parsedFailures[1].status(), equalTo(RestStatus.INTERNAL_SERVER_ERROR)); + assertThat(parsedFailures[1].getCause().getMessage(), containsString("fizz")); + + ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("group_shard_failures", "false")); + BytesReference bytesReferenceWithoutDedup = toShuffledXContent(response, xContentType, params, humanReadable); + try(XContentParser parser = createParser(xContentType.xContent(), bytesReferenceWithoutDedup)) { + parsedResponse = RefreshResponse.fromXContent(parser); + assertNull(parser.nextToken()); + } + + assertThat(parsedResponse.getShardFailures().length, equalTo(3)); + parsedFailures = parsedResponse.getShardFailures(); + for (int i = 0; i < 3; i++) { + if (i < 2) { + assertThat(parsedFailures[i].index(), equalTo("test")); + assertThat(parsedFailures[i].shardId(), equalTo(i)); + assertThat(parsedFailures[i].status(), equalTo(RestStatus.INTERNAL_SERVER_ERROR)); + assertThat(parsedFailures[i].getCause().getMessage(), containsString("foo")); + } else { + assertThat(parsedFailures[i].index(), equalTo("test")); + assertThat(parsedFailures[i].shardId(), equalTo(i)); + assertThat(parsedFailures[i].status(), equalTo(RestStatus.INTERNAL_SERVER_ERROR)); + assertThat(parsedFailures[i].getCause().getMessage(), containsString("fizz")); + } + } + } + + private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException { + RefreshResponse response = createTestItem(10); + boolean humanReadable = randomBoolean(); + XContentType xContentType = randomFrom(XContentType.values()); + BytesReference bytesReference = toShuffledXContent(response, xContentType, ToXContent.EMPTY_PARAMS, humanReadable); + if (addRandomFields) { + bytesReference = insertRandomFields(xContentType, bytesReference, null, random()); + } + RefreshResponse parsedResponse; + try(XContentParser parser = createParser(xContentType.xContent(), bytesReference)) { + parsedResponse = RefreshResponse.fromXContent(parser); + assertNull(parser.nextToken()); + } + + assertThat(response.getTotalShards(), equalTo(parsedResponse.getTotalShards())); + assertThat(response.getSuccessfulShards(), equalTo(parsedResponse.getSuccessfulShards())); + assertThat(response.getFailedShards(), equalTo(parsedResponse.getFailedShards())); + assertFailureEquals(response.getShardFailures(), parsedResponse.getShardFailures()); + } + + private static void assertFailureEquals(DefaultShardOperationFailedException[] original, + DefaultShardOperationFailedException[] parsedback) { + assertThat(original.length, equalTo(parsedback.length)); + for (int i = 0; i < original.length; i++) { + assertThat(original[i].index(), equalTo(parsedback[i].index())); + assertThat(original[i].shardId(), equalTo(parsedback[i].shardId())); + assertThat(original[i].status(), equalTo(parsedback[i].status())); + assertThat(parsedback[i].getCause().getMessage(), containsString(original[i].getCause().getMessage())); + } + } + + private static RefreshResponse createTestItem(int totalShards) { + List failures = null; + int successfulShards = randomInt(totalShards); + int failedShards = totalShards - successfulShards; + if (failedShards > 0) { + failures = new ArrayList<>(); + for (int i = 0; i < failedShards; i++) { + ElasticsearchException exception = new ElasticsearchException("exception message " + i); + exception.setIndex(new Index("index" + i, "_na_")); + exception.setShard(new ShardId("index" + i, "_na_", i)); + if (randomBoolean()) { + failures.add(new DefaultShardOperationFailedException(exception)); + } else { + failures.add(new DefaultShardOperationFailedException("index" + i, i, new Exception("exception message " + i))); + } + } + } + return new RefreshResponse(totalShards, successfulShards, failedShards, failures); + } +} diff --git a/server/src/test/java/org/elasticsearch/action/support/DefaultShardOperationFailedExceptionTests.java b/server/src/test/java/org/elasticsearch/action/support/DefaultShardOperationFailedExceptionTests.java new file mode 100644 index 0000000000000..28099506e08e6 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/support/DefaultShardOperationFailedExceptionTests.java @@ -0,0 +1,112 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.support; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.XContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; + +public class DefaultShardOperationFailedExceptionTests extends ESTestCase { + + public void testToString() { + { + DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException( + new ElasticsearchException("foo", new IllegalArgumentException("bar", new RuntimeException("baz")))); + assertEquals("[null][-1] failed, reason [ElasticsearchException[foo]; nested: " + + "IllegalArgumentException[bar]; nested: RuntimeException[baz]; ]", exception.toString()); + } + { + ElasticsearchException elasticsearchException = new ElasticsearchException("foo"); + elasticsearchException.setIndex(new Index("index1", "_na_")); + elasticsearchException.setShard(new ShardId("index1", "_na_", 1)); + DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException(elasticsearchException); + assertEquals("[index1][1] failed, reason [ElasticsearchException[foo]]", exception.toString()); + } + { + DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException("index2", 2, new Exception("foo")); + assertEquals("[index2][2] failed, reason [Exception[foo]]", exception.toString()); + } + } + + public void testToXContent() throws IOException { + { + DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException(new ElasticsearchException("foo")); + assertEquals("{\"shard\":-1,\"index\":null,\"status\":\"INTERNAL_SERVER_ERROR\"," + + "\"reason\":{\"type\":\"exception\",\"reason\":\"foo\"}}", Strings.toString(exception)); + } + { + DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException( + new ElasticsearchException("foo", new IllegalArgumentException("bar"))); + assertEquals("{\"shard\":-1,\"index\":null,\"status\":\"INTERNAL_SERVER_ERROR\",\"reason\":{\"type\":\"exception\"," + + "\"reason\":\"foo\",\"caused_by\":{\"type\":\"illegal_argument_exception\",\"reason\":\"bar\"}}}", + Strings.toString(exception)); + } + { + DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException( + new BroadcastShardOperationFailedException(new ShardId("test", "_uuid", 2), "foo", new IllegalStateException("bar"))); + assertEquals("{\"shard\":2,\"index\":\"test\",\"status\":\"INTERNAL_SERVER_ERROR\"," + + "\"reason\":{\"type\":\"illegal_state_exception\",\"reason\":\"bar\"}}", Strings.toString(exception)); + } + { + DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException("test", 1, + new IllegalArgumentException("foo")); + assertEquals("{\"shard\":1,\"index\":\"test\",\"status\":\"BAD_REQUEST\"," + + "\"reason\":{\"type\":\"illegal_argument_exception\",\"reason\":\"foo\"}}", Strings.toString(exception)); + } + } + + public void testFromXContent() throws IOException { + XContent xContent = randomFrom(XContentType.values()).xContent(); + XContentBuilder builder = XContentBuilder.builder(xContent) + .startObject() + .field("shard", 1) + .field("index", "test") + .field("status", "INTERNAL_SERVER_ERROR") + .startObject("reason") + .field("type", "exception") + .field("reason", "foo") + .endObject() + .endObject(); + builder = shuffleXContent(builder); + DefaultShardOperationFailedException parsed; + try(XContentParser parser = createParser(xContent, builder.bytes())) { + assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); + parsed = DefaultShardOperationFailedException.fromXContent(parser); + assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); + assertNull(parser.nextToken()); + } + + assertNotNull(parsed); + assertEquals(parsed.shardId(), 1); + assertEquals(parsed.index(), "test"); + assertEquals(parsed.status(), RestStatus.INTERNAL_SERVER_ERROR); + assertEquals(parsed.getCause().getMessage(), "Elasticsearch exception [type=exception, reason=foo]"); + } +}