Skip to content

Commit

Permalink
Prevent searchable snapshots indices to be shrunk/split (#75227) (#75289
Browse files Browse the repository at this point in the history
)

Today if we try to shrink or to split a searchable snapshot
index using the Resize API a new index will be created
but can't be assigned, and even if it was assigned it won't
work as the number of shards can't be changed and must
always match the number of shards from the snapshot.

This commit adds some verification to prevent a snapshot
backed indices to be resized and if an attempt is made,
throw a better error message.

Note that cloning is supported since #56595 and in this
change we make sure that it is only used to convert the
searchable snapshot index back to a regular index.

Relates #74977 (comment)
  • Loading branch information
tlrx authored Jul 13, 2021
1 parent 714ccf3 commit ec7f27e
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
import static org.elasticsearch.cluster.metadata.IndexMetadata.SETTING_NUMBER_OF_SHARDS;
import static org.elasticsearch.cluster.metadata.MetadataCreateDataStreamService.validateTimestampFieldMapping;
import static org.elasticsearch.cluster.metadata.MetadataIndexTemplateService.resolveSettings;
import static org.elasticsearch.index.IndexModule.INDEX_RECOVERY_TYPE_SETTING;
import static org.elasticsearch.index.IndexModule.INDEX_STORE_TYPE_SETTING;

/**
* Service responsible for submitting create index requests
Expand Down Expand Up @@ -1140,6 +1142,9 @@ private static List<String> validateIndexCustomPath(Settings settings, @Nullable
*/
static List<String> validateShrinkIndex(ClusterState state, String sourceIndex, String targetIndexName, Settings targetIndexSettings) {
IndexMetadata sourceMetadata = validateResize(state, sourceIndex, targetIndexName, targetIndexSettings);
if ("snapshot".equals(INDEX_STORE_TYPE_SETTING.get(sourceMetadata.getSettings()))) {
throw new IllegalArgumentException("can't shrink searchable snapshot index [" + sourceIndex + ']');
}
assert INDEX_NUMBER_OF_SHARDS_SETTING.exists(targetIndexSettings);
IndexMetadata.selectShrinkShards(0, sourceMetadata, INDEX_NUMBER_OF_SHARDS_SETTING.get(targetIndexSettings));

Expand Down Expand Up @@ -1171,6 +1176,9 @@ static List<String> validateShrinkIndex(ClusterState state, String sourceIndex,

static void validateSplitIndex(ClusterState state, String sourceIndex, String targetIndexName, Settings targetIndexSettings) {
IndexMetadata sourceMetadata = validateResize(state, sourceIndex, targetIndexName, targetIndexSettings);
if ("snapshot".equals(INDEX_STORE_TYPE_SETTING.get(sourceMetadata.getSettings()))) {
throw new IllegalArgumentException("can't split searchable snapshot index [" + sourceIndex + ']');
}
IndexMetadata.selectSplitShard(0, sourceMetadata, IndexMetadata.INDEX_NUMBER_OF_SHARDS_SETTING.get(targetIndexSettings));
if (sourceMetadata.getCreationVersion().before(Version.V_6_0_0_alpha1)) {
// ensure we have a single type since this would make the splitting code considerably more complex
Expand All @@ -1182,6 +1190,15 @@ static void validateSplitIndex(ClusterState state, String sourceIndex, String ta

static void validateCloneIndex(ClusterState state, String sourceIndex, String targetIndexName, Settings targetIndexSettings) {
IndexMetadata sourceMetadata = validateResize(state, sourceIndex, targetIndexName, targetIndexSettings);
if ("snapshot".equals(INDEX_STORE_TYPE_SETTING.get(sourceMetadata.getSettings()))) {
for (Setting<?> nonCloneableSetting : Arrays.asList(INDEX_STORE_TYPE_SETTING, INDEX_RECOVERY_TYPE_SETTING)) {
if (nonCloneableSetting.exists(targetIndexSettings) == false) {
throw new IllegalArgumentException("can't clone searchable snapshot index [" + sourceIndex + "]; setting ["
+ nonCloneableSetting.getKey()
+ "] should be overridden");
}
}
}
IndexMetadata.selectCloneShard(0, sourceMetadata, INDEX_NUMBER_OF_SHARDS_SETTING.get(targetIndexSettings));
}

Expand All @@ -1201,7 +1218,6 @@ static IndexMetadata validateResize(ClusterState state, String sourceIndex, Stri
throw new IllegalArgumentException(String.format(Locale.ROOT, "cannot resize the write index [%s] for data stream [%s]",
sourceIndex, source.getParentDataStream().getName()));
}

// ensure index is read-only
if (state.blocks().indexBlocked(ClusterBlockLevel.WRITE, sourceIndex) == false) {
throw new IllegalStateException("index " + sourceIndex + " must be read-only to resize index. use \"index.blocks.write=true\"");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.searchablesnapshots;

import org.elasticsearch.action.admin.indices.shrink.ResizeType;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexModule;
import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.xpack.cluster.routing.allocation.DataTierAllocationDecider;
import org.elasticsearch.xpack.core.DataTier;
import org.junit.After;
import org.junit.Before;

import static java.util.Collections.singletonList;
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_NUMBER_OF_REPLICAS_SETTING;
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_NUMBER_OF_ROUTING_SHARDS_SETTING;
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_NUMBER_OF_SHARDS_SETTING;
import static org.elasticsearch.index.IndexSettings.INDEX_SOFT_DELETES_SETTING;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotRequest.Storage;
import static org.hamcrest.Matchers.equalTo;

@ESIntegTestCase.ClusterScope(numDataNodes = 1)
public class SearchableSnapshotsResizeIntegTests extends BaseFrozenSearchableSnapshotsIntegTestCase {

@Before
@Override
public void setUp() throws Exception {
super.setUp();
createRepository("repository", FsRepository.TYPE);
assertAcked(
prepareCreate(
"index",
Settings.builder()
.put(INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 0)
.put(INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 2)
.put(INDEX_NUMBER_OF_ROUTING_SHARDS_SETTING.getKey(), 4)
.put(INDEX_SOFT_DELETES_SETTING.getKey(), true)
)
);
indexRandomDocs("index", scaledRandomIntBetween(0, 1_000));
createSnapshot("repository", "snapshot", singletonList("index"));
assertAcked(client().admin().indices().prepareDelete("index"));
mountSnapshot("repository", "snapshot", "index", "mounted-index", Settings.EMPTY, randomFrom(Storage.values()));
ensureGreen("mounted-index");
}

@After
@Override
public void tearDown() throws Exception {
assertAcked(client().admin().indices().prepareDelete("mounted-*"));
assertAcked(client().admin().cluster().prepareDeleteSnapshot("repository", "snapshot").get());
assertAcked(client().admin().cluster().prepareDeleteRepository("repository"));
super.tearDown();
}

public void testShrinkSearchableSnapshotIndex() {
final IllegalArgumentException exception = expectThrows(
IllegalArgumentException.class,
() -> client().admin()
.indices()
.prepareResizeIndex("mounted-index", "shrunk-index")
.setResizeType(ResizeType.SHRINK)
.setSettings(indexSettingsNoReplicas(1).build())
.get()
);
assertThat(exception.getMessage(), equalTo("can't shrink searchable snapshot index [mounted-index]"));
}

public void testSplitSearchableSnapshotIndex() {
final IllegalArgumentException exception = expectThrows(
IllegalArgumentException.class,
() -> client().admin()
.indices()
.prepareResizeIndex("mounted-index", "split-index")
.setResizeType(ResizeType.SPLIT)
.setSettings(indexSettingsNoReplicas(4).build())
.get()
);
assertThat(exception.getMessage(), equalTo("can't split searchable snapshot index [mounted-index]"));
}

public void testCloneSearchableSnapshotIndex() {
IllegalArgumentException exception = expectThrows(
IllegalArgumentException.class,
() -> client().admin().indices().prepareResizeIndex("mounted-index", "cloned-index").setResizeType(ResizeType.CLONE).get()
);
assertThat(
exception.getMessage(),
equalTo("can't clone searchable snapshot index [mounted-index]; setting [index.store.type] should be overridden")
);

exception = expectThrows(
IllegalArgumentException.class,
() -> client().admin()
.indices()
.prepareResizeIndex("mounted-index", "cloned-index")
.setResizeType(ResizeType.CLONE)
.setSettings(Settings.builder().putNull(IndexModule.INDEX_STORE_TYPE_SETTING.getKey()).build())
.get()
);
assertThat(
exception.getMessage(),
equalTo("can't clone searchable snapshot index [mounted-index]; setting [index.recovery.type] should be overridden")
);

assertAcked(
client().admin()
.indices()
.prepareResizeIndex("mounted-index", "cloned-index")
.setResizeType(ResizeType.CLONE)
.setSettings(
Settings.builder()
.putNull(IndexModule.INDEX_STORE_TYPE_SETTING.getKey())
.putNull(IndexModule.INDEX_RECOVERY_TYPE_SETTING.getKey())
.put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, DataTier.DATA_HOT)
.put(INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 0)
.build()
)
);
ensureGreen("cloned-index");
assertAcked(client().admin().indices().prepareDelete("cloned-index"));
}
}

0 comments on commit ec7f27e

Please sign in to comment.