Skip to content

Commit a2c3dae

Browse files
authored
Return 200 OK response code for a cluster health timeout (#78968)
Returning 408 for a cluster health timeout was deprecated in #78180 and backported to 7.x in #78940 Now we can do a breaking change in 8.0 respecting the user choice to run ES in 7.x compatible mode via the REST Compatibility layer. Fixes #70849
1 parent 733381b commit a2c3dae

File tree

11 files changed

+110
-38
lines changed

11 files changed

+110
-38
lines changed

client/rest-high-level/src/test/java/org/elasticsearch/client/ClusterClientIT.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,14 +334,9 @@ public void testClusterHealthNotFoundIndex() throws IOException {
334334

335335
assertThat(response, notNullValue());
336336
assertThat(response.isTimedOut(), equalTo(true));
337-
assertThat(response.status(), equalTo(RestStatus.REQUEST_TIMEOUT));
337+
assertThat(response.status(), equalTo(RestStatus.OK));
338338
assertThat(response.getStatus(), equalTo(ClusterHealthStatus.RED));
339339
assertNoIndices(response);
340-
assertCriticalWarnings(
341-
"The HTTP status code for a cluster health timeout will be changed from 408 to 200 in a "
342-
+ "future version. Set the [return_200_for_cluster_health_timeout] query parameter to [true] to suppress this message and "
343-
+ "opt in to the future behaviour now."
344-
);
345340
}
346341

347342
public void testRemoteInfo() throws Exception {

docs/changelog/78968.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pr: 78968
2+
summary: HTTP Status code has changed for the Cluster Health API in case of a server timeout
3+
area: CRUD
4+
type: breaking
5+
issues: []
6+
breaking:
7+
title: HTTP Status code has changed for the Cluster Health API in case of a server timeout
8+
area: API
9+
details: |-
10+
The cluster health API includes options for waiting
11+
for certain health conditions to be satisfied. If the requested conditions are
12+
not satisfied within a timeout then ES will send back a normal response
13+
including the field `"timed_out": true`. In earlier versions it would also use
14+
the HTTP response code `408 Request timeout` if the request timed out, and `200
15+
OK` otherwise. The `408 Request timeout` response code is not appropriate for
16+
this situation, so from version 8.0.0 ES will use the response code `200 OK`
17+
for both cases.
18+
impact: |-
19+
To detect a server timeout, check the `timed_out` field of the JSON response.
20+
notable: true

docs/reference/migration/migrate_8_0/cluster.asciidoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,21 @@ time out.
3232
*Impact* +
3333
Do not set `cluster.join.timeout` in your `elasticsearch.yml` file.
3434
====
35+
36+
.HTTP Status code has changed for the Cluster Health API in case of a server timeout.
37+
[%collapsible]
38+
====
39+
*Details* +
40+
The {ref}/cluster-health.html[cluster health API] includes options for waiting
41+
for certain health conditions to be satisfied. If the requested conditions are
42+
not satisfied within a timeout then {es} will send back a normal response
43+
including the field `"timed_out": true`. In earlier versions it would also use
44+
the HTTP response code `408 Request timeout` if the request timed out, and `200
45+
OK` otherwise. The `408 Request timeout` response code is not appropriate for
46+
this situation, so from version 8.0.0 {es} will use the response code `200 OK`
47+
for both cases.
48+
49+
*Impact* +
50+
To detect a server timeout, check the `timed_out` field of the JSON response.
51+
====
3552
// end::notable-breaking-changes[]

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.health/20_request_timeout.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
---
22
"cluster health request timeout on waiting for nodes":
3+
- skip:
4+
version: " - 8.0.99"
5+
reason: "Set for 7.99.99 when back-ported to 8.0"
36
- do:
4-
catch: request_timeout
57
cluster.health:
68
wait_for_nodes: 10
79
timeout: 1ms
@@ -19,8 +21,10 @@
1921

2022
---
2123
"cluster health request timeout waiting for active shards":
24+
- skip:
25+
version: " - 8.0.99"
26+
reason: "Set for 7.99.99 when back-ported to 8.0"
2227
- do:
23-
catch: request_timeout
2428
cluster.health:
2529
timeout: 1ms
2630
wait_for_active_shards: 5

server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponse.java

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
import org.elasticsearch.common.Strings;
1717
import org.elasticsearch.common.io.stream.StreamInput;
1818
import org.elasticsearch.common.io.stream.StreamOutput;
19-
import org.elasticsearch.common.logging.DeprecationLogger;
2019
import org.elasticsearch.common.xcontent.StatusToXContentObject;
20+
import org.elasticsearch.core.RestApiVersion;
2121
import org.elasticsearch.core.TimeValue;
2222
import org.elasticsearch.rest.RestStatus;
23-
import org.elasticsearch.rest.action.search.RestSearchAction;
2423
import org.elasticsearch.xcontent.ConstructingObjectParser;
2524
import org.elasticsearch.xcontent.ObjectParser;
2625
import org.elasticsearch.xcontent.ParseField;
@@ -57,7 +56,6 @@ public class ClusterHealthResponse extends ActionResponse implements StatusToXCo
5756
private static final String INITIALIZING_SHARDS = "initializing_shards";
5857
private static final String UNASSIGNED_SHARDS = "unassigned_shards";
5958
private static final String INDICES = "indices";
60-
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestSearchAction.class);
6159

6260
private static final ConstructingObjectParser<ClusterHealthResponse, Void> PARSER = new ConstructingObjectParser<>(
6361
"cluster_health_response",
@@ -122,12 +120,6 @@ public class ClusterHealthResponse extends ActionResponse implements StatusToXCo
122120
XContentParser parser,
123121
Void context,
124122
String index) -> ClusterIndexHealth.innerFromXContent(parser, index);
125-
static final String ES_CLUSTER_HEALTH_REQUEST_TIMEOUT_200_KEY = "return_200_for_cluster_health_timeout";
126-
static final String CLUSTER_HEALTH_REQUEST_TIMEOUT_DEPRECATION_MSG = "The HTTP status code for a cluster health timeout "
127-
+ "will be changed from 408 to 200 in a future version. Set the ["
128-
+ ES_CLUSTER_HEALTH_REQUEST_TIMEOUT_200_KEY
129-
+ "] "
130-
+ "query parameter to [true] to suppress this message and opt in to the future behaviour now.";
131123

132124
static {
133125
// ClusterStateHealth fields
@@ -351,15 +343,16 @@ public String toString() {
351343

352344
@Override
353345
public RestStatus status() {
354-
if (isTimedOut() == false) {
355-
return RestStatus.OK;
356-
}
357-
if (return200ForClusterHealthTimeout) {
358-
return RestStatus.OK;
359-
} else {
360-
deprecationLogger.compatibleCritical("cluster_health_request_timeout", CLUSTER_HEALTH_REQUEST_TIMEOUT_DEPRECATION_MSG);
346+
return status(RestApiVersion.current());
347+
}
348+
349+
@Override
350+
public RestStatus status(RestApiVersion restApiVersion) {
351+
// Legacy behaviour
352+
if (isTimedOut() && restApiVersion == RestApiVersion.V_7 && return200ForClusterHealthTimeout == false) {
361353
return RestStatus.REQUEST_TIMEOUT;
362354
}
355+
return RestStatus.OK;
363356
}
364357

365358
@Override

server/src/main/java/org/elasticsearch/common/xcontent/StatusToXContentObject.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
package org.elasticsearch.common.xcontent;
99

10+
import org.elasticsearch.core.RestApiVersion;
1011
import org.elasticsearch.rest.RestStatus;
1112
import org.elasticsearch.xcontent.ToXContentObject;
1213

@@ -20,4 +21,8 @@ public interface StatusToXContentObject extends ToXContentObject {
2021
* Returns the REST status to make sure it is returned correctly
2122
*/
2223
RestStatus status();
24+
25+
default RestStatus status(RestApiVersion restApiVersion) {
26+
return status();
27+
}
2328
}

server/src/main/java/org/elasticsearch/rest/action/RestStatusToXContentListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public RestStatusToXContentListener(RestChannel channel, Function<Response, Stri
4444
public RestResponse buildResponse(Response response, XContentBuilder builder) throws Exception {
4545
assert response.isFragment() == false; // would be nice if we could make default methods final
4646
response.toXContent(builder, channel.request());
47-
RestResponse restResponse = new BytesRestResponse(response.status(), builder);
47+
RestResponse restResponse = new BytesRestResponse(response.status(builder.getRestApiVersion()), builder);
4848
if (RestStatus.CREATED == restResponse.status()) {
4949
final String location = extractLocation.apply(response);
5050
if (location != null) {

server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClusterHealthAction.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
import org.elasticsearch.cluster.health.ClusterHealthStatus;
1616
import org.elasticsearch.common.Priority;
1717
import org.elasticsearch.common.Strings;
18+
import org.elasticsearch.common.logging.DeprecationCategory;
19+
import org.elasticsearch.common.logging.DeprecationLogger;
1820
import org.elasticsearch.rest.BaseRestHandler;
1921
import org.elasticsearch.rest.RestRequest;
2022
import org.elasticsearch.rest.action.RestStatusToXContentListener;
23+
import org.elasticsearch.rest.action.search.RestSearchAction;
2124

2225
import java.io.IOException;
2326
import java.util.Collections;
@@ -30,6 +33,12 @@
3033

3134
public class RestClusterHealthAction extends BaseRestHandler {
3235

36+
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestSearchAction.class);
37+
private static final String RETURN_200_FOR_CLUSTER_HEALTH_TIMEOUT = "return_200_for_cluster_health_timeout";
38+
private static final String CLUSTER_HEALTH_REQUEST_TIMEOUT_DEPRECATION_MSG = "the ["
39+
+ RETURN_200_FOR_CLUSTER_HEALTH_TIMEOUT
40+
+ "] parameter is deprecated and will be removed in a future release.";
41+
3342
@Override
3443
public List<Route> routes() {
3544
return List.of(new Route(GET, "/_cluster/health"), new Route(GET, "/_cluster/health/{index}"));
@@ -81,9 +90,15 @@ public static ClusterHealthRequest fromRequest(final RestRequest request) {
8190
if (request.param("wait_for_events") != null) {
8291
clusterHealthRequest.waitForEvents(Priority.valueOf(request.param("wait_for_events").toUpperCase(Locale.ROOT)));
8392
}
84-
clusterHealthRequest.return200ForClusterHealthTimeout(
85-
request.paramAsBoolean("return_200_for_cluster_health_timeout", clusterHealthRequest.doesReturn200ForClusterHealthTimeout())
86-
);
93+
String return200ForClusterHealthTimeout = request.param(RETURN_200_FOR_CLUSTER_HEALTH_TIMEOUT);
94+
if (return200ForClusterHealthTimeout != null) {
95+
deprecationLogger.warn(
96+
DeprecationCategory.API,
97+
"cluster_health_request_timeout",
98+
CLUSTER_HEALTH_REQUEST_TIMEOUT_DEPRECATION_MSG
99+
);
100+
}
101+
clusterHealthRequest.return200ForClusterHealthTimeout(Boolean.parseBoolean(return200ForClusterHealthTimeout));
87102
return clusterHealthRequest;
88103
}
89104

server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthResponsesTests.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.common.io.stream.StreamInput;
2121
import org.elasticsearch.common.io.stream.Writeable;
2222
import org.elasticsearch.common.settings.Settings;
23+
import org.elasticsearch.core.RestApiVersion;
2324
import org.elasticsearch.core.TimeValue;
2425
import org.elasticsearch.rest.RestStatus;
2526
import org.elasticsearch.test.AbstractSerializingTestCase;
@@ -43,24 +44,39 @@
4344
public class ClusterHealthResponsesTests extends AbstractSerializingTestCase<ClusterHealthResponse> {
4445
private final ClusterHealthRequest.Level level = randomFrom(ClusterHealthRequest.Level.values());
4546

46-
public void testIsTimeout() {
47+
public void testIsTimeoutReturns200ByDefault() {
4748
ClusterHealthResponse res = new ClusterHealthResponse("", Strings.EMPTY_ARRAY, ClusterState.EMPTY_STATE, false);
4849
for (int i = 0; i < 5; i++) {
4950
res.setTimedOut(randomBoolean());
50-
if (res.isTimedOut()) {
51-
assertEquals(RestStatus.REQUEST_TIMEOUT, res.status());
52-
assertCriticalWarnings(ClusterHealthResponse.CLUSTER_HEALTH_REQUEST_TIMEOUT_DEPRECATION_MSG);
53-
} else {
54-
assertEquals(RestStatus.OK, res.status());
55-
}
51+
assertEquals(RestStatus.OK, res.status(RestApiVersion.V_8));
5652
}
5753
}
5854

5955
public void testTimeoutReturns200IfOptedIn() {
6056
ClusterHealthResponse res = new ClusterHealthResponse("", Strings.EMPTY_ARRAY, ClusterState.EMPTY_STATE, true);
6157
for (int i = 0; i < 5; i++) {
6258
res.setTimedOut(randomBoolean());
63-
assertEquals(RestStatus.OK, res.status());
59+
assertEquals(RestStatus.OK, res.status(RestApiVersion.V_8));
60+
}
61+
}
62+
63+
public void testTimeoutReturns200InIfOptedInV7CompatibilityMode() {
64+
ClusterHealthResponse res = new ClusterHealthResponse("", Strings.EMPTY_ARRAY, ClusterState.EMPTY_STATE, true);
65+
for (int i = 0; i < 5; i++) {
66+
res.setTimedOut(randomBoolean());
67+
assertEquals(RestStatus.OK, res.status(RestApiVersion.V_7));
68+
}
69+
}
70+
71+
public void testTimeoutReturns408InV7CompatibilityMode() {
72+
ClusterHealthResponse res = new ClusterHealthResponse("", Strings.EMPTY_ARRAY, ClusterState.EMPTY_STATE, false);
73+
for (int i = 0; i < 5; i++) {
74+
res.setTimedOut(randomBoolean());
75+
if (res.isTimedOut()) {
76+
assertEquals(RestStatus.REQUEST_TIMEOUT, res.status(RestApiVersion.V_7));
77+
} else {
78+
assertEquals(RestStatus.OK, res.status(RestApiVersion.V_7));
79+
}
6480
}
6581
}
6682

server/src/test/java/org/elasticsearch/rest/action/admin/cluster/RestClusterHealthActionTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.elasticsearch.rest.action.admin.cluster;
1010

11+
import org.apache.logging.log4j.Level;
1112
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
1213
import org.elasticsearch.action.support.ActiveShardCount;
1314
import org.elasticsearch.cluster.health.ClusterHealthStatus;
@@ -68,6 +69,14 @@ public void testFromRequest() {
6869
assertThat(clusterHealthRequest.waitForNodes(), equalTo(waitForNodes));
6970
assertThat(clusterHealthRequest.waitForEvents(), equalTo(waitForEvents));
7071
assertThat(clusterHealthRequest.doesReturn200ForClusterHealthTimeout(), equalTo(requestTimeout200));
72+
73+
assertWarnings(
74+
true,
75+
new DeprecationWarning(
76+
Level.WARN,
77+
"the [return_200_for_cluster_health_timeout] parameter is deprecated and will be removed in a future release."
78+
)
79+
);
7180
}
7281

7382
private FakeRestRequest buildRestRequest(Map<String, String> params) {

x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/rollup/security_tests.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ teardown:
126126

127127
# this is a hacky way to sleep for 5s, since we will never have 10 nodes
128128
- do:
129-
catch: request_timeout
130129
cluster.health:
131130
wait_for_nodes: 10
132131
timeout: "5s"
@@ -286,7 +285,6 @@ teardown:
286285

287286
# this is a hacky way to sleep for 5s, since we will never have 10 nodes
288287
- do:
289-
catch: request_timeout
290288
cluster.health:
291289
wait_for_nodes: 10
292290
timeout: "5s"

0 commit comments

Comments
 (0)