Jelajahi Sumber

Add _reload_search_analyzers endpoint to HLRC (#43733)

This change adds the new endpoint that allows reloading of search analyzers to
the high-level java rest client.

Relates to #43313
Christoph Büscher 6 tahun lalu
induk
melakukan
1f61152591

+ 26 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java

@@ -61,6 +61,8 @@ import org.elasticsearch.client.indices.GetMappingsResponse;
 import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
 import org.elasticsearch.client.indices.PutIndexTemplateRequest;
 import org.elasticsearch.client.indices.PutMappingRequest;
+import org.elasticsearch.client.indices.ReloadAnalyzersRequest;
+import org.elasticsearch.client.indices.ReloadAnalyzersResponse;
 import org.elasticsearch.client.indices.UnfreezeIndexRequest;
 import org.elasticsearch.client.indices.rollover.RolloverRequest;
 import org.elasticsearch.client.indices.rollover.RolloverResponse;
@@ -1328,4 +1330,28 @@ public final class IndicesClient {
         restHighLevelClient.performRequestAsyncAndParseEntity(request, IndicesRequestConverters::deleteTemplate,
             options, AcknowledgedResponse::fromXContent, listener, emptySet());
     }
+
+    /**
+     * Synchronously calls the _reload_search_analyzers API
+     *
+     * @param request the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     */
+    public ReloadAnalyzersResponse reloadAnalyzers(ReloadAnalyzersRequest request, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, IndicesRequestConverters::reloadAnalyzers, options,
+                ReloadAnalyzersResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously calls the _reload_search_analyzers API
+     *
+     * @param request the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     */
+    public void reloadAnalyzersAsync(ReloadAnalyzersRequest request, RequestOptions options,
+            ActionListener<ReloadAnalyzersResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request, IndicesRequestConverters::reloadAnalyzers, options,
+                ReloadAnalyzersResponse::fromXContent, listener, emptySet());
+    }
 }

+ 10 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java

@@ -50,6 +50,7 @@ import org.elasticsearch.client.indices.GetMappingsRequest;
 import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
 import org.elasticsearch.client.indices.PutIndexTemplateRequest;
 import org.elasticsearch.client.indices.PutMappingRequest;
+import org.elasticsearch.client.indices.ReloadAnalyzersRequest;
 import org.elasticsearch.client.indices.UnfreezeIndexRequest;
 import org.elasticsearch.client.indices.rollover.RolloverRequest;
 import org.elasticsearch.common.Strings;
@@ -644,4 +645,13 @@ final class IndicesRequestConverters {
         request.addParameters(params.asMap());
         return request;
     }
+
+    static Request reloadAnalyzers(ReloadAnalyzersRequest reloadAnalyzersRequest) {
+        String endpoint = RequestConverters.endpoint(reloadAnalyzersRequest.getIndices(), "_reload_search_analyzers");
+        Request request = new Request(HttpPost.METHOD_NAME, endpoint);
+        RequestConverters.Params parameters = new RequestConverters.Params();
+        parameters.withIndicesOptions(reloadAnalyzersRequest.indicesOptions());
+        request.addParameters(parameters.asMap());
+        return request;
+    }
 }

+ 6 - 2
client/rest-high-level/src/main/java/org/elasticsearch/client/core/BroadcastResponse.java

@@ -45,7 +45,7 @@ public class BroadcastResponse {
         return shards;
     }
 
-    BroadcastResponse(final Shards shards) {
+    protected BroadcastResponse(final Shards shards) {
         this.shards = Objects.requireNonNull(shards);
     }
 
@@ -56,7 +56,7 @@ public class BroadcastResponse {
             a -> new BroadcastResponse((Shards) a[0]));
 
     static {
-        PARSER.declareObject(ConstructingObjectParser.constructorArg(), Shards.SHARDS_PARSER, SHARDS_FIELD);
+        declareShardsField(PARSER);
     }
 
     /**
@@ -70,6 +70,10 @@ public class BroadcastResponse {
         return PARSER.parse(parser, null);
     }
 
+    protected static <T extends BroadcastResponse> void declareShardsField(ConstructingObjectParser<T, Void> PARSER) {
+        PARSER.declareObject(ConstructingObjectParser.constructorArg(), Shards.SHARDS_PARSER, SHARDS_FIELD);
+    }
+
     /**
      * Represents the results of a collection of shards on which a request was executed against.
      */

+ 68 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/indices/ReloadAnalyzersRequest.java

@@ -0,0 +1,68 @@
+/*
+ * 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.client.indices;
+
+import org.elasticsearch.action.support.IndicesOptions;
+import org.elasticsearch.client.Validatable;
+
+import java.util.Objects;
+
+/**
+ * Request for the _reload_search_analyzers API
+ */
+public final class ReloadAnalyzersRequest implements Validatable {
+
+    private final String[] indices;
+    private IndicesOptions indicesOptions = IndicesOptions.strictExpandOpen();
+
+    /**
+     * Creates a new reload analyzers request
+     * @param indices the index for which to reload analyzers
+     */
+    public ReloadAnalyzersRequest(String... indices) {
+        this.indices = Objects.requireNonNull(indices);
+    }
+
+    /**
+     * Returns the indices
+     */
+    public String[] getIndices() {
+        return indices;
+    }
+
+    /**
+     * Specifies what type of requested indices to ignore and how to deal with wildcard expressions.
+     * For example indices that don't exist.
+     *
+     * @return the current behaviour when it comes to index names and wildcard indices expressions
+     */
+    public IndicesOptions indicesOptions() {
+        return indicesOptions;
+    }
+
+    /**
+     * Specifies what type of requested indices to ignore and how to deal with wildcard expressions.
+     * For example indices that don't exist.
+     *
+     * @param indicesOptions the desired behaviour regarding indices to ignore and wildcard indices expressions
+     */
+    public void setIndicesOptions(IndicesOptions indicesOptions) {
+        this.indicesOptions = indicesOptions;
+    }
+}

+ 108 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/indices/ReloadAnalyzersResponse.java

@@ -0,0 +1,108 @@
+/*
+ * 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.client.indices;
+
+import org.elasticsearch.client.core.BroadcastResponse;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+
+/**
+ * The response object that will be returned when reloading analyzers
+ */
+public class ReloadAnalyzersResponse extends BroadcastResponse {
+
+    private final Map<String, ReloadDetails> reloadDetails;
+
+    ReloadAnalyzersResponse(final Shards shards, Map<String, ReloadDetails> reloadDetails) {
+        super(shards);
+        this.reloadDetails = reloadDetails;
+    }
+
+    @SuppressWarnings({ "unchecked" })
+    private static final ConstructingObjectParser<ReloadAnalyzersResponse, Void> PARSER = new ConstructingObjectParser<>("reload_analyzer",
+            true, arg -> {
+                Shards shards = (Shards) arg[0];
+                List<Tuple<String, ReloadDetails>> results = (List<Tuple<String, ReloadDetails>>) arg[1];
+                Map<String, ReloadDetails> reloadDetails = new HashMap<>();
+                for (Tuple<String, ReloadDetails> result : results) {
+                    reloadDetails.put(result.v1(), result.v2());
+                }
+                return new ReloadAnalyzersResponse(shards, reloadDetails);
+            });
+
+    @SuppressWarnings({ "unchecked" })
+    private static final ConstructingObjectParser<Tuple<String, ReloadDetails>, Void> ENTRY_PARSER = new ConstructingObjectParser<>(
+            "reload_analyzer.entry", true, arg -> {
+                String index = (String) arg[0];
+                Set<String> nodeIds = new HashSet<>((List<String>) arg[1]);
+                Set<String> analyzers = new HashSet<>((List<String>) arg[2]);
+                return new Tuple<>(index, new ReloadDetails(index, nodeIds, analyzers));
+            });
+
+    static {
+        declareShardsField(PARSER);
+        PARSER.declareObjectArray(constructorArg(), ENTRY_PARSER, new ParseField("reload_details"));
+        ENTRY_PARSER.declareString(constructorArg(), new ParseField("index"));
+        ENTRY_PARSER.declareStringArray(constructorArg(), new ParseField("reloaded_node_ids"));
+        ENTRY_PARSER.declareStringArray(constructorArg(), new ParseField("reloaded_analyzers"));
+    }
+
+    public static ReloadAnalyzersResponse fromXContent(XContentParser parser) {
+        return PARSER.apply(parser, null);
+    }
+
+    public Map<String, ReloadDetails> getReloadedDetails() {
+        return reloadDetails;
+    }
+
+    public static class ReloadDetails {
+
+        private final String indexName;
+        private final Set<String> reloadedIndicesNodes;
+        private final Set<String> reloadedAnalyzers;
+
+        public ReloadDetails(String name, Set<String> reloadedIndicesNodes, Set<String> reloadedAnalyzers) {
+            this.indexName = name;
+            this.reloadedIndicesNodes = reloadedIndicesNodes;
+            this.reloadedAnalyzers = reloadedAnalyzers;
+        }
+
+        public String getIndexName() {
+            return indexName;
+        }
+
+        public Set<String> getReloadedIndicesNodes() {
+            return reloadedIndicesNodes;
+        }
+
+        public Set<String> getReloadedAnalyzers() {
+            return reloadedAnalyzers;
+        }
+    }
+}

+ 12 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java

@@ -73,6 +73,8 @@ import org.elasticsearch.client.indices.IndexTemplateMetaData;
 import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
 import org.elasticsearch.client.indices.PutIndexTemplateRequest;
 import org.elasticsearch.client.indices.PutMappingRequest;
+import org.elasticsearch.client.indices.ReloadAnalyzersRequest;
+import org.elasticsearch.client.indices.ReloadAnalyzersResponse;
 import org.elasticsearch.client.indices.UnfreezeIndexRequest;
 import org.elasticsearch.client.indices.rollover.RolloverRequest;
 import org.elasticsearch.client.indices.rollover.RolloverResponse;
@@ -1877,4 +1879,14 @@ public class IndicesClientIT extends ESRestHighLevelClientTestCase {
         assertTrue(unfreeze.isShardsAcknowledged());
         assertTrue(unfreeze.isAcknowledged());
     }
+
+    public void testReloadAnalyzer() throws IOException {
+        createIndex("test", Settings.EMPTY);
+        RestHighLevelClient client = highLevelClient();
+
+        ReloadAnalyzersResponse reloadResponse = execute(new ReloadAnalyzersRequest("test"), client.indices()::reloadAnalyzers,
+            client.indices()::reloadAnalyzersAsync);
+        assertNotNull(reloadResponse.shards());
+        assertTrue(reloadResponse.getReloadedDetails().containsKey("test"));
+    }
 }

+ 18 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesRequestConvertersTests.java

@@ -54,6 +54,7 @@ import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
 import org.elasticsearch.client.indices.PutIndexTemplateRequest;
 import org.elasticsearch.client.indices.PutMappingRequest;
 import org.elasticsearch.client.indices.RandomCreateIndexGenerator;
+import org.elasticsearch.client.indices.ReloadAnalyzersRequest;
 import org.elasticsearch.client.indices.rollover.RolloverRequest;
 import org.elasticsearch.common.CheckedFunction;
 import org.elasticsearch.common.Strings;
@@ -1215,4 +1216,21 @@ public class IndicesRequestConvertersTests extends ESTestCase {
         Assert.assertThat(request.getParameters(), equalTo(expectedParams));
         Assert.assertThat(request.getEntity(), nullValue());
     }
+
+    public void testReloadAnalyzers() {
+        String[] indices = RequestConvertersTests.randomIndicesNames(1, 5);
+        StringJoiner endpoint = new StringJoiner("/", "/", "");
+        if (indices != null && indices.length > 0) {
+            endpoint.add(String.join(",", indices));
+        }
+        ReloadAnalyzersRequest reloadRequest = new ReloadAnalyzersRequest(indices);
+        Map<String, String> expectedParams = new HashMap<>();
+        RequestConvertersTests.setRandomIndicesOptions(reloadRequest::setIndicesOptions, reloadRequest::indicesOptions,
+                expectedParams);
+        Request request = IndicesRequestConverters.reloadAnalyzers(reloadRequest);
+        Assert.assertThat(request.getMethod(), equalTo(HttpPost.METHOD_NAME));
+        Assert.assertThat(request.getEndpoint(), equalTo(endpoint + "/_reload_search_analyzers"));
+        Assert.assertThat(request.getParameters(), equalTo(expectedParams));
+        Assert.assertThat(request.getEntity(), nullValue());
+    }
 }

+ 1 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

@@ -824,6 +824,7 @@ public class RestHighLevelClientTests extends ESTestCase {
                                 apiName.startsWith("ccr.") == false &&
                                 apiName.startsWith("data_frame") == false &&
                                 apiName.endsWith("freeze") == false &&
+                                apiName.endsWith("reload_analyzers") == false &&
                                 // IndicesClientIT.getIndexTemplate should be renamed "getTemplate" in version 8.0 when we
                                 // can get rid of 7.0's deprecated "getTemplate"
                                 apiName.equals("indices.get_index_template") == false) {

+ 77 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java

@@ -58,6 +58,7 @@ import org.elasticsearch.client.GetAliasesResponse;
 import org.elasticsearch.client.RequestOptions;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.elasticsearch.client.SyncedFlushResponse;
+import org.elasticsearch.client.core.BroadcastResponse.Shards;
 import org.elasticsearch.client.core.ShardsAcknowledgedResponse;
 import org.elasticsearch.client.indices.AnalyzeRequest;
 import org.elasticsearch.client.indices.AnalyzeResponse;
@@ -77,6 +78,9 @@ import org.elasticsearch.client.indices.IndexTemplateMetaData;
 import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
 import org.elasticsearch.client.indices.PutIndexTemplateRequest;
 import org.elasticsearch.client.indices.PutMappingRequest;
+import org.elasticsearch.client.indices.ReloadAnalyzersRequest;
+import org.elasticsearch.client.indices.ReloadAnalyzersResponse;
+import org.elasticsearch.client.indices.ReloadAnalyzersResponse.ReloadDetails;
 import org.elasticsearch.client.indices.UnfreezeIndexRequest;
 import org.elasticsearch.client.indices.rollover.RolloverRequest;
 import org.elasticsearch.client.indices.rollover.RolloverResponse;
@@ -2748,4 +2752,77 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
 
         assertTrue(latch.await(30L, TimeUnit.SECONDS));
     }
+
+    public void testReloadSearchAnalyzers() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+        {
+            CreateIndexResponse createIndexResponse = client.indices().create(new CreateIndexRequest("index"), RequestOptions.DEFAULT);
+            assertTrue(createIndexResponse.isAcknowledged());
+        }
+
+        {
+            // tag::reload-analyzers-request
+            ReloadAnalyzersRequest request = new ReloadAnalyzersRequest("index"); // <1>
+            // end::reload-analyzers-request
+
+            // tag::reload-analyzers-request-indicesOptions
+            request.setIndicesOptions(IndicesOptions.strictExpandOpen()); // <1>
+            // end::reload-analyzers-request-indicesOptions
+
+            // tag::reload-analyzers-execute
+            ReloadAnalyzersResponse reloadResponse = client.indices().reloadAnalyzers(request, RequestOptions.DEFAULT);
+            // end::reload-analyzers-execute
+
+            // tag::reload-analyzers-response
+            Shards shards = reloadResponse.shards(); // <1>
+            Map<String, ReloadDetails> reloadDetails = reloadResponse.getReloadedDetails(); // <2>
+            ReloadDetails details = reloadDetails.get("index"); // <3>
+            String indexName = details.getIndexName(); // <4>
+            Set<String> indicesNodes = details.getReloadedIndicesNodes(); // <5>
+            Set<String> analyzers = details.getReloadedAnalyzers();  // <6>
+            // end::reload-analyzers-response
+            assertNotNull(shards);
+            assertEquals("index", indexName);
+            assertEquals(1, indicesNodes.size());
+            assertEquals(0, analyzers.size());
+
+            // tag::reload-analyzers-execute-listener
+            ActionListener<ReloadAnalyzersResponse> listener =
+                new ActionListener<ReloadAnalyzersResponse>() {
+                    @Override
+                    public void onResponse(ReloadAnalyzersResponse reloadResponse) {
+                        // <1>
+                    }
+
+                    @Override
+                    public void onFailure(Exception e) {
+                        // <2>
+                    }
+                };
+            // end::reload-analyzers-execute-listener
+
+            // Replace the empty listener by a blocking listener in test
+            final CountDownLatch latch = new CountDownLatch(1);
+            listener = new LatchedActionListener<>(listener, latch);
+
+            // tag::reload-analyzers-execute-async
+            client.indices().reloadAnalyzersAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            // end::reload-analyzers-execute-async
+
+            assertTrue(latch.await(30L, TimeUnit.SECONDS));
+        }
+
+        {
+            // tag::reload-analyzers-notfound
+            try {
+                ReloadAnalyzersRequest request = new ReloadAnalyzersRequest("does_not_exist");
+                client.indices().reloadAnalyzers(request, RequestOptions.DEFAULT);
+            } catch (ElasticsearchException exception) {
+                if (exception.status() == RestStatus.BAD_REQUEST) {
+                    // <1>
+                }
+            }
+            // end::reload-analyzers-notfound
+        }
+    }
 }

+ 111 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/indices/ReloadAnalyzersResponseTests.java

@@ -0,0 +1,111 @@
+/*
+ * 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.client.indices;
+
+import org.elasticsearch.action.support.DefaultShardOperationFailedException;
+import org.elasticsearch.client.AbstractResponseTestCase;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.index.seqno.RetentionLeaseNotFoundException;
+import org.elasticsearch.xpack.core.action.ReloadAnalyzersResponse.ReloadDetails;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.isIn;
+
+public class ReloadAnalyzersResponseTests
+        extends AbstractResponseTestCase<org.elasticsearch.xpack.core.action.ReloadAnalyzersResponse, ReloadAnalyzersResponse> {
+
+    private String index;
+    private String id;
+    private Set<Integer> shardIds;
+
+    @Override
+    protected org.elasticsearch.xpack.core.action.ReloadAnalyzersResponse createServerTestInstance() {
+        index = randomAlphaOfLength(8);
+        id = randomAlphaOfLength(8);
+        final int total = randomIntBetween(1, 16);
+        final int successful = total - scaledRandomIntBetween(0, total);
+        final int failed = scaledRandomIntBetween(0, total - successful);
+        final List<DefaultShardOperationFailedException> failures = new ArrayList<>();
+        shardIds = new HashSet<>();
+        for (int i = 0; i < failed; i++) {
+            final DefaultShardOperationFailedException failure = new DefaultShardOperationFailedException(
+                index,
+                randomValueOtherThanMany(shardIds::contains, () -> randomIntBetween(0, total - 1)),
+                new RetentionLeaseNotFoundException(id));
+            failures.add(failure);
+            shardIds.add(failure.shardId());
+        }
+        Map<String, ReloadDetails> reloadedDetailsMap = new HashMap<>();
+        int randomIndices = randomIntBetween(0, 5);
+        for (int i = 0; i < randomIndices; i++) {
+            String indexName = randomAlphaOfLengthBetween(5, 10);
+            Set<String> randomNodeIds = new HashSet<>(Arrays.asList(generateRandomStringArray(5, 5, false, true)));
+            Set<String> randomAnalyzers = new HashSet<>(Arrays.asList(generateRandomStringArray(5, 5, false, true)));
+
+            ReloadDetails reloadedDetails = new ReloadDetails(indexName, randomNodeIds, randomAnalyzers);
+            reloadedDetailsMap.put(indexName, reloadedDetails);
+        }
+        return new org.elasticsearch.xpack.core.action.ReloadAnalyzersResponse(total, successful, failed, failures, reloadedDetailsMap);
+    }
+
+    @Override
+    protected ReloadAnalyzersResponse doParseToClientInstance(XContentParser parser) throws IOException {
+        return ReloadAnalyzersResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected void assertInstances(org.elasticsearch.xpack.core.action.ReloadAnalyzersResponse serverTestInstance,
+            ReloadAnalyzersResponse clientInstance) {
+        assertThat(clientInstance.shards().total(), equalTo(serverTestInstance.getTotalShards()));
+        assertThat(clientInstance.shards().successful(), equalTo(serverTestInstance.getSuccessfulShards()));
+        assertThat(clientInstance.shards().skipped(), equalTo(0));
+        assertThat(clientInstance.shards().failed(), equalTo(serverTestInstance.getFailedShards()));
+        assertThat(clientInstance.shards().failures(), hasSize(clientInstance.shards().failed() == 0 ? 0 : 1)); // failures are grouped
+        if (clientInstance.shards().failed() > 0) {
+            final DefaultShardOperationFailedException groupedFailure = clientInstance.shards().failures().iterator().next();
+            assertThat(groupedFailure.index(), equalTo(index));
+            assertThat(groupedFailure.shardId(), isIn(shardIds));
+            assertThat(groupedFailure.reason(), containsString("reason=retention lease with ID [" + id + "] not found"));
+        }
+        Map<String, ReloadDetails> serverDetails = serverTestInstance.getReloadDetails();
+        assertThat(clientInstance.getReloadedDetails().size(), equalTo(serverDetails.size()));
+        for (Entry<String, org.elasticsearch.client.indices.ReloadAnalyzersResponse.ReloadDetails> entry : clientInstance
+                .getReloadedDetails().entrySet()) {
+            String indexName = entry.getKey();
+            assertTrue(serverDetails.keySet().contains(indexName));
+            assertEquals(serverDetails.get(indexName).getIndexName(), entry.getValue().getIndexName());
+            assertEquals(serverDetails.get(indexName).getReloadedAnalyzers(), entry.getValue().getReloadedAnalyzers());
+            assertEquals(serverDetails.get(indexName).getReloadedIndicesNodes(), entry.getValue().getReloadedIndicesNodes());
+        }
+    }
+
+}

+ 50 - 0
docs/java-rest/high-level/indices/reload_analyzers.asciidoc

@@ -0,0 +1,50 @@
+--
+:api: reload-analyzers
+:request: ReloadAnalyzersRequest
+:response: ReloadAnalyzersResponse
+--
+
+[id="{upid}-{api}"]
+=== Reload Search Analyzers API
+
+[id="{upid}-{api}-request"]
+==== Reload Search Analyzers Request
+
+An +{request}+ requires an `index` argument:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+<1> The index to reload
+
+==== Optional arguments
+The following arguments can optionally be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-indicesOptions]
+--------------------------------------------------
+<1> Setting `IndicesOptions` controls how unavailable indices are resolved and
+how wildcard expressions are expanded
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Reload Search Analyzers Response
+
+The returned +{response}+ allows to retrieve information about the
+executed operation as follows:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> Shard statistics. Note that reloading does not happen on each shard of an
+index, but once on each node the index has shards on. The reported shard count
+can therefore differ from the number of index shards
+<2> Reloading details of all indices the request was executed on
+<3> Details can be retrieved by index name
+<4> The reloaded index name
+<5> The nodes the index was reloaded on
+<6> The analyzer names that were reloaded

+ 1 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -155,6 +155,7 @@ include::indices/get_index.asciidoc[]
 include::indices/freeze_index.asciidoc[]
 include::indices/unfreeze_index.asciidoc[]
 include::indices/delete_template.asciidoc[]
+include::indices/reload_analyzers.asciidoc[]
 
 == Cluster APIs
 

+ 4 - 0
docs/reference/indices/apis/reload-analyzers.asciidoc

@@ -99,3 +99,7 @@ analyzers that were reloaded:
 // TESTRESPONSE[s/"total" : 2/"total" : $body._shards.total/]
 // TESTRESPONSE[s/"successful" : 2/"successful" : $body._shards.successful/]
 // TESTRESPONSE[s/mfdqTXn_T7SGr2Ho2KT8uw/$body.reload_details.0.reloaded_node_ids.0/]
+
+NOTE: Reloading does not happen on each shard of an index, but once on each node
+the index has shards on. The total shard count can therefore differ from the number
+of index shards.

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/ReloadAnalyzersResponse.java

@@ -105,7 +105,7 @@ public class ReloadAnalyzersResponse extends BroadcastResponse  {
         private final Set<String> reloadedIndicesNodes;
         private final Set<String> reloadedAnalyzers;
 
-        ReloadDetails(String name, Set<String> reloadedIndicesNodes, Set<String> reloadedAnalyzers) {
+        public ReloadDetails(String name, Set<String> reloadedIndicesNodes, Set<String> reloadedAnalyzers) {
             this.indexName = name;
             this.reloadedIndicesNodes = reloadedIndicesNodes;
             this.reloadedAnalyzers = reloadedAnalyzers;