|
8 | 8 |
|
9 | 9 | import org.elasticsearch.ElasticsearchException; |
10 | 10 | import org.elasticsearch.ElasticsearchStatusException; |
| 11 | +import org.elasticsearch.ExceptionsHelper; |
| 12 | +import org.elasticsearch.ResourceAlreadyExistsException; |
| 13 | +import org.elasticsearch.ResourceNotFoundException; |
11 | 14 | import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; |
12 | 15 | import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest; |
13 | 16 | import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse; |
|
16 | 19 | import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; |
17 | 20 | import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; |
18 | 21 | import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse; |
| 22 | +import org.elasticsearch.action.admin.indices.flush.FlushRequest; |
| 23 | +import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeRequest; |
19 | 24 | import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; |
20 | 25 | import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; |
21 | 26 | import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; |
22 | 27 | import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; |
| 28 | +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; |
23 | 29 | import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; |
24 | 30 | import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; |
25 | 31 | import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest; |
|
53 | 59 | import org.elasticsearch.index.shard.ShardId; |
54 | 60 | import org.elasticsearch.persistent.PersistentTasksCustomMetaData; |
55 | 61 | import org.elasticsearch.rest.RestStatus; |
| 62 | +import org.elasticsearch.snapshots.SnapshotRestoreException; |
56 | 63 | import org.elasticsearch.tasks.TaskInfo; |
57 | 64 | import org.elasticsearch.transport.NoSuchRemoteClusterException; |
58 | 65 | import org.elasticsearch.xpack.CcrIntegTestCase; |
|
75 | 82 | import java.util.Locale; |
76 | 83 | import java.util.Map; |
77 | 84 | import java.util.Objects; |
| 85 | +import java.util.Set; |
78 | 86 | import java.util.concurrent.Semaphore; |
79 | 87 | import java.util.concurrent.TimeUnit; |
80 | 88 | import java.util.concurrent.atomic.AtomicBoolean; |
|
86 | 94 | import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; |
87 | 95 | import static org.hamcrest.Matchers.empty; |
88 | 96 | import static org.hamcrest.Matchers.equalTo; |
| 97 | +import static org.hamcrest.Matchers.greaterThan; |
89 | 98 | import static org.hamcrest.Matchers.greaterThanOrEqualTo; |
90 | 99 | import static org.hamcrest.Matchers.hasSize; |
91 | 100 | import static org.hamcrest.Matchers.is; |
@@ -943,6 +952,98 @@ public void testUpdateAnalysisLeaderIndexSettings() throws Exception { |
943 | 952 | assertThat(hasFollowIndexBeenClosedChecker.getAsBoolean(), is(true)); |
944 | 953 | } |
945 | 954 |
|
| 955 | + public void testMustCloseIndexAndPauseToRestartWithPutFollowing() throws Exception { |
| 956 | + final int numberOfPrimaryShards = randomIntBetween(1, 3); |
| 957 | + final String leaderIndexSettings = getIndexSettings(numberOfPrimaryShards, between(0, 1), |
| 958 | + singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true")); |
| 959 | + assertAcked(leaderClient().admin().indices().prepareCreate("index1").setSource(leaderIndexSettings, XContentType.JSON)); |
| 960 | + ensureLeaderYellow("index1"); |
| 961 | + |
| 962 | + final PutFollowAction.Request followRequest = putFollow("index1", "index2"); |
| 963 | + PutFollowAction.Response response = followerClient().execute(PutFollowAction.INSTANCE, followRequest).get(); |
| 964 | + assertTrue(response.isFollowIndexCreated()); |
| 965 | + assertTrue(response.isFollowIndexShardsAcked()); |
| 966 | + assertTrue(response.isIndexFollowingStarted()); |
| 967 | + |
| 968 | + final PutFollowAction.Request followRequest2 = putFollow("index1", "index2"); |
| 969 | + expectThrows(SnapshotRestoreException.class, |
| 970 | + () -> followerClient().execute(PutFollowAction.INSTANCE, followRequest2).actionGet()); |
| 971 | + |
| 972 | + followerClient().admin().indices().prepareClose("index2").get(); |
| 973 | + expectThrows(ResourceAlreadyExistsException.class, |
| 974 | + () -> followerClient().execute(PutFollowAction.INSTANCE, followRequest2).actionGet()); |
| 975 | + } |
| 976 | + |
| 977 | + public void testIndexFallBehind() throws Exception { |
| 978 | + final int numberOfPrimaryShards = randomIntBetween(1, 3); |
| 979 | + final String leaderIndexSettings = getIndexSettings(numberOfPrimaryShards, between(0, 1), |
| 980 | + singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true")); |
| 981 | + assertAcked(leaderClient().admin().indices().prepareCreate("index1").setSource(leaderIndexSettings, XContentType.JSON)); |
| 982 | + ensureLeaderYellow("index1"); |
| 983 | + |
| 984 | + final int numDocs = randomIntBetween(2, 64); |
| 985 | + logger.info("Indexing [{}] docs as first batch", numDocs); |
| 986 | + for (int i = 0; i < numDocs; i++) { |
| 987 | + final String source = String.format(Locale.ROOT, "{\"f\":%d}", i); |
| 988 | + leaderClient().prepareIndex("index1", "doc", Integer.toString(i)).setSource(source, XContentType.JSON).get(); |
| 989 | + } |
| 990 | + |
| 991 | + final PutFollowAction.Request followRequest = putFollow("index1", "index2"); |
| 992 | + PutFollowAction.Response response = followerClient().execute(PutFollowAction.INSTANCE, followRequest).get(); |
| 993 | + assertTrue(response.isFollowIndexCreated()); |
| 994 | + assertTrue(response.isFollowIndexShardsAcked()); |
| 995 | + assertTrue(response.isIndexFollowingStarted()); |
| 996 | + |
| 997 | + assertIndexFullyReplicatedToFollower("index1", "index2"); |
| 998 | + for (int i = 0; i < numDocs; i++) { |
| 999 | + assertBusy(assertExpectedDocumentRunnable(i)); |
| 1000 | + } |
| 1001 | + |
| 1002 | + pauseFollow("index2"); |
| 1003 | + |
| 1004 | + for (int i = 0; i < numDocs; i++) { |
| 1005 | + final String source = String.format(Locale.ROOT, "{\"f\":%d}", i * 2); |
| 1006 | + leaderClient().prepareIndex("index1", "doc", Integer.toString(i)).setSource(source, XContentType.JSON).get(); |
| 1007 | + } |
| 1008 | + leaderClient().prepareDelete("index1", "doc", "1").get(); |
| 1009 | + leaderClient().admin().indices().refresh(new RefreshRequest("index1")).actionGet(); |
| 1010 | + leaderClient().admin().indices().flush(new FlushRequest("index1").force(true)).actionGet(); |
| 1011 | + ForceMergeRequest forceMergeRequest = new ForceMergeRequest("index1"); |
| 1012 | + forceMergeRequest.maxNumSegments(1); |
| 1013 | + leaderClient().admin().indices().forceMerge(forceMergeRequest).actionGet(); |
| 1014 | + |
| 1015 | + followerClient().execute(ResumeFollowAction.INSTANCE, followRequest.getFollowRequest()).get(); |
| 1016 | + |
| 1017 | + assertBusy(() -> { |
| 1018 | + List<ShardFollowNodeTaskStatus> statuses = getFollowTaskStatuses("index2"); |
| 1019 | + Set<ResourceNotFoundException> exceptions = statuses.stream() |
| 1020 | + .map(ShardFollowNodeTaskStatus::getFatalException) |
| 1021 | + .filter(Objects::nonNull) |
| 1022 | + .map(ExceptionsHelper::unwrapCause) |
| 1023 | + .filter(e -> e instanceof ResourceNotFoundException) |
| 1024 | + .map(e -> (ResourceNotFoundException) e) |
| 1025 | + .filter(e -> e.getMetadataKeys().contains("es.requested_operations_missing")) |
| 1026 | + .collect(Collectors.toSet()); |
| 1027 | + assertThat(exceptions.size(), greaterThan(0)); |
| 1028 | + }); |
| 1029 | + |
| 1030 | + followerClient().admin().indices().prepareClose("index2").get(); |
| 1031 | + pauseFollow("index2"); |
| 1032 | + |
| 1033 | + |
| 1034 | + final PutFollowAction.Request followRequest2 = putFollow("index1", "index2"); |
| 1035 | + PutFollowAction.Response response2 = followerClient().execute(PutFollowAction.INSTANCE, followRequest2).get(); |
| 1036 | + assertTrue(response2.isFollowIndexCreated()); |
| 1037 | + assertTrue(response2.isFollowIndexShardsAcked()); |
| 1038 | + assertTrue(response2.isIndexFollowingStarted()); |
| 1039 | + |
| 1040 | + ensureFollowerGreen("index2"); |
| 1041 | + assertIndexFullyReplicatedToFollower("index1", "index2"); |
| 1042 | + for (int i = 2; i < numDocs; i++) { |
| 1043 | + assertBusy(assertExpectedDocumentRunnable(i, i * 2)); |
| 1044 | + } |
| 1045 | + } |
| 1046 | + |
946 | 1047 | private long getFollowTaskSettingsVersion(String followerIndex) { |
947 | 1048 | long settingsVersion = -1L; |
948 | 1049 | for (ShardFollowNodeTaskStatus status : getFollowTaskStatuses(followerIndex)) { |
@@ -1028,9 +1129,13 @@ private CheckedRunnable<Exception> assertTask(final int numberOfPrimaryShards, f |
1028 | 1129 | } |
1029 | 1130 |
|
1030 | 1131 | private CheckedRunnable<Exception> assertExpectedDocumentRunnable(final int value) { |
| 1132 | + return assertExpectedDocumentRunnable(value, value); |
| 1133 | + } |
| 1134 | + |
| 1135 | + private CheckedRunnable<Exception> assertExpectedDocumentRunnable(final int key, final int value) { |
1031 | 1136 | return () -> { |
1032 | | - final GetResponse getResponse = followerClient().prepareGet("index2", "doc", Integer.toString(value)).get(); |
1033 | | - assertTrue("Doc with id [" + value + "] is missing", getResponse.isExists()); |
| 1137 | + final GetResponse getResponse = followerClient().prepareGet("index2", "doc", Integer.toString(key)).get(); |
| 1138 | + assertTrue("Doc with id [" + key + "] is missing", getResponse.isExists()); |
1034 | 1139 | assertTrue((getResponse.getSource().containsKey("f"))); |
1035 | 1140 | assertThat(getResponse.getSource().get("f"), equalTo(value)); |
1036 | 1141 | }; |
|
0 commit comments