diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/tsdb/10_settings.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/tsdb/10_settings.yml index 861b3292bd520..4df1579cd4597 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/tsdb/10_settings.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/tsdb/10_settings.yml @@ -168,3 +168,65 @@ routing required: mappings: _routing: required: true +--- +set start_time and end_time: + - skip: + version: " - 8.0.99" + reason: introduced in 8.1.0 + - do: + indices.create: + index: test_index + body: + settings: + index: + mode: time_series + routing_path: [metricset] + time_series: + start_time: 1632625782000 + end_time: 1632625792000 + + - do: + indices.put_settings: + index: test_index + body: + index: + time_series: + end_time: 1632625793000 + + - do: + catch: /index.time_series.end_time must be larger than current value \[1632625793000\]/ + indices.put_settings: + index: test_index + body: + index: + time_series: + end_time: 1632625792000 + + - do: + indices.delete: + index: test_index + +--- +set start_time and end_time without timeseries mode: + - skip: + version: " - 8.0.99" + reason: introduced in 8.1.0 + - do: + catch: /\[index.time_series.start_time\] requires \[index.mode=time_series\]/ + indices.create: + index: test_index + body: + settings: + index: + time_series: + start_time: 1632625782000 + + - do: + catch: /\[index.time_series.end_time\] requires \[index.mode=time_series\]/ + indices.create: + index: test_index + body: + settings: + index: + time_series: + end_time: 1632625782000 diff --git a/server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java b/server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java index 9ffade1d68821..998eed2127a06 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java @@ -184,6 +184,8 @@ private static Set> builtInIndexSettings() { Set> result = new HashSet<>(ALWAYS_ENABLED_BUILT_IN_INDEX_SETTINGS); result.add(IndexSettings.MODE); result.add(IndexMetadata.INDEX_ROUTING_PATH); + result.add(IndexSettings.TIME_SERIES_START_TIME); + result.add(IndexSettings.TIME_SERIES_END_TIME); return Set.copyOf(result); } diff --git a/server/src/main/java/org/elasticsearch/common/settings/Setting.java b/server/src/main/java/org/elasticsearch/common/settings/Setting.java index 23f5ffcbef56a..d016211b46b3a 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/Setting.java +++ b/server/src/main/java/org/elasticsearch/common/settings/Setting.java @@ -22,6 +22,7 @@ import org.elasticsearch.core.Nullable; import org.elasticsearch.core.TimeValue; import org.elasticsearch.core.Tuple; +import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.xcontent.DeprecationHandler; import org.elasticsearch.xcontent.NamedXContentRegistry; import org.elasticsearch.xcontent.ToXContentObject; @@ -31,6 +32,7 @@ import org.elasticsearch.xcontent.XContentType; import java.io.IOException; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; @@ -1331,6 +1333,16 @@ public static Setting longSetting(String key, long defaultValue, long minV ); } + public static Setting dateSetting(String key, Instant defaultValue, Validator validator, Property... properties) { + return new Setting<>( + key, + defaultValue.toString(), + (s) -> Instant.from(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parse(s)), + validator, + properties + ); + } + public static Setting simpleString(String key, Property... properties) { return new Setting<>(key, s -> "", Function.identity(), properties); } diff --git a/server/src/main/java/org/elasticsearch/index/IndexMode.java b/server/src/main/java/org/elasticsearch/index/IndexMode.java index 3c7227a7db575..7c672d65632c3 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexMode.java +++ b/server/src/main/java/org/elasticsearch/index/IndexMode.java @@ -32,18 +32,23 @@ /** * "Mode" that controls which behaviors and settings an index supports. + *

+ * For the most part this class concentrates on validating settings and + * mappings. Most different behavior is controlled by forcing settings + * to be set or not set and by enabling extra fields in the mapping. */ public enum IndexMode { STANDARD { @Override void validateWithOtherSettings(Map, Object> settings) { - if (false == Objects.equals( - IndexMetadata.INDEX_ROUTING_PATH.getDefault(Settings.EMPTY), - settings.get(IndexMetadata.INDEX_ROUTING_PATH) - )) { - throw new IllegalArgumentException( - "[" + IndexMetadata.INDEX_ROUTING_PATH.getKey() + "] requires [" + IndexSettings.MODE.getKey() + "=time_series]" - ); + settingRequiresTimeSeries(settings, IndexMetadata.INDEX_ROUTING_PATH); + settingRequiresTimeSeries(settings, IndexSettings.TIME_SERIES_START_TIME); + settingRequiresTimeSeries(settings, IndexSettings.TIME_SERIES_END_TIME); + } + + private void settingRequiresTimeSeries(Map, Object> settings, Setting setting) { + if (false == Objects.equals(setting.getDefault(Settings.EMPTY), settings.get(setting))) { + throw new IllegalArgumentException("[" + setting.getKey() + "] requires [" + IndexSettings.MODE.getKey() + "=time_series]"); } } @@ -67,10 +72,13 @@ void validateWithOtherSettings(Map, Object> settings) { throw new IllegalArgumentException(error(unsupported)); } } - if (IndexMetadata.INDEX_ROUTING_PATH.getDefault(Settings.EMPTY).equals(settings.get(IndexMetadata.INDEX_ROUTING_PATH))) { - throw new IllegalArgumentException( - "[" + IndexSettings.MODE.getKey() + "=time_series] requires [" + IndexMetadata.INDEX_ROUTING_PATH.getKey() + "]" - ); + settingRequiresTimeSeries(settings, IndexMetadata.INDEX_ROUTING_PATH); + // TODO make start and stop time required + } + + private void settingRequiresTimeSeries(Map, Object> settings, Setting setting) { + if (Objects.equals(setting.getDefault(Settings.EMPTY), settings.get(setting))) { + throw new IllegalArgumentException("[" + IndexSettings.MODE.getKey() + "=time_series] requires [" + setting.getKey() + "]"); } } @@ -150,7 +158,12 @@ private void validateTimeStampField(Object timestampFieldValue) { static final List> VALIDATE_WITH_SETTINGS = List.copyOf( Stream.concat( - Stream.of(IndexMetadata.INDEX_ROUTING_PARTITION_SIZE_SETTING, IndexMetadata.INDEX_ROUTING_PATH), + Stream.of( + IndexMetadata.INDEX_ROUTING_PARTITION_SIZE_SETTING, + IndexMetadata.INDEX_ROUTING_PATH, + IndexSettings.TIME_SERIES_START_TIME, + IndexSettings.TIME_SERIES_END_TIME + ), TIME_SERIES_UNSUPPORTED.stream() ).collect(toSet()) ); diff --git a/server/src/main/java/org/elasticsearch/index/IndexSettings.java b/server/src/main/java/org/elasticsearch/index/IndexSettings.java index c908823ea5dc3..23df096597588 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexSettings.java +++ b/server/src/main/java/org/elasticsearch/index/IndexSettings.java @@ -18,6 +18,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.time.DateUtils; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.core.Booleans; @@ -26,6 +27,7 @@ import org.elasticsearch.ingest.IngestService; import org.elasticsearch.node.Node; +import java.time.Instant; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -474,6 +476,46 @@ public static boolean isTimeSeriesModeEnabled() { return Build.CURRENT.isSnapshot() || (TIME_SERIES_MODE_FEATURE_FLAG_REGISTERED != null && TIME_SERIES_MODE_FEATURE_FLAG_REGISTERED); } + /** + * in time series mode, the start time of the index, timestamp must larger than start_time + */ + public static final Setting TIME_SERIES_START_TIME = Setting.dateSetting( + "index.time_series.start_time", + Instant.ofEpochMilli(0), + v -> {}, + Property.IndexScope, + Property.Final + ); + + /** + * in time series mode, the end time of the index, timestamp must smaller than start_time + */ + public static final Setting TIME_SERIES_END_TIME = Setting.dateSetting( + "index.time_series.end_time", + DateUtils.MAX_NANOSECOND_INSTANT, + new Setting.Validator<>() { + @Override + public void validate(Instant value) {} + + @Override + public void validate(Instant value, Map, Object> settings) { + @SuppressWarnings("unchecked") + Instant startTime = (Instant) settings.get(TIME_SERIES_START_TIME); + if (startTime.toEpochMilli() > value.toEpochMilli()) { + throw new IllegalArgumentException("index.time_series.end_time must be larger than index.time_series.start_time"); + } + } + + @Override + public Iterator> settings() { + List> settings = List.of(TIME_SERIES_START_TIME); + return settings.iterator(); + } + }, + Property.IndexScope, + Property.Dynamic + ); + /** * The {@link IndexMode "mode"} of the index. */ @@ -509,6 +551,14 @@ public Iterator> settings() { * The {@link IndexMode "mode"} of the index. */ private final IndexMode mode; + /** + * Start time of the time_series index. + */ + private final long timeSeriesStartTime; + /** + * End time of the time_series index. + */ + private volatile long timeSeriesEndTime; // volatile fields are updated via #updateIndexMetadata(IndexMetadata) under lock private volatile Settings settings; @@ -651,7 +701,8 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti this.indexMetadata = indexMetadata; numberOfShards = settings.getAsInt(IndexMetadata.SETTING_NUMBER_OF_SHARDS, null); mode = isTimeSeriesModeEnabled() ? scopedSettings.get(MODE) : IndexMode.STANDARD; - + timeSeriesStartTime = TIME_SERIES_START_TIME.get(settings).toEpochMilli(); + timeSeriesEndTime = TIME_SERIES_END_TIME.get(settings).toEpochMilli(); this.searchThrottled = INDEX_SEARCH_THROTTLED.get(settings); this.queryStringLenient = QUERY_STRING_LENIENT_SETTING.get(settings); this.queryStringAnalyzeWildcard = QUERY_STRING_ANALYZE_WILDCARD.get(nodeSettings); @@ -762,6 +813,7 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_DEPTH_LIMIT_SETTING, this::setMappingDepthLimit); scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING, this::setMappingFieldNameLengthLimit); scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING, this::setMappingDimensionFieldsLimit); + scopedSettings.addSettingsUpdateConsumer(TIME_SERIES_END_TIME, this::updateTimeSeriesEndTime); } private void setSearchIdleAfter(TimeValue searchIdleAfter) { @@ -1275,4 +1327,22 @@ public long getMappingDimensionFieldsLimit() { private void setMappingDimensionFieldsLimit(long value) { this.mappingDimensionFieldsLimit = value; } + + public long getTimeSeriesStartTime() { + return timeSeriesStartTime; + } + + public long getTimeSeriesEndTime() { + return timeSeriesEndTime; + } + + public void updateTimeSeriesEndTime(Instant endTimeInstant) { + long endTime = endTimeInstant.toEpochMilli(); + if (this.timeSeriesEndTime > endTime) { + throw new IllegalArgumentException( + "index.time_series.end_time must be larger than current value [" + this.timeSeriesEndTime + "]" + ); + } + this.timeSeriesEndTime = endTime; + } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DataStreamTimestampFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DataStreamTimestampFieldMapper.java index 4bb39df46d241..5b7b0e5fe6273 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DataStreamTimestampFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DataStreamTimestampFieldMapper.java @@ -13,6 +13,7 @@ import org.apache.lucene.search.Query; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.index.IndexMode; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentType; @@ -23,6 +24,7 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.core.TimeValue.NSEC_PER_MSEC; import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; /** @@ -204,6 +206,29 @@ public void postParse(DocumentParserContext context) throws IOException { if (numberOfValues > 1) { throw new IllegalArgumentException("data stream timestamp field [" + DEFAULT_PATH + "] encountered multiple values"); } + + validateTimestamp(fields[0], context); + } + + private void validateTimestamp(IndexableField field, DocumentParserContext context) { + if (context.indexSettings().getMode() == null || context.indexSettings().getMode() != IndexMode.TIME_SERIES) { + return; + } + + long value = field.numericValue().longValue(); + if (context.mappingLookup().getMapper(DEFAULT_PATH).typeName().equals(DateFieldMapper.DATE_NANOS_CONTENT_TYPE)) { + value /= NSEC_PER_MSEC; + } + + long startTime = context.indexSettings().getTimeSeriesStartTime(); + if (value < startTime) { + throw new IllegalArgumentException("time series index @timestamp value [" + value + "] must be larger than " + startTime); + } + + long endTime = context.indexSettings().getTimeSeriesEndTime(); + if (value >= endTime) { + throw new IllegalArgumentException("time series index @timestamp value [" + value + "] must be smaller than " + endTime); + } } @Override diff --git a/server/src/test/java/org/elasticsearch/index/IndexSettingsTests.java b/server/src/test/java/org/elasticsearch/index/IndexSettingsTests.java index 299aca0750ec2..069412b998e69 100644 --- a/server/src/test/java/org/elasticsearch/index/IndexSettingsTests.java +++ b/server/src/test/java/org/elasticsearch/index/IndexSettingsTests.java @@ -20,7 +20,9 @@ import org.elasticsearch.index.translog.Translog; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.VersionUtils; +import org.hamcrest.Matchers; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -29,6 +31,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import static org.elasticsearch.index.IndexSettings.TIME_SERIES_END_TIME; +import static org.elasticsearch.index.IndexSettings.TIME_SERIES_START_TIME; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.StringContains.containsString; @@ -678,4 +682,45 @@ public void testCustomDataPathDeprecated() { assertThat(indexSettings.hasCustomDataPath(), is(true)); assertSettingDeprecationsAndWarnings(new Setting[] { IndexMetadata.INDEX_DATA_PATH_SETTING }); } + + public void testUpdateTimeSeriesTimeRange() { + long endTime = System.currentTimeMillis(); + long startTime = endTime - TimeUnit.DAYS.toMillis(1); + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "foo") + .put(TIME_SERIES_START_TIME.getKey(), startTime) + .put(TIME_SERIES_END_TIME.getKey(), endTime) + .build(); + IndexMetadata metadata = newIndexMeta("test", settings); + IndexSettings indexSettings = new IndexSettings(metadata, Settings.EMPTY); + + // test update end_time + // smaller + IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> indexSettings.updateTimeSeriesEndTime(Instant.ofEpochMilli(endTime - randomLongBetween(1, 1000))) + ); + assertThat(e.getMessage(), Matchers.containsString("index.time_series.end_time must be larger than current value")); + + // success + long newEndTime = endTime + randomLongBetween(1, 1000); + indexSettings.updateTimeSeriesEndTime(Instant.ofEpochMilli(newEndTime)); + assertEquals(newEndTime, indexSettings.getTimeSeriesEndTime()); + } + + public void testTimeSeriesTimeBoundary() { + long startTime = System.currentTimeMillis(); + long endTime = startTime - randomLongBetween(1, 1000); + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "foo") + .put(TIME_SERIES_START_TIME.getKey(), startTime) + .put(TIME_SERIES_END_TIME.getKey(), endTime) + .build(); + IndexMetadata metadata = newIndexMeta("test", settings); + + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new IndexSettings(metadata, Settings.EMPTY)); + assertThat(e.getMessage(), Matchers.containsString("index.time_series.end_time must be larger than index.time_series.start_time")); + } } diff --git a/server/src/test/java/org/elasticsearch/index/TimeSeriesModeTests.java b/server/src/test/java/org/elasticsearch/index/TimeSeriesModeTests.java index e48400182df95..d16c1d273207c 100644 --- a/server/src/test/java/org/elasticsearch/index/TimeSeriesModeTests.java +++ b/server/src/test/java/org/elasticsearch/index/TimeSeriesModeTests.java @@ -9,6 +9,7 @@ package org.elasticsearch.index; import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.mapper.DataStreamTimestampFieldMapper; import org.elasticsearch.index.mapper.DateFieldMapper; @@ -18,6 +19,8 @@ import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.mapper.MapperServiceTestCase; +import org.elasticsearch.index.mapper.ParsedDocument; +import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.StringFieldScript; @@ -25,10 +28,13 @@ import org.elasticsearch.search.lookup.SearchLookup; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentType; import java.io.IOException; import java.util.Map; +import java.util.concurrent.TimeUnit; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; @@ -141,6 +147,109 @@ public void testBadTimestamp() throws IOException { ); } + public void testWithoutTimestamp() throws IOException { + Settings s = Settings.builder() + .put(IndexSettings.MODE.getKey(), "time_series") + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "foo") + .build(); + + DocumentMapper mapper = createMapperService(s, mapping(b -> b.startObject("@timestamp").field("type", "date").endObject())) + .documentMapper(); + MapperParsingException e = expectThrows( + MapperParsingException.class, + () -> mapper.parse( + new SourceToParse("1", BytesReference.bytes(XContentFactory.jsonBuilder().startObject().endObject()), XContentType.JSON) + ) + ); + assertThat(e.getRootCause().getMessage(), containsString("data stream timestamp field [@timestamp] is missing")); + } + + public void testEnableTimestampRange() throws IOException { + long endTime = System.currentTimeMillis(); + long startTime = endTime - TimeUnit.DAYS.toMillis(1); + + Settings s = Settings.builder() + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), startTime) + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), endTime) + .put(IndexSettings.MODE.getKey(), "time_series") + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "foo") + .build(); + DocumentMapper mapper = createMapperService( + s, + mapping(b -> b.startObject("@timestamp").field("type", randomBoolean() ? "date" : "date_nanos").endObject()) + ).documentMapper(); + ParsedDocument doc = mapper.parse( + new SourceToParse( + "1", + BytesReference.bytes( + XContentFactory.jsonBuilder().startObject().field("@timestamp", randomLongBetween(startTime, endTime)).endObject() + ), + XContentType.JSON + ) + ); + // Look, mah, no failure. + assertNotNull(doc.rootDoc().getNumericValue("@timestamp")); + } + + public void testBadStartTime() throws IOException { + long endTime = System.currentTimeMillis(); + long startTime = endTime - TimeUnit.DAYS.toMillis(1); + + Settings s = Settings.builder() + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), startTime) + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), endTime) + .put(IndexSettings.MODE.getKey(), "time_series") + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "foo") + .build(); + + DocumentMapper mapper = createMapperService(s, mapping(b -> b.startObject("@timestamp").field("type", "date").endObject())) + .documentMapper(); + MapperParsingException e = expectThrows( + MapperParsingException.class, + () -> mapper.parse( + new SourceToParse( + "1", + BytesReference.bytes( + XContentFactory.jsonBuilder() + .startObject() + .field("@timestamp", Math.max(startTime - randomLongBetween(1, 3), 0)) + .endObject() + ), + XContentType.JSON + ) + ) + ); + assertThat(e.getRootCause().getMessage(), containsString("must be larger than")); + } + + public void testBadEndTime() throws IOException { + long endTime = System.currentTimeMillis(); + long startTime = endTime - TimeUnit.DAYS.toMillis(1); + + Settings s = Settings.builder() + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), startTime) + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), endTime) + .put(IndexSettings.MODE.getKey(), "time_series") + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "foo") + .build(); + + DocumentMapper mapper = createMapperService(s, mapping(b -> b.startObject("@timestamp").field("type", "date").endObject())) + .documentMapper(); + MapperParsingException e = expectThrows( + MapperParsingException.class, + () -> mapper.parse( + new SourceToParse( + "1", + BytesReference.bytes( + XContentFactory.jsonBuilder().startObject().field("@timestamp", endTime + randomLongBetween(0, 3)).endObject() + ), + XContentType.JSON + ) + ) + ); + assertThat(e.getRootCause().getMessage(), containsString("must be smaller than")); + } + public void testEnabledTimeStampMapper() throws IOException { Settings s = Settings.builder() .put(IndexSettings.MODE.getKey(), "time_series") diff --git a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowActionTests.java b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowActionTests.java index d687e9d16d81e..711179541ef3d 100644 --- a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowActionTests.java +++ b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowActionTests.java @@ -332,6 +332,7 @@ public void testDynamicIndexSettingsAreClassified() { replicatedSettings.add(MapperService.INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING); replicatedSettings.add(IndexSettings.MAX_NGRAM_DIFF_SETTING); replicatedSettings.add(IndexSettings.MAX_SHINGLE_DIFF_SETTING); + replicatedSettings.add(IndexSettings.TIME_SERIES_END_TIME); for (Setting setting : IndexScopedSettings.BUILT_IN_INDEX_SETTINGS) { if (setting.isDynamic()) {