-
Notifications
You must be signed in to change notification settings - Fork 25k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enforce cluster UUIDs #37775
Enforce cluster UUIDs #37775
Conversation
Pinging @elastic/es-distributed |
@DaveCTurner @andrershov this is ready for a first review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added minor comments.
It also would be nice to add integration test, similar to testIndexImportedFromDataOnlyNodesIfMasterLostDataFolder
or RecoveryFromGatewayIT.testTwoNodeFirstNodeCleared
that will clear data folder of master eligible node and after that data node won't be able to join the cluster because join validation will fail due to clusterUUID mismatch.
server/src/main/java/org/elasticsearch/cluster/coordination/CoordinationState.java
Outdated
Show resolved
Hide resolved
final ClusterState localState = currentStateSupplier.get(); | ||
if (localState.metaData().clusterUUIDCommitted() && | ||
localState.metaData().clusterUUID().equals(request.getState().metaData().clusterUUID()) == false) { | ||
throw new CoordinationStateRejectedException("join validation on cluster state" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason why we log warning message in Coordinator
, but do not log it here?
server/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java
Outdated
Show resolved
Hide resolved
shiftedNode.getLocalNode(), n -> shiftedNode.persistedState); | ||
cluster1.clusterNodes.add(newNode); | ||
|
||
MockLogAppender mockAppender = new MockLogAppender(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a pity that we don't have a better way of understanding that join validation has failed, other than analyzing log output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to that. Why can't we e.g. call cluster1.runFor(DEFAULT_STABILISATION_TIME)
and then assert that the node didn't manage to join the cluster?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've extended it to DEFAULT_STABILISATION_TIME
, but I also want to make sure it fails for the right reason, hence keeping the logging check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm all ears if you have better suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about asserting that if we change nothing except the cluster UUID on disk then the node does join the cluster?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But there is no guarantee that it will if it has same term but higher version than master? I think these kinds of tests will be more useful after having the detach-cluster tool.
.coordinationMetaData(CoordinationMetaData.builder(metaData.coordinationMetaData()) | ||
.term(0L).build()) | ||
.build(), | ||
term -> 0L); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems that resetting term
and currentTerm
to 0 was required? shall we do the same in elasticsearch-node tool? What about version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, this was required. I'll leave the definitive way to the tool (added TODO in c3e0aa2)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. I left a few small comments.
@@ -268,7 +268,7 @@ public void testMixCurrentAndRecoveredState() { | |||
final ClusterState updatedState = mixCurrentStateAndRecoveredState(currentState, recoveredState); | |||
|
|||
assertThat(updatedState.metaData().clusterUUID(), not(equalTo("_na_"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should really use UNKNOWN_CLUSTER_UUID
here and elsewhere in the tests too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see 3dc2c62
metaDataBuilder = MetaData.builder(lastAcceptedState.metaData()); | ||
metaDataBuilder.coordinationMetaData(coordinationMetaData); | ||
} | ||
if (lastAcceptedState.metaData().clusterUUID().equals(MetaData.UNKNOWN_CLUSTER_UUID) == false && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we commit a state without a cluster UUID? Feels like this should be an assertion to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the master node is a Zen1 node that has not recovered its state yet, that can unfortunately be the case (testMixedClusterFormation found this). I've added an assertion to that effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we write the assertion in a way that means we will have to remove it when Zen1 is no more? E.g. mention ZEN1_BWC_TERM
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in 2697b44
final ClusterNode shiftedNode = randomFrom(cluster2.clusterNodes).restartedNode(); | ||
final ClusterNode newNode = cluster1.new ClusterNode(nextNodeIndex.getAndIncrement(), | ||
shiftedNode.getLocalNode(), n -> shiftedNode.persistedState); | ||
cluster1.clusterNodes.add(newNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙈
shiftedNode.getLocalNode(), n -> shiftedNode.persistedState); | ||
cluster1.clusterNodes.add(newNode); | ||
|
||
MockLogAppender mockAppender = new MockLogAppender(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to that. Why can't we e.g. call cluster1.runFor(DEFAULT_STABILISATION_TIME)
and then assert that the node didn't manage to join the cluster?
@elasticmachine run elasticsearch-ci/1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but there are still some open questions from @andrershov.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM2, the only unanswered question is about logging, but I guess it's done this way to simplify testing.
@elasticmachine what have you done? |
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
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 #37775
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 #37775
This is a forward-port of parts of elastic#41063 to `master`, adding a test to show that join validation does indeed verify that the cluster UUIDs match. Relates elastic#37775
This is a forward-port of parts of elastic#41063 to `master`, adding a test to show that join validation does indeed verify that the cluster UUIDs match. Relates elastic#37775
This PR adds join validation around cluster UUIDs, preventing a node to join a cluster if it was previously part of another cluster.
The PR introduces a new flag to the cluster state,
clusterUUIDCommitted
, which denotes whether the node has locked into a cluster with the given uuid. When a cluster is committed, this flag will turn to true, and subsequent cluster state updates will keep the information about committal.Note that coordinating-only nodes are still free to switch clusters at will (after restart), as they don't carry any persistent state.