Skip to content

Commit 3a44506

Browse files
authored
Merge branch 'main' into 2025/04/07/AbstractBlobContainerRetriesTestCase-createBlobContainer-blobContainerPath
2 parents 8b394d0 + 72066ea commit 3a44506

File tree

25 files changed

+282
-58
lines changed

25 files changed

+282
-58
lines changed

docs/changelog/125562.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 125562
2+
summary: Improve handling of empty response
3+
area: Infra/REST API
4+
type: bug
5+
issues:
6+
- 57639

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_balance/10_basic.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ setup:
180180
- requires:
181181
cluster_features: ["gte_v8.8.0"]
182182
reason: "reset API added in in 8.8.0"
183+
test_runner_features: [ capabilities ]
184+
capabilities:
185+
- method: DELETE
186+
path: /_internal/desired_balance
187+
capabilities: [ plain_text_empty_response ]
183188

184189
- do:
185190
_internal.delete_desired_balance: { }

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/10_basic.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ setup:
33
- requires:
44
cluster_features: ["gte_v8.13.0"]
55
reason: "API added in in 8.1.0 but modified in 8.13 (node_version field removed)"
6+
test_runner_features: [ capabilities ]
7+
capabilities:
8+
- method: DELETE
9+
path: /_internal/desired_nodes
10+
capabilities: [ plain_text_empty_response ]
611
---
712
teardown:
813
- do:

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/11_old_format.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ setup:
66
- requires:
77
cluster_features: ["gte_v8.3.0"]
88
reason: "API added in in 8.1.0 but modified in 8.3"
9+
test_runner_features: [ capabilities ]
10+
capabilities:
11+
- method: DELETE
12+
path: /_internal/desired_nodes
13+
capabilities: [ plain_text_empty_response ]
914
---
1015
teardown:
1116
- do:

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/20_dry_run.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ setup:
33
- requires:
44
cluster_features: ["gte_v8.4.0"]
55
reason: "Support for the dry run option was added in in 8.4.0"
6+
test_runner_features: [ capabilities ]
7+
capabilities:
8+
- method: DELETE
9+
path: /_internal/desired_nodes
10+
capabilities: [ plain_text_empty_response ]
611
---
712
teardown:
813
- do:

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.voting_config_exclusions/10_basic.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
setup:
2+
- requires:
3+
test_runner_features: [ capabilities ]
4+
capabilities:
5+
- method: POST
6+
path: /_cluster/voting_config_exclusions
7+
capabilities: [ plain_text_empty_response ]
8+
- method: DELETE
9+
path: /_cluster/voting_config_exclusions
10+
capabilities: [ plain_text_empty_response ]
11+
reason: needs these capabilities
12+
13+
---
114
teardown:
215
- do:
316
cluster.delete_voting_config_exclusions: {}

server/src/internalClusterTest/java/org/elasticsearch/cluster/coordination/VotingConfigurationIT.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@
99
package org.elasticsearch.cluster.coordination;
1010

1111
import org.elasticsearch.ElasticsearchException;
12-
import org.elasticsearch.action.admin.cluster.configuration.AddVotingConfigExclusionsRequest;
13-
import org.elasticsearch.action.admin.cluster.configuration.TransportAddVotingConfigExclusionsAction;
12+
import org.elasticsearch.client.Request;
13+
import org.elasticsearch.client.Response;
1414
import org.elasticsearch.cluster.ClusterState;
1515
import org.elasticsearch.cluster.node.DiscoveryNode;
16+
import org.elasticsearch.cluster.service.ClusterService;
1617
import org.elasticsearch.common.Priority;
1718
import org.elasticsearch.plugins.Plugin;
1819
import org.elasticsearch.test.ESIntegTestCase;
1920
import org.elasticsearch.test.transport.MockTransportService;
2021
import org.elasticsearch.transport.TransportService;
2122

23+
import java.io.IOException;
2224
import java.util.Collection;
2325
import java.util.Collections;
2426
import java.util.List;
2527
import java.util.Set;
26-
import java.util.concurrent.ExecutionException;
2728

29+
import static org.hamcrest.Matchers.empty;
2830
import static org.hamcrest.Matchers.equalTo;
2931
import static org.hamcrest.Matchers.hasItem;
3032
import static org.hamcrest.Matchers.hasSize;
@@ -38,18 +40,39 @@ protected Collection<Class<? extends Plugin>> nodePlugins() {
3840
return Collections.singletonList(MockTransportService.TestPlugin.class);
3941
}
4042

41-
public void testAbdicateAfterVotingConfigExclusionAdded() throws ExecutionException, InterruptedException {
43+
@Override
44+
protected boolean addMockHttpTransport() {
45+
return false; // enable HTTP
46+
}
47+
48+
public void testAbdicateAfterVotingConfigExclusionAdded() throws IOException {
4249
internalCluster().setBootstrapMasterNodeIndex(0);
4350
internalCluster().startNodes(2);
4451
final String originalMaster = internalCluster().getMasterName();
52+
final var restClient = getRestClient();
4553

4654
logger.info("--> excluding master node {}", originalMaster);
47-
client().execute(
48-
TransportAddVotingConfigExclusionsAction.TYPE,
49-
new AddVotingConfigExclusionsRequest(TEST_REQUEST_TIMEOUT, originalMaster)
50-
).get();
55+
final var excludeRequest = new Request("POST", "/_cluster/voting_config_exclusions");
56+
excludeRequest.addParameter("node_names", originalMaster);
57+
assertEmptyResponse(restClient.performRequest(excludeRequest));
58+
5159
clusterAdmin().prepareHealth(TEST_REQUEST_TIMEOUT).setWaitForEvents(Priority.LANGUID).get();
5260
assertNotEquals(originalMaster, internalCluster().getMasterName());
61+
62+
final var clearRequest = new Request("DELETE", "/_cluster/voting_config_exclusions");
63+
clearRequest.addParameter("wait_for_removal", "false");
64+
assertEmptyResponse(restClient.performRequest(clearRequest));
65+
66+
assertThat(
67+
internalCluster().getInstance(ClusterService.class).state().metadata().coordinationMetadata().getVotingConfigExclusions(),
68+
empty()
69+
);
70+
}
71+
72+
private void assertEmptyResponse(Response response) throws IOException {
73+
assertEquals("text/plain; charset=UTF-8", response.getHeader("content-type"));
74+
assertEquals(0, response.getEntity().getContentLength());
75+
assertEquals(0, response.getEntity().getContent().readAllBytes().length);
5376
}
5477

5578
public void testElectsNodeNotInVotingConfiguration() throws Exception {

server/src/main/java/org/elasticsearch/action/ActionResponse.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
package org.elasticsearch.action;
1111

1212
import org.elasticsearch.common.io.stream.StreamOutput;
13+
import org.elasticsearch.rest.action.EmptyResponseListener;
1314
import org.elasticsearch.transport.TransportResponse;
14-
import org.elasticsearch.xcontent.ToXContentObject;
15-
import org.elasticsearch.xcontent.XContentBuilder;
15+
import org.elasticsearch.xcontent.ToXContent;
16+
import org.elasticsearch.xcontent.XContent;
1617

1718
/**
1819
* Base class for responses to action requests.
@@ -21,20 +22,23 @@ public abstract class ActionResponse extends TransportResponse {
2122

2223
public ActionResponse() {}
2324

24-
public static final class Empty extends ActionResponse implements ToXContentObject {
25+
/**
26+
* A response with no payload. This is deliberately not an implementation of {@link ToXContent} or similar because an empty response
27+
* has no valid {@link XContent} representation. Use {@link EmptyResponseListener} to convert this to a valid (plain-text) REST
28+
* response instead.
29+
*/
30+
public static final class Empty extends ActionResponse {
31+
32+
private Empty() { /* singleton */ }
33+
2534
public static final ActionResponse.Empty INSTANCE = new ActionResponse.Empty();
2635

2736
@Override
2837
public String toString() {
29-
return "EmptyActionResponse{}";
38+
return "ActionResponse.Empty{}";
3039
}
3140

3241
@Override
3342
public void writeTo(StreamOutput out) {}
34-
35-
@Override
36-
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) {
37-
return builder;
38-
}
3943
}
4044
}

server/src/main/java/org/elasticsearch/action/datastreams/autosharding/DataStreamAutoShardingService.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ public String toString() {
313313
return Strings.format(
314314
"For data stream %s: %s based on [inc/dec cooldowns %s/%s, %d-%d threads, "
315315
+ "write index %s has all-time/recent/peak loads %g/%g/%g, current shards %d, "
316-
+ "using %s value %g for increase gives %d shards%s]",
316+
+ "increase calculation gives %d shards using %s load %g for write index%s]",
317317
inputs.dataStream,
318318
result,
319319
inputs.increaseShardsCooldown,
@@ -325,19 +325,19 @@ public String toString() {
325325
inputs.writeIndexRecentLoad,
326326
inputs.writeIndexPeakLoad,
327327
inputs.currentNumberOfWriteIndexShards,
328+
increaseCalculation.optimalShardCountForIncrease,
328329
inputs.increaseShardsMetric,
329330
increaseCalculation.writeIndexLoadForIncrease,
330-
increaseCalculation.optimalShardCountForIncrease,
331331
decreaseCalculation == null
332332
? ""
333333
: Strings.format(
334-
", and using %s value %g for dec based on %s gives %d shards",
334+
", decrease calculation gives %d shards using %s load %g for %s",
335+
decreaseCalculation.optimalShardCountForDecrease,
335336
inputs.decreaseShardsMetric,
336337
decreaseCalculation.maxLoadWithinCooldown.load,
337338
decreaseCalculation.maxLoadWithinCooldown.previousIndexWithMaxLoad != null
338339
? decreaseCalculation.maxLoadWithinCooldown.previousIndexWithMaxLoad
339-
: "write index",
340-
decreaseCalculation.optimalShardCountForDecrease
340+
: "write index"
341341
)
342342
);
343343
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.rest.action;
11+
12+
import org.elasticsearch.action.ActionResponse;
13+
import org.elasticsearch.common.bytes.BytesArray;
14+
import org.elasticsearch.rest.RestChannel;
15+
import org.elasticsearch.rest.RestResponse;
16+
import org.elasticsearch.rest.RestStatus;
17+
18+
/**
19+
* A listener which converts a successful {@link ActionResponse.Empty} action response into a {@code 200 OK} REST response with empty body.
20+
*/
21+
public final class EmptyResponseListener extends RestResponseListener<ActionResponse.Empty> {
22+
public EmptyResponseListener(RestChannel channel) {
23+
super(channel);
24+
}
25+
26+
@Override
27+
public RestResponse buildResponse(ActionResponse.Empty ignored) throws Exception {
28+
// Content-type header is not required for an empty body but some clients may expect it; the empty body is a valid text/plain entity
29+
// so we use that here.
30+
return new RestResponse(RestStatus.OK, RestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY);
31+
}
32+
33+
/**
34+
* Capability name for APIs that previously would return an invalid zero-byte {@code application/json} response so that the YAML test
35+
* runner can avoid those APIs.
36+
*/
37+
public static final String PLAIN_TEXT_EMPTY_RESPONSE_CAPABILITY_NAME = "plain_text_empty_response";
38+
}

0 commit comments

Comments
 (0)