From e677d67f9fc5f97d87e9c331f27c79a856e1f18f Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Wed, 24 Mar 2021 10:58:31 +0100 Subject: [PATCH] Prevent snapshot backed indices to be followed using CCR Today nothing prevents CCR's auto-follow patterns to pick up snapshot backed indices on a remote cluster. This can lead to various errors on the follower cluster that are not obvious to troubleshoot for a user (ex: multiple engine factories provided). This commit adds verifications to CCR to make it fail faster when a user tries to follow an index that is backed by a snapshot, providing a more obvious error message. Backport of #70580 --- .../test/rest/ESRestTestCase.java | 43 ++++++- .../plugin/ccr/qa/multi-cluster/build.gradle | 4 + .../elasticsearch/xpack/ccr/AutoFollowIT.java | 108 ++++++++++++++++-- .../xpack/ccr/FollowIndexIT.java | 46 +++++++- .../ccr/action/AutoFollowCoordinator.java | 14 +++ .../ccr/action/TransportPutFollowAction.java | 6 + .../action/TransportResumeFollowAction.java | 9 ++ 7 files changed, 213 insertions(+), 17 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java index 6847609e09972..57ae888ac12e6 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java @@ -11,6 +11,7 @@ import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; @@ -1237,8 +1238,12 @@ protected static void createIndex(String name, Settings settings, String mapping } protected static void deleteIndex(String name) throws IOException { + deleteIndex(client(), name); + } + + protected static void deleteIndex(RestClient client, String name) throws IOException { Request request = new Request("DELETE", "/" + name); - client().performRequest(request); + client.performRequest(request); } protected static void updateIndexSettings(String index, Settings.Builder settings) throws IOException { @@ -1357,19 +1362,38 @@ protected static Map responseAsMap(Response response) throws IOE } protected static void registerRepository(String repository, String type, boolean verify, Settings settings) throws IOException { + registerRepository(client(), repository, type, verify, settings); + } + + protected static void registerRepository( + RestClient client, + String repository, + String type, + boolean verify, + Settings settings + ) throws IOException { final Request request = new Request(HttpPut.METHOD_NAME, "_snapshot/" + repository); request.addParameter("verify", Boolean.toString(verify)); request.setJsonEntity(Strings.toString(new PutRepositoryRequest(repository).type(type).settings(settings))); - final Response response = client().performRequest(request); + final Response response = client.performRequest(request); assertAcked("Failed to create repository [" + repository + "] of type [" + type + "]: " + response, response); } protected static void createSnapshot(String repository, String snapshot, boolean waitForCompletion) throws IOException { + createSnapshot(client(), repository, snapshot, waitForCompletion); + } + + protected static void createSnapshot( + RestClient client, + String repository, + String snapshot, + boolean waitForCompletion + ) throws IOException { final Request request = new Request(HttpPut.METHOD_NAME, "_snapshot/" + repository + '/' + snapshot); request.addParameter("wait_for_completion", Boolean.toString(waitForCompletion)); - final Response response = client().performRequest(request); + final Response response = client.performRequest(request); assertThat( "Failed to create snapshot [" + snapshot + "] in repository [" + repository + "]: " + response, response.getStatusLine().getStatusCode(), @@ -1389,6 +1413,19 @@ protected static void restoreSnapshot(String repository, String snapshot, boolea ); } + protected static void deleteSnapshot(String repository, String snapshot, boolean ignoreMissing) throws IOException { + deleteSnapshot(client(), repository, snapshot, ignoreMissing); + } + + protected static void deleteSnapshot(RestClient client, String repository, String snapshot, boolean ignoreMissing) throws IOException { + final Request request = new Request(HttpDelete.METHOD_NAME, "_snapshot/" + repository + '/' + snapshot); + if (ignoreMissing) { + request.addParameter("ignore", "404"); + } + final Response response = client.performRequest(request); + assertThat(response.getStatusLine().getStatusCode(), ignoreMissing ? anyOf(equalTo(200), equalTo(404)) : equalTo(200)); + } + @SuppressWarnings("unchecked") private static void assertAcked(String message, Response response) throws IOException { final int responseStatusCode = response.getStatusLine().getStatusCode(); diff --git a/x-pack/plugin/ccr/qa/multi-cluster/build.gradle b/x-pack/plugin/ccr/qa/multi-cluster/build.gradle index 0028f679c528e..d33401c3db27f 100644 --- a/x-pack/plugin/ccr/qa/multi-cluster/build.gradle +++ b/x-pack/plugin/ccr/qa/multi-cluster/build.gradle @@ -13,6 +13,7 @@ testClusters { 'leader-cluster' { testDistribution = 'DEFAULT' setting 'xpack.license.self_generated.type', 'trial' + setting 'path.repo', "${buildDir}/cluster/shared/repo/leader-cluster" } 'middle-cluster' { testDistribution = 'DEFAULT' @@ -25,12 +26,14 @@ testClusters { tasks.register("leader-cluster", RestIntegTestTask) { mustRunAfter("precommit") systemProperty 'tests.target_cluster', 'leader' + systemProperty 'tests.leader_cluster_repository_path', "${buildDir}/cluster/shared/repo/leader-cluster" } tasks.register("middle-cluster", RestIntegTestTask) { dependsOn "leader-cluster" useCluster testClusters."leader-cluster" systemProperty 'tests.target_cluster', 'middle' + systemProperty 'tests.leader_cluster_repository_path', "${buildDir}/cluster/shared/repo/leader-cluster" nonInputProperties.systemProperty 'tests.leader_host', "${-> testClusters.named('leader-cluster').get().getAllHttpSocketURI().get(0)}" } @@ -41,6 +44,7 @@ tasks.register('follow-cluster', RestIntegTestTask) { useCluster testClusters."leader-cluster" useCluster testClusters."middle-cluster" systemProperty 'tests.target_cluster', 'follow' + systemProperty 'tests.leader_cluster_repository_path', "${buildDir}/cluster/shared/repo/leader-cluster" nonInputProperties.systemProperty 'tests.leader_host', "${-> testClusters.named('leader-cluster').get().getAllHttpSocketURI().get(0)}" nonInputProperties.systemProperty 'tests.middle_host', diff --git a/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/AutoFollowIT.java b/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/AutoFollowIT.java index 5cb8e1844837c..b0112f29554e9 100644 --- a/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/AutoFollowIT.java +++ b/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/AutoFollowIT.java @@ -7,14 +7,17 @@ package org.elasticsearch.xpack.ccr; +import org.apache.http.client.methods.HttpPost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; import org.elasticsearch.client.RestClient; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ObjectPath; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.rest.RestStatus; import java.io.IOException; import java.text.SimpleDateFormat; @@ -23,12 +26,17 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +import static org.elasticsearch.common.xcontent.ObjectPath.eval; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.emptyOrNullString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; public class AutoFollowIT extends ESCCRRestTestCase { @@ -37,7 +45,7 @@ public class AutoFollowIT extends ESCCRRestTestCase { public void testMultipleAutoFollowPatternsDifferentClusters() throws Exception { if ("follow".equals(targetCluster) == false) { - logger.info("skipping test, waiting for target cluster [follow]" ); + logger.info("skipping test, waiting for target cluster [follow]"); return; } @@ -79,7 +87,7 @@ public void testMultipleAutoFollowPatternsDifferentClusters() throws Exception { public void testAutoFollowPatterns() throws Exception { if ("follow".equals(targetCluster) == false) { - logger.info("skipping test, waiting for target cluster [follow]" ); + logger.info("skipping test, waiting for target cluster [follow]"); return; } @@ -138,7 +146,7 @@ public void testAutoFollowPatterns() throws Exception { public void testPutAutoFollowPatternThatOverridesRequiredLeaderSetting() throws IOException { if ("follow".equals(targetCluster) == false) { - logger.info("skipping test, waiting for target cluster [follow]" ); + logger.info("skipping test, waiting for target cluster [follow]"); return; } @@ -211,7 +219,7 @@ public void testDataStreams() throws Exception { // First rollover and ensure second backing index is replicated: { try (RestClient leaderClient = buildLeaderClient()) { - Request rolloverRequest = new Request("POST", "/" + dataStreamName + "/_rollover"); + Request rolloverRequest = new Request("POST", "/" + dataStreamName + "/_rollover"); assertOK(leaderClient.performRequest(rolloverRequest)); verifyDataStream(leaderClient, dataStreamName, backingIndexName(dataStreamName, 1), backingIndexName(dataStreamName, 2)); @@ -232,7 +240,7 @@ public void testDataStreams() throws Exception { // Second rollover and ensure third backing index is replicated: { try (RestClient leaderClient = buildLeaderClient()) { - Request rolloverRequest = new Request("POST", "/" + dataStreamName + "/_rollover"); + Request rolloverRequest = new Request("POST", "/" + dataStreamName + "/_rollover"); assertOK(leaderClient.performRequest(rolloverRequest)); verifyDataStream(leaderClient, dataStreamName, backingIndexName(dataStreamName, 1), backingIndexName(dataStreamName, 2), backingIndexName(dataStreamName, 3)); @@ -284,7 +292,7 @@ public void testDataStreams_autoFollowAfterDataStreamCreated() throws Exception // Rollover and ensure only second backing index is replicated: { try (RestClient leaderClient = buildLeaderClient()) { - Request rolloverRequest = new Request("POST", "/" + dataStreamName + "/_rollover"); + Request rolloverRequest = new Request("POST", "/" + dataStreamName + "/_rollover"); assertOK(leaderClient.performRequest(rolloverRequest)); verifyDataStream(leaderClient, dataStreamName, backingIndexName(dataStreamName, 1), backingIndexName(dataStreamName, 2)); @@ -354,7 +362,7 @@ public void testRolloverDataStreamInFollowClusterForbidden() throws Exception { // Rollover in leader cluster and ensure second backing index is replicated: { try (RestClient leaderClient = buildLeaderClient()) { - Request rolloverRequest = new Request("POST", "/" + dataStreamName + "/_rollover"); + Request rolloverRequest = new Request("POST", "/" + dataStreamName + "/_rollover"); assertOK(leaderClient.performRequest(rolloverRequest)); verifyDataStream(leaderClient, dataStreamName, backingIndexName(dataStreamName, 1), backingIndexName(dataStreamName, 2)); @@ -374,7 +382,7 @@ public void testRolloverDataStreamInFollowClusterForbidden() throws Exception { // Try rollover in follow cluster { - Request rolloverRequest1 = new Request("POST", "/" + dataStreamName + "/_rollover"); + Request rolloverRequest1 = new Request("POST", "/" + dataStreamName + "/_rollover"); Exception e = expectThrows(ResponseException.class, () -> client().performRequest(rolloverRequest1)); assertThat(e.getMessage(), containsString("data stream [" + dataStreamName + "] cannot be rolled over, " + "because it is a replicated data stream")); @@ -386,7 +394,7 @@ public void testRolloverDataStreamInFollowClusterForbidden() throws Exception { unfollow(backingIndexName(dataStreamName, 1)); // Try again - Request rolloverRequest2 = new Request("POST", "/" + dataStreamName + "/_rollover"); + Request rolloverRequest2 = new Request("POST", "/" + dataStreamName + "/_rollover"); e = expectThrows(ResponseException.class, () -> client().performRequest(rolloverRequest2)); assertThat(e.getMessage(), containsString("data stream [" + dataStreamName + "] cannot be rolled over, " + "because it is a replicated data stream")); @@ -397,7 +405,7 @@ public void testRolloverDataStreamInFollowClusterForbidden() throws Exception { assertOK(client().performRequest(promoteRequest)); // Try again and now the rollover should be successful because local data stream is now : - Request rolloverRequest3 = new Request("POST", "/" + dataStreamName + "/_rollover"); + Request rolloverRequest3 = new Request("POST", "/" + dataStreamName + "/_rollover"); assertOK(client().performRequest(rolloverRequest3)); verifyDataStream(client(), dataStreamName, backingIndexName(dataStreamName, 1), backingIndexName(dataStreamName, 2), backingIndexName(dataStreamName, 3)); @@ -458,7 +466,7 @@ public void testRolloverAliasInFollowClusterForbidden() throws Exception { // Rollover in leader cluster and ensure second backing index is replicated: { try (RestClient leaderClient = buildLeaderClient()) { - Request rolloverRequest = new Request("POST", "/" + aliasName + "/_rollover"); + Request rolloverRequest = new Request("POST", "/" + aliasName + "/_rollover"); assertOK(leaderClient.performRequest(rolloverRequest)); verifyAlias(leaderClient, aliasName, true, aliasName + "-000002", aliasName + "-000001"); @@ -479,7 +487,7 @@ public void testRolloverAliasInFollowClusterForbidden() throws Exception { // Try rollover in follow cluster, this should fail, because is_write_index property of an alias isn't // replicated to follow cluster. { - Request rolloverRequest1 = new Request("POST", "/" + aliasName + "/_rollover"); + Request rolloverRequest1 = new Request("POST", "/" + aliasName + "/_rollover"); Exception e = expectThrows(ResponseException.class, () -> client().performRequest(rolloverRequest1)); assertThat(e.getMessage(), containsString("rollover target [" + aliasName + "] does not point to a write index")); verifyAlias(client(), aliasName, false, aliasName + "-000002", aliasName + "-000001"); @@ -663,6 +671,82 @@ public void testDataStreamsBiDirectionalReplication() throws Exception { } } + public void testAutoFollowSearchableSnapshotsFails() throws Exception { + if ("follow".equals(targetCluster) == false) { + return; + } + + final String testPrefix = getTestName().toLowerCase(Locale.ROOT); + int initialNumberOfSuccessfulFollowedIndicesInFollowCluster = getNumberOfSuccessfulFollowedIndices(); + + final String autoFollowPattern = "pattern-" + testPrefix; + createAutoFollowPattern(client(), autoFollowPattern, testPrefix + "-*", "leader_cluster"); + + // Create a regular index on leader + final String regularIndex = testPrefix + "-regular"; + { + try (RestClient leaderClient = buildLeaderClient()) { + for (int i = 0; i < 10; i++) { + Request indexRequest = new Request("POST", "/" + regularIndex + "/_doc"); + indexRequest.addParameter("refresh", "true"); + indexRequest.setJsonEntity("{\"value\":" + i + "}"); + assertOK(leaderClient.performRequest(indexRequest)); + } + verifyDocuments(leaderClient, regularIndex, 10); + } + } + + // Create a snapshot backed index on leader + final String mountedIndex = testPrefix + "-mounted"; + { + try (RestClient leaderClient = buildLeaderClient()) { + final String systemPropertyRepoPath = System.getProperty("tests.leader_cluster_repository_path"); + assertThat("Missing system property [tests.leader_cluster_repository_path]", + systemPropertyRepoPath, not(emptyOrNullString())); + final String repositoryPath = systemPropertyRepoPath + '/' + testPrefix; + + final String repository = testPrefix + "-repository"; + registerRepository(leaderClient, repository, "fs", true, Settings.builder().put("location", repositoryPath).build()); + + final String indexName = testPrefix + "-index"; + for (int i = 0; i < 5; i++) { + Request indexRequest = new Request("POST", "/" + indexName + "/_doc"); + indexRequest.addParameter("refresh", "true"); + indexRequest.setJsonEntity("{\"value\":" + i + "}"); + assertOK(leaderClient.performRequest(indexRequest)); + } + verifyDocuments(leaderClient, indexName, 5); + + final String snapshot = testPrefix + "-snapshot"; + deleteSnapshot(leaderClient, repository, snapshot, true); + createSnapshot(leaderClient, repository, snapshot, true); + deleteIndex(leaderClient, indexName); + + final Request mountRequest = new Request(HttpPost.METHOD_NAME, "/_snapshot/" + repository + '/' + snapshot + "/_mount"); + mountRequest.setJsonEntity("{\"index\": \"" + indexName + "\",\"renamed_index\": \"" + mountedIndex + "\"}"); + final Response mountResponse = leaderClient.performRequest(mountRequest); + assertThat(mountResponse.getStatusLine().getStatusCode(), equalTo(RestStatus.OK.getStatus())); + ensureYellow(mountedIndex, leaderClient); + } + } + + assertBusy(() -> { + Request statsRequest = new Request("GET", "/_ccr/stats"); + Map response = toMap(client().performRequest(statsRequest)); + assertThat(eval("auto_follow_stats.number_of_successful_follow_indices", response), + equalTo(initialNumberOfSuccessfulFollowedIndicesInFollowCluster + 2)); + assertThat(eval("auto_follow_stats.recent_auto_follow_errors", response), + hasSize(greaterThan(0))); + assertThat(eval("auto_follow_stats.recent_auto_follow_errors.0.auto_follow_exception.reason", response), + containsString("index to follow [" + mountedIndex + "] is a searchable snapshot index and cannot be used " + + "for cross-cluster replication purpose")); + ensureYellow(regularIndex); + verifyDocuments(client(), regularIndex, 10); + }); + + deleteAutoFollowPattern(client(), autoFollowPattern); + } + private int getNumberOfSuccessfulFollowedIndices() throws IOException { return getNumberOfSuccessfulFollowedIndices(client()); } diff --git a/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java b/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java index c958cb5c943fc..1cf27112f0a56 100644 --- a/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java +++ b/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java @@ -6,23 +6,28 @@ */ package org.elasticsearch.xpack.ccr; +import org.apache.http.client.methods.HttpPost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; import org.elasticsearch.client.RestClient; import org.elasticsearch.cluster.metadata.DataStream; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.repositories.fs.FsRepository; +import org.elasticsearch.rest.RestStatus; import java.io.IOException; +import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; -import static org.elasticsearch.xpack.ccr.AutoFollowIT.verifyDataStream; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.emptyOrNullString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; public class FollowIndexIT extends ESCCRRestTestCase { @@ -141,7 +146,7 @@ public void testFollowThatOverridesNonExistentSetting() throws IOException { } } - public void testFollowNonExistingLeaderIndex() throws Exception { + public void testFollowNonExistingLeaderIndex() { if ("follow".equals(targetCluster) == false) { logger.info("skipping test, waiting for target cluster [follow]" ); return; @@ -190,4 +195,41 @@ public void testChangeBackingIndexNameFails() throws Exception { assertThat(failure.getMessage(), containsString("a backing index name in the local and remote cluster must remain the same")); } + public void testFollowSearchableSnapshotsFails() throws Exception { + final String testPrefix = getTestName().toLowerCase(Locale.ROOT); + + final String mountedIndex = "mounted-" + testPrefix; + if ("leader".equals(targetCluster)) { + final String systemPropertyRepoPath = System.getProperty("tests.leader_cluster_repository_path"); + assertThat("Missing system property [tests.leader_cluster_repository_path]", systemPropertyRepoPath, not(emptyOrNullString())); + final String repositoryPath = systemPropertyRepoPath + '/' + testPrefix; + + final String repository = "repository-" + testPrefix; + registerRepository(repository, FsRepository.TYPE, true, Settings.builder().put("location", repositoryPath).build()); + + final String indexName = "index-" + testPrefix; + createIndex(indexName, Settings.EMPTY); + + final String snapshot = "snapshot-" + testPrefix; + deleteSnapshot(repository, snapshot, true); + createSnapshot(repository, snapshot, true); + deleteIndex(indexName); + + final Request mountRequest = new Request(HttpPost.METHOD_NAME, "/_snapshot/" + repository + '/' + snapshot + "/_mount"); + mountRequest.setJsonEntity("{\"index\": \"" + indexName + "\",\"renamed_index\": \"" + mountedIndex + "\"}"); + final Response mountResponse = client().performRequest(mountRequest); + assertThat( + "Failed to mount snapshot [" + snapshot + "] from repository [" + repository + "]: " + mountResponse, + mountResponse.getStatusLine().getStatusCode(), + equalTo(RestStatus.OK.getStatus()) + ); + ensureGreen(mountedIndex); + + } else { + final ResponseException e = expectThrows(ResponseException.class, () -> followIndex(mountedIndex, mountedIndex + "-follower")); + assertThat(e.getMessage(), containsString("is a searchable snapshot index and cannot be used as a leader index for " + + "cross-cluster replication purpose")); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(400)); + } + } } diff --git a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java index 9ebf833239386..4931ea454f177 100644 --- a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java +++ b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java @@ -45,6 +45,7 @@ import org.elasticsearch.xpack.core.ccr.AutoFollowMetadata.AutoFollowPattern; import org.elasticsearch.xpack.core.ccr.AutoFollowStats; import org.elasticsearch.xpack.core.ccr.action.PutFollowAction; +import org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants; import java.util.ArrayList; import java.util.Collections; @@ -520,6 +521,19 @@ private void checkAutoFollowPattern(String autoFollowPattenName, } groupedListener.onResponse(new Tuple<>(indexToFollow, failure)); }); + } else if (SearchableSnapshotsConstants.isSearchableSnapshotStore(leaderIndexSettings)) { + String message = String.format(Locale.ROOT, + "index to follow [%s] is a searchable snapshot index and cannot be used for cross-cluster replication purpose", + indexToFollow.getName() + ); + LOGGER.debug(message); + updateAutoFollowMetadata(recordLeaderIndexAsFollowFunction(autoFollowPattenName, indexToFollow), error -> { + ElasticsearchException failure = new ElasticsearchException(message); + if (error != null) { + failure.addSuppressed(error); + } + groupedListener.onResponse(new Tuple<>(indexToFollow, failure)); + }); } else if (leaderIndexAlreadyFollowed(autoFollowPattern, indexToFollow, localMetadata)) { updateAutoFollowMetadata(recordLeaderIndexAsFollowFunction(autoFollowPattenName, indexToFollow), error -> groupedListener.onResponse(new Tuple<>(indexToFollow, error))); diff --git a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/TransportPutFollowAction.java b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/TransportPutFollowAction.java index 5587d9419413f..06bbbd0946ae2 100644 --- a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/TransportPutFollowAction.java +++ b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/TransportPutFollowAction.java @@ -43,6 +43,7 @@ import org.elasticsearch.xpack.core.ccr.action.FollowParameters; import org.elasticsearch.xpack.core.ccr.action.PutFollowAction; import org.elasticsearch.xpack.core.ccr.action.ResumeFollowAction; +import org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants; import java.util.ArrayList; import java.util.Collections; @@ -128,6 +129,11 @@ private void createFollowerIndex( "] does not have soft deletes enabled")); return; } + if (SearchableSnapshotsConstants.isSearchableSnapshotStore(leaderIndexMetadata.getSettings())) { + listener.onFailure(new IllegalArgumentException("leader index [" + request.getLeaderIndex() + + "] is a searchable snapshot index and cannot be used as a leader index for cross-cluster replication purpose")); + return; + } final Settings replicatedRequestSettings = TransportResumeFollowAction.filter(request.getSettings()); if (replicatedRequestSettings.isEmpty() == false) { diff --git a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowAction.java b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowAction.java index 8006d4228f4af..408ef6e641822 100644 --- a/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowAction.java +++ b/x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/TransportResumeFollowAction.java @@ -51,6 +51,7 @@ import org.elasticsearch.xpack.core.ccr.action.FollowParameters; import org.elasticsearch.xpack.core.ccr.action.ResumeFollowAction; import org.elasticsearch.xpack.core.ccr.action.ShardFollowTask; +import org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants; import java.io.IOException; import java.util.Collections; @@ -208,10 +209,18 @@ static void validate( throw new IllegalArgumentException("leader index [" + leaderIndex.getIndex().getName() + "] does not have soft deletes enabled"); } + if (SearchableSnapshotsConstants.isSearchableSnapshotStore(leaderIndex.getSettings())) { + throw new IllegalArgumentException("leader index [" + leaderIndex.getIndex().getName() + + "] is a searchable snapshot index and cannot be used for cross-cluster replication purpose"); + } if (IndexSettings.INDEX_SOFT_DELETES_SETTING.get(followIndex.getSettings()) == false) { throw new IllegalArgumentException("follower index [" + request.getFollowerIndex() + "] does not have soft deletes enabled"); } + if (SearchableSnapshotsConstants.isSearchableSnapshotStore(followIndex.getSettings())) { + throw new IllegalArgumentException("follower index [" + request.getFollowerIndex() + + "] is a searchable snapshot index and cannot be used for cross-cluster replication purpose"); + } if (leaderIndex.getNumberOfShards() != followIndex.getNumberOfShards()) { throw new IllegalArgumentException("leader index primary shards [" + leaderIndex.getNumberOfShards() + "] does not match with the number of shards of the follow index [" + followIndex.getNumberOfShards() + "]");