|
8 | 8 |
|
9 | 9 | package org.elasticsearch.snapshots;
|
10 | 10 |
|
| 11 | +import org.apache.lucene.util.BytesRef; |
11 | 12 | import org.elasticsearch.action.ActionFuture;
|
| 13 | +import org.elasticsearch.action.ActionListener; |
12 | 14 | import org.elasticsearch.action.ActionRequestValidationException;
|
| 15 | +import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteRepositoryRequest; |
| 16 | +import org.elasticsearch.action.admin.cluster.repositories.delete.TransportDeleteRepositoryAction; |
13 | 17 | import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
|
14 | 18 | import org.elasticsearch.action.admin.cluster.repositories.put.TransportPutRepositoryAction;
|
15 | 19 | import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
|
|
23 | 27 | import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
24 | 28 | import org.elasticsearch.action.admin.indices.create.TransportCreateIndexAction;
|
25 | 29 | import org.elasticsearch.action.support.RefCountingListener;
|
| 30 | +import org.elasticsearch.action.support.SubscribableListener; |
| 31 | +import org.elasticsearch.action.support.master.AcknowledgedResponse; |
26 | 32 | import org.elasticsearch.cluster.SnapshotsInProgress;
|
27 | 33 | import org.elasticsearch.common.Strings;
|
| 34 | +import org.elasticsearch.common.blobstore.fs.FsBlobStore; |
28 | 35 | import org.elasticsearch.common.settings.Settings;
|
| 36 | +import org.elasticsearch.common.util.concurrent.ConcurrentCollections; |
| 37 | +import org.elasticsearch.common.util.concurrent.EsExecutors; |
| 38 | +import org.elasticsearch.common.xcontent.XContentHelper; |
29 | 39 | import org.elasticsearch.core.Predicates;
|
| 40 | +import org.elasticsearch.repositories.RepositoriesService; |
| 41 | +import org.elasticsearch.repositories.RepositoryData; |
30 | 42 | import org.elasticsearch.repositories.RepositoryMissingException;
|
| 43 | +import org.elasticsearch.repositories.blobstore.BlobStoreRepository; |
31 | 44 | import org.elasticsearch.repositories.fs.FsRepository;
|
32 | 45 | import org.elasticsearch.search.sort.SortOrder;
|
33 | 46 | import org.elasticsearch.test.ESTestCase;
|
| 47 | +import org.elasticsearch.test.XContentTestUtils; |
34 | 48 | import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
|
35 | 49 | import org.elasticsearch.threadpool.ThreadPool;
|
| 50 | +import org.elasticsearch.xcontent.XContentType; |
| 51 | +import org.elasticsearch.xcontent.json.JsonXContent; |
36 | 52 |
|
| 53 | +import java.nio.file.Files; |
37 | 54 | import java.nio.file.Path;
|
38 | 55 | import java.util.ArrayList;
|
39 | 56 | import java.util.Collection;
|
@@ -819,6 +836,17 @@ public void testAllFeatures() {
|
819 | 836 | }
|
820 | 837 | });
|
821 | 838 |
|
| 839 | + if (randomBoolean()) { |
| 840 | + // Sometimes also simulate bwc repository contents where some details are missing from the root blob |
| 841 | + safeAwait(l -> { |
| 842 | + try (var listeners = new RefCountingListener(l.map(v -> null))) { |
| 843 | + for (final var repositoryName : randomSubsetOf(repositories)) { |
| 844 | + removeDetailsForRandomSnapshots(repositoryName, listeners.acquire()); |
| 845 | + } |
| 846 | + } |
| 847 | + }); |
| 848 | + } |
| 849 | + |
822 | 850 | Predicate<SnapshotInfo> snapshotInfoPredicate = Predicates.always();
|
823 | 851 |
|
824 | 852 | // {repository} path parameter
|
@@ -1000,4 +1028,102 @@ public void testAllFeatures() {
|
1000 | 1028 |
|
1001 | 1029 | assertEquals(0, remaining);
|
1002 | 1030 | }
|
| 1031 | + |
| 1032 | + /** |
| 1033 | + * Older versions of Elasticsearch don't record in {@link RepositoryData} all the details needed for the get-snapshots API to pick out |
| 1034 | + * the right snapshots, so in this case the API must fall back to reading those details from each candidate {@link SnapshotInfo} blob. |
| 1035 | + * Simulate this situation by manipulating the {@link RepositoryData} blob directly to remove all the optional details from some subset |
| 1036 | + * of its snapshots. |
| 1037 | + */ |
| 1038 | + private static void removeDetailsForRandomSnapshots(String repositoryName, ActionListener<Void> listener) { |
| 1039 | + final Set<SnapshotId> snapshotsWithoutDetails = ConcurrentCollections.newConcurrentSet(); |
| 1040 | + final var masterRepositoriesService = internalCluster().getCurrentMasterNodeInstance(RepositoriesService.class); |
| 1041 | + final var repository = asInstanceOf(FsRepository.class, masterRepositoriesService.repository(repositoryName)); |
| 1042 | + final var repositoryMetadata = repository.getMetadata(); |
| 1043 | + final var repositorySettings = repositoryMetadata.settings(); |
| 1044 | + final var repositoryDataBlobPath = asInstanceOf(FsBlobStore.class, repository.blobStore()).path() |
| 1045 | + .resolve(BlobStoreRepository.INDEX_FILE_PREFIX + repositoryMetadata.generation()); |
| 1046 | + |
| 1047 | + SubscribableListener |
| 1048 | + |
| 1049 | + // unregister the repository while we're mucking around with its internals |
| 1050 | + .<AcknowledgedResponse>newForked( |
| 1051 | + l -> client().execute( |
| 1052 | + TransportDeleteRepositoryAction.TYPE, |
| 1053 | + new DeleteRepositoryRequest(TEST_REQUEST_TIMEOUT, TEST_REQUEST_TIMEOUT, repositoryName), |
| 1054 | + l |
| 1055 | + ) |
| 1056 | + ) |
| 1057 | + .andThenAccept(ElasticsearchAssertions::assertAcked) |
| 1058 | + |
| 1059 | + // rewrite the RepositoryData blob with some details removed |
| 1060 | + .andThenAccept(ignored -> { |
| 1061 | + // load the existing RepositoryData JSON blob as raw maps/lists/etc. |
| 1062 | + final var repositoryDataBytes = Files.readAllBytes(repositoryDataBlobPath); |
| 1063 | + final var repositoryDataMap = XContentHelper.convertToMap( |
| 1064 | + JsonXContent.jsonXContent, |
| 1065 | + repositoryDataBytes, |
| 1066 | + 0, |
| 1067 | + repositoryDataBytes.length, |
| 1068 | + true |
| 1069 | + ); |
| 1070 | + |
| 1071 | + // modify the contents |
| 1072 | + final var snapshotsList = asInstanceOf(List.class, repositoryDataMap.get("snapshots")); |
| 1073 | + for (final var snapshotObj : snapshotsList) { |
| 1074 | + if (randomBoolean()) { |
| 1075 | + continue; |
| 1076 | + } |
| 1077 | + final var snapshotMap = asInstanceOf(Map.class, snapshotObj); |
| 1078 | + snapshotsWithoutDetails.add( |
| 1079 | + new SnapshotId( |
| 1080 | + asInstanceOf(String.class, snapshotMap.get("name")), |
| 1081 | + asInstanceOf(String.class, snapshotMap.get("uuid")) |
| 1082 | + ) |
| 1083 | + ); |
| 1084 | + |
| 1085 | + // remove the optional details fields |
| 1086 | + assertNotNull(snapshotMap.remove("start_time_millis")); |
| 1087 | + assertNotNull(snapshotMap.remove("end_time_millis")); |
| 1088 | + assertNotNull(snapshotMap.remove("slm_policy")); |
| 1089 | + } |
| 1090 | + |
| 1091 | + // overwrite the RepositoryData JSON blob with its new contents |
| 1092 | + final var updatedRepositoryDataBytes = XContentTestUtils.convertToXContent(repositoryDataMap, XContentType.JSON); |
| 1093 | + try (var outputStream = Files.newOutputStream(repositoryDataBlobPath)) { |
| 1094 | + BytesRef bytesRef; |
| 1095 | + final var iterator = updatedRepositoryDataBytes.iterator(); |
| 1096 | + while ((bytesRef = iterator.next()) != null) { |
| 1097 | + outputStream.write(bytesRef.bytes, bytesRef.offset, bytesRef.length); |
| 1098 | + } |
| 1099 | + } |
| 1100 | + }) |
| 1101 | + |
| 1102 | + // re-register the repository |
| 1103 | + .<AcknowledgedResponse>andThen( |
| 1104 | + l -> client().execute( |
| 1105 | + TransportPutRepositoryAction.TYPE, |
| 1106 | + new PutRepositoryRequest(TEST_REQUEST_TIMEOUT, TEST_REQUEST_TIMEOUT, repositoryName).type(FsRepository.TYPE) |
| 1107 | + .settings(repositorySettings), |
| 1108 | + l |
| 1109 | + ) |
| 1110 | + ) |
| 1111 | + .andThenAccept(ElasticsearchAssertions::assertAcked) |
| 1112 | + |
| 1113 | + // verify that the details are indeed now missing |
| 1114 | + .<RepositoryData>andThen( |
| 1115 | + l -> masterRepositoriesService.repository(repositoryName).getRepositoryData(EsExecutors.DIRECT_EXECUTOR_SERVICE, l) |
| 1116 | + ) |
| 1117 | + .andThenAccept(repositoryData -> { |
| 1118 | + for (SnapshotId snapshotId : repositoryData.getSnapshotIds()) { |
| 1119 | + assertEquals( |
| 1120 | + repositoryName + "/" + snapshotId.toString() + ": " + repositoryData.getSnapshotDetails(snapshotId), |
| 1121 | + snapshotsWithoutDetails.contains(snapshotId), |
| 1122 | + repositoryData.hasMissingDetails(snapshotId) |
| 1123 | + ); |
| 1124 | + } |
| 1125 | + }) |
| 1126 | + |
| 1127 | + .addListener(listener); |
| 1128 | + } |
1003 | 1129 | }
|
0 commit comments