Skip to content

Commit a678f85

Browse files
authored
Update upgrade assistant API to account for percolator fields (elastic#137548)
This change modifies the deprecation API to check for any indices prior to 8.19 that have percolator fields in the mappings. Any that do are then returned as required to be reindexed or deleted. Read-only is excluded as this would not fix the underlying transport version issue. Note this change only concerns user created indices. System indices will need to be addressed separately as part of the migration API, but should be able to share code. ES-13234
1 parent 9056012 commit a678f85

File tree

8 files changed

+324
-151
lines changed

8 files changed

+324
-151
lines changed

docs/reference/rest-api/common-options.asciidoc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ Returns:
243243
"index.creation_date": "1474389951325",
244244
"index.uuid": "n6gzFZTgS664GUfx0Xrpjw",
245245
"index.version.created": ...,
246+
"index.transport_version.created": ...,
246247
"index.routing.allocation.include._tier_preference" : "data_content",
247248
"index.provided_name" : "my-index-000001"
248249
}
@@ -252,6 +253,7 @@ Returns:
252253
// TESTRESPONSE[s/1474389951325/$body.my-index-000001.settings.index\\\\.creation_date/]
253254
// TESTRESPONSE[s/n6gzFZTgS664GUfx0Xrpjw/$body.my-index-000001.settings.index\\\\.uuid/]
254255
// TESTRESPONSE[s/"index.version.created": \.\.\./"index.version.created": $body.my-index-000001.settings.index\\\\.version\\\\.created/]
256+
// TESTRESPONSE[s/"index.transport_version.created": \.\.\./"index.transport_version.created": $body.my-index-000001.settings.index\\\\.transport_version\\\\.created/]
255257

256258
When the `flat_settings` flag is `false`, settings are returned in a more
257259
human readable structured format:
@@ -275,7 +277,10 @@ Returns:
275277
"creation_date": "1474389951325",
276278
"uuid": "n6gzFZTgS664GUfx0Xrpjw",
277279
"version": {
278-
"created": ...
280+
"created": .v.
281+
},
282+
"transport_version": {
283+
"created": .tv.
279284
},
280285
"routing": {
281286
"allocation": {
@@ -292,7 +297,8 @@ Returns:
292297
--------------------------------------------------
293298
// TESTRESPONSE[s/1474389951325/$body.my-index-000001.settings.index.creation_date/]
294299
// TESTRESPONSE[s/n6gzFZTgS664GUfx0Xrpjw/$body.my-index-000001.settings.index.uuid/]
295-
// TESTRESPONSE[s/"created": \.\.\./"created": $body.my-index-000001.settings.index.version.created/]
300+
// TESTRESPONSE[s/"created": \.v\./"created": $body.my-index-000001.settings.index.version.created/]
301+
// TESTRESPONSE[s/"created": \.tv\./"created": $body.my-index-000001.settings.index.transport_version.created/]
296302

297303
By default `flat_settings` is set to `false`.
298304

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,16 @@ public static APIBlock readFrom(StreamInput input) throws IOException {
369369
Property.PrivateIndex
370370
);
371371

372+
public static final String SETTING_TRANSPORT_VERSION_CREATED = "index.transport_version.created";
373+
374+
public static final Setting<TransportVersion> SETTING_INDEX_TRANSPORT_VERSION_CREATED = Setting.versionIdSetting(
375+
SETTING_TRANSPORT_VERSION_CREATED,
376+
TransportVersion.fromId(0),
377+
TransportVersion::fromId,
378+
Property.IndexScope,
379+
Property.PrivateIndex
380+
);
381+
372382
public static final String SETTING_VERSION_CREATED_STRING = "index.version.created_string";
373383
public static final String SETTING_CREATION_DATE = "index.creation_date";
374384

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,9 @@ static Settings aggregateIndexSettings(
11271127
IndexVersion createdVersion = IndexVersion.min(IndexVersion.current(), nodes.getMaxDataNodeCompatibleIndexVersion());
11281128
indexSettingsBuilder.put(IndexMetadata.SETTING_VERSION_CREATED, createdVersion);
11291129
}
1130+
if (indexSettingsBuilder.get(IndexMetadata.SETTING_TRANSPORT_VERSION_CREATED) == null) {
1131+
indexSettingsBuilder.put(IndexMetadata.SETTING_TRANSPORT_VERSION_CREATED, TransportVersion.current());
1132+
}
11301133
if (INDEX_NUMBER_OF_SHARDS_SETTING.exists(indexSettingsBuilder) == false) {
11311134
indexSettingsBuilder.put(SETTING_NUMBER_OF_SHARDS, INDEX_NUMBER_OF_SHARDS_SETTING.get(settings));
11321135
}

server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
5858
MergeSchedulerConfig.MAX_THREAD_COUNT_SETTING,
5959
IndexMetadata.SETTING_INDEX_VERSION_CREATED,
6060
IndexMetadata.SETTING_INDEX_VERSION_COMPATIBILITY,
61+
IndexMetadata.SETTING_INDEX_TRANSPORT_VERSION_CREATED,
6162
IndexMetadata.INDEX_ROUTING_EXCLUDE_GROUP_SETTING,
6263
IndexMetadata.INDEX_ROUTING_INCLUDE_GROUP_SETTING,
6364
IndexMetadata.INDEX_ROUTING_REQUIRE_GROUP_SETTING,

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/deprecation/DeprecatedIndexPredicate.java

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77

88
package org.elasticsearch.xpack.core.deprecation;
99

10+
import org.elasticsearch.TransportVersion;
1011
import org.elasticsearch.cluster.metadata.IndexMetadata;
1112
import org.elasticsearch.cluster.metadata.Metadata;
1213
import org.elasticsearch.cluster.metadata.MetadataIndexStateService;
1314
import org.elasticsearch.index.Index;
1415
import org.elasticsearch.index.IndexVersion;
1516
import org.elasticsearch.index.IndexVersions;
1617

18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.function.BiFunction;
22+
import java.util.function.Function;
1723
import java.util.function.Predicate;
1824

1925
public class DeprecatedIndexPredicate {
@@ -49,7 +55,7 @@ public static Predicate<Index> getReindexRequiredPredicate(Metadata metadata, bo
4955
* if false, only those without a block are returned
5056
* @param includeSystem if true, all indices including system will be returned,
5157
* if false, only non-system indices are returned
52-
* @return a predicate that returns true for indices that need to be reindexed
58+
* @return returns true for indices that need to be reindexed
5359
*/
5460
public static boolean reindexRequired(IndexMetadata indexMetadata, boolean filterToBlockedStatus, boolean includeSystem) {
5561
return creationVersionBeforeMinimumWritableVersion(indexMetadata)
@@ -58,6 +64,59 @@ && isNotSearchableSnapshot(indexMetadata)
5864
&& matchBlockedStatus(indexMetadata, filterToBlockedStatus);
5965
}
6066

67+
/**
68+
* This method checks if this index is on the current transport version for the current minor release version.
69+
*
70+
* @param indexMetadata the index metadata
71+
* @param filterToBlockedStatus if true, only indices that are write blocked will be returned,
72+
* if false, only those without a block are returned
73+
* @param includeSystem if true, all indices including system will be returned,
74+
* if false, only non-system indices are returned
75+
* @return returns true for indices that need to be reindexed
76+
*/
77+
public static boolean reindexRequiredForTransportVersion(
78+
IndexMetadata indexMetadata,
79+
boolean filterToBlockedStatus,
80+
boolean includeSystem
81+
) {
82+
return transportVersionBeforeCurrentMinorRelease(indexMetadata)
83+
&& (includeSystem || isNotSystem(indexMetadata))
84+
&& isNotSearchableSnapshot(indexMetadata)
85+
&& matchBlockedStatus(indexMetadata, filterToBlockedStatus);
86+
}
87+
88+
/**
89+
* This method checks if this index requires reindexing based on if it has percolator fields older than the current transport version
90+
* for the current minor release.
91+
*
92+
* @param indexMetadata the index metadata
93+
* @param filterToBlockedStatus if true, only indices that are write blocked will be returned,
94+
* if false, only those without a block are returned
95+
* @param includeSystem if true, all indices including system will be returned,
96+
* if false, only non-system indices are returned
97+
* @return returns a message as a string for each incompatible percolator field found
98+
*/
99+
public static List<String> reindexRequiredForPecolatorFields(
100+
IndexMetadata indexMetadata,
101+
boolean filterToBlockedStatus,
102+
boolean includeSystem
103+
) {
104+
List<String> percolatorIncompatibleFieldMappings = new ArrayList<>();
105+
if (reindexRequiredForTransportVersion(indexMetadata, filterToBlockedStatus, includeSystem) && indexMetadata.mapping() != null) {
106+
percolatorIncompatibleFieldMappings.addAll(
107+
findInPropertiesRecursively(
108+
indexMetadata.mapping().type(),
109+
indexMetadata.mapping().sourceAsMap(),
110+
property -> "percolator".equals(property.get("type")),
111+
(type, entry) -> "Field [" + entry.getKey() + "] is of type [" + indexMetadata.mapping().type() + "]",
112+
"",
113+
""
114+
)
115+
);
116+
}
117+
return percolatorIncompatibleFieldMappings;
118+
}
119+
61120
private static boolean isNotSystem(IndexMetadata indexMetadata) {
62121
return indexMetadata.isSystem() == false;
63122
}
@@ -73,4 +132,76 @@ private static boolean creationVersionBeforeMinimumWritableVersion(IndexMetadata
73132
private static boolean matchBlockedStatus(IndexMetadata indexMetadata, boolean filterToBlockedStatus) {
74133
return MetadataIndexStateService.VERIFIED_READ_ONLY_SETTING.get(indexMetadata.getSettings()) == filterToBlockedStatus;
75134
}
135+
136+
private static boolean transportVersionBeforeCurrentMinorRelease(IndexMetadata indexMetadata) {
137+
// We divide each transport version by 1000 to get the base id.
138+
return IndexMetadata.SETTING_INDEX_TRANSPORT_VERSION_CREATED.get(indexMetadata.getSettings()).id() / 1000 < TransportVersion
139+
.current()
140+
.id() / 1000;
141+
}
142+
143+
/**
144+
* iterates through the "properties" field of mappings and returns any predicates that match in the
145+
* form of issue-strings.
146+
*
147+
* @param type the document type
148+
* @param parentMap the mapping to read properties from
149+
* @param predicate the predicate to check against for issues, issue is returned if predicate evaluates to true
150+
* @param fieldFormatter a function that takes a type and mapping field entry and returns a formatted field representation
151+
* @return a list of issues found in fields
152+
*/
153+
@SuppressWarnings("unchecked")
154+
public static List<String> findInPropertiesRecursively(
155+
String type,
156+
Map<String, Object> parentMap,
157+
Function<Map<?, ?>, Boolean> predicate,
158+
BiFunction<String, Map.Entry<?, ?>, String> fieldFormatter,
159+
String fieldBeginMarker,
160+
String fieldEndMarker
161+
) {
162+
List<String> issues = new ArrayList<>();
163+
Map<?, ?> properties = (Map<?, ?>) parentMap.get("properties");
164+
if (properties == null) {
165+
return issues;
166+
}
167+
for (Map.Entry<?, ?> entry : properties.entrySet()) {
168+
Map<String, Object> valueMap = (Map<String, Object>) entry.getValue();
169+
if (predicate.apply(valueMap)) {
170+
issues.add(fieldBeginMarker + fieldFormatter.apply(type, entry) + fieldEndMarker);
171+
}
172+
173+
Map<?, ?> values = (Map<?, ?>) valueMap.get("fields");
174+
if (values != null) {
175+
for (Map.Entry<?, ?> multifieldEntry : values.entrySet()) {
176+
Map<String, Object> multifieldValueMap = (Map<String, Object>) multifieldEntry.getValue();
177+
if (predicate.apply(multifieldValueMap)) {
178+
issues.add(
179+
fieldBeginMarker
180+
+ fieldFormatter.apply(type, entry)
181+
+ ", multifield: "
182+
+ multifieldEntry.getKey()
183+
+ fieldEndMarker
184+
);
185+
}
186+
if (multifieldValueMap.containsKey("properties")) {
187+
issues.addAll(
188+
findInPropertiesRecursively(
189+
type,
190+
multifieldValueMap,
191+
predicate,
192+
fieldFormatter,
193+
fieldBeginMarker,
194+
fieldEndMarker
195+
)
196+
);
197+
}
198+
}
199+
}
200+
if (valueMap.containsKey("properties")) {
201+
issues.addAll(findInPropertiesRecursively(type, valueMap, predicate, fieldFormatter, fieldBeginMarker, fieldEndMarker));
202+
}
203+
}
204+
205+
return issues;
206+
}
76207
}

x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/DataStreamDeprecationChecker.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,24 @@ public Map<String, List<DeprecationIssue>> check(ClusterState clusterState) {
8888

8989
static DeprecationIssue oldIndicesCheck(DataStream dataStream, ClusterState clusterState) {
9090
List<Index> backingIndices = dataStream.getIndices();
91-
91+
Set<String> percolatorIndicesNeedingUpgrade = getReindexRequiredIndicesWithPercolatorFields(backingIndices, clusterState, false);
92+
if (percolatorIndicesNeedingUpgrade.isEmpty() == false) {
93+
return new DeprecationIssue(
94+
DeprecationIssue.Level.CRITICAL,
95+
"Field mappings with incompatible percolator type",
96+
"https://www.elastic.co/guide/en/elasticsearch/reference/8.19/percolator.html#_reindexing_your_percolator_queries",
97+
"The data stream was created before 8.19 and contains mappings that must be reindexed due to containing percolator fields.",
98+
false,
99+
ofEntries(
100+
entry("reindex_required", true),
101+
entry("excluded_actions", List.of("readOnly")),
102+
entry("total_backing_indices", backingIndices.size()),
103+
entry("indices_requiring_upgrade_count", percolatorIndicesNeedingUpgrade.size()),
104+
entry("indices_requiring_upgrade", percolatorIndicesNeedingUpgrade)
105+
)
106+
);
107+
}
92108
Set<String> indicesNeedingUpgrade = getReindexRequiredIndices(backingIndices, clusterState, false);
93-
94109
if (indicesNeedingUpgrade.isEmpty() == false) {
95110
return new DeprecationIssue(
96111
DeprecationIssue.Level.CRITICAL,
@@ -112,6 +127,23 @@ static DeprecationIssue oldIndicesCheck(DataStream dataStream, ClusterState clus
112127

113128
static DeprecationIssue ignoredOldIndicesCheck(DataStream dataStream, ClusterState clusterState) {
114129
List<Index> backingIndices = dataStream.getIndices();
130+
Set<String> percolatorIgnoredIndices = getReindexRequiredIndicesWithPercolatorFields(backingIndices, clusterState, true);
131+
if (percolatorIgnoredIndices.isEmpty() == false) {
132+
return new DeprecationIssue(
133+
DeprecationIssue.Level.CRITICAL,
134+
"Field mappings with incompatible percolator type",
135+
"https://www.elastic.co/guide/en/elasticsearch/reference/8.19/percolator.html#_reindexing_your_percolator_queries",
136+
"The data stream was created before 8.19 and contains mappings that must be reindexed due to containing percolator fields.",
137+
false,
138+
ofEntries(
139+
entry("reindex_required", true),
140+
entry("excluded_actions", List.of("readOnly")),
141+
entry("total_backing_indices", backingIndices.size()),
142+
entry("ignored_indices_requiring_upgrade_count", percolatorIgnoredIndices.size()),
143+
entry("ignored_indices_requiring_upgrade", percolatorIgnoredIndices)
144+
)
145+
);
146+
}
115147
Set<String> ignoredIndices = getReindexRequiredIndices(backingIndices, clusterState, true);
116148
if (ignoredIndices.isEmpty() == false) {
117149
return new DeprecationIssue(
@@ -144,6 +176,23 @@ private static Set<String> getReindexRequiredIndices(
144176
.collect(Collectors.toUnmodifiableSet());
145177
}
146178

179+
private static Set<String> getReindexRequiredIndicesWithPercolatorFields(
180+
List<Index> backingIndices,
181+
ClusterState clusterState,
182+
boolean filterToBlockedStatus
183+
) {
184+
return backingIndices.stream()
185+
.filter(
186+
index -> DeprecatedIndexPredicate.reindexRequiredForPecolatorFields(
187+
clusterState.metadata().index(index),
188+
filterToBlockedStatus,
189+
false
190+
).isEmpty() == false
191+
)
192+
.map(Index::getName)
193+
.collect(Collectors.toUnmodifiableSet());
194+
}
195+
147196
@Override
148197
public String getName() {
149198
return NAME;

0 commit comments

Comments
 (0)