Skip to content

Commit f8b2ed9

Browse files
authored
Updating TransportSimulateIndexTemplateAction.resolveTemplate() to account for data stream overrides (#132131)
1 parent fa2ceb4 commit f8b2ed9

File tree

7 files changed

+245
-18
lines changed

7 files changed

+245
-18
lines changed

docs/changelog/132131.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pr: 132131
2+
summary: Updating `TransportSimulateIndexTemplateAction.resolveTemplate()` to account
3+
for data stream overrides
4+
area: Indices APIs
5+
type: bug
6+
issues:
7+
- 131425

qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2085,3 +2085,100 @@ setup:
20852085
- match: { docs.0.doc._index: "subobject-index-1" }
20862086
- match: { docs.0.doc._source.a\.b: "some text" }
20872087
- not_exists: docs.0.doc.error
2088+
2089+
---
2090+
"Simulate ingest with data stream with mapping overrides":
2091+
- skip:
2092+
features: headers
2093+
2094+
- do:
2095+
indices.put_index_template:
2096+
name: test
2097+
body:
2098+
index_patterns: test*
2099+
template:
2100+
lifecycle:
2101+
data_retention: "7d"
2102+
data_stream: {}
2103+
2104+
- do:
2105+
indices.create_data_stream:
2106+
name: test
2107+
- is_true: acknowledged
2108+
2109+
- do:
2110+
cluster.health:
2111+
wait_for_status: yellow
2112+
2113+
- do:
2114+
indices.put_data_stream_mappings:
2115+
name: test
2116+
body:
2117+
properties:
2118+
foo:
2119+
type: boolean
2120+
2121+
- match: { data_streams.0.applied_to_data_stream: true }
2122+
2123+
# For an ordinary simulate request with no overrides, we fetch the mapping from the write index, and do not take the
2124+
# data stream mapping override into account. So the foo field is unmapped, and we can write text to it.
2125+
- do:
2126+
headers:
2127+
Content-Type: application/json
2128+
simulate.ingest:
2129+
index: test
2130+
body: >
2131+
{
2132+
"docs": [
2133+
{
2134+
"_id": "asdf",
2135+
"_source": {
2136+
"@timestamp": 1234,
2137+
"foo": "bar"
2138+
}
2139+
}
2140+
]
2141+
}
2142+
- length: { docs: 1 }
2143+
- match: { docs.0.doc._index: "test" }
2144+
- match: { docs.0.doc._source.foo: "bar" }
2145+
- not_exists: docs.0.doc.error
2146+
2147+
# If template overrides are given, we go to the data stream's template and mapping override (even if the given
2148+
# template overrides are irrelevant as is the case in this test). Now we see that the foo field is mapped as a
2149+
# boolean, so we get a validation error when trying to write text to that field.
2150+
- do:
2151+
headers:
2152+
Content-Type: application/json
2153+
simulate.ingest:
2154+
index: test
2155+
body: >
2156+
{
2157+
"docs": [
2158+
{
2159+
"_id": "asdf",
2160+
"_source": {
2161+
"@timestamp": 1234,
2162+
"foo": "bar"
2163+
}
2164+
}
2165+
],
2166+
"component_template_substitutions": {
2167+
"mappings_template": {
2168+
"template": {
2169+
"mappings": {
2170+
"dynamic": "true",
2171+
"properties": {
2172+
"foo": {
2173+
"type": "keyword"
2174+
}
2175+
}
2176+
}
2177+
}
2178+
}
2179+
}
2180+
}
2181+
- length: { docs: 1 }
2182+
- match: { docs.0.doc._index: "test" }
2183+
- match: { docs.0.doc._source.foo: "bar" }
2184+
- match: { docs.0.doc.error.type: "document_parsing_exception" }

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.simulate_index_template/10_basic.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,55 @@
250250
- match: {template.lifecycle.data_retention: "7d"}
251251
- is_true: template.lifecycle.rollover
252252
- match: {overlapping: []}
253+
254+
---
255+
"Simulate index template with data stream with mapping and setting overrides":
256+
- requires:
257+
cluster_features: [ "logs_stream" ]
258+
reason: requires setting 'logs_stream' to get or set data stream mappings
259+
260+
- do:
261+
indices.put_index_template:
262+
name: test
263+
body:
264+
index_patterns: test*
265+
template:
266+
lifecycle:
267+
data_retention: "7d"
268+
data_stream: {}
269+
270+
- do:
271+
indices.create_data_stream:
272+
name: test
273+
- is_true: acknowledged
274+
275+
- do:
276+
cluster.health:
277+
wait_for_status: yellow
278+
279+
- do:
280+
indices.put_data_stream_mappings:
281+
name: test
282+
body:
283+
properties:
284+
foo:
285+
type: keyword
286+
287+
- match: { data_streams.0.applied_to_data_stream: true }
288+
289+
- do:
290+
indices.put_data_stream_settings:
291+
name: test
292+
body:
293+
index:
294+
refresh_interval: "47s"
295+
296+
- match: { data_streams.0.applied_to_data_stream: true }
297+
298+
- do:
299+
indices.simulate_index_template:
300+
name: test
301+
include_defaults: true
302+
303+
- match: {template.mappings.properties.foo.type: "keyword"}
304+
- match: {template.settings.index.refresh_interval: "47s"}

server/src/main/java/org/elasticsearch/action/admin/indices/template/post/TransportSimulateIndexTemplateAction.java

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.common.settings.ClusterSettings;
3434
import org.elasticsearch.common.settings.Settings;
3535
import org.elasticsearch.common.util.concurrent.EsExecutors;
36+
import org.elasticsearch.core.Nullable;
3637
import org.elasticsearch.core.UpdateForV10;
3738
import org.elasticsearch.index.IndexService;
3839
import org.elasticsearch.index.IndexSettingProvider;
@@ -48,7 +49,9 @@
4849
import org.elasticsearch.transport.TransportService;
4950
import org.elasticsearch.xcontent.NamedXContentRegistry;
5051

52+
import java.io.IOException;
5153
import java.time.Instant;
54+
import java.util.ArrayList;
5255
import java.util.HashMap;
5356
import java.util.HashSet;
5457
import java.util.List;
@@ -158,7 +161,6 @@ protected void localClusterStateOperation(
158161
listener.onResponse(new SimulateIndexTemplateResponse(null, null));
159162
return;
160163
}
161-
162164
final ProjectMetadata tempProjectMetadata = resolveTemporaryState(matchingTemplate, request.getIndexName(), projectWithTemplate);
163165
ComposableIndexTemplate templateV2 = tempProjectMetadata.templatesV2().get(matchingTemplate);
164166
assert templateV2 != null : "the matched template must exist";
@@ -167,6 +169,7 @@ protected void localClusterStateOperation(
167169
matchingTemplate,
168170
request.getIndexName(),
169171
projectWithTemplate,
172+
state.metadata().dataStreams().get(request.getIndexName()),
170173
isDslOnlyMode,
171174
xContentRegistry,
172175
indicesService,
@@ -233,44 +236,42 @@ public static ProjectMetadata resolveTemporaryState(
233236
/**
234237
* Take a template and index name as well as state where the template exists, and return a final
235238
* {@link Template} that represents all the resolved Settings, Mappings, Aliases and Lifecycle
239+
*
240+
* @param matchingTemplate
241+
* @param indexName
242+
* @param simulatedProject
243+
* @param dataStream Used to get any data stream mapping or settings overrides to merge into the returned template
244+
* @param isDslOnlyMode
245+
* @param xContentRegistry
246+
* @param indicesService
247+
* @param systemIndices
248+
* @param indexSettingProviders
249+
* @return
250+
* @throws Exception
236251
*/
237252
public static Template resolveTemplate(
238253
final String matchingTemplate,
239254
final String indexName,
240255
final ProjectMetadata simulatedProject,
256+
@Nullable final DataStream dataStream,
241257
final boolean isDslOnlyMode,
242258
final NamedXContentRegistry xContentRegistry,
243259
final IndicesService indicesService,
244260
final SystemIndices systemIndices,
245261
Set<IndexSettingProvider> indexSettingProviders
246262
) throws Exception {
247-
Settings templateSettings = resolveSettings(simulatedProject, matchingTemplate);
248-
249263
List<Map<String, AliasMetadata>> resolvedAliases = MetadataIndexTemplateService.resolveAliases(simulatedProject, matchingTemplate);
250264

251265
ComposableIndexTemplate template = simulatedProject.templatesV2().get(matchingTemplate);
266+
Settings templateSettings = collectSettings(simulatedProject, dataStream, matchingTemplate, template);
252267
// create the index with dummy settings in the cluster state so we can parse and validate the aliases
253268
Settings.Builder dummySettings = Settings.builder()
254269
.put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current())
255270
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
256271
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
257272
.put(IndexMetadata.SETTING_INDEX_UUID, UUIDs.randomBase64UUID());
258273

259-
/*
260-
* If the index name doesn't look like a data stream backing index, then MetadataCreateIndexService.collectV2Mappings() won't
261-
* include data stream specific mappings in its response.
262-
*/
263-
String simulatedIndexName = template.getDataStreamTemplate() != null
264-
&& indexName.startsWith(DataStream.BACKING_INDEX_PREFIX) == false
265-
? DataStream.getDefaultBackingIndexName(indexName, 1)
266-
: indexName;
267-
List<CompressedXContent> mappings = MetadataCreateIndexService.collectV2Mappings(
268-
null, // empty request mapping as the user can't specify any explicit mappings via the simulate api
269-
simulatedProject,
270-
template,
271-
xContentRegistry,
272-
simulatedIndexName
273-
);
274+
List<CompressedXContent> mappings = collectMappings(simulatedProject, dataStream, template, indexName, xContentRegistry);
274275

275276
// First apply settings sourced from index settings providers
276277
final var now = Instant.now();
@@ -360,6 +361,73 @@ public static Template resolveTemplate(
360361
);
361362
}
362363

364+
/**
365+
* This method collects the mappings from the given template, pulling them from the given simulatedProject. If the template is a data
366+
* stream template and the given dataStream is not null, this method also appends any mapping overrides from the data stream itself.
367+
* @param simulatedProject Used to fetch the component templates referenced from the template
368+
* @param dataStream Used to fetch any mappings explicitly set on the data stream
369+
* @param template The template matching the index, used to fetch mappings
370+
* @param indexName The name of the index whose templates we are fetching
371+
* @param xContentRegistry Used to parse mappings if necessary
372+
* @return A list of matching mappings in ascending priority order
373+
* @throws IOException
374+
*/
375+
private static List<CompressedXContent> collectMappings(
376+
ProjectMetadata simulatedProject,
377+
@Nullable DataStream dataStream,
378+
ComposableIndexTemplate template,
379+
String indexName,
380+
NamedXContentRegistry xContentRegistry
381+
) throws IOException {
382+
/*
383+
* If the index name doesn't look like a data stream backing index, then MetadataCreateIndexService.collectV2Mappings() won't
384+
* include data stream specific mappings in its response.
385+
*/
386+
String simulatedIndexName = template.getDataStreamTemplate() != null
387+
&& indexName.startsWith(DataStream.BACKING_INDEX_PREFIX) == false
388+
? DataStream.getDefaultBackingIndexName(indexName, 1)
389+
: indexName;
390+
List<CompressedXContent> mappings = MetadataCreateIndexService.collectV2Mappings(
391+
null, // empty request mapping as the user can't specify any explicit mappings via the simulate api
392+
simulatedProject,
393+
template,
394+
xContentRegistry, // This is never used since requestMappings is always null, but it is not marked explicitly as @Nullable
395+
simulatedIndexName
396+
);
397+
if (template.getDataStreamTemplate() != null && dataStream != null) {
398+
CompressedXContent dataStreamMappingOverrides = dataStream.getMappings();
399+
if (ComposableIndexTemplate.EMPTY_MAPPINGS.equals(dataStreamMappingOverrides) == false) {
400+
// The data stream has had mapping overrides applied, so include these
401+
mappings = new ArrayList<>(mappings);
402+
mappings.add(dataStreamMappingOverrides);
403+
}
404+
}
405+
return mappings;
406+
}
407+
408+
/**
409+
* This method collects the settings from the given template using the given simulatedProjcet. If dataStream is not null, it also merges
410+
* in any settings overrides on the data stream itself.
411+
* @param simulatedProject The project metadata used to get the settings
412+
* @param dataStream If not null, this is used to get data stream settings overrides
413+
* @param templateName The name of the template
414+
* @param template The template, used to check whether this is a data strema template
415+
* @return The settings to be used
416+
*/
417+
private static Settings collectSettings(
418+
final ProjectMetadata simulatedProject,
419+
@Nullable final DataStream dataStream,
420+
String templateName,
421+
ComposableIndexTemplate template
422+
) {
423+
Settings templateSettings = resolveSettings(simulatedProject, templateName);
424+
if (template.getDataStreamTemplate() != null && dataStream != null) {
425+
// The data stream has had settings overrides applied, so include them
426+
templateSettings = templateSettings.merge(dataStream.getSettings());
427+
}
428+
return templateSettings;
429+
}
430+
363431
private static IndexLongFieldRange getEventIngestedRange(String indexName, ProjectMetadata simulatedProject) {
364432
final IndexMetadata indexMetadata = simulatedProject.index(indexName);
365433
return indexMetadata == null ? IndexLongFieldRange.NO_SHARDS : indexMetadata.getEventIngestedRange();

server/src/main/java/org/elasticsearch/action/admin/indices/template/post/TransportSimulateTemplateAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ protected void localClusterStateOperation(
179179
matchingTemplate,
180180
temporaryIndexName,
181181
projectWithTemplate,
182+
null, // we never match a data stream
182183
isDslOnlyMode,
183184
xContentRegistry,
184185
indicesService,

server/src/main/java/org/elasticsearch/action/bulk/TransportSimulateBulkAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ private Tuple<Collection<String>, Exception> validateMappings(
270270
matchingTemplate,
271271
request.index(),
272272
simulatedProjectMetadata,
273+
project.dataStreams().get(request.index()),
273274
isDataStreamsLifecycleOnlyMode(clusterService.getSettings()),
274275
xContentRegistry,
275276
indicesService,

server/src/test/java/org/elasticsearch/action/admin/indices/template/post/TransportSimulateIndexTemplateActionTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public boolean overrulesTemplateAndRequestSettings() {
9797
matchingTemplate,
9898
indexName,
9999
simulatedProject,
100+
null,
100101
isDslOnlyMode,
101102
xContentRegistry(),
102103
indicesService,

0 commit comments

Comments
 (0)