From 27d4ce272db8046079e835ad6eb8c6d8e8ac0342 Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Sun, 1 Apr 2018 20:48:59 -0400 Subject: [PATCH 01/14] Update NOTICE to 2018 --- NOTICE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE.txt b/NOTICE.txt index 643a060cd05c4..f1e3198ab4a9a 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Elasticsearch -Copyright 2009-2017 Elasticsearch +Copyright 2009-2018 Elasticsearch This product includes software developed by The Apache Software Foundation (http://www.apache.org/). From d1777368921e1a34ddb31a63ef20cd13a5e315f7 Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Sun, 1 Apr 2018 22:51:04 -0400 Subject: [PATCH 02/14] REST high-level client: add Cluster Health API --- .../elasticsearch/client/ClusterClient.java | 24 +++ .../org/elasticsearch/client/Request.java | 43 +++++ .../elasticsearch/client/ClusterClientIT.java | 22 +++ .../elasticsearch/client/RequestTests.java | 22 +++ .../cluster/health/ClusterHealthResponse.java | 166 +++++++++++++++--- .../cluster/health/ClusterIndexHealth.java | 159 ++++++++++++++--- .../cluster/health/ClusterShardHealth.java | 102 ++++++++++- .../cluster/health/ClusterStateHealth.java | 65 ++++++- .../common/util/CollectionUtils.java | 68 ++++++- .../health/ClusterHealthResponsesTests.java | 93 ++++++++-- .../health/ClusterIndexHealthTests.java | 52 +++++- .../health/ClusterShardHealthTests.java | 46 +++++ .../test/AbstractSerializingTestCase.java | 6 +- .../AbstractStreamableXContentTestCase.java | 6 +- .../test/AbstractXContentTestCase.java | 24 +-- 15 files changed, 805 insertions(+), 93 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java index 177e33d727010..12d93b5e8df01 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java @@ -21,6 +21,8 @@ import org.apache.http.Header; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; @@ -63,4 +65,26 @@ public void putSettingsAsync(ClusterUpdateSettingsRequest clusterUpdateSettingsR restHighLevelClient.performRequestAsyncAndParseEntity(clusterUpdateSettingsRequest, Request::clusterPutSettings, ClusterUpdateSettingsResponse::fromXContent, listener, emptySet(), headers); } + + /** + * Get cluster health using the Cluster Health API + *

+ * See + * Cluster Health API on elastic.co + */ + public ClusterHealthResponse health(ClusterHealthRequest healthRequest, Header... headers) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(healthRequest, Request::clusterHealth, ClusterHealthResponse::fromXContent, + emptySet(), headers); + } + + /** + * Asynchronously get cluster health using the Cluster Health API + *

+ * See + * Cluster Health API on elastic.co + */ + public void healthAsync(ClusterHealthRequest healthRequest, ActionListener listener, Header... headers) { + restHighLevelClient.performRequestAsyncAndParseEntity(healthRequest, Request::clusterHealth, ClusterHealthResponse::fromXContent, + listener, emptySet(), headers); + } } 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 802b1492be092..3506baa2702df 100644 --- 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 @@ -29,6 +29,7 @@ import org.apache.http.entity.ContentType; import org.apache.lucene.util.BytesRef; import org.elasticsearch.action.DocWriteRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; @@ -58,6 +59,7 @@ import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.SuppressForbidden; @@ -583,6 +585,22 @@ static Request clusterPutSettings(ClusterUpdateSettingsRequest clusterUpdateSett return new Request(HttpPut.METHOD_NAME, "/_cluster/settings", parameters.getParams(), entity); } + static Request clusterHealth(ClusterHealthRequest healthRequest) { + Params params = Params.builder(); + params.withWaitForStatus(healthRequest.waitForStatus()); + params.withWaitForNoRelocatingShards(healthRequest.waitForNoRelocatingShards()); + params.withWaitForNoInitializingShards(healthRequest.waitForNoInitializingShards()); + params.withWaitForActiveShards(healthRequest.waitForActiveShards()); + params.withWaitForNodes(healthRequest.waitForNodes()); + params.withTimeout(healthRequest.timeout()); + params.withMasterTimeout(healthRequest.masterNodeTimeout()); + params.withLocal(healthRequest.local()); + params.putParam("level", "shards"); + String[] indices = healthRequest.indices() == null ? Strings.EMPTY_ARRAY : healthRequest.indices(); + String endpoint = endpoint(indices, "_cluster/health"); + return new Request(HttpGet.METHOD_NAME, endpoint, params.getParams(), null); + } + static Request rollover(RolloverRequest rolloverRequest) throws IOException { Params params = Params.builder(); params.withTimeout(rolloverRequest.timeout()); @@ -833,6 +851,31 @@ Params withIncludeDefaults(boolean includeDefaults) { return this; } + Params withWaitForStatus(ClusterHealthStatus status) { + if (status != null) { + return putParam("wait_for_status", status.name().toLowerCase(Locale.ROOT)); + } + return this; + } + + Params withWaitForNoRelocatingShards(boolean waitNoRelocatingShards) { + if (waitNoRelocatingShards) { + return putParam("wait_for_no_relocating_shards", Boolean.TRUE.toString()); + } + return this; + } + + Params withWaitForNoInitializingShards(boolean waitNoInitShards) { + if (waitNoInitShards) { + return putParam("wait_for_no_initializing_shards", Boolean.TRUE.toString()); + } + return this; + } + + Params withWaitForNodes(String waitForNodes) { + return putParam("wait_for_nodes", waitForNodes); + } + Map getParams() { return Collections.unmodifiableMap(params); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java index 9314bb2e36cea..b74b85540f730 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java @@ -20,8 +20,11 @@ package org.elasticsearch.client; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; +import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; @@ -34,8 +37,10 @@ import java.util.HashMap; import java.util.Map; +import static java.util.Collections.emptyMap; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @@ -105,4 +110,21 @@ public void testClusterUpdateSettingNonExistent() { assertThat(exception.getMessage(), equalTo( "Elasticsearch exception [type=illegal_argument_exception, reason=transient setting [" + setting + "], not recognized]")); } + + public void testClusterHealth() throws IOException { + ClusterHealthRequest request = new ClusterHealthRequest(); + ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); + + assertThat(response, notNullValue()); + assertThat(response.isTimedOut(), is(false)); + assertThat(response.status(), is(RestStatus.OK)); + assertThat(response.getStatus(), is(ClusterHealthStatus.GREEN)); + assertThat(response.getIndices(), is(emptyMap())); + assertThat(response.getActivePrimaryShards(), is(0)); + assertThat(response.getActiveShards(), is(0)); + assertThat(response.getDelayedUnassignedShards(), is(0)); + assertThat(response.getInitializingShards(), is(0)); + assertThat(response.getUnassignedShards(), is(0)); + assertThat(response.getActiveShardsPercent(), is(100d)); + } } 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 75ac543fbb4ce..29377fac4256c 100644 --- 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 @@ -31,6 +31,7 @@ import org.apache.http.util.EntityUtils; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.DocWriteRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions; @@ -127,6 +128,8 @@ import static org.elasticsearch.search.RandomSearchRequestGenerator.randomSearchRequest; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.Matchers.nullValue; public class RequestTests extends ESTestCase { @@ -1325,6 +1328,25 @@ public void testClusterPutSettings() throws IOException { assertEquals(expectedParams, expectedRequest.getParameters()); } + public void testClusterHealth() { + ClusterHealthRequest healthRequest = new ClusterHealthRequest(); + Map expectedParams = new HashMap<>(); + setRandomLocal(healthRequest, expectedParams); + setRandomTimeout(healthRequest::timeout, AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, expectedParams); + setRandomMasterTimeout(healthRequest, expectedParams); + expectedParams.put("level", "shards"); + // Default value in ClusterHealthRequest is NONE but in Request.Params::withWaitForActiveShards is DEFAULT + expectedParams.put("wait_for_active_shards", "0"); + //TODO add random filling for other properties + + Request request = Request.clusterHealth(healthRequest); + assertThat(request, is(notNullValue())); + assertThat(request.getMethod(), is(HttpGet.METHOD_NAME)); + assertThat(request.getEntity(), is(nullValue())); + assertThat(request.getEndpoint(), is("/_cluster/health")); + assertThat(request.getParameters(), is(expectedParams)); + } + public void testRollover() throws IOException { RolloverRequest rolloverRequest = new RolloverRequest(randomAlphaOfLengthBetween(3, 10), randomBoolean() ? null : randomAlphaOfLengthBetween(3, 10)); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java index 74779711c73a9..dcaadd4af6456 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java @@ -24,19 +24,114 @@ import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.health.ClusterIndexHealth; import org.elasticsearch.cluster.health.ClusterStateHealth; -import org.elasticsearch.common.Strings; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.StatusToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.rest.RestStatus; import java.io.IOException; +import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; + +import static java.util.Collections.emptyMap; +import static org.elasticsearch.common.util.CollectionUtils.isEmpty; +import static org.elasticsearch.common.util.CollectionUtils.newHashMapWithExpectedSize; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; public class ClusterHealthResponse extends ActionResponse implements StatusToXContentObject { + private static final String CLUSTER_NAME = "cluster_name"; + private static final String STATUS = "status"; + private static final String TIMED_OUT = "timed_out"; + private static final String NUMBER_OF_NODES = "number_of_nodes"; + private static final String NUMBER_OF_DATA_NODES = "number_of_data_nodes"; + private static final String NUMBER_OF_PENDING_TASKS = "number_of_pending_tasks"; + private static final String NUMBER_OF_IN_FLIGHT_FETCH = "number_of_in_flight_fetch"; + private static final String DELAYED_UNASSIGNED_SHARDS = "delayed_unassigned_shards"; + private static final String TASK_MAX_WAIT_TIME_IN_QUEUE = "task_max_waiting_in_queue"; + private static final String TASK_MAX_WAIT_TIME_IN_QUEUE_IN_MILLIS = "task_max_waiting_in_queue_millis"; + private static final String ACTIVE_SHARDS_PERCENT_AS_NUMBER = "active_shards_percent_as_number"; + private static final String ACTIVE_SHARDS_PERCENT = "active_shards_percent"; + private static final String ACTIVE_PRIMARY_SHARDS = "active_primary_shards"; + private static final String ACTIVE_SHARDS = "active_shards"; + private static final String RELOCATING_SHARDS = "relocating_shards"; + private static final String INITIALIZING_SHARDS = "initializing_shards"; + private static final String UNASSIGNED_SHARDS = "unassigned_shards"; + private static final String INDICES = "indices"; + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("cluster_health_response", true, + a -> { + int i = 0; + // ClusterStateHealth fields + int numberOfNodes = (int) a[i++]; + int numberOfDataNodes = (int) a[i++]; + int activeShards = (int) a[i++]; + int relocatingShards = (int) a[i++]; + int activePrimaryShards = (int) a[i++]; + int initializingShards = (int) a[i++]; + int unassignedShards = (int) a[i++]; + double activeShardsPercent = (double) a[i++]; + String statusStr = (String) a[i++]; + ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); + List indexList = (List) a[i++]; + final Map indices; + if (isEmpty(indexList)) { + indices = emptyMap(); + } else { + indices = newHashMapWithExpectedSize(indexList.size()); + for (ClusterIndexHealth indexHealth : indexList) { + indices.put(indexHealth.getIndex(), indexHealth); + } + } + ClusterStateHealth stateHealth = new ClusterStateHealth(activePrimaryShards, activeShards, relocatingShards, + initializingShards, unassignedShards, numberOfNodes, numberOfDataNodes, activeShardsPercent, status, indices); + + // ClusterHealthResponse fields + String clusterName = (String) a[i++]; + int numberOfPendingTasks = (int) a[i++]; + int numberOfInFlightFetch = (int) a[i++]; + int delayedUnassignedShards = (int) a[i++]; + long taskMaxWaitingTimeMillis = (long) a[i++]; + boolean timedOut = (boolean) a[i++]; + return new ClusterHealthResponse(clusterName, numberOfPendingTasks, numberOfInFlightFetch, delayedUnassignedShards, + TimeValue.timeValueMillis(taskMaxWaitingTimeMillis), timedOut, stateHealth); + }); + + public static final ObjectParser.NamedObjectParser INDEX_PARSER = + (XContentParser p, Void c, String nameIgnored) -> ClusterIndexHealth.fromXContent(p); + + static { + // ClusterStateHealth fields + PARSER.declareInt(constructorArg(), new ParseField(NUMBER_OF_NODES)); + PARSER.declareInt(constructorArg(), new ParseField(NUMBER_OF_DATA_NODES)); + PARSER.declareInt(constructorArg(), new ParseField(ACTIVE_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(RELOCATING_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(ACTIVE_PRIMARY_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(INITIALIZING_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(UNASSIGNED_SHARDS)); + PARSER.declareDouble(constructorArg(), new ParseField(ACTIVE_SHARDS_PERCENT_AS_NUMBER)); + PARSER.declareString(constructorArg(), new ParseField(STATUS)); + // Can be absent if LEVEL == 'cluster' + PARSER.declareNamedObjects(optionalConstructorArg(), INDEX_PARSER, new ParseField(INDICES)); + + // ClusterHealthResponse fields + PARSER.declareString(constructorArg(), new ParseField(CLUSTER_NAME)); + PARSER.declareInt(constructorArg(), new ParseField(NUMBER_OF_PENDING_TASKS)); + PARSER.declareInt(constructorArg(), new ParseField(NUMBER_OF_IN_FLIGHT_FETCH)); + PARSER.declareInt(constructorArg(), new ParseField(DELAYED_UNASSIGNED_SHARDS)); + PARSER.declareLong(constructorArg(), new ParseField(TASK_MAX_WAIT_TIME_IN_QUEUE_IN_MILLIS)); + PARSER.declareBoolean(constructorArg(), new ParseField(TIMED_OUT)); + } + private String clusterName; private int numberOfPendingTasks = 0; private int numberOfInFlightFetch = 0; @@ -60,11 +155,23 @@ public ClusterHealthResponse(String clusterName, String[] concreteIndices, Clust this.numberOfPendingTasks = numberOfPendingTasks; this.numberOfInFlightFetch = numberOfInFlightFetch; this.delayedUnassignedShards = delayedUnassignedShards; + this.taskMaxWaitingTime = taskMaxWaitingTime; + this.clusterStateHealth = new ClusterStateHealth(clusterState, concreteIndices); + this.clusterHealthStatus = clusterStateHealth.getStatus(); + } + + /** + * For XContent Parser and serialization tests + */ + ClusterHealthResponse(String clusterName, int numberOfPendingTasks, int numberOfInFlightFetch, int delayedUnassignedShards, + TimeValue taskMaxWaitingTime, boolean timedOut, ClusterStateHealth clusterStateHealth) { this.clusterName = clusterName; this.numberOfPendingTasks = numberOfPendingTasks; this.numberOfInFlightFetch = numberOfInFlightFetch; + this.delayedUnassignedShards = delayedUnassignedShards; this.taskMaxWaitingTime = taskMaxWaitingTime; - this.clusterStateHealth = new ClusterStateHealth(clusterState, concreteIndices); + this.timedOut = timedOut; + this.clusterStateHealth = clusterStateHealth; this.clusterHealthStatus = clusterStateHealth.getStatus(); } @@ -202,7 +309,16 @@ public void writeTo(StreamOutput out) throws IOException { @Override public String toString() { - return Strings.toString(this); + return "ClusterHealthResponse{" + + "clusterName='" + clusterName + '\'' + + ", numberOfPendingTasks=" + numberOfPendingTasks + + ", numberOfInFlightFetch=" + numberOfInFlightFetch + + ", delayedUnassignedShards=" + delayedUnassignedShards + + ", taskMaxWaitingTime=" + taskMaxWaitingTime + + ", timedOut=" + timedOut + + ", clusterStateHealth=" + clusterStateHealth + + ", clusterHealthStatus=" + clusterHealthStatus + + '}'; } @Override @@ -210,25 +326,6 @@ public RestStatus status() { return isTimedOut() ? RestStatus.REQUEST_TIMEOUT : RestStatus.OK; } - private static final String CLUSTER_NAME = "cluster_name"; - private static final String STATUS = "status"; - private static final String TIMED_OUT = "timed_out"; - private static final String NUMBER_OF_NODES = "number_of_nodes"; - private static final String NUMBER_OF_DATA_NODES = "number_of_data_nodes"; - private static final String NUMBER_OF_PENDING_TASKS = "number_of_pending_tasks"; - private static final String NUMBER_OF_IN_FLIGHT_FETCH = "number_of_in_flight_fetch"; - private static final String DELAYED_UNASSIGNED_SHARDS = "delayed_unassigned_shards"; - private static final String TASK_MAX_WAIT_TIME_IN_QUEUE = "task_max_waiting_in_queue"; - private static final String TASK_MAX_WAIT_TIME_IN_QUEUE_IN_MILLIS = "task_max_waiting_in_queue_millis"; - private static final String ACTIVE_SHARDS_PERCENT_AS_NUMBER = "active_shards_percent_as_number"; - private static final String ACTIVE_SHARDS_PERCENT = "active_shards_percent"; - private static final String ACTIVE_PRIMARY_SHARDS = "active_primary_shards"; - private static final String ACTIVE_SHARDS = "active_shards"; - private static final String RELOCATING_SHARDS = "relocating_shards"; - private static final String INITIALIZING_SHARDS = "initializing_shards"; - private static final String UNASSIGNED_SHARDS = "unassigned_shards"; - private static final String INDICES = "indices"; - @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); @@ -263,4 +360,29 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.endObject(); return builder; } + + public static ClusterHealthResponse fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClusterHealthResponse that = (ClusterHealthResponse) o; + return numberOfPendingTasks == that.numberOfPendingTasks && + numberOfInFlightFetch == that.numberOfInFlightFetch && + delayedUnassignedShards == that.delayedUnassignedShards && + timedOut == that.timedOut && + Objects.equals(clusterName, that.clusterName) && + Objects.equals(taskMaxWaitingTime, that.taskMaxWaitingTime) && + Objects.equals(clusterStateHealth, that.clusterStateHealth) && + clusterHealthStatus == that.clusterHealthStatus; + } + + @Override + public int hashCode() { + return Objects.hash(clusterName, numberOfPendingTasks, numberOfInFlightFetch, delayedUnassignedShards, taskMaxWaitingTime, + timedOut, clusterStateHealth, clusterHealthStatus); + } } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java index 75c564c20385e..edb590e06607b 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java @@ -22,19 +22,86 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.IndexShardRoutingTable; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ToXContentFragment; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; + +import static java.util.Collections.emptyMap; +import static org.elasticsearch.common.util.CollectionUtils.isEmpty; +import static org.elasticsearch.common.util.CollectionUtils.newHashMapWithExpectedSize; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; public final class ClusterIndexHealth implements Iterable, Writeable, ToXContentFragment { + private static final String INDEX = "index"; + private static final String STATUS = "status"; + private static final String NUMBER_OF_SHARDS = "number_of_shards"; + private static final String NUMBER_OF_REPLICAS = "number_of_replicas"; + private static final String ACTIVE_PRIMARY_SHARDS = "active_primary_shards"; + private static final String ACTIVE_SHARDS = "active_shards"; + private static final String RELOCATING_SHARDS = "relocating_shards"; + private static final String INITIALIZING_SHARDS = "initializing_shards"; + private static final String UNASSIGNED_SHARDS = "unassigned_shards"; + private static final String SHARDS = "shards"; + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("cluster_index_health", true, + a -> { + int i = 0; + String index = (String) a[i++]; + int numberOfShards = (int) a[i++]; + int numberOfReplicas = (int) a[i++]; + int activeShards = (int) a[i++]; + int relocatingShards = (int) a[i++]; + int initializingShards = (int) a[i++]; + int unassignedShards = (int) a[i++]; + int activePrimaryShards = (int) a[i++]; + String statusStr = (String) a[i++]; + ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); + List shardList = (List) a[i++]; + final Map shards; + if (isEmpty(shardList)) { + shards = emptyMap(); + } else { + shards = newHashMapWithExpectedSize(shardList.size()); + for (ClusterShardHealth shardHealth : shardList) { + shards.put(shardHealth.getShardId(), shardHealth); + } + } + return new ClusterIndexHealth(index, numberOfShards, numberOfReplicas, activeShards, relocatingShards, initializingShards, + unassignedShards, activePrimaryShards, status, shards); + }); + + public static final ObjectParser.NamedObjectParser SHARD_PARSER = + (XContentParser p, Void c, String nameIgnored) -> ClusterShardHealth.fromXContent(p); + + static { + PARSER.declareString(constructorArg(), new ParseField(INDEX)); + PARSER.declareInt(constructorArg(), new ParseField(NUMBER_OF_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(NUMBER_OF_REPLICAS)); + PARSER.declareInt(constructorArg(), new ParseField(ACTIVE_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(RELOCATING_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(INITIALIZING_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(UNASSIGNED_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(ACTIVE_PRIMARY_SHARDS)); + PARSER.declareString(constructorArg(), new ParseField(STATUS)); + // Can be absent if LEVEL == 'indices' or 'cluster' + PARSER.declareNamedObjects(optionalConstructorArg(), SHARD_PARSER, new ParseField(SHARDS)); + } private final String index; private final int numberOfShards; @@ -45,13 +112,14 @@ public final class ClusterIndexHealth implements Iterable, W private final int unassignedShards; private final int activePrimaryShards; private final ClusterHealthStatus status; - private final Map shards = new HashMap<>(); + private final Map shards; public ClusterIndexHealth(final IndexMetaData indexMetaData, final IndexRoutingTable indexRoutingTable) { this.index = indexMetaData.getIndex().getName(); this.numberOfShards = indexMetaData.getNumberOfShards(); this.numberOfReplicas = indexMetaData.getNumberOfReplicas(); + shards = new HashMap<>(); for (IndexShardRoutingTable shardRoutingTable : indexRoutingTable) { int shardId = shardRoutingTable.shardId().id(); shards.put(shardId, new ClusterShardHealth(shardId, shardRoutingTable)); @@ -104,12 +172,31 @@ public ClusterIndexHealth(final StreamInput in) throws IOException { status = ClusterHealthStatus.fromValue(in.readByte()); int size = in.readVInt(); + shards = newHashMapWithExpectedSize(size); for (int i = 0; i < size; i++) { ClusterShardHealth shardHealth = new ClusterShardHealth(in); - shards.put(shardHealth.getId(), shardHealth); + shards.put(shardHealth.getShardId(), shardHealth); } } + /** + * For XContent Parser and serialization tests + */ + ClusterIndexHealth(String index, int numberOfShards, int numberOfReplicas, int activeShards, int relocatingShards, + int initializingShards, int unassignedShards, int activePrimaryShards, ClusterHealthStatus status, + Map shards) { + this.index = index; + this.numberOfShards = numberOfShards; + this.numberOfReplicas = numberOfReplicas; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.activePrimaryShards = activePrimaryShards; + this.status = status; + this.shards = shards; + } + public String getIndex() { return index; } @@ -173,19 +260,9 @@ public void writeTo(final StreamOutput out) throws IOException { } } - private static final String STATUS = "status"; - private static final String NUMBER_OF_SHARDS = "number_of_shards"; - private static final String NUMBER_OF_REPLICAS = "number_of_replicas"; - private static final String ACTIVE_PRIMARY_SHARDS = "active_primary_shards"; - private static final String ACTIVE_SHARDS = "active_shards"; - private static final String RELOCATING_SHARDS = "relocating_shards"; - private static final String INITIALIZING_SHARDS = "initializing_shards"; - private static final String UNASSIGNED_SHARDS = "unassigned_shards"; - private static final String SHARDS = "shards"; - private static final String PRIMARY_ACTIVE = "primary_active"; - @Override public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + builder.field(INDEX, getIndex()); builder.field(STATUS, getStatus().name().toLowerCase(Locale.ROOT)); builder.field(NUMBER_OF_SHARDS, getNumberOfShards()); builder.field(NUMBER_OF_REPLICAS, getNumberOfReplicas()); @@ -197,22 +274,56 @@ public XContentBuilder toXContent(final XContentBuilder builder, final Params pa if ("shards".equals(params.param("level", "indices"))) { builder.startObject(SHARDS); - for (ClusterShardHealth shardHealth : shards.values()) { - builder.startObject(Integer.toString(shardHealth.getId())); - - builder.field(STATUS, shardHealth.getStatus().name().toLowerCase(Locale.ROOT)); - builder.field(PRIMARY_ACTIVE, shardHealth.isPrimaryActive()); - builder.field(ACTIVE_SHARDS, shardHealth.getActiveShards()); - builder.field(RELOCATING_SHARDS, shardHealth.getRelocatingShards()); - builder.field(INITIALIZING_SHARDS, shardHealth.getInitializingShards()); - builder.field(UNASSIGNED_SHARDS, shardHealth.getUnassignedShards()); - + builder.startObject(Integer.toString(shardHealth.getShardId())); + shardHealth.toXContent(builder, params); builder.endObject(); } - builder.endObject(); } return builder; } + + public static ClusterIndexHealth fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } + + @Override + public String toString() { + return "ClusterIndexHealth{" + + "index='" + index + '\'' + + ", numberOfShards=" + numberOfShards + + ", numberOfReplicas=" + numberOfReplicas + + ", activeShards=" + activeShards + + ", relocatingShards=" + relocatingShards + + ", initializingShards=" + initializingShards + + ", unassignedShards=" + unassignedShards + + ", activePrimaryShards=" + activePrimaryShards + + ", status=" + status + + ", shards.size=" + (shards == null ? "null" : shards.size()) + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClusterIndexHealth that = (ClusterIndexHealth) o; + return numberOfShards == that.numberOfShards && + numberOfReplicas == that.numberOfReplicas && + activeShards == that.activeShards && + relocatingShards == that.relocatingShards && + initializingShards == that.initializingShards && + unassignedShards == that.unassignedShards && + activePrimaryShards == that.activePrimaryShards && + Objects.equals(index, that.index) && + status == that.status && + Objects.equals(shards, that.shards); + } + + @Override + public int hashCode() { + return Objects.hash(index, numberOfShards, numberOfReplicas, activeShards, relocatingShards, initializingShards, unassignedShards, + activePrimaryShards, status, shards); + } } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java index 12131b11f3f66..79ae1de2ab0d4 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java @@ -24,13 +24,56 @@ import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentFragment; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; - -public final class ClusterShardHealth implements Writeable { +import java.util.Locale; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; + +public final class ClusterShardHealth implements Writeable, ToXContentFragment { + private static final String SHARD_ID = "shard_id"; + private static final String STATUS = "status"; + private static final String ACTIVE_SHARDS = "active_shards"; + private static final String RELOCATING_SHARDS = "relocating_shards"; + private static final String INITIALIZING_SHARDS = "initializing_shards"; + private static final String UNASSIGNED_SHARDS = "unassigned_shards"; + private static final String PRIMARY_ACTIVE = "primary_active"; + + public static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("cluster_shard_health", true, + a -> { + int i = 0; + int shardId = (int) a[i++]; + boolean primaryActive = (boolean) a[i++]; + int activeShards = (int) a[i++]; + int relocatingShards = (int) a[i++]; + int initializingShards = (int) a[i++]; + int unassignedShards = (int) a[i++]; + String statusStr = (String) a[i++]; + ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); + return new ClusterShardHealth(shardId, status, activeShards, relocatingShards, initializingShards, unassignedShards, + primaryActive); + }); + + static { + PARSER.declareInt(constructorArg(), new ParseField(SHARD_ID)); + PARSER.declareBoolean(constructorArg(), new ParseField(PRIMARY_ACTIVE)); + PARSER.declareInt(constructorArg(), new ParseField(ACTIVE_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(RELOCATING_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(INITIALIZING_SHARDS)); + PARSER.declareInt(constructorArg(), new ParseField(UNASSIGNED_SHARDS)); + PARSER.declareString(constructorArg(), new ParseField(STATUS)); + } private final int shardId; private final ClusterHealthStatus status; @@ -88,7 +131,21 @@ public ClusterShardHealth(final StreamInput in) throws IOException { primaryActive = in.readBoolean(); } - public int getId() { + /** + * For XContent Parser and serialization tests + */ + ClusterShardHealth(int shardId, ClusterHealthStatus status, int activeShards, int relocatingShards, int initializingShards, + int unassignedShards, boolean primaryActive) { + this.shardId = shardId; + this.status = status; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.primaryActive = primaryActive; + } + + public int getShardId() { return shardId; } @@ -155,4 +212,43 @@ public static ClusterHealthStatus getInactivePrimaryHealth(final ShardRouting sh } } + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(SHARD_ID, getShardId()); + builder.field(STATUS, getStatus().name().toLowerCase(Locale.ROOT)); + builder.field(PRIMARY_ACTIVE, isPrimaryActive()); + builder.field(ACTIVE_SHARDS, getActiveShards()); + builder.field(RELOCATING_SHARDS, getRelocatingShards()); + builder.field(INITIALIZING_SHARDS, getInitializingShards()); + builder.field(UNASSIGNED_SHARDS, getUnassignedShards()); + return builder; + } + + public static ClusterShardHealth fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } + + @Override + public String toString() { + return Strings.toString(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ClusterShardHealth)) return false; + ClusterShardHealth that = (ClusterShardHealth) o; + return shardId == that.shardId && + activeShards == that.activeShards && + relocatingShards == that.relocatingShards && + initializingShards == that.initializingShards && + unassignedShards == that.unassignedShards && + primaryActive == that.primaryActive && + status == that.status; + } + + @Override + public int hashCode() { + return Objects.hash(shardId, status, activeShards, relocatingShards, initializingShards, unassignedShards, primaryActive); + } } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java index 8aeb110c37007..41ad8cb82fa48 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java @@ -33,6 +33,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.common.util.CollectionUtils.newHashMapWithExpectedSize; public final class ClusterStateHealth implements Iterable, Writeable { @@ -45,7 +48,7 @@ public final class ClusterStateHealth implements Iterable, W private final int unassignedShards; private final double activeShardsPercent; private final ClusterHealthStatus status; - private final Map indices = new HashMap<>(); + private final Map indices; /** * Creates a new ClusterStateHealth instance considering the current cluster state and all indices in the cluster. @@ -65,7 +68,7 @@ public ClusterStateHealth(final ClusterState clusterState) { public ClusterStateHealth(final ClusterState clusterState, final String[] concreteIndices) { numberOfNodes = clusterState.nodes().getSize(); numberOfDataNodes = clusterState.nodes().getDataNodes().size(); - + indices = new HashMap<>(); for (String index : concreteIndices) { IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(index); IndexMetaData indexMetaData = clusterState.metaData().index(index); @@ -134,6 +137,7 @@ public ClusterStateHealth(final StreamInput in) throws IOException { numberOfDataNodes = in.readVInt(); status = ClusterHealthStatus.fromValue(in.readByte()); int size = in.readVInt(); + indices = newHashMapWithExpectedSize(size); for (int i = 0; i < size; i++) { ClusterIndexHealth indexHealth = new ClusterIndexHealth(in); indices.put(indexHealth.getIndex(), indexHealth); @@ -141,6 +145,24 @@ public ClusterStateHealth(final StreamInput in) throws IOException { activeShardsPercent = in.readDouble(); } + /** + * For ClusterHealthResponse's XContent Parser + */ + public ClusterStateHealth(int activePrimaryShards, int activeShards, int relocatingShards, int initializingShards, int unassignedShards, + int numberOfNodes, int numberOfDataNodes, double activeShardsPercent, ClusterHealthStatus status, + Map indices) { + this.activePrimaryShards = activePrimaryShards; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.numberOfNodes = numberOfNodes; + this.numberOfDataNodes = numberOfDataNodes; + this.activeShardsPercent = activeShardsPercent; + this.status = status; + this.indices = indices; + } + public int getActiveShards() { return activeShards; } @@ -202,4 +224,43 @@ public void writeTo(final StreamOutput out) throws IOException { } out.writeDouble(activeShardsPercent); } + + @Override + public String toString() { + return "ClusterStateHealth{" + + "numberOfNodes=" + numberOfNodes + + ", numberOfDataNodes=" + numberOfDataNodes + + ", activeShards=" + activeShards + + ", relocatingShards=" + relocatingShards + + ", activePrimaryShards=" + activePrimaryShards + + ", initializingShards=" + initializingShards + + ", unassignedShards=" + unassignedShards + + ", activeShardsPercent=" + activeShardsPercent + + ", status=" + status + + ", indices.size=" + (indices == null ? "null" : indices.size()) + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClusterStateHealth that = (ClusterStateHealth) o; + return numberOfNodes == that.numberOfNodes && + numberOfDataNodes == that.numberOfDataNodes && + activeShards == that.activeShards && + relocatingShards == that.relocatingShards && + activePrimaryShards == that.activePrimaryShards && + initializingShards == that.initializingShards && + unassignedShards == that.unassignedShards && + Double.compare(that.activeShardsPercent, activeShardsPercent) == 0 && + status == that.status && + Objects.equals(indices, that.indices); + } + + @Override + public int hashCode() { + return Objects.hash(numberOfNodes, numberOfDataNodes, activeShards, relocatingShards, activePrimaryShards, initializingShards, + unassignedShards, activeShardsPercent, status, indices); + } } diff --git a/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java b/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java index 08d02cdea3172..c457ce6387c29 100644 --- a/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java +++ b/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java @@ -19,6 +19,13 @@ package org.elasticsearch.common.util; +import com.carrotsearch.hppc.ObjectArrayList; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.BytesRefArray; +import org.apache.lucene.util.BytesRefBuilder; +import org.apache.lucene.util.InPlaceMergeSorter; +import org.apache.lucene.util.IntroSorter; + import java.nio.file.Path; import java.util.AbstractList; import java.util.ArrayList; @@ -26,6 +33,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; @@ -34,15 +42,12 @@ import java.util.RandomAccess; import java.util.Set; -import com.carrotsearch.hppc.ObjectArrayList; -import org.apache.lucene.util.BytesRef; -import org.apache.lucene.util.BytesRefArray; -import org.apache.lucene.util.BytesRefBuilder; -import org.apache.lucene.util.InPlaceMergeSorter; -import org.apache.lucene.util.IntroSorter; - /** Collections-related utility methods. */ public class CollectionUtils { + /** + * The largest power of two that can be represented as an {@code int}. + */ + public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); public static void sort(final long[] array, int len) { new IntroSorter() { @@ -145,6 +150,18 @@ public static boolean isEmpty(Object[] array) { return array == null || array.length == 0; } + /** + * Null-safe check if the specified collection is empty. + *

+ * Null returns true. + * + * @param coll the collection to check, may be null + * @return true if empty or null + */ + public static boolean isEmpty(final Collection coll) { + return coll == null || coll.isEmpty(); + } + /** * Return a rotated view of the given list with the given distance. */ @@ -427,4 +444,41 @@ public static List> eagerPartition(List list, int size) { return result; } + + /** + * Creates a {@code HashMap} instance, with a high enough "initial capacity" that it should hold + * {@code expectedSize} elements without growth. This behavior cannot be broadly guaranteed, but it is observed + * to be true for OpenJDK 1.7. + * It also can't be guaranteed that the method isn't inadvertently oversizing the returned map. + * + *

From Guava 24.1-jre. com.google.common.collect.Maps

+ * + * @param expectedSize the number of entries you expect to add to the returned map + * @return a new, empty {@code HashMap} with enough capacity to hold {@code expectedSize} entries without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashMap newHashMapWithExpectedSize(int expectedSize) { + return new HashMap<>(capacity(expectedSize)); + } + + /** + * Returns a capacity that is sufficient to keep the map from being resized as long as it grows no larger + * than expectedSize and the load factor is ≥ its default (0.75). + * + *

From Guava 24.1-jre. com.google.common.collect.Maps

+ */ + static int capacity(int expectedSize) { + if (expectedSize < 3) { + if (expectedSize < 0) { + throw new IllegalArgumentException("expectedSize cannot be negative but was: " + expectedSize); + } + return expectedSize + 1; + } + if (expectedSize < MAX_POWER_OF_TWO) { + // This is the calculation used in JDK8,9,10 to resize when a putAll happens. + // It seems to be the most conservative calculation we can make. 0.75 is the default load factor. + return (int) ((float) expectedSize / 0.75F + 1.0F); + } + return Integer.MAX_VALUE; // any large value + } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java index d0d452df478a9..f4ae70401f169 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java @@ -21,38 +21,48 @@ import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.health.ClusterIndexHealth; +import org.elasticsearch.cluster.health.ClusterIndexHealthTests; import org.elasticsearch.cluster.health.ClusterStateHealth; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.util.CollectionUtils; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.AbstractStreamableXContentTestCase; import org.hamcrest.Matchers; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Pattern; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThanOrEqualTo; -public class ClusterHealthResponsesTests extends ESTestCase { +public class ClusterHealthResponsesTests extends AbstractStreamableXContentTestCase { - public void testIsTimeout() throws IOException { - ClusterHealthResponse res = new ClusterHealthResponse(); - for (int i = 0; i < 5; i++) { - res.setTimedOut(randomBoolean()); - if (res.isTimedOut()) { - assertEquals(RestStatus.REQUEST_TIMEOUT, res.status()); - } else { - assertEquals(RestStatus.OK, res.status()); - } - } + public void testIsTimeout() { + ClusterHealthResponse responseTimeout = new ClusterHealthResponse(); + responseTimeout.setTimedOut(true); + assertEquals(RestStatus.REQUEST_TIMEOUT, responseTimeout.status()); + } + + public void testIsNotTimeout() { + ClusterHealthResponse responseOk = new ClusterHealthResponse(); + responseOk.setTimedOut(false); + assertEquals(RestStatus.OK, responseOk.status()); } - public void testClusterHealth() throws IOException { + public void testClusterHealth() { ClusterState clusterState = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.getDefault(Settings.EMPTY)).build(); int pendingTasks = randomIntBetween(0, 200); int inFlight = randomIntBetween(0, 200); @@ -80,13 +90,60 @@ private void assertClusterHealth(ClusterHealthResponse clusterHealth) { assertThat(clusterHealth.getNumberOfDataNodes(), Matchers.equalTo(clusterStateHealth.getNumberOfDataNodes())); } - ClusterHealthResponse maybeSerialize(ClusterHealthResponse clusterHealth) throws IOException { + ClusterHealthResponse maybeSerialize(ClusterHealthResponse clusterHealth) { if (randomBoolean()) { - BytesStreamOutput out = new BytesStreamOutput(); - clusterHealth.writeTo(out); - StreamInput in = out.bytes().streamInput(); - clusterHealth = ClusterHealthResponse.readResponseFrom(in); + try { + BytesStreamOutput out = new BytesStreamOutput(); + clusterHealth.writeTo(out); + StreamInput in = out.bytes().streamInput(); + clusterHealth = ClusterHealthResponse.readResponseFrom(in); + } catch (IOException e) { + throw new IllegalStateException(e); + } } return clusterHealth; } + + @Override + protected ClusterHealthResponse doParseInstance(XContentParser parser) { + return ClusterHealthResponse.fromXContent(parser); + } + + @Override + protected ClusterHealthResponse createBlankInstance() { + return new ClusterHealthResponse(); + } + + @Override + protected ClusterHealthResponse createTestInstance() { + int indicesSize = randomInt(20); + Map indices = CollectionUtils.newHashMapWithExpectedSize(indicesSize); + for (int i = 0; i < indicesSize; i++) { + String indexName = randomAlphaOfLengthBetween(1, 5) + i; + indices.put(indexName, ClusterIndexHealthTests.randomIndexHealthWithShards(indexName)); + } + + ClusterStateHealth stateHealth = new ClusterStateHealth(randomInt(100), randomInt(100), randomInt(100), + randomInt(100), randomInt(100), randomInt(100), randomInt(100), + randomDoubleBetween(0d, 100d, true), randomFrom(ClusterHealthStatus.values()), indices); + + return new ClusterHealthResponse(randomAlphaOfLengthBetween(1, 10), randomInt(100), randomInt(100), randomInt(100), + TimeValue.timeValueMillis(randomInt(10000)), randomBoolean(), stateHealth); + } + + protected ToXContent.Params getToXContentParams() { + Map map = new HashMap<>(); + map.put("level", "shards"); + return new ToXContent.MapParams(map); + } + + protected boolean supportsUnknownFields() { + return true; + } + + private static final Pattern SHARDS_IN_XCONTENT = Pattern.compile("^indices\\." + "\\w+" + "\\.shards$"); + + protected Predicate getRandomFieldsExcludeFilter() { + return field -> "indices".equals(field) || SHARDS_IN_XCONTENT.matcher(field).find(); + } } diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java index 215f28f727587..4609a15a7bea0 100644 --- a/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java @@ -22,11 +22,18 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.RoutingTableGenerator; -import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractSerializingTestCase; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; import static org.hamcrest.CoreMatchers.equalTo; -public class ClusterIndexHealthTests extends ESTestCase { +public class ClusterIndexHealthTests extends AbstractSerializingTestCase { public void testClusterIndexHealth() { RoutingTableGenerator routingTableGenerator = new RoutingTableGenerator(); int numberOfShards = randomInt(3) + 1; @@ -36,11 +43,9 @@ public void testClusterIndexHealth() { IndexRoutingTable indexRoutingTable = routingTableGenerator.genIndexRoutingTable(indexMetaData, counter); ClusterIndexHealth indexHealth = new ClusterIndexHealth(indexMetaData, indexRoutingTable); - logger.info("index status: {}, expected {}", indexHealth.getStatus(), counter.status()); assertIndexHealth(indexHealth, counter, indexMetaData); } - private void assertIndexHealth(ClusterIndexHealth indexHealth, RoutingTableGenerator.ShardCounter counter, IndexMetaData indexMetaData) { assertThat(indexHealth.getStatus(), equalTo(counter.status())); assertThat(indexHealth.getNumberOfShards(), equalTo(indexMetaData.getNumberOfShards())); @@ -57,4 +62,43 @@ private void assertIndexHealth(ClusterIndexHealth indexHealth, RoutingTableGener assertThat(totalShards, equalTo(indexMetaData.getNumberOfShards() * (1 + indexMetaData.getNumberOfReplicas()))); } + + @Override + protected ClusterIndexHealth createTestInstance() { + return randomIndexHealthWithShards(randomAlphaOfLengthBetween(1, 10)); + } + + public static ClusterIndexHealth randomIndexHealthWithShards(String indexName) { + Map shards = new HashMap<>(); + for (int i = 0; i < randomIntBetween(1, 10); i++) { + shards.put(i, ClusterShardHealthTests.randomShardHealth(i)); + } + + return new ClusterIndexHealth(indexName, randomInt(1000), randomInt(1000), randomInt(1000), randomInt(1000), + randomInt(1000), randomInt(1000), randomInt(1000), randomFrom(ClusterHealthStatus.values()), shards); + } + + @Override + protected Writeable.Reader instanceReader() { + return ClusterIndexHealth::new; + } + + @Override + protected ClusterIndexHealth doParseInstance(XContentParser parser) { + return ClusterIndexHealth.fromXContent(parser); + } + + protected ToXContent.Params getToXContentParams() { + Map map = new HashMap<>(); + map.put("level", "shards"); + return new ToXContent.MapParams(map); + } + + protected boolean supportsUnknownFields() { + return true; + } + + protected Predicate getRandomFieldsExcludeFilter() { + return "shards"::equals; + } } diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java new file mode 100644 index 0000000000000..9dd19bc0aca13 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java @@ -0,0 +1,46 @@ +/* + * 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.cluster.health; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractSerializingTestCase; + +public class ClusterShardHealthTests extends AbstractSerializingTestCase { + + @Override + protected ClusterShardHealth doParseInstance(XContentParser parser) { + return ClusterShardHealth.fromXContent(parser); + } + + @Override + protected ClusterShardHealth createTestInstance() { + return randomShardHealth(randomInt(1000)); + } + + static ClusterShardHealth randomShardHealth(int id) { + return new ClusterShardHealth(id, randomFrom(ClusterHealthStatus.values()), randomInt(1000), randomInt(1000), + randomInt(1000), randomInt(1000), randomBoolean()); + } + + @Override + protected Writeable.Reader instanceReader() { + return ClusterShardHealth::new; + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java index a725c967973d4..8a129770f695c 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java @@ -35,7 +35,7 @@ public abstract class AbstractSerializingTestCase getRandomFieldsExcludeFilter() { protected String[] getShuffleFieldsExceptions() { return Strings.EMPTY_ARRAY; } + + protected ToXContent.Params getToXContentParams() { + return ToXContent.EMPTY_PARAMS; + } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java index 0c165f92e3998..8f45a0bdbb436 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java @@ -35,7 +35,7 @@ public abstract class AbstractStreamableXContentTestCase getRandomFieldsExcludeFilter() { protected String[] getShuffleFieldsExceptions() { return Strings.EMPTY_ARRAY; } + + protected ToXContent.Params getToXContentParams() { + return ToXContent.EMPTY_PARAMS; + } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java index 983897049c767..719acf4066f4c 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java @@ -42,18 +42,15 @@ public abstract class AbstractXContentTestCase extends EST protected static final int NUMBER_OF_TEST_RUNS = 20; public static void testFromXContent(int numberOfTestRuns, Supplier instanceSupplier, - boolean supportsUnknownFields, String[] shuffleFieldsExceptions, - Predicate randomFieldsExcludeFilter, - CheckedBiFunction - createParserFunction, - CheckedFunction parseFunction, - BiConsumer assertEqualsConsumer, - boolean assertToXContentEquivalence) throws IOException { + boolean supportsUnknownFields, String[] shuffleFieldsExceptions, Predicate randomFieldsExcludeFilter, + CheckedBiFunction createParserFunction, + CheckedFunction parseFunction, BiConsumer assertEqualsConsumer, + boolean assertToXContentEquivalence, ToXContent.Params toXContentParams) throws IOException { for (int runs = 0; runs < numberOfTestRuns; runs++) { T testInstance = instanceSupplier.get(); XContentType xContentType = randomFrom(XContentType.values()); - BytesReference shuffled = toShuffledXContent(testInstance, xContentType, ToXContent.EMPTY_PARAMS, false, createParserFunction, - shuffleFieldsExceptions); + BytesReference shuffled = toShuffledXContent(testInstance, xContentType, toXContentParams,false, + createParserFunction, shuffleFieldsExceptions); BytesReference withRandomFields; if (supportsUnknownFields) { // we add a few random fields to check that parser is lenient on new fields @@ -65,7 +62,8 @@ public static void testFromXContent(int numberOfTestRuns, T parsed = parseFunction.apply(parser); assertEqualsConsumer.accept(testInstance, parsed); if (assertToXContentEquivalence) { - assertToXContentEquivalent(shuffled, XContentHelper.toXContent(parsed, xContentType, false), xContentType); + assertToXContentEquivalent(shuffled, XContentHelper.toXContent(parsed, xContentType, toXContentParams,false), + xContentType); } } } @@ -77,7 +75,7 @@ public static void testFromXContent(int numberOfTestRuns, public final void testFromXContent() throws IOException { testFromXContent(NUMBER_OF_TEST_RUNS, this::createTestInstance, supportsUnknownFields(), getShuffleFieldsExceptions(), getRandomFieldsExcludeFilter(), this::createParser, this::parseInstance, this::assertEqualInstances, - assertToXContentEquivalence()); + assertToXContentEquivalence(), getToXContentParams()); } /** @@ -127,4 +125,8 @@ protected Predicate getRandomFieldsExcludeFilter() { protected String[] getShuffleFieldsExceptions() { return Strings.EMPTY_ARRAY; } + + protected ToXContent.Params getToXContentParams() { + return ToXContent.EMPTY_PARAMS; + } } From 18dfa6ee0570358fbb4c4296d70b07a5fa5cf8b5 Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Thu, 5 Apr 2018 17:47:59 -0400 Subject: [PATCH 03/14] Round 2 - Fix remarks - Add level parameter - Add documentation - Improve tests --- NOTICE.txt | 2 +- .../elasticsearch/client/ClusterClient.java | 9 +- .../org/elasticsearch/client/Request.java | 21 +- .../elasticsearch/client/ClusterClientIT.java | 111 ++++++++-- .../elasticsearch/client/RequestTests.java | 79 ++++++-- .../ClusterClientDocumentationIT.java | 165 ++++++++++++++- .../high-level/cluster/health.asciidoc | 189 ++++++++++++++++++ .../high-level/supported-apis.asciidoc | 2 + docs/reference/cluster/health.asciidoc | 5 + .../cluster/health/ClusterHealthRequest.java | 14 ++ .../cluster/health/ClusterHealthResponse.java | 13 +- .../cluster/health/ClusterIndexHealth.java | 12 +- .../cluster/health/ClusterStateHealth.java | 3 +- .../common/util/CollectionUtils.java | 54 ----- .../health/ClusterHealthResponsesTests.java | 3 +- 15 files changed, 573 insertions(+), 109 deletions(-) create mode 100644 docs/java-rest/high-level/cluster/health.asciidoc diff --git a/NOTICE.txt b/NOTICE.txt index f1e3198ab4a9a..643a060cd05c4 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Elasticsearch -Copyright 2009-2018 Elasticsearch +Copyright 2009-2017 Elasticsearch This product includes software developed by The Apache Software Foundation (http://www.apache.org/). diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java index 12d93b5e8df01..e805798374c8e 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ClusterClient.java @@ -25,10 +25,12 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; +import org.elasticsearch.rest.RestStatus; import java.io.IOException; import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; /** * A wrapper for the {@link RestHighLevelClient} that provides methods for accessing the Cluster API. @@ -71,10 +73,12 @@ public void putSettingsAsync(ClusterUpdateSettingsRequest clusterUpdateSettingsR *

* See * Cluster Health API on elastic.co + *

+ * If timeout occurred, {@link ClusterHealthResponse} will have isTimedOut() == true and status() == RestStatus.REQUEST_TIMEOUT */ public ClusterHealthResponse health(ClusterHealthRequest healthRequest, Header... headers) throws IOException { return restHighLevelClient.performRequestAndParseEntity(healthRequest, Request::clusterHealth, ClusterHealthResponse::fromXContent, - emptySet(), headers); + singleton(RestStatus.REQUEST_TIMEOUT.getStatus()), headers); } /** @@ -82,9 +86,10 @@ public ClusterHealthResponse health(ClusterHealthRequest healthRequest, Header.. *

* See * Cluster Health API on elastic.co + * If timeout occurred, {@link ClusterHealthResponse} will have isTimedOut() == true and status() == RestStatus.REQUEST_TIMEOUT */ public void healthAsync(ClusterHealthRequest healthRequest, ActionListener listener, Header... headers) { restHighLevelClient.performRequestAsyncAndParseEntity(healthRequest, Request::clusterHealth, ClusterHealthResponse::fromXContent, - listener, emptySet(), headers); + listener, singleton(RestStatus.REQUEST_TIMEOUT.getStatus()), headers); } } 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 3506baa2702df..65504f4e76753 100644 --- 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 @@ -61,6 +61,7 @@ import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Priority; import org.elasticsearch.common.Strings; import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.bytes.BytesReference; @@ -592,12 +593,13 @@ static Request clusterHealth(ClusterHealthRequest healthRequest) { params.withWaitForNoInitializingShards(healthRequest.waitForNoInitializingShards()); params.withWaitForActiveShards(healthRequest.waitForActiveShards()); params.withWaitForNodes(healthRequest.waitForNodes()); + params.withWaitForEvents(healthRequest.waitForEvents()); params.withTimeout(healthRequest.timeout()); params.withMasterTimeout(healthRequest.masterNodeTimeout()); params.withLocal(healthRequest.local()); - params.putParam("level", "shards"); + params.withLevel(healthRequest.level()); String[] indices = healthRequest.indices() == null ? Strings.EMPTY_ARRAY : healthRequest.indices(); - String endpoint = endpoint(indices, "_cluster/health"); + String endpoint = endpoint("_cluster/health", indices); return new Request(HttpGet.METHOD_NAME, endpoint, params.getParams(), null); } @@ -661,6 +663,10 @@ static String endpoint(String[] indices, String endpoint, String[] suffixes) { .addCommaSeparatedPathParts(suffixes).build(); } + static String endpoint(String endpoint, String[] suffixes) { + return new EndpointBuilder().addPathPartAsIs(endpoint).addCommaSeparatedPathParts(suffixes).build(); + } + static String endpoint(String[] indices, String endpoint, String type) { return new EndpointBuilder().addCommaSeparatedPathParts(indices).addPathPartAsIs(endpoint).addPathPart(type).build(); } @@ -876,6 +882,17 @@ Params withWaitForNodes(String waitForNodes) { return putParam("wait_for_nodes", waitForNodes); } + Params withLevel(String level) { + return putParam("level", level); + } + + Params withWaitForEvents(Priority waitForEvents) { + if (waitForEvents != null) { + return putParam("wait_for_events", waitForEvents.name().toLowerCase(Locale.ROOT)); + } + return this; + } + Map getParams() { return Collections.unmodifiableMap(params); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java index b74b85540f730..8d57ca042a3d4 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java @@ -25,6 +25,8 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.health.ClusterIndexHealth; +import org.elasticsearch.cluster.health.ClusterShardHealth; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; @@ -40,7 +42,6 @@ import static java.util.Collections.emptyMap; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @@ -111,20 +112,106 @@ public void testClusterUpdateSettingNonExistent() { "Elasticsearch exception [type=illegal_argument_exception, reason=transient setting [" + setting + "], not recognized]")); } - public void testClusterHealth() throws IOException { + public void testClusterHealthGreen() throws IOException { ClusterHealthRequest request = new ClusterHealthRequest(); + request.timeout("5s"); ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); assertThat(response, notNullValue()); - assertThat(response.isTimedOut(), is(false)); - assertThat(response.status(), is(RestStatus.OK)); - assertThat(response.getStatus(), is(ClusterHealthStatus.GREEN)); - assertThat(response.getIndices(), is(emptyMap())); - assertThat(response.getActivePrimaryShards(), is(0)); - assertThat(response.getActiveShards(), is(0)); - assertThat(response.getDelayedUnassignedShards(), is(0)); - assertThat(response.getInitializingShards(), is(0)); - assertThat(response.getUnassignedShards(), is(0)); - assertThat(response.getActiveShardsPercent(), is(100d)); + assertThat(response.isTimedOut(), equalTo(false)); + assertThat(response.status(), equalTo(RestStatus.OK)); + assertThat(response.getStatus(), equalTo(ClusterHealthStatus.GREEN)); + emptyClusterAssertion(response); + } + + public void testClusterHealthYellow() throws IOException { + createIndex("index", Settings.EMPTY); + createIndex("index2", Settings.EMPTY); + ClusterHealthRequest request = new ClusterHealthRequest(); + request.timeout("5s"); + boolean requestOneIndex = randomBoolean(); + if (requestOneIndex) { + request.indices("index"); + } + String level = randomFrom("default-shards", "cluster", "indices", "shards"); + if (!"default-shards".equals(level)) { + request.level(level); + } + ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); + + assertThat(response, notNullValue()); + assertThat(response.isTimedOut(), equalTo(false)); + assertThat(response.status(), equalTo(RestStatus.OK)); + assertThat(response.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); + assertThat(response.getActivePrimaryShards(), equalTo(requestOneIndex? 5 : 10)); + assertThat(response.getNumberOfDataNodes(), equalTo(1)); + assertThat(response.getNumberOfNodes(), equalTo(1)); + assertThat(response.getActiveShards(), equalTo(requestOneIndex? 5 : 10)); + assertThat(response.getDelayedUnassignedShards(), equalTo(0)); + assertThat(response.getInitializingShards(), equalTo(0)); + assertThat(response.getUnassignedShards(), equalTo(requestOneIndex? 5 : 10)); + assertThat(response.getActiveShardsPercent(), equalTo(50d)); + if ("shards".equals(level) || "indices".equals(level) || "default-shards".equals(level)) { + assertThat(response.getIndices().size(), equalTo(requestOneIndex ? 1 : 2)); + for (Map.Entry entry : response.getIndices().entrySet()) { + indexAssertion(entry.getKey(), entry.getValue(), level); + } + } else { + assertThat(response.getIndices().size(), equalTo(0)); + } + } + + private void indexAssertion(String indexName, ClusterIndexHealth indexHealth, String level) { + assertThat(indexHealth, notNullValue()); + assertThat(indexHealth.getIndex(),equalTo(indexName)); + assertThat(indexHealth.getActivePrimaryShards(),equalTo(5)); + assertThat(indexHealth.getActiveShards(),equalTo(5)); + assertThat(indexHealth.getNumberOfReplicas(),equalTo(1)); + assertThat(indexHealth.getInitializingShards(),equalTo(0)); + assertThat(indexHealth.getUnassignedShards(),equalTo(5)); + assertThat(indexHealth.getRelocatingShards(),equalTo(0)); + assertThat(indexHealth.getStatus(),equalTo(ClusterHealthStatus.YELLOW)); + if ("shards".equals(level) || "default-shards".equals(level)) { + assertThat(indexHealth.getShards().size(), equalTo(5)); + for (Map.Entry entry : indexHealth.getShards().entrySet()) { + shardAssertion(entry.getKey(), entry.getValue()); + } + } else { + assertThat(indexHealth.getShards().size(), equalTo(0)); + } + } + + private void shardAssertion(int shardId, ClusterShardHealth shardHealth) { + assertThat(shardHealth, notNullValue()); + assertThat(shardHealth.getShardId(), equalTo(shardId)); + assertThat(shardHealth.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); + assertThat(shardHealth.getActiveShards(), equalTo(1)); + assertThat(shardHealth.getInitializingShards(), equalTo(0)); + assertThat(shardHealth.getUnassignedShards(), equalTo(1)); + assertThat(shardHealth.getRelocatingShards(), equalTo(0)); + } + + public void testClusterHealthNotFoundIndex() throws IOException { + ClusterHealthRequest request = new ClusterHealthRequest("notexisted-index"); + request.timeout("5s"); + ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); + + assertThat(response, notNullValue()); + assertThat(response.isTimedOut(), equalTo(true)); + assertThat(response.status(), equalTo(RestStatus.REQUEST_TIMEOUT)); + assertThat(response.getStatus(), equalTo(ClusterHealthStatus.RED)); + emptyClusterAssertion(response); + } + + public static void emptyClusterAssertion(ClusterHealthResponse response) { + assertThat(response.getIndices(), equalTo(emptyMap())); + assertThat(response.getActivePrimaryShards(), equalTo(0)); + assertThat(response.getNumberOfDataNodes(), equalTo(1)); + assertThat(response.getNumberOfNodes(), equalTo(1)); + assertThat(response.getActiveShards(), equalTo(0)); + assertThat(response.getDelayedUnassignedShards(), equalTo(0)); + assertThat(response.getInitializingShards(), equalTo(0)); + assertThat(response.getUnassignedShards(), equalTo(0)); + assertThat(response.getActiveShardsPercent(), equalTo(100d)); } } 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 29377fac4256c..6be52e37fa825 100644 --- 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 @@ -68,8 +68,10 @@ import org.elasticsearch.action.support.master.MasterNodeRequest; import org.elasticsearch.action.support.replication.ReplicationRequest; import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.CheckedFunction; +import org.elasticsearch.common.Priority; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; @@ -128,7 +130,6 @@ import static org.elasticsearch.search.RandomSearchRequestGenerator.randomSearchRequest; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @@ -1332,19 +1333,72 @@ public void testClusterHealth() { ClusterHealthRequest healthRequest = new ClusterHealthRequest(); Map expectedParams = new HashMap<>(); setRandomLocal(healthRequest, expectedParams); - setRandomTimeout(healthRequest::timeout, AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, expectedParams); - setRandomMasterTimeout(healthRequest, expectedParams); - expectedParams.put("level", "shards"); - // Default value in ClusterHealthRequest is NONE but in Request.Params::withWaitForActiveShards is DEFAULT - expectedParams.put("wait_for_active_shards", "0"); - //TODO add random filling for other properties + if (randomBoolean()) { + String timeout = randomTimeValue(); + healthRequest.timeout(timeout); + expectedParams.put("timeout", timeout); + if (randomBoolean()) { + String masterTimeout = randomTimeValue(); + healthRequest.masterNodeTimeout(masterTimeout); + expectedParams.put("master_timeout", masterTimeout); + } else { + // If Master Timeout wasn't set it uses the same value as Timeout + expectedParams.put("master_timeout", timeout); + } + } else { + expectedParams.put("timeout", "30s"); + expectedParams.put("master_timeout", "30s"); + } + setRandomWaitForActiveShards(healthRequest::waitForActiveShards, expectedParams); + if (!expectedParams.containsKey("wait_for_active_shards")) { + expectedParams.put("wait_for_active_shards", "0"); + } + if (randomBoolean()) { + String level = randomFrom("cluster", "indices", "shards"); + healthRequest.level(level); + expectedParams.put("level", level); + } else { + expectedParams.put("level", "shards"); + } + if (randomBoolean()) { + Priority priority = randomFrom(Priority.values()); + healthRequest.waitForEvents(priority); + expectedParams.put("wait_for_events", priority.name().toLowerCase(Locale.ROOT)); + } + if (randomBoolean()) { + ClusterHealthStatus status = randomFrom(ClusterHealthStatus.values()); + healthRequest.waitForStatus(status); + expectedParams.put("wait_for_status", status.name().toLowerCase(Locale.ROOT)); + } + if (randomBoolean()) { + boolean waitForNoInitializingShards = randomBoolean(); + healthRequest.waitForNoInitializingShards(waitForNoInitializingShards); + if (waitForNoInitializingShards) { + expectedParams.put("wait_for_no_initializing_shards", Boolean.TRUE.toString()); + } + } + if (randomBoolean()) { + boolean waitForNoRelocatingShards = randomBoolean(); + healthRequest.waitForNoRelocatingShards(waitForNoRelocatingShards); + if (waitForNoRelocatingShards) { + expectedParams.put("wait_for_no_relocating_shards", Boolean.TRUE.toString()); + } + } + String[] indices = randomIndicesNames(0, 5); + if (indices.length > 0) { + healthRequest.indices(indices); + } Request request = Request.clusterHealth(healthRequest); - assertThat(request, is(notNullValue())); - assertThat(request.getMethod(), is(HttpGet.METHOD_NAME)); - assertThat(request.getEntity(), is(nullValue())); - assertThat(request.getEndpoint(), is("/_cluster/health")); - assertThat(request.getParameters(), is(expectedParams)); + assertThat(request, notNullValue()); + assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME)); + assertThat(request.getEntity(), nullValue()); + if (indices.length > 0) { + assertThat(request.getEndpoint(), equalTo("/_cluster/health/" + String.join(",", indices))); + } else { + assertThat(request.getEndpoint(), equalTo("/_cluster/health")); + } + assertThat(request.getParameters(), equalTo(expectedParams)); } public void testRollover() throws IOException { @@ -1523,6 +1577,7 @@ public void testEndpoint() { new String[]{"type1", "type2"}, "_endpoint")); assertEquals("/index1,index2/_endpoint/suffix1,suffix2", Request.endpoint(new String[]{"index1", "index2"}, "_endpoint", new String[]{"suffix1", "suffix2"})); + assertEquals("/_endpoint/suffix1,suffix2", Request.endpoint("_endpoint", new String[]{"suffix1", "suffix2"})); } public void testCreateContentType() { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java index 0747ca757c4b9..68c0b60e8caec 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java @@ -21,16 +21,23 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.LatchedActionListener; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; +import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.client.ClusterClientIT; import org.elasticsearch.client.ESRestHighLevelClientTestCase; import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.health.ClusterIndexHealth; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.indices.recovery.RecoverySettings; +import org.elasticsearch.rest.RestStatus; import java.io.IOException; import java.util.HashMap; @@ -39,6 +46,7 @@ import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; /** * This class is used to generate the Java Cluster API documentation. @@ -68,19 +76,19 @@ public void testClusterPutSettings() throws IOException { // end::put-settings-request // tag::put-settings-create-settings - String transientSettingKey = + String transientSettingKey = RecoverySettings.INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING.getKey(); int transientSettingValue = 10; - Settings transientSettings = + Settings transientSettings = Settings.builder() .put(transientSettingKey, transientSettingValue, ByteSizeUnit.BYTES) .build(); // <1> - String persistentSettingKey = + String persistentSettingKey = EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(); - String persistentSettingValue = + String persistentSettingValue = EnableAllocationDecider.Allocation.NONE.name(); - Settings persistentSettings = + Settings persistentSettings = Settings.builder() .put(persistentSettingKey, persistentSettingValue) .build(); // <2> @@ -93,9 +101,9 @@ public void testClusterPutSettings() throws IOException { { // tag::put-settings-settings-builder - Settings.Builder transientSettingsBuilder = + Settings.Builder transientSettingsBuilder = Settings.builder() - .put(transientSettingKey, transientSettingValue, ByteSizeUnit.BYTES); + .put(transientSettingKey, transientSettingValue, ByteSizeUnit.BYTES); request.transientSettings(transientSettingsBuilder); // <1> // end::put-settings-settings-builder } @@ -156,7 +164,7 @@ public void testClusterUpdateSettingsAsync() throws Exception { ClusterUpdateSettingsRequest request = new ClusterUpdateSettingsRequest(); // tag::put-settings-execute-listener - ActionListener listener = + ActionListener listener = new ActionListener() { @Override public void onResponse(ClusterUpdateSettingsResponse response) { @@ -181,4 +189,145 @@ public void onFailure(Exception e) { assertTrue(latch.await(30L, TimeUnit.SECONDS)); } } + + public void testClusterHealth() throws IOException { + RestHighLevelClient client = highLevelClient(); + { + // tag::health-request + ClusterHealthRequest request = new ClusterHealthRequest(); + // end::health-request + } + { + // tag:health-request-indices-ctr + ClusterHealthRequest request = new ClusterHealthRequest("index1", "index2"); + // end:health-request-indices-ctr + } + { + // tag:health-request-indices-setter + ClusterHealthRequest request = new ClusterHealthRequest(); + request.indices("index1", "index2"); + // end:health-request-indices-setter + } + ClusterHealthRequest request = new ClusterHealthRequest(); + + // tag:health-request-timeout + request.timeout(TimeValue.timeValueSeconds(50)); // <1> + request.timeout("50s"); // <2> + // end:health-request-timeout + + // tag:health-request-master-timeout + request.masterNodeTimeout(TimeValue.timeValueSeconds(20)); // <1> + request.masterNodeTimeout("20s"); // <2> + // end:health-request-master-timeout + + // tag:health-request-wait-status + request.waitForStatus(ClusterHealthStatus.GREEN); // <1> + request.waitForGreenStatus(); // <2> + // end:health-request-wait-status + + // tag:health-request-level + request.level("cluster"); // <1> + // end:health-request-level + + // tag:health-request-wait-relocation + request.waitForNoRelocatingShards(true); // <1> + // end:health-request-wait-relocation + + // tag:health-request-wait-initializing + request.waitForNoInitializingShards(true); // <1> + // end:health-request-wait-initializing + + // tag:health-request-wait-nodes + request.waitForNodes("2"); // <1> + request.waitForNodes(">=2"); // <2> + request.waitForNodes("le(2)"); // <3> + // end:health-request-wait-nodes + + // tag:health-request-wait-active + request.waitForActiveShards(5); // <1> + request.waitForActiveShards(ActiveShardCount.ALL); // <2> + // end:health-request-wait-active + + // tag:health-request-local + request.local(true); // <1> + // end:health-request-local + + // tag::health-execute + ClusterHealthResponse response = client.cluster().health(request); + // end::health-execute + + assertThat(response, notNullValue()); + { + // tag::health-response-general + String clusterName = response.getClusterName(); // <1> + ClusterHealthStatus status = response.getStatus(); // <2> + // end::health-response-general + + // tag::health-response-request-status + boolean timedOut = response.isTimedOut(); // <1> + RestStatus restStatus = response.status(); // <2> + // end::health-response-request-status + + // tag::health-response-nodes + int numberOfNodes = response.getNumberOfNodes(); // <1> + int numberOfDataNodes = response.getNumberOfDataNodes(); // <2> + // end::health-response-nodes + + // tag::health-response-shards + int activeShards = response.getActiveShards(); // <1> + int activePrimaryShards = response.getActivePrimaryShards(); // <2> + int relocatingShards = response.getRelocatingShards(); // <3> + int initializingShards = response.getInitializingShards(); // <4> + int unassignedShards = response.getUnassignedShards(); // <5> + int delayedUnassignedShards = response.getDelayedUnassignedShards(); // <6> + double activeShardsPercent = response.getActiveShardsPercent(); // <7> + // end::health-response-shards + + // tag::health-response-task + TimeValue taskMaxWaitingTime = response.getTaskMaxWaitingTime(); // <1> + int numberOfPendingTasks = response.getNumberOfPendingTasks(); // <2> + int numberOfInFlightFetch = response.getNumberOfInFlightFetch(); // <3> + // end::health-response-task + + // tag::health-response-indices + Map indices = response.getIndices(); // <1> + // end::health-response-indices + } + assertThat(response.isTimedOut(), equalTo(false)); + assertThat(response.status(), equalTo(RestStatus.OK)); + assertThat(response.getStatus(), equalTo(ClusterHealthStatus.GREEN)); + ClusterClientIT.emptyClusterAssertion(response); + } + + public void testClusterHealthAsync() throws Exception { + RestHighLevelClient client = highLevelClient(); + { + ClusterHealthRequest request = new ClusterHealthRequest(); + + // tag::health-execute-listener + ActionListener listener = + new ActionListener() { + @Override + public void onResponse(ClusterHealthResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::health-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::health-execute-async + client.cluster().healthAsync(request, listener); // <1> + // end::health-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } } diff --git a/docs/java-rest/high-level/cluster/health.asciidoc b/docs/java-rest/high-level/cluster/health.asciidoc new file mode 100644 index 0000000000000..071626525e607 --- /dev/null +++ b/docs/java-rest/high-level/cluster/health.asciidoc @@ -0,0 +1,189 @@ +[[java-rest-high-cluster-health]] +=== Cluster Health API + +The Cluster Health API allows to get cluster health. + +[[java-rest-high-cluster-health-request]] +==== Cluster Health Request + +A `ClusterHealthRequest`: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request] +-------------------------------------------------- +There is no required parameters. By default the client will check all indices and will not wait +for any events. + +==== Indices + +Indices which should be checked can be passed in constructor: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-indices-ctr] +-------------------------------------------------- + +Or using the method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-indices-setter] +-------------------------------------------------- + +==== Other parameters + +Other parameters can be passed only through methods: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-timeout] +-------------------------------------------------- +<1> Timeout for the request as a `TimeValue`. Defaults to 30 seconds +<2> As a `String` + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-master-timeout] +-------------------------------------------------- +<1> Timeout to connect to the master node as a `TimeValue`. Defaults to the same as `timeout` +<2> As a `String` + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-status] +-------------------------------------------------- +<1> The status to wait for or better i.e. `green` > `yellow` > `red`. Accepts `ClusterHealthStatus` instance +<2> Using predefined method + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-level] +-------------------------------------------------- +<1> The level of representation. Possible levels: +- `cluster` - indices collection will be empty +- `indices` - indices will have empty shards collections +- `shards` - full details. Used by default + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-relocation] +-------------------------------------------------- +<1> Wait for 0 relocating shards. Default `false` + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-initializing] +-------------------------------------------------- +<1> Wait for 0 initializing shards. Default `false` + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-nodes] +-------------------------------------------------- +<1> Wait for `N` nodes in the cluster. Defaults to `0` +<2> Using `>=N`, `<=N`, `>N` and ` Using `ge(N)`, `le(N)`, `gt(N)`, `lt(N)` notation + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-active] +-------------------------------------------------- +<1> Wait for `N` shards to be active in the cluster +<2> Wait for all shards to be active in the cluster + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-master-timeout] +-------------------------------------------------- +<1> Non-master node can be used for this request. Defaults to `false` + +[[java-rest-high-cluster-health-sync]] +==== Synchronous Execution + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-execute] +-------------------------------------------------- + +[[java-rest-high-cluster-health-async]] +==== Asynchronous Execution + +The asynchronous execution of a cluster update settings requires both the +`ClusterHealthRequest` instance and an `ActionListener` instance to be +passed to the asynchronous method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-execute-async] +-------------------------------------------------- +<1> The `ClusterHealthRequest` 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 `ClusterHealthResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of a failure. The raised exception is provided as an argument + +[[java-rest-high-cluster-health-response]] +==== Cluster Health Response + +The returned `ClusterHealthResponse` contains the next information about the +cluster: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-general] +-------------------------------------------------- +<1> Name of the cluster +<2> Cluster status (`green`, `yellow` or `red`) + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-request-status] +-------------------------------------------------- +<1> Whether request was timed out while processing +<2> Status of request (`OK` or `REQUEST_TIMEOUT`). Other errors will be thrown as exceptions + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-nodes] +-------------------------------------------------- +<1> Number of nodes in the cluster +<2> Number of data nodes in the cluster + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-shards] +-------------------------------------------------- +<1> Number of active shards +<2> Number of primary active shards +<3> Number of relocating shards +<4> Number of initializing shards +<5> Number of unassigned shards +<6> Number of unassigned shards that are currently being delayed +<7> Percent of active shards + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-task] +-------------------------------------------------- +<1> Maximum wait time of all tasks in the queue +<2> Number of currently pending tasks +<3> Number of async fetches that are currently ongoing + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-indices] +-------------------------------------------------- +<1> Detailed information about indices in the cluster \ 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 0330b1903c5bf..0a66339256868 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -93,5 +93,7 @@ include::indices/exists_alias.asciidoc[] The Java High Level REST Client supports the following Cluster APIs: * <> +* <> include::cluster/put_settings.asciidoc[] +include::cluster/health.asciidoc[] diff --git a/docs/reference/cluster/health.asciidoc b/docs/reference/cluster/health.asciidoc index 6cc99a25476d9..37e055018091a 100644 --- a/docs/reference/cluster/health.asciidoc +++ b/docs/reference/cluster/health.asciidoc @@ -108,6 +108,11 @@ The cluster health API accepts the following request parameters: A time based parameter controlling how long to wait if one of the wait_for_XXX are provided. Defaults to `30s`. +`master_timeout`:: + A time based parameter controlling how long to wait if the master has not been + discovered yet or disconnected. + If not provided, uses the same value as `timeout`. + `local`:: If `true` returns the local node information and does not provide the state from master node. Default: `false`. diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java index 25a62d03f0b80..300691615930b 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java @@ -45,6 +45,12 @@ public class ClusterHealthRequest extends MasterNodeReadRequest indexList = (List) a[i++]; final Map indices; - if (isEmpty(indexList)) { + if (indexList == null || indexList.isEmpty()) { indices = emptyMap(); } else { - indices = newHashMapWithExpectedSize(indexList.size()); + indices = new HashMap<>(indexList.size()); for (ClusterIndexHealth indexHealth : indexList) { indices.put(indexHealth.getIndex(), indexHealth); } @@ -370,12 +369,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClusterHealthResponse that = (ClusterHealthResponse) o; - return numberOfPendingTasks == that.numberOfPendingTasks && + return Objects.equals(clusterName, that.clusterName) && + numberOfPendingTasks == that.numberOfPendingTasks && numberOfInFlightFetch == that.numberOfInFlightFetch && delayedUnassignedShards == that.delayedUnassignedShards && - timedOut == that.timedOut && - Objects.equals(clusterName, that.clusterName) && Objects.equals(taskMaxWaitingTime, that.taskMaxWaitingTime) && + timedOut == that.timedOut && Objects.equals(clusterStateHealth, that.clusterStateHealth) && clusterHealthStatus == that.clusterHealthStatus; } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java index edb590e06607b..6ee35ef07734e 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java @@ -41,8 +41,6 @@ import java.util.Objects; import static java.util.Collections.emptyMap; -import static org.elasticsearch.common.util.CollectionUtils.isEmpty; -import static org.elasticsearch.common.util.CollectionUtils.newHashMapWithExpectedSize; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; @@ -74,10 +72,10 @@ public final class ClusterIndexHealth implements Iterable, W ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); List shardList = (List) a[i++]; final Map shards; - if (isEmpty(shardList)) { + if (shardList == null || shardList.isEmpty()) { shards = emptyMap(); } else { - shards = newHashMapWithExpectedSize(shardList.size()); + shards = new HashMap<>(shardList.size()); for (ClusterShardHealth shardHealth : shardList) { shards.put(shardHealth.getShardId(), shardHealth); } @@ -172,7 +170,7 @@ public ClusterIndexHealth(final StreamInput in) throws IOException { status = ClusterHealthStatus.fromValue(in.readByte()); int size = in.readVInt(); - shards = newHashMapWithExpectedSize(size); + shards = new HashMap<>(size); for (int i = 0; i < size; i++) { ClusterShardHealth shardHealth = new ClusterShardHealth(in); shards.put(shardHealth.getShardId(), shardHealth); @@ -309,14 +307,14 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClusterIndexHealth that = (ClusterIndexHealth) o; - return numberOfShards == that.numberOfShards && + return Objects.equals(index, that.index) && + numberOfShards == that.numberOfShards && numberOfReplicas == that.numberOfReplicas && activeShards == that.activeShards && relocatingShards == that.relocatingShards && initializingShards == that.initializingShards && unassignedShards == that.unassignedShards && activePrimaryShards == that.activePrimaryShards && - Objects.equals(index, that.index) && status == that.status && Objects.equals(shards, that.shards); } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java index 41ad8cb82fa48..99dd41f63d815 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java @@ -35,7 +35,6 @@ import java.util.Map; import java.util.Objects; -import static org.elasticsearch.common.util.CollectionUtils.newHashMapWithExpectedSize; public final class ClusterStateHealth implements Iterable, Writeable { @@ -137,7 +136,7 @@ public ClusterStateHealth(final StreamInput in) throws IOException { numberOfDataNodes = in.readVInt(); status = ClusterHealthStatus.fromValue(in.readByte()); int size = in.readVInt(); - indices = newHashMapWithExpectedSize(size); + indices = new HashMap<>(size); for (int i = 0; i < size; i++) { ClusterIndexHealth indexHealth = new ClusterIndexHealth(in); indices.put(indexHealth.getIndex(), indexHealth); diff --git a/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java b/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java index c457ce6387c29..4ddd1cd7f698d 100644 --- a/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java +++ b/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java @@ -33,7 +33,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; @@ -44,10 +43,6 @@ /** Collections-related utility methods. */ public class CollectionUtils { - /** - * The largest power of two that can be represented as an {@code int}. - */ - public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); public static void sort(final long[] array, int len) { new IntroSorter() { @@ -150,18 +145,6 @@ public static boolean isEmpty(Object[] array) { return array == null || array.length == 0; } - /** - * Null-safe check if the specified collection is empty. - *

- * Null returns true. - * - * @param coll the collection to check, may be null - * @return true if empty or null - */ - public static boolean isEmpty(final Collection coll) { - return coll == null || coll.isEmpty(); - } - /** * Return a rotated view of the given list with the given distance. */ @@ -444,41 +427,4 @@ public static List> eagerPartition(List list, int size) { return result; } - - /** - * Creates a {@code HashMap} instance, with a high enough "initial capacity" that it should hold - * {@code expectedSize} elements without growth. This behavior cannot be broadly guaranteed, but it is observed - * to be true for OpenJDK 1.7. - * It also can't be guaranteed that the method isn't inadvertently oversizing the returned map. - * - *

From Guava 24.1-jre. com.google.common.collect.Maps

- * - * @param expectedSize the number of entries you expect to add to the returned map - * @return a new, empty {@code HashMap} with enough capacity to hold {@code expectedSize} entries without resizing - * @throws IllegalArgumentException if {@code expectedSize} is negative - */ - public static HashMap newHashMapWithExpectedSize(int expectedSize) { - return new HashMap<>(capacity(expectedSize)); - } - - /** - * Returns a capacity that is sufficient to keep the map from being resized as long as it grows no larger - * than expectedSize and the load factor is ≥ its default (0.75). - * - *

From Guava 24.1-jre. com.google.common.collect.Maps

- */ - static int capacity(int expectedSize) { - if (expectedSize < 3) { - if (expectedSize < 0) { - throw new IllegalArgumentException("expectedSize cannot be negative but was: " + expectedSize); - } - return expectedSize + 1; - } - if (expectedSize < MAX_POWER_OF_TWO) { - // This is the calculation used in JDK8,9,10 to resize when a putAll happens. - // It seems to be the most conservative calculation we can make. 0.75 is the default load factor. - return (int) ((float) expectedSize / 0.75F + 1.0F); - } - return Integer.MAX_VALUE; // any large value - } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java index f4ae70401f169..9fa1c9940e99e 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java @@ -30,7 +30,6 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.rest.RestStatus; @@ -117,7 +116,7 @@ protected ClusterHealthResponse createBlankInstance() { @Override protected ClusterHealthResponse createTestInstance() { int indicesSize = randomInt(20); - Map indices = CollectionUtils.newHashMapWithExpectedSize(indicesSize); + Map indices = new HashMap<>(indicesSize); for (int i = 0; i < indicesSize; i++) { String indexName = randomAlphaOfLengthBetween(1, 5) + i; indices.put(indexName, ClusterIndexHealthTests.randomIndexHealthWithShards(indexName)); From 67eb6085943bba5fa036e90f5707aafd3d62a5f5 Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Wed, 11 Apr 2018 10:52:16 -0400 Subject: [PATCH 04/14] Round 3 - Fix documentation - Move health calculation logic from constructors to HealthAction - Index, Shards: Remove toXContent name serialization. startObject(name) inside the fragment - Add missing overrides - Use 8 spaces continuation indent --- .../org/elasticsearch/client/Request.java | 6 +- .../elasticsearch/client/ClusterClientIT.java | 76 +++++-- .../elasticsearch/client/RequestTests.java | 61 +++--- .../ClusterClientDocumentationIT.java | 44 ++-- .../high-level/cluster/health.asciidoc | 10 +- .../cluster/health/ClusterHealthRequest.java | 14 +- .../cluster/health/ClusterHealthResponse.java | 149 +++++++------ .../health/TransportClusterHealthAction.java | 195 ++++++++++++++++- .../stats/TransportClusterStatsAction.java | 4 +- .../TransportIndicesShardStoresAction.java | 3 +- .../cluster/health/ClusterIndexHealth.java | 204 +++++++----------- .../cluster/health/ClusterShardHealth.java | 151 ++++--------- .../cluster/health/ClusterStateHealth.java | 154 +++---------- .../routing/allocation/AllocationService.java | 7 +- .../health/ClusterHealthResponsesTests.java | 83 +++++-- .../health/ClusterIndexHealthTests.java | 106 ++++++++- .../health/ClusterShardHealthTests.java | 63 ++++++ .../health/ClusterStateHealthTests.java | 14 +- .../cluster/routing/PrimaryTermsTests.java | 3 +- .../routing/RoutingTableGenerator.java | 2 +- .../DecisionsImpactOnClusterHealthTests.java | 3 +- .../gateway/PrimaryShardAllocatorTests.java | 3 +- .../action/cat/RestIndicesActionTests.java | 5 +- .../test/AbstractXContentTestCase.java | 14 +- 24 files changed, 818 insertions(+), 556 deletions(-) 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 65504f4e76753..da3ee262e0085 100644 --- 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 @@ -599,7 +599,7 @@ static Request clusterHealth(ClusterHealthRequest healthRequest) { params.withLocal(healthRequest.local()); params.withLevel(healthRequest.level()); String[] indices = healthRequest.indices() == null ? Strings.EMPTY_ARRAY : healthRequest.indices(); - String endpoint = endpoint("_cluster/health", indices); + String endpoint = new EndpointBuilder().addPathPartAsIs("_cluster/health").addCommaSeparatedPathParts(indices).build(); return new Request(HttpGet.METHOD_NAME, endpoint, params.getParams(), null); } @@ -663,10 +663,6 @@ static String endpoint(String[] indices, String endpoint, String[] suffixes) { .addCommaSeparatedPathParts(suffixes).build(); } - static String endpoint(String endpoint, String[] suffixes) { - return new EndpointBuilder().addPathPartAsIs(endpoint).addCommaSeparatedPathParts(suffixes).build(); - } - static String endpoint(String[] indices, String endpoint, String type) { return new EndpointBuilder().addCommaSeparatedPathParts(indices).addPathPartAsIs(endpoint).addPathPart(type).build(); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java index 8d57ca042a3d4..a0c860123129c 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java @@ -124,44 +124,74 @@ public void testClusterHealthGreen() throws IOException { emptyClusterAssertion(response); } - public void testClusterHealthYellow() throws IOException { + public void testClusterHealthYellowClusterLevel() throws IOException { createIndex("index", Settings.EMPTY); createIndex("index2", Settings.EMPTY); ClusterHealthRequest request = new ClusterHealthRequest(); request.timeout("5s"); - boolean requestOneIndex = randomBoolean(); - if (requestOneIndex) { - request.indices("index"); - } - String level = randomFrom("default-shards", "cluster", "indices", "shards"); - if (!"default-shards".equals(level)) { - request.level(level); + request.level("cluster"); + ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); + + yellowTenShardsClusterAssertion(response); + assertThat(response.getIndices().size(), equalTo(0)); + } + + public void testClusterHealthYellowIndicesLevel() throws IOException { + createIndex("index", Settings.EMPTY); + createIndex("index2", Settings.EMPTY); + ClusterHealthRequest request = new ClusterHealthRequest(); + request.timeout("5s"); + request.level("indices"); + ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); + + yellowTenShardsClusterAssertion(response); + assertThat(response.getIndices().size(), equalTo(2)); + for (Map.Entry entry : response.getIndices().entrySet()) { + indexAssertion(entry.getKey(), entry.getValue(), true); } + } + + private void yellowTenShardsClusterAssertion(ClusterHealthResponse response) { + assertThat(response, notNullValue()); + assertThat(response.isTimedOut(), equalTo(false)); + assertThat(response.status(), equalTo(RestStatus.OK)); + assertThat(response.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); + assertThat(response.getActivePrimaryShards(), equalTo(10)); + assertThat(response.getNumberOfDataNodes(), equalTo(1)); + assertThat(response.getNumberOfNodes(), equalTo(1)); + assertThat(response.getActiveShards(), equalTo(10)); + assertThat(response.getDelayedUnassignedShards(), equalTo(0)); + assertThat(response.getInitializingShards(), equalTo(0)); + assertThat(response.getUnassignedShards(), equalTo(10)); + assertThat(response.getActiveShardsPercent(), equalTo(50d)); + } + + + public void testClusterHealthYellowSpecificIndex() throws IOException { + createIndex("index", Settings.EMPTY); + createIndex("index2", Settings.EMPTY); + ClusterHealthRequest request = new ClusterHealthRequest("index"); + request.timeout("5s"); ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); assertThat(response, notNullValue()); assertThat(response.isTimedOut(), equalTo(false)); assertThat(response.status(), equalTo(RestStatus.OK)); assertThat(response.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); - assertThat(response.getActivePrimaryShards(), equalTo(requestOneIndex? 5 : 10)); + assertThat(response.getActivePrimaryShards(), equalTo(5)); assertThat(response.getNumberOfDataNodes(), equalTo(1)); assertThat(response.getNumberOfNodes(), equalTo(1)); - assertThat(response.getActiveShards(), equalTo(requestOneIndex? 5 : 10)); + assertThat(response.getActiveShards(), equalTo(5)); assertThat(response.getDelayedUnassignedShards(), equalTo(0)); assertThat(response.getInitializingShards(), equalTo(0)); - assertThat(response.getUnassignedShards(), equalTo(requestOneIndex? 5 : 10)); + assertThat(response.getUnassignedShards(), equalTo(5)); assertThat(response.getActiveShardsPercent(), equalTo(50d)); - if ("shards".equals(level) || "indices".equals(level) || "default-shards".equals(level)) { - assertThat(response.getIndices().size(), equalTo(requestOneIndex ? 1 : 2)); - for (Map.Entry entry : response.getIndices().entrySet()) { - indexAssertion(entry.getKey(), entry.getValue(), level); - } - } else { - assertThat(response.getIndices().size(), equalTo(0)); - } + assertThat(response.getIndices().size(), equalTo(1)); + Map.Entry index = response.getIndices().entrySet().iterator().next(); + indexAssertion(index.getKey(), index.getValue(), false); } - private void indexAssertion(String indexName, ClusterIndexHealth indexHealth, String level) { + private void indexAssertion(String indexName, ClusterIndexHealth indexHealth, boolean emptyShards) { assertThat(indexHealth, notNullValue()); assertThat(indexHealth.getIndex(),equalTo(indexName)); assertThat(indexHealth.getActivePrimaryShards(),equalTo(5)); @@ -171,13 +201,13 @@ private void indexAssertion(String indexName, ClusterIndexHealth indexHealth, St assertThat(indexHealth.getUnassignedShards(),equalTo(5)); assertThat(indexHealth.getRelocatingShards(),equalTo(0)); assertThat(indexHealth.getStatus(),equalTo(ClusterHealthStatus.YELLOW)); - if ("shards".equals(level) || "default-shards".equals(level)) { + if (emptyShards) { + assertThat(indexHealth.getShards().size(), equalTo(0)); + } else { assertThat(indexHealth.getShards().size(), equalTo(5)); for (Map.Entry entry : indexHealth.getShards().entrySet()) { shardAssertion(entry.getKey(), entry.getValue()); } - } else { - assertThat(indexHealth.getShards().size(), equalTo(0)); } } 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 6be52e37fa825..b907af44eb81f 100644 --- 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 @@ -1333,26 +1333,35 @@ public void testClusterHealth() { ClusterHealthRequest healthRequest = new ClusterHealthRequest(); Map expectedParams = new HashMap<>(); setRandomLocal(healthRequest, expectedParams); - if (randomBoolean()) { - String timeout = randomTimeValue(); - healthRequest.timeout(timeout); - expectedParams.put("timeout", timeout); - if (randomBoolean()) { - String masterTimeout = randomTimeValue(); - healthRequest.masterNodeTimeout(masterTimeout); + String timeoutType = randomFrom("timeout", "masterTimeout", "both", "none"); + String timeout = randomTimeValue(); + String masterTimeout = randomTimeValue(); + switch (timeoutType) { + case "timeout": + healthRequest.timeout(timeout); + expectedParams.put("timeout", timeout); + // If Master Timeout wasn't set it uses the same value as Timeout + expectedParams.put("master_timeout", timeout); + break; + case "masterTimeout": + expectedParams.put("timeout", "30s"); + healthRequest.timeout(masterTimeout); expectedParams.put("master_timeout", masterTimeout); - } else { - // If Master Timeout wasn't set it uses the same value as Timeout + break; + case "both": + healthRequest.timeout(timeout); + expectedParams.put("timeout", timeout); + healthRequest.masterNodeTimeout(timeout); expectedParams.put("master_timeout", timeout); - } - } else { - expectedParams.put("timeout", "30s"); - expectedParams.put("master_timeout", "30s"); - } - setRandomWaitForActiveShards(healthRequest::waitForActiveShards, expectedParams); - if (!expectedParams.containsKey("wait_for_active_shards")) { - expectedParams.put("wait_for_active_shards", "0"); - } + break; + case "none": + expectedParams.put("timeout", "30s"); + expectedParams.put("master_timeout", "30s"); + break; + default: + throw new UnsupportedOperationException(); + } + setRandomWaitForActiveShards(healthRequest::waitForActiveShards, expectedParams, "0"); if (randomBoolean()) { String level = randomFrom("cluster", "indices", "shards"); healthRequest.level(level); @@ -1384,16 +1393,14 @@ public void testClusterHealth() { expectedParams.put("wait_for_no_relocating_shards", Boolean.TRUE.toString()); } } - String[] indices = randomIndicesNames(0, 5); - if (indices.length > 0) { - healthRequest.indices(indices); - } + String[] indices = randomBoolean() ? null : randomIndicesNames(0, 5); + healthRequest.indices(indices); Request request = Request.clusterHealth(healthRequest); assertThat(request, notNullValue()); assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME)); assertThat(request.getEntity(), nullValue()); - if (indices.length > 0) { + if (indices != null && indices.length > 0) { assertThat(request.getEndpoint(), equalTo("/_cluster/health/" + String.join(",", indices))); } else { assertThat(request.getEndpoint(), equalTo("/_cluster/health")); @@ -1577,7 +1584,6 @@ public void testEndpoint() { new String[]{"type1", "type2"}, "_endpoint")); assertEquals("/index1,index2/_endpoint/suffix1,suffix2", Request.endpoint(new String[]{"index1", "index2"}, "_endpoint", new String[]{"suffix1", "suffix2"})); - assertEquals("/_endpoint/suffix1,suffix2", Request.endpoint("_endpoint", new String[]{"suffix1", "suffix2"})); } public void testCreateContentType() { @@ -1721,6 +1727,11 @@ private static void setRandomMasterTimeout(MasterNodeRequest request, Map setter, Map expectedParams) { + setRandomWaitForActiveShards(setter, expectedParams, null); + } + + private static void setRandomWaitForActiveShards(Consumer setter, Map expectedParams, + String defaultValue) { if (randomBoolean()) { String waitForActiveShardsString; int waitForActiveShards = randomIntBetween(-1, 5); @@ -1731,6 +1742,8 @@ private static void setRandomWaitForActiveShards(Consumer sett } setter.accept(ActiveShardCount.parseString(waitForActiveShardsString)); expectedParams.put("wait_for_active_shards", waitForActiveShardsString); + } else if (defaultValue != null){ + expectedParams.put("wait_for_active_shards", defaultValue); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java index 68c0b60e8caec..40381115fb84d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java @@ -198,59 +198,59 @@ public void testClusterHealth() throws IOException { // end::health-request } { - // tag:health-request-indices-ctr + // tag::health-request-indices-ctr ClusterHealthRequest request = new ClusterHealthRequest("index1", "index2"); - // end:health-request-indices-ctr + // end::health-request-indices-ctr } { - // tag:health-request-indices-setter + // tag::health-request-indices-setter ClusterHealthRequest request = new ClusterHealthRequest(); request.indices("index1", "index2"); - // end:health-request-indices-setter + // end::health-request-indices-setter } ClusterHealthRequest request = new ClusterHealthRequest(); - // tag:health-request-timeout + // tag::health-request-timeout request.timeout(TimeValue.timeValueSeconds(50)); // <1> request.timeout("50s"); // <2> - // end:health-request-timeout + // end::health-request-timeout - // tag:health-request-master-timeout + // tag::health-request-master-timeout request.masterNodeTimeout(TimeValue.timeValueSeconds(20)); // <1> request.masterNodeTimeout("20s"); // <2> - // end:health-request-master-timeout + // end::health-request-master-timeout - // tag:health-request-wait-status + // tag::health-request-wait-status request.waitForStatus(ClusterHealthStatus.GREEN); // <1> request.waitForGreenStatus(); // <2> - // end:health-request-wait-status + // end::health-request-wait-status - // tag:health-request-level + // tag::health-request-level request.level("cluster"); // <1> - // end:health-request-level + // end::health-request-level - // tag:health-request-wait-relocation + // tag::health-request-wait-relocation request.waitForNoRelocatingShards(true); // <1> - // end:health-request-wait-relocation + // end::health-request-wait-relocation - // tag:health-request-wait-initializing + // tag::health-request-wait-initializing request.waitForNoInitializingShards(true); // <1> - // end:health-request-wait-initializing + // end::health-request-wait-initializing - // tag:health-request-wait-nodes + // tag::health-request-wait-nodes request.waitForNodes("2"); // <1> request.waitForNodes(">=2"); // <2> request.waitForNodes("le(2)"); // <3> - // end:health-request-wait-nodes + // end::health-request-wait-nodes - // tag:health-request-wait-active + // tag::health-request-wait-active request.waitForActiveShards(5); // <1> request.waitForActiveShards(ActiveShardCount.ALL); // <2> - // end:health-request-wait-active + // end::health-request-wait-active - // tag:health-request-local + // tag::health-request-local request.local(true); // <1> - // end:health-request-local + // end::health-request-local // tag::health-execute ClusterHealthResponse response = client.cluster().health(request); diff --git a/docs/java-rest/high-level/cluster/health.asciidoc b/docs/java-rest/high-level/cluster/health.asciidoc index 071626525e607..11bc51bf2e8ea 100644 --- a/docs/java-rest/high-level/cluster/health.asciidoc +++ b/docs/java-rest/high-level/cluster/health.asciidoc @@ -1,7 +1,7 @@ [[java-rest-high-cluster-health]] === Cluster Health API -The Cluster Health API allows to get cluster health. +The Cluster Health API allows getting cluster health. [[java-rest-high-cluster-health-request]] ==== Cluster Health Request @@ -12,12 +12,12 @@ A `ClusterHealthRequest`: -------------------------------------------------- include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request] -------------------------------------------------- -There is no required parameters. By default the client will check all indices and will not wait +There are no required parameters. By default, the client will check all indices and will not wait for any events. ==== Indices -Indices which should be checked can be passed in constructor: +Indices which should be checked can be passed in the constructor: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- @@ -94,7 +94,7 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wai ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-master-timeout] +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-local] -------------------------------------------------- <1> Non-master node can be used for this request. Defaults to `false` @@ -153,7 +153,7 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-ge include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-request-status] -------------------------------------------------- <1> Whether request was timed out while processing -<2> Status of request (`OK` or `REQUEST_TIMEOUT`). Other errors will be thrown as exceptions +<2> Status of the request (`OK` or `REQUEST_TIMEOUT`). Other errors will be thrown as exceptions ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java index 300691615930b..3ec0ad1f9e1ea 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java @@ -48,7 +48,7 @@ public class ClusterHealthRequest extends MasterNodeReadRequest PARSER = - new ConstructingObjectParser<>("cluster_health_response", true, - a -> { - int i = 0; - // ClusterStateHealth fields - int numberOfNodes = (int) a[i++]; - int numberOfDataNodes = (int) a[i++]; - int activeShards = (int) a[i++]; - int relocatingShards = (int) a[i++]; - int activePrimaryShards = (int) a[i++]; - int initializingShards = (int) a[i++]; - int unassignedShards = (int) a[i++]; - double activeShardsPercent = (double) a[i++]; - String statusStr = (String) a[i++]; - ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); - List indexList = (List) a[i++]; - final Map indices; - if (indexList == null || indexList.isEmpty()) { - indices = emptyMap(); - } else { - indices = new HashMap<>(indexList.size()); - for (ClusterIndexHealth indexHealth : indexList) { - indices.put(indexHealth.getIndex(), indexHealth); - } - } - ClusterStateHealth stateHealth = new ClusterStateHealth(activePrimaryShards, activeShards, relocatingShards, - initializingShards, unassignedShards, numberOfNodes, numberOfDataNodes, activeShardsPercent, status, indices); - - // ClusterHealthResponse fields - String clusterName = (String) a[i++]; - int numberOfPendingTasks = (int) a[i++]; - int numberOfInFlightFetch = (int) a[i++]; - int delayedUnassignedShards = (int) a[i++]; - long taskMaxWaitingTimeMillis = (long) a[i++]; - boolean timedOut = (boolean) a[i++]; - return new ClusterHealthResponse(clusterName, numberOfPendingTasks, numberOfInFlightFetch, delayedUnassignedShards, - TimeValue.timeValueMillis(taskMaxWaitingTimeMillis), timedOut, stateHealth); - }); - - public static final ObjectParser.NamedObjectParser INDEX_PARSER = - (XContentParser p, Void c, String nameIgnored) -> ClusterIndexHealth.fromXContent(p); + new ConstructingObjectParser<>("cluster_health_response", true, + parsedObjects -> { + int i = 0; + // ClusterStateHealth fields + int numberOfNodes = (int) parsedObjects[i++]; + int numberOfDataNodes = (int) parsedObjects[i++]; + int activeShards = (int) parsedObjects[i++]; + int relocatingShards = (int) parsedObjects[i++]; + int activePrimaryShards = (int) parsedObjects[i++]; + int initializingShards = (int) parsedObjects[i++]; + int unassignedShards = (int) parsedObjects[i++]; + double activeShardsPercent = (double) parsedObjects[i++]; + String statusStr = (String) parsedObjects[i++]; + ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); + @SuppressWarnings("unchecked") List indexList = (List) parsedObjects[i++]; + final Map indices; + if (indexList == null || indexList.isEmpty()) { + indices = emptyMap(); + } else { + indices = new HashMap<>(indexList.size()); + for (ClusterIndexHealth indexHealth : indexList) { + indices.put(indexHealth.getIndex(), indexHealth); + } + } + ClusterStateHealth stateHealth = new ClusterStateHealth(activePrimaryShards, activeShards, relocatingShards, + initializingShards, unassignedShards, numberOfNodes, numberOfDataNodes, activeShardsPercent, status, + indices); + + // ClusterHealthResponse fields + String clusterName = (String) parsedObjects[i++]; + int numberOfPendingTasks = (int) parsedObjects[i++]; + int numberOfInFlightFetch = (int) parsedObjects[i++]; + int delayedUnassignedShards = (int) parsedObjects[i++]; + long taskMaxWaitingTimeMillis = (long) parsedObjects[i++]; + boolean timedOut = (boolean) parsedObjects[i]; + return new ClusterHealthResponse(clusterName, numberOfPendingTasks, numberOfInFlightFetch, delayedUnassignedShards, + TimeValue.timeValueMillis(taskMaxWaitingTimeMillis), timedOut, stateHealth); + }); + + private static final ObjectParser.NamedObjectParser INDEX_PARSER = + (XContentParser parser, Void context, String index) -> ClusterIndexHealth.innerFromXContent(parser, index); static { // ClusterStateHealth fields @@ -118,7 +120,7 @@ public class ClusterHealthResponse extends ActionResponse implements StatusToXCo PARSER.declareInt(constructorArg(), new ParseField(INITIALIZING_SHARDS)); PARSER.declareInt(constructorArg(), new ParseField(UNASSIGNED_SHARDS)); PARSER.declareDouble(constructorArg(), new ParseField(ACTIVE_SHARDS_PERCENT_AS_NUMBER)); - PARSER.declareString(constructorArg(), new ParseField(STATUS)); + PARSER.declareString(constructorArg(), new ParseField(STATUS)); // Can be absent if LEVEL == 'cluster' PARSER.declareNamedObjects(optionalConstructorArg(), INDEX_PARSER, new ParseField(INDICES)); @@ -143,34 +145,29 @@ public class ClusterHealthResponse extends ActionResponse implements StatusToXCo ClusterHealthResponse() { } - /** needed for plugins BWC */ - public ClusterHealthResponse(String clusterName, String[] concreteIndices, ClusterState clusterState) { - this(clusterName, concreteIndices, clusterState, -1, -1, -1, TimeValue.timeValueHours(0)); - } - - public ClusterHealthResponse(String clusterName, String[] concreteIndices, ClusterState clusterState, int numberOfPendingTasks, - int numberOfInFlightFetch, int delayedUnassignedShards, TimeValue taskMaxWaitingTime) { + public ClusterHealthResponse(String clusterName, int numberOfPendingTasks, int numberOfInFlightFetch, int delayedUnassignedShards, + TimeValue taskMaxWaitingTime, boolean timedOut, ClusterStateHealth clusterStateHealth) { this.clusterName = clusterName; this.numberOfPendingTasks = numberOfPendingTasks; this.numberOfInFlightFetch = numberOfInFlightFetch; this.delayedUnassignedShards = delayedUnassignedShards; this.taskMaxWaitingTime = taskMaxWaitingTime; - this.clusterStateHealth = new ClusterStateHealth(clusterState, concreteIndices); + this.timedOut = timedOut; + this.clusterStateHealth = clusterStateHealth; this.clusterHealthStatus = clusterStateHealth.getStatus(); } /** - * For XContent Parser and serialization tests + * needed for plugins BWC + * Do not use for other purposes */ - ClusterHealthResponse(String clusterName, int numberOfPendingTasks, int numberOfInFlightFetch, int delayedUnassignedShards, - TimeValue taskMaxWaitingTime, boolean timedOut, ClusterStateHealth clusterStateHealth) { + public ClusterHealthResponse(String clusterName, String[] concreteIndices, ClusterState clusterState) { this.clusterName = clusterName; - this.numberOfPendingTasks = numberOfPendingTasks; - this.numberOfInFlightFetch = numberOfInFlightFetch; - this.delayedUnassignedShards = delayedUnassignedShards; - this.taskMaxWaitingTime = taskMaxWaitingTime; - this.timedOut = timedOut; - this.clusterStateHealth = clusterStateHealth; + this.numberOfPendingTasks = -1; + this.numberOfInFlightFetch = -1; + this.delayedUnassignedShards = -1; + this.taskMaxWaitingTime = TimeValue.timeValueHours(0); + this.clusterStateHealth = calculateStateHealth(clusterState, concreteIndices); this.clusterHealthStatus = clusterStateHealth.getStatus(); } @@ -309,15 +306,15 @@ public void writeTo(StreamOutput out) throws IOException { @Override public String toString() { return "ClusterHealthResponse{" + - "clusterName='" + clusterName + '\'' + - ", numberOfPendingTasks=" + numberOfPendingTasks + - ", numberOfInFlightFetch=" + numberOfInFlightFetch + - ", delayedUnassignedShards=" + delayedUnassignedShards + - ", taskMaxWaitingTime=" + taskMaxWaitingTime + - ", timedOut=" + timedOut + - ", clusterStateHealth=" + clusterStateHealth + - ", clusterHealthStatus=" + clusterHealthStatus + - '}'; + "clusterName='" + clusterName + '\'' + + ", numberOfPendingTasks=" + numberOfPendingTasks + + ", numberOfInFlightFetch=" + numberOfInFlightFetch + + ", delayedUnassignedShards=" + delayedUnassignedShards + + ", taskMaxWaitingTime=" + taskMaxWaitingTime + + ", timedOut=" + timedOut + + ", clusterStateHealth=" + clusterStateHealth + + ", clusterHealthStatus=" + clusterHealthStatus + + '}'; } @Override @@ -350,9 +347,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (outputIndices) { builder.startObject(INDICES); for (ClusterIndexHealth indexHealth : clusterStateHealth.getIndices().values()) { - builder.startObject(indexHealth.getIndex()); indexHealth.toXContent(builder, params); - builder.endObject(); } builder.endObject(); } @@ -370,18 +365,18 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; ClusterHealthResponse that = (ClusterHealthResponse) o; return Objects.equals(clusterName, that.clusterName) && - numberOfPendingTasks == that.numberOfPendingTasks && - numberOfInFlightFetch == that.numberOfInFlightFetch && - delayedUnassignedShards == that.delayedUnassignedShards && - Objects.equals(taskMaxWaitingTime, that.taskMaxWaitingTime) && - timedOut == that.timedOut && - Objects.equals(clusterStateHealth, that.clusterStateHealth) && - clusterHealthStatus == that.clusterHealthStatus; + numberOfPendingTasks == that.numberOfPendingTasks && + numberOfInFlightFetch == that.numberOfInFlightFetch && + delayedUnassignedShards == that.delayedUnassignedShards && + Objects.equals(taskMaxWaitingTime, that.taskMaxWaitingTime) && + timedOut == that.timedOut && + Objects.equals(clusterStateHealth, that.clusterStateHealth) && + clusterHealthStatus == that.clusterHealthStatus; } @Override public int hashCode() { return Objects.hash(clusterName, numberOfPendingTasks, numberOfInFlightFetch, delayedUnassignedShards, taskMaxWaitingTime, - timedOut, clusterStateHealth, clusterHealthStatus); + timedOut, clusterStateHealth, clusterHealthStatus); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java index 697849985afeb..dd33dfa4d76c0 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java @@ -32,8 +32,17 @@ import org.elasticsearch.cluster.NotMasterException; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.health.ClusterIndexHealth; +import org.elasticsearch.cluster.health.ClusterShardHealth; +import org.elasticsearch.cluster.health.ClusterStateHealth; +import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.routing.IndexRoutingTable; +import org.elasticsearch.cluster.routing.IndexShardRoutingTable; +import org.elasticsearch.cluster.routing.RecoverySource; +import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; +import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; @@ -41,10 +50,14 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.gateway.GatewayAllocator; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.function.Predicate; public class TransportClusterHealthAction extends TransportMasterNodeReadAction { @@ -315,15 +328,187 @@ private ClusterHealthResponse clusterHealth(ClusterHealthRequest request, Cluste try { concreteIndices = indexNameExpressionResolver.concreteIndexNames(clusterState, request); } catch (IndexNotFoundException e) { + ClusterStateHealth stateHealth = calculateStateHealth(clusterState, Strings.EMPTY_ARRAY); + ClusterHealthResponse response = new ClusterHealthResponse(clusterState.getClusterName().value(), numberOfPendingTasks, + numberOfInFlightFetch, UnassignedInfo.getNumberOfDelayedUnassigned(clusterState), pendingTaskTimeInQueue, + false, stateHealth); // one of the specified indices is not there - treat it as RED. - ClusterHealthResponse response = new ClusterHealthResponse(clusterState.getClusterName().value(), Strings.EMPTY_ARRAY, clusterState, - numberOfPendingTasks, numberOfInFlightFetch, UnassignedInfo.getNumberOfDelayedUnassigned(clusterState), - pendingTaskTimeInQueue); response.setStatus(ClusterHealthStatus.RED); return response; } + ClusterStateHealth stateHealth = calculateStateHealth(clusterState, concreteIndices); + return new ClusterHealthResponse(clusterState.getClusterName().value(), numberOfPendingTasks, numberOfInFlightFetch, + UnassignedInfo.getNumberOfDelayedUnassigned(clusterState), pendingTaskTimeInQueue, false, stateHealth); + } + + /** + * Creates a new ClusterStateHealth instance considering the current cluster state and all indices in the cluster. + * + * @param clusterState The current cluster state. Must not be null. + */ + public static ClusterStateHealth calculateStateHealth(final ClusterState clusterState) { + return calculateStateHealth(clusterState, clusterState.metaData().getConcreteAllIndices()); + } + + /** + * Creates a new ClusterStateHealth instance considering the current cluster state and the provided index names. + * + * @param clusterState The current cluster state. Must not be null. + * @param concreteIndices An array of index names to consider. Must not be null but may be empty. + */ + public static ClusterStateHealth calculateStateHealth(final ClusterState clusterState, final String[] concreteIndices) { + int numberOfNodes = clusterState.nodes().getSize(); + int numberOfDataNodes = clusterState.nodes().getDataNodes().size(); + Map indices = new HashMap<>(); + for (String index : concreteIndices) { + IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(index); + IndexMetaData indexMetaData = clusterState.metaData().index(index); + if (indexRoutingTable == null) { + continue; + } + ClusterIndexHealth indexHealth = calculateIndexHealth(indexMetaData, indexRoutingTable); + indices.put(indexHealth.getIndex(), indexHealth); + } + ClusterHealthStatus computeStatus = ClusterHealthStatus.GREEN; + int computeActivePrimaryShards = 0; + int computeActiveShards = 0; + int computeRelocatingShards = 0; + int computeInitializingShards = 0; + int computeUnassignedShards = 0; + + for (ClusterIndexHealth indexHealth : indices.values()) { + computeActivePrimaryShards += indexHealth.getActivePrimaryShards(); + computeActiveShards += indexHealth.getActiveShards(); + computeRelocatingShards += indexHealth.getRelocatingShards(); + computeInitializingShards += indexHealth.getInitializingShards(); + computeUnassignedShards += indexHealth.getUnassignedShards(); + if (indexHealth.getStatus() == ClusterHealthStatus.RED) { + computeStatus = ClusterHealthStatus.RED; + } else if (indexHealth.getStatus() == ClusterHealthStatus.YELLOW && computeStatus != ClusterHealthStatus.RED) { + computeStatus = ClusterHealthStatus.YELLOW; + } + } + if (clusterState.blocks().hasGlobalBlock(RestStatus.SERVICE_UNAVAILABLE)) { + computeStatus = ClusterHealthStatus.RED; + } + double activeShardsPercent; + // shortcut on green + if (computeStatus.equals(ClusterHealthStatus.GREEN)) { + activeShardsPercent = 100; + } else { + List shardRoutings = clusterState.getRoutingTable().allShards(); + int activeShardCount = 0; + int totalShardCount = 0; + for (ShardRouting shardRouting : shardRoutings) { + if (shardRouting.active()) activeShardCount++; + totalShardCount++; + } + activeShardsPercent = (((double) activeShardCount) / totalShardCount) * 100; + } + return new ClusterStateHealth(computeActivePrimaryShards, computeActiveShards, computeRelocatingShards, + computeInitializingShards, computeUnassignedShards, numberOfNodes, numberOfDataNodes, activeShardsPercent, + computeStatus, indices); + } - return new ClusterHealthResponse(clusterState.getClusterName().value(), concreteIndices, clusterState, numberOfPendingTasks, - numberOfInFlightFetch, UnassignedInfo.getNumberOfDelayedUnassigned(clusterState), pendingTaskTimeInQueue); + public static ClusterIndexHealth calculateIndexHealth(final IndexMetaData indexMetaData, final IndexRoutingTable indexRoutingTable) { + String index = indexMetaData.getIndex().getName(); + int numberOfShards = indexMetaData.getNumberOfShards(); + int numberOfReplicas = indexMetaData.getNumberOfReplicas(); + + Map shards = new HashMap<>(); + for (IndexShardRoutingTable shardRoutingTable : indexRoutingTable) { + int shardId = shardRoutingTable.shardId().id(); + shards.put(shardId, calculateShardHealth(shardId, shardRoutingTable)); + } + + // update the index status + ClusterHealthStatus computeStatus = ClusterHealthStatus.GREEN; + int computeActivePrimaryShards = 0; + int computeActiveShards = 0; + int computeRelocatingShards = 0; + int computeInitializingShards = 0; + int computeUnassignedShards = 0; + for (ClusterShardHealth shardHealth : shards.values()) { + if (shardHealth.isPrimaryActive()) { + computeActivePrimaryShards++; + } + computeActiveShards += shardHealth.getActiveShards(); + computeRelocatingShards += shardHealth.getRelocatingShards(); + computeInitializingShards += shardHealth.getInitializingShards(); + computeUnassignedShards += shardHealth.getUnassignedShards(); + + if (shardHealth.getStatus() == ClusterHealthStatus.RED) { + computeStatus = ClusterHealthStatus.RED; + } else if (shardHealth.getStatus() == ClusterHealthStatus.YELLOW && computeStatus != ClusterHealthStatus.RED) { + // do not override an existing red + computeStatus = ClusterHealthStatus.YELLOW; + } + } + if (shards.isEmpty()) { // might be since none has been created yet (two phase index creation) + computeStatus = ClusterHealthStatus.RED; + } + return new ClusterIndexHealth(index, numberOfShards, numberOfReplicas, computeActiveShards, computeRelocatingShards, + computeInitializingShards, computeUnassignedShards, computeActivePrimaryShards, computeStatus, shards); + } + + public static ClusterShardHealth calculateShardHealth(final int shardId, final IndexShardRoutingTable shardRoutingTable) { + int computeActiveShards = 0; + int computeRelocatingShards = 0; + int computeInitializingShards = 0; + int computeUnassignedShards = 0; + for (ShardRouting shardRouting : shardRoutingTable) { + if (shardRouting.active()) { + computeActiveShards++; + if (shardRouting.relocating()) { + // the shard is relocating, the one it is relocating to will be in initializing state, so we don't count it + computeRelocatingShards++; + } + } else if (shardRouting.initializing()) { + computeInitializingShards++; + } else if (shardRouting.unassigned()) { + computeUnassignedShards++; + } + } + ClusterHealthStatus computeStatus; + final ShardRouting primaryRouting = shardRoutingTable.primaryShard(); + if (primaryRouting.active()) { + if (computeActiveShards == shardRoutingTable.size()) { + computeStatus = ClusterHealthStatus.GREEN; + } else { + computeStatus = ClusterHealthStatus.YELLOW; + } + } else { + computeStatus = getInactivePrimaryHealth(primaryRouting); + } + return new ClusterShardHealth(shardId, computeStatus, computeActiveShards, computeRelocatingShards, computeInitializingShards, + computeUnassignedShards, primaryRouting.active()); + } + + /** + * Checks if an inactive primary shard should cause the cluster health to go RED. + * + * An inactive primary shard in an index should cause the cluster health to be RED to make it visible that some of the existing data is + * unavailable. In case of index creation, snapshot restore or index shrinking, which are unexceptional events in the cluster lifecycle, + * cluster health should not turn RED for the time where primaries are still in the initializing state but go to YELLOW instead. + * However, in case of exceptional events, for example when the primary shard cannot be assigned to a node or initialization fails at + * some point, cluster health should still turn RED. + * + * NB: this method should *not* be called on active shards nor on non-primary shards. + */ + public static ClusterHealthStatus getInactivePrimaryHealth(final ShardRouting shardRouting) { + assert shardRouting.primary() : "cannot invoke on a replica shard: " + shardRouting; + assert shardRouting.active() == false : "cannot invoke on an active shard: " + shardRouting; + assert shardRouting.unassignedInfo() != null : "cannot invoke on a shard with no UnassignedInfo: " + shardRouting; + assert shardRouting.recoverySource() != null : "cannot invoke on a shard that has no recovery source" + shardRouting; + final UnassignedInfo unassignedInfo = shardRouting.unassignedInfo(); + RecoverySource.Type recoveryType = shardRouting.recoverySource().getType(); + if (unassignedInfo.getLastAllocationStatus() != AllocationStatus.DECIDERS_NO && unassignedInfo.getNumFailedAllocations() == 0 + && (recoveryType == RecoverySource.Type.EMPTY_STORE + || recoveryType == RecoverySource.Type.LOCAL_SHARDS + || recoveryType == RecoverySource.Type.SNAPSHOT)) { + return ClusterHealthStatus.YELLOW; + } else { + return ClusterHealthStatus.RED; + } } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java index c87b55b0bbd7d..146ad61d1ce14 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java @@ -20,6 +20,7 @@ package org.elasticsearch.action.admin.cluster.stats; import org.elasticsearch.action.FailedNodeException; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; import org.elasticsearch.action.admin.indices.stats.CommonStats; @@ -29,7 +30,6 @@ import org.elasticsearch.action.support.nodes.BaseNodeRequest; import org.elasticsearch.action.support.nodes.TransportNodesAction; import org.elasticsearch.cluster.health.ClusterHealthStatus; -import org.elasticsearch.cluster.health.ClusterStateHealth; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; @@ -112,7 +112,7 @@ protected ClusterStatsNodeResponse nodeOperation(ClusterStatsNodeRequest nodeReq ClusterHealthStatus clusterStatus = null; if (clusterService.state().nodes().isLocalNodeElectedMaster()) { - clusterStatus = new ClusterStateHealth(clusterService.state()).getStatus(); + clusterStatus = TransportClusterHealthAction.calculateStateHealth(clusterService.state()).getStatus(); } return new ClusterStatsNodeResponse(nodeInfo.getNode(), clusterStatus, nodeInfo, nodeStats, shardsStats.toArray(new ShardStats[shardsStats.size()])); diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java index 0741965f5e5c9..1945c0ced7236 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java @@ -22,6 +22,7 @@ import org.apache.lucene.util.CollectionUtil; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.FailedNodeException; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.TransportMasterNodeReadAction; import org.elasticsearch.cluster.ClusterState; @@ -99,7 +100,7 @@ protected void masterOperation(IndicesShardStoresRequest request, ClusterState s } for (IndexShardRoutingTable routing : indexShardRoutingTables) { final int shardId = routing.shardId().id(); - ClusterShardHealth shardHealth = new ClusterShardHealth(shardId, routing); + ClusterShardHealth shardHealth = TransportClusterHealthAction.calculateShardHealth(shardId, routing); if (request.shardStatuses().contains(shardHealth.getStatus())) { shardIdsToFetch.add(routing.shardId()); } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java index 6ee35ef07734e..9d88158c62c0e 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java @@ -19,10 +19,8 @@ package org.elasticsearch.cluster.health; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.cluster.routing.IndexRoutingTable; -import org.elasticsearch.cluster.routing.IndexShardRoutingTable; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; @@ -43,9 +41,9 @@ import static java.util.Collections.emptyMap; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; public final class ClusterIndexHealth implements Iterable, Writeable, ToXContentFragment { - private static final String INDEX = "index"; private static final String STATUS = "status"; private static final String NUMBER_OF_SHARDS = "number_of_shards"; private static final String NUMBER_OF_REPLICAS = "number_of_replicas"; @@ -56,39 +54,37 @@ public final class ClusterIndexHealth implements Iterable, W private static final String UNASSIGNED_SHARDS = "unassigned_shards"; private static final String SHARDS = "shards"; - private static final ConstructingObjectParser PARSER = - new ConstructingObjectParser<>("cluster_index_health", true, - a -> { - int i = 0; - String index = (String) a[i++]; - int numberOfShards = (int) a[i++]; - int numberOfReplicas = (int) a[i++]; - int activeShards = (int) a[i++]; - int relocatingShards = (int) a[i++]; - int initializingShards = (int) a[i++]; - int unassignedShards = (int) a[i++]; - int activePrimaryShards = (int) a[i++]; - String statusStr = (String) a[i++]; - ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); - List shardList = (List) a[i++]; - final Map shards; - if (shardList == null || shardList.isEmpty()) { - shards = emptyMap(); - } else { - shards = new HashMap<>(shardList.size()); - for (ClusterShardHealth shardHealth : shardList) { - shards.put(shardHealth.getShardId(), shardHealth); - } - } - return new ClusterIndexHealth(index, numberOfShards, numberOfReplicas, activeShards, relocatingShards, initializingShards, - unassignedShards, activePrimaryShards, status, shards); - }); - - public static final ObjectParser.NamedObjectParser SHARD_PARSER = - (XContentParser p, Void c, String nameIgnored) -> ClusterShardHealth.fromXContent(p); + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("cluster_index_health", true, + (parsedObjects, index) -> { + int i = 0; + int numberOfShards = (int) parsedObjects[i++]; + int numberOfReplicas = (int) parsedObjects[i++]; + int activeShards = (int) parsedObjects[i++]; + int relocatingShards = (int) parsedObjects[i++]; + int initializingShards = (int) parsedObjects[i++]; + int unassignedShards = (int) parsedObjects[i++]; + int activePrimaryShards = (int) parsedObjects[i++]; + String statusStr = (String) parsedObjects[i++]; + ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); + @SuppressWarnings("unchecked") List shardList = (List) parsedObjects[i]; + final Map shards; + if (shardList == null || shardList.isEmpty()) { + shards = emptyMap(); + } else { + shards = new HashMap<>(shardList.size()); + for (ClusterShardHealth shardHealth : shardList) { + shards.put(shardHealth.getShardId(), shardHealth); + } + } + return new ClusterIndexHealth(index, numberOfShards, numberOfReplicas, activeShards, relocatingShards, + initializingShards, unassignedShards, activePrimaryShards, status, shards); + }); + + public static final ObjectParser.NamedObjectParser SHARD_PARSER = + (XContentParser p, String indexIgnored, String shardId) -> ClusterShardHealth.innerFromXContent(p, Integer.valueOf(shardId)); static { - PARSER.declareString(constructorArg(), new ParseField(INDEX)); PARSER.declareInt(constructorArg(), new ParseField(NUMBER_OF_SHARDS)); PARSER.declareInt(constructorArg(), new ParseField(NUMBER_OF_REPLICAS)); PARSER.declareInt(constructorArg(), new ParseField(ACTIVE_SHARDS)); @@ -112,50 +108,19 @@ public final class ClusterIndexHealth implements Iterable, W private final ClusterHealthStatus status; private final Map shards; - public ClusterIndexHealth(final IndexMetaData indexMetaData, final IndexRoutingTable indexRoutingTable) { - this.index = indexMetaData.getIndex().getName(); - this.numberOfShards = indexMetaData.getNumberOfShards(); - this.numberOfReplicas = indexMetaData.getNumberOfReplicas(); - - shards = new HashMap<>(); - for (IndexShardRoutingTable shardRoutingTable : indexRoutingTable) { - int shardId = shardRoutingTable.shardId().id(); - shards.put(shardId, new ClusterShardHealth(shardId, shardRoutingTable)); - } - - // update the index status - ClusterHealthStatus computeStatus = ClusterHealthStatus.GREEN; - int computeActivePrimaryShards = 0; - int computeActiveShards = 0; - int computeRelocatingShards = 0; - int computeInitializingShards = 0; - int computeUnassignedShards = 0; - for (ClusterShardHealth shardHealth : shards.values()) { - if (shardHealth.isPrimaryActive()) { - computeActivePrimaryShards++; - } - computeActiveShards += shardHealth.getActiveShards(); - computeRelocatingShards += shardHealth.getRelocatingShards(); - computeInitializingShards += shardHealth.getInitializingShards(); - computeUnassignedShards += shardHealth.getUnassignedShards(); - - if (shardHealth.getStatus() == ClusterHealthStatus.RED) { - computeStatus = ClusterHealthStatus.RED; - } else if (shardHealth.getStatus() == ClusterHealthStatus.YELLOW && computeStatus != ClusterHealthStatus.RED) { - // do not override an existing red - computeStatus = ClusterHealthStatus.YELLOW; - } - } - if (shards.isEmpty()) { // might be since none has been created yet (two phase index creation) - computeStatus = ClusterHealthStatus.RED; - } - - this.status = computeStatus; - this.activePrimaryShards = computeActivePrimaryShards; - this.activeShards = computeActiveShards; - this.relocatingShards = computeRelocatingShards; - this.initializingShards = computeInitializingShards; - this.unassignedShards = computeUnassignedShards; + public ClusterIndexHealth(String index, int numberOfShards, int numberOfReplicas, int activeShards, int relocatingShards, + int initializingShards, int unassignedShards, int activePrimaryShards, ClusterHealthStatus status, + Map shards) { + this.index = index; + this.numberOfShards = numberOfShards; + this.numberOfReplicas = numberOfReplicas; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.activePrimaryShards = activePrimaryShards; + this.status = status; + this.shards = shards; } public ClusterIndexHealth(final StreamInput in) throws IOException { @@ -177,24 +142,6 @@ public ClusterIndexHealth(final StreamInput in) throws IOException { } } - /** - * For XContent Parser and serialization tests - */ - ClusterIndexHealth(String index, int numberOfShards, int numberOfReplicas, int activeShards, int relocatingShards, - int initializingShards, int unassignedShards, int activePrimaryShards, ClusterHealthStatus status, - Map shards) { - this.index = index; - this.numberOfShards = numberOfShards; - this.numberOfReplicas = numberOfReplicas; - this.activeShards = activeShards; - this.relocatingShards = relocatingShards; - this.initializingShards = initializingShards; - this.unassignedShards = unassignedShards; - this.activePrimaryShards = activePrimaryShards; - this.status = status; - this.shards = shards; - } - public String getIndex() { return index; } @@ -260,7 +207,7 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { - builder.field(INDEX, getIndex()); + builder.startObject(getIndex()); builder.field(STATUS, getStatus().name().toLowerCase(Locale.ROOT)); builder.field(NUMBER_OF_SHARDS, getNumberOfShards()); builder.field(NUMBER_OF_REPLICAS, getNumberOfReplicas()); @@ -273,33 +220,46 @@ public XContentBuilder toXContent(final XContentBuilder builder, final Params pa if ("shards".equals(params.param("level", "indices"))) { builder.startObject(SHARDS); for (ClusterShardHealth shardHealth : shards.values()) { - builder.startObject(Integer.toString(shardHealth.getShardId())); shardHealth.toXContent(builder, params); - builder.endObject(); } builder.endObject(); } + builder.endObject(); return builder; } + public static ClusterIndexHealth innerFromXContent(XContentParser parser, String index) { + return PARSER.apply(parser, index); + } + public static ClusterIndexHealth fromXContent(XContentParser parser) { - return PARSER.apply(parser, null); + try { + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + XContentParser.Token token = parser.nextToken(); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + String index = parser.currentName(); + ClusterIndexHealth parsed = innerFromXContent(parser, index); + ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation); + return parsed; + } catch (IOException e) { + throw new ParsingException(parser.getTokenLocation(), "[ClusterIndexHealth::fromXContent] failed to parse object", e); + } } @Override public String toString() { return "ClusterIndexHealth{" + - "index='" + index + '\'' + - ", numberOfShards=" + numberOfShards + - ", numberOfReplicas=" + numberOfReplicas + - ", activeShards=" + activeShards + - ", relocatingShards=" + relocatingShards + - ", initializingShards=" + initializingShards + - ", unassignedShards=" + unassignedShards + - ", activePrimaryShards=" + activePrimaryShards + - ", status=" + status + - ", shards.size=" + (shards == null ? "null" : shards.size()) + - '}'; + "index='" + index + '\'' + + ", numberOfShards=" + numberOfShards + + ", numberOfReplicas=" + numberOfReplicas + + ", activeShards=" + activeShards + + ", relocatingShards=" + relocatingShards + + ", initializingShards=" + initializingShards + + ", unassignedShards=" + unassignedShards + + ", activePrimaryShards=" + activePrimaryShards + + ", status=" + status + + ", shards.size=" + (shards == null ? "null" : shards.size()) + + '}'; } @Override @@ -308,20 +268,20 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; ClusterIndexHealth that = (ClusterIndexHealth) o; return Objects.equals(index, that.index) && - numberOfShards == that.numberOfShards && - numberOfReplicas == that.numberOfReplicas && - activeShards == that.activeShards && - relocatingShards == that.relocatingShards && - initializingShards == that.initializingShards && - unassignedShards == that.unassignedShards && - activePrimaryShards == that.activePrimaryShards && - status == that.status && - Objects.equals(shards, that.shards); + numberOfShards == that.numberOfShards && + numberOfReplicas == that.numberOfReplicas && + activeShards == that.activeShards && + relocatingShards == that.relocatingShards && + initializingShards == that.initializingShards && + unassignedShards == that.unassignedShards && + activePrimaryShards == that.activePrimaryShards && + status == that.status && + Objects.equals(shards, that.shards); } @Override public int hashCode() { return Objects.hash(index, numberOfShards, numberOfReplicas, activeShards, relocatingShards, initializingShards, unassignedShards, - activePrimaryShards, status, shards); + activePrimaryShards, status, shards); } } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java index 79ae1de2ab0d4..ee68ab80778d5 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java @@ -19,12 +19,8 @@ package org.elasticsearch.cluster.health; -import org.elasticsearch.cluster.routing.IndexShardRoutingTable; -import org.elasticsearch.cluster.routing.RecoverySource; -import org.elasticsearch.cluster.routing.ShardRouting; -import org.elasticsearch.cluster.routing.UnassignedInfo; -import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -39,9 +35,9 @@ import java.util.Objects; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; public final class ClusterShardHealth implements Writeable, ToXContentFragment { - private static final String SHARD_ID = "shard_id"; private static final String STATUS = "status"; private static final String ACTIVE_SHARDS = "active_shards"; private static final String RELOCATING_SHARDS = "relocating_shards"; @@ -51,22 +47,21 @@ public final class ClusterShardHealth implements Writeable, ToXContentFragment { public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("cluster_shard_health", true, - a -> { - int i = 0; - int shardId = (int) a[i++]; - boolean primaryActive = (boolean) a[i++]; - int activeShards = (int) a[i++]; - int relocatingShards = (int) a[i++]; - int initializingShards = (int) a[i++]; - int unassignedShards = (int) a[i++]; - String statusStr = (String) a[i++]; - ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); - return new ClusterShardHealth(shardId, status, activeShards, relocatingShards, initializingShards, unassignedShards, - primaryActive); - }); + (parsedObjects, shardId) -> { + int i = 0; + boolean primaryActive = (boolean) parsedObjects[i++]; + int activeShards = (int) parsedObjects[i++]; + int relocatingShards = (int) parsedObjects[i++]; + int initializingShards = (int) parsedObjects[i++]; + int unassignedShards = (int) parsedObjects[i++]; + String statusStr = (String) parsedObjects[i]; + ClusterHealthStatus status = ClusterHealthStatus.fromString(statusStr); + return new ClusterShardHealth(shardId, status, activeShards, relocatingShards, initializingShards, unassignedShards, + primaryActive); + }); static { - PARSER.declareInt(constructorArg(), new ParseField(SHARD_ID)); +// PARSER.declareInt(constructorArg(), new ParseField(SHARD_ID)); PARSER.declareBoolean(constructorArg(), new ParseField(PRIMARY_ACTIVE)); PARSER.declareInt(constructorArg(), new ParseField(ACTIVE_SHARDS)); PARSER.declareInt(constructorArg(), new ParseField(RELOCATING_SHARDS)); @@ -83,42 +78,15 @@ public final class ClusterShardHealth implements Writeable, ToXContentFragment { private final int unassignedShards; private final boolean primaryActive; - public ClusterShardHealth(final int shardId, final IndexShardRoutingTable shardRoutingTable) { + public ClusterShardHealth(int shardId, ClusterHealthStatus status, int activeShards, int relocatingShards, int initializingShards, + int unassignedShards, boolean primaryActive) { this.shardId = shardId; - int computeActiveShards = 0; - int computeRelocatingShards = 0; - int computeInitializingShards = 0; - int computeUnassignedShards = 0; - for (ShardRouting shardRouting : shardRoutingTable) { - if (shardRouting.active()) { - computeActiveShards++; - if (shardRouting.relocating()) { - // the shard is relocating, the one it is relocating to will be in initializing state, so we don't count it - computeRelocatingShards++; - } - } else if (shardRouting.initializing()) { - computeInitializingShards++; - } else if (shardRouting.unassigned()) { - computeUnassignedShards++; - } - } - ClusterHealthStatus computeStatus; - final ShardRouting primaryRouting = shardRoutingTable.primaryShard(); - if (primaryRouting.active()) { - if (computeActiveShards == shardRoutingTable.size()) { - computeStatus = ClusterHealthStatus.GREEN; - } else { - computeStatus = ClusterHealthStatus.YELLOW; - } - } else { - computeStatus = getInactivePrimaryHealth(primaryRouting); - } - this.status = computeStatus; - this.activeShards = computeActiveShards; - this.relocatingShards = computeRelocatingShards; - this.initializingShards = computeInitializingShards; - this.unassignedShards = computeUnassignedShards; - this.primaryActive = primaryRouting.active(); + this.status = status; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.primaryActive = primaryActive; } public ClusterShardHealth(final StreamInput in) throws IOException { @@ -131,20 +99,6 @@ public ClusterShardHealth(final StreamInput in) throws IOException { primaryActive = in.readBoolean(); } - /** - * For XContent Parser and serialization tests - */ - ClusterShardHealth(int shardId, ClusterHealthStatus status, int activeShards, int relocatingShards, int initializingShards, - int unassignedShards, boolean primaryActive) { - this.shardId = shardId; - this.status = status; - this.activeShards = activeShards; - this.relocatingShards = relocatingShards; - this.initializingShards = initializingShards; - this.unassignedShards = unassignedShards; - this.primaryActive = primaryActive; - } - public int getShardId() { return shardId; } @@ -184,48 +138,35 @@ public void writeTo(final StreamOutput out) throws IOException { out.writeBoolean(primaryActive); } - /** - * Checks if an inactive primary shard should cause the cluster health to go RED. - * - * An inactive primary shard in an index should cause the cluster health to be RED to make it visible that some of the existing data is - * unavailable. In case of index creation, snapshot restore or index shrinking, which are unexceptional events in the cluster lifecycle, - * cluster health should not turn RED for the time where primaries are still in the initializing state but go to YELLOW instead. - * However, in case of exceptional events, for example when the primary shard cannot be assigned to a node or initialization fails at - * some point, cluster health should still turn RED. - * - * NB: this method should *not* be called on active shards nor on non-primary shards. - */ - public static ClusterHealthStatus getInactivePrimaryHealth(final ShardRouting shardRouting) { - assert shardRouting.primary() : "cannot invoke on a replica shard: " + shardRouting; - assert shardRouting.active() == false : "cannot invoke on an active shard: " + shardRouting; - assert shardRouting.unassignedInfo() != null : "cannot invoke on a shard with no UnassignedInfo: " + shardRouting; - assert shardRouting.recoverySource() != null : "cannot invoke on a shard that has no recovery source" + shardRouting; - final UnassignedInfo unassignedInfo = shardRouting.unassignedInfo(); - RecoverySource.Type recoveryType = shardRouting.recoverySource().getType(); - if (unassignedInfo.getLastAllocationStatus() != AllocationStatus.DECIDERS_NO && unassignedInfo.getNumFailedAllocations() == 0 - && (recoveryType == RecoverySource.Type.EMPTY_STORE - || recoveryType == RecoverySource.Type.LOCAL_SHARDS - || recoveryType == RecoverySource.Type.SNAPSHOT)) { - return ClusterHealthStatus.YELLOW; - } else { - return ClusterHealthStatus.RED; - } - } - @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.field(SHARD_ID, getShardId()); + builder.startObject(Integer.toString(getShardId())); builder.field(STATUS, getStatus().name().toLowerCase(Locale.ROOT)); builder.field(PRIMARY_ACTIVE, isPrimaryActive()); builder.field(ACTIVE_SHARDS, getActiveShards()); builder.field(RELOCATING_SHARDS, getRelocatingShards()); builder.field(INITIALIZING_SHARDS, getInitializingShards()); builder.field(UNASSIGNED_SHARDS, getUnassignedShards()); + builder.endObject(); return builder; } + static ClusterShardHealth innerFromXContent(XContentParser parser, Integer shardId) { + return PARSER.apply(parser, shardId); + } + public static ClusterShardHealth fromXContent(XContentParser parser) { - return PARSER.apply(parser, null); + try { + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + XContentParser.Token token = parser.nextToken(); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + String shardIdStr = parser.currentName(); + ClusterShardHealth parsed = innerFromXContent(parser, Integer.valueOf(shardIdStr)); + ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation); + return parsed; + } catch (IOException e) { + throw new ParsingException(parser.getTokenLocation(), "[ClusterShardHealth::fromXContent] failed to parse object", e); + } } @Override @@ -239,12 +180,12 @@ public boolean equals(Object o) { if (!(o instanceof ClusterShardHealth)) return false; ClusterShardHealth that = (ClusterShardHealth) o; return shardId == that.shardId && - activeShards == that.activeShards && - relocatingShards == that.relocatingShards && - initializingShards == that.initializingShards && - unassignedShards == that.unassignedShards && - primaryActive == that.primaryActive && - status == that.status; + activeShards == that.activeShards && + relocatingShards == that.relocatingShards && + initializingShards == that.initializingShards && + unassignedShards == that.unassignedShards && + primaryActive == that.primaryActive && + status == that.status; } @Override diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java index 99dd41f63d815..096b515a12585 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java @@ -18,20 +18,14 @@ */ package org.elasticsearch.cluster.health; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.cluster.routing.IndexRoutingTable; -import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.rest.RestStatus; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Objects; @@ -49,81 +43,19 @@ public final class ClusterStateHealth implements Iterable, W private final ClusterHealthStatus status; private final Map indices; - /** - * Creates a new ClusterStateHealth instance considering the current cluster state and all indices in the cluster. - * - * @param clusterState The current cluster state. Must not be null. - */ - public ClusterStateHealth(final ClusterState clusterState) { - this(clusterState, clusterState.metaData().getConcreteAllIndices()); - } - - /** - * Creates a new ClusterStateHealth instance considering the current cluster state and the provided index names. - * - * @param clusterState The current cluster state. Must not be null. - * @param concreteIndices An array of index names to consider. Must not be null but may be empty. - */ - public ClusterStateHealth(final ClusterState clusterState, final String[] concreteIndices) { - numberOfNodes = clusterState.nodes().getSize(); - numberOfDataNodes = clusterState.nodes().getDataNodes().size(); - indices = new HashMap<>(); - for (String index : concreteIndices) { - IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(index); - IndexMetaData indexMetaData = clusterState.metaData().index(index); - if (indexRoutingTable == null) { - continue; - } - - ClusterIndexHealth indexHealth = new ClusterIndexHealth(indexMetaData, indexRoutingTable); - - indices.put(indexHealth.getIndex(), indexHealth); - } - - ClusterHealthStatus computeStatus = ClusterHealthStatus.GREEN; - int computeActivePrimaryShards = 0; - int computeActiveShards = 0; - int computeRelocatingShards = 0; - int computeInitializingShards = 0; - int computeUnassignedShards = 0; - - for (ClusterIndexHealth indexHealth : indices.values()) { - computeActivePrimaryShards += indexHealth.getActivePrimaryShards(); - computeActiveShards += indexHealth.getActiveShards(); - computeRelocatingShards += indexHealth.getRelocatingShards(); - computeInitializingShards += indexHealth.getInitializingShards(); - computeUnassignedShards += indexHealth.getUnassignedShards(); - if (indexHealth.getStatus() == ClusterHealthStatus.RED) { - computeStatus = ClusterHealthStatus.RED; - } else if (indexHealth.getStatus() == ClusterHealthStatus.YELLOW && computeStatus != ClusterHealthStatus.RED) { - computeStatus = ClusterHealthStatus.YELLOW; - } - } - - if (clusterState.blocks().hasGlobalBlock(RestStatus.SERVICE_UNAVAILABLE)) { - computeStatus = ClusterHealthStatus.RED; - } - - this.status = computeStatus; - this.activePrimaryShards = computeActivePrimaryShards; - this.activeShards = computeActiveShards; - this.relocatingShards = computeRelocatingShards; - this.initializingShards = computeInitializingShards; - this.unassignedShards = computeUnassignedShards; - - // shortcut on green - if (computeStatus.equals(ClusterHealthStatus.GREEN)) { - this.activeShardsPercent = 100; - } else { - List shardRoutings = clusterState.getRoutingTable().allShards(); - int activeShardCount = 0; - int totalShardCount = 0; - for (ShardRouting shardRouting : shardRoutings) { - if (shardRouting.active()) activeShardCount++; - totalShardCount++; - } - this.activeShardsPercent = (((double) activeShardCount) / totalShardCount) * 100; - } + public ClusterStateHealth(int activePrimaryShards, int activeShards, int relocatingShards, int initializingShards, int unassignedShards, + int numberOfNodes, int numberOfDataNodes, double activeShardsPercent, ClusterHealthStatus status, + Map indices) { + this.activePrimaryShards = activePrimaryShards; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.numberOfNodes = numberOfNodes; + this.numberOfDataNodes = numberOfDataNodes; + this.activeShardsPercent = activeShardsPercent; + this.status = status; + this.indices = indices; } public ClusterStateHealth(final StreamInput in) throws IOException { @@ -144,24 +76,6 @@ public ClusterStateHealth(final StreamInput in) throws IOException { activeShardsPercent = in.readDouble(); } - /** - * For ClusterHealthResponse's XContent Parser - */ - public ClusterStateHealth(int activePrimaryShards, int activeShards, int relocatingShards, int initializingShards, int unassignedShards, - int numberOfNodes, int numberOfDataNodes, double activeShardsPercent, ClusterHealthStatus status, - Map indices) { - this.activePrimaryShards = activePrimaryShards; - this.activeShards = activeShards; - this.relocatingShards = relocatingShards; - this.initializingShards = initializingShards; - this.unassignedShards = unassignedShards; - this.numberOfNodes = numberOfNodes; - this.numberOfDataNodes = numberOfDataNodes; - this.activeShardsPercent = activeShardsPercent; - this.status = status; - this.indices = indices; - } - public int getActiveShards() { return activeShards; } @@ -227,17 +141,17 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public String toString() { return "ClusterStateHealth{" + - "numberOfNodes=" + numberOfNodes + - ", numberOfDataNodes=" + numberOfDataNodes + - ", activeShards=" + activeShards + - ", relocatingShards=" + relocatingShards + - ", activePrimaryShards=" + activePrimaryShards + - ", initializingShards=" + initializingShards + - ", unassignedShards=" + unassignedShards + - ", activeShardsPercent=" + activeShardsPercent + - ", status=" + status + - ", indices.size=" + (indices == null ? "null" : indices.size()) + - '}'; + "numberOfNodes=" + numberOfNodes + + ", numberOfDataNodes=" + numberOfDataNodes + + ", activeShards=" + activeShards + + ", relocatingShards=" + relocatingShards + + ", activePrimaryShards=" + activePrimaryShards + + ", initializingShards=" + initializingShards + + ", unassignedShards=" + unassignedShards + + ", activeShardsPercent=" + activeShardsPercent + + ", status=" + status + + ", indices.size=" + (indices == null ? "null" : indices.size()) + + '}'; } @Override @@ -246,20 +160,20 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; ClusterStateHealth that = (ClusterStateHealth) o; return numberOfNodes == that.numberOfNodes && - numberOfDataNodes == that.numberOfDataNodes && - activeShards == that.activeShards && - relocatingShards == that.relocatingShards && - activePrimaryShards == that.activePrimaryShards && - initializingShards == that.initializingShards && - unassignedShards == that.unassignedShards && - Double.compare(that.activeShardsPercent, activeShardsPercent) == 0 && - status == that.status && - Objects.equals(indices, that.indices); + numberOfDataNodes == that.numberOfDataNodes && + activeShards == that.activeShards && + relocatingShards == that.relocatingShards && + activePrimaryShards == that.activePrimaryShards && + initializingShards == that.initializingShards && + unassignedShards == that.unassignedShards && + Double.compare(that.activeShardsPercent, activeShardsPercent) == 0 && + status == that.status && + Objects.equals(indices, that.indices); } @Override public int hashCode() { return Objects.hash(numberOfNodes, numberOfDataNodes, activeShards, relocatingShards, activePrimaryShards, initializingShards, - unassignedShards, activeShardsPercent, status, indices); + unassignedShards, activeShardsPercent, status, indices); } } diff --git a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java index e6032c52585ec..1adf3188b0d34 100644 --- a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java +++ b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java @@ -20,6 +20,7 @@ package org.elasticsearch.cluster.routing.allocation; import org.apache.logging.log4j.message.ParameterizedMessage; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.ClusterInfoService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.RestoreInProgress; @@ -131,9 +132,9 @@ protected ClusterState buildResultAndLogHealthChange(ClusterState oldState, Rout } final ClusterState newState = newStateBuilder.build(); logClusterHealthStateChange( - new ClusterStateHealth(oldState), - new ClusterStateHealth(newState), - reason + TransportClusterHealthAction.calculateStateHealth(oldState), + TransportClusterHealthAction.calculateStateHealth(newState), + reason ); return newState; } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java index 9fa1c9940e99e..fd410c2a158f6 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java @@ -25,7 +25,6 @@ import org.elasticsearch.cluster.health.ClusterIndexHealth; import org.elasticsearch.cluster.health.ClusterIndexHealthTests; import org.elasticsearch.cluster.health.ClusterStateHealth; -import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; @@ -37,6 +36,7 @@ import org.hamcrest.Matchers; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; @@ -48,6 +48,7 @@ import static org.hamcrest.Matchers.lessThanOrEqualTo; public class ClusterHealthResponsesTests extends AbstractStreamableXContentTestCase { + private final String level = randomFrom("shards", "indices", "cluster"); public void testIsTimeout() { ClusterHealthResponse responseTimeout = new ClusterHealthResponse(); @@ -67,7 +68,9 @@ public void testClusterHealth() { int inFlight = randomIntBetween(0, 200); int delayedUnassigned = randomIntBetween(0, 200); TimeValue pendingTaskInQueueTime = TimeValue.timeValueMillis(randomIntBetween(1000, 100000)); - ClusterHealthResponse clusterHealth = new ClusterHealthResponse("bla", new String[] {MetaData.ALL}, clusterState, pendingTasks, inFlight, delayedUnassigned, pendingTaskInQueueTime); + ClusterStateHealth stateHealth = TransportClusterHealthAction.calculateStateHealth(clusterState); + ClusterHealthResponse clusterHealth = new ClusterHealthResponse("bla", pendingTasks, inFlight, + delayedUnassigned, pendingTaskInQueueTime, false, stateHealth); clusterHealth = maybeSerialize(clusterHealth); assertClusterHealth(clusterHealth); assertThat(clusterHealth.getNumberOfPendingTasks(), Matchers.equalTo(pendingTasks)); @@ -117,32 +120,86 @@ protected ClusterHealthResponse createBlankInstance() { protected ClusterHealthResponse createTestInstance() { int indicesSize = randomInt(20); Map indices = new HashMap<>(indicesSize); - for (int i = 0; i < indicesSize; i++) { - String indexName = randomAlphaOfLengthBetween(1, 5) + i; - indices.put(indexName, ClusterIndexHealthTests.randomIndexHealthWithShards(indexName)); + if ("indices".equals(level) || "shards".equals(level)) { + for (int i = 0; i < indicesSize; i++) { + String indexName = randomAlphaOfLengthBetween(1, 5) + i; + indices.put(indexName, ClusterIndexHealthTests.randomIndexHealth(indexName, level)); + } } - ClusterStateHealth stateHealth = new ClusterStateHealth(randomInt(100), randomInt(100), randomInt(100), - randomInt(100), randomInt(100), randomInt(100), randomInt(100), - randomDoubleBetween(0d, 100d, true), randomFrom(ClusterHealthStatus.values()), indices); + randomInt(100), randomInt(100), randomInt(100), randomInt(100), + randomDoubleBetween(0d, 100d, true), randomFrom(ClusterHealthStatus.values()), indices); return new ClusterHealthResponse(randomAlphaOfLengthBetween(1, 10), randomInt(100), randomInt(100), randomInt(100), - TimeValue.timeValueMillis(randomInt(10000)), randomBoolean(), stateHealth); + TimeValue.timeValueMillis(randomInt(10000)), randomBoolean(), stateHealth); } + @Override protected ToXContent.Params getToXContentParams() { - Map map = new HashMap<>(); - map.put("level", "shards"); - return new ToXContent.MapParams(map); + return new ToXContent.MapParams(Collections.singletonMap("level", level)); } + @Override protected boolean supportsUnknownFields() { return true; } - private static final Pattern SHARDS_IN_XCONTENT = Pattern.compile("^indices\\." + "\\w+" + "\\.shards$"); + // Ignore all paths which looks like "indices.RANDOMINDEXNAME.shards" + private static final Pattern SHARDS_IN_XCONTENT = Pattern.compile("^indices\\.\\w+\\.shards$"); + @Override protected Predicate getRandomFieldsExcludeFilter() { return field -> "indices".equals(field) || SHARDS_IN_XCONTENT.matcher(field).find(); } + + @Override + protected ClusterHealthResponse mutateInstance(ClusterHealthResponse instance) { + String mutate = randomFrom("clusterName", "numberOfPendingTasks","numberOfInFlightFetch", "delayedUnassignedShards", + "taskMaxWaitingTime", "timedOut", "clusterStateHealth"); + switch (mutate) { + case "clusterName": + return new ClusterHealthResponse(instance.getClusterName() + randomAlphaOfLengthBetween(2, 5), + instance.getNumberOfPendingTasks(), instance.getNumberOfInFlightFetch(), + instance.getDelayedUnassignedShards(), instance.getTaskMaxWaitingTime(), + instance.isTimedOut(), instance.getClusterStateHealth()); + case "numberOfPendingTasks": + return new ClusterHealthResponse(instance.getClusterName(), + instance.getNumberOfPendingTasks() + between(1, 10), instance.getNumberOfInFlightFetch(), + instance.getDelayedUnassignedShards(), instance.getTaskMaxWaitingTime(), + instance.isTimedOut(), instance.getClusterStateHealth()); + case "numberOfInFlightFetch": + return new ClusterHealthResponse(instance.getClusterName(), + instance.getNumberOfPendingTasks(), instance.getNumberOfInFlightFetch() + between(1, 10), + instance.getDelayedUnassignedShards(), instance.getTaskMaxWaitingTime(), + instance.isTimedOut(), instance.getClusterStateHealth()); + case "delayedUnassignedShards": + return new ClusterHealthResponse(instance.getClusterName(), + instance.getNumberOfPendingTasks(), instance.getNumberOfInFlightFetch(), + instance.getDelayedUnassignedShards() + between(1, 10), instance.getTaskMaxWaitingTime(), + instance.isTimedOut(), instance.getClusterStateHealth()); + case "taskMaxWaitingTime": + + return new ClusterHealthResponse(instance.getClusterName(), + instance.getNumberOfPendingTasks(), instance.getNumberOfInFlightFetch(), + instance.getDelayedUnassignedShards(), new TimeValue(instance.getTaskMaxWaitingTime().millis() + between(1, 10)), + instance.isTimedOut(), instance.getClusterStateHealth()); + case "timedOut": + return new ClusterHealthResponse(instance.getClusterName(), + instance.getNumberOfPendingTasks(), instance.getNumberOfInFlightFetch(), + instance.getDelayedUnassignedShards(), instance.getTaskMaxWaitingTime(), + instance.isTimedOut() == false, instance.getClusterStateHealth()); + case "clusterStateHealth": + ClusterStateHealth state = instance.getClusterStateHealth(); + ClusterStateHealth newState = new ClusterStateHealth(state.getActivePrimaryShards() + between(1, 10), + state.getActiveShards(), state.getRelocatingShards(), state.getInitializingShards(), state.getUnassignedShards(), + state.getNumberOfNodes(), state.getNumberOfDataNodes(), state.getActiveShardsPercent(), state.getStatus(), + state.getIndices()); + return new ClusterHealthResponse(instance.getClusterName(), + instance.getNumberOfPendingTasks(), instance.getNumberOfInFlightFetch(), + instance.getDelayedUnassignedShards(), instance.getTaskMaxWaitingTime(), + instance.isTimedOut(), newState); + default: + throw new UnsupportedOperationException(); + } + } } diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java index 4609a15a7bea0..ba5626816f014 100644 --- a/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.cluster.health; import org.elasticsearch.Version; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.RoutingTableGenerator; @@ -27,13 +28,20 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractSerializingTestCase; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.equalTo; public class ClusterIndexHealthTests extends AbstractSerializingTestCase { + private final String level = randomFrom("shards", "indices"); + public void testClusterIndexHealth() { RoutingTableGenerator routingTableGenerator = new RoutingTableGenerator(); int numberOfShards = randomInt(3) + 1; @@ -42,7 +50,7 @@ public void testClusterIndexHealth() { RoutingTableGenerator.ShardCounter counter = new RoutingTableGenerator.ShardCounter(); IndexRoutingTable indexRoutingTable = routingTableGenerator.genIndexRoutingTable(indexMetaData, counter); - ClusterIndexHealth indexHealth = new ClusterIndexHealth(indexMetaData, indexRoutingTable); + ClusterIndexHealth indexHealth = TransportClusterHealthAction.calculateIndexHealth(indexMetaData, indexRoutingTable); assertIndexHealth(indexHealth, counter, indexMetaData); } @@ -65,17 +73,18 @@ private void assertIndexHealth(ClusterIndexHealth indexHealth, RoutingTableGener @Override protected ClusterIndexHealth createTestInstance() { - return randomIndexHealthWithShards(randomAlphaOfLengthBetween(1, 10)); + return randomIndexHealth(randomAlphaOfLengthBetween(1, 10), level); } - public static ClusterIndexHealth randomIndexHealthWithShards(String indexName) { + public static ClusterIndexHealth randomIndexHealth(String indexName, String level) { Map shards = new HashMap<>(); - for (int i = 0; i < randomIntBetween(1, 10); i++) { - shards.put(i, ClusterShardHealthTests.randomShardHealth(i)); + if ("shards".equals(level)) { + for (int i = 0; i < randomInt(5); i++) { + shards.put(i, ClusterShardHealthTests.randomShardHealth(i)); + } } - return new ClusterIndexHealth(indexName, randomInt(1000), randomInt(1000), randomInt(1000), randomInt(1000), - randomInt(1000), randomInt(1000), randomInt(1000), randomFrom(ClusterHealthStatus.values()), shards); + randomInt(1000), randomInt(1000), randomInt(1000), randomFrom(ClusterHealthStatus.values()), shards); } @Override @@ -88,17 +97,92 @@ protected ClusterIndexHealth doParseInstance(XContentParser parser) { return ClusterIndexHealth.fromXContent(parser); } + @Override protected ToXContent.Params getToXContentParams() { - Map map = new HashMap<>(); - map.put("level", "shards"); - return new ToXContent.MapParams(map); + return new ToXContent.MapParams(Collections.singletonMap("level", level)); } + @Override protected boolean supportsUnknownFields() { return true; } + // Ignore all paths which looks like "RANDOMINDEXNAME.shards" + private static final Pattern SHARDS_IN_XCONTENT = Pattern.compile("^\\w+\\.shards$"); + + @Override protected Predicate getRandomFieldsExcludeFilter() { - return "shards"::equals; + return field -> "".equals(field) || SHARDS_IN_XCONTENT.matcher(field).find(); + } + @Override + protected ClusterIndexHealth mutateInstance(ClusterIndexHealth instance) throws IOException { + String mutate = randomFrom("index", "numberOfShards", "numberOfReplicas", "activeShards", "relocatingShards", + "initializingShards", "unassignedShards", "activePrimaryShards", "status", "shards"); + switch (mutate) { + case "index": + return new ClusterIndexHealth(instance.getIndex() + randomAlphaOfLengthBetween(2, 5), instance.getNumberOfShards(), + instance.getNumberOfReplicas(), instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.getActivePrimaryShards(), instance.getStatus(), instance.getShards()); + case "numberOfShards": + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards() + between(1, 10), + instance.getNumberOfReplicas(), instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.getActivePrimaryShards(), instance.getStatus(), instance.getShards()); + case "numberOfReplicas": + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards(), + instance.getNumberOfReplicas() + between(1, 10), instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.getActivePrimaryShards(), instance.getStatus(), instance.getShards()); + case "activeShards": + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards(), + instance.getNumberOfReplicas(), instance.getActiveShards() + between(1, 10), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.getActivePrimaryShards(), instance.getStatus(), instance.getShards()); + case "relocatingShards": + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards(), + instance.getNumberOfReplicas(), instance.getActiveShards(), instance.getRelocatingShards() + between(1, 10), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.getActivePrimaryShards(), instance.getStatus(), instance.getShards()); + case "initializingShards": + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards(), + instance.getNumberOfReplicas(), instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards() + between(1, 10), instance.getUnassignedShards(), + instance.getActivePrimaryShards(), instance.getStatus(), instance.getShards()); + case "unassignedShards": + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards(), + instance.getNumberOfReplicas(), instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards() + between(1, 10), + instance.getActivePrimaryShards(), instance.getStatus(), instance.getShards()); + case "activePrimaryShards": + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards(), + instance.getNumberOfReplicas(), instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.getActivePrimaryShards() + between(1, 10), instance.getStatus(), instance.getShards()); + case "status": + ClusterHealthStatus status = randomFrom( + Arrays.stream(ClusterHealthStatus.values()).filter( + value -> !value.equals(instance.getStatus()) + ).collect(Collectors.toList()) + ); + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards(), + instance.getNumberOfReplicas(), instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.getActivePrimaryShards(), status, instance.getShards()); + case "shards": + Map map; + if (instance.getShards().isEmpty()) { + map = Collections.singletonMap(0, ClusterShardHealthTests.randomShardHealth(0)); + } else { + map = new HashMap<>(instance.getShards()); + map.remove(map.keySet().iterator().next()); + } + return new ClusterIndexHealth(instance.getIndex(), instance.getNumberOfShards(), + instance.getNumberOfReplicas(), instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.getActivePrimaryShards(), instance.getStatus(), map); + default: + throw new UnsupportedOperationException(); + } } } diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java index 9dd19bc0aca13..0cdf5923a6e9c 100644 --- a/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java @@ -22,6 +22,10 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractSerializingTestCase; +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.stream.Collectors; + public class ClusterShardHealthTests extends AbstractSerializingTestCase { @Override @@ -43,4 +47,63 @@ static ClusterShardHealth randomShardHealth(int id) { protected Writeable.Reader instanceReader() { return ClusterShardHealth::new; } + + @Override + protected boolean supportsUnknownFields() { + return true; + } + + @Override + protected Predicate getRandomFieldsExcludeFilter() { + return ""::equals; + } + + @Override + protected ClusterShardHealth mutateInstance(final ClusterShardHealth instance) { + String mutate = randomFrom("shardId", "status", "activeShards", "relocatingShards", "initializingShards", + "unassignedShards", "primaryActive"); + switch (mutate) { + case "shardId": + return new ClusterShardHealth(instance.getShardId() + between(1, 10), instance.getStatus(), + instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.isPrimaryActive()); + case "status": + ClusterHealthStatus status = randomFrom( + Arrays.stream(ClusterHealthStatus.values()).filter( + value -> !value.equals(instance.getStatus()) + ).collect(Collectors.toList()) + ); + return new ClusterShardHealth(instance.getShardId(), status, + instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.isPrimaryActive()); + case "activeShards": + return new ClusterShardHealth(instance.getShardId(), instance.getStatus(), + instance.getActiveShards() + between(1, 10), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.isPrimaryActive()); + case "relocatingShards": + return new ClusterShardHealth(instance.getShardId(), instance.getStatus(), + instance.getActiveShards(), instance.getRelocatingShards() + between(1, 10), + instance.getInitializingShards(), instance.getUnassignedShards(), instance.isPrimaryActive()); + case "initializingShards": + return new ClusterShardHealth(instance.getShardId(), instance.getStatus(), + instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards() + between(1, 10), instance.getUnassignedShards(), + instance.isPrimaryActive()); + case "unassignedShards": + return new ClusterShardHealth(instance.getShardId(), instance.getStatus(), + instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards() + between(1, 10), + instance.isPrimaryActive()); + case "primaryActive": + return new ClusterShardHealth(instance.getShardId(), instance.getStatus(), + instance.getActiveShards(), instance.getRelocatingShards(), + instance.getInitializingShards(), instance.getUnassignedShards(), + instance.isPrimaryActive() == false); + default: + throw new UnsupportedOperationException(); + } + } } diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterStateHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterStateHealthTests.java index 0f5f4870ae1bb..95eb30b7d76bc 100644 --- a/server/src/test/java/org/elasticsearch/cluster/health/ClusterStateHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterStateHealthTests.java @@ -176,7 +176,7 @@ public void testClusterHealth() throws IOException { String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames( clusterState, IndicesOptions.strictExpand(), (String[]) null ); - ClusterStateHealth clusterStateHealth = new ClusterStateHealth(clusterState, concreteIndices); + ClusterStateHealth clusterStateHealth = TransportClusterHealthAction.calculateStateHealth(clusterState, concreteIndices); logger.info("cluster status: {}, expected {}", clusterStateHealth.getStatus(), counter.status()); clusterStateHealth = maybeSerialize(clusterStateHealth); assertClusterHealth(clusterStateHealth, counter); @@ -189,7 +189,7 @@ public void testClusterHealthOnIndexCreation() { for (int i = 0; i < clusterStates.size(); i++) { // make sure cluster health is always YELLOW, up until the last state where it should be GREEN final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); + final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { assertThat(health.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); } else { @@ -206,7 +206,7 @@ public void testClusterHealthOnIndexCreationWithFailedAllocations() { // make sure cluster health is YELLOW up until the final cluster state, which contains primary shard // failed allocations that should make the cluster health RED final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); + final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { assertThat(health.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); } else { @@ -222,7 +222,7 @@ public void testClusterHealthOnClusterRecovery() { for (int i = 0; i < clusterStates.size(); i++) { // make sure cluster health is YELLOW up until the final cluster state, when it turns GREEN final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); + final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { assertThat(health.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); } else { @@ -239,7 +239,7 @@ public void testClusterHealthOnClusterRecoveryWithFailures() { // make sure cluster health is YELLOW up until the final cluster state, which contains primary shard // failed allocations that should make the cluster health RED final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); + final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { assertThat(health.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); } else { @@ -256,7 +256,7 @@ public void testClusterHealthOnClusterRecoveryWithPreviousAllocationIds() { // because there were previous allocation ids, we should be RED until the primaries are started, // then move to YELLOW, and the last state should be GREEN when all shards have been started final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); + final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { // if the inactive primaries are due solely to recovery (not failed allocation or previously being allocated), // then cluster health is YELLOW, otherwise RED @@ -275,7 +275,7 @@ public void testClusterHealthOnClusterRecoveryWithPreviousAllocationIdsAndAlloca final String indexName = "test-idx"; final String[] indices = new String[] { indexName }; for (final ClusterState clusterState : simulateClusterRecoveryStates(indexName, true, true)) { - final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); + final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); // if the inactive primaries are due solely to recovery (not failed allocation or previously being allocated) // then cluster health is YELLOW, otherwise RED if (primaryInactiveDueToRecovery(indexName, clusterState)) { diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/PrimaryTermsTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/PrimaryTermsTests.java index 65526896864d6..5971eecfcb88f 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/PrimaryTermsTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/PrimaryTermsTests.java @@ -20,6 +20,7 @@ package org.elasticsearch.cluster.routing; import org.elasticsearch.Version; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ESAllocationTestCase; import org.elasticsearch.cluster.health.ClusterStateHealth; @@ -129,7 +130,7 @@ private void applyRerouteResult(ClusterState newClusterState) { builder.metaData(MetaData.builder(newClusterState.metaData()).version(newClusterState.metaData().version() + 1)); } this.clusterState = builder.build(); - final ClusterStateHealth clusterHealth = new ClusterStateHealth(clusterState); + final ClusterStateHealth clusterHealth = TransportClusterHealthAction.calculateStateHealth(clusterState); logger.info("applied reroute. active shards: p [{}], t [{}], init shards: [{}], relocating: [{}]", clusterHealth.getActivePrimaryShards(), clusterHealth.getActiveShards(), clusterHealth.getInitializingShards(), clusterHealth.getRelocatingShards()); diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/RoutingTableGenerator.java b/server/src/test/java/org/elasticsearch/cluster/routing/RoutingTableGenerator.java index f53cce07daea0..a62f0e8c0faa2 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/RoutingTableGenerator.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/RoutingTableGenerator.java @@ -24,7 +24,7 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.index.shard.ShardId; -import static org.elasticsearch.cluster.health.ClusterShardHealth.getInactivePrimaryHealth; +import static org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction.getInactivePrimaryHealth; public class RoutingTableGenerator { private static int node_id = 1; diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DecisionsImpactOnClusterHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DecisionsImpactOnClusterHealthTests.java index bee2275743bac..f227e95453877 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DecisionsImpactOnClusterHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DecisionsImpactOnClusterHealthTests.java @@ -20,6 +20,7 @@ package org.elasticsearch.cluster.routing.allocation; import org.elasticsearch.Version; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ESAllocationTestCase; @@ -152,7 +153,7 @@ private ClusterState runAllocationTest(final Settings settings, clusterState = ClusterState.builder(clusterState).routingTable(routingTable).build(); logger.info("--> assert cluster health"); - ClusterStateHealth health = new ClusterStateHealth(clusterState); + ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState); assertThat(health.getStatus(), equalTo(expectedStatus)); return clusterState; diff --git a/server/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java b/server/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java index e3687548190a3..5292a2d20b0a9 100644 --- a/server/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java +++ b/server/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java @@ -21,6 +21,7 @@ import org.apache.lucene.index.CorruptIndexException; import org.elasticsearch.Version; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ESAllocationTestCase; @@ -428,7 +429,7 @@ private void assertClusterHealthStatus(RoutingAllocation allocation, ClusterHeal ClusterState clusterState = ClusterState.builder(new ClusterName("test-cluster")) .routingTable(newRoutingTable) .build(); - ClusterStateHealth clusterStateHealth = new ClusterStateHealth(clusterState); + ClusterStateHealth clusterStateHealth = TransportClusterHealthAction.calculateStateHealth(clusterState); assertThat(clusterStateHealth.getStatus().ordinal(), lessThanOrEqualTo(expectedStatus.ordinal())); } diff --git a/server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java b/server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java index cd592c9ed1e9c..92803c3798cce 100644 --- a/server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java +++ b/server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java @@ -21,12 +21,14 @@ import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.admin.indices.stats.CommonStats; import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; import org.elasticsearch.action.admin.indices.stats.IndicesStatsTests; import org.elasticsearch.action.admin.indices.stats.ShardStats; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.health.ClusterStateHealth; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.MetaData; @@ -106,8 +108,9 @@ public void testBuildTable() { for (int i = 0; i < indices.length; i++) { indicesStr[i] = indices[i].getName(); } + ClusterStateHealth stateHealth = TransportClusterHealthAction.calculateStateHealth(clusterState); final ClusterHealthResponse clusterHealth = new ClusterHealthResponse( - clusterState.getClusterName().value(), indicesStr, clusterState, 0, 0, 0, TimeValue.timeValueMillis(1000L) + clusterState.getClusterName().value(), 0, 0, 0, TimeValue.timeValueMillis(1000L), false, stateHealth ); final Table table = action.buildTable(new FakeRestRequest(), indices, clusterHealth, randomIndicesStatsResponse(indices), metaData); diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java index 719acf4066f4c..e23feca248914 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java @@ -42,10 +42,14 @@ public abstract class AbstractXContentTestCase extends EST protected static final int NUMBER_OF_TEST_RUNS = 20; public static void testFromXContent(int numberOfTestRuns, Supplier instanceSupplier, - boolean supportsUnknownFields, String[] shuffleFieldsExceptions, Predicate randomFieldsExcludeFilter, - CheckedBiFunction createParserFunction, - CheckedFunction parseFunction, BiConsumer assertEqualsConsumer, - boolean assertToXContentEquivalence, ToXContent.Params toXContentParams) throws IOException { + boolean supportsUnknownFields, String[] shuffleFieldsExceptions, + Predicate randomFieldsExcludeFilter, + CheckedBiFunction + createParserFunction, + CheckedFunction parseFunction, + BiConsumer assertEqualsConsumer, + boolean assertToXContentEquivalence, + ToXContent.Params toXContentParams) throws IOException { for (int runs = 0; runs < numberOfTestRuns; runs++) { T testInstance = instanceSupplier.get(); XContentType xContentType = randomFrom(XContentType.values()); @@ -62,7 +66,7 @@ public static void testFromXContent(int numberOfTestRuns, T parsed = parseFunction.apply(parser); assertEqualsConsumer.accept(testInstance, parsed); if (assertToXContentEquivalence) { - assertToXContentEquivalent(shuffled, XContentHelper.toXContent(parsed, xContentType, toXContentParams,false), + assertToXContentEquivalent(shuffled, XContentHelper.toXContent(parsed, xContentType, toXContentParams, false), xContentType); } } From 4f0c01ed54fa291d2979a09b38c2430b5d6447bd Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Tue, 29 May 2018 03:14:07 -0400 Subject: [PATCH 05/14] Revert "Move health calculation logic from constructors to HealthAction" --- .../cluster/health/ClusterHealthResponse.java | 30 +-- .../health/TransportClusterHealthAction.java | 195 +----------------- .../stats/TransportClusterStatsAction.java | 4 +- .../TransportIndicesShardStoresAction.java | 3 +- .../cluster/health/ClusterIndexHealth.java | 78 +++++-- .../cluster/health/ClusterShardHealth.java | 90 +++++++- .../cluster/health/ClusterStateHealth.java | 112 ++++++++-- .../routing/allocation/AllocationService.java | 7 +- .../health/ClusterHealthResponsesTests.java | 5 +- .../health/ClusterIndexHealthTests.java | 3 +- .../health/ClusterStateHealthTests.java | 14 +- .../cluster/routing/PrimaryTermsTests.java | 3 +- .../routing/RoutingTableGenerator.java | 2 +- .../DecisionsImpactOnClusterHealthTests.java | 3 +- .../gateway/PrimaryShardAllocatorTests.java | 3 +- .../action/cat/RestIndicesActionTests.java | 5 +- 16 files changed, 289 insertions(+), 268 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java index 6b987d2536f38..23168caa7182d 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java @@ -43,7 +43,6 @@ import java.util.Objects; import static java.util.Collections.emptyMap; -import static org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction.calculateStateHealth; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; @@ -145,29 +144,34 @@ public class ClusterHealthResponse extends ActionResponse implements StatusToXCo ClusterHealthResponse() { } - public ClusterHealthResponse(String clusterName, int numberOfPendingTasks, int numberOfInFlightFetch, int delayedUnassignedShards, - TimeValue taskMaxWaitingTime, boolean timedOut, ClusterStateHealth clusterStateHealth) { + /** needed for plugins BWC */ + public ClusterHealthResponse(String clusterName, String[] concreteIndices, ClusterState clusterState) { + this(clusterName, concreteIndices, clusterState, -1, -1, -1, TimeValue.timeValueHours(0)); + } + + public ClusterHealthResponse(String clusterName, String[] concreteIndices, ClusterState clusterState, int numberOfPendingTasks, + int numberOfInFlightFetch, int delayedUnassignedShards, TimeValue taskMaxWaitingTime) { this.clusterName = clusterName; this.numberOfPendingTasks = numberOfPendingTasks; this.numberOfInFlightFetch = numberOfInFlightFetch; this.delayedUnassignedShards = delayedUnassignedShards; this.taskMaxWaitingTime = taskMaxWaitingTime; - this.timedOut = timedOut; - this.clusterStateHealth = clusterStateHealth; + this.clusterStateHealth = new ClusterStateHealth(clusterState, concreteIndices); this.clusterHealthStatus = clusterStateHealth.getStatus(); } /** - * needed for plugins BWC - * Do not use for other purposes + * For XContent Parser and serialization tests */ - public ClusterHealthResponse(String clusterName, String[] concreteIndices, ClusterState clusterState) { + ClusterHealthResponse(String clusterName, int numberOfPendingTasks, int numberOfInFlightFetch, int delayedUnassignedShards, + TimeValue taskMaxWaitingTime, boolean timedOut, ClusterStateHealth clusterStateHealth) { this.clusterName = clusterName; - this.numberOfPendingTasks = -1; - this.numberOfInFlightFetch = -1; - this.delayedUnassignedShards = -1; - this.taskMaxWaitingTime = TimeValue.timeValueHours(0); - this.clusterStateHealth = calculateStateHealth(clusterState, concreteIndices); + this.numberOfPendingTasks = numberOfPendingTasks; + this.numberOfInFlightFetch = numberOfInFlightFetch; + this.delayedUnassignedShards = delayedUnassignedShards; + this.taskMaxWaitingTime = taskMaxWaitingTime; + this.timedOut = timedOut; + this.clusterStateHealth = clusterStateHealth; this.clusterHealthStatus = clusterStateHealth.getStatus(); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java index dd33dfa4d76c0..697849985afeb 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java @@ -32,17 +32,8 @@ import org.elasticsearch.cluster.NotMasterException; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.health.ClusterHealthStatus; -import org.elasticsearch.cluster.health.ClusterIndexHealth; -import org.elasticsearch.cluster.health.ClusterShardHealth; -import org.elasticsearch.cluster.health.ClusterStateHealth; -import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.routing.IndexRoutingTable; -import org.elasticsearch.cluster.routing.IndexShardRoutingTable; -import org.elasticsearch.cluster.routing.RecoverySource; -import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; -import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; @@ -50,14 +41,10 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.gateway.GatewayAllocator; import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.rest.RestStatus; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.function.Predicate; public class TransportClusterHealthAction extends TransportMasterNodeReadAction { @@ -328,187 +315,15 @@ private ClusterHealthResponse clusterHealth(ClusterHealthRequest request, Cluste try { concreteIndices = indexNameExpressionResolver.concreteIndexNames(clusterState, request); } catch (IndexNotFoundException e) { - ClusterStateHealth stateHealth = calculateStateHealth(clusterState, Strings.EMPTY_ARRAY); - ClusterHealthResponse response = new ClusterHealthResponse(clusterState.getClusterName().value(), numberOfPendingTasks, - numberOfInFlightFetch, UnassignedInfo.getNumberOfDelayedUnassigned(clusterState), pendingTaskTimeInQueue, - false, stateHealth); // one of the specified indices is not there - treat it as RED. + ClusterHealthResponse response = new ClusterHealthResponse(clusterState.getClusterName().value(), Strings.EMPTY_ARRAY, clusterState, + numberOfPendingTasks, numberOfInFlightFetch, UnassignedInfo.getNumberOfDelayedUnassigned(clusterState), + pendingTaskTimeInQueue); response.setStatus(ClusterHealthStatus.RED); return response; } - ClusterStateHealth stateHealth = calculateStateHealth(clusterState, concreteIndices); - return new ClusterHealthResponse(clusterState.getClusterName().value(), numberOfPendingTasks, numberOfInFlightFetch, - UnassignedInfo.getNumberOfDelayedUnassigned(clusterState), pendingTaskTimeInQueue, false, stateHealth); - } - - /** - * Creates a new ClusterStateHealth instance considering the current cluster state and all indices in the cluster. - * - * @param clusterState The current cluster state. Must not be null. - */ - public static ClusterStateHealth calculateStateHealth(final ClusterState clusterState) { - return calculateStateHealth(clusterState, clusterState.metaData().getConcreteAllIndices()); - } - - /** - * Creates a new ClusterStateHealth instance considering the current cluster state and the provided index names. - * - * @param clusterState The current cluster state. Must not be null. - * @param concreteIndices An array of index names to consider. Must not be null but may be empty. - */ - public static ClusterStateHealth calculateStateHealth(final ClusterState clusterState, final String[] concreteIndices) { - int numberOfNodes = clusterState.nodes().getSize(); - int numberOfDataNodes = clusterState.nodes().getDataNodes().size(); - Map indices = new HashMap<>(); - for (String index : concreteIndices) { - IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(index); - IndexMetaData indexMetaData = clusterState.metaData().index(index); - if (indexRoutingTable == null) { - continue; - } - ClusterIndexHealth indexHealth = calculateIndexHealth(indexMetaData, indexRoutingTable); - indices.put(indexHealth.getIndex(), indexHealth); - } - ClusterHealthStatus computeStatus = ClusterHealthStatus.GREEN; - int computeActivePrimaryShards = 0; - int computeActiveShards = 0; - int computeRelocatingShards = 0; - int computeInitializingShards = 0; - int computeUnassignedShards = 0; - - for (ClusterIndexHealth indexHealth : indices.values()) { - computeActivePrimaryShards += indexHealth.getActivePrimaryShards(); - computeActiveShards += indexHealth.getActiveShards(); - computeRelocatingShards += indexHealth.getRelocatingShards(); - computeInitializingShards += indexHealth.getInitializingShards(); - computeUnassignedShards += indexHealth.getUnassignedShards(); - if (indexHealth.getStatus() == ClusterHealthStatus.RED) { - computeStatus = ClusterHealthStatus.RED; - } else if (indexHealth.getStatus() == ClusterHealthStatus.YELLOW && computeStatus != ClusterHealthStatus.RED) { - computeStatus = ClusterHealthStatus.YELLOW; - } - } - if (clusterState.blocks().hasGlobalBlock(RestStatus.SERVICE_UNAVAILABLE)) { - computeStatus = ClusterHealthStatus.RED; - } - double activeShardsPercent; - // shortcut on green - if (computeStatus.equals(ClusterHealthStatus.GREEN)) { - activeShardsPercent = 100; - } else { - List shardRoutings = clusterState.getRoutingTable().allShards(); - int activeShardCount = 0; - int totalShardCount = 0; - for (ShardRouting shardRouting : shardRoutings) { - if (shardRouting.active()) activeShardCount++; - totalShardCount++; - } - activeShardsPercent = (((double) activeShardCount) / totalShardCount) * 100; - } - return new ClusterStateHealth(computeActivePrimaryShards, computeActiveShards, computeRelocatingShards, - computeInitializingShards, computeUnassignedShards, numberOfNodes, numberOfDataNodes, activeShardsPercent, - computeStatus, indices); - } - public static ClusterIndexHealth calculateIndexHealth(final IndexMetaData indexMetaData, final IndexRoutingTable indexRoutingTable) { - String index = indexMetaData.getIndex().getName(); - int numberOfShards = indexMetaData.getNumberOfShards(); - int numberOfReplicas = indexMetaData.getNumberOfReplicas(); - - Map shards = new HashMap<>(); - for (IndexShardRoutingTable shardRoutingTable : indexRoutingTable) { - int shardId = shardRoutingTable.shardId().id(); - shards.put(shardId, calculateShardHealth(shardId, shardRoutingTable)); - } - - // update the index status - ClusterHealthStatus computeStatus = ClusterHealthStatus.GREEN; - int computeActivePrimaryShards = 0; - int computeActiveShards = 0; - int computeRelocatingShards = 0; - int computeInitializingShards = 0; - int computeUnassignedShards = 0; - for (ClusterShardHealth shardHealth : shards.values()) { - if (shardHealth.isPrimaryActive()) { - computeActivePrimaryShards++; - } - computeActiveShards += shardHealth.getActiveShards(); - computeRelocatingShards += shardHealth.getRelocatingShards(); - computeInitializingShards += shardHealth.getInitializingShards(); - computeUnassignedShards += shardHealth.getUnassignedShards(); - - if (shardHealth.getStatus() == ClusterHealthStatus.RED) { - computeStatus = ClusterHealthStatus.RED; - } else if (shardHealth.getStatus() == ClusterHealthStatus.YELLOW && computeStatus != ClusterHealthStatus.RED) { - // do not override an existing red - computeStatus = ClusterHealthStatus.YELLOW; - } - } - if (shards.isEmpty()) { // might be since none has been created yet (two phase index creation) - computeStatus = ClusterHealthStatus.RED; - } - return new ClusterIndexHealth(index, numberOfShards, numberOfReplicas, computeActiveShards, computeRelocatingShards, - computeInitializingShards, computeUnassignedShards, computeActivePrimaryShards, computeStatus, shards); - } - - public static ClusterShardHealth calculateShardHealth(final int shardId, final IndexShardRoutingTable shardRoutingTable) { - int computeActiveShards = 0; - int computeRelocatingShards = 0; - int computeInitializingShards = 0; - int computeUnassignedShards = 0; - for (ShardRouting shardRouting : shardRoutingTable) { - if (shardRouting.active()) { - computeActiveShards++; - if (shardRouting.relocating()) { - // the shard is relocating, the one it is relocating to will be in initializing state, so we don't count it - computeRelocatingShards++; - } - } else if (shardRouting.initializing()) { - computeInitializingShards++; - } else if (shardRouting.unassigned()) { - computeUnassignedShards++; - } - } - ClusterHealthStatus computeStatus; - final ShardRouting primaryRouting = shardRoutingTable.primaryShard(); - if (primaryRouting.active()) { - if (computeActiveShards == shardRoutingTable.size()) { - computeStatus = ClusterHealthStatus.GREEN; - } else { - computeStatus = ClusterHealthStatus.YELLOW; - } - } else { - computeStatus = getInactivePrimaryHealth(primaryRouting); - } - return new ClusterShardHealth(shardId, computeStatus, computeActiveShards, computeRelocatingShards, computeInitializingShards, - computeUnassignedShards, primaryRouting.active()); - } - - /** - * Checks if an inactive primary shard should cause the cluster health to go RED. - * - * An inactive primary shard in an index should cause the cluster health to be RED to make it visible that some of the existing data is - * unavailable. In case of index creation, snapshot restore or index shrinking, which are unexceptional events in the cluster lifecycle, - * cluster health should not turn RED for the time where primaries are still in the initializing state but go to YELLOW instead. - * However, in case of exceptional events, for example when the primary shard cannot be assigned to a node or initialization fails at - * some point, cluster health should still turn RED. - * - * NB: this method should *not* be called on active shards nor on non-primary shards. - */ - public static ClusterHealthStatus getInactivePrimaryHealth(final ShardRouting shardRouting) { - assert shardRouting.primary() : "cannot invoke on a replica shard: " + shardRouting; - assert shardRouting.active() == false : "cannot invoke on an active shard: " + shardRouting; - assert shardRouting.unassignedInfo() != null : "cannot invoke on a shard with no UnassignedInfo: " + shardRouting; - assert shardRouting.recoverySource() != null : "cannot invoke on a shard that has no recovery source" + shardRouting; - final UnassignedInfo unassignedInfo = shardRouting.unassignedInfo(); - RecoverySource.Type recoveryType = shardRouting.recoverySource().getType(); - if (unassignedInfo.getLastAllocationStatus() != AllocationStatus.DECIDERS_NO && unassignedInfo.getNumFailedAllocations() == 0 - && (recoveryType == RecoverySource.Type.EMPTY_STORE - || recoveryType == RecoverySource.Type.LOCAL_SHARDS - || recoveryType == RecoverySource.Type.SNAPSHOT)) { - return ClusterHealthStatus.YELLOW; - } else { - return ClusterHealthStatus.RED; - } + return new ClusterHealthResponse(clusterState.getClusterName().value(), concreteIndices, clusterState, numberOfPendingTasks, + numberOfInFlightFetch, UnassignedInfo.getNumberOfDelayedUnassigned(clusterState), pendingTaskTimeInQueue); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java index 146ad61d1ce14..c87b55b0bbd7d 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java @@ -20,7 +20,6 @@ package org.elasticsearch.action.admin.cluster.stats; import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; import org.elasticsearch.action.admin.indices.stats.CommonStats; @@ -30,6 +29,7 @@ import org.elasticsearch.action.support.nodes.BaseNodeRequest; import org.elasticsearch.action.support.nodes.TransportNodesAction; import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.health.ClusterStateHealth; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; @@ -112,7 +112,7 @@ protected ClusterStatsNodeResponse nodeOperation(ClusterStatsNodeRequest nodeReq ClusterHealthStatus clusterStatus = null; if (clusterService.state().nodes().isLocalNodeElectedMaster()) { - clusterStatus = TransportClusterHealthAction.calculateStateHealth(clusterService.state()).getStatus(); + clusterStatus = new ClusterStateHealth(clusterService.state()).getStatus(); } return new ClusterStatsNodeResponse(nodeInfo.getNode(), clusterStatus, nodeInfo, nodeStats, shardsStats.toArray(new ShardStats[shardsStats.size()])); diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java index 1945c0ced7236..0741965f5e5c9 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java @@ -22,7 +22,6 @@ import org.apache.lucene.util.CollectionUtil; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.TransportMasterNodeReadAction; import org.elasticsearch.cluster.ClusterState; @@ -100,7 +99,7 @@ protected void masterOperation(IndicesShardStoresRequest request, ClusterState s } for (IndexShardRoutingTable routing : indexShardRoutingTables) { final int shardId = routing.shardId().id(); - ClusterShardHealth shardHealth = TransportClusterHealthAction.calculateShardHealth(shardId, routing); + ClusterShardHealth shardHealth = new ClusterShardHealth(shardId, routing); if (request.shardStatuses().contains(shardHealth.getStatus())) { shardIdsToFetch.add(routing.shardId()); } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java index 9d88158c62c0e..39a3ec2762c1c 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java @@ -19,6 +19,9 @@ package org.elasticsearch.cluster.health; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.routing.IndexRoutingTable; +import org.elasticsearch.cluster.routing.IndexShardRoutingTable; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; @@ -108,19 +111,50 @@ public final class ClusterIndexHealth implements Iterable, W private final ClusterHealthStatus status; private final Map shards; - public ClusterIndexHealth(String index, int numberOfShards, int numberOfReplicas, int activeShards, int relocatingShards, - int initializingShards, int unassignedShards, int activePrimaryShards, ClusterHealthStatus status, - Map shards) { - this.index = index; - this.numberOfShards = numberOfShards; - this.numberOfReplicas = numberOfReplicas; - this.activeShards = activeShards; - this.relocatingShards = relocatingShards; - this.initializingShards = initializingShards; - this.unassignedShards = unassignedShards; - this.activePrimaryShards = activePrimaryShards; - this.status = status; - this.shards = shards; + public ClusterIndexHealth(final IndexMetaData indexMetaData, final IndexRoutingTable indexRoutingTable) { + this.index = indexMetaData.getIndex().getName(); + this.numberOfShards = indexMetaData.getNumberOfShards(); + this.numberOfReplicas = indexMetaData.getNumberOfReplicas(); + + shards = new HashMap<>(); + for (IndexShardRoutingTable shardRoutingTable : indexRoutingTable) { + int shardId = shardRoutingTable.shardId().id(); + shards.put(shardId, new ClusterShardHealth(shardId, shardRoutingTable)); + } + + // update the index status + ClusterHealthStatus computeStatus = ClusterHealthStatus.GREEN; + int computeActivePrimaryShards = 0; + int computeActiveShards = 0; + int computeRelocatingShards = 0; + int computeInitializingShards = 0; + int computeUnassignedShards = 0; + for (ClusterShardHealth shardHealth : shards.values()) { + if (shardHealth.isPrimaryActive()) { + computeActivePrimaryShards++; + } + computeActiveShards += shardHealth.getActiveShards(); + computeRelocatingShards += shardHealth.getRelocatingShards(); + computeInitializingShards += shardHealth.getInitializingShards(); + computeUnassignedShards += shardHealth.getUnassignedShards(); + + if (shardHealth.getStatus() == ClusterHealthStatus.RED) { + computeStatus = ClusterHealthStatus.RED; + } else if (shardHealth.getStatus() == ClusterHealthStatus.YELLOW && computeStatus != ClusterHealthStatus.RED) { + // do not override an existing red + computeStatus = ClusterHealthStatus.YELLOW; + } + } + if (shards.isEmpty()) { // might be since none has been created yet (two phase index creation) + computeStatus = ClusterHealthStatus.RED; + } + + this.status = computeStatus; + this.activePrimaryShards = computeActivePrimaryShards; + this.activeShards = computeActiveShards; + this.relocatingShards = computeRelocatingShards; + this.initializingShards = computeInitializingShards; + this.unassignedShards = computeUnassignedShards; } public ClusterIndexHealth(final StreamInput in) throws IOException { @@ -142,6 +176,24 @@ public ClusterIndexHealth(final StreamInput in) throws IOException { } } + /** + * For XContent Parser and serialization tests + */ + ClusterIndexHealth(String index, int numberOfShards, int numberOfReplicas, int activeShards, int relocatingShards, + int initializingShards, int unassignedShards, int activePrimaryShards, ClusterHealthStatus status, + Map shards) { + this.index = index; + this.numberOfShards = numberOfShards; + this.numberOfReplicas = numberOfReplicas; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.activePrimaryShards = activePrimaryShards; + this.status = status; + this.shards = shards; + } + public String getIndex() { return index; } diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java index ee68ab80778d5..c7ad5e86aea47 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java @@ -19,6 +19,11 @@ package org.elasticsearch.cluster.health; +import org.elasticsearch.cluster.routing.IndexShardRoutingTable; +import org.elasticsearch.cluster.routing.RecoverySource; +import org.elasticsearch.cluster.routing.ShardRouting; +import org.elasticsearch.cluster.routing.UnassignedInfo; +import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; @@ -78,15 +83,42 @@ public final class ClusterShardHealth implements Writeable, ToXContentFragment { private final int unassignedShards; private final boolean primaryActive; - public ClusterShardHealth(int shardId, ClusterHealthStatus status, int activeShards, int relocatingShards, int initializingShards, - int unassignedShards, boolean primaryActive) { + public ClusterShardHealth(final int shardId, final IndexShardRoutingTable shardRoutingTable) { this.shardId = shardId; - this.status = status; - this.activeShards = activeShards; - this.relocatingShards = relocatingShards; - this.initializingShards = initializingShards; - this.unassignedShards = unassignedShards; - this.primaryActive = primaryActive; + int computeActiveShards = 0; + int computeRelocatingShards = 0; + int computeInitializingShards = 0; + int computeUnassignedShards = 0; + for (ShardRouting shardRouting : shardRoutingTable) { + if (shardRouting.active()) { + computeActiveShards++; + if (shardRouting.relocating()) { + // the shard is relocating, the one it is relocating to will be in initializing state, so we don't count it + computeRelocatingShards++; + } + } else if (shardRouting.initializing()) { + computeInitializingShards++; + } else if (shardRouting.unassigned()) { + computeUnassignedShards++; + } + } + ClusterHealthStatus computeStatus; + final ShardRouting primaryRouting = shardRoutingTable.primaryShard(); + if (primaryRouting.active()) { + if (computeActiveShards == shardRoutingTable.size()) { + computeStatus = ClusterHealthStatus.GREEN; + } else { + computeStatus = ClusterHealthStatus.YELLOW; + } + } else { + computeStatus = getInactivePrimaryHealth(primaryRouting); + } + this.status = computeStatus; + this.activeShards = computeActiveShards; + this.relocatingShards = computeRelocatingShards; + this.initializingShards = computeInitializingShards; + this.unassignedShards = computeUnassignedShards; + this.primaryActive = primaryRouting.active(); } public ClusterShardHealth(final StreamInput in) throws IOException { @@ -99,6 +131,20 @@ public ClusterShardHealth(final StreamInput in) throws IOException { primaryActive = in.readBoolean(); } + /** + * For XContent Parser and serialization tests + */ + ClusterShardHealth(int shardId, ClusterHealthStatus status, int activeShards, int relocatingShards, int initializingShards, + int unassignedShards, boolean primaryActive) { + this.shardId = shardId; + this.status = status; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.primaryActive = primaryActive; + } + public int getShardId() { return shardId; } @@ -138,6 +184,34 @@ public void writeTo(final StreamOutput out) throws IOException { out.writeBoolean(primaryActive); } + /** + * Checks if an inactive primary shard should cause the cluster health to go RED. + * + * An inactive primary shard in an index should cause the cluster health to be RED to make it visible that some of the existing data is + * unavailable. In case of index creation, snapshot restore or index shrinking, which are unexceptional events in the cluster lifecycle, + * cluster health should not turn RED for the time where primaries are still in the initializing state but go to YELLOW instead. + * However, in case of exceptional events, for example when the primary shard cannot be assigned to a node or initialization fails at + * some point, cluster health should still turn RED. + * + * NB: this method should *not* be called on active shards nor on non-primary shards. + */ + public static ClusterHealthStatus getInactivePrimaryHealth(final ShardRouting shardRouting) { + assert shardRouting.primary() : "cannot invoke on a replica shard: " + shardRouting; + assert shardRouting.active() == false : "cannot invoke on an active shard: " + shardRouting; + assert shardRouting.unassignedInfo() != null : "cannot invoke on a shard with no UnassignedInfo: " + shardRouting; + assert shardRouting.recoverySource() != null : "cannot invoke on a shard that has no recovery source" + shardRouting; + final UnassignedInfo unassignedInfo = shardRouting.unassignedInfo(); + RecoverySource.Type recoveryType = shardRouting.recoverySource().getType(); + if (unassignedInfo.getLastAllocationStatus() != AllocationStatus.DECIDERS_NO && unassignedInfo.getNumFailedAllocations() == 0 + && (recoveryType == RecoverySource.Type.EMPTY_STORE + || recoveryType == RecoverySource.Type.LOCAL_SHARDS + || recoveryType == RecoverySource.Type.SNAPSHOT)) { + return ClusterHealthStatus.YELLOW; + } else { + return ClusterHealthStatus.RED; + } + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(Integer.toString(getShardId())); diff --git a/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java b/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java index 096b515a12585..6d36f4dec1d26 100644 --- a/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java +++ b/server/src/main/java/org/elasticsearch/cluster/health/ClusterStateHealth.java @@ -18,14 +18,20 @@ */ package org.elasticsearch.cluster.health; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.routing.IndexRoutingTable; +import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.rest.RestStatus; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -43,19 +49,81 @@ public final class ClusterStateHealth implements Iterable, W private final ClusterHealthStatus status; private final Map indices; - public ClusterStateHealth(int activePrimaryShards, int activeShards, int relocatingShards, int initializingShards, int unassignedShards, - int numberOfNodes, int numberOfDataNodes, double activeShardsPercent, ClusterHealthStatus status, - Map indices) { - this.activePrimaryShards = activePrimaryShards; - this.activeShards = activeShards; - this.relocatingShards = relocatingShards; - this.initializingShards = initializingShards; - this.unassignedShards = unassignedShards; - this.numberOfNodes = numberOfNodes; - this.numberOfDataNodes = numberOfDataNodes; - this.activeShardsPercent = activeShardsPercent; - this.status = status; - this.indices = indices; + /** + * Creates a new ClusterStateHealth instance considering the current cluster state and all indices in the cluster. + * + * @param clusterState The current cluster state. Must not be null. + */ + public ClusterStateHealth(final ClusterState clusterState) { + this(clusterState, clusterState.metaData().getConcreteAllIndices()); + } + + /** + * Creates a new ClusterStateHealth instance considering the current cluster state and the provided index names. + * + * @param clusterState The current cluster state. Must not be null. + * @param concreteIndices An array of index names to consider. Must not be null but may be empty. + */ + public ClusterStateHealth(final ClusterState clusterState, final String[] concreteIndices) { + numberOfNodes = clusterState.nodes().getSize(); + numberOfDataNodes = clusterState.nodes().getDataNodes().size(); + indices = new HashMap<>(); + for (String index : concreteIndices) { + IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(index); + IndexMetaData indexMetaData = clusterState.metaData().index(index); + if (indexRoutingTable == null) { + continue; + } + + ClusterIndexHealth indexHealth = new ClusterIndexHealth(indexMetaData, indexRoutingTable); + + indices.put(indexHealth.getIndex(), indexHealth); + } + + ClusterHealthStatus computeStatus = ClusterHealthStatus.GREEN; + int computeActivePrimaryShards = 0; + int computeActiveShards = 0; + int computeRelocatingShards = 0; + int computeInitializingShards = 0; + int computeUnassignedShards = 0; + + for (ClusterIndexHealth indexHealth : indices.values()) { + computeActivePrimaryShards += indexHealth.getActivePrimaryShards(); + computeActiveShards += indexHealth.getActiveShards(); + computeRelocatingShards += indexHealth.getRelocatingShards(); + computeInitializingShards += indexHealth.getInitializingShards(); + computeUnassignedShards += indexHealth.getUnassignedShards(); + if (indexHealth.getStatus() == ClusterHealthStatus.RED) { + computeStatus = ClusterHealthStatus.RED; + } else if (indexHealth.getStatus() == ClusterHealthStatus.YELLOW && computeStatus != ClusterHealthStatus.RED) { + computeStatus = ClusterHealthStatus.YELLOW; + } + } + + if (clusterState.blocks().hasGlobalBlock(RestStatus.SERVICE_UNAVAILABLE)) { + computeStatus = ClusterHealthStatus.RED; + } + + this.status = computeStatus; + this.activePrimaryShards = computeActivePrimaryShards; + this.activeShards = computeActiveShards; + this.relocatingShards = computeRelocatingShards; + this.initializingShards = computeInitializingShards; + this.unassignedShards = computeUnassignedShards; + + // shortcut on green + if (computeStatus.equals(ClusterHealthStatus.GREEN)) { + this.activeShardsPercent = 100; + } else { + List shardRoutings = clusterState.getRoutingTable().allShards(); + int activeShardCount = 0; + int totalShardCount = 0; + for (ShardRouting shardRouting : shardRoutings) { + if (shardRouting.active()) activeShardCount++; + totalShardCount++; + } + this.activeShardsPercent = (((double) activeShardCount) / totalShardCount) * 100; + } } public ClusterStateHealth(final StreamInput in) throws IOException { @@ -76,6 +144,24 @@ public ClusterStateHealth(final StreamInput in) throws IOException { activeShardsPercent = in.readDouble(); } + /** + * For ClusterHealthResponse's XContent Parser + */ + public ClusterStateHealth(int activePrimaryShards, int activeShards, int relocatingShards, int initializingShards, int unassignedShards, + int numberOfNodes, int numberOfDataNodes, double activeShardsPercent, ClusterHealthStatus status, + Map indices) { + this.activePrimaryShards = activePrimaryShards; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.numberOfNodes = numberOfNodes; + this.numberOfDataNodes = numberOfDataNodes; + this.activeShardsPercent = activeShardsPercent; + this.status = status; + this.indices = indices; + } + public int getActiveShards() { return activeShards; } diff --git a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java index 1adf3188b0d34..e6032c52585ec 100644 --- a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java +++ b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java @@ -20,7 +20,6 @@ package org.elasticsearch.cluster.routing.allocation; import org.apache.logging.log4j.message.ParameterizedMessage; -import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.ClusterInfoService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.RestoreInProgress; @@ -132,9 +131,9 @@ protected ClusterState buildResultAndLogHealthChange(ClusterState oldState, Rout } final ClusterState newState = newStateBuilder.build(); logClusterHealthStateChange( - TransportClusterHealthAction.calculateStateHealth(oldState), - TransportClusterHealthAction.calculateStateHealth(newState), - reason + new ClusterStateHealth(oldState), + new ClusterStateHealth(newState), + reason ); return newState; } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java index fd410c2a158f6..c0ef8261069b9 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java @@ -25,6 +25,7 @@ import org.elasticsearch.cluster.health.ClusterIndexHealth; import org.elasticsearch.cluster.health.ClusterIndexHealthTests; import org.elasticsearch.cluster.health.ClusterStateHealth; +import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; @@ -68,9 +69,7 @@ public void testClusterHealth() { int inFlight = randomIntBetween(0, 200); int delayedUnassigned = randomIntBetween(0, 200); TimeValue pendingTaskInQueueTime = TimeValue.timeValueMillis(randomIntBetween(1000, 100000)); - ClusterStateHealth stateHealth = TransportClusterHealthAction.calculateStateHealth(clusterState); - ClusterHealthResponse clusterHealth = new ClusterHealthResponse("bla", pendingTasks, inFlight, - delayedUnassigned, pendingTaskInQueueTime, false, stateHealth); + ClusterHealthResponse clusterHealth = new ClusterHealthResponse("bla", new String[] {MetaData.ALL}, clusterState, pendingTasks, inFlight, delayedUnassigned, pendingTaskInQueueTime); clusterHealth = maybeSerialize(clusterHealth); assertClusterHealth(clusterHealth); assertThat(clusterHealth.getNumberOfPendingTasks(), Matchers.equalTo(pendingTasks)); diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java index ba5626816f014..214f4fe360e6b 100644 --- a/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java @@ -19,7 +19,6 @@ package org.elasticsearch.cluster.health; import org.elasticsearch.Version; -import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.RoutingTableGenerator; @@ -50,7 +49,7 @@ public void testClusterIndexHealth() { RoutingTableGenerator.ShardCounter counter = new RoutingTableGenerator.ShardCounter(); IndexRoutingTable indexRoutingTable = routingTableGenerator.genIndexRoutingTable(indexMetaData, counter); - ClusterIndexHealth indexHealth = TransportClusterHealthAction.calculateIndexHealth(indexMetaData, indexRoutingTable); + ClusterIndexHealth indexHealth = new ClusterIndexHealth(indexMetaData, indexRoutingTable); assertIndexHealth(indexHealth, counter, indexMetaData); } diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterStateHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterStateHealthTests.java index 95eb30b7d76bc..0f5f4870ae1bb 100644 --- a/server/src/test/java/org/elasticsearch/cluster/health/ClusterStateHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterStateHealthTests.java @@ -176,7 +176,7 @@ public void testClusterHealth() throws IOException { String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames( clusterState, IndicesOptions.strictExpand(), (String[]) null ); - ClusterStateHealth clusterStateHealth = TransportClusterHealthAction.calculateStateHealth(clusterState, concreteIndices); + ClusterStateHealth clusterStateHealth = new ClusterStateHealth(clusterState, concreteIndices); logger.info("cluster status: {}, expected {}", clusterStateHealth.getStatus(), counter.status()); clusterStateHealth = maybeSerialize(clusterStateHealth); assertClusterHealth(clusterStateHealth, counter); @@ -189,7 +189,7 @@ public void testClusterHealthOnIndexCreation() { for (int i = 0; i < clusterStates.size(); i++) { // make sure cluster health is always YELLOW, up until the last state where it should be GREEN final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); + final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { assertThat(health.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); } else { @@ -206,7 +206,7 @@ public void testClusterHealthOnIndexCreationWithFailedAllocations() { // make sure cluster health is YELLOW up until the final cluster state, which contains primary shard // failed allocations that should make the cluster health RED final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); + final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { assertThat(health.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); } else { @@ -222,7 +222,7 @@ public void testClusterHealthOnClusterRecovery() { for (int i = 0; i < clusterStates.size(); i++) { // make sure cluster health is YELLOW up until the final cluster state, when it turns GREEN final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); + final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { assertThat(health.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); } else { @@ -239,7 +239,7 @@ public void testClusterHealthOnClusterRecoveryWithFailures() { // make sure cluster health is YELLOW up until the final cluster state, which contains primary shard // failed allocations that should make the cluster health RED final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); + final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { assertThat(health.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); } else { @@ -256,7 +256,7 @@ public void testClusterHealthOnClusterRecoveryWithPreviousAllocationIds() { // because there were previous allocation ids, we should be RED until the primaries are started, // then move to YELLOW, and the last state should be GREEN when all shards have been started final ClusterState clusterState = clusterStates.get(i); - final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); + final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); if (i < clusterStates.size() - 1) { // if the inactive primaries are due solely to recovery (not failed allocation or previously being allocated), // then cluster health is YELLOW, otherwise RED @@ -275,7 +275,7 @@ public void testClusterHealthOnClusterRecoveryWithPreviousAllocationIdsAndAlloca final String indexName = "test-idx"; final String[] indices = new String[] { indexName }; for (final ClusterState clusterState : simulateClusterRecoveryStates(indexName, true, true)) { - final ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState, indices); + final ClusterStateHealth health = new ClusterStateHealth(clusterState, indices); // if the inactive primaries are due solely to recovery (not failed allocation or previously being allocated) // then cluster health is YELLOW, otherwise RED if (primaryInactiveDueToRecovery(indexName, clusterState)) { diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/PrimaryTermsTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/PrimaryTermsTests.java index 5971eecfcb88f..65526896864d6 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/PrimaryTermsTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/PrimaryTermsTests.java @@ -20,7 +20,6 @@ package org.elasticsearch.cluster.routing; import org.elasticsearch.Version; -import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ESAllocationTestCase; import org.elasticsearch.cluster.health.ClusterStateHealth; @@ -130,7 +129,7 @@ private void applyRerouteResult(ClusterState newClusterState) { builder.metaData(MetaData.builder(newClusterState.metaData()).version(newClusterState.metaData().version() + 1)); } this.clusterState = builder.build(); - final ClusterStateHealth clusterHealth = TransportClusterHealthAction.calculateStateHealth(clusterState); + final ClusterStateHealth clusterHealth = new ClusterStateHealth(clusterState); logger.info("applied reroute. active shards: p [{}], t [{}], init shards: [{}], relocating: [{}]", clusterHealth.getActivePrimaryShards(), clusterHealth.getActiveShards(), clusterHealth.getInitializingShards(), clusterHealth.getRelocatingShards()); diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/RoutingTableGenerator.java b/server/src/test/java/org/elasticsearch/cluster/routing/RoutingTableGenerator.java index a62f0e8c0faa2..f53cce07daea0 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/RoutingTableGenerator.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/RoutingTableGenerator.java @@ -24,7 +24,7 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.index.shard.ShardId; -import static org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction.getInactivePrimaryHealth; +import static org.elasticsearch.cluster.health.ClusterShardHealth.getInactivePrimaryHealth; public class RoutingTableGenerator { private static int node_id = 1; diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DecisionsImpactOnClusterHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DecisionsImpactOnClusterHealthTests.java index f227e95453877..bee2275743bac 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DecisionsImpactOnClusterHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DecisionsImpactOnClusterHealthTests.java @@ -20,7 +20,6 @@ package org.elasticsearch.cluster.routing.allocation; import org.elasticsearch.Version; -import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ESAllocationTestCase; @@ -153,7 +152,7 @@ private ClusterState runAllocationTest(final Settings settings, clusterState = ClusterState.builder(clusterState).routingTable(routingTable).build(); logger.info("--> assert cluster health"); - ClusterStateHealth health = TransportClusterHealthAction.calculateStateHealth(clusterState); + ClusterStateHealth health = new ClusterStateHealth(clusterState); assertThat(health.getStatus(), equalTo(expectedStatus)); return clusterState; diff --git a/server/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java b/server/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java index 5292a2d20b0a9..e3687548190a3 100644 --- a/server/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java +++ b/server/src/test/java/org/elasticsearch/gateway/PrimaryShardAllocatorTests.java @@ -21,7 +21,6 @@ import org.apache.lucene.index.CorruptIndexException; import org.elasticsearch.Version; -import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ESAllocationTestCase; @@ -429,7 +428,7 @@ private void assertClusterHealthStatus(RoutingAllocation allocation, ClusterHeal ClusterState clusterState = ClusterState.builder(new ClusterName("test-cluster")) .routingTable(newRoutingTable) .build(); - ClusterStateHealth clusterStateHealth = TransportClusterHealthAction.calculateStateHealth(clusterState); + ClusterStateHealth clusterStateHealth = new ClusterStateHealth(clusterState); assertThat(clusterStateHealth.getStatus().ordinal(), lessThanOrEqualTo(expectedStatus.ordinal())); } diff --git a/server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java b/server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java index 92803c3798cce..cd592c9ed1e9c 100644 --- a/server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java +++ b/server/src/test/java/org/elasticsearch/rest/action/cat/RestIndicesActionTests.java @@ -21,14 +21,12 @@ import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; -import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.admin.indices.stats.CommonStats; import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; import org.elasticsearch.action.admin.indices.stats.IndicesStatsTests; import org.elasticsearch.action.admin.indices.stats.ShardStats; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.health.ClusterStateHealth; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.MetaData; @@ -108,9 +106,8 @@ public void testBuildTable() { for (int i = 0; i < indices.length; i++) { indicesStr[i] = indices[i].getName(); } - ClusterStateHealth stateHealth = TransportClusterHealthAction.calculateStateHealth(clusterState); final ClusterHealthResponse clusterHealth = new ClusterHealthResponse( - clusterState.getClusterName().value(), 0, 0, 0, TimeValue.timeValueMillis(1000L), false, stateHealth + clusterState.getClusterName().value(), indicesStr, clusterState, 0, 0, 0, TimeValue.timeValueMillis(1000L) ); final Table table = action.buildTable(new FakeRestRequest(), indices, clusterHealth, randomIndicesStatsResponse(indices), metaData); From 5a6ed00106c829c63fa1466522d40c8a7880021c Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Tue, 29 May 2018 16:55:58 +0700 Subject: [PATCH 06/14] Round 4 - Revert ClusterHealthResponsesTests fixes - ClusterClientIT method renames - Documentation fixes - Revert ClusterHealthResponse::toString --- .../elasticsearch/client/ClusterClientIT.java | 22 ++++++------ .../elasticsearch/client/RequestTests.java | 2 +- .../ClusterClientDocumentationIT.java | 2 +- .../high-level/cluster/health.asciidoc | 6 ++-- docs/reference/cluster/health.asciidoc | 4 +++ .../cluster/health/ClusterHealthResponse.java | 12 ++----- .../common/util/CollectionUtils.java | 14 ++++---- .../health/ClusterHealthResponsesTests.java | 36 +++++++++---------- 8 files changed, 45 insertions(+), 53 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java index a0c860123129c..c857fec600160 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java @@ -121,7 +121,7 @@ public void testClusterHealthGreen() throws IOException { assertThat(response.isTimedOut(), equalTo(false)); assertThat(response.status(), equalTo(RestStatus.OK)); assertThat(response.getStatus(), equalTo(ClusterHealthStatus.GREEN)); - emptyClusterAssertion(response); + assertNoIndices(response); } public void testClusterHealthYellowClusterLevel() throws IOException { @@ -132,7 +132,7 @@ public void testClusterHealthYellowClusterLevel() throws IOException { request.level("cluster"); ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); - yellowTenShardsClusterAssertion(response); + assertTenYellowShards(response); assertThat(response.getIndices().size(), equalTo(0)); } @@ -144,14 +144,14 @@ public void testClusterHealthYellowIndicesLevel() throws IOException { request.level("indices"); ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); - yellowTenShardsClusterAssertion(response); + assertTenYellowShards(response); assertThat(response.getIndices().size(), equalTo(2)); for (Map.Entry entry : response.getIndices().entrySet()) { - indexAssertion(entry.getKey(), entry.getValue(), true); + assertYellowIndex(entry.getKey(), entry.getValue(), true); } } - private void yellowTenShardsClusterAssertion(ClusterHealthResponse response) { + private static void assertTenYellowShards(ClusterHealthResponse response) { assertThat(response, notNullValue()); assertThat(response.isTimedOut(), equalTo(false)); assertThat(response.status(), equalTo(RestStatus.OK)); @@ -188,10 +188,10 @@ public void testClusterHealthYellowSpecificIndex() throws IOException { assertThat(response.getActiveShardsPercent(), equalTo(50d)); assertThat(response.getIndices().size(), equalTo(1)); Map.Entry index = response.getIndices().entrySet().iterator().next(); - indexAssertion(index.getKey(), index.getValue(), false); + assertYellowIndex(index.getKey(), index.getValue(), false); } - private void indexAssertion(String indexName, ClusterIndexHealth indexHealth, boolean emptyShards) { + private static void assertYellowIndex(String indexName, ClusterIndexHealth indexHealth, boolean emptyShards) { assertThat(indexHealth, notNullValue()); assertThat(indexHealth.getIndex(),equalTo(indexName)); assertThat(indexHealth.getActivePrimaryShards(),equalTo(5)); @@ -206,12 +206,12 @@ private void indexAssertion(String indexName, ClusterIndexHealth indexHealth, bo } else { assertThat(indexHealth.getShards().size(), equalTo(5)); for (Map.Entry entry : indexHealth.getShards().entrySet()) { - shardAssertion(entry.getKey(), entry.getValue()); + assertYellowShard(entry.getKey(), entry.getValue()); } } } - private void shardAssertion(int shardId, ClusterShardHealth shardHealth) { + private static void assertYellowShard(int shardId, ClusterShardHealth shardHealth) { assertThat(shardHealth, notNullValue()); assertThat(shardHealth.getShardId(), equalTo(shardId)); assertThat(shardHealth.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); @@ -230,10 +230,10 @@ public void testClusterHealthNotFoundIndex() throws IOException { assertThat(response.isTimedOut(), equalTo(true)); assertThat(response.status(), equalTo(RestStatus.REQUEST_TIMEOUT)); assertThat(response.getStatus(), equalTo(ClusterHealthStatus.RED)); - emptyClusterAssertion(response); + assertNoIndices(response); } - public static void emptyClusterAssertion(ClusterHealthResponse response) { + public static void assertNoIndices(ClusterHealthResponse response) { assertThat(response.getIndices(), equalTo(emptyMap())); assertThat(response.getActivePrimaryShards(), equalTo(0)); assertThat(response.getNumberOfDataNodes(), equalTo(1)); 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 b907af44eb81f..dc4c7a6ae0a10 100644 --- 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 @@ -1345,7 +1345,7 @@ public void testClusterHealth() { break; case "masterTimeout": expectedParams.put("timeout", "30s"); - healthRequest.timeout(masterTimeout); + healthRequest.masterNodeTimeout(masterTimeout); expectedParams.put("master_timeout", masterTimeout); break; case "both": diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java index 40381115fb84d..35d4fb88e751f 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java @@ -296,7 +296,7 @@ public void testClusterHealth() throws IOException { assertThat(response.isTimedOut(), equalTo(false)); assertThat(response.status(), equalTo(RestStatus.OK)); assertThat(response.getStatus(), equalTo(ClusterHealthStatus.GREEN)); - ClusterClientIT.emptyClusterAssertion(response); + ClusterClientIT.assertNoIndices(response); } public void testClusterHealthAsync() throws Exception { diff --git a/docs/java-rest/high-level/cluster/health.asciidoc b/docs/java-rest/high-level/cluster/health.asciidoc index 11bc51bf2e8ea..7a0ca2d21f772 100644 --- a/docs/java-rest/high-level/cluster/health.asciidoc +++ b/docs/java-rest/high-level/cluster/health.asciidoc @@ -24,7 +24,7 @@ Indices which should be checked can be passed in the constructor: include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-indices-ctr] -------------------------------------------------- -Or using the method: +Or using the corresponding setter method: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- @@ -33,7 +33,7 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-ind ==== Other parameters -Other parameters can be passed only through methods: +Other parameters can be passed only through setters methods: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- @@ -109,7 +109,7 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-execute] [[java-rest-high-cluster-health-async]] ==== Asynchronous Execution -The asynchronous execution of a cluster update settings requires both the +The asynchronous execution of a cluster health request requires both the `ClusterHealthRequest` instance and an `ActionListener` instance to be passed to the asynchronous method: diff --git a/docs/reference/cluster/health.asciidoc b/docs/reference/cluster/health.asciidoc index 37e055018091a..4e6f89d84946b 100644 --- a/docs/reference/cluster/health.asciidoc +++ b/docs/reference/cluster/health.asciidoc @@ -104,6 +104,10 @@ The cluster health API accepts the following request parameters: Alternatively, it is possible to use `ge(N)`, `le(N)`, `gt(N)` and `lt(N)` notation. +`wait_for_events`:: + Can be one of `immediate`, `urgent`, `high`, `normal`, `low`, `languid`. + Wait until all currently queued events with the given priority are processed. + `timeout`:: A time based parameter controlling how long to wait if one of the wait_for_XXX are provided. Defaults to `30s`. diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java index 23168caa7182d..d3b8e94f8fc0e 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java @@ -25,6 +25,7 @@ import org.elasticsearch.cluster.health.ClusterIndexHealth; import org.elasticsearch.cluster.health.ClusterStateHealth; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.TimeValue; @@ -309,16 +310,7 @@ public void writeTo(StreamOutput out) throws IOException { @Override public String toString() { - return "ClusterHealthResponse{" + - "clusterName='" + clusterName + '\'' + - ", numberOfPendingTasks=" + numberOfPendingTasks + - ", numberOfInFlightFetch=" + numberOfInFlightFetch + - ", delayedUnassignedShards=" + delayedUnassignedShards + - ", taskMaxWaitingTime=" + taskMaxWaitingTime + - ", timedOut=" + timedOut + - ", clusterStateHealth=" + clusterStateHealth + - ", clusterHealthStatus=" + clusterHealthStatus + - '}'; + return Strings.toString(this); } @Override diff --git a/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java b/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java index 4ddd1cd7f698d..08d02cdea3172 100644 --- a/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java +++ b/server/src/main/java/org/elasticsearch/common/util/CollectionUtils.java @@ -19,13 +19,6 @@ package org.elasticsearch.common.util; -import com.carrotsearch.hppc.ObjectArrayList; -import org.apache.lucene.util.BytesRef; -import org.apache.lucene.util.BytesRefArray; -import org.apache.lucene.util.BytesRefBuilder; -import org.apache.lucene.util.InPlaceMergeSorter; -import org.apache.lucene.util.IntroSorter; - import java.nio.file.Path; import java.util.AbstractList; import java.util.ArrayList; @@ -41,6 +34,13 @@ import java.util.RandomAccess; import java.util.Set; +import com.carrotsearch.hppc.ObjectArrayList; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.BytesRefArray; +import org.apache.lucene.util.BytesRefBuilder; +import org.apache.lucene.util.InPlaceMergeSorter; +import org.apache.lucene.util.IntroSorter; + /** Collections-related utility methods. */ public class CollectionUtils { diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java index c0ef8261069b9..1aad9b78033aa 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java @@ -51,19 +51,19 @@ public class ClusterHealthResponsesTests extends AbstractStreamableXContentTestCase { private final String level = randomFrom("shards", "indices", "cluster"); - public void testIsTimeout() { - ClusterHealthResponse responseTimeout = new ClusterHealthResponse(); - responseTimeout.setTimedOut(true); - assertEquals(RestStatus.REQUEST_TIMEOUT, responseTimeout.status()); - } - - public void testIsNotTimeout() { - ClusterHealthResponse responseOk = new ClusterHealthResponse(); - responseOk.setTimedOut(false); - assertEquals(RestStatus.OK, responseOk.status()); + public void testIsTimeout() throws IOException { + ClusterHealthResponse res = new ClusterHealthResponse(); + for (int i = 0; i < 5; i++) { + res.setTimedOut(randomBoolean()); + if (res.isTimedOut()) { + assertEquals(RestStatus.REQUEST_TIMEOUT, res.status()); + } else { + assertEquals(RestStatus.OK, res.status()); + } + } } - public void testClusterHealth() { + public void testClusterHealth() throws IOException { ClusterState clusterState = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.getDefault(Settings.EMPTY)).build(); int pendingTasks = randomIntBetween(0, 200); int inFlight = randomIntBetween(0, 200); @@ -91,16 +91,12 @@ private void assertClusterHealth(ClusterHealthResponse clusterHealth) { assertThat(clusterHealth.getNumberOfDataNodes(), Matchers.equalTo(clusterStateHealth.getNumberOfDataNodes())); } - ClusterHealthResponse maybeSerialize(ClusterHealthResponse clusterHealth) { + ClusterHealthResponse maybeSerialize(ClusterHealthResponse clusterHealth) throws IOException { if (randomBoolean()) { - try { - BytesStreamOutput out = new BytesStreamOutput(); - clusterHealth.writeTo(out); - StreamInput in = out.bytes().streamInput(); - clusterHealth = ClusterHealthResponse.readResponseFrom(in); - } catch (IOException e) { - throw new IllegalStateException(e); - } + BytesStreamOutput out = new BytesStreamOutput(); + clusterHealth.writeTo(out); + StreamInput in = out.bytes().streamInput(); + clusterHealth = ClusterHealthResponse.readResponseFrom(in); } return clusterHealth; } From d4486ab7cb2b3a580d35795bba894ce06eab6a17 Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Fri, 1 Jun 2018 19:09:38 +0700 Subject: [PATCH 07/14] Fix tests after moving to 1 shard by default --- .../elasticsearch/client/ClusterClientIT.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java index 3189cb16e5cb5..a7f7d7c2226de 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java @@ -198,13 +198,13 @@ private static void assertTenYellowShards(ClusterHealthResponse response) { assertThat(response.isTimedOut(), equalTo(false)); assertThat(response.status(), equalTo(RestStatus.OK)); assertThat(response.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); - assertThat(response.getActivePrimaryShards(), equalTo(10)); + assertThat(response.getActivePrimaryShards(), equalTo(2)); assertThat(response.getNumberOfDataNodes(), equalTo(1)); assertThat(response.getNumberOfNodes(), equalTo(1)); - assertThat(response.getActiveShards(), equalTo(10)); + assertThat(response.getActiveShards(), equalTo(2)); assertThat(response.getDelayedUnassignedShards(), equalTo(0)); assertThat(response.getInitializingShards(), equalTo(0)); - assertThat(response.getUnassignedShards(), equalTo(10)); + assertThat(response.getUnassignedShards(), equalTo(2)); assertThat(response.getActiveShardsPercent(), equalTo(50d)); } @@ -220,13 +220,13 @@ public void testClusterHealthYellowSpecificIndex() throws IOException { assertThat(response.isTimedOut(), equalTo(false)); assertThat(response.status(), equalTo(RestStatus.OK)); assertThat(response.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); - assertThat(response.getActivePrimaryShards(), equalTo(5)); + assertThat(response.getActivePrimaryShards(), equalTo(1)); assertThat(response.getNumberOfDataNodes(), equalTo(1)); assertThat(response.getNumberOfNodes(), equalTo(1)); - assertThat(response.getActiveShards(), equalTo(5)); + assertThat(response.getActiveShards(), equalTo(1)); assertThat(response.getDelayedUnassignedShards(), equalTo(0)); assertThat(response.getInitializingShards(), equalTo(0)); - assertThat(response.getUnassignedShards(), equalTo(5)); + assertThat(response.getUnassignedShards(), equalTo(1)); assertThat(response.getActiveShardsPercent(), equalTo(50d)); assertThat(response.getIndices().size(), equalTo(1)); Map.Entry index = response.getIndices().entrySet().iterator().next(); @@ -236,17 +236,17 @@ public void testClusterHealthYellowSpecificIndex() throws IOException { private static void assertYellowIndex(String indexName, ClusterIndexHealth indexHealth, boolean emptyShards) { assertThat(indexHealth, notNullValue()); assertThat(indexHealth.getIndex(),equalTo(indexName)); - assertThat(indexHealth.getActivePrimaryShards(),equalTo(5)); - assertThat(indexHealth.getActiveShards(),equalTo(5)); + assertThat(indexHealth.getActivePrimaryShards(),equalTo(1)); + assertThat(indexHealth.getActiveShards(),equalTo(1)); assertThat(indexHealth.getNumberOfReplicas(),equalTo(1)); assertThat(indexHealth.getInitializingShards(),equalTo(0)); - assertThat(indexHealth.getUnassignedShards(),equalTo(5)); + assertThat(indexHealth.getUnassignedShards(),equalTo(1)); assertThat(indexHealth.getRelocatingShards(),equalTo(0)); assertThat(indexHealth.getStatus(),equalTo(ClusterHealthStatus.YELLOW)); if (emptyShards) { assertThat(indexHealth.getShards().size(), equalTo(0)); } else { - assertThat(indexHealth.getShards().size(), equalTo(5)); + assertThat(indexHealth.getShards().size(), equalTo(1)); for (Map.Entry entry : indexHealth.getShards().entrySet()) { assertYellowShard(entry.getKey(), entry.getValue()); } From bd24a05b9ee0620bdc5662fe0d478bed30a34fde Mon Sep 17 00:00:00 2001 From: javanna Date: Wed, 6 Jun 2018 14:31:13 +0200 Subject: [PATCH 08/14] small update --- .../client/RequestConverters.java | 4 +-- .../elasticsearch/client/ClusterClientIT.java | 4 +-- .../client/RequestConvertersTests.java | 5 ++-- .../ClusterClientDocumentationIT.java | 2 +- .../cluster/health/ClusterHealthRequest.java | 26 ++++++++++--------- .../cluster/health/ClusterIndexHealth.java | 21 ++++++--------- .../cluster/health/ClusterShardHealth.java | 22 ++++++---------- .../health/ClusterHealthResponsesTests.java | 7 ++--- .../health/ClusterIndexHealthTests.java | 18 ++++++++----- .../health/ClusterShardHealthTests.java | 4 ++- .../test/AbstractSerializingTestCase.java | 4 +++ .../AbstractStreamableXContentTestCase.java | 4 +++ .../test/AbstractXContentTestCase.java | 4 +++ 13 files changed, 67 insertions(+), 58 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java index a277e5b9aa05d..46c2f3eeaae7b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java @@ -1069,8 +1069,8 @@ Params withWaitForNodes(String waitForNodes) { return putParam("wait_for_nodes", waitForNodes); } - Params withLevel(String level) { - return putParam("level", level); + Params withLevel(ClusterHealthRequest.Level level) { + return putParam("level", level.name().toLowerCase(Locale.ROOT)); } Params withWaitForEvents(Priority waitForEvents) { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java index a7f7d7c2226de..220484c04c238 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java @@ -171,7 +171,7 @@ public void testClusterHealthYellowClusterLevel() throws IOException { createIndex("index2", Settings.EMPTY); ClusterHealthRequest request = new ClusterHealthRequest(); request.timeout("5s"); - request.level("cluster"); + request.level(ClusterHealthRequest.Level.CLUSTER); ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); assertTenYellowShards(response); @@ -183,7 +183,7 @@ public void testClusterHealthYellowIndicesLevel() throws IOException { createIndex("index2", Settings.EMPTY); ClusterHealthRequest request = new ClusterHealthRequest(); request.timeout("5s"); - request.level("indices"); + request.level(ClusterHealthRequest.Level.INDICES); ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); assertTenYellowShards(response); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index 0555a262a09cd..b55f96ef93e76 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -95,7 +95,6 @@ import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.index.RandomCreateIndexGenerator; import org.elasticsearch.index.VersionType; import org.elasticsearch.index.query.TermQueryBuilder; @@ -1463,9 +1462,9 @@ public void testClusterHealth() { } setRandomWaitForActiveShards(healthRequest::waitForActiveShards, expectedParams, "0"); if (randomBoolean()) { - String level = randomFrom("cluster", "indices", "shards"); + ClusterHealthRequest.Level level = randomFrom(ClusterHealthRequest.Level.values()); healthRequest.level(level); - expectedParams.put("level", level); + expectedParams.put("level", level.name().toLowerCase(Locale.ROOT)); } else { expectedParams.put("level", "shards"); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java index 2914dc06a4d3d..ef8724ea1e3cb 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java @@ -302,7 +302,7 @@ public void testClusterHealth() throws IOException { // end::health-request-wait-status // tag::health-request-level - request.level("cluster"); // <1> + request.level(ClusterHealthRequest.Level.CLUSTER); // <1> // end::health-request-level // tag::health-request-wait-relocation diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java index ca952f70e95ad..59a291888d09e 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java @@ -33,6 +33,7 @@ import org.elasticsearch.common.unit.TimeValue; import java.io.IOException; +import java.util.Objects; import java.util.concurrent.TimeUnit; public class ClusterHealthRequest extends MasterNodeReadRequest implements IndicesRequest.Replaceable { @@ -46,11 +47,10 @@ public class ClusterHealthRequest extends MasterNodeReadRequest { - private final String level = randomFrom("shards", "indices", "cluster"); + private final ClusterHealthRequest.Level level = randomFrom(ClusterHealthRequest.Level.values()); - public void testIsTimeout() throws IOException { + public void testIsTimeout() { ClusterHealthResponse res = new ClusterHealthResponse(); for (int i = 0; i < 5; i++) { res.setTimedOut(randomBoolean()); @@ -131,7 +132,7 @@ protected ClusterHealthResponse createTestInstance() { @Override protected ToXContent.Params getToXContentParams() { - return new ToXContent.MapParams(Collections.singletonMap("level", level)); + return new ToXContent.MapParams(Collections.singletonMap("level", level.name().toLowerCase(Locale.ROOT))); } @Override diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java index 214f4fe360e6b..851ab63297a21 100644 --- a/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterIndexHealthTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.cluster.health; import org.elasticsearch.Version; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.RoutingTableGenerator; @@ -31,6 +32,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -39,13 +41,14 @@ import static org.hamcrest.CoreMatchers.equalTo; public class ClusterIndexHealthTests extends AbstractSerializingTestCase { - private final String level = randomFrom("shards", "indices"); + private final ClusterHealthRequest.Level level = randomFrom(ClusterHealthRequest.Level.SHARDS, ClusterHealthRequest.Level.INDICES); public void testClusterIndexHealth() { RoutingTableGenerator routingTableGenerator = new RoutingTableGenerator(); int numberOfShards = randomInt(3) + 1; int numberOfReplicas = randomInt(4); - IndexMetaData indexMetaData = IndexMetaData.builder("test1").settings(settings(Version.CURRENT)).numberOfShards(numberOfShards).numberOfReplicas(numberOfReplicas).build(); + IndexMetaData indexMetaData = IndexMetaData.builder("test1").settings(settings(Version.CURRENT)) + .numberOfShards(numberOfShards).numberOfReplicas(numberOfReplicas).build(); RoutingTableGenerator.ShardCounter counter = new RoutingTableGenerator.ShardCounter(); IndexRoutingTable indexRoutingTable = routingTableGenerator.genIndexRoutingTable(indexMetaData, counter); @@ -53,7 +56,8 @@ public void testClusterIndexHealth() { assertIndexHealth(indexHealth, counter, indexMetaData); } - private void assertIndexHealth(ClusterIndexHealth indexHealth, RoutingTableGenerator.ShardCounter counter, IndexMetaData indexMetaData) { + private void assertIndexHealth(ClusterIndexHealth indexHealth, RoutingTableGenerator.ShardCounter counter, + IndexMetaData indexMetaData) { assertThat(indexHealth.getStatus(), equalTo(counter.status())); assertThat(indexHealth.getNumberOfShards(), equalTo(indexMetaData.getNumberOfShards())); assertThat(indexHealth.getNumberOfReplicas(), equalTo(indexMetaData.getNumberOfReplicas())); @@ -75,9 +79,9 @@ protected ClusterIndexHealth createTestInstance() { return randomIndexHealth(randomAlphaOfLengthBetween(1, 10), level); } - public static ClusterIndexHealth randomIndexHealth(String indexName, String level) { + public static ClusterIndexHealth randomIndexHealth(String indexName, ClusterHealthRequest.Level level) { Map shards = new HashMap<>(); - if ("shards".equals(level)) { + if (level == ClusterHealthRequest.Level.SHARDS) { for (int i = 0; i < randomInt(5); i++) { shards.put(i, ClusterShardHealthTests.randomShardHealth(i)); } @@ -92,13 +96,13 @@ protected Writeable.Reader instanceReader() { } @Override - protected ClusterIndexHealth doParseInstance(XContentParser parser) { + protected ClusterIndexHealth doParseInstance(XContentParser parser) throws IOException { return ClusterIndexHealth.fromXContent(parser); } @Override protected ToXContent.Params getToXContentParams() { - return new ToXContent.MapParams(Collections.singletonMap("level", level)); + return new ToXContent.MapParams(Collections.singletonMap("level", level.name().toLowerCase(Locale.ROOT))); } @Override diff --git a/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java b/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java index 0cdf5923a6e9c..6ee0fc1ee0a67 100644 --- a/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/health/ClusterShardHealthTests.java @@ -22,6 +22,7 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractSerializingTestCase; +import java.io.IOException; import java.util.Arrays; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -29,7 +30,7 @@ public class ClusterShardHealthTests extends AbstractSerializingTestCase { @Override - protected ClusterShardHealth doParseInstance(XContentParser parser) { + protected ClusterShardHealth doParseInstance(XContentParser parser) throws IOException { return ClusterShardHealth.fromXContent(parser); } @@ -55,6 +56,7 @@ protected boolean supportsUnknownFields() { @Override protected Predicate getRandomFieldsExcludeFilter() { + //don't inject random fields at the root, which contains arbitrary shard ids return ""::equals; } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java index 8a129770f695c..aaa7376076531 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java @@ -21,6 +21,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; @@ -65,6 +66,9 @@ protected String[] getShuffleFieldsExceptions() { return Strings.EMPTY_ARRAY; } + /** + * Params that have to be provided when calling calling {@link ToXContent#toXContent(XContentBuilder, ToXContent.Params)} + */ protected ToXContent.Params getToXContentParams() { return ToXContent.EMPTY_PARAMS; } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java index 8f45a0bdbb436..4c9d2f7f95231 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java @@ -21,6 +21,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; @@ -65,6 +66,9 @@ protected String[] getShuffleFieldsExceptions() { return Strings.EMPTY_ARRAY; } + /** + * Params that have to be provided when calling calling {@link ToXContent#toXContent(XContentBuilder, ToXContent.Params)} + */ protected ToXContent.Params getToXContentParams() { return ToXContent.EMPTY_PARAMS; } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java index e23feca248914..fd5700c68a981 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java @@ -25,6 +25,7 @@ import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; @@ -130,6 +131,9 @@ protected String[] getShuffleFieldsExceptions() { return Strings.EMPTY_ARRAY; } + /** + * Params that have to be provided when calling calling {@link ToXContent#toXContent(XContentBuilder, ToXContent.Params)} + */ protected ToXContent.Params getToXContentParams() { return ToXContent.EMPTY_PARAMS; } From 803086792416298c53d5cc9f8acde364dfd29a72 Mon Sep 17 00:00:00 2001 From: javanna Date: Wed, 6 Jun 2018 15:43:20 +0200 Subject: [PATCH 09/14] docs update --- .../elasticsearch/client/ClusterClientIT.java | 9 +-- .../client/RequestConvertersTests.java | 2 +- .../ClusterClientDocumentationIT.java | 79 +++++++++++++------ .../high-level/cluster/health.asciidoc | 27 ++++--- .../high-level/supported-apis.asciidoc | 2 - 5 files changed, 76 insertions(+), 43 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java index f13d791157ae8..2ae6f9dc186ef 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java @@ -132,7 +132,7 @@ public void testClusterHealthYellowClusterLevel() throws IOException { request.level(ClusterHealthRequest.Level.CLUSTER); ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); - assertTenYellowShards(response); + assertYellowShards(response); assertThat(response.getIndices().size(), equalTo(0)); } @@ -144,14 +144,14 @@ public void testClusterHealthYellowIndicesLevel() throws IOException { request.level(ClusterHealthRequest.Level.INDICES); ClusterHealthResponse response = execute(request, highLevelClient().cluster()::health, highLevelClient().cluster()::healthAsync); - assertTenYellowShards(response); + assertYellowShards(response); assertThat(response.getIndices().size(), equalTo(2)); for (Map.Entry entry : response.getIndices().entrySet()) { assertYellowIndex(entry.getKey(), entry.getValue(), true); } } - private static void assertTenYellowShards(ClusterHealthResponse response) { + private static void assertYellowShards(ClusterHealthResponse response) { assertThat(response, notNullValue()); assertThat(response.isTimedOut(), equalTo(false)); assertThat(response.status(), equalTo(RestStatus.OK)); @@ -166,7 +166,6 @@ private static void assertTenYellowShards(ClusterHealthResponse response) { assertThat(response.getActiveShardsPercent(), equalTo(50d)); } - public void testClusterHealthYellowSpecificIndex() throws IOException { createIndex("index", Settings.EMPTY); createIndex("index2", Settings.EMPTY); @@ -233,7 +232,7 @@ public void testClusterHealthNotFoundIndex() throws IOException { assertNoIndices(response); } - public static void assertNoIndices(ClusterHealthResponse response) { + private static void assertNoIndices(ClusterHealthResponse response) { assertThat(response.getIndices(), equalTo(emptyMap())); assertThat(response.getActivePrimaryShards(), equalTo(0)); assertThat(response.getNumberOfDataNodes(), equalTo(1)); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index 78b32bed36e7c..44124b44819a6 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -2133,7 +2133,7 @@ private static void setRandomWaitForActiveShards(Consumer sett } setter.accept(ActiveShardCount.parseString(waitForActiveShardsString)); expectedParams.put("wait_for_active_shards", waitForActiveShardsString); - } else if (defaultValue != null){ + } else if (defaultValue != null) { expectedParams.put("wait_for_active_shards", defaultValue); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java index d5a416f65bf22..69296e206bea7 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java @@ -25,12 +25,13 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsResponse; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.support.ActiveShardCount; -import org.elasticsearch.client.ClusterClientIT; import org.elasticsearch.client.ESRestHighLevelClientTestCase; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.health.ClusterIndexHealth; +import org.elasticsearch.cluster.health.ClusterShardHealth; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; @@ -188,6 +189,7 @@ public void onFailure(Exception e) { public void testClusterHealth() throws IOException { RestHighLevelClient client = highLevelClient(); + client.indices().create(new CreateIndexRequest("index")); { // tag::health-request ClusterHealthRequest request = new ClusterHealthRequest(); @@ -252,23 +254,26 @@ public void testClusterHealth() throws IOException { ClusterHealthResponse response = client.cluster().health(request); // end::health-execute + assertThat(response.isTimedOut(), equalTo(false)); + assertThat(response.status(), equalTo(RestStatus.OK)); + assertThat(response.getStatus(), equalTo(ClusterHealthStatus.GREEN)); assertThat(response, notNullValue()); - { - // tag::health-response-general - String clusterName = response.getClusterName(); // <1> - ClusterHealthStatus status = response.getStatus(); // <2> - // end::health-response-general + // tag::health-response-general + String clusterName = response.getClusterName(); // <1> + ClusterHealthStatus status = response.getStatus(); // <2> + // end::health-response-general - // tag::health-response-request-status - boolean timedOut = response.isTimedOut(); // <1> - RestStatus restStatus = response.status(); // <2> - // end::health-response-request-status + // tag::health-response-request-status + boolean timedOut = response.isTimedOut(); // <1> + RestStatus restStatus = response.status(); // <2> + // end::health-response-request-status - // tag::health-response-nodes - int numberOfNodes = response.getNumberOfNodes(); // <1> - int numberOfDataNodes = response.getNumberOfDataNodes(); // <2> - // end::health-response-nodes + // tag::health-response-nodes + int numberOfNodes = response.getNumberOfNodes(); // <1> + int numberOfDataNodes = response.getNumberOfDataNodes(); // <2> + // end::health-response-nodes + { // tag::health-response-shards int activeShards = response.getActiveShards(); // <1> int activePrimaryShards = response.getActivePrimaryShards(); // <2> @@ -278,21 +283,43 @@ public void testClusterHealth() throws IOException { int delayedUnassignedShards = response.getDelayedUnassignedShards(); // <6> double activeShardsPercent = response.getActiveShardsPercent(); // <7> // end::health-response-shards + } - // tag::health-response-task - TimeValue taskMaxWaitingTime = response.getTaskMaxWaitingTime(); // <1> - int numberOfPendingTasks = response.getNumberOfPendingTasks(); // <2> - int numberOfInFlightFetch = response.getNumberOfInFlightFetch(); // <3> - // end::health-response-task + // tag::health-response-task + TimeValue taskMaxWaitingTime = response.getTaskMaxWaitingTime(); // <1> + int numberOfPendingTasks = response.getNumberOfPendingTasks(); // <2> + int numberOfInFlightFetch = response.getNumberOfInFlightFetch(); // <3> + // end::health-response-task - // tag::health-response-indices - Map indices = response.getIndices(); // <1> - // end::health-response-indices + // tag::health-response-indices + Map indices = response.getIndices(); // <1> + // end::health-response-indices + + { + // tag::health-response-index + ClusterIndexHealth index = indices.get("index"); // <1> + ClusterHealthStatus indexStatus = index.getStatus(); + int numberOfShards = index.getNumberOfShards(); + int numberOfReplicas = index.getNumberOfReplicas(); + int activeShards = index.getActiveShards(); + int activePrimaryShards = index.getActivePrimaryShards(); + int initializingShards = index.getInitializingShards(); + int relocatingShards = index.getRelocatingShards(); + int unassignedShards = index.getUnassignedShards(); + // end::health-response-index + + // tag::health-response-shard-details + Map shards = index.getShards(); // <1> + ClusterShardHealth shardHealth = shards.get(0); + int shardId = shardHealth.getShardId(); + ClusterHealthStatus shardStatus = shardHealth.getStatus(); + int active = shardHealth.getActiveShards(); + int initializing = shardHealth.getInitializingShards(); + int unassigned = shardHealth.getUnassignedShards(); + int relocating = shardHealth.getRelocatingShards(); + boolean primaryActive = shardHealth.isPrimaryActive(); + // end::health-response-shard-details } - assertThat(response.isTimedOut(), equalTo(false)); - assertThat(response.status(), equalTo(RestStatus.OK)); - assertThat(response.getStatus(), equalTo(ClusterHealthStatus.GREEN)); - ClusterClientIT.assertNoIndices(response); } public void testClusterHealthAsync() throws Exception { diff --git a/docs/java-rest/high-level/cluster/health.asciidoc b/docs/java-rest/high-level/cluster/health.asciidoc index 7a0ca2d21f772..0471dbec1cc57 100644 --- a/docs/java-rest/high-level/cluster/health.asciidoc +++ b/docs/java-rest/high-level/cluster/health.asciidoc @@ -33,7 +33,7 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-ind ==== Other parameters -Other parameters can be passed only through setters methods: +Other parameters can be passed only through setter methods: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- @@ -53,29 +53,26 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-mas -------------------------------------------------- include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-status] -------------------------------------------------- -<1> The status to wait for or better i.e. `green` > `yellow` > `red`. Accepts `ClusterHealthStatus` instance +<1> The status to wait (e.g. `green`, `yellow`, or `red`). Accepts a `ClusterHealthStatus` value. <2> Using predefined method ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-level] -------------------------------------------------- -<1> The level of representation. Possible levels: -- `cluster` - indices collection will be empty -- `indices` - indices will have empty shards collections -- `shards` - full details. Used by default +<1> The level of detail of the returned health information. Accepts a `ClusterHealthRequest.Level` value. ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-relocation] -------------------------------------------------- -<1> Wait for 0 relocating shards. Default `false` +<1> Wait for 0 relocating shards. Defaults to `false` ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-initializing] -------------------------------------------------- -<1> Wait for 0 initializing shards. Default `false` +<1> Wait for 0 initializing shards. Defaults to `false` ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- @@ -186,4 +183,16 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-ta -------------------------------------------------- include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-indices] -------------------------------------------------- -<1> Detailed information about indices in the cluster \ No newline at end of file +<1> Detailed information about indices in the cluster + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-index] +-------------------------------------------------- +<1> Detailed information about a specific index + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-response-shard-details] +-------------------------------------------------- +<1> Detailed information about a specific shard \ 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 1a682ee9b2cf5..b71a5fe179a38 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -107,11 +107,9 @@ include::indices/put_template.asciidoc[] The Java High Level REST Client supports the following Cluster APIs: * <> -* <> * <> include::cluster/put_settings.asciidoc[] -include::cluster/put_pipeline.asciidoc[] include::cluster/health.asciidoc[] == Ingest APIs From 27cbf8040446f52746b7565d7cec109466885aee Mon Sep 17 00:00:00 2001 From: javanna Date: Wed, 6 Jun 2018 16:15:02 +0200 Subject: [PATCH 10/14] adjust compile after merge --- .../java/org/elasticsearch/tasks/ListTasksResponseTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/elasticsearch/tasks/ListTasksResponseTests.java b/server/src/test/java/org/elasticsearch/tasks/ListTasksResponseTests.java index b280446db1c74..93e4cab5c700a 100644 --- a/server/src/test/java/org/elasticsearch/tasks/ListTasksResponseTests.java +++ b/server/src/test/java/org/elasticsearch/tasks/ListTasksResponseTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.action.TaskOperationFailure; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractXContentTestCase; @@ -146,7 +147,7 @@ public void testFromXContentWithFailures() throws IOException { boolean assertToXContentEquivalence = false; AbstractXContentTestCase.testFromXContent(NUMBER_OF_TEST_RUNS, instanceSupplier, supportsUnknownFields, Strings.EMPTY_ARRAY, getRandomFieldsExcludeFilter(), this::createParser, this::doParseInstance, - this::assertEqualInstances, assertToXContentEquivalence); + this::assertEqualInstances, assertToXContentEquivalence, ToXContent.EMPTY_PARAMS); } private static ListTasksResponse createTestInstanceWithFailures() { From 31b5beefebaada3088e4302ac117cee6618ae58b Mon Sep 17 00:00:00 2001 From: javanna Date: Wed, 6 Jun 2018 20:46:45 +0200 Subject: [PATCH 11/14] update docs --- .../client/documentation/ClusterClientDocumentationIT.java | 6 +++--- docs/java-rest/high-level/cluster/health.asciidoc | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java index 69296e206bea7..b529bdc578fd0 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java @@ -224,7 +224,7 @@ public void testClusterHealth() throws IOException { // end::health-request-wait-status // tag::health-request-level - request.level(ClusterHealthRequest.Level.CLUSTER); // <1> + request.level(ClusterHealthRequest.Level.SHARDS); // <1> // end::health-request-level // tag::health-request-wait-relocation @@ -242,8 +242,8 @@ public void testClusterHealth() throws IOException { // end::health-request-wait-nodes // tag::health-request-wait-active - request.waitForActiveShards(5); // <1> - request.waitForActiveShards(ActiveShardCount.ALL); // <2> + request.waitForActiveShards(ActiveShardCount.ALL); // <1> + request.waitForActiveShards(5); // <2> // end::health-request-wait-active // tag::health-request-local diff --git a/docs/java-rest/high-level/cluster/health.asciidoc b/docs/java-rest/high-level/cluster/health.asciidoc index 0471dbec1cc57..e77fd5c478cce 100644 --- a/docs/java-rest/high-level/cluster/health.asciidoc +++ b/docs/java-rest/high-level/cluster/health.asciidoc @@ -86,8 +86,9 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wai -------------------------------------------------- include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-active] -------------------------------------------------- -<1> Wait for `N` shards to be active in the cluster -<2> Wait for all shards to be active in the cluster + +<1> Wait for all shards to be active in the cluster +<2> Wait for `N` shards to be active in the cluster ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- From a52905825156d834a2ab2a894069b4fd28e522fd Mon Sep 17 00:00:00 2001 From: javanna Date: Wed, 6 Jun 2018 23:19:11 +0200 Subject: [PATCH 12/14] update docs test --- .../documentation/ClusterClientDocumentationIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java index b529bdc578fd0..3c848e64b7b71 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java @@ -219,8 +219,8 @@ public void testClusterHealth() throws IOException { // end::health-request-master-timeout // tag::health-request-wait-status - request.waitForStatus(ClusterHealthStatus.GREEN); // <1> - request.waitForGreenStatus(); // <2> + request.waitForStatus(ClusterHealthStatus.YELLOW); // <1> + request.waitForYellowStatus(); // <2> // end::health-request-wait-status // tag::health-request-level @@ -243,7 +243,7 @@ public void testClusterHealth() throws IOException { // tag::health-request-wait-active request.waitForActiveShards(ActiveShardCount.ALL); // <1> - request.waitForActiveShards(5); // <2> + request.waitForActiveShards(1); // <2> // end::health-request-wait-active // tag::health-request-local @@ -256,7 +256,7 @@ public void testClusterHealth() throws IOException { assertThat(response.isTimedOut(), equalTo(false)); assertThat(response.status(), equalTo(RestStatus.OK)); - assertThat(response.getStatus(), equalTo(ClusterHealthStatus.GREEN)); + assertThat(response.getStatus(), equalTo(ClusterHealthStatus.YELLOW)); assertThat(response, notNullValue()); // tag::health-response-general String clusterName = response.getClusterName(); // <1> From 27c90a8226c0b295e9724e3c7e2bee8d66b45264 Mon Sep 17 00:00:00 2001 From: javanna Date: Fri, 8 Jun 2018 11:18:40 +0200 Subject: [PATCH 13/14] fix compile error --- .../java/org/elasticsearch/tasks/CancelTasksResponseTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/elasticsearch/tasks/CancelTasksResponseTests.java b/server/src/test/java/org/elasticsearch/tasks/CancelTasksResponseTests.java index 3233edefb30d4..56b92bb1e25c6 100644 --- a/server/src/test/java/org/elasticsearch/tasks/CancelTasksResponseTests.java +++ b/server/src/test/java/org/elasticsearch/tasks/CancelTasksResponseTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractXContentTestCase; @@ -96,7 +97,7 @@ public void testFromXContentWithFailures() throws IOException { boolean assertToXContentEquivalence = false; AbstractXContentTestCase.testFromXContent(NUMBER_OF_TEST_RUNS, instanceSupplier, supportsUnknownFields, Strings.EMPTY_ARRAY, getRandomFieldsExcludeFilter(), this::createParser, this::doParseInstance, - this::assertEqualInstances, assertToXContentEquivalence); + this::assertEqualInstances, assertToXContentEquivalence, ToXContent.EMPTY_PARAMS); } private static CancelTasksResponse createTestInstanceWithFailures() { From f4c8b3f1b5704491c96f7b303fa1a2adf70eca1e Mon Sep 17 00:00:00 2001 From: javanna Date: Mon, 11 Jun 2018 18:51:16 +0200 Subject: [PATCH 14/14] address review comments --- .../client/documentation/ClusterClientDocumentationIT.java | 5 +++++ docs/java-rest/high-level/cluster/health.asciidoc | 6 ++++++ .../action/admin/cluster/health/ClusterHealthResponse.java | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java index 0cef2663202d2..84a124f764b38 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/ClusterClientDocumentationIT.java @@ -34,6 +34,7 @@ import org.elasticsearch.cluster.health.ClusterIndexHealth; import org.elasticsearch.cluster.health.ClusterShardHealth; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; +import org.elasticsearch.common.Priority; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.TimeValue; @@ -224,6 +225,10 @@ public void testClusterHealth() throws IOException { request.waitForYellowStatus(); // <2> // end::health-request-wait-status + // tag::health-request-wait-events + request.waitForEvents(Priority.NORMAL); // <1> + // end::health-request-wait-events + // tag::health-request-level request.level(ClusterHealthRequest.Level.SHARDS); // <1> // end::health-request-level diff --git a/docs/java-rest/high-level/cluster/health.asciidoc b/docs/java-rest/high-level/cluster/health.asciidoc index e77fd5c478cce..6c0f926f15f42 100644 --- a/docs/java-rest/high-level/cluster/health.asciidoc +++ b/docs/java-rest/high-level/cluster/health.asciidoc @@ -56,6 +56,12 @@ include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wai <1> The status to wait (e.g. `green`, `yellow`, or `red`). Accepts a `ClusterHealthStatus` value. <2> Using predefined method +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-wait-events] +-------------------------------------------------- +<1> The priority of the events to wait for. Accepts a `Priority` value. + ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- include-tagged::{doc-tests}/ClusterClientDocumentationIT.java[health-request-level] diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java index 8f6452d68c3f4..dec0bc7ef8523 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java @@ -151,7 +151,7 @@ public ClusterHealthResponse(String clusterName, String[] concreteIndices, Clust } public ClusterHealthResponse(String clusterName, String[] concreteIndices, ClusterState clusterState, int numberOfPendingTasks, - int numberOfInFlightFetch, int delayedUnassignedShards, TimeValue taskMaxWaitingTime) { + int numberOfInFlightFetch, int delayedUnassignedShards, TimeValue taskMaxWaitingTime) { this.clusterName = clusterName; this.numberOfPendingTasks = numberOfPendingTasks; this.numberOfInFlightFetch = numberOfInFlightFetch; @@ -165,7 +165,7 @@ public ClusterHealthResponse(String clusterName, String[] concreteIndices, Clust * For XContent Parser and serialization tests */ ClusterHealthResponse(String clusterName, int numberOfPendingTasks, int numberOfInFlightFetch, int delayedUnassignedShards, - TimeValue taskMaxWaitingTime, boolean timedOut, ClusterStateHealth clusterStateHealth) { + TimeValue taskMaxWaitingTime, boolean timedOut, ClusterStateHealth clusterStateHealth) { this.clusterName = clusterName; this.numberOfPendingTasks = numberOfPendingTasks; this.numberOfInFlightFetch = numberOfInFlightFetch;