Skip to content

Commit c30e05e

Browse files
Prevent Old Version Clusters From Corrupting Snapshot Repositories (#50853)
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 ea4c2e3 commit c30e05e

File tree

2 files changed

+39
-25
lines changed

2 files changed

+39
-25
lines changed

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

+38-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,13 +31,15 @@
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;
@@ -60,8 +63,6 @@
6063
* <li>Run against the old version cluster from the first step: {@link TestStep#STEP3_OLD_CLUSTER}</li>
6164
* <li>Run against the current version cluster from the second step: {@link TestStep#STEP4_NEW_CLUSTER}</li>
6265
* </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.
6566
*/
6667
public class MultiVersionRepositoryAccessIT extends ESRestTestCase {
6768

@@ -98,7 +99,7 @@ public static TestStep parse(String value) {
9899
}
99100
}
100101

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

103104
@Override
104105
protected boolean preserveSnapshotsUponCompletion() {
@@ -192,31 +193,46 @@ public void testReadOnlyRepo() throws IOException {
192193
}
193194

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