|
9 | 9 | import org.apache.lucene.search.TotalHits;
|
10 | 10 | import org.elasticsearch.ExceptionsHelper;
|
11 | 11 | import org.elasticsearch.ResourceNotFoundException;
|
| 12 | +import org.elasticsearch.action.admin.cluster.allocation.ClusterAllocationExplanation; |
12 | 13 | import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
|
13 | 14 | import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotIndexShardStatus;
|
14 | 15 | import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotStats;
|
|
20 | 21 | import org.elasticsearch.action.admin.indices.stats.ShardStats;
|
21 | 22 | import org.elasticsearch.action.index.IndexRequestBuilder;
|
22 | 23 | import org.elasticsearch.cluster.ClusterState;
|
| 24 | +import org.elasticsearch.cluster.RestoreInProgress; |
23 | 25 | import org.elasticsearch.cluster.metadata.DataStream;
|
24 | 26 | import org.elasticsearch.cluster.metadata.IndexMetadata;
|
25 | 27 | import org.elasticsearch.cluster.metadata.RepositoryMetadata;
|
26 | 28 | import org.elasticsearch.cluster.node.DiscoveryNode;
|
27 | 29 | import org.elasticsearch.cluster.routing.ShardRouting;
|
| 30 | +import org.elasticsearch.cluster.routing.allocation.AllocateUnassignedDecision; |
| 31 | +import org.elasticsearch.cluster.routing.allocation.AllocationDecision; |
| 32 | +import org.elasticsearch.cluster.routing.allocation.NodeAllocationResult; |
| 33 | +import org.elasticsearch.cluster.routing.allocation.decider.Decision; |
28 | 34 | import org.elasticsearch.common.Priority;
|
29 | 35 | import org.elasticsearch.common.Strings;
|
30 | 36 | import org.elasticsearch.common.settings.Settings;
|
@@ -971,6 +977,123 @@ public void testSnapshotOfSearchableSnapshotIncludesNoDataButCanBeRestored() thr
|
971 | 977 | );
|
972 | 978 | }
|
973 | 979 |
|
| 980 | + public void testSnapshotOfSearchableSnapshotCanBeRestoredBeforeRepositoryRegistered() throws Exception { |
| 981 | + final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT); |
| 982 | + createAndPopulateIndex( |
| 983 | + indexName, |
| 984 | + Settings.builder().put(INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1).put(INDEX_SOFT_DELETES_SETTING.getKey(), true) |
| 985 | + ); |
| 986 | + |
| 987 | + final TotalHits originalAllHits = internalCluster().client() |
| 988 | + .prepareSearch(indexName) |
| 989 | + .setTrackTotalHits(true) |
| 990 | + .get() |
| 991 | + .getHits() |
| 992 | + .getTotalHits(); |
| 993 | + final TotalHits originalBarHits = internalCluster().client() |
| 994 | + .prepareSearch(indexName) |
| 995 | + .setTrackTotalHits(true) |
| 996 | + .setQuery(matchQuery("foo", "bar")) |
| 997 | + .get() |
| 998 | + .getHits() |
| 999 | + .getTotalHits(); |
| 1000 | + logger.info("--> [{}] in total, of which [{}] match the query", originalAllHits, originalBarHits); |
| 1001 | + |
| 1002 | + // Take snapshot containing the actual data to one repository |
| 1003 | + final String dataRepoName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT); |
| 1004 | + createRepository(dataRepoName, "fs"); |
| 1005 | + |
| 1006 | + final SnapshotId dataSnapshot = createSnapshot(dataRepoName, "data-snapshot", List.of(indexName)).snapshotId(); |
| 1007 | + assertAcked(client().admin().indices().prepareDelete(indexName)); |
| 1008 | + |
| 1009 | + final String restoredIndexName = randomValueOtherThan(indexName, () -> randomAlphaOfLength(10).toLowerCase(Locale.ROOT)); |
| 1010 | + mountSnapshot(dataRepoName, dataSnapshot.getName(), indexName, restoredIndexName, Settings.EMPTY); |
| 1011 | + ensureGreen(restoredIndexName); |
| 1012 | + |
| 1013 | + if (randomBoolean()) { |
| 1014 | + logger.info("--> closing index before snapshot"); |
| 1015 | + assertAcked(client().admin().indices().prepareClose(restoredIndexName)); |
| 1016 | + } |
| 1017 | + |
| 1018 | + // Back up the cluster to a different repo |
| 1019 | + final String backupRepoName = randomValueOtherThan(dataRepoName, () -> randomAlphaOfLength(10).toLowerCase(Locale.ROOT)); |
| 1020 | + createRepository(backupRepoName, "fs"); |
| 1021 | + final SnapshotId backupSnapshot = createSnapshot(backupRepoName, "backup-snapshot", List.of(restoredIndexName)).snapshotId(); |
| 1022 | + |
| 1023 | + // Clear out data & the repo that contains it |
| 1024 | + final RepositoryMetadata dataRepoMetadata = client().admin() |
| 1025 | + .cluster() |
| 1026 | + .prepareGetRepositories(dataRepoName) |
| 1027 | + .get() |
| 1028 | + .repositories() |
| 1029 | + .get(0); |
| 1030 | + assertAcked(client().admin().indices().prepareDelete(restoredIndexName)); |
| 1031 | + assertAcked(client().admin().cluster().prepareDeleteRepository(dataRepoName)); |
| 1032 | + |
| 1033 | + // Restore the backup snapshot |
| 1034 | + assertThat( |
| 1035 | + client().admin() |
| 1036 | + .cluster() |
| 1037 | + .prepareRestoreSnapshot(backupRepoName, backupSnapshot.getName()) |
| 1038 | + .setIndices(restoredIndexName) |
| 1039 | + .get() |
| 1040 | + .status(), |
| 1041 | + equalTo(RestStatus.ACCEPTED) |
| 1042 | + ); |
| 1043 | + |
| 1044 | + assertBusy(() -> { |
| 1045 | + final ClusterAllocationExplanation clusterAllocationExplanation = client().admin() |
| 1046 | + .cluster() |
| 1047 | + .prepareAllocationExplain() |
| 1048 | + .setIndex(restoredIndexName) |
| 1049 | + .setShard(0) |
| 1050 | + .setPrimary(true) |
| 1051 | + .get() |
| 1052 | + .getExplanation(); |
| 1053 | + |
| 1054 | + final String description = Strings.toString(clusterAllocationExplanation); |
| 1055 | + final AllocateUnassignedDecision allocateDecision = clusterAllocationExplanation.getShardAllocationDecision() |
| 1056 | + .getAllocateDecision(); |
| 1057 | + assertTrue(description, allocateDecision.isDecisionTaken()); |
| 1058 | + assertThat(description, allocateDecision.getAllocationDecision(), equalTo(AllocationDecision.NO)); |
| 1059 | + for (NodeAllocationResult nodeAllocationResult : allocateDecision.getNodeDecisions()) { |
| 1060 | + for (Decision decision : nodeAllocationResult.getCanAllocateDecision().getDecisions()) { |
| 1061 | + final String explanation = decision.getExplanation(); |
| 1062 | + if (explanation.contains("this index is backed by a searchable snapshot") |
| 1063 | + && explanation.contains("no such repository is registered") |
| 1064 | + && explanation.contains("the required repository was originally named [" + dataRepoName + "]")) { |
| 1065 | + return; |
| 1066 | + } |
| 1067 | + } |
| 1068 | + } |
| 1069 | + |
| 1070 | + fail(description); |
| 1071 | + }); |
| 1072 | + |
| 1073 | + assertBusy(() -> { |
| 1074 | + final RestoreInProgress restoreInProgress = client().admin() |
| 1075 | + .cluster() |
| 1076 | + .prepareState() |
| 1077 | + .clear() |
| 1078 | + .setCustoms(true) |
| 1079 | + .get() |
| 1080 | + .getState() |
| 1081 | + .custom(RestoreInProgress.TYPE, RestoreInProgress.EMPTY); |
| 1082 | + assertTrue(Strings.toString(restoreInProgress, true, true), restoreInProgress.isEmpty()); |
| 1083 | + }); |
| 1084 | + |
| 1085 | + // Re-register the repository containing the actual data & verify that the shards are now allocated |
| 1086 | + final String newRepositoryName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT); |
| 1087 | + final Settings.Builder settings = Settings.builder().put(dataRepoMetadata.settings()); |
| 1088 | + if (randomBoolean()) { |
| 1089 | + settings.put(READONLY_SETTING_KEY, "true"); |
| 1090 | + } |
| 1091 | + assertAcked(clusterAdmin().preparePutRepository(newRepositoryName).setType("fs").setSettings(settings)); |
| 1092 | + |
| 1093 | + ensureGreen(restoredIndexName); |
| 1094 | + assertTotalHits(restoredIndexName, originalAllHits, originalBarHits); |
| 1095 | + } |
| 1096 | + |
974 | 1097 | private void assertSearchableSnapshotStats(String indexName, boolean cacheEnabled, List<String> nonCachedExtensions) {
|
975 | 1098 | final SearchableSnapshotsStatsResponse statsResponse = client().execute(
|
976 | 1099 | SearchableSnapshotsStatsAction.INSTANCE,
|
|
0 commit comments