Browse Source

Prohibit changes to index mode, source, and sort settings during resize (#115812) (#115971)

Relates to #115811, but applies to resize requests.

The index.mode, source.mode, and index.sort.* settings cannot be 
modified during resize, as this may lead to data corruption or issues
retrieving _source. This change enforces a restriction on modifying
these settings during resize. While a fine-grained check could allow
equivalent settings, it seems simpler and safer to reject resize
requests if any of these settings are specified.
Nhat Nguyen 11 months ago
parent
commit
6baa7c3132

+ 5 - 0
docs/changelog/115812.yaml

@@ -0,0 +1,5 @@
+pr: 115812
+summary: "Prohibit changes to index mode, source, and sort settings during resize"
+area: Logs
+type: bug
+issues: []

+ 47 - 0
server/src/internalClusterTest/java/org/elasticsearch/action/admin/indices/create/CloneIndexIT.java

@@ -12,6 +12,7 @@ import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
 import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
 import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
+import org.elasticsearch.common.ValidationException;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.IndexVersion;
 import org.elasticsearch.index.query.TermsQueryBuilder;
@@ -20,9 +21,12 @@ import org.elasticsearch.test.ESIntegTestCase;
 import org.elasticsearch.test.index.IndexVersionUtils;
 import org.elasticsearch.xcontent.XContentType;
 
+import java.util.List;
+
 import static org.elasticsearch.action.admin.indices.create.ShrinkIndexIT.assertNoResizeSourceIndexSettings;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
+import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
 
 public class CloneIndexIT extends ESIntegTestCase {
@@ -109,4 +113,47 @@ public class CloneIndexIT extends ESIntegTestCase {
 
     }
 
+    public void testResizeChangeIndexMode() {
+        prepareCreate("source").setSettings(indexSettings(1, 0)).setMapping("@timestamp", "type=date", "host.name", "type=keyword").get();
+        updateIndexSettings(Settings.builder().put("index.blocks.write", true), "source");
+        List<Settings> indexSettings = List.of(
+            Settings.builder().put("index.mode", "logsdb").build(),
+            Settings.builder().put("index.mode", "time_series").put("index.routing_path", "host.name").build(),
+            Settings.builder().put("index.mode", "lookup").build()
+        );
+        for (Settings settings : indexSettings) {
+            IllegalArgumentException error = expectThrows(IllegalArgumentException.class, () -> {
+                indicesAdmin().prepareResizeIndex("source", "target").setResizeType(ResizeType.CLONE).setSettings(settings).get();
+            });
+            assertThat(error.getMessage(), equalTo("can't change setting [index.mode] during resize"));
+        }
+    }
+
+    public void testResizeChangeSyntheticSource() {
+        prepareCreate("source").setSettings(indexSettings(between(1, 5), 0))
+            .setMapping("@timestamp", "type=date", "host.name", "type=keyword")
+            .get();
+        updateIndexSettings(Settings.builder().put("index.blocks.write", true), "source");
+        IllegalArgumentException error = expectThrows(IllegalArgumentException.class, () -> {
+            indicesAdmin().prepareResizeIndex("source", "target")
+                .setResizeType(ResizeType.CLONE)
+                .setSettings(Settings.builder().put("index.mapping.source.mode", "synthetic").putNull("index.blocks.write").build())
+                .get();
+        });
+        assertThat(error.getMessage(), containsString("can't change setting [index.mapping.source.mode] during resize"));
+    }
+
+    public void testResizeChangeIndexSorts() {
+        prepareCreate("source").setSettings(indexSettings(between(1, 5), 0))
+            .setMapping("@timestamp", "type=date", "host.name", "type=keyword")
+            .get();
+        updateIndexSettings(Settings.builder().put("index.blocks.write", true), "source");
+        ValidationException error = expectThrows(ValidationException.class, () -> {
+            indicesAdmin().prepareResizeIndex("source", "target")
+                .setResizeType(ResizeType.CLONE)
+                .setSettings(Settings.builder().putList("index.sort.field", List.of("@timestamp")).build())
+                .get();
+        });
+        assertThat(error.getMessage(), containsString("can't override index sort when resizing an index"));
+    }
 }

+ 1 - 1
server/src/internalClusterTest/java/org/elasticsearch/index/LookupIndexModeIT.java

@@ -198,7 +198,7 @@ public class LookupIndexModeIT extends ESIntegTestCase {
             IllegalArgumentException.class,
             () -> client().admin().indices().execute(ResizeAction.INSTANCE, shrink).actionGet()
         );
-        assertThat(error.getMessage(), equalTo("can't change index.mode of index [regular-1] from [standard] to [lookup]"));
+        assertThat(error.getMessage(), equalTo("can't change setting [index.mode] during resize"));
     }
 
     public void testDoNotOverrideAutoExpandReplicas() {

+ 14 - 7
server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java

@@ -62,11 +62,13 @@ import org.elasticsearch.index.IndexService;
 import org.elasticsearch.index.IndexSettingProvider;
 import org.elasticsearch.index.IndexSettingProviders;
 import org.elasticsearch.index.IndexSettings;
+import org.elasticsearch.index.IndexSortConfig;
 import org.elasticsearch.index.IndexVersion;
 import org.elasticsearch.index.IndexVersions;
 import org.elasticsearch.index.mapper.DocumentMapper;
 import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.mapper.MapperService.MergeReason;
+import org.elasticsearch.index.mapper.SourceFieldMapper;
 import org.elasticsearch.index.query.SearchExecutionContext;
 import org.elasticsearch.index.shard.IndexLongFieldRange;
 import org.elasticsearch.indices.IndexCreationException;
@@ -1567,6 +1569,15 @@ public class MetadataCreateIndexService {
         IndexMetadata.selectCloneShard(0, sourceMetadata, INDEX_NUMBER_OF_SHARDS_SETTING.get(targetIndexSettings));
     }
 
+    private static final Set<String> UNMODIFIABLE_SETTINGS_DURING_RESIZE = Set.of(
+        IndexSettings.MODE.getKey(),
+        SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(),
+        IndexSortConfig.INDEX_SORT_FIELD_SETTING.getKey(),
+        IndexSortConfig.INDEX_SORT_ORDER_SETTING.getKey(),
+        IndexSortConfig.INDEX_SORT_MODE_SETTING.getKey(),
+        IndexSortConfig.INDEX_SORT_MISSING_SETTING.getKey()
+    );
+
     static IndexMetadata validateResize(
         Metadata metadata,
         ClusterBlocks clusterBlocks,
@@ -1604,13 +1615,9 @@ public class MetadataCreateIndexService {
             // of if the source shards are divisible by the number of target shards
             IndexMetadata.getRoutingFactor(sourceMetadata.getNumberOfShards(), INDEX_NUMBER_OF_SHARDS_SETTING.get(targetIndexSettings));
         }
-        if (targetIndexSettings.hasValue(IndexSettings.MODE.getKey())) {
-            IndexMode oldMode = Objects.requireNonNullElse(sourceMetadata.getIndexMode(), IndexMode.STANDARD);
-            IndexMode newMode = IndexSettings.MODE.get(targetIndexSettings);
-            if (newMode != oldMode) {
-                throw new IllegalArgumentException(
-                    "can't change index.mode of index [" + sourceIndex + "] from [" + oldMode + "] to [" + newMode + "]"
-                );
+        for (String setting : UNMODIFIABLE_SETTINGS_DURING_RESIZE) {
+            if (targetIndexSettings.hasValue(setting)) {
+                throw new IllegalArgumentException("can't change setting [" + setting + "] during resize");
             }
         }
         return sourceMetadata;