Skip to content

Commit 9a2330d

Browse files
DaveCTurnerdavidkyle
authored andcommitted
Test get-snapshots API with missing details (elastic#111903)
Extends the test added in elastic#111786 to check that the API still works correctly even in the BwC case that the details needed are not in the `RepositoryData` and must be read from the individual `SnapshotInfo` blobs.
1 parent b3c1172 commit 9a2330d

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88

99
package org.elasticsearch.snapshots;
1010

11+
import org.apache.lucene.util.BytesRef;
1112
import org.elasticsearch.action.ActionFuture;
13+
import org.elasticsearch.action.ActionListener;
1214
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;
1317
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
1418
import org.elasticsearch.action.admin.cluster.repositories.put.TransportPutRepositoryAction;
1519
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
@@ -23,17 +27,30 @@
2327
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
2428
import org.elasticsearch.action.admin.indices.create.TransportCreateIndexAction;
2529
import org.elasticsearch.action.support.RefCountingListener;
30+
import org.elasticsearch.action.support.SubscribableListener;
31+
import org.elasticsearch.action.support.master.AcknowledgedResponse;
2632
import org.elasticsearch.cluster.SnapshotsInProgress;
2733
import org.elasticsearch.common.Strings;
34+
import org.elasticsearch.common.blobstore.fs.FsBlobStore;
2835
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;
2939
import org.elasticsearch.core.Predicates;
40+
import org.elasticsearch.repositories.RepositoriesService;
41+
import org.elasticsearch.repositories.RepositoryData;
3042
import org.elasticsearch.repositories.RepositoryMissingException;
43+
import org.elasticsearch.repositories.blobstore.BlobStoreRepository;
3144
import org.elasticsearch.repositories.fs.FsRepository;
3245
import org.elasticsearch.search.sort.SortOrder;
3346
import org.elasticsearch.test.ESTestCase;
47+
import org.elasticsearch.test.XContentTestUtils;
3448
import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
3549
import org.elasticsearch.threadpool.ThreadPool;
50+
import org.elasticsearch.xcontent.XContentType;
51+
import org.elasticsearch.xcontent.json.JsonXContent;
3652

53+
import java.nio.file.Files;
3754
import java.nio.file.Path;
3855
import java.util.ArrayList;
3956
import java.util.Collection;
@@ -819,6 +836,17 @@ public void testAllFeatures() {
819836
}
820837
});
821838

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+
822850
Predicate<SnapshotInfo> snapshotInfoPredicate = Predicates.always();
823851

824852
// {repository} path parameter
@@ -1000,4 +1028,102 @@ public void testAllFeatures() {
10001028

10011029
assertEquals(0, remaining);
10021030
}
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+
}
10031129
}

0 commit comments

Comments
 (0)