Skip to content

Commit 84f233a

Browse files
authored
Allow archive and searchable snapshots indices in N-2 version (#118941)
This change (along with the required change #118923 for 8.18) relaxes the index compatibility version checks to allow archive and searchable snapshot indices in version N-2 to exist on a 9.x cluster. Relates ES-10274
1 parent c808137 commit 84f233a

File tree

18 files changed

+568
-138
lines changed

18 files changed

+568
-138
lines changed

docs/changelog/118941.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 118941
2+
summary: Allow archive and searchable snapshots indices in N-2 version
3+
area: Recovery
4+
type: enhancement
5+
issues: []

qa/lucene-index-compatibility/src/javaRestTest/java/org/elasticsearch/lucene/AbstractLuceneIndexCompatibilityTestCase.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import com.carrotsearch.randomizedtesting.annotations.TestCaseOrdering;
1616

1717
import org.elasticsearch.client.Request;
18+
import org.elasticsearch.common.settings.Settings;
19+
import org.elasticsearch.core.Strings;
1820
import org.elasticsearch.test.cluster.ElasticsearchCluster;
1921
import org.elasticsearch.test.cluster.local.LocalClusterConfigProvider;
2022
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
@@ -28,12 +30,15 @@
2830

2931
import java.util.Comparator;
3032
import java.util.Locale;
33+
import java.util.stream.IntStream;
3134
import java.util.stream.Stream;
3235

3336
import static org.elasticsearch.test.cluster.util.Version.CURRENT;
3437
import static org.elasticsearch.test.cluster.util.Version.fromString;
3538
import static org.elasticsearch.test.rest.ObjectPath.createFromResponse;
39+
import static org.hamcrest.Matchers.allOf;
3640
import static org.hamcrest.Matchers.equalTo;
41+
import static org.hamcrest.Matchers.is;
3742
import static org.hamcrest.Matchers.notNullValue;
3843

3944
/**
@@ -113,6 +118,12 @@ protected String suffix(String name) {
113118
return name + '-' + getTestName().split(" ")[0].toLowerCase(Locale.ROOT);
114119
}
115120

121+
protected Settings repositorySettings() {
122+
return Settings.builder()
123+
.put("location", REPOSITORY_PATH.getRoot().toPath().resolve(suffix("location")).toFile().getPath())
124+
.build();
125+
}
126+
116127
protected static Version clusterVersion() throws Exception {
117128
var response = assertOK(client().performRequest(new Request("GET", "/")));
118129
var responseBody = createFromResponse(response);
@@ -121,12 +132,56 @@ protected static Version clusterVersion() throws Exception {
121132
return version;
122133
}
123134

124-
protected static Version indexLuceneVersion(String indexName) throws Exception {
135+
protected static Version indexVersion(String indexName) throws Exception {
125136
var response = assertOK(client().performRequest(new Request("GET", "/" + indexName + "/_settings")));
126137
int id = Integer.parseInt(createFromResponse(response).evaluate(indexName + ".settings.index.version.created"));
127138
return new Version((byte) ((id / 1000000) % 100), (byte) ((id / 10000) % 100), (byte) ((id / 100) % 100));
128139
}
129140

141+
protected static void indexDocs(String indexName, int numDocs) throws Exception {
142+
var request = new Request("POST", "/_bulk");
143+
var docs = new StringBuilder();
144+
IntStream.range(0, numDocs).forEach(n -> docs.append(Strings.format("""
145+
{"index":{"_id":"%s","_index":"%s"}}
146+
{"test":"test"}
147+
""", n, indexName)));
148+
request.setJsonEntity(docs.toString());
149+
var response = assertOK(client().performRequest(request));
150+
assertThat(entityAsMap(response).get("errors"), allOf(notNullValue(), is(false)));
151+
}
152+
153+
protected static void mountIndex(String repository, String snapshot, String indexName, boolean partial, String renamedIndexName)
154+
throws Exception {
155+
var request = new Request("POST", "/_snapshot/" + repository + "/" + snapshot + "/_mount");
156+
request.addParameter("wait_for_completion", "true");
157+
var storage = partial ? "shared_cache" : "full_copy";
158+
request.addParameter("storage", storage);
159+
request.setJsonEntity(Strings.format("""
160+
{
161+
"index": "%s",
162+
"renamed_index": "%s"
163+
}""", indexName, renamedIndexName));
164+
var responseBody = createFromResponse(client().performRequest(request));
165+
assertThat(responseBody.evaluate("snapshot.shards.total"), equalTo((int) responseBody.evaluate("snapshot.shards.successful")));
166+
assertThat(responseBody.evaluate("snapshot.shards.failed"), equalTo(0));
167+
}
168+
169+
protected static void restoreIndex(String repository, String snapshot, String indexName, String renamedIndexName) throws Exception {
170+
var request = new Request("POST", "/_snapshot/" + repository + "/" + snapshot + "/_restore");
171+
request.addParameter("wait_for_completion", "true");
172+
request.setJsonEntity(org.elasticsearch.common.Strings.format("""
173+
{
174+
"indices": "%s",
175+
"include_global_state": false,
176+
"rename_pattern": "(.+)",
177+
"rename_replacement": "%s",
178+
"include_aliases": false
179+
}""", indexName, renamedIndexName));
180+
var responseBody = createFromResponse(client().performRequest(request));
181+
assertThat(responseBody.evaluate("snapshot.shards.total"), equalTo((int) responseBody.evaluate("snapshot.shards.failed")));
182+
assertThat(responseBody.evaluate("snapshot.shards.successful"), equalTo(0));
183+
}
184+
130185
/**
131186
* Execute the test suite with the parameters provided by the {@link #parameters()} in version order.
132187
*/

qa/lucene-index-compatibility/src/javaRestTest/java/org/elasticsearch/lucene/LuceneCompatibilityIT.java

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,18 @@
1010
package org.elasticsearch.lucene;
1111

1212
import org.elasticsearch.client.Request;
13+
import org.elasticsearch.client.ResponseException;
1314
import org.elasticsearch.cluster.metadata.IndexMetadata;
1415
import org.elasticsearch.common.Strings;
1516
import org.elasticsearch.common.settings.Settings;
1617
import org.elasticsearch.index.IndexSettings;
1718
import org.elasticsearch.repositories.fs.FsRepository;
19+
import org.elasticsearch.rest.RestStatus;
1820
import org.elasticsearch.test.cluster.util.Version;
1921

20-
import java.util.stream.IntStream;
21-
22-
import static org.elasticsearch.test.rest.ObjectPath.createFromResponse;
22+
import static org.hamcrest.CoreMatchers.containsString;
2323
import static org.hamcrest.Matchers.allOf;
2424
import static org.hamcrest.Matchers.equalTo;
25-
import static org.hamcrest.Matchers.is;
26-
import static org.hamcrest.Matchers.notNullValue;
2725

2826
public class LuceneCompatibilityIT extends AbstractLuceneIndexCompatibilityTestCase {
2927

@@ -35,22 +33,19 @@ public LuceneCompatibilityIT(Version version) {
3533
super(version);
3634
}
3735

36+
/**
37+
* Creates an index and a snapshot on N-2, then restores the snapshot on N.
38+
*/
3839
public void testRestoreIndex() throws Exception {
3940
final String repository = suffix("repository");
4041
final String snapshot = suffix("snapshot");
4142
final String index = suffix("index");
4243
final int numDocs = 1234;
4344

44-
logger.debug("--> registering repository [{}]", repository);
45-
registerRepository(
46-
client(),
47-
repository,
48-
FsRepository.TYPE,
49-
true,
50-
Settings.builder().put("location", REPOSITORY_PATH.getRoot().getPath()).build()
51-
);
52-
5345
if (VERSION_MINUS_2.equals(clusterVersion())) {
46+
logger.debug("--> registering repository [{}]", repository);
47+
registerRepository(client(), repository, FsRepository.TYPE, true, repositorySettings());
48+
5449
logger.debug("--> creating index [{}]", index);
5550
createIndex(
5651
client(),
@@ -63,17 +58,7 @@ public void testRestoreIndex() throws Exception {
6358
);
6459

6560
logger.debug("--> indexing [{}] docs in [{}]", numDocs, index);
66-
final var bulks = new StringBuilder();
67-
IntStream.range(0, numDocs).forEach(n -> bulks.append(Strings.format("""
68-
{"index":{"_id":"%s","_index":"%s"}}
69-
{"test":"test"}
70-
""", n, index)));
71-
72-
var bulkRequest = new Request("POST", "/_bulk");
73-
bulkRequest.setJsonEntity(bulks.toString());
74-
var bulkResponse = client().performRequest(bulkRequest);
75-
assertOK(bulkResponse);
76-
assertThat(entityAsMap(bulkResponse).get("errors"), allOf(notNullValue(), is(false)));
61+
indexDocs(index, numDocs);
7762

7863
logger.debug("--> creating snapshot [{}]", snapshot);
7964
createSnapshot(client(), repository, snapshot, true);
@@ -83,7 +68,7 @@ public void testRestoreIndex() throws Exception {
8368
if (VERSION_MINUS_1.equals(clusterVersion())) {
8469
ensureGreen(index);
8570

86-
assertThat(indexLuceneVersion(index), equalTo(VERSION_MINUS_2));
71+
assertThat(indexVersion(index), equalTo(VERSION_MINUS_2));
8772
assertDocCount(client(), index, numDocs);
8873

8974
logger.debug("--> deleting index [{}]", index);
@@ -93,9 +78,9 @@ public void testRestoreIndex() throws Exception {
9378

9479
if (VERSION_CURRENT.equals(clusterVersion())) {
9580
var restoredIndex = suffix("index-restored");
96-
logger.debug("--> restoring index [{}] as archive [{}]", index, restoredIndex);
81+
logger.debug("--> restoring index [{}] as [{}]", index, restoredIndex);
9782

98-
// Restoring the archive will fail as Elasticsearch does not support reading N-2 yet
83+
// Restoring the index will fail as Elasticsearch does not support reading N-2 yet
9984
var request = new Request("POST", "/_snapshot/" + repository + "/" + snapshot + "/_restore");
10085
request.addParameter("wait_for_completion", "true");
10186
request.setJsonEntity(Strings.format("""
@@ -106,9 +91,20 @@ public void testRestoreIndex() throws Exception {
10691
"rename_replacement": "%s",
10792
"include_aliases": false
10893
}""", index, restoredIndex));
109-
var responseBody = createFromResponse(client().performRequest(request));
110-
assertThat(responseBody.evaluate("snapshot.shards.total"), equalTo((int) responseBody.evaluate("snapshot.shards.failed")));
111-
assertThat(responseBody.evaluate("snapshot.shards.successful"), equalTo(0));
94+
95+
var responseException = expectThrows(ResponseException.class, () -> client().performRequest(request));
96+
assertEquals(RestStatus.INTERNAL_SERVER_ERROR.getStatus(), responseException.getResponse().getStatusLine().getStatusCode());
97+
assertThat(
98+
responseException.getMessage(),
99+
allOf(
100+
containsString("cannot restore index [[" + index),
101+
containsString("because it cannot be upgraded"),
102+
containsString("has current compatibility version [" + VERSION_MINUS_2 + '-' + VERSION_MINUS_1.getMajor() + ".0.0]"),
103+
containsString("but the minimum compatible version is [" + VERSION_MINUS_1.getMajor() + ".0.0]."),
104+
containsString("It should be re-indexed in Elasticsearch " + VERSION_MINUS_1.getMajor() + ".x"),
105+
containsString("before upgrading to " + VERSION_CURRENT)
106+
)
107+
);
112108
}
113109
}
114110
}

qa/lucene-index-compatibility/src/javaRestTest/java/org/elasticsearch/lucene/SearchableSnapshotCompatibilityIT.java

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,13 @@
99

1010
package org.elasticsearch.lucene;
1111

12-
import org.elasticsearch.client.Request;
1312
import org.elasticsearch.cluster.metadata.IndexMetadata;
14-
import org.elasticsearch.common.Strings;
1513
import org.elasticsearch.common.settings.Settings;
1614
import org.elasticsearch.index.IndexSettings;
1715
import org.elasticsearch.repositories.fs.FsRepository;
1816
import org.elasticsearch.test.cluster.util.Version;
1917

20-
import java.util.stream.IntStream;
21-
22-
import static org.elasticsearch.test.rest.ObjectPath.createFromResponse;
23-
import static org.hamcrest.Matchers.allOf;
2418
import static org.hamcrest.Matchers.equalTo;
25-
import static org.hamcrest.Matchers.is;
26-
import static org.hamcrest.Matchers.notNullValue;
2719

2820
public class SearchableSnapshotCompatibilityIT extends AbstractLuceneIndexCompatibilityTestCase {
2921

@@ -37,24 +29,19 @@ public SearchableSnapshotCompatibilityIT(Version version) {
3729
super(version);
3830
}
3931

40-
// TODO Add a test to mount the N-2 index on N-1 and then search it on N
41-
32+
/**
33+
* Creates an index and a snapshot on N-2, then mounts the snapshot on N.
34+
*/
4235
public void testSearchableSnapshot() throws Exception {
4336
final String repository = suffix("repository");
4437
final String snapshot = suffix("snapshot");
4538
final String index = suffix("index");
4639
final int numDocs = 1234;
4740

48-
logger.debug("--> registering repository [{}]", repository);
49-
registerRepository(
50-
client(),
51-
repository,
52-
FsRepository.TYPE,
53-
true,
54-
Settings.builder().put("location", REPOSITORY_PATH.getRoot().getPath()).build()
55-
);
56-
5741
if (VERSION_MINUS_2.equals(clusterVersion())) {
42+
logger.debug("--> registering repository [{}]", repository);
43+
registerRepository(client(), repository, FsRepository.TYPE, true, repositorySettings());
44+
5845
logger.debug("--> creating index [{}]", index);
5946
createIndex(
6047
client(),
@@ -67,17 +54,7 @@ public void testSearchableSnapshot() throws Exception {
6754
);
6855

6956
logger.debug("--> indexing [{}] docs in [{}]", numDocs, index);
70-
final var bulks = new StringBuilder();
71-
IntStream.range(0, numDocs).forEach(n -> bulks.append(Strings.format("""
72-
{"index":{"_id":"%s","_index":"%s"}}
73-
{"test":"test"}
74-
""", n, index)));
75-
76-
var bulkRequest = new Request("POST", "/_bulk");
77-
bulkRequest.setJsonEntity(bulks.toString());
78-
var bulkResponse = client().performRequest(bulkRequest);
79-
assertOK(bulkResponse);
80-
assertThat(entityAsMap(bulkResponse).get("errors"), allOf(notNullValue(), is(false)));
57+
indexDocs(index, numDocs);
8158

8259
logger.debug("--> creating snapshot [{}]", snapshot);
8360
createSnapshot(client(), repository, snapshot, true);
@@ -87,7 +64,7 @@ public void testSearchableSnapshot() throws Exception {
8764
if (VERSION_MINUS_1.equals(clusterVersion())) {
8865
ensureGreen(index);
8966

90-
assertThat(indexLuceneVersion(index), equalTo(VERSION_MINUS_2));
67+
assertThat(indexVersion(index), equalTo(VERSION_MINUS_2));
9168
assertDocCount(client(), index, numDocs);
9269

9370
logger.debug("--> deleting index [{}]", index);
@@ -98,20 +75,75 @@ public void testSearchableSnapshot() throws Exception {
9875
if (VERSION_CURRENT.equals(clusterVersion())) {
9976
var mountedIndex = suffix("index-mounted");
10077
logger.debug("--> mounting index [{}] as [{}]", index, mountedIndex);
78+
mountIndex(repository, snapshot, index, randomBoolean(), mountedIndex);
79+
80+
ensureGreen(mountedIndex);
81+
82+
assertThat(indexVersion(mountedIndex), equalTo(VERSION_MINUS_2));
83+
assertDocCount(client(), mountedIndex, numDocs);
84+
85+
logger.debug("--> adding replica to test peer-recovery");
86+
updateIndexSettings(mountedIndex, Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1));
87+
ensureGreen(mountedIndex);
88+
}
89+
}
90+
91+
/**
92+
* Creates an index and a snapshot on N-2, mounts the snapshot on N -1 and then upgrades to N.
93+
*/
94+
public void testSearchableSnapshotUpgrade() throws Exception {
95+
final String mountedIndex = suffix("index-mounted");
96+
final String repository = suffix("repository");
97+
final String snapshot = suffix("snapshot");
98+
final String index = suffix("index");
99+
final int numDocs = 4321;
100+
101+
if (VERSION_MINUS_2.equals(clusterVersion())) {
102+
logger.debug("--> registering repository [{}]", repository);
103+
registerRepository(client(), repository, FsRepository.TYPE, true, repositorySettings());
104+
105+
logger.debug("--> creating index [{}]", index);
106+
createIndex(
107+
client(),
108+
index,
109+
Settings.builder()
110+
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
111+
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
112+
.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), true)
113+
.build()
114+
);
115+
116+
logger.debug("--> indexing [{}] docs in [{}]", numDocs, index);
117+
indexDocs(index, numDocs);
118+
119+
logger.debug("--> creating snapshot [{}]", snapshot);
120+
createSnapshot(client(), repository, snapshot, true);
121+
122+
logger.debug("--> deleting index [{}]", index);
123+
deleteIndex(index);
124+
return;
125+
}
126+
127+
if (VERSION_MINUS_1.equals(clusterVersion())) {
128+
logger.debug("--> mounting index [{}] as [{}]", index, mountedIndex);
129+
mountIndex(repository, snapshot, index, randomBoolean(), mountedIndex);
130+
131+
ensureGreen(mountedIndex);
132+
133+
assertThat(indexVersion(mountedIndex), equalTo(VERSION_MINUS_2));
134+
assertDocCount(client(), mountedIndex, numDocs);
135+
return;
136+
}
137+
138+
if (VERSION_CURRENT.equals(clusterVersion())) {
139+
ensureGreen(mountedIndex);
140+
141+
assertThat(indexVersion(mountedIndex), equalTo(VERSION_MINUS_2));
142+
assertDocCount(client(), mountedIndex, numDocs);
101143

102-
// Mounting the index will fail as Elasticsearch does not support reading N-2 yet
103-
var request = new Request("POST", "/_snapshot/" + repository + "/" + snapshot + "/_mount");
104-
request.addParameter("wait_for_completion", "true");
105-
var storage = randomBoolean() ? "shared_cache" : "full_copy";
106-
request.addParameter("storage", storage);
107-
request.setJsonEntity(Strings.format("""
108-
{
109-
"index": "%s",
110-
"renamed_index": "%s"
111-
}""", index, mountedIndex));
112-
var responseBody = createFromResponse(client().performRequest(request));
113-
assertThat(responseBody.evaluate("snapshot.shards.total"), equalTo((int) responseBody.evaluate("snapshot.shards.failed")));
114-
assertThat(responseBody.evaluate("snapshot.shards.successful"), equalTo(0));
144+
logger.debug("--> adding replica to test peer-recovery");
145+
updateIndexSettings(mountedIndex, Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1));
146+
ensureGreen(mountedIndex);
115147
}
116148
}
117149
}

0 commit comments

Comments
 (0)