|
52 | 52 | import org.opensearch.action.support.PlainActionFuture;
|
53 | 53 | import org.opensearch.cluster.metadata.IndexMetadata;
|
54 | 54 | import org.opensearch.cluster.node.DiscoveryNode;
|
| 55 | +import org.opensearch.cluster.routing.IndexShardRoutingTable; |
| 56 | +import org.opensearch.cluster.routing.ShardRouting; |
55 | 57 | import org.opensearch.common.Numbers;
|
56 | 58 | import org.opensearch.common.Randomness;
|
57 | 59 | import org.opensearch.common.SetOnce;
|
|
141 | 143 | import static org.mockito.Mockito.anyString;
|
142 | 144 | import static org.mockito.Mockito.doAnswer;
|
143 | 145 | import static org.mockito.Mockito.mock;
|
| 146 | +import static org.mockito.Mockito.times; |
| 147 | +import static org.mockito.Mockito.verify; |
144 | 148 | import static org.mockito.Mockito.when;
|
145 | 149 |
|
146 | 150 | /**
|
@@ -746,6 +750,206 @@ void phase2(
|
746 | 750 | assertFalse(phase2Called.get());
|
747 | 751 | }
|
748 | 752 |
|
| 753 | + /* |
| 754 | + If the replica allocation id is not reflected in source nodes routing table even after retries, |
| 755 | + recoveries should fail |
| 756 | + */ |
| 757 | + public void testThrowExceptionOnNoTargetInRouting() throws IOException { |
| 758 | + final RecoverySettings recoverySettings = new RecoverySettings(Settings.EMPTY, service); |
| 759 | + final StartRecoveryRequest request = getStartRecoveryRequest(); |
| 760 | + final IndexShard shard = mock(IndexShard.class); |
| 761 | + when(shard.seqNoStats()).thenReturn(mock(SeqNoStats.class)); |
| 762 | + when(shard.segmentStats(anyBoolean(), anyBoolean())).thenReturn(mock(SegmentsStats.class)); |
| 763 | + when(shard.isRelocatedPrimary()).thenReturn(false); |
| 764 | + final org.opensearch.index.shard.ReplicationGroup replicationGroup = mock(org.opensearch.index.shard.ReplicationGroup.class); |
| 765 | + final IndexShardRoutingTable routingTable = mock(IndexShardRoutingTable.class); |
| 766 | + when(routingTable.getByAllocationId(anyString())).thenReturn(null); |
| 767 | + when(shard.getReplicationGroup()).thenReturn(replicationGroup); |
| 768 | + when(replicationGroup.getRoutingTable()).thenReturn(routingTable); |
| 769 | + when(shard.acquireSafeIndexCommit()).thenReturn(mock(GatedCloseable.class)); |
| 770 | + doAnswer(invocation -> { |
| 771 | + ((ActionListener<Releasable>) invocation.getArguments()[0]).onResponse(() -> {}); |
| 772 | + return null; |
| 773 | + }).when(shard).acquirePrimaryOperationPermit(any(), anyString(), any()); |
| 774 | + |
| 775 | + final IndexMetadata.Builder indexMetadata = IndexMetadata.builder("test") |
| 776 | + .settings( |
| 777 | + Settings.builder() |
| 778 | + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, between(0, 5)) |
| 779 | + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, between(1, 5)) |
| 780 | + .put(IndexMetadata.SETTING_VERSION_CREATED, VersionUtils.randomVersion(random())) |
| 781 | + .put(IndexMetadata.SETTING_INDEX_UUID, UUIDs.randomBase64UUID(random())) |
| 782 | + ); |
| 783 | + if (randomBoolean()) { |
| 784 | + indexMetadata.state(IndexMetadata.State.CLOSE); |
| 785 | + } |
| 786 | + when(shard.indexSettings()).thenReturn(new IndexSettings(indexMetadata.build(), Settings.EMPTY)); |
| 787 | + |
| 788 | + final AtomicBoolean phase1Called = new AtomicBoolean(); |
| 789 | + final AtomicBoolean prepareTargetForTranslogCalled = new AtomicBoolean(); |
| 790 | + final AtomicBoolean phase2Called = new AtomicBoolean(); |
| 791 | + final RecoverySourceHandler handler = new LocalStorePeerRecoverySourceHandler( |
| 792 | + shard, |
| 793 | + mock(RecoveryTargetHandler.class), |
| 794 | + threadPool, |
| 795 | + request, |
| 796 | + Math.toIntExact(recoverySettings.getChunkSize().getBytes()), |
| 797 | + between(1, 8), |
| 798 | + between(1, 8) |
| 799 | + ) { |
| 800 | + |
| 801 | + @Override |
| 802 | + void phase1( |
| 803 | + IndexCommit snapshot, |
| 804 | + long startingSeqNo, |
| 805 | + IntSupplier translogOps, |
| 806 | + ActionListener<SendFileResult> listener, |
| 807 | + boolean skipCreateRetentionLeaseStep |
| 808 | + ) { |
| 809 | + phase1Called.set(true); |
| 810 | + super.phase1(snapshot, startingSeqNo, translogOps, listener, skipCreateRetentionLeaseStep); |
| 811 | + } |
| 812 | + |
| 813 | + @Override |
| 814 | + void prepareTargetForTranslog(int totalTranslogOps, ActionListener<TimeValue> listener) { |
| 815 | + prepareTargetForTranslogCalled.set(true); |
| 816 | + super.prepareTargetForTranslog(totalTranslogOps, listener); |
| 817 | + } |
| 818 | + |
| 819 | + @Override |
| 820 | + void phase2( |
| 821 | + long startingSeqNo, |
| 822 | + long endingSeqNo, |
| 823 | + Translog.Snapshot snapshot, |
| 824 | + long maxSeenAutoIdTimestamp, |
| 825 | + long maxSeqNoOfUpdatesOrDeletes, |
| 826 | + RetentionLeases retentionLeases, |
| 827 | + long mappingVersion, |
| 828 | + ActionListener<SendSnapshotResult> listener |
| 829 | + ) throws IOException { |
| 830 | + phase2Called.set(true); |
| 831 | + super.phase2( |
| 832 | + startingSeqNo, |
| 833 | + endingSeqNo, |
| 834 | + snapshot, |
| 835 | + maxSeenAutoIdTimestamp, |
| 836 | + maxSeqNoOfUpdatesOrDeletes, |
| 837 | + retentionLeases, |
| 838 | + mappingVersion, |
| 839 | + listener |
| 840 | + ); |
| 841 | + } |
| 842 | + |
| 843 | + }; |
| 844 | + PlainActionFuture<RecoveryResponse> future = new PlainActionFuture<>(); |
| 845 | + expectThrows(DelayRecoveryException.class, () -> { |
| 846 | + handler.recoverToTarget(future); |
| 847 | + future.actionGet(); |
| 848 | + }); |
| 849 | + verify(routingTable, times(5)).getByAllocationId(null); |
| 850 | + assertFalse(phase1Called.get()); |
| 851 | + assertFalse(prepareTargetForTranslogCalled.get()); |
| 852 | + assertFalse(phase2Called.get()); |
| 853 | + } |
| 854 | + |
| 855 | + /* |
| 856 | + Tests when the replica allocation id is reflected in source nodes routing table even after 1 retry |
| 857 | + */ |
| 858 | + public void testTargetInRoutingInSecondAttempt() throws IOException { |
| 859 | + final RecoverySettings recoverySettings = new RecoverySettings(Settings.EMPTY, service); |
| 860 | + final StartRecoveryRequest request = getStartRecoveryRequest(); |
| 861 | + final IndexShard shard = mock(IndexShard.class); |
| 862 | + when(shard.seqNoStats()).thenReturn(mock(SeqNoStats.class)); |
| 863 | + when(shard.segmentStats(anyBoolean(), anyBoolean())).thenReturn(mock(SegmentsStats.class)); |
| 864 | + when(shard.isRelocatedPrimary()).thenReturn(false); |
| 865 | + when(shard.getRetentionLeases()).thenReturn(mock(RetentionLeases.class)); |
| 866 | + final org.opensearch.index.shard.ReplicationGroup replicationGroup = mock(org.opensearch.index.shard.ReplicationGroup.class); |
| 867 | + final IndexShardRoutingTable routingTable = mock(IndexShardRoutingTable.class); |
| 868 | + final ShardRouting shardRouting = mock(ShardRouting.class); |
| 869 | + when(shardRouting.initializing()).thenReturn(true); |
| 870 | + when(shardRouting.currentNodeId()).thenReturn("node"); |
| 871 | + when(routingTable.getByAllocationId(any())).thenReturn(null, shardRouting); |
| 872 | + when(shard.getReplicationGroup()).thenReturn(replicationGroup); |
| 873 | + when(replicationGroup.getRoutingTable()).thenReturn(routingTable); |
| 874 | + when(shard.acquireSafeIndexCommit()).thenReturn(mock(GatedCloseable.class)); |
| 875 | + doAnswer(invocation -> { |
| 876 | + ((ActionListener<Releasable>) invocation.getArguments()[0]).onResponse(() -> {}); |
| 877 | + return null; |
| 878 | + }).when(shard).acquirePrimaryOperationPermit(any(), anyString(), any()); |
| 879 | + |
| 880 | + final IndexMetadata.Builder indexMetadata = IndexMetadata.builder("test") |
| 881 | + .settings( |
| 882 | + Settings.builder() |
| 883 | + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, between(0, 5)) |
| 884 | + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, between(1, 5)) |
| 885 | + .put(IndexMetadata.SETTING_VERSION_CREATED, VersionUtils.randomVersion(random())) |
| 886 | + .put(IndexMetadata.SETTING_INDEX_UUID, UUIDs.randomBase64UUID(random())) |
| 887 | + ); |
| 888 | + if (randomBoolean()) { |
| 889 | + indexMetadata.state(IndexMetadata.State.CLOSE); |
| 890 | + } |
| 891 | + when(shard.indexSettings()).thenReturn(new IndexSettings(indexMetadata.build(), Settings.EMPTY)); |
| 892 | + |
| 893 | + final AtomicBoolean phase1Called = new AtomicBoolean(); |
| 894 | + final AtomicBoolean prepareTargetForTranslogCalled = new AtomicBoolean(); |
| 895 | + final AtomicBoolean phase2Called = new AtomicBoolean(); |
| 896 | + final RecoverySourceHandler handler = new LocalStorePeerRecoverySourceHandler( |
| 897 | + shard, |
| 898 | + mock(RecoveryTargetHandler.class), |
| 899 | + threadPool, |
| 900 | + request, |
| 901 | + Math.toIntExact(recoverySettings.getChunkSize().getBytes()), |
| 902 | + between(1, 8), |
| 903 | + between(1, 8) |
| 904 | + ) { |
| 905 | + |
| 906 | + @Override |
| 907 | + void phase1( |
| 908 | + IndexCommit snapshot, |
| 909 | + long startingSeqNo, |
| 910 | + IntSupplier translogOps, |
| 911 | + ActionListener<SendFileResult> listener, |
| 912 | + boolean skipCreateRetentionLeaseStep |
| 913 | + ) { |
| 914 | + phase1Called.set(true); |
| 915 | + super.phase1(snapshot, startingSeqNo, translogOps, listener, skipCreateRetentionLeaseStep); |
| 916 | + } |
| 917 | + |
| 918 | + @Override |
| 919 | + void prepareTargetForTranslog(int totalTranslogOps, ActionListener<TimeValue> listener) { |
| 920 | + prepareTargetForTranslogCalled.set(true); |
| 921 | + super.prepareTargetForTranslog(totalTranslogOps, listener); |
| 922 | + } |
| 923 | + |
| 924 | + @Override |
| 925 | + void phase2( |
| 926 | + long startingSeqNo, |
| 927 | + long endingSeqNo, |
| 928 | + Translog.Snapshot snapshot, |
| 929 | + long maxSeenAutoIdTimestamp, |
| 930 | + long maxSeqNoOfUpdatesOrDeletes, |
| 931 | + RetentionLeases retentionLeases, |
| 932 | + long mappingVersion, |
| 933 | + ActionListener<SendSnapshotResult> listener |
| 934 | + ) throws IOException { |
| 935 | + phase2Called.set(true); |
| 936 | + super.phase2( |
| 937 | + startingSeqNo, |
| 938 | + endingSeqNo, |
| 939 | + snapshot, |
| 940 | + maxSeenAutoIdTimestamp, |
| 941 | + maxSeqNoOfUpdatesOrDeletes, |
| 942 | + retentionLeases, |
| 943 | + mappingVersion, |
| 944 | + listener |
| 945 | + ); |
| 946 | + } |
| 947 | + |
| 948 | + }; |
| 949 | + handler.waitForAssignmentPropagate(new SetOnce<>()); |
| 950 | + verify(routingTable, times(2)).getByAllocationId(null); |
| 951 | + } |
| 952 | + |
749 | 953 | public void testCancellationsDoesNotLeakPrimaryPermits() throws Exception {
|
750 | 954 | final CancellableThreads cancellableThreads = new CancellableThreads();
|
751 | 955 | final IndexShard shard = mock(IndexShard.class);
|
|
0 commit comments