Browse Source

[8.x] Introduce `index.mapping.source.mode` setting to override `_source.mode` (#114433) (#114680)

* Introduce `index.mapping.source.mode` setting to override `_source.mode` (#114433)

* featur : introduce index.mapping.source.mode setting

Introduce a new `index.mapper.source.mode` setting which will be used
to override the mapping level `_source.mode`. For now the mapping
level setting will stay and be deprecated later with another PR.

The setting takes precedence always precedence. When not defined
the index mode is used and can be overridden by the _source.mode
mapping level definition.

(cherry picked from commit edcabb80b788c0bf91b7edbaf4c79deb8b7b8553)

* fix: replace return switch with switch case

* fix: stored source mode not supported in 8.16

We also update a few error messages to account
for a few minor differences.

* Revert "fix: stored source mode not supported in 8.16"

This reverts commit 2e523c3c0752ab37e4b4001b1c3443a804ded47c.

* fix: stored source mode not supported in 8.16

We also update a few error messages to account
for a few minor differences.

* fix: update error message for time_series

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Salvatore Campagna 1 year ago
parent
commit
c4188b5ace

+ 2 - 0
server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java

@@ -35,6 +35,7 @@ import org.elasticsearch.index.fielddata.IndexFieldDataService;
 import org.elasticsearch.index.mapper.FieldMapper;
 import org.elasticsearch.index.mapper.IgnoredSourceFieldMapper;
 import org.elasticsearch.index.mapper.MapperService;
+import org.elasticsearch.index.mapper.SourceFieldMapper;
 import org.elasticsearch.index.similarity.SimilarityService;
 import org.elasticsearch.index.store.FsDirectoryFactory;
 import org.elasticsearch.index.store.Store;
@@ -187,6 +188,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
         FieldMapper.SYNTHETIC_SOURCE_KEEP_INDEX_SETTING,
         IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_WRITE_SETTING,
         IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_READ_SETTING,
+        SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING,
 
         // validate that built-in similarities don't get redefined
         Setting.groupSetting("index.similarity.", (s) -> {

+ 7 - 0
server/src/main/java/org/elasticsearch/index/IndexSettings.java

@@ -28,6 +28,7 @@ import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.features.NodeFeature;
 import org.elasticsearch.index.mapper.IgnoredSourceFieldMapper;
 import org.elasticsearch.index.mapper.Mapper;
+import org.elasticsearch.index.mapper.SourceFieldMapper;
 import org.elasticsearch.index.translog.Translog;
 import org.elasticsearch.ingest.IngestService;
 import org.elasticsearch.node.Node;
@@ -807,6 +808,7 @@ public final class IndexSettings {
     private volatile long mappingDimensionFieldsLimit;
     private volatile boolean skipIgnoredSourceWrite;
     private volatile boolean skipIgnoredSourceRead;
+    private final SourceFieldMapper.Mode indexMappingSourceMode;
 
     /**
      * The maximum number of refresh listeners allows on this shard.
@@ -967,6 +969,7 @@ public final class IndexSettings {
         es87TSDBCodecEnabled = scopedSettings.get(TIME_SERIES_ES87TSDB_CODEC_ENABLED_SETTING);
         skipIgnoredSourceWrite = scopedSettings.get(IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_WRITE_SETTING);
         skipIgnoredSourceRead = scopedSettings.get(IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_READ_SETTING);
+        indexMappingSourceMode = scopedSettings.get(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING);
 
         scopedSettings.addSettingsUpdateConsumer(
             MergePolicyConfig.INDEX_COMPOUND_FORMAT_SETTING,
@@ -1646,6 +1649,10 @@ public final class IndexSettings {
         this.skipIgnoredSourceRead = value;
     }
 
+    public SourceFieldMapper.Mode getIndexMappingSourceMode() {
+        return indexMappingSourceMode;
+    }
+
     /**
      * The bounds for {@code @timestamp} on this index or
      * {@code null} if there are no bounds.

+ 99 - 22
server/src/main/java/org/elasticsearch/index/mapper/SourceFieldMapper.java

@@ -18,11 +18,13 @@ import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.common.Explicit;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.CollectionUtils;
 import org.elasticsearch.core.Nullable;
 import org.elasticsearch.features.NodeFeature;
 import org.elasticsearch.index.IndexMode;
+import org.elasticsearch.index.IndexSettings;
 import org.elasticsearch.index.IndexVersions;
 import org.elasticsearch.index.query.QueryShardException;
 import org.elasticsearch.index.query.SearchExecutionContext;
@@ -59,8 +61,20 @@ public class SourceFieldMapper extends MetadataFieldMapper {
 
     public static final String LOSSY_PARAMETERS_ALLOWED_SETTING_NAME = "index.lossy.source-mapping-parameters";
 
+    public static final Setting<Mode> INDEX_MAPPER_SOURCE_MODE_SETTING = Setting.enumSetting(SourceFieldMapper.Mode.class, settings -> {
+        final IndexMode indexMode = IndexSettings.MODE.get(settings);
+
+        switch (indexMode) {
+            case LOGSDB:
+            case TIME_SERIES:
+                return Mode.SYNTHETIC.name();
+            default:
+                return Mode.STORED.name();
+        }
+    }, "index.mapping.source.mode", value -> {}, Setting.Property.Final, Setting.Property.IndexScope);
+
     /** The source mode */
-    private enum Mode {
+    public enum Mode {
         DISABLED,
         STORED,
         SYNTHETIC
@@ -93,6 +107,15 @@ public class SourceFieldMapper extends MetadataFieldMapper {
         true
     );
 
+    private static final SourceFieldMapper TSDB_DEFAULT_STORED = new SourceFieldMapper(
+        Mode.STORED,
+        Explicit.IMPLICIT_TRUE,
+        Strings.EMPTY_ARRAY,
+        Strings.EMPTY_ARRAY,
+        IndexMode.TIME_SERIES,
+        true
+    );
+
     private static final SourceFieldMapper TSDB_DEFAULT_NO_RECOVERY_SOURCE = new SourceFieldMapper(
         Mode.SYNTHETIC,
         Explicit.IMPLICIT_TRUE,
@@ -102,6 +125,15 @@ public class SourceFieldMapper extends MetadataFieldMapper {
         false
     );
 
+    private static final SourceFieldMapper TSDB_DEFAULT_NO_RECOVERY_SOURCE_STORED = new SourceFieldMapper(
+        Mode.STORED,
+        Explicit.IMPLICIT_TRUE,
+        Strings.EMPTY_ARRAY,
+        Strings.EMPTY_ARRAY,
+        IndexMode.TIME_SERIES,
+        false
+    );
+
     private static final SourceFieldMapper LOGSDB_DEFAULT = new SourceFieldMapper(
         Mode.SYNTHETIC,
         Explicit.IMPLICIT_TRUE,
@@ -111,6 +143,15 @@ public class SourceFieldMapper extends MetadataFieldMapper {
         true
     );
 
+    private static final SourceFieldMapper LOGSDB_DEFAULT_STORED = new SourceFieldMapper(
+        Mode.STORED,
+        Explicit.IMPLICIT_TRUE,
+        Strings.EMPTY_ARRAY,
+        Strings.EMPTY_ARRAY,
+        IndexMode.LOGSDB,
+        true
+    );
+
     private static final SourceFieldMapper LOGSDB_DEFAULT_NO_RECOVERY_SOURCE = new SourceFieldMapper(
         Mode.SYNTHETIC,
         Explicit.IMPLICIT_TRUE,
@@ -120,6 +161,15 @@ public class SourceFieldMapper extends MetadataFieldMapper {
         false
     );
 
+    private static final SourceFieldMapper LOGSDB_DEFAULT_NO_RECOVERY_SOURCE_STORED = new SourceFieldMapper(
+        Mode.STORED,
+        Explicit.IMPLICIT_TRUE,
+        Strings.EMPTY_ARRAY,
+        Strings.EMPTY_ARRAY,
+        IndexMode.LOGSDB,
+        false
+    );
+
     /*
      * Synthetic source was added as the default for TSDB in v.8.7. The legacy field mapper below
      * is used in bwc tests and mixed clusters containing time series indexes created in an earlier version.
@@ -194,6 +244,8 @@ public class SourceFieldMapper extends MetadataFieldMapper {
             m -> Arrays.asList(toType(m).excludes)
         );
 
+        private final Settings settings;
+
         private final IndexMode indexMode;
 
         private final boolean supportsNonDefaultParameterValues;
@@ -207,6 +259,7 @@ public class SourceFieldMapper extends MetadataFieldMapper {
             boolean enableRecoverySource
         ) {
             super(Defaults.NAME);
+            this.settings = settings;
             this.indexMode = indexMode;
             this.supportsNonDefaultParameterValues = supportsCheckForNonDefaultParams == false
                 || settings.getAsBoolean(LOSSY_PARAMETERS_ALLOWED_SETTING_NAME, true);
@@ -223,10 +276,10 @@ public class SourceFieldMapper extends MetadataFieldMapper {
             return new Parameter<?>[] { enabled, mode, includes, excludes };
         }
 
-        private boolean isDefault() {
-            Mode m = mode.get();
-            if (m != null
-                && (((indexMode != null && indexMode.isSyntheticSourceEnabled() && m == Mode.SYNTHETIC) == false) || m == Mode.DISABLED)) {
+        private boolean isDefault(final Mode sourceMode) {
+            if (sourceMode != null
+                && (((indexMode != null && indexMode.isSyntheticSourceEnabled() && sourceMode == Mode.SYNTHETIC) == false)
+                    || sourceMode == Mode.DISABLED)) {
                 return false;
             }
             return enabled.get().value() && includes.getValue().isEmpty() && excludes.getValue().isEmpty();
@@ -242,12 +295,14 @@ public class SourceFieldMapper extends MetadataFieldMapper {
                     throw new MapperParsingException("Cannot set both [mode] and [enabled] parameters");
                 }
             }
-            if (isDefault()) {
-                return switch (indexMode) {
-                    case TIME_SERIES -> enableRecoverySource ? TSDB_DEFAULT : TSDB_DEFAULT_NO_RECOVERY_SOURCE;
-                    case LOGSDB -> enableRecoverySource ? LOGSDB_DEFAULT : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE;
-                    default -> enableRecoverySource ? DEFAULT : DEFAULT_NO_RECOVERY_SOURCE;
-                };
+            // NOTE: if the `index.mapper.source.mode` exists it takes precedence to determine the source mode for `_source`
+            // otherwise the mode is determined according to `index.mode` and `_source.mode`.
+            final Mode sourceMode = INDEX_MAPPER_SOURCE_MODE_SETTING.exists(settings)
+                ? INDEX_MAPPER_SOURCE_MODE_SETTING.get(settings)
+                : mode.get();
+            if (isDefault(sourceMode)) {
+                return resolveSourceMode(indexMode, sourceMode, enableRecoverySource);
+
             }
             if (supportsNonDefaultParameterValues == false) {
                 List<String> disallowed = new ArrayList<>();
@@ -271,8 +326,9 @@ public class SourceFieldMapper extends MetadataFieldMapper {
                     );
                 }
             }
+
             SourceFieldMapper sourceFieldMapper = new SourceFieldMapper(
-                mode.get(),
+                sourceMode,
                 enabled.get(),
                 includes.getValue().toArray(Strings.EMPTY_ARRAY),
                 excludes.getValue().toArray(Strings.EMPTY_ARRAY),
@@ -287,21 +343,42 @@ public class SourceFieldMapper extends MetadataFieldMapper {
 
     }
 
+    private static SourceFieldMapper resolveSourceMode(final IndexMode indexMode, final Mode sourceMode, boolean enableRecoverySource) {
+        if (indexMode == IndexMode.STANDARD) {
+            return enableRecoverySource ? DEFAULT : DEFAULT_NO_RECOVERY_SOURCE;
+        }
+        final SourceFieldMapper syntheticWithoutRecoverySource = indexMode == IndexMode.TIME_SERIES
+            ? TSDB_DEFAULT_NO_RECOVERY_SOURCE
+            : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE;
+        final SourceFieldMapper syntheticWithRecoverySource = indexMode == IndexMode.TIME_SERIES ? TSDB_DEFAULT : LOGSDB_DEFAULT;
+        final SourceFieldMapper storedWithoutRecoverySource = indexMode == IndexMode.TIME_SERIES
+            ? TSDB_DEFAULT_NO_RECOVERY_SOURCE_STORED
+            : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE_STORED;
+        final SourceFieldMapper storedWithRecoverySource = indexMode == IndexMode.TIME_SERIES ? TSDB_DEFAULT_STORED : LOGSDB_DEFAULT_STORED;
+
+        switch (sourceMode) {
+            case SYNTHETIC:
+                return enableRecoverySource ? syntheticWithRecoverySource : syntheticWithoutRecoverySource;
+            case STORED:
+                return enableRecoverySource ? storedWithRecoverySource : storedWithoutRecoverySource;
+            case DISABLED:
+                throw new IllegalArgumentException("_source cannot be disabled in index using [" + indexMode + "] index mode");
+            default:
+                throw new IllegalStateException("Unexpected value: " + sourceMode);
+        }
+    }
+
     public static final TypeParser PARSER = new ConfigurableTypeParser(c -> {
-        var indexMode = c.getIndexSettings().getMode();
+        final IndexMode indexMode = c.getIndexSettings().getMode();
         boolean enableRecoverySource = INDICES_RECOVERY_SOURCE_ENABLED_SETTING.get(c.getSettings());
+        final Mode settingSourceMode = INDEX_MAPPER_SOURCE_MODE_SETTING.get(c.getSettings());
+
         if (indexMode.isSyntheticSourceEnabled()) {
-            if (indexMode == IndexMode.TIME_SERIES) {
-                if (c.getIndexSettings().getIndexVersionCreated().onOrAfter(IndexVersions.V_8_7_0)) {
-                    return enableRecoverySource ? TSDB_DEFAULT : TSDB_DEFAULT_NO_RECOVERY_SOURCE;
-                } else {
-                    return enableRecoverySource ? TSDB_LEGACY_DEFAULT : TSDB_LEGACY_DEFAULT_NO_RECOVERY_SOURCE;
-                }
-            } else if (indexMode == IndexMode.LOGSDB) {
-                return enableRecoverySource ? LOGSDB_DEFAULT : LOGSDB_DEFAULT_NO_RECOVERY_SOURCE;
+            if (indexMode == IndexMode.TIME_SERIES && c.getIndexSettings().getIndexVersionCreated().before(IndexVersions.V_8_7_0)) {
+                return enableRecoverySource ? TSDB_LEGACY_DEFAULT : TSDB_LEGACY_DEFAULT_NO_RECOVERY_SOURCE;
             }
         }
-        return enableRecoverySource ? DEFAULT : DEFAULT_NO_RECOVERY_SOURCE;
+        return resolveSourceMode(indexMode, settingSourceMode, enableRecoverySource);
     },
         c -> new Builder(
             c.getIndexSettings().getMode(),

+ 2 - 0
x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowActionTests.java

@@ -18,6 +18,7 @@ import org.elasticsearch.index.IndexVersion;
 import org.elasticsearch.index.MapperTestUtils;
 import org.elasticsearch.index.mapper.IgnoredSourceFieldMapper;
 import org.elasticsearch.index.mapper.MapperService;
+import org.elasticsearch.index.mapper.SourceFieldMapper;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.xpack.ccr.Ccr;
 import org.elasticsearch.xpack.ccr.CcrSettings;
@@ -334,6 +335,7 @@ public class TransportResumeFollowActionTests extends ESTestCase {
         replicatedSettings.add(IndexSettings.PREFER_ILM_SETTING);
         replicatedSettings.add(IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_READ_SETTING);
         replicatedSettings.add(IgnoredSourceFieldMapper.SKIP_IGNORED_SOURCE_WRITE_SETTING);
+        replicatedSettings.add(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING);
 
         for (Setting<?> setting : IndexScopedSettings.BUILT_IN_INDEX_SETTINGS) {
             // removed settings have no effect, they are only there for BWC

+ 819 - 0
x-pack/plugin/logsdb/src/yamlRestTest/resources/rest-api-spec/test/40_source_mode_setting.yml

@@ -0,0 +1,819 @@
+---
+create an index with disabled source mode and standard index mode without setting:
+  - do:
+      indices.create:
+        index: test_disabled_standard
+        body:
+          settings:
+            index:
+              mode: standard
+          mappings:
+            _source:
+              mode: disabled
+
+  - do:
+      indices.get_mapping:
+        index: test_disabled_standard
+
+  - match: { test_disabled_standard.mappings._source.mode: disabled }
+
+---
+create an index with stored source mode and standard index mode without setting:
+  - do:
+      indices.create:
+        index: test_stored_standard
+        body:
+          settings:
+            index:
+              mode: standard
+          mappings:
+            _source:
+              mode: stored
+
+  - do:
+      indices.get_mapping:
+        index: test_stored_standard
+
+  - match: { test_stored_standard.mappings._source.mode: stored }
+
+---
+create an index with synthetic source mode and standard index mode without setting:
+  - do:
+      indices.create:
+        index: test_synthetic_standard
+        body:
+          settings:
+            index:
+              mode: standard
+          mappings:
+            _source:
+              mode: synthetic
+
+  - do:
+      indices.get_mapping:
+        index: test_synthetic_standard
+
+  - match: { test_synthetic_standard.mappings._source.mode: synthetic }
+
+---
+create an index with disabled source mode and logsdb index mode without setting:
+  - do:
+      catch: bad_request
+      indices.create:
+        index: test_disabled_logsdb
+        body:
+          settings:
+            index:
+              mode: logsdb
+          mappings:
+            _source:
+              mode: disabled
+
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: Indices with with index mode [logsdb] only support synthetic source" }
+
+---
+create an index with stored source mode and logsdb index mode without setting:
+  - do:
+      catch: bad_request
+      indices.create:
+        index: test_stored_logsdb
+        body:
+          settings:
+            index:
+              mode: logsdb
+          mappings:
+            _source:
+              mode: stored
+
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: Indices with with index mode [logsdb] only support synthetic source" }
+
+---
+create an index with synthetic source mode and logsdb index mode without setting:
+  - do:
+      indices.create:
+        index: test_synthetic_logsdb
+        body:
+          settings:
+            index:
+              mode: logsdb
+          mappings:
+            _source:
+              mode: synthetic
+
+  - do:
+      indices.get_mapping:
+        index: test_synthetic_logsdb
+
+  - match: { test_synthetic_logsdb.mappings._source.mode: synthetic }
+
+---
+create an index with disabled source mode and time series index mode without setting:
+  - do:
+      catch: bad_request
+      indices.create:
+        index: test_disabled_time_series
+        body:
+          settings:
+            index:
+              mode: time_series
+              routing_path: [ keyword ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            _source:
+              mode: disabled
+            properties:
+              keyword:
+                type: keyword
+                time_series_dimension: true
+
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: time series indices only support synthetic source" }
+
+---
+create an index with stored source mode and time series index mode without setting:
+  - do:
+      catch: bad_request
+      indices.create:
+        index: test_stored_time_series
+        body:
+          settings:
+            index:
+              mode: time_series
+              routing_path: [ keyword ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            _source:
+              mode: stored
+            properties:
+              keyword:
+                type: keyword
+                time_series_dimension: true
+
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: time series indices only support synthetic source" }
+
+
+---
+create an index with synthetic source mode and time series index mode without setting:
+  - do:
+      indices.create:
+        index: test_synthetic_time_series
+        body:
+          settings:
+            index:
+              mode: time_series
+              routing_path: [ keyword ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            _source:
+              mode: synthetic
+            properties:
+              keyword:
+                type: keyword
+                time_series_dimension: true
+
+  - do:
+      indices.get_settings:
+        index: "test_synthetic_time_series"
+  - match: { test_synthetic_time_series.settings.index.mode: time_series }
+
+  - do:
+      indices.get_mapping:
+        index: test_synthetic_time_series
+
+  - match: { test_synthetic_time_series.mappings._source.mode: synthetic }
+
+---
+create an index with stored source mode:
+  - do:
+      indices.create:
+        index: test_stored_default
+        body:
+          mappings:
+            _source:
+              mode: stored
+
+  - do:
+      indices.get_mapping:
+        index: test_stored_default
+
+  - match: { test_stored_default.mappings._source.mode: stored }
+
+---
+override stored to synthetic source mode:
+  - do:
+      indices.create:
+        index: test_stored_override
+        body:
+          settings:
+            index:
+              mapping.source.mode: synthetic
+          mappings:
+            _source:
+              mode: stored
+
+  - do:
+      indices.get_mapping:
+        index: test_stored_override
+
+  - match: { test_stored_override.mappings._source.mode: synthetic }
+
+---
+override stored to disabled source mode:
+  - do:
+      indices.create:
+        index: test_stored_disabled
+        body:
+          settings:
+            index:
+              mapping.source.mode: disabled
+          mappings:
+            _source:
+              mode: stored
+
+  - do:
+      indices.get_mapping:
+        index: test_stored_disabled
+
+  - match: { test_stored_disabled.mappings._source.mode: disabled }
+
+---
+create an index with disabled source mode:
+  - do:
+      indices.create:
+        index: test_disabled_default
+        body:
+          mappings:
+            _source:
+              mode: disabled
+
+  - do:
+      indices.get_mapping:
+        index: test_disabled_default
+
+  - match: { test_disabled_default.mappings._source.mode: disabled }
+
+---
+override disabled to synthetic source mode:
+  - do:
+      indices.create:
+        index: test_disabled_synthetic
+        body:
+          settings:
+            index:
+              mapping.source.mode: synthetic
+          mappings:
+            _source:
+              mode: disabled
+
+  - do:
+      indices.get_mapping:
+        index: test_disabled_synthetic
+
+  - match: { test_disabled_synthetic.mappings._source.mode: synthetic }
+
+---
+override disabled to stored source mode:
+  - do:
+      indices.create:
+        index: test_disabled_stored
+        body:
+          settings:
+            index:
+              mapping.source.mode: stored
+          mappings:
+            _source:
+              mode: disabled
+
+  - do:
+      indices.get_mapping:
+        index: test_disabled_stored
+
+  - match: { test_disabled_stored.mappings._source.mode: stored }
+
+---
+create an index with synthetic source mode:
+  - do:
+      indices.create:
+        index: test_synthetic_default
+        body:
+          mappings:
+            _source:
+              mode: synthetic
+
+  - do:
+      indices.get_mapping:
+        index: test_synthetic_default
+
+  - match: { test_synthetic_default.mappings._source.mode: synthetic }
+
+---
+override synthetic to stored source mode:
+  - do:
+      indices.create:
+        index: test_synthetic_stored
+        body:
+          settings:
+            index:
+              mapping.source.mode: stored
+          mappings:
+            _source:
+              mode: synthetic
+
+  - do:
+      indices.get_mapping:
+        index: test_synthetic_stored
+
+  - match: { test_synthetic_stored.mappings._source.mode: stored }
+
+---
+override synthetic to disabled source mode:
+  - do:
+      indices.create:
+        index: test_synthetic_disabled
+        body:
+          settings:
+            index:
+              mapping.source.mode: disabled
+          mappings:
+            _source:
+              mode: synthetic
+
+  - do:
+      indices.get_mapping:
+        index: test_synthetic_disabled
+
+  - match: { test_synthetic_disabled.mappings._source.mode: disabled }
+
+---
+create an index with unspecified source mode:
+  - do:
+      indices.create:
+        index: test_unset_default
+
+  - do:
+      indices.get_mapping:
+        index: test_unset_default
+
+  - match: { test_unset_default.mappings._source.mode: null }
+
+---
+override unspecified to stored source mode:
+  - do:
+      indices.create:
+        index: test_unset_stored
+        body:
+          settings:
+            index:
+              mapping.source.mode: stored
+
+  - do:
+      indices.get_mapping:
+        index: test_unset_stored
+
+  - match: { test_unset_stored.mappings: { } }
+
+---
+override unspecified to disabled source mode:
+  - do:
+      indices.create:
+        index: test_unset_disabled
+        body:
+          settings:
+            index:
+              mapping.source.mode: disabled
+
+  - do:
+      indices.get_mapping:
+        index: test_unset_disabled
+
+  - match: { test_unset_disabled.mappings: { } }
+
+---
+override unspecified to synthetic source mode:
+  - do:
+      indices.create:
+        index: test_unset_synthetic
+        body:
+          settings:
+            index:
+              mapping.source.mode: synthetic
+
+  - do:
+      indices.get_mapping:
+        index: test_unset_synthetic
+
+  - match: { test_unset_synthetic.mappings: { } }
+
+---
+create an index with standard index mode:
+  - do:
+      indices.create:
+        index: test_standard_index_mode
+        body:
+          settings:
+            index:
+              mode: standard
+          mappings:
+            _source:
+              mode: stored
+
+  - do:
+      indices.get_mapping:
+        index: test_standard_index_mode
+
+  - match: { test_standard_index_mode.mappings._source.mode: stored }
+
+---
+create an index with time_series index mode and synthetic source:
+  - do:
+      indices.create:
+        index: test_time_series_index_mode_synthetic
+        body:
+          settings:
+            index:
+              mode: time_series
+              mapping.source.mode: synthetic
+              routing_path: [ keyword ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            properties:
+              keyword:
+                type: keyword
+                time_series_dimension: true
+
+  - do:
+      indices.get_settings:
+        index: "test_time_series_index_mode_synthetic"
+  - match: { test_time_series_index_mode_synthetic.settings.index.mode: time_series }
+
+
+  - do:
+      indices.get_mapping:
+        index: test_time_series_index_mode_synthetic
+
+  - match: { test_time_series_index_mode_synthetic.mappings._source.mode: synthetic }
+
+---
+create an index with logsdb index mode and synthetic source:
+  - do:
+      indices.create:
+        index: test_logsdb_index_mode_synthetic
+        body:
+          settings:
+            index:
+              mode: logsdb
+              mapping.source.mode: synthetic
+
+  - do:
+      indices.get_settings:
+        index: "test_logsdb_index_mode_synthetic"
+  - match: { test_logsdb_index_mode_synthetic.settings.index.mode: logsdb }
+
+  - do:
+      indices.get_mapping:
+        index: test_logsdb_index_mode_synthetic
+
+  - match: { test_logsdb_index_mode_synthetic.mappings._source.mode: synthetic }
+
+---
+create an index with time_series index mode and stored source:
+  - do:
+      catch: bad_request
+      indices.create:
+        index: test_time_series_index_mode_undefined
+        body:
+          settings:
+            index:
+              mode: time_series
+              mapping.source.mode: stored
+              routing_path: [ keyword ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            properties:
+              keyword:
+                type: keyword
+                time_series_dimension: true
+
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: time series indices only support synthetic source" }
+
+---
+create an index with logsdb index mode and stored source:
+  - do:
+      catch: bad_request
+      indices.create:
+        index: test_logsdb_index_mode_undefined
+        body:
+          settings:
+            index:
+              mode: logsdb
+              mapping.source.mode: stored
+
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: Indices with with index mode [logsdb] only support synthetic source" }
+
+---
+create an index with time_series index mode and disabled source:
+  - do:
+      catch: bad_request
+      indices.create:
+        index: test_time_series_index_mode
+        body:
+          settings:
+            index:
+              mode: time_series
+              mapping.source.mode: disabled
+              routing_path: [ keyword ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            properties:
+              keyword:
+                type: keyword
+                time_series_dimension: true
+
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: _source cannot be disabled in index using [time_series] index mode" }
+
+---
+create an index with logsdb index mode and disabled source:
+  - do:
+      catch: bad_request
+      indices.create:
+        index: test_logsdb_index_mode
+        body:
+          settings:
+            index:
+              mode: logsdb
+              mapping.source.mode: disabled
+
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: _source cannot be disabled in index using [logsdb] index mode" }
+
+---
+modify final setting after index creation:
+  - do:
+      indices.create:
+        index: test_modify_setting
+        body:
+          settings:
+            index:
+              mapping.source.mode: stored
+
+  - do:
+      catch: /.*Can't update non dynamic setting.*/
+      indices.put_settings:
+        index: test_modify_setting
+        body:
+          index:
+            mapping.source.mode: synthetic
+
+---
+modify source mapping from stored to disabled after index creation:
+  - do:
+      indices.create:
+        index: test_modify_source_mode_stored_disabled
+        body:
+          settings:
+            index:
+              mapping.source.mode: stored
+
+  - do:
+      indices.put_mapping:
+        index: test_modify_source_mode_stored_disabled
+        body:
+          _source:
+            mode: disabled
+  - is_true: acknowledged
+
+  - do:
+      indices.get_mapping:
+        index: test_modify_source_mode_stored_disabled
+  - match: { test_modify_source_mode_stored_disabled.mappings._source.mode: stored }
+
+---
+modify source mapping from stored to synthetic after index creation:
+  - do:
+      indices.create:
+        index: test_modify_source_mode_stored_synthetic
+        body:
+          settings:
+            index:
+              mapping.source.mode: stored
+
+  - do:
+      indices.put_mapping:
+        index: test_modify_source_mode_stored_synthetic
+        body:
+          _source:
+            mode: synthetic
+  - is_true: acknowledged
+
+  - do:
+      indices.get_mapping:
+        index: test_modify_source_mode_stored_synthetic
+  - match: { test_modify_source_mode_stored_synthetic.mappings._source.mode: stored }
+
+---
+modify source mapping from disabled to stored after index creation:
+  - do:
+      indices.create:
+        index: test_modify_source_mode_disabled_stored
+        body:
+          settings:
+            index:
+              mapping.source.mode: disabled
+
+  - do:
+      indices.put_mapping:
+        index: test_modify_source_mode_disabled_stored
+        body:
+          _source:
+            mode: stored
+  - is_true: acknowledged
+
+  - do:
+      indices.get_mapping:
+        index: test_modify_source_mode_disabled_stored
+  - match: { test_modify_source_mode_disabled_stored.mappings._source.mode: disabled }
+
+---
+modify source mapping from disabled to synthetic after index creation:
+  - do:
+      indices.create:
+        index: test_modify_source_mode_disabled_synthetic
+        body:
+          settings:
+            index:
+              mapping.source.mode: disabled
+
+  - do:
+      indices.put_mapping:
+        index: test_modify_source_mode_disabled_synthetic
+        body:
+          _source:
+            mode: synthetic
+  - is_true: acknowledged
+
+  - do:
+      indices.get_mapping:
+        index: test_modify_source_mode_disabled_synthetic
+  - match: { test_modify_source_mode_disabled_synthetic.mappings._source.mode: disabled }
+
+---
+modify source mapping from synthetic to stored after index creation:
+  - do:
+      indices.create:
+        index: test_modify_source_mode_synthetic_stored
+        body:
+          settings:
+            index:
+              mapping.source.mode: synthetic
+
+  - do:
+      indices.put_mapping:
+        index: test_modify_source_mode_synthetic_stored
+        body:
+          _source:
+            mode: stored
+  - is_true: acknowledged
+
+  - do:
+      indices.get_mapping:
+        index: test_modify_source_mode_synthetic_stored
+  - match: { test_modify_source_mode_synthetic_stored.mappings._source.mode: synthetic }
+
+---
+modify source mapping from synthetic to disabled after index creation:
+  - do:
+      indices.create:
+        index: test_modify_source_mode_synthetic_disabled
+        body:
+          settings:
+            index:
+              mapping.source.mode: synthetic
+
+  - do:
+      indices.put_mapping:
+        index: test_modify_source_mode_synthetic_disabled
+        body:
+          _source:
+            mode: disabled
+  - is_true: acknowledged
+
+  - do:
+      indices.get_mapping:
+        index: test_modify_source_mode_synthetic_disabled
+  - match: { test_modify_source_mode_synthetic_disabled.mappings._source.mode: synthetic }
+
+---
+modify logsdb index source mode to disabled after index creation:
+  - do:
+      indices.create:
+        index: test_modify_logsdb_disabled_after_creation
+        body:
+          settings:
+            index:
+              mode: logsdb
+
+  - do:
+      catch: bad_request
+      indices.put_mapping:
+        index: test_modify_logsdb_disabled_after_creation
+        body:
+          _source:
+            mode: disabled
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: Indices with with index mode [logsdb] only support synthetic source" }
+
+---
+modify logsdb index source mode to stored after index creation:
+  - do:
+      indices.create:
+        index: test_modify_logsdb_stored_after_creation
+        body:
+          settings:
+            index:
+              mode: logsdb
+
+  - do:
+      catch: bad_request
+      indices.put_mapping:
+        index: test_modify_logsdb_stored_after_creation
+        body:
+          _source:
+            mode: stored
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: Indices with with index mode [logsdb] only support synthetic source" }
+
+---
+modify time_series index source mode to disabled after index creation:
+  - do:
+      indices.create:
+        index: test_modify_time_series_disabled_after_creation
+        body:
+          settings:
+            index:
+              mode: time_series
+              routing_path: [ keyword ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            properties:
+              keyword:
+                type: keyword
+                time_series_dimension: true
+
+  - do:
+      catch: bad_request
+      indices.put_mapping:
+        index: test_modify_time_series_disabled_after_creation
+        body:
+          _source:
+            mode: disabled
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: time series indices only support synthetic source" }
+
+---
+modify time_series index source mode to stored after index creation:
+  - do:
+      indices.create:
+        index: test_modify_time_series_stored_after_creation
+        body:
+          settings:
+            index:
+              mode: time_series
+              routing_path: [ keyword ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            properties:
+              keyword:
+                type: keyword
+                time_series_dimension: true
+
+  - do:
+      catch: bad_request
+      indices.put_mapping:
+        index: test_modify_time_series_stored_after_creation
+        body:
+          _source:
+            mode: stored
+  - match: { error.type: "mapper_parsing_exception" }
+  - match: { error.reason: "Failed to parse mapping: time series indices only support synthetic source" }