Skip to content

Commit f2afb88

Browse files
authored
Fixing get data stream API when data stream index mode has been changed to time_series (#137852)
This fixes an edge case introduced by #137407. If a data stream is created with the standard `index_mode`, and then the template changes the mode to `time_series` without a `routing_path` and the data stream is not rolled over, then the get data stream API and the get data stream mappings API will fail with errors mentioning something like `failed to apply settings java.lang.IllegalArgumentException: [index.mode=time_series] requires a non-empty [index.routing_path]`. Below is an example stack trace: ``` [2025-11-10T12:51:39,973][WARN ][o.e.c.s.IndexScopedSettings] [runTask-0] [.ds-quickstart-2-2025.11.10-000001] failed to apply settings java.lang.IllegalArgumentException: [index.mode=time_series] requires a non-empty [index.routing_path] at [email protected]/org.elasticsearch.index.IndexMode$2.validateWithOtherSettings(IndexMode.java:156) at [email protected]/org.elasticsearch.index.IndexSettings$3.validate(IndexSettings.java:751) at [email protected]/org.elasticsearch.index.IndexSettings$3.validate(IndexSettings.java:745) at [email protected]/org.elasticsearch.common.settings.Setting.get(Setting.java:589) at [email protected]/org.elasticsearch.common.settings.Setting.get(Setting.java:561) at [email protected]/org.elasticsearch.index.mapper.MapperService.lambda$static$0(MapperService.java:134) at [email protected]/org.elasticsearch.common.settings.Setting.innerGetRaw(Setting.java:657) at [email protected]/org.elasticsearch.common.settings.Setting.getRaw(Setting.java:632) at [email protected]/org.elasticsearch.common.settings.Setting$Updater.hasChanged(Setting.java:1317) at [email protected]/org.elasticsearch.common.settings.AbstractScopedSettings$SettingUpdater.updater(AbstractScopedSettings.java:683) at [email protected]/org.elasticsearch.common.settings.AbstractScopedSettings.applySettings(AbstractScopedSettings.java:168) at [email protected]/org.elasticsearch.index.IndexSettings.updateIndexMetadata(IndexSettings.java:1495) at [email protected]/org.elasticsearch.cluster.metadata.DataStream.lambda$getEffectiveMappings$1(DataStream.java:520) at [email protected]/org.elasticsearch.indices.IndicesService.withTempIndexService(IndicesService.java:773) at [email protected]/org.elasticsearch.cluster.metadata.DataStream.getEffectiveMappings(DataStream.java:495) at [email protected]/org.elasticsearch.cluster.metadata.MetadataDataStreamsService.getEffectiveSettings(MetadataDataStreamsService.java:612) at [email protected]/org.elasticsearch.cluster.metadata.MetadataDataStreamsService.getEffectiveSettings(MetadataDataStreamsService.java:593) at [email protected]/org.elasticsearch.datastreams.action.TransportGetDataStreamsAction.innerOperation(TransportGetDataStreamsAction.java:283) at [email protected]/org.elasticsearch.datastreams.action.TransportGetDataStreamsAction.localClusterStateOperation(TransportGetDataStreamsAction.java:179) at [email protected]/org.elasticsearch.datastreams.action.TransportGetDataStreamsAction.localClusterStateOperation(TransportGetDataStreamsAction.java:72) at [email protected]/org.elasticsearch.action.support.local.TransportLocalProjectMetadataAction.localClusterStateOperation(TransportLocalProjectMetadataAction.java:59) at [email protected]/org.elasticsearch.action.support.local.TransportLocalClusterStateAction.lambda$innerDoExecute$0(TransportLocalClusterStateAction.java:92) at [email protected]/org.elasticsearch.action.ActionRunnable$4.doRun(ActionRunnable.java:101) at [email protected]/org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:1076) at [email protected]/org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:27) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) at java.base/java.lang.Thread.run(Thread.java:1474) ```
1 parent efe4d42 commit f2afb88

File tree

3 files changed

+285
-5
lines changed

3 files changed

+285
-5
lines changed

docs/changelog/137852.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 137852
2+
summary: Fixing get data stream API when data stream index mode has been changed
3+
to `time_series`
4+
area: Data streams
5+
type: bug
6+
issues: []

modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/280_data_stream_mappings-timeseries.yml

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,267 @@
322322
- match: { data_streams.0.effective_mappings: null }
323323
- set: { data_streams.0.indices.0.index_name: oldIndexName }
324324
- set: { data_streams.0.indices.1.index_name: newIndexName }
325+
326+
---
327+
"Test change data stream from standard to time_series with no routing_path":
328+
# This test makes sure that if we change the index mode of a data stream from standard to time_series without a
329+
# configured routing_path, the get data stream and get data stream mappings APIs do not blow up before or after
330+
# rollover.
331+
- do:
332+
indices.put_index_template:
333+
name: my-template
334+
body:
335+
index_patterns: [ test-data-stream-* ]
336+
data_stream: { }
337+
template:
338+
settings:
339+
number_of_replicas: 0
340+
mappings:
341+
properties:
342+
field1:
343+
type: keyword
344+
345+
- do:
346+
indices.create_data_stream:
347+
name: test-data-stream-1
348+
349+
- do:
350+
indices.put_index_template:
351+
name: my-template
352+
body:
353+
index_patterns: [ test-data-stream-* ]
354+
data_stream: { }
355+
template:
356+
settings:
357+
number_of_replicas: 0
358+
mode: time_series
359+
mappings:
360+
properties:
361+
field1:
362+
type: keyword
363+
time_series_dimension: true
364+
365+
- do:
366+
cluster.health:
367+
index: "test-data-stream-1"
368+
wait_for_status: green
369+
370+
- do:
371+
indices.get_data_stream_mappings:
372+
name: test-data-stream-1
373+
- match: { data_streams.0.name: test-data-stream-1 }
374+
- match: { data_streams.0.mappings: {} }
375+
- length: { data_streams.0.effective_mappings.properties: 2 }
376+
377+
- do:
378+
indices.get_data_stream:
379+
name: test-data-stream-1
380+
- match: { data_streams.0.name: test-data-stream-1 }
381+
- match: { data_streams.0.mappings: {} }
382+
- match: { data_streams.0.effective_mappings: null }
383+
384+
- do:
385+
indices.rollover:
386+
alias: "test-data-stream-1"
387+
388+
- do:
389+
cluster.health:
390+
index: "test-data-stream-1"
391+
wait_for_status: green
392+
393+
- do:
394+
indices.get_data_stream_mappings:
395+
name: test-data-stream-1
396+
- match: { data_streams.0.name: test-data-stream-1 }
397+
- length: { data_streams.0.effective_mappings.properties: 2 }
398+
- match: { data_streams.0.effective_mappings.properties.field1.type: "keyword" }
399+
400+
- do:
401+
indices.get_data_stream:
402+
name: test-data-stream-1
403+
- match: { data_streams.0.name: test-data-stream-1 }
404+
- match: { data_streams.0.mappings: { } }
405+
- match: { data_streams.0.effective_mappings: null }
406+
- set: { data_streams.0.indices.0.index_name: oldIndexName }
407+
- set: { data_streams.0.indices.1.index_name: newIndexName }
408+
409+
---
410+
"Test change data stream from time_series to standard with no routing_path":
411+
# This test makes sure that if we change the index mode of a data stream from time_series without a configured
412+
# routing_path to standard, the get data stream and get data stream mappings APIs do not blow up before or after
413+
# rollover.
414+
- do:
415+
indices.put_index_template:
416+
name: my-template
417+
body:
418+
index_patterns: [ my-data-stream-* ]
419+
data_stream: { }
420+
template:
421+
settings:
422+
number_of_replicas: 0
423+
mode: time_series
424+
mappings:
425+
properties:
426+
field1:
427+
type: keyword
428+
time_series_dimension: true
429+
430+
- do:
431+
indices.create_data_stream:
432+
name: my-data-stream-1
433+
434+
- do:
435+
cluster.health:
436+
index: "my-data-stream-1"
437+
wait_for_status: green
438+
439+
- do:
440+
indices.get_data_stream_mappings:
441+
name: my-data-stream-1
442+
- match: { data_streams.0.name: my-data-stream-1 }
443+
- match: { data_streams.0.mappings: {} }
444+
- length: { data_streams.0.effective_mappings.properties: 2 }
445+
446+
- do:
447+
indices.get_data_stream:
448+
name: my-data-stream-1
449+
- match: { data_streams.0.name: my-data-stream-1 }
450+
- match: { data_streams.0.mappings: {} }
451+
- match: { data_streams.0.effective_mappings: null }
452+
453+
- do:
454+
indices.put_index_template:
455+
name: my-template
456+
body:
457+
index_patterns: [ my-data-stream-* ]
458+
data_stream: { }
459+
template:
460+
settings:
461+
number_of_replicas: 0
462+
mappings:
463+
properties:
464+
field1:
465+
type: keyword
466+
467+
- do:
468+
indices.get_data_stream_mappings:
469+
name: my-data-stream-1
470+
- match: { data_streams.0.name: my-data-stream-1 }
471+
- match: { data_streams.0.mappings: {} }
472+
- length: { data_streams.0.effective_mappings.properties: 2 }
473+
474+
- do:
475+
indices.get_data_stream:
476+
name: my-data-stream-1
477+
- match: { data_streams.0.name: my-data-stream-1 }
478+
- match: { data_streams.0.mappings: {} }
479+
- match: { data_streams.0.effective_mappings: null }
480+
481+
- do:
482+
indices.rollover:
483+
alias: "my-data-stream-1"
484+
485+
- do:
486+
cluster.health:
487+
index: "my-data-stream-1"
488+
wait_for_status: green
489+
490+
- do:
491+
indices.get_data_stream_mappings:
492+
name: my-data-stream-1
493+
- match: { data_streams.0.name: my-data-stream-1 }
494+
- length: { data_streams.0.effective_mappings.properties: 2 }
495+
- match: { data_streams.0.effective_mappings.properties.field1.type: "keyword" }
496+
497+
- do:
498+
indices.get_data_stream:
499+
name: my-data-stream-1
500+
- match: { data_streams.0.name: my-data-stream-1 }
501+
- match: { data_streams.0.mappings: { } }
502+
- match: { data_streams.0.effective_mappings: null }
503+
- set: { data_streams.0.indices.0.index_name: oldIndexName }
504+
- set: { data_streams.0.indices.1.index_name: newIndexName }
505+
506+
---
507+
"Test change data stream from standard to time_series with routing_path":
508+
# This test makes sure that if we change the index mode of a data stream from standard to time_series with a
509+
# configured routing_path, the get data stream and get data stream mappings APIs do not blow up before or after
510+
# rollover.
511+
- do:
512+
indices.put_index_template:
513+
name: my-template
514+
body:
515+
index_patterns: [ test-data-stream-* ]
516+
data_stream: { }
517+
template:
518+
settings:
519+
number_of_replicas: 0
520+
mappings:
521+
properties:
522+
field1:
523+
type: keyword
524+
525+
- do:
526+
indices.create_data_stream:
527+
name: test-data-stream-1
528+
529+
- do:
530+
indices.put_index_template:
531+
name: my-template
532+
body:
533+
index_patterns: [ test-data-stream-* ]
534+
data_stream: { }
535+
template:
536+
settings:
537+
number_of_replicas: 0
538+
routing_path: field1
539+
mode: time_series
540+
mappings:
541+
properties:
542+
field1:
543+
type: keyword
544+
time_series_dimension: true
545+
546+
- do:
547+
cluster.health:
548+
index: "test-data-stream-1"
549+
wait_for_status: green
550+
551+
- do:
552+
indices.get_data_stream_mappings:
553+
name: test-data-stream-1
554+
- match: { data_streams.0.name: test-data-stream-1 }
555+
- match: { data_streams.0.mappings: {} }
556+
- length: { data_streams.0.effective_mappings.properties: 2 }
557+
558+
- do:
559+
indices.get_data_stream:
560+
name: test-data-stream-1
561+
- match: { data_streams.0.name: test-data-stream-1 }
562+
- match: { data_streams.0.mappings: {} }
563+
- match: { data_streams.0.effective_mappings: null }
564+
565+
- do:
566+
indices.rollover:
567+
alias: "test-data-stream-1"
568+
569+
- do:
570+
cluster.health:
571+
index: "test-data-stream-1"
572+
wait_for_status: green
573+
574+
- do:
575+
indices.get_data_stream_mappings:
576+
name: test-data-stream-1
577+
- match: { data_streams.0.name: test-data-stream-1 }
578+
- length: { data_streams.0.effective_mappings.properties: 2 }
579+
- match: { data_streams.0.effective_mappings.properties.field1.type: "keyword" }
580+
581+
- do:
582+
indices.get_data_stream:
583+
name: test-data-stream-1
584+
- match: { data_streams.0.name: test-data-stream-1 }
585+
- match: { data_streams.0.mappings: { } }
586+
- match: { data_streams.0.effective_mappings: null }
587+
- set: { data_streams.0.indices.0.index_name: oldIndexName }
588+
- set: { data_streams.0.indices.1.index_name: newIndexName }

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -511,13 +511,23 @@ public static CompressedXContent getEffectiveMappings(
511511
* mapping, we make sure to correct the index mode and index routing path here.
512512
*/
513513
IndexMetadata oldIndexMetadata = indexService.getMetadata();
514-
Settings.Builder settingsBuilder = Settings.builder().put(oldIndexMetadata.getSettings());
515-
settingsBuilder.put(indexModeSettingName, templateSettings.get(indexModeSettingName));
516514

515+
Settings oldIndexSettings = oldIndexMetadata.getSettings();
517516
String indexRoutingPathSettingName = IndexMetadata.INDEX_ROUTING_PATH.getKey();
518-
settingsBuilder.put(indexRoutingPathSettingName, templateSettings.get(indexRoutingPathSettingName));
519-
IndexMetadata newIndexMetadata = new IndexMetadata.Builder(oldIndexMetadata).settings(settingsBuilder.build()).build();
520-
mapperService.getIndexSettings().updateIndexMetadata(newIndexMetadata);
517+
if (Objects.equals(
518+
templateSettings.get(indexRoutingPathSettingName),
519+
oldIndexSettings.get(indexRoutingPathSettingName)
520+
) == false) {
521+
/*
522+
* If the routing_path has changed, we need to make sure to update it so that validation does not fail when we merge
523+
* mappings.
524+
*/
525+
Settings.Builder settingsBuilder = Settings.builder().put(oldIndexSettings);
526+
settingsBuilder.put(indexModeSettingName, templateSettings.get(indexModeSettingName));
527+
settingsBuilder.put(indexRoutingPathSettingName, templateSettings.get(indexRoutingPathSettingName));
528+
IndexMetadata newIndexMetadata = new IndexMetadata.Builder(oldIndexMetadata).settings(settingsBuilder.build()).build();
529+
mapperService.getIndexSettings().updateIndexMetadata(newIndexMetadata);
530+
}
521531
}
522532
CompressedXContent mergedMapping = mapperService.merge(
523533
MapperService.SINGLE_MAPPING_NAME,

0 commit comments

Comments
 (0)