Prevent snapshots to be mounted as system indices (#61517) (#61727)

System indices can be snapshotted and are therefore potential candidates 
to be mounted as searchable snapshot indices. As of today nothing 
prevents a snapshot to be mounted under an index name starting with . 
and this can lead to conflicting situations because searchable snapshot 
indices are read-only and Elasticsearch expects some system indices 
to be writable; because searchable snapshot indices will soon use an 
internal system index (#60522) to speed up recoveries and we should
prevent the system index to be itself a searchable snapshot index 
(leading to some deadlock situation for recovery).

This commit introduces a changes to prevent snapshots to be mounted 
as a system index.
This commit is contained in:
Tanguy Leroux 2020-09-01 11:13:28 +02:00 committed by GitHub
parent 92eb6e7844
commit 787dfda4c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 8 deletions

View File

@ -673,6 +673,7 @@ public class Node implements Closeable {
b.bind(RerouteService.class).toInstance(rerouteService); b.bind(RerouteService.class).toInstance(rerouteService);
b.bind(ShardLimitValidator.class).toInstance(shardLimitValidator); b.bind(ShardLimitValidator.class).toInstance(shardLimitValidator);
b.bind(FsHealthService.class).toInstance(fsHealthService); b.bind(FsHealthService.class).toInstance(fsHealthService);
b.bind(SystemIndices.class).toInstance(systemIndices);
} }
); );
injector = modules.createInjector(); injector = modules.createInjector();

View File

@ -65,6 +65,7 @@ import org.elasticsearch.plugins.PersistentTaskPlugin;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.RepositoryPlugin; import org.elasticsearch.plugins.RepositoryPlugin;
import org.elasticsearch.plugins.ScriptPlugin; import org.elasticsearch.plugins.ScriptPlugin;
import org.elasticsearch.plugins.SystemIndexPlugin;
import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.repositories.RepositoriesService;
import org.elasticsearch.repositories.Repository; import org.elasticsearch.repositories.Repository;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
@ -102,7 +103,8 @@ import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
public class LocalStateCompositeXPackPlugin extends XPackPlugin implements ScriptPlugin, ActionPlugin, IngestPlugin, NetworkPlugin, public class LocalStateCompositeXPackPlugin extends XPackPlugin implements ScriptPlugin, ActionPlugin, IngestPlugin, NetworkPlugin,
ClusterPlugin, DiscoveryPlugin, MapperPlugin, AnalysisPlugin, PersistentTaskPlugin, EnginePlugin, IndexStorePlugin { ClusterPlugin, DiscoveryPlugin, MapperPlugin, AnalysisPlugin, PersistentTaskPlugin, EnginePlugin, IndexStorePlugin,
SystemIndexPlugin {
private XPackLicenseState licenseState; private XPackLicenseState licenseState;
private SSLService sslService; private SSLService sslService;

View File

@ -24,6 +24,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.indices.SystemIndices;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.repositories.IndexId; import org.elasticsearch.repositories.IndexId;
import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.repositories.RepositoriesService;
@ -62,6 +63,7 @@ public class TransportMountSearchableSnapshotAction extends TransportMasterNodeA
private final Client client; private final Client client;
private final RepositoriesService repositoriesService; private final RepositoriesService repositoriesService;
private final XPackLicenseState licenseState; private final XPackLicenseState licenseState;
private final SystemIndices systemIndices;
@Inject @Inject
public TransportMountSearchableSnapshotAction( public TransportMountSearchableSnapshotAction(
@ -72,7 +74,8 @@ public class TransportMountSearchableSnapshotAction extends TransportMasterNodeA
RepositoriesService repositoriesService, RepositoriesService repositoriesService,
ActionFilters actionFilters, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver, IndexNameExpressionResolver indexNameExpressionResolver,
XPackLicenseState licenseState XPackLicenseState licenseState,
SystemIndices systemIndices
) { ) {
super( super(
MountSearchableSnapshotAction.NAME, MountSearchableSnapshotAction.NAME,
@ -86,6 +89,7 @@ public class TransportMountSearchableSnapshotAction extends TransportMasterNodeA
this.client = client; this.client = client;
this.repositoriesService = repositoriesService; this.repositoriesService = repositoriesService;
this.licenseState = Objects.requireNonNull(licenseState); this.licenseState = Objects.requireNonNull(licenseState);
this.systemIndices = Objects.requireNonNull(systemIndices);
} }
@Override @Override
@ -130,6 +134,11 @@ public class TransportMountSearchableSnapshotAction extends TransportMasterNodeA
) { ) {
SearchableSnapshots.ensureValidLicense(licenseState); SearchableSnapshots.ensureValidLicense(licenseState);
final String mountedIndexName = request.mountedIndexName();
if (systemIndices.isSystemIndex(mountedIndexName)) {
throw new ElasticsearchException("system index [{}] cannot be mounted as searchable snapshots", mountedIndexName);
}
final String repoName = request.repositoryName(); final String repoName = request.repositoryName();
final String snapName = request.snapshotName(); final String snapName = request.snapshotName();
final String indexName = request.snapshotIndexName(); final String indexName = request.snapshotIndexName();
@ -166,7 +175,7 @@ public class TransportMountSearchableSnapshotAction extends TransportMasterNodeA
.indices(indexName) .indices(indexName)
// Always rename it to the desired mounted index name // Always rename it to the desired mounted index name
.renamePattern(".+") .renamePattern(".+")
.renameReplacement(request.mountedIndexName()) .renameReplacement(mountedIndexName)
// Pass through index settings, adding the index-level settings required to use searchable snapshots // Pass through index settings, adding the index-level settings required to use searchable snapshots
.indexSettings( .indexSettings(
Settings.builder() Settings.builder()

View File

@ -7,24 +7,32 @@
package org.elasticsearch.xpack.searchablesnapshots; package org.elasticsearch.xpack.searchablesnapshots;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.SystemIndexDescriptor;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin; import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection;
public class LocalStateSearchableSnapshots extends LocalStateCompositeXPackPlugin { public class LocalStateSearchableSnapshots extends LocalStateCompositeXPackPlugin {
private final SearchableSnapshots plugin;
public LocalStateSearchableSnapshots(final Settings settings, final Path configPath) { public LocalStateSearchableSnapshots(final Settings settings, final Path configPath) {
super(settings, configPath); super(settings, configPath);
LocalStateSearchableSnapshots thisVar = this; this.plugin = new SearchableSnapshots(settings) {
plugins.add(new SearchableSnapshots(settings) {
@Override @Override
protected XPackLicenseState getLicenseState() { protected XPackLicenseState getLicenseState() {
return thisVar.getLicenseState(); return LocalStateSearchableSnapshots.this.getLicenseState();
} }
}); };
plugins.add(plugin);
}
@Override
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
return plugin.getSystemIndexDescriptors(settings);
} }
} }

View File

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.searchablesnapshots;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.OriginSettingClient;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.SystemIndexDescriptor;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.SystemIndexPlugin;
import org.elasticsearch.xpack.core.ClientHelper;
import org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotAction;
import org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
public class SearchableSnapshotsSystemIndicesIntegTests extends BaseSearchableSnapshotsIntegTestCase {
@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
final List<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins());
plugins.add(TestSystemIndexPlugin.class);
return plugins;
}
public void testCannotMountSystemIndex() throws Exception {
executeTest(TestSystemIndexPlugin.INDEX_NAME, new OriginSettingClient(client(), ClientHelper.SEARCHABLE_SNAPSHOTS_ORIGIN));
}
public void testCannotMountSnapshotBlobCacheIndex() throws Exception {
executeTest(SearchableSnapshotsConstants.SNAPSHOT_BLOB_CACHE_INDEX, client());
}
private void executeTest(final String indexName, final Client client) throws Exception {
final boolean isHidden = randomBoolean();
createAndPopulateIndex(indexName, Settings.builder().put(IndexMetadata.SETTING_INDEX_HIDDEN, isHidden));
final String repositoryName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
createRepo(repositoryName);
final String snapshotName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
final CreateSnapshotResponse snapshotResponse = client.admin()
.cluster()
.prepareCreateSnapshot(repositoryName, snapshotName)
.setIndices(indexName)
.setWaitForCompletion(true)
.get();
final int numPrimaries = getNumShards(indexName).numPrimaries;
assertThat(snapshotResponse.getSnapshotInfo().successfulShards(), equalTo(numPrimaries));
assertThat(snapshotResponse.getSnapshotInfo().failedShards(), equalTo(0));
if (randomBoolean()) {
assertAcked(client.admin().indices().prepareClose(indexName));
} else {
assertAcked(client.admin().indices().prepareDelete(indexName));
}
final MountSearchableSnapshotRequest mountRequest = new MountSearchableSnapshotRequest(
indexName,
repositoryName,
snapshotName,
indexName,
Settings.builder().put(IndexMetadata.SETTING_INDEX_HIDDEN, randomBoolean()).build(),
Strings.EMPTY_ARRAY,
true
);
final ElasticsearchException exception = expectThrows(
ElasticsearchException.class,
() -> client.execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).actionGet()
);
assertThat(exception.getMessage(), containsString("system index [" + indexName + "] cannot be mounted as searchable snapshots"));
}
public static class TestSystemIndexPlugin extends Plugin implements SystemIndexPlugin {
static final String INDEX_NAME = ".test-system-index";
@Override
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
return org.elasticsearch.common.collect.List.of(
new SystemIndexDescriptor(INDEX_NAME, "System index for [" + getTestClass().getName() + ']')
);
}
}
}