Skip to content

Commit 6156caf

Browse files
committed
Validate cluster UUID when joining Zen1 cluster
Today we fail to join a Zen2 cluster if the cluster UUID does not match our own, but we do not perform the same validation when joining a Zen1 cluster. This means that a Zen2 node will pass join validation and be added to a Zen1 cluster but will reject all cluster states from the master. Relates elastic#37775
1 parent f9018ab commit 6156caf

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

server/src/main/java/org/elasticsearch/cluster/coordination/JoinHelper.java

+7
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ public ClusterTasksResult<JoinTaskExecutor.Task> execute(ClusterState currentSta
153153
transportService.registerRequestHandler(MembershipAction.DISCOVERY_JOIN_VALIDATE_ACTION_NAME,
154154
ValidateJoinRequest::new, ThreadPool.Names.GENERIC,
155155
(request, channel, task) -> {
156+
final ClusterState localState = currentStateSupplier.get();
157+
if (localState.metaData().clusterUUIDCommitted() &&
158+
localState.metaData().clusterUUID().equals(request.getState().metaData().clusterUUID()) == false) {
159+
throw new CoordinationStateRejectedException("join validation on cluster state" +
160+
" with a different cluster uuid " + request.getState().metaData().clusterUUID() +
161+
" than local cluster uuid " + localState.metaData().clusterUUID() + ", rejecting");
162+
}
156163
joinValidators.forEach(action -> action.accept(transportService.getLocalNode(), request.getState()));
157164
channel.sendResponse(Empty.INSTANCE);
158165
});

server/src/test/java/org/elasticsearch/cluster/coordination/JoinHelperTests.java

+63
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,19 @@
2020

2121
import org.apache.logging.log4j.Level;
2222
import org.elasticsearch.Version;
23+
import org.elasticsearch.action.ActionListenerResponseHandler;
24+
import org.elasticsearch.action.support.PlainActionFuture;
25+
import org.elasticsearch.cluster.ClusterName;
26+
import org.elasticsearch.cluster.ClusterState;
2327
import org.elasticsearch.cluster.NotMasterException;
28+
import org.elasticsearch.cluster.metadata.MetaData;
2429
import org.elasticsearch.cluster.node.DiscoveryNode;
2530
import org.elasticsearch.common.settings.Settings;
31+
import org.elasticsearch.discovery.zen.MembershipAction;
2632
import org.elasticsearch.test.ESTestCase;
2733
import org.elasticsearch.test.transport.CapturingTransport;
2834
import org.elasticsearch.test.transport.CapturingTransport.CapturedRequest;
35+
import org.elasticsearch.test.transport.MockTransport;
2936
import org.elasticsearch.transport.RemoteTransportException;
3037
import org.elasticsearch.transport.TransportException;
3138
import org.elasticsearch.transport.TransportResponse;
@@ -35,6 +42,7 @@
3542
import java.util.Optional;
3643

3744
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
45+
import static org.hamcrest.Matchers.containsString;
3846
import static org.hamcrest.Matchers.equalTo;
3947
import static org.hamcrest.core.Is.is;
4048

@@ -131,4 +139,59 @@ public void testFailedJoinAttemptLogLevel() {
131139
new RemoteTransportException("caused by NotMasterException",
132140
new NotMasterException("test"))), is(Level.DEBUG));
133141
}
142+
143+
public void testJoinValidationRejectsMismatchedClusterUUID() {
144+
DeterministicTaskQueue deterministicTaskQueue = new DeterministicTaskQueue(
145+
Settings.builder().put(NODE_NAME_SETTING.getKey(), "node0").build(), random());
146+
MockTransport mockTransport = new MockTransport();
147+
DiscoveryNode localNode = new DiscoveryNode("node0", buildNewFakeTransportAddress(), Version.CURRENT);
148+
149+
final ClusterState localClusterState = ClusterState.builder(ClusterName.DEFAULT).metaData(MetaData.builder()
150+
.generateClusterUuidIfNeeded().clusterUUIDCommitted(true)).build();
151+
152+
TransportService transportService = mockTransport.createTransportService(Settings.EMPTY,
153+
deterministicTaskQueue.getThreadPool(), TransportService.NOOP_TRANSPORT_INTERCEPTOR,
154+
x -> localNode, null, Collections.emptySet());
155+
new JoinHelper(Settings.EMPTY, null, null, transportService, () -> 0L, () -> localClusterState,
156+
(joinRequest, joinCallback) -> { throw new AssertionError(); }, startJoinRequest -> { throw new AssertionError(); },
157+
Collections.emptyList()); // registers request handler
158+
transportService.start();
159+
transportService.acceptIncomingRequests();
160+
161+
{
162+
final ClusterState otherClusterState = ClusterState.builder(ClusterName.DEFAULT).metaData(MetaData.builder()
163+
.generateClusterUuidIfNeeded()).build();
164+
165+
final PlainActionFuture<TransportResponse.Empty> future = new PlainActionFuture<>();
166+
transportService.sendRequest(localNode, JoinHelper.VALIDATE_JOIN_ACTION_NAME,
167+
new ValidateJoinRequest(otherClusterState),
168+
new ActionListenerResponseHandler<>(future, in -> TransportResponse.Empty.INSTANCE));
169+
deterministicTaskQueue.runAllTasks();
170+
171+
final CoordinationStateRejectedException coordinationStateRejectedException
172+
= expectThrows(CoordinationStateRejectedException.class, future::actionGet);
173+
assertThat(coordinationStateRejectedException.getMessage(),
174+
containsString("join validation on cluster state with a different cluster uuid"));
175+
assertThat(coordinationStateRejectedException.getMessage(), containsString(localClusterState.metaData().clusterUUID()));
176+
assertThat(coordinationStateRejectedException.getMessage(), containsString(otherClusterState.metaData().clusterUUID()));
177+
}
178+
179+
{
180+
final ClusterState otherClusterState = ClusterState.builder(ClusterName.DEFAULT).metaData(MetaData.builder()
181+
.generateClusterUuidIfNeeded()).build();
182+
183+
final PlainActionFuture<TransportResponse.Empty> future = new PlainActionFuture<>();
184+
transportService.sendRequest(localNode, MembershipAction.DISCOVERY_JOIN_VALIDATE_ACTION_NAME,
185+
new ValidateJoinRequest(otherClusterState),
186+
new ActionListenerResponseHandler<>(future, in -> TransportResponse.Empty.INSTANCE));
187+
deterministicTaskQueue.runAllTasks();
188+
189+
final CoordinationStateRejectedException coordinationStateRejectedException
190+
= expectThrows(CoordinationStateRejectedException.class, future::actionGet);
191+
assertThat(coordinationStateRejectedException.getMessage(),
192+
containsString("join validation on cluster state with a different cluster uuid"));
193+
assertThat(coordinationStateRejectedException.getMessage(), containsString(localClusterState.metaData().clusterUUID()));
194+
assertThat(coordinationStateRejectedException.getMessage(), containsString(otherClusterState.metaData().clusterUUID()));
195+
}
196+
}
134197
}

0 commit comments

Comments
 (0)