|
22 | 22 | import org.elasticsearch.action.ActionRequestValidationException; |
23 | 23 | import org.elasticsearch.action.admin.indices.close.CloseIndexResponse; |
24 | 24 | import org.elasticsearch.action.support.ActiveShardCount; |
| 25 | +import org.elasticsearch.client.Client; |
25 | 26 | import org.elasticsearch.cluster.ClusterState; |
26 | 27 | import org.elasticsearch.cluster.block.ClusterBlockException; |
27 | 28 | import org.elasticsearch.cluster.health.ClusterHealthStatus; |
28 | 29 | import org.elasticsearch.cluster.metadata.IndexMetaData; |
29 | 30 | import org.elasticsearch.cluster.metadata.MetaDataIndexStateService; |
| 31 | +import org.elasticsearch.cluster.node.DiscoveryNode; |
30 | 32 | import org.elasticsearch.cluster.routing.ShardRouting; |
31 | 33 | import org.elasticsearch.common.settings.Settings; |
32 | 34 | import org.elasticsearch.common.unit.ByteSizeUnit; |
33 | 35 | import org.elasticsearch.common.unit.ByteSizeValue; |
| 36 | +import org.elasticsearch.common.util.set.Sets; |
34 | 37 | import org.elasticsearch.index.IndexNotFoundException; |
35 | 38 | import org.elasticsearch.index.IndexSettings; |
36 | 39 | import org.elasticsearch.indices.IndexClosedException; |
| 40 | +import org.elasticsearch.indices.recovery.RecoveryState; |
37 | 41 | import org.elasticsearch.test.BackgroundIndexer; |
38 | 42 | import org.elasticsearch.test.ESIntegTestCase; |
| 43 | +import org.elasticsearch.test.InternalTestCluster; |
39 | 44 |
|
40 | 45 | import java.util.ArrayList; |
41 | 46 | import java.util.List; |
42 | 47 | import java.util.Locale; |
43 | 48 | import java.util.concurrent.CountDownLatch; |
| 49 | +import java.util.stream.Collectors; |
44 | 50 | import java.util.stream.IntStream; |
45 | 51 |
|
46 | 52 | import static java.util.Collections.emptySet; |
|
50 | 56 | import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; |
51 | 57 | import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; |
52 | 58 | import static org.hamcrest.Matchers.containsString; |
| 59 | +import static org.hamcrest.Matchers.empty; |
53 | 60 | import static org.hamcrest.Matchers.equalTo; |
54 | 61 | import static org.hamcrest.Matchers.hasSize; |
55 | 62 | import static org.hamcrest.Matchers.is; |
| 63 | +import static org.hamcrest.Matchers.not; |
56 | 64 | import static org.hamcrest.Matchers.notNullValue; |
57 | 65 |
|
58 | 66 | public class CloseIndexIT extends ESIntegTestCase { |
@@ -338,6 +346,81 @@ public void testCloseIndexWaitForActiveShards() throws Exception { |
338 | 346 | assertIndexIsClosed(indexName); |
339 | 347 | } |
340 | 348 |
|
| 349 | + public void testNoopPeerRecoveriesWhenIndexClosed() throws Exception { |
| 350 | + final String indexName = "noop-peer-recovery-test"; |
| 351 | + int numberOfReplicas = between(1, 2); |
| 352 | + internalCluster().ensureAtLeastNumDataNodes(numberOfReplicas + between(1, 2)); |
| 353 | + createIndex(indexName, Settings.builder() |
| 354 | + .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) |
| 355 | + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, numberOfReplicas) |
| 356 | + .put("index.routing.rebalance.enable", "none") |
| 357 | + .build()); |
| 358 | + int iterations = between(1, 3); |
| 359 | + for (int iter = 0; iter < iterations; iter++) { |
| 360 | + indexRandom(randomBoolean(), randomBoolean(), randomBoolean(), IntStream.range(0, randomIntBetween(0, 50)) |
| 361 | + .mapToObj(n -> client().prepareIndex(indexName, "_doc").setSource("num", n)).collect(toList())); |
| 362 | + ensureGreen(indexName); |
| 363 | + |
| 364 | + // Closing an index should execute noop peer recovery |
| 365 | + assertAcked(client().admin().indices().prepareClose(indexName).get()); |
| 366 | + assertIndexIsClosed(indexName); |
| 367 | + ensureGreen(indexName); |
| 368 | + assertNoFileBasedRecovery(indexName); |
| 369 | + internalCluster().assertSameDocIdsOnShards(); |
| 370 | + |
| 371 | + // Open a closed index should execute noop recovery |
| 372 | + assertAcked(client().admin().indices().prepareOpen(indexName).get()); |
| 373 | + assertIndexIsOpened(indexName); |
| 374 | + ensureGreen(indexName); |
| 375 | + assertNoFileBasedRecovery(indexName); |
| 376 | + internalCluster().assertSameDocIdsOnShards(); |
| 377 | + } |
| 378 | + } |
| 379 | + |
| 380 | + /** |
| 381 | + * Ensures that if a replica of a closed index does not have the same content as the primary, then a file-based recovery will occur. |
| 382 | + */ |
| 383 | + public void testRecoverExistingReplica() throws Exception { |
| 384 | + final String indexName = "test-recover-existing-replica"; |
| 385 | + internalCluster().ensureAtLeastNumDataNodes(2); |
| 386 | + List<String> dataNodes = randomSubsetOf(2, Sets.newHashSet( |
| 387 | + clusterService().state().nodes().getDataNodes().valuesIt()).stream().map(DiscoveryNode::getName).collect(Collectors.toSet())); |
| 388 | + createIndex(indexName, Settings.builder() |
| 389 | + .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) |
| 390 | + .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1) |
| 391 | + .put("index.routing.allocation.include._name", String.join(",", dataNodes)) |
| 392 | + .build()); |
| 393 | + indexRandom(randomBoolean(), randomBoolean(), randomBoolean(), IntStream.range(0, randomIntBetween(0, 50)) |
| 394 | + .mapToObj(n -> client().prepareIndex(indexName, "_doc").setSource("num", n)).collect(toList())); |
| 395 | + ensureGreen(indexName); |
| 396 | + if (randomBoolean()) { |
| 397 | + client().admin().indices().prepareFlush(indexName).get(); |
| 398 | + } else { |
| 399 | + client().admin().indices().prepareSyncedFlush(indexName).get(); |
| 400 | + } |
| 401 | + // index more documents while one shard copy is offline |
| 402 | + internalCluster().restartNode(dataNodes.get(1), new InternalTestCluster.RestartCallback() { |
| 403 | + @Override |
| 404 | + public Settings onNodeStopped(String nodeName) throws Exception { |
| 405 | + Client client = client(dataNodes.get(0)); |
| 406 | + int moreDocs = randomIntBetween(1, 50); |
| 407 | + for (int i = 0; i < moreDocs; i++) { |
| 408 | + client.prepareIndex(indexName, "_doc").setSource("num", i).get(); |
| 409 | + } |
| 410 | + assertAcked(client.admin().indices().prepareClose(indexName)); |
| 411 | + return super.onNodeStopped(nodeName); |
| 412 | + } |
| 413 | + }); |
| 414 | + assertIndexIsClosed(indexName); |
| 415 | + ensureGreen(indexName); |
| 416 | + internalCluster().assertSameDocIdsOnShards(); |
| 417 | + for (RecoveryState recovery : client().admin().indices().prepareRecoveries(indexName).get().shardRecoveryStates().get(indexName)) { |
| 418 | + if (recovery.getPrimary() == false) { |
| 419 | + assertThat(recovery.getIndex().fileDetails(), not(empty())); |
| 420 | + } |
| 421 | + } |
| 422 | + } |
| 423 | + |
341 | 424 | static void assertIndexIsClosed(final String... indices) { |
342 | 425 | final ClusterState clusterState = client().admin().cluster().prepareState().get().getState(); |
343 | 426 | for (String index : indices) { |
@@ -383,4 +466,12 @@ static void assertException(final Throwable throwable, final String indexName) { |
383 | 466 | fail("Unexpected exception: " + t); |
384 | 467 | } |
385 | 468 | } |
| 469 | + |
| 470 | + void assertNoFileBasedRecovery(String indexName) { |
| 471 | + for (RecoveryState recovery : client().admin().indices().prepareRecoveries(indexName).get().shardRecoveryStates().get(indexName)) { |
| 472 | + if (recovery.getPrimary() == false) { |
| 473 | + assertThat(recovery.getIndex().fileDetails(), empty()); |
| 474 | + } |
| 475 | + } |
| 476 | + } |
386 | 477 | } |
0 commit comments