Skip to content

Commit 609b015

Browse files
Prevent Old Version Clusters From Corrupting Snapshot Repositories (#50853) (#50913)
Follow up to #50692 that starts writing a `min_version` field to the `RepositoryData` so that pre-7.6 ES versions can not read it (and potentially corrupt it if they attempt to modify the repo contents) after the repository moved to the new metadata format.
1 parent cc8aafc commit 609b015

File tree

2 files changed

+40
-25
lines changed

2 files changed

+40
-25
lines changed

qa/repository-multi-version/src/test/java/org/elasticsearch/upgrades/MultiVersionRepositoryAccessIT.java

+39-22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.upgrades;
2121

22+
import org.elasticsearch.ElasticsearchStatusException;
2223
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
2324
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
2425
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
@@ -30,18 +31,21 @@
3031
import org.elasticsearch.client.Request;
3132
import org.elasticsearch.client.RequestOptions;
3233
import org.elasticsearch.client.Response;
34+
import org.elasticsearch.client.ResponseException;
3335
import org.elasticsearch.client.RestClient;
3436
import org.elasticsearch.client.RestHighLevelClient;
3537
import org.elasticsearch.common.settings.Settings;
3638
import org.elasticsearch.common.xcontent.DeprecationHandler;
3739
import org.elasticsearch.common.xcontent.XContentParser;
3840
import org.elasticsearch.common.xcontent.json.JsonXContent;
3941
import org.elasticsearch.snapshots.RestoreInfo;
42+
import org.elasticsearch.snapshots.SnapshotsService;
4043
import org.elasticsearch.test.rest.ESRestTestCase;
4144

4245
import java.io.IOException;
4346
import java.io.InputStream;
4447
import java.net.HttpURLConnection;
48+
import java.util.Arrays;
4549
import java.util.List;
4650
import java.util.Map;
4751
import java.util.stream.Collectors;
@@ -60,8 +64,6 @@
6064
* <li>Run against the old version cluster from the first step: {@link TestStep#STEP3_OLD_CLUSTER}</li>
6165
* <li>Run against the current version cluster from the second step: {@link TestStep#STEP4_NEW_CLUSTER}</li>
6266
* </ul>
63-
* TODO: Add two more steps: delete all old version snapshots from the repository, then downgrade again and verify that the repository
64-
* is not being corrupted. This requires first merging the logic for reading the min_version field in RepositoryData back to 7.6.
6567
*/
6668
public class MultiVersionRepositoryAccessIT extends ESRestTestCase {
6769

@@ -98,7 +100,7 @@ public static TestStep parse(String value) {
98100
}
99101
}
100102

101-
protected static final TestStep TEST_STEP = TestStep.parse(System.getProperty("tests.rest.suite"));
103+
private static final TestStep TEST_STEP = TestStep.parse(System.getProperty("tests.rest.suite"));
102104

103105
@Override
104106
protected boolean preserveSnapshotsUponCompletion() {
@@ -192,31 +194,46 @@ public void testReadOnlyRepo() throws IOException {
192194
}
193195

194196
public void testUpgradeMovesRepoToNewMetaVersion() throws IOException {
195-
if (TEST_STEP.ordinal() > 1) {
196-
// Only testing the first 2 steps here
197-
return;
198-
}
199197
final String repoName = getTestName();
200198
try (RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(adminClient().getNodes().toArray(new Node[0])))) {
201199
final int shards = 3;
202200
createIndex(client, "test-index", shards);
203201
createRepository(client, repoName, false);
204-
createSnapshot(client, repoName, "snapshot-" + TEST_STEP);
205-
final List<Map<String, Object>> snapshots = listSnapshots(repoName);
206-
// Every step creates one snapshot
207-
assertThat(snapshots, hasSize(TEST_STEP.ordinal() + 1));
208-
assertSnapshotStatusSuccessful(client, repoName,
209-
snapshots.stream().map(sn -> (String) sn.get("snapshot")).toArray(String[]::new));
210-
if (TEST_STEP == TestStep.STEP1_OLD_CLUSTER) {
211-
ensureSnapshotRestoreWorks(client, repoName, "snapshot-" + TestStep.STEP1_OLD_CLUSTER, shards);
202+
// only create some snapshots in the first two steps
203+
if (TEST_STEP == TestStep.STEP1_OLD_CLUSTER || TEST_STEP == TestStep.STEP2_NEW_CLUSTER) {
204+
createSnapshot(client, repoName, "snapshot-" + TEST_STEP);
205+
final List<Map<String, Object>> snapshots = listSnapshots(repoName);
206+
// Every step creates one snapshot
207+
assertThat(snapshots, hasSize(TEST_STEP.ordinal() + 1));
208+
assertSnapshotStatusSuccessful(client, repoName,
209+
snapshots.stream().map(sn -> (String) sn.get("snapshot")).toArray(String[]::new));
210+
if (TEST_STEP == TestStep.STEP1_OLD_CLUSTER) {
211+
ensureSnapshotRestoreWorks(client, repoName, "snapshot-" + TestStep.STEP1_OLD_CLUSTER, shards);
212+
} else {
213+
deleteSnapshot(client, repoName, "snapshot-" + TestStep.STEP1_OLD_CLUSTER);
214+
ensureSnapshotRestoreWorks(client, repoName, "snapshot-" + TestStep.STEP2_NEW_CLUSTER, shards);
215+
createSnapshot(client, repoName, "snapshot-1");
216+
ensureSnapshotRestoreWorks(client, repoName, "snapshot-1", shards);
217+
deleteSnapshot(client, repoName, "snapshot-" + TestStep.STEP2_NEW_CLUSTER);
218+
createSnapshot(client, repoName, "snapshot-2");
219+
ensureSnapshotRestoreWorks(client, repoName, "snapshot-2", shards);
220+
}
212221
} else {
213-
deleteSnapshot(client, repoName, "snapshot-" + TestStep.STEP1_OLD_CLUSTER);
214-
ensureSnapshotRestoreWorks(client, repoName, "snapshot-" + TestStep.STEP2_NEW_CLUSTER, shards);
215-
createSnapshot(client, repoName, "snapshot-1");
216-
ensureSnapshotRestoreWorks(client, repoName, "snapshot-1", shards);
217-
deleteSnapshot(client, repoName, "snapshot-" + TestStep.STEP2_NEW_CLUSTER);
218-
createSnapshot(client, repoName, "snapshot-2");
219-
ensureSnapshotRestoreWorks(client, repoName, "snapshot-2", shards);
222+
if (minimumNodeVersion().before(SnapshotsService.SHARD_GEN_IN_REPO_DATA_VERSION)) {
223+
assertThat(TEST_STEP, is(TestStep.STEP3_OLD_CLUSTER));
224+
final List<Class<? extends Exception>> expectedExceptions =
225+
Arrays.asList(ResponseException.class, ElasticsearchStatusException.class);
226+
expectThrowsAnyOf(expectedExceptions, () -> listSnapshots(repoName));
227+
expectThrowsAnyOf(expectedExceptions, () -> deleteSnapshot(client, repoName, "snapshot-1"));
228+
expectThrowsAnyOf(expectedExceptions, () -> deleteSnapshot(client, repoName, "snapshot-2"));
229+
expectThrowsAnyOf(expectedExceptions, () -> createSnapshot(client, repoName, "snapshot-impossible"));
230+
} else {
231+
assertThat(listSnapshots(repoName), hasSize(2));
232+
if (TEST_STEP == TestStep.STEP4_NEW_CLUSTER) {
233+
ensureSnapshotRestoreWorks(client, repoName, "snapshot-1", shards);
234+
ensureSnapshotRestoreWorks(client, repoName, "snapshot-2", shards);
235+
}
236+
}
220237
}
221238
} finally {
222239
deleteRepository(repoName);

server/src/main/java/org/elasticsearch/repositories/RepositoryData.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,8 @@ public XContentBuilder snapshotsToXContent(final XContentBuilder builder, final
365365
}
366366
builder.endObject();
367367
if (shouldWriteShardGens) {
368-
// TODO: write this field once 7.6 is able to read it and add tests to :qa:snapshot-repository-downgrade that make sure older
369-
// ES versions can't corrupt the repository by writing to it and all the snapshots in it are v7.6 or newer
370368
// Add min version field to make it impossible for older ES versions to deserialize this object
371-
// builder.field(MIN_VERSION, SnapshotsService.SHARD_GEN_IN_REPO_DATA_VERSION.toString());
369+
builder.field(MIN_VERSION, SnapshotsService.SHARD_GEN_IN_REPO_DATA_VERSION.toString());
372370
}
373371
builder.endObject();
374372
return builder;

0 commit comments

Comments
 (0)