Skip to content

Commit a716ede

Browse files
authored
[8.x] Allow archive and searchable snapshots indices in N-2 version (#118923)
This is the backport of #118941 for 8.18. This change relaxes the index compatibility version checks to allow archive and searchable snapshot indices in version N-2 to exist on a 9.x cluster. It uses the min. read-only index compatible version added in #118884 to accept join requests from 9.x nodes that can read indices created in version 7.x (N-2) as long as they have a write block and are either archive or searchable snapshot indices. Relates ES-10274
1 parent 2ae5911 commit a716ede

File tree

11 files changed

+232
-46
lines changed

11 files changed

+232
-46
lines changed

server/src/main/java/org/elasticsearch/cluster/coordination/NodeJoinExecutor.java

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import java.util.function.Function;
5050
import java.util.stream.Collectors;
5151

52+
import static org.elasticsearch.cluster.metadata.IndexMetadataVerifier.isReadOnlySupportedVersion;
5253
import static org.elasticsearch.gateway.GatewayService.STATE_NOT_RECOVERED_BLOCK;
5354

5455
public class NodeJoinExecutor implements ClusterStateTaskExecutor<JoinTask> {
@@ -179,8 +180,12 @@ public ClusterState execute(BatchExecutionContext<JoinTask> batchExecutionContex
179180
Set<String> newNodeEffectiveFeatures = enforceNodeFeatureBarrier(node, effectiveClusterFeatures, features);
180181
// we do this validation quite late to prevent race conditions between nodes joining and importing dangling indices
181182
// we have to reject nodes that don't support all indices we have in this cluster
182-
ensureIndexCompatibility(node.getMinIndexVersion(), node.getMaxIndexVersion(), initialState.getMetadata());
183-
183+
ensureIndexCompatibility(
184+
node.getMinIndexVersion(),
185+
node.getMinReadOnlyIndexVersion(),
186+
node.getMaxIndexVersion(),
187+
initialState.getMetadata()
188+
);
184189
nodesBuilder.add(node);
185190
compatibilityVersionsMap.put(node.getId(), compatibilityVersions);
186191
// store the actual node features here, not including assumed features, as this is persisted in cluster state
@@ -394,9 +399,15 @@ private Set<String> calculateEffectiveClusterFeatures(DiscoveryNodes nodes, Map<
394399
* will not be created with a newer version of elasticsearch as well as that all indices are newer or equal to the minimum index
395400
* compatibility version.
396401
* @see IndexVersions#MINIMUM_COMPATIBLE
402+
* @see IndexVersions#MINIMUM_READONLY_COMPATIBLE
397403
* @throws IllegalStateException if any index is incompatible with the given version
398404
*/
399-
public static void ensureIndexCompatibility(IndexVersion minSupportedVersion, IndexVersion maxSupportedVersion, Metadata metadata) {
405+
public static void ensureIndexCompatibility(
406+
IndexVersion minSupportedVersion,
407+
IndexVersion minReadOnlySupportedVersion,
408+
IndexVersion maxSupportedVersion,
409+
Metadata metadata
410+
) {
400411
// we ensure that all indices in the cluster we join are compatible with us no matter if they are
401412
// closed or not we can't read mappings of these indices so we need to reject the join...
402413
for (IndexMetadata idxMetadata : metadata) {
@@ -411,14 +422,17 @@ public static void ensureIndexCompatibility(IndexVersion minSupportedVersion, In
411422
);
412423
}
413424
if (idxMetadata.getCompatibilityVersion().before(minSupportedVersion)) {
414-
throw new IllegalStateException(
415-
"index "
416-
+ idxMetadata.getIndex()
417-
+ " version not supported: "
418-
+ idxMetadata.getCompatibilityVersion().toReleaseVersion()
419-
+ " minimum compatible index version is: "
420-
+ minSupportedVersion.toReleaseVersion()
421-
);
425+
boolean isReadOnlySupported = isReadOnlySupportedVersion(idxMetadata, minSupportedVersion, minReadOnlySupportedVersion);
426+
if (isReadOnlySupported == false) {
427+
throw new IllegalStateException(
428+
"index "
429+
+ idxMetadata.getIndex()
430+
+ " version not supported: "
431+
+ idxMetadata.getCompatibilityVersion().toReleaseVersion()
432+
+ " minimum compatible index version is: "
433+
+ minSupportedVersion.toReleaseVersion()
434+
);
435+
}
422436
}
423437
}
424438
}
@@ -542,7 +556,12 @@ public static Collection<BiConsumer<DiscoveryNode, ClusterState>> addBuiltInJoin
542556
final Collection<BiConsumer<DiscoveryNode, ClusterState>> validators = new ArrayList<>();
543557
validators.add((node, state) -> {
544558
ensureNodesCompatibility(node.getVersion(), state.getNodes());
545-
ensureIndexCompatibility(node.getMinIndexVersion(), node.getMaxIndexVersion(), state.getMetadata());
559+
ensureIndexCompatibility(
560+
node.getMinIndexVersion(),
561+
node.getMinReadOnlyIndexVersion(),
562+
node.getMaxIndexVersion(),
563+
state.getMetadata()
564+
);
546565
});
547566
validators.addAll(onJoinValidators);
548567
return Collections.unmodifiableCollection(validators);

server/src/main/java/org/elasticsearch/cluster/metadata/IndexMetadataVerifier.java

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,12 @@ public IndexMetadataVerifier(
8888
* If the index does not need upgrade it returns the index metadata unchanged, otherwise it returns a modified index metadata. If index
8989
* cannot be updated the method throws an exception.
9090
*/
91-
public IndexMetadata verifyIndexMetadata(IndexMetadata indexMetadata, IndexVersion minimumIndexCompatibilityVersion) {
92-
checkSupportedVersion(indexMetadata, minimumIndexCompatibilityVersion);
91+
public IndexMetadata verifyIndexMetadata(
92+
IndexMetadata indexMetadata,
93+
IndexVersion minimumIndexCompatibilityVersion,
94+
IndexVersion minimumReadOnlyIndexCompatibilityVersion
95+
) {
96+
checkSupportedVersion(indexMetadata, minimumIndexCompatibilityVersion, minimumReadOnlyIndexCompatibilityVersion);
9397

9498
// First convert any shared_cache searchable snapshot indices to only use _tier_preference: data_frozen
9599
IndexMetadata newMetadata = convertSharedCacheTierPreference(indexMetadata);
@@ -105,26 +109,81 @@ public IndexMetadata verifyIndexMetadata(IndexMetadata indexMetadata, IndexVersi
105109
}
106110

107111
/**
108-
* Check that the index version is compatible. Elasticsearch does not support indices created before the
109-
* previous major version.
112+
* Check that the index version is compatible. Elasticsearch supports reading and writing indices created in the current version ("N")
113+
+ as well as the previous major version ("N-1"). Elasticsearch only supports reading indices created down to the penultimate version
114+
+ ("N-2") and does not support reading nor writing any version below that.
110115
*/
111-
private static void checkSupportedVersion(IndexMetadata indexMetadata, IndexVersion minimumIndexCompatibilityVersion) {
112-
boolean isSupportedVersion = indexMetadata.getCompatibilityVersion().onOrAfter(minimumIndexCompatibilityVersion);
113-
if (isSupportedVersion == false) {
114-
throw new IllegalStateException(
115-
"The index "
116-
+ indexMetadata.getIndex()
117-
+ " has current compatibility version ["
118-
+ indexMetadata.getCompatibilityVersion().toReleaseVersion()
119-
+ "] but the minimum compatible version is ["
120-
+ minimumIndexCompatibilityVersion.toReleaseVersion()
121-
+ "]. It should be re-indexed in Elasticsearch "
122-
+ (Version.CURRENT.major - 1)
123-
+ ".x before upgrading to "
124-
+ Build.current().version()
125-
+ "."
126-
);
116+
private static void checkSupportedVersion(
117+
IndexMetadata indexMetadata,
118+
IndexVersion minimumIndexCompatibilityVersion,
119+
IndexVersion minimumReadOnlyIndexCompatibilityVersion
120+
) {
121+
if (isFullySupportedVersion(indexMetadata, minimumIndexCompatibilityVersion)) {
122+
return;
123+
}
124+
if (isReadOnlySupportedVersion(indexMetadata, minimumIndexCompatibilityVersion, minimumReadOnlyIndexCompatibilityVersion)) {
125+
return;
126+
}
127+
throw new IllegalStateException(
128+
"The index "
129+
+ indexMetadata.getIndex()
130+
+ " has current compatibility version ["
131+
+ indexMetadata.getCompatibilityVersion().toReleaseVersion()
132+
+ "] but the minimum compatible version is ["
133+
+ minimumIndexCompatibilityVersion.toReleaseVersion()
134+
+ "]. It should be re-indexed in Elasticsearch "
135+
+ (Version.CURRENT.major - 1)
136+
+ ".x before upgrading to "
137+
+ Build.current().version()
138+
+ "."
139+
);
140+
}
141+
142+
private static boolean isFullySupportedVersion(IndexMetadata indexMetadata, IndexVersion minimumIndexCompatibilityVersion) {
143+
return indexMetadata.getCompatibilityVersion().onOrAfter(minimumIndexCompatibilityVersion);
144+
}
145+
146+
/**
147+
* Returns {@code true} if the index version is compatible in read-only mode. As of today, only searchable snapshots and archive indices
148+
* in version N-2 with a write block are read-only compatible. This method throws an {@link IllegalStateException} if the index is
149+
* either a searchable snapshot or an archive index with a read-only compatible version but is missing the write block.
150+
*
151+
* @param indexMetadata the index metadata
152+
* @param minimumIndexCompatibilityVersion the min. index compatible version for reading and writing indices (used in assertion)
153+
* @param minReadOnlyIndexCompatibilityVersion the min. index compatible version for only reading indices
154+
*
155+
* @return {@code true} if the index version is compatible in read-only mode, {@code false} otherwise.
156+
* @throws IllegalStateException if the index is read-only compatible but has no write block in place.
157+
*/
158+
public static boolean isReadOnlySupportedVersion(
159+
IndexMetadata indexMetadata,
160+
IndexVersion minimumIndexCompatibilityVersion,
161+
IndexVersion minReadOnlyIndexCompatibilityVersion
162+
) {
163+
boolean isReadOnlySupportedVersion = indexMetadata.getCompatibilityVersion().onOrAfter(minReadOnlyIndexCompatibilityVersion);
164+
assert isFullySupportedVersion(indexMetadata, minimumIndexCompatibilityVersion) == false;
165+
166+
if (isReadOnlySupportedVersion
167+
&& (indexMetadata.isSearchableSnapshot() || indexMetadata.getCreationVersion().isLegacyIndexVersion())) {
168+
boolean isReadOnly = IndexMetadata.INDEX_BLOCKS_WRITE_SETTING.get(indexMetadata.getSettings());
169+
if (isReadOnly == false) {
170+
throw new IllegalStateException(
171+
"The index "
172+
+ indexMetadata.getIndex()
173+
+ " created in version ["
174+
+ indexMetadata.getCreationVersion()
175+
+ "] with current compatibility version ["
176+
+ indexMetadata.getCompatibilityVersion().toReleaseVersion()
177+
+ "] must be marked as read-only using the setting ["
178+
+ IndexMetadata.SETTING_BLOCKS_WRITE
179+
+ "] set to [true] before upgrading to "
180+
+ Build.current().version()
181+
+ '.'
182+
);
183+
}
184+
return true;
127185
}
186+
return false;
128187
}
129188

130189
/**

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexStateService.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,7 @@ private ClusterState openIndices(final Index[] indices, final ClusterState curre
11201120
final Metadata.Builder metadata = Metadata.builder(currentState.metadata());
11211121
final ClusterBlocks.Builder blocks = ClusterBlocks.builder(currentState.blocks());
11221122
final IndexVersion minIndexCompatibilityVersion = currentState.getNodes().getMinSupportedIndexVersion();
1123+
final IndexVersion minReadOnlyIndexCompatibilityVersion = currentState.getNodes().getMinReadOnlySupportedIndexVersion();
11231124

11241125
for (IndexMetadata indexMetadata : indicesToOpen) {
11251126
final Index index = indexMetadata.getIndex();
@@ -1137,7 +1138,11 @@ private ClusterState openIndices(final Index[] indices, final ClusterState curre
11371138

11381139
// The index might be closed because we couldn't import it due to an old incompatible
11391140
// version, so we need to verify its compatibility.
1140-
newIndexMetadata = indexMetadataVerifier.verifyIndexMetadata(newIndexMetadata, minIndexCompatibilityVersion);
1141+
newIndexMetadata = indexMetadataVerifier.verifyIndexMetadata(
1142+
newIndexMetadata,
1143+
minIndexCompatibilityVersion,
1144+
minReadOnlyIndexCompatibilityVersion
1145+
);
11411146
try {
11421147
indicesService.verifyIndexMetadata(newIndexMetadata, newIndexMetadata);
11431148
} catch (Exception e) {

server/src/main/java/org/elasticsearch/gateway/GatewayMetaState.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,11 @@ static Metadata upgradeMetadata(Metadata metadata, IndexMetadataVerifier indexMe
308308
boolean changed = false;
309309
final Metadata.Builder upgradedMetadata = Metadata.builder(metadata);
310310
for (IndexMetadata indexMetadata : metadata) {
311-
IndexMetadata newMetadata = indexMetadataVerifier.verifyIndexMetadata(indexMetadata, IndexVersions.MINIMUM_COMPATIBLE);
311+
IndexMetadata newMetadata = indexMetadataVerifier.verifyIndexMetadata(
312+
indexMetadata,
313+
IndexVersions.MINIMUM_COMPATIBLE,
314+
IndexVersions.MINIMUM_READONLY_COMPATIBLE
315+
);
312316
changed |= indexMetadata != newMetadata;
313317
upgradedMetadata.put(newMetadata, false);
314318
}

server/src/main/java/org/elasticsearch/gateway/LocalAllocateDangledIndices.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public ClusterState execute(ClusterState currentState) {
125125
currentState.routingTable()
126126
);
127127
IndexVersion minIndexCompatibilityVersion = currentState.nodes().getMinSupportedIndexVersion();
128+
IndexVersion minReadOnlyIndexCompatibilityVersion = currentState.nodes().getMinReadOnlySupportedIndexVersion();
128129
IndexVersion maxIndexCompatibilityVersion = currentState.nodes().getMaxDataNodeCompatibleIndexVersion();
129130
boolean importNeeded = false;
130131
StringBuilder sb = new StringBuilder();
@@ -176,7 +177,11 @@ public ClusterState execute(ClusterState currentState) {
176177
try {
177178
// The dangled index might be from an older version, we need to make sure it's compatible
178179
// with the current version.
179-
newIndexMetadata = indexMetadataVerifier.verifyIndexMetadata(indexMetadata, minIndexCompatibilityVersion);
180+
newIndexMetadata = indexMetadataVerifier.verifyIndexMetadata(
181+
indexMetadata,
182+
minIndexCompatibilityVersion,
183+
minReadOnlyIndexCompatibilityVersion
184+
);
180185
newIndexMetadata = IndexMetadata.builder(newIndexMetadata)
181186
.settings(
182187
Settings.builder()

server/src/main/java/org/elasticsearch/index/IndexVersion.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public static IndexVersion current() {
124124
}
125125

126126
public boolean isLegacyIndexVersion() {
127-
return before(IndexVersions.MINIMUM_COMPATIBLE);
127+
return before(IndexVersions.MINIMUM_READONLY_COMPATIBLE);
128128
}
129129

130130
public static IndexVersion getMinimumCompatibleIndexVersion(int versionId) {

server/src/main/java/org/elasticsearch/snapshots/RestoreService.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,7 @@ public ClusterState execute(ClusterState currentState) {
13151315
final Map<ShardId, ShardRestoreStatus> shards = new HashMap<>();
13161316

13171317
final IndexVersion minIndexCompatibilityVersion = currentState.getNodes().getMinSupportedIndexVersion();
1318+
final IndexVersion minReadOnlyIndexCompatibilityVersion = currentState.getNodes().getMinReadOnlySupportedIndexVersion();
13181319
final String localNodeId = clusterService.state().nodes().getLocalNodeId();
13191320
for (Map.Entry<String, IndexId> indexEntry : indicesToRestore.entrySet()) {
13201321
final IndexId index = indexEntry.getValue();
@@ -1327,12 +1328,16 @@ public ClusterState execute(ClusterState currentState) {
13271328
request.indexSettings(),
13281329
request.ignoreIndexSettings()
13291330
);
1330-
if (snapshotIndexMetadata.getCompatibilityVersion().before(minIndexCompatibilityVersion)) {
1331+
if (snapshotIndexMetadata.getCompatibilityVersion().isLegacyIndexVersion()) {
13311332
// adapt index metadata so that it can be understood by current version
13321333
snapshotIndexMetadata = convertLegacyIndex(snapshotIndexMetadata, currentState, indicesService);
13331334
}
13341335
try {
1335-
snapshotIndexMetadata = indexMetadataVerifier.verifyIndexMetadata(snapshotIndexMetadata, minIndexCompatibilityVersion);
1336+
snapshotIndexMetadata = indexMetadataVerifier.verifyIndexMetadata(
1337+
snapshotIndexMetadata,
1338+
minIndexCompatibilityVersion,
1339+
minReadOnlyIndexCompatibilityVersion
1340+
);
13361341
} catch (Exception ex) {
13371342
throw new SnapshotRestoreException(snapshot, "cannot restore index [" + index + "] because it cannot be upgraded", ex);
13381343
}

0 commit comments

Comments
 (0)