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