|
45 | 45 | import org.elasticsearch.cluster.metadata.MetaData;
|
46 | 46 | import org.elasticsearch.cluster.node.DiscoveryNode;
|
47 | 47 | import org.elasticsearch.cluster.node.DiscoveryNode.Role;
|
| 48 | +import org.elasticsearch.cluster.routing.allocation.AllocationService; |
48 | 49 | import org.elasticsearch.cluster.service.ClusterApplierService;
|
49 | 50 | import org.elasticsearch.cluster.service.ClusterService;
|
50 | 51 | import org.elasticsearch.common.Nullable;
|
|
70 | 71 | import org.elasticsearch.discovery.SeedHostsProvider.HostsResolver;
|
71 | 72 | import org.elasticsearch.discovery.zen.PublishClusterStateStats;
|
72 | 73 | import org.elasticsearch.env.NodeEnvironment;
|
| 74 | +import org.elasticsearch.gateway.ClusterStateUpdaters; |
| 75 | +import org.elasticsearch.gateway.GatewayService; |
73 | 76 | import org.elasticsearch.gateway.MetaStateService;
|
74 | 77 | import org.elasticsearch.gateway.MockGatewayMetaState;
|
75 | 78 | import org.elasticsearch.indices.cluster.FakeThreadPoolMasterService;
|
|
131 | 134 | import static org.elasticsearch.cluster.coordination.NoMasterBlockService.NO_MASTER_BLOCK_WRITES;
|
132 | 135 | import static org.elasticsearch.cluster.coordination.Reconfigurator.CLUSTER_AUTO_SHRINK_VOTING_CONFIGURATION;
|
133 | 136 | import static org.elasticsearch.discovery.PeerFinder.DISCOVERY_FIND_PEERS_INTERVAL_SETTING;
|
| 137 | +import static org.elasticsearch.gateway.GatewayService.STATE_NOT_RECOVERED_BLOCK; |
134 | 138 | import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
|
135 | 139 | import static org.elasticsearch.transport.TransportService.NOOP_TRANSPORT_INTERCEPTOR;
|
136 | 140 | import static org.hamcrest.Matchers.containsString;
|
@@ -191,6 +195,45 @@ public void testRepeatableTests() throws Exception {
|
191 | 195 | assertEquals(result1, result2);
|
192 | 196 | }
|
193 | 197 |
|
| 198 | + /** |
| 199 | + * This test was added to verify that state recovery is properly reset on a node after it has become master and successfully |
| 200 | + * recovered a state (see {@link GatewayService}). The situation which triggers this with a decent likelihood is as follows: |
| 201 | + * 3 master-eligible nodes (leader, follower1, follower2), the followers are shut down (leader remains), when followers come back |
| 202 | + * one of them becomes leader and publishes first state (with STATE_NOT_RECOVERED_BLOCK) to old leader, which accepts it. |
| 203 | + * Old leader is initiating an election at the same time, and wins election. It becomes leader again, but as it previously |
| 204 | + * successfully completed state recovery, is never reset to a state where state recovery can be retried. |
| 205 | + */ |
| 206 | + public void testStateRecoveryResetAfterPreviousLeadership() { |
| 207 | + final Cluster cluster = new Cluster(3); |
| 208 | + cluster.runRandomly(); |
| 209 | + cluster.stabilise(); |
| 210 | + |
| 211 | + final ClusterNode leader = cluster.getAnyLeader(); |
| 212 | + final ClusterNode follower1 = cluster.getAnyNodeExcept(leader); |
| 213 | + final ClusterNode follower2 = cluster.getAnyNodeExcept(leader, follower1); |
| 214 | + |
| 215 | + // restart follower1 and follower2 |
| 216 | + for (ClusterNode clusterNode : Arrays.asList(follower1, follower2)) { |
| 217 | + clusterNode.close(); |
| 218 | + cluster.clusterNodes.forEach( |
| 219 | + cn -> cluster.deterministicTaskQueue.scheduleNow(cn.onNode( |
| 220 | + new Runnable() { |
| 221 | + @Override |
| 222 | + public void run() { |
| 223 | + cn.transportService.disconnectFromNode(clusterNode.getLocalNode()); |
| 224 | + } |
| 225 | + |
| 226 | + @Override |
| 227 | + public String toString() { |
| 228 | + return "disconnect from " + clusterNode.getLocalNode() + " after shutdown"; |
| 229 | + } |
| 230 | + }))); |
| 231 | + cluster.clusterNodes.replaceAll(cn -> cn == clusterNode ? cn.restartedNode() : cn); |
| 232 | + } |
| 233 | + |
| 234 | + cluster.stabilise(); |
| 235 | + } |
| 236 | + |
194 | 237 | public void testCanUpdateClusterStateAfterStabilisation() {
|
195 | 238 | final Cluster cluster = new Cluster(randomIntBetween(1, 5));
|
196 | 239 | cluster.runRandomly();
|
@@ -1525,6 +1568,10 @@ void stabilise(long stabilisationDurationMillis) {
|
1525 | 1568 |
|
1526 | 1569 | assertTrue(leaderId + " has been bootstrapped", leader.coordinator.isInitialConfigurationSet());
|
1527 | 1570 | assertTrue(leaderId + " exists in its last-applied state", leader.getLastAppliedClusterState().getNodes().nodeExists(leaderId));
|
| 1571 | + assertThat(leaderId + " has no NO_MASTER_BLOCK", |
| 1572 | + leader.getLastAppliedClusterState().blocks().hasGlobalBlockWithId(NO_MASTER_BLOCK_ID), equalTo(false)); |
| 1573 | + assertThat(leaderId + " has no STATE_NOT_RECOVERED_BLOCK", |
| 1574 | + leader.getLastAppliedClusterState().blocks().hasGlobalBlock(STATE_NOT_RECOVERED_BLOCK), equalTo(false)); |
1528 | 1575 | assertThat(leaderId + " has applied its state ", leader.getLastAppliedClusterState().getVersion(), isEqualToLeaderVersion);
|
1529 | 1576 |
|
1530 | 1577 | for (final ClusterNode clusterNode : clusterNodes) {
|
@@ -1556,6 +1603,8 @@ void stabilise(long stabilisationDurationMillis) {
|
1556 | 1603 | equalTo(leader.getLocalNode()));
|
1557 | 1604 | assertThat(nodeId + " has no NO_MASTER_BLOCK",
|
1558 | 1605 | clusterNode.getLastAppliedClusterState().blocks().hasGlobalBlockWithId(NO_MASTER_BLOCK_ID), equalTo(false));
|
| 1606 | + assertThat(nodeId + " has no STATE_NOT_RECOVERED_BLOCK", |
| 1607 | + clusterNode.getLastAppliedClusterState().blocks().hasGlobalBlock(STATE_NOT_RECOVERED_BLOCK), equalTo(false)); |
1559 | 1608 | } else {
|
1560 | 1609 | assertThat(nodeId + " is not following " + leaderId, clusterNode.coordinator.getMode(), is(CANDIDATE));
|
1561 | 1610 | assertThat(nodeId + " has no master", clusterNode.getLastAppliedClusterState().nodes().getMasterNode(), nullValue());
|
@@ -1725,7 +1774,8 @@ class MockPersistedState implements PersistedState {
|
1725 | 1774 | } else {
|
1726 | 1775 | nodeEnvironment = null;
|
1727 | 1776 | delegate = new InMemoryPersistedState(0L,
|
1728 |
| - clusterState(0L, 0L, localNode, VotingConfiguration.EMPTY_CONFIG, VotingConfiguration.EMPTY_CONFIG, 0L)); |
| 1777 | + ClusterStateUpdaters.addStateNotRecoveredBlock( |
| 1778 | + clusterState(0L, 0L, localNode, VotingConfiguration.EMPTY_CONFIG, VotingConfiguration.EMPTY_CONFIG, 0L))); |
1729 | 1779 | }
|
1730 | 1780 | } catch (IOException e) {
|
1731 | 1781 | throw new UncheckedIOException("Unable to create MockPersistedState", e);
|
@@ -1765,8 +1815,9 @@ class MockPersistedState implements PersistedState {
|
1765 | 1815 | clusterState.writeTo(outStream);
|
1766 | 1816 | StreamInput inStream = new NamedWriteableAwareStreamInput(outStream.bytes().streamInput(),
|
1767 | 1817 | new NamedWriteableRegistry(ClusterModule.getNamedWriteables()));
|
| 1818 | + // adapt cluster state to new localNode instance and add blocks |
1768 | 1819 | delegate = new InMemoryPersistedState(adaptCurrentTerm.apply(oldState.getCurrentTerm()),
|
1769 |
| - ClusterState.readFrom(inStream, newLocalNode)); // adapts it to new localNode instance |
| 1820 | + ClusterStateUpdaters.addStateNotRecoveredBlock(ClusterState.readFrom(inStream, newLocalNode))); |
1770 | 1821 | }
|
1771 | 1822 | } catch (IOException e) {
|
1772 | 1823 | throw new UncheckedIOException("Unable to create MockPersistedState", e);
|
@@ -1870,15 +1921,19 @@ protected Optional<DisruptableMockTransport> getDisruptableMockTransport(Transpo
|
1870 | 1921 | transportService));
|
1871 | 1922 | final Collection<BiConsumer<DiscoveryNode, ClusterState>> onJoinValidators =
|
1872 | 1923 | Collections.singletonList((dn, cs) -> extraJoinValidators.forEach(validator -> validator.accept(dn, cs)));
|
| 1924 | + final AllocationService allocationService = ESAllocationTestCase.createAllocationService(Settings.EMPTY); |
1873 | 1925 | coordinator = new Coordinator("test_node", settings, clusterSettings, transportService, writableRegistry(),
|
1874 |
| - ESAllocationTestCase.createAllocationService(Settings.EMPTY), masterService, this::getPersistedState, |
| 1926 | + allocationService, masterService, this::getPersistedState, |
1875 | 1927 | Cluster.this::provideSeedHosts, clusterApplierService, onJoinValidators, Randomness.get());
|
1876 | 1928 | masterService.setClusterStatePublisher(coordinator);
|
| 1929 | + final GatewayService gatewayService = new GatewayService(settings, allocationService, clusterService, |
| 1930 | + deterministicTaskQueue.getThreadPool(this::onNode), null, null, coordinator); |
1877 | 1931 |
|
1878 | 1932 | logger.trace("starting up [{}]", localNode);
|
1879 | 1933 | transportService.start();
|
1880 | 1934 | transportService.acceptIncomingRequests();
|
1881 | 1935 | coordinator.start();
|
| 1936 | + gatewayService.start(); |
1882 | 1937 | clusterService.start();
|
1883 | 1938 | coordinator.startInitialJoin();
|
1884 | 1939 | }
|
|
0 commit comments