|
32 | 32 |
|
33 | 33 | package org.opensearch.cluster.coordination;
|
34 | 34 |
|
| 35 | +import java.util.Collection; |
| 36 | +import java.util.concurrent.atomic.AtomicBoolean; |
35 | 37 | import org.opensearch.OpenSearchParseException;
|
36 | 38 | import org.opensearch.Version;
|
37 | 39 | import org.opensearch.action.ActionRequest;
|
38 | 40 | import org.opensearch.action.ActionRequestBuilder;
|
39 | 41 | import org.opensearch.action.index.IndexResponse;
|
40 | 42 | import org.opensearch.action.support.master.AcknowledgedResponse;
|
| 43 | +import org.opensearch.cluster.ClusterInfoService; |
41 | 44 | import org.opensearch.cluster.ClusterState;
|
42 | 45 | import org.opensearch.cluster.ClusterStateUpdateTask;
|
| 46 | +import org.opensearch.cluster.action.shard.ShardStateAction; |
43 | 47 | import org.opensearch.cluster.block.ClusterBlocks;
|
44 | 48 | import org.opensearch.cluster.metadata.IndexMetadata;
|
45 | 49 | import org.opensearch.cluster.metadata.MappingMetadata;
|
46 | 50 | import org.opensearch.cluster.metadata.Metadata;
|
47 | 51 | import org.opensearch.cluster.node.DiscoveryNode;
|
48 | 52 | import org.opensearch.cluster.node.DiscoveryNodes;
|
| 53 | +import org.opensearch.cluster.routing.RoutingChangesObserver; |
| 54 | +import org.opensearch.cluster.routing.RoutingNodes; |
49 | 55 | import org.opensearch.cluster.routing.RoutingTable;
|
50 | 56 | import org.opensearch.cluster.routing.ShardRouting;
|
| 57 | +import org.opensearch.cluster.routing.ShardRoutingState; |
51 | 58 | import org.opensearch.cluster.routing.allocation.AllocationService;
|
| 59 | +import org.opensearch.cluster.routing.allocation.ExistingShardsAllocator; |
52 | 60 | import org.opensearch.cluster.service.ClusterService;
|
53 | 61 | import org.opensearch.common.action.ActionFuture;
|
54 | 62 | import org.opensearch.common.settings.Settings;
|
55 | 63 | import org.opensearch.common.unit.TimeValue;
|
56 | 64 | import org.opensearch.core.action.ActionResponse;
|
57 | 65 | import org.opensearch.core.index.Index;
|
| 66 | +import org.opensearch.core.transport.TransportResponse; |
58 | 67 | import org.opensearch.discovery.Discovery;
|
59 | 68 | import org.opensearch.index.IndexService;
|
60 | 69 | import org.opensearch.index.mapper.DocumentMapper;
|
61 | 70 | import org.opensearch.index.mapper.MapperService;
|
62 | 71 | import org.opensearch.indices.IndicesService;
|
| 72 | +import org.opensearch.plugins.Plugin; |
| 73 | +import org.opensearch.snapshots.SnapshotsInfoService; |
63 | 74 | import org.opensearch.test.OpenSearchIntegTestCase;
|
64 | 75 | import org.opensearch.test.disruption.BlockClusterStateProcessing;
|
| 76 | +import org.opensearch.test.transport.MockTransportService; |
| 77 | +import org.opensearch.transport.TransportService; |
65 | 78 | import org.opensearch.transport.TransportSettings;
|
66 | 79 |
|
67 | 80 | import java.util.List;
|
|
71 | 84 | import static java.util.Collections.emptyMap;
|
72 | 85 | import static java.util.Collections.emptySet;
|
73 | 86 | import static org.opensearch.action.DocWriteResponse.Result.CREATED;
|
| 87 | +import static org.opensearch.cluster.action.shard.ShardStateAction.SHARD_STARTED_ACTION_NAME; |
74 | 88 | import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked;
|
75 | 89 | import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertHitCount;
|
76 | 90 | import static org.hamcrest.Matchers.equalTo;
|
@@ -409,4 +423,145 @@ public void testDelayedMappingPropagationOnReplica() throws Exception {
|
409 | 423 | assertThat(dynamicMappingsFut.get(10, TimeUnit.SECONDS).getResult(), equalTo(CREATED));
|
410 | 424 | }
|
411 | 425 |
|
| 426 | + |
| 427 | + public void testDisassociateNodesWhileShardInit() throws InterruptedException { |
| 428 | + final String clusterManagerName = internalCluster().startClusterManagerOnlyNode(Settings.builder().put(TransportSettings.CONNECT_TIMEOUT.getKey(), "1s") |
| 429 | + .put(ExistingShardsAllocator.EXISTING_SHARDS_ALLOCATOR_BATCH_MODE.getKey(), true).build()); |
| 430 | + internalCluster().startDataOnlyNode(Settings.builder().put(TransportSettings.CONNECT_TIMEOUT.getKey(), "1s").build()); |
| 431 | + internalCluster().startDataOnlyNode(Settings.builder().put(TransportSettings.CONNECT_TIMEOUT.getKey(), "1s").build()); |
| 432 | + String node2 = internalCluster().startDataOnlyNode(Settings.builder().put(TransportSettings.CONNECT_TIMEOUT.getKey(), "1s").build()); |
| 433 | + |
| 434 | + final ClusterService clusterService = internalCluster().clusterService(clusterManagerName); |
| 435 | + blockShardStartedResponse(clusterManagerName, clusterService); |
| 436 | + |
| 437 | + final String index = "index"; |
| 438 | + |
| 439 | + //create index with 3 primary and 1 replica each |
| 440 | + prepareCreate(index).setSettings( |
| 441 | + Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 3).put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1) |
| 442 | + //.put(INDEX_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(), "primaries") |
| 443 | + ).get(); |
| 444 | + ensureGreen(index); |
| 445 | + |
| 446 | + // close to have some unassigned started shards shards.. |
| 447 | + client().admin().indices().prepareClose(index).get(); |
| 448 | + |
| 449 | + //block so that replicas are always in init and not started |
| 450 | + blockReplicaStart.set(true); |
| 451 | + final AllocationService allocationService = internalCluster().getInstance(AllocationService.class, clusterManagerName); |
| 452 | + clusterService.submitStateUpdateTask("test-delete-node-and-reroute", new ClusterStateUpdateTask() { |
| 453 | + @Override |
| 454 | + public ClusterState execute(ClusterState currentState) { |
| 455 | + ClusterState.Builder builder = ClusterState.builder(currentState); |
| 456 | + // open index |
| 457 | + final IndexMetadata indexMetadata = IndexMetadata.builder(currentState.metadata().index(index)) |
| 458 | + .state(IndexMetadata.State.OPEN) |
| 459 | + .build(); |
| 460 | + |
| 461 | + builder.metadata(Metadata.builder(currentState.metadata()).put(indexMetadata, true)); |
| 462 | + builder.blocks(ClusterBlocks.builder().blocks(currentState.blocks()).removeIndexBlocks(index)); |
| 463 | + ClusterState updatedState = builder.build(); |
| 464 | + RoutingTable.Builder routingTable = RoutingTable.builder(updatedState.routingTable()); |
| 465 | + routingTable.addAsRecovery(updatedState.metadata().index(index)); |
| 466 | + updatedState = ClusterState.builder(updatedState).routingTable(routingTable.build()).build(); |
| 467 | + return allocationService.reroute(updatedState, "reroute"); |
| 468 | + } |
| 469 | + |
| 470 | + @Override |
| 471 | + public void onFailure(String source, Exception e) { |
| 472 | + logger.error(e.getMessage(), e); |
| 473 | + } |
| 474 | + }); |
| 475 | + |
| 476 | + ensureYellow(index); |
| 477 | + waitUntil(() -> clusterService.state().getRoutingNodes().shardsWithState(ShardRoutingState.INITIALIZING).size() == 3); |
| 478 | + |
| 479 | + logger.info("Initializing shards"); |
| 480 | + logger.info(clusterService.state().getRoutingNodes().shardsWithState(ShardRoutingState.INITIALIZING)); |
| 481 | + |
| 482 | + //trigger 2nd reroute after shard in initialized |
| 483 | + clusterService.submitStateUpdateTask("test-delete-node-and-reroute", new ClusterStateUpdateTask() { |
| 484 | + @Override |
| 485 | + public ClusterState execute(ClusterState currentState) { |
| 486 | + return allocationService.reroute(currentState, "reroute"); |
| 487 | + } |
| 488 | + |
| 489 | + @Override |
| 490 | + public void onFailure(String source, Exception e) {} |
| 491 | + }); |
| 492 | + ensureYellow(index); |
| 493 | + waitUntil(() -> clusterService.state().getRoutingNodes().shardsWithState(ShardRoutingState.INITIALIZING).size() == 3); |
| 494 | + clusterService.submitStateUpdateTask("test-remove-injected-node", new ClusterStateUpdateTask() { |
| 495 | + @Override |
| 496 | + public ClusterState execute(ClusterState currentState) throws Exception { |
| 497 | + //remove the primary node of replica shard which is in init |
| 498 | + ShardRouting next = currentState.getRoutingNodes().shardsWithState(ShardRoutingState.INITIALIZING).get(0); |
| 499 | + ShardRouting primaryShard = currentState.getRoutingNodes().activePrimary(next.shardId()); |
| 500 | + |
| 501 | + ClusterState.Builder builder = ClusterState.builder(currentState); |
| 502 | + builder.nodes(DiscoveryNodes.builder(currentState.nodes()).remove(primaryShard.currentNodeId())); |
| 503 | + currentState = builder.build(); |
| 504 | + logger.info("removed the node {}", primaryShard.currentNodeId()); |
| 505 | + logger.info("shard {}", next); |
| 506 | + return allocationService.disassociateDeadNodes(currentState, true, "reroute"); |
| 507 | + } |
| 508 | + |
| 509 | + @Override |
| 510 | + public void onFailure(String source, Exception e) {} |
| 511 | + }); |
| 512 | + //sleep for reroute to get triggeredn |
| 513 | + Thread.sleep(300 * 1000); |
| 514 | + |
| 515 | + waitUntil(() -> clusterService.state().nodes().getSize() == 3); |
| 516 | + logger.info(clusterService.state().getRoutingNodes().shardsWithState(ShardRoutingState.INITIALIZING)); |
| 517 | + blockReplicaStart.set(false); |
| 518 | + |
| 519 | + clusterService.submitStateUpdateTask("test-inject-node-and-reroute", new ClusterStateUpdateTask() { |
| 520 | + @Override |
| 521 | + public ClusterState execute(ClusterState currentState) { |
| 522 | + ClusterState.Builder builder = ClusterState.builder(currentState); |
| 523 | + final IndexMetadata indexMetadata = IndexMetadata.builder(currentState.metadata().index(index)) |
| 524 | + .state(IndexMetadata.State.OPEN) |
| 525 | + .build(); |
| 526 | + builder.metadata(Metadata.builder(currentState.metadata()).put(indexMetadata, true)); |
| 527 | + builder.blocks(ClusterBlocks.builder().blocks(currentState.blocks()).removeIndexBlocks(index)); |
| 528 | + ClusterState updatedState = builder.build(); |
| 529 | + RoutingTable.Builder routingTable = RoutingTable.builder(updatedState.routingTable()); |
| 530 | + routingTable.addAsRecovery(updatedState.metadata().index(index)); |
| 531 | + updatedState = ClusterState.builder(updatedState).routingTable(routingTable.build()).build(); |
| 532 | + |
| 533 | + return allocationService.reroute(updatedState, "reroute"); |
| 534 | + } |
| 535 | + |
| 536 | + @Override |
| 537 | + public void onFailure(String source, Exception e) {} |
| 538 | + }); |
| 539 | + |
| 540 | + ensureGreen(index); |
| 541 | + } |
| 542 | + |
| 543 | + AtomicBoolean blockReplicaStart = new AtomicBoolean(false); |
| 544 | + private void blockShardStartedResponse(String master, ClusterService service) { |
| 545 | + MockTransportService primaryService = (MockTransportService) internalCluster().getInstance(TransportService.class, master); |
| 546 | + primaryService.addRequestHandlingBehavior(SHARD_STARTED_ACTION_NAME, (handler, request, channel, task) -> { |
| 547 | + |
| 548 | + if (blockReplicaStart.get()) { |
| 549 | + ShardStateAction.StartedShardEntry req = (ShardStateAction.StartedShardEntry) request; |
| 550 | + final ShardRouting matched = service.state().getRoutingTable().getByAllocationId(req.getShardId(), req.getAllocationId()); |
| 551 | + |
| 552 | + if (matched != null && matched.primary() == false) { |
| 553 | + channel.sendResponse(TransportResponse.Empty.INSTANCE); |
| 554 | + } else { |
| 555 | + handler.messageReceived(request, channel, task); |
| 556 | + } |
| 557 | + } else { |
| 558 | + handler.messageReceived(request, channel, task); |
| 559 | + } |
| 560 | + }); |
| 561 | + } |
| 562 | + |
| 563 | + @Override |
| 564 | + protected Collection<Class<? extends Plugin>> nodePlugins() { |
| 565 | + return List.of(MockTransportService.TestPlugin.class); |
| 566 | + } |
412 | 567 | }
|
0 commit comments