diff --git a/server/src/main/java/org/elasticsearch/node/Node.java b/server/src/main/java/org/elasticsearch/node/Node.java index ee663378f9b27..55df41ac89433 100644 --- a/server/src/main/java/org/elasticsearch/node/Node.java +++ b/server/src/main/java/org/elasticsearch/node/Node.java @@ -653,6 +653,7 @@ protected Node(final Environment initialEnvironment, b.bind(RerouteService.class).toInstance(rerouteService); b.bind(ShardLimitValidator.class).toInstance(shardLimitValidator); b.bind(FsHealthService.class).toInstance(fsHealthService); + b.bind(SystemIndices.class).toInstance(systemIndices); } ); injector = modules.createInjector(); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java index dc6e3a59a7ce4..54555c2639968 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java @@ -64,6 +64,7 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.RepositoryPlugin; import org.elasticsearch.plugins.ScriptPlugin; +import org.elasticsearch.plugins.SystemIndexPlugin; import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.repositories.Repository; import org.elasticsearch.rest.RestController; @@ -101,7 +102,8 @@ import static java.util.stream.Collectors.toList; 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 SSLService sslService; diff --git a/x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/TransportMountSearchableSnapshotAction.java b/x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/TransportMountSearchableSnapshotAction.java index ced0c03cf642a..c2c06a1281682 100644 --- a/x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/TransportMountSearchableSnapshotAction.java +++ b/x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/TransportMountSearchableSnapshotAction.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.indices.SystemIndices; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.repositories.IndexId; import org.elasticsearch.repositories.RepositoriesService; @@ -63,6 +64,7 @@ public class TransportMountSearchableSnapshotAction extends TransportMasterNodeA private final Client client; private final RepositoriesService repositoriesService; private final XPackLicenseState licenseState; + private final SystemIndices systemIndices; @Inject public TransportMountSearchableSnapshotAction( @@ -73,7 +75,8 @@ public TransportMountSearchableSnapshotAction( RepositoriesService repositoriesService, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver, - XPackLicenseState licenseState + XPackLicenseState licenseState, + SystemIndices systemIndices ) { super( MountSearchableSnapshotAction.NAME, @@ -87,6 +90,7 @@ public TransportMountSearchableSnapshotAction( this.client = client; this.repositoriesService = repositoriesService; this.licenseState = Objects.requireNonNull(licenseState); + this.systemIndices = Objects.requireNonNull(systemIndices); } @Override @@ -132,6 +136,11 @@ protected void masterOperation( ) { 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 snapName = request.snapshotName(); final String indexName = request.snapshotIndexName(); @@ -168,7 +177,7 @@ protected void masterOperation( .indices(indexName) // Always rename it to the desired mounted index name .renamePattern(".+") - .renameReplacement(request.mountedIndexName()) + .renameReplacement(mountedIndexName) // Pass through index settings, adding the index-level settings required to use searchable snapshots .indexSettings( Settings.builder() diff --git a/x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/xpack/searchablesnapshots/LocalStateSearchableSnapshots.java b/x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/xpack/searchablesnapshots/LocalStateSearchableSnapshots.java index 88615d02ebb1a..8140663b3f856 100644 --- a/x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/xpack/searchablesnapshots/LocalStateSearchableSnapshots.java +++ b/x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/xpack/searchablesnapshots/LocalStateSearchableSnapshots.java @@ -7,24 +7,32 @@ package org.elasticsearch.xpack.searchablesnapshots; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.indices.SystemIndexDescriptor; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin; import java.nio.file.Path; +import java.util.Collection; public class LocalStateSearchableSnapshots extends LocalStateCompositeXPackPlugin { + private final SearchableSnapshots plugin; + public LocalStateSearchableSnapshots(final Settings settings, final Path configPath) { super(settings, configPath); - LocalStateSearchableSnapshots thisVar = this; - - plugins.add(new SearchableSnapshots(settings) { + this.plugin = new SearchableSnapshots(settings) { @Override protected XPackLicenseState getLicenseState() { - return thisVar.getLicenseState(); + return LocalStateSearchableSnapshots.this.getLicenseState(); } - }); + }; + plugins.add(plugin); + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + return plugin.getSystemIndexDescriptors(settings); } } diff --git a/x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsSystemIndicesIntegTests.java b/x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsSystemIndicesIntegTests.java new file mode 100644 index 0000000000000..8015e4ee1b892 --- /dev/null +++ b/x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsSystemIndicesIntegTests.java @@ -0,0 +1,100 @@ +/* + * 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> nodePlugins() { + final List> 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 getSystemIndexDescriptors(Settings settings) { + return List.of(new SystemIndexDescriptor(INDEX_NAME, "System index for [" + getTestClass().getName() + ']')); + } + } +}