92
92
93
93
import static org .elasticsearch .cluster .coordination .NoMasterBlockService .NO_MASTER_BLOCK_ID ;
94
94
import static org .elasticsearch .core .Strings .format ;
95
+ import static org .elasticsearch .discovery .SettingsBasedSeedHostsProvider .DISCOVERY_SEED_HOSTS_SETTING ;
95
96
import static org .elasticsearch .gateway .ClusterStateUpdaters .hideStateIfNotRecovered ;
96
97
import static org .elasticsearch .gateway .GatewayService .STATE_NOT_RECOVERED_BLOCK ;
97
98
import static org .elasticsearch .monitor .StatusInfo .Status .UNHEALTHY ;
@@ -116,6 +117,13 @@ public class Coordinator extends AbstractLifecycleComponent implements ClusterSt
116
117
Setting .Property .NodeScope
117
118
);
118
119
120
+ public static final Setting <TimeValue > SINGLE_NODE_CLUSTER_SEED_HOSTS_CHECK_INTERVAL_SETTING = Setting .timeSetting (
121
+ "cluster.discovery_configuration_check.interval" ,
122
+ TimeValue .timeValueMillis (30000 ),
123
+ TimeValue .timeValueMillis (1 ),
124
+ Setting .Property .NodeScope
125
+ );
126
+
119
127
public static final String COMMIT_STATE_ACTION_NAME = "internal:cluster/coordination/commit_state" ;
120
128
121
129
private final Settings settings ;
@@ -140,6 +148,9 @@ public class Coordinator extends AbstractLifecycleComponent implements ClusterSt
140
148
private final SeedHostsResolver configuredHostsResolver ;
141
149
private final TimeValue publishTimeout ;
142
150
private final TimeValue publishInfoTimeout ;
151
+ private final TimeValue singleNodeClusterSeedHostsCheckInterval ;
152
+ @ Nullable
153
+ private Scheduler .Cancellable singleNodeClusterChecker = null ;
143
154
private final PublicationTransportHandler publicationHandler ;
144
155
private final LeaderChecker leaderChecker ;
145
156
private final FollowersChecker followersChecker ;
@@ -218,6 +229,7 @@ public Coordinator(
218
229
this .joinAccumulator = new InitialJoinAccumulator ();
219
230
this .publishTimeout = PUBLISH_TIMEOUT_SETTING .get (settings );
220
231
this .publishInfoTimeout = PUBLISH_INFO_TIMEOUT_SETTING .get (settings );
232
+ this .singleNodeClusterSeedHostsCheckInterval = SINGLE_NODE_CLUSTER_SEED_HOSTS_CHECK_INTERVAL_SETTING .get (settings );
221
233
this .random = random ;
222
234
this .electionSchedulerFactory = new ElectionSchedulerFactory (settings , random , transportService .getThreadPool ());
223
235
this .preVoteCollector = new PreVoteCollector (
@@ -739,6 +751,38 @@ private void processJoinRequest(JoinRequest joinRequest, ActionListener<Void> jo
739
751
}
740
752
}
741
753
754
+ private void cancelSingleNodeClusterChecker () {
755
+ assert Thread .holdsLock (mutex ) : "Coordinator mutex not held" ;
756
+ if (singleNodeClusterChecker != null ) {
757
+ singleNodeClusterChecker .cancel ();
758
+ singleNodeClusterChecker = null ;
759
+ }
760
+ }
761
+
762
+ private void checkSingleNodeCluster () {
763
+ if (applierState .nodes ().size () > 1 ) {
764
+ return ;
765
+ }
766
+
767
+ if (DISCOVERY_SEED_HOSTS_SETTING .exists (settings )) {
768
+ if (DISCOVERY_SEED_HOSTS_SETTING .get (settings ).isEmpty ()) {
769
+ // For a single-node cluster, the only acceptable setting is an empty list.
770
+ return ;
771
+ } else {
772
+ logger .warn (
773
+ """
774
+ This node is a fully-formed single-node cluster with cluster UUID [{}], but it is configured as if to \
775
+ discover other nodes and form a multi-node cluster via the [{}] setting. Fully-formed clusters do not \
776
+ attempt to discover other nodes, and nodes with different cluster UUIDs cannot belong to the same cluster. \
777
+ The cluster UUID persists across restarts and can only be changed by deleting the contents of the node's \
778
+ data path(s). Remove the discovery configuration to suppress this message.""" ,
779
+ applierState .metadata ().clusterUUID (),
780
+ DISCOVERY_SEED_HOSTS_SETTING .getKey () + "=" + DISCOVERY_SEED_HOSTS_SETTING .get (settings )
781
+ );
782
+ }
783
+ }
784
+ }
785
+
742
786
void becomeCandidate (String method ) {
743
787
assert Thread .holdsLock (mutex ) : "Coordinator mutex not held" ;
744
788
logger .debug (
@@ -748,6 +792,7 @@ void becomeCandidate(String method) {
748
792
mode ,
749
793
lastKnownLeader
750
794
);
795
+ cancelSingleNodeClusterChecker ();
751
796
752
797
if (mode != Mode .CANDIDATE ) {
753
798
final Mode prevMode = mode ;
@@ -803,6 +848,13 @@ private void becomeLeader() {
803
848
804
849
assert leaderChecker .leader () == null : leaderChecker .leader ();
805
850
followersChecker .updateFastResponseState (getCurrentTerm (), mode );
851
+
852
+ if (applierState .nodes ().size () > 1 ) {
853
+ cancelSingleNodeClusterChecker ();
854
+ } else if (singleNodeClusterChecker == null ) {
855
+ singleNodeClusterChecker = transportService .getThreadPool ()
856
+ .scheduleWithFixedDelay (() -> { checkSingleNodeCluster (); }, this .singleNodeClusterSeedHostsCheckInterval , Names .SAME );
857
+ }
806
858
}
807
859
808
860
void becomeFollower (String method , DiscoveryNode leaderNode ) {
@@ -822,6 +874,7 @@ void becomeFollower(String method, DiscoveryNode leaderNode) {
822
874
lastKnownLeader
823
875
);
824
876
}
877
+ cancelSingleNodeClusterChecker ();
825
878
826
879
final boolean restartLeaderChecker = (mode == Mode .FOLLOWER && Optional .of (leaderNode ).equals (lastKnownLeader )) == false ;
827
880
@@ -1028,6 +1081,10 @@ assert getLocalNode().equals(applierState.nodes().getMasterNode())
1028
1081
: coordinationState .get ().getLastAcceptedConfiguration ()
1029
1082
+ " != "
1030
1083
+ coordinationState .get ().getLastCommittedConfiguration ();
1084
+
1085
+ if (coordinationState .get ().getLastAcceptedState ().nodes ().size () == 1 ) {
1086
+ assert singleNodeClusterChecker != null ;
1087
+ }
1031
1088
} else if (mode == Mode .FOLLOWER ) {
1032
1089
assert coordinationState .get ().electionWon () == false : getLocalNode () + " is FOLLOWER so electionWon() should be false" ;
1033
1090
assert lastKnownLeader .isPresent () && (lastKnownLeader .get ().equals (getLocalNode ()) == false );
@@ -1045,6 +1102,7 @@ assert getLocalNode().equals(applierState.nodes().getMasterNode())
1045
1102
assert currentPublication .map (Publication ::isCommitted ).orElse (true );
1046
1103
assert preVoteCollector .getLeader ().equals (lastKnownLeader .get ()) : preVoteCollector ;
1047
1104
assert clusterFormationFailureHelper .isRunning () == false ;
1105
+ assert singleNodeClusterChecker == null ;
1048
1106
} else {
1049
1107
assert mode == Mode .CANDIDATE ;
1050
1108
assert joinAccumulator instanceof JoinHelper .CandidateJoinAccumulator ;
0 commit comments