From 6cae1b89f60be84c212a74e16ab605f2280de6b3 Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Mon, 26 Nov 2018 21:54:06 +0100 Subject: [PATCH 1/4] [Zen2] Implement Tombstone REST APIs * Adds REST API for withdrawing votes and clearing vote withdrawls * Tests added to Netty4 module since we need a real Network impl. for Http endpoints --- .../rest/discovery/Zen2RestApiIT.java | 152 ++++++++++++++++++ .../elasticsearch/action/ActionModule.java | 4 + .../AddVotingTombstonesResponse.java | 9 +- .../ClearVotingTombstonesResponse.java | 9 +- .../RestAddVotingTombstonesAction.java | 61 +++++++ .../RestClearVotingTombstonesAction.java | 53 ++++++ 6 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java create mode 100644 server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestAddVotingTombstonesAction.java create mode 100644 server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java new file mode 100644 index 0000000000000..85ac2c9f898d5 --- /dev/null +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java @@ -0,0 +1,152 @@ +/* + * 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.rest.discovery; + +import org.apache.http.HttpHost; +import org.elasticsearch.ESNetty4IntegTestCase; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequestBuilder; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.Node; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.cluster.coordination.ClusterBootstrapService; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.routing.UnassignedInfo; +import org.elasticsearch.common.Priority; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.discovery.DiscoverySettings; +import org.elasticsearch.discovery.zen.ElectMasterService; +import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.InternalTestCluster; +import org.elasticsearch.test.discovery.TestZenDiscovery; +import org.hamcrest.Matchers; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.core.Is.is; + +// TODO: Move these tests to a more appropriate module +@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0, transportClientRatio = 0, autoMinMasterNodes = false) +public class Zen2RestApiIT extends ESNetty4IntegTestCase { + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return Settings.builder().put(super.nodeSettings(nodeOrdinal)) + .put(TestZenDiscovery.USE_ZEN2.getKey(), true) + .put(ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.getKey(), 2) + .put(ClusterBootstrapService.INITIAL_MASTER_NODE_COUNT_SETTING.getKey(), 2) + .put(DiscoverySettings.INITIAL_STATE_TIMEOUT_SETTING.getKey(), "5s") + .build(); + } + + @Override + protected boolean addMockHttpTransport() { + return false; // enable http + } + + public void testAddAndClearVotingTombstones() throws Exception { + final int nodeCount = 2; + final List nodes = internalCluster().startNodes(nodeCount); + createIndex("test", + Settings.builder() + .put(UnassignedInfo.INDEX_DELAYED_NODE_LEFT_TIMEOUT_SETTING.getKey(), TimeValue.ZERO) // assign shards + .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, nodeCount) // causes rebalancing + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1) + .build()); + ensureGreen("test"); + + RestClient restClient = getRestClient(); + + internalCluster().rollingRestart(new InternalTestCluster.RestartCallback() { + @Override + public void doAfterNodes(int n, Client client) throws IOException { + ensureGreen("test"); + Response response = + restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/" + internalCluster().getNodeNames()[n])); + assertThat(response.getStatusLine().getStatusCode(), is(200)); + } + + @Override + public Settings onNodeStopped(String nodeName) throws IOException { + String viaNode = randomValueOtherThan(nodeName, () -> randomFrom(nodes)); + + List allNodes = restClient.getNodes(); + try { + restClient.setNodes( + Collections.singletonList( + new Node( + HttpHost.create( + internalCluster().getInstance(HttpServerTransport.class, viaNode) + .boundAddress().publishAddress().toString() + ) + ) + ) + ); + Response deleteResponse = restClient.performRequest(new Request("DELETE", "/_cluster/withdrawn_votes")); + assertThat(deleteResponse.getStatusLine().getStatusCode(), is(200)); + + final ClusterHealthRequestBuilder clusterHealthRequestBuilder = client(viaNode).admin().cluster().prepareHealth() + .setWaitForEvents(Priority.LANGUID) + .setWaitForNodes(Integer.toString(nodeCount - 1)) + .setTimeout(TimeValue.timeValueSeconds(30L)); + + clusterHealthRequestBuilder.setWaitForYellowStatus(); + ClusterHealthResponse clusterHealthResponse = clusterHealthRequestBuilder.get(); + assertFalse(nodeName, clusterHealthResponse.isTimedOut()); + return Settings.EMPTY; + } finally { + restClient.setNodes(allNodes); + } + } + }); + ensureStableCluster(nodeCount); + ensureGreen("test"); + assertThat(internalCluster().size(), is(2)); + } + + public void testBasicRestApi() throws Exception { + List nodes = internalCluster().startNodes(3); + RestClient restClient = getRestClient(); + Response deleteResponse = restClient.performRequest(new Request("DELETE", "/_cluster/withdrawn_votes")); + assertThat(deleteResponse.getStatusLine().getStatusCode(), is(200)); + assertThat(deleteResponse.getEntity().getContentLength(), is(0L)); + Response response = + restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/" + nodes.get(0))); + assertThat(response.getStatusLine().getStatusCode(), is(200)); + assertThat(response.getEntity().getContentLength(), is(0L)); + try { + restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/invalid")); + fail("Invalid node name should throw."); + } catch (ResponseException e) { + assertThat(e.getResponse().getStatusLine().getStatusCode(), is(400)); + assertThat( + e.getCause().getMessage(), + Matchers.containsString("add voting tombstones request for [invalid] matched no master-eligible nodes") + ); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index d3b0ea5d9f9ef..0c0ed3063f098 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -225,6 +225,7 @@ import org.elasticsearch.rest.action.RestFieldCapabilitiesAction; import org.elasticsearch.rest.action.RestMainAction; import org.elasticsearch.rest.action.admin.cluster.RestCancelTasksAction; +import org.elasticsearch.rest.action.admin.cluster.RestClearVotingTombstonesAction; import org.elasticsearch.rest.action.admin.cluster.RestClusterAllocationExplainAction; import org.elasticsearch.rest.action.admin.cluster.RestClusterGetSettingsAction; import org.elasticsearch.rest.action.admin.cluster.RestClusterHealthAction; @@ -254,6 +255,7 @@ import org.elasticsearch.rest.action.admin.cluster.RestRestoreSnapshotAction; import org.elasticsearch.rest.action.admin.cluster.RestSnapshotsStatusAction; import org.elasticsearch.rest.action.admin.cluster.RestVerifyRepositoryAction; +import org.elasticsearch.rest.action.admin.cluster.RestAddVotingTombstonesAction; import org.elasticsearch.rest.action.admin.indices.RestAnalyzeAction; import org.elasticsearch.rest.action.admin.indices.RestClearIndicesCacheAction; import org.elasticsearch.rest.action.admin.indices.RestCloseIndexAction; @@ -543,6 +545,8 @@ public void initRestHandlers(Supplier nodesInCluster) { catActions.add((AbstractCatAction) a); } }; + registerHandler.accept(new RestAddVotingTombstonesAction(settings, restController)); + registerHandler.accept(new RestClearVotingTombstonesAction(settings, restController)); registerHandler.accept(new RestMainAction(settings, restController)); registerHandler.accept(new RestNodesInfoAction(settings, restController, settingsFilter)); registerHandler.accept(new RestRemoteClusterInfoAction(settings, restController)); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/configuration/AddVotingTombstonesResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/configuration/AddVotingTombstonesResponse.java index 2fee3c848c5f0..af49ba56af56f 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/configuration/AddVotingTombstonesResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/configuration/AddVotingTombstonesResponse.java @@ -21,6 +21,8 @@ import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; import java.io.IOException; @@ -28,7 +30,7 @@ * A response to {@link AddVotingTombstonesRequest} indicating that voting tombstones have been added for the requested nodes and these * nodes have been removed from the voting configuration. */ -public class AddVotingTombstonesResponse extends ActionResponse { +public class AddVotingTombstonesResponse extends ActionResponse implements ToXContentObject { public AddVotingTombstonesResponse() { } @@ -46,4 +48,9 @@ public void readFrom(StreamInput in) throws IOException { public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); } + + @Override + public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + return builder; + } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/configuration/ClearVotingTombstonesResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/configuration/ClearVotingTombstonesResponse.java index 1237e2e265fed..d84b7704b61cb 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/configuration/ClearVotingTombstonesResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/configuration/ClearVotingTombstonesResponse.java @@ -21,13 +21,15 @@ import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; import java.io.IOException; /** * A response to {@link ClearVotingTombstonesRequest} indicating that voting tombstones have been cleared from the cluster state. */ -public class ClearVotingTombstonesResponse extends ActionResponse { +public class ClearVotingTombstonesResponse extends ActionResponse implements ToXContentObject { public ClearVotingTombstonesResponse() { } @@ -44,4 +46,9 @@ public void readFrom(StreamInput in) throws IOException { public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); } + + @Override + public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + return builder; + } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestAddVotingTombstonesAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestAddVotingTombstonesAction.java new file mode 100644 index 0000000000000..dceb405d1b510 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestAddVotingTombstonesAction.java @@ -0,0 +1,61 @@ +/* + * 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.rest.action.admin.cluster; + +import org.elasticsearch.action.admin.cluster.configuration.AddVotingTombstonesAction; +import org.elasticsearch.action.admin.cluster.configuration.AddVotingTombstonesRequest; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; + +public class RestAddVotingTombstonesAction extends BaseRestHandler { + + private static final TimeValue DEFAULT_TIMEOUT = TimeValue.timeValueSeconds(30L); + + public RestAddVotingTombstonesAction(Settings settings, RestController controller) { + super(settings); + controller.registerHandler(RestRequest.Method.POST, "/_cluster/withdrawn_votes/{node_name}", this); + } + + @Override + public String getName() { + return "add_voting_tombstones_action"; + } + + @Override + protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + String nodeName = request.param("node_name"); + AddVotingTombstonesRequest addVotingTombstonesRequest = new AddVotingTombstonesRequest( + new String[]{nodeName}, + TimeValue.parseTimeValue(request.param("timeout"), DEFAULT_TIMEOUT, getClass().getSimpleName() + ".timeout") + ); + return channel -> client.execute( + AddVotingTombstonesAction.INSTANCE, + addVotingTombstonesRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java new file mode 100644 index 0000000000000..d746f51b84dea --- /dev/null +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java @@ -0,0 +1,53 @@ +/* + * 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.rest.action.admin.cluster; + +import org.elasticsearch.action.admin.cluster.configuration.ClearVotingTombstonesAction; +import org.elasticsearch.action.admin.cluster.configuration.ClearVotingTombstonesRequest; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; + +public class RestClearVotingTombstonesAction extends BaseRestHandler { + + public RestClearVotingTombstonesAction(Settings settings, RestController controller) { + super(settings); + controller.registerHandler(RestRequest.Method.DELETE, "/_cluster/withdrawn_votes", this); + } + + @Override + public String getName() { + return "clear_voting_tombstones_action"; + } + + @Override + protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + return channel -> client.execute( + ClearVotingTombstonesAction.INSTANCE, + new ClearVotingTombstonesRequest(), + new RestToXContentListener<>(channel) + ); + } +} From e167a09f8d3cf91c76bec75bc2ade63c517a5005 Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Wed, 28 Nov 2018 19:00:59 +0100 Subject: [PATCH 2/4] CR: comments --- .../rest/discovery/Zen2RestApiIT.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java index 85ac2c9f898d5..5f20cc60645c0 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java @@ -35,7 +35,6 @@ import org.elasticsearch.common.Priority; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.discovery.DiscoverySettings; import org.elasticsearch.discovery.zen.ElectMasterService; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.test.ESIntegTestCase; @@ -59,7 +58,6 @@ protected Settings nodeSettings(int nodeOrdinal) { .put(TestZenDiscovery.USE_ZEN2.getKey(), true) .put(ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.getKey(), 2) .put(ClusterBootstrapService.INITIAL_MASTER_NODE_COUNT_SETTING.getKey(), 2) - .put(DiscoverySettings.INITIAL_STATE_TIMEOUT_SETTING.getKey(), "5s") .build(); } @@ -68,13 +66,12 @@ protected boolean addMockHttpTransport() { return false; // enable http } - public void testAddAndClearVotingTombstones() throws Exception { - final int nodeCount = 2; - final List nodes = internalCluster().startNodes(nodeCount); + public void testRollingRestartOfTwoNodeCluster() throws Exception { + final List nodes = internalCluster().startNodes(2); createIndex("test", Settings.builder() .put(UnassignedInfo.INDEX_DELAYED_NODE_LEFT_TIMEOUT_SETTING.getKey(), TimeValue.ZERO) // assign shards - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, nodeCount) // causes rebalancing + .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 2) // causes rebalancing .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1) .build()); ensureGreen("test"); @@ -109,13 +106,12 @@ public Settings onNodeStopped(String nodeName) throws IOException { Response deleteResponse = restClient.performRequest(new Request("DELETE", "/_cluster/withdrawn_votes")); assertThat(deleteResponse.getStatusLine().getStatusCode(), is(200)); - final ClusterHealthRequestBuilder clusterHealthRequestBuilder = client(viaNode).admin().cluster().prepareHealth() + ClusterHealthResponse clusterHealthResponse = client(viaNode).admin().cluster().prepareHealth() .setWaitForEvents(Priority.LANGUID) - .setWaitForNodes(Integer.toString(nodeCount - 1)) - .setTimeout(TimeValue.timeValueSeconds(30L)); - - clusterHealthRequestBuilder.setWaitForYellowStatus(); - ClusterHealthResponse clusterHealthResponse = clusterHealthRequestBuilder.get(); + .setWaitForNodes(Integer.toString(1)) + .setTimeout(TimeValue.timeValueSeconds(30L)) + .setWaitForYellowStatus() + .get(); assertFalse(nodeName, clusterHealthResponse.isTimedOut()); return Settings.EMPTY; } finally { @@ -123,7 +119,7 @@ public Settings onNodeStopped(String nodeName) throws IOException { } } }); - ensureStableCluster(nodeCount); + ensureStableCluster(2); ensureGreen("test"); assertThat(internalCluster().size(), is(2)); } @@ -134,8 +130,7 @@ public void testBasicRestApi() throws Exception { Response deleteResponse = restClient.performRequest(new Request("DELETE", "/_cluster/withdrawn_votes")); assertThat(deleteResponse.getStatusLine().getStatusCode(), is(200)); assertThat(deleteResponse.getEntity().getContentLength(), is(0L)); - Response response = - restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/" + nodes.get(0))); + Response response = restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/" + nodes.get(0))); assertThat(response.getStatusLine().getStatusCode(), is(200)); assertThat(response.getEntity().getContentLength(), is(0L)); try { From 8456401850427e70d92cf0e2c56de86f336a3cf7 Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Thu, 29 Nov 2018 06:29:09 +0100 Subject: [PATCH 3/4] CR: don't wait for node removal + safer setting for zen1 node count --- .../org/elasticsearch/rest/discovery/Zen2RestApiIT.java | 9 ++++----- .../admin/cluster/RestClearVotingTombstonesAction.java | 8 +++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java index 5f20cc60645c0..e36fa327fddce 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java @@ -21,7 +21,6 @@ import org.apache.http.HttpHost; import org.elasticsearch.ESNetty4IntegTestCase; -import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequestBuilder; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.client.Client; import org.elasticsearch.client.Node; @@ -56,7 +55,7 @@ public class Zen2RestApiIT extends ESNetty4IntegTestCase { protected Settings nodeSettings(int nodeOrdinal) { return Settings.builder().put(super.nodeSettings(nodeOrdinal)) .put(TestZenDiscovery.USE_ZEN2.getKey(), true) - .put(ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.getKey(), 2) + .put(ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.getKey(), Integer.MAX_VALUE) .put(ClusterBootstrapService.INITIAL_MASTER_NODE_COUNT_SETTING.getKey(), 2) .build(); } @@ -127,12 +126,12 @@ public Settings onNodeStopped(String nodeName) throws IOException { public void testBasicRestApi() throws Exception { List nodes = internalCluster().startNodes(3); RestClient restClient = getRestClient(); + Response response = restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/" + nodes.get(2))); + assertThat(response.getStatusLine().getStatusCode(), is(200)); + assertThat(response.getEntity().getContentLength(), is(0L)); Response deleteResponse = restClient.performRequest(new Request("DELETE", "/_cluster/withdrawn_votes")); assertThat(deleteResponse.getStatusLine().getStatusCode(), is(200)); assertThat(deleteResponse.getEntity().getContentLength(), is(0L)); - Response response = restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/" + nodes.get(0))); - assertThat(response.getStatusLine().getStatusCode(), is(200)); - assertThat(response.getEntity().getContentLength(), is(0L)); try { restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/invalid")); fail("Invalid node name should throw."); diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java index d746f51b84dea..9e22c55045626 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java @@ -44,10 +44,8 @@ public String getName() { @Override protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - return channel -> client.execute( - ClearVotingTombstonesAction.INSTANCE, - new ClearVotingTombstonesRequest(), - new RestToXContentListener<>(channel) - ); + ClearVotingTombstonesRequest req = new ClearVotingTombstonesRequest(); + req.setWaitForRemoval(false); + return channel -> client.execute(ClearVotingTombstonesAction.INSTANCE, req, new RestToXContentListener<>(channel)); } } From 0d5d5a75be110124f44146d86d4aacf9edd90c9b Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Thu, 29 Nov 2018 10:28:25 +0100 Subject: [PATCH 4/4] CR: expose param --- .../rest/discovery/Zen2RestApiIT.java | 24 +++++++++++++++++-- .../RestClearVotingTombstonesAction.java | 4 +++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java index e36fa327fddce..f7f4a9e4e148f 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/rest/discovery/Zen2RestApiIT.java @@ -47,7 +47,9 @@ import static org.hamcrest.core.Is.is; -// TODO: Move these tests to a more appropriate module +// These tests are here today so they have access to a proper REST client. They cannot be in :server:integTest since the REST client needs a +// proper transport implementation, and they cannot be REST tests today since they need to restart nodes. When #35599 and friends land we +// should be able to move these tests to run against a proper cluster instead. TODO do this. @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0, transportClientRatio = 0, autoMinMasterNodes = false) public class Zen2RestApiIT extends ESNetty4IntegTestCase { @@ -123,15 +125,33 @@ public Settings onNodeStopped(String nodeName) throws IOException { assertThat(internalCluster().size(), is(2)); } - public void testBasicRestApi() throws Exception { + public void testClearVotingTombstonesNotWaitingForRemoval() throws Exception { List nodes = internalCluster().startNodes(3); RestClient restClient = getRestClient(); Response response = restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/" + nodes.get(2))); assertThat(response.getStatusLine().getStatusCode(), is(200)); assertThat(response.getEntity().getContentLength(), is(0L)); + Response deleteResponse = restClient.performRequest(new Request("DELETE", "/_cluster/withdrawn_votes/?wait_for_removal=false")); + assertThat(deleteResponse.getStatusLine().getStatusCode(), is(200)); + assertThat(deleteResponse.getEntity().getContentLength(), is(0L)); + } + + public void testClearVotingTombstonesWaitingForRemoval() throws Exception { + List nodes = internalCluster().startNodes(3); + RestClient restClient = getRestClient(); + String nodeToWithdraw = nodes.get(randomIntBetween(0, 2)); + Response response = restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/" + nodeToWithdraw)); + assertThat(response.getStatusLine().getStatusCode(), is(200)); + assertThat(response.getEntity().getContentLength(), is(0L)); + internalCluster().stopRandomNode(InternalTestCluster.nameFilter(nodeToWithdraw)); Response deleteResponse = restClient.performRequest(new Request("DELETE", "/_cluster/withdrawn_votes")); assertThat(deleteResponse.getStatusLine().getStatusCode(), is(200)); assertThat(deleteResponse.getEntity().getContentLength(), is(0L)); + } + + public void testFailsOnUnknownNode() throws Exception { + internalCluster().startNodes(3); + RestClient restClient = getRestClient(); try { restClient.performRequest(new Request("POST", "/_cluster/withdrawn_votes/invalid")); fail("Invalid node name should throw."); diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java index 9e22c55045626..6556874c860ce 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestClearVotingTombstonesAction.java @@ -45,7 +45,9 @@ public String getName() { @Override protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { ClearVotingTombstonesRequest req = new ClearVotingTombstonesRequest(); - req.setWaitForRemoval(false); + if (request.hasParam("wait_for_removal")) { + req.setWaitForRemoval(request.paramAsBoolean("wait_for_removal", true)); + } return channel -> client.execute(ClearVotingTombstonesAction.INSTANCE, req, new RestToXContentListener<>(channel)); } }