From 345b57cc05cc433f5e461c2bdd334dd4a33b80a2 Mon Sep 17 00:00:00 2001 From: Pat Whelan Date: Wed, 15 Oct 2025 17:48:19 -0400 Subject: [PATCH 1/5] [ML] Disable CPS for Dataframes Cross-Project Search and Cross-Cluster Search is indefinitely disabled for Dataframe Analytics. The error message will now display if the syntax would have otherwise resolved to the respective feature. --- .../validation/SourceDestValidator.java | 23 ++++- ...eClusterMinimumVersionValidationTests.java | 2 +- .../validation/SourceDestValidatorTests.java | 76 +++++++++++++++++ .../xpack/ml/integration/DataframeCpsIT.java | 85 +++++++++++++++++++ .../TransportPutDataFrameAnalyticsAction.java | 2 + ...ransportStartDataFrameAnalyticsAction.java | 2 + .../TransportPreviewTransformAction.java | 2 + .../TransportValidateTransformAction.java | 2 + 8 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java index 700158712707a..01b73344cf209 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java @@ -70,6 +70,7 @@ public final class SourceDestValidator { public static final String REMOTE_CLUSTER_LICENSE_INACTIVE = "License check failed for remote cluster " + "alias [{0}], license is not active"; public static final String REMOTE_SOURCE_INDICES_NOT_SUPPORTED = "remote source indices are not supported"; + public static final String CROSS_PROJECT_INDICES_NOT_SUPPORTED = "cross-project indices are not supported"; public static final String REMOTE_CLUSTERS_TRANSPORT_TOO_OLD = "remote clusters are expected to run at least version [{0}] (reason: [{1}])," + " but the following clusters were too old: [{2}]"; public static final String PIPELINE_MISSING = "Pipeline with id [{0}] could not be found"; @@ -78,6 +79,7 @@ public final class SourceDestValidator { private final RemoteClusterService remoteClusterService; private final RemoteClusterLicenseChecker remoteClusterLicenseChecker; private final IngestService ingestService; + private final boolean crossProjectEnabled; private final String nodeName; private final String license; @@ -90,6 +92,7 @@ static class Context { private final RemoteClusterService remoteClusterService; private final RemoteClusterLicenseChecker remoteClusterLicenseChecker; private final IngestService ingestService; + private final boolean crossProjectEnabled; private final String[] source; private final String destIndex; private final String destPipeline; @@ -107,6 +110,7 @@ static class Context { final RemoteClusterService remoteClusterService, final RemoteClusterLicenseChecker remoteClusterLicenseChecker, final IngestService ingestService, + boolean crossProjectEnabled, final String[] source, final String destIndex, final String destPipeline, @@ -118,6 +122,7 @@ static class Context { this.remoteClusterService = remoteClusterService; this.remoteClusterLicenseChecker = remoteClusterLicenseChecker; this.ingestService = ingestService; + this.crossProjectEnabled = crossProjectEnabled; this.source = source; this.destIndex = destIndex; this.destPipeline = destPipeline; @@ -165,6 +170,10 @@ public String getLicense() { return license; } + private boolean crossProjectEnabled() { + return crossProjectEnabled; + } + public SortedSet resolveSource() { if (resolvedSource == null) { resolveLocalAndRemoteSource(); @@ -266,16 +275,18 @@ public interface SourceDestValidation { * Create a new Source Dest Validator * * @param indexNameExpressionResolver A valid IndexNameExpressionResolver object - * @param remoteClusterService A valid RemoteClusterService object + * @param remoteClusterService A valid RemoteClusterService object * @param remoteClusterLicenseChecker A RemoteClusterLicenseChecker or null if CCS is disabled - * @param nodeName the name of this node - * @param license the license of the feature validated for + * @param crossProjectEnabled Determines if cross-project is enabled for this cluster + * @param nodeName the name of this node + * @param license the license of the feature validated for */ public SourceDestValidator( IndexNameExpressionResolver indexNameExpressionResolver, RemoteClusterService remoteClusterService, RemoteClusterLicenseChecker remoteClusterLicenseChecker, IngestService ingestService, + boolean crossProjectEnabled, String nodeName, String license ) { @@ -283,6 +294,7 @@ public SourceDestValidator( this.remoteClusterService = remoteClusterService; this.remoteClusterLicenseChecker = remoteClusterLicenseChecker; this.ingestService = ingestService; + this.crossProjectEnabled = crossProjectEnabled; this.nodeName = nodeName; this.license = license; } @@ -311,6 +323,7 @@ public void validate( remoteClusterService, remoteClusterLicenseChecker, ingestService, + crossProjectEnabled, source, destIndex, destPipeline, @@ -555,7 +568,9 @@ static class RemoteSourceNotSupportedValidation implements SourceDestValidation @Override public void validate(Context context, ActionListener listener) { if (context.resolveRemoteSource().isEmpty() == false) { - context.addValidationError(REMOTE_SOURCE_INDICES_NOT_SUPPORTED); + context.addValidationError( + context.crossProjectEnabled() ? CROSS_PROJECT_INDICES_NOT_SUPPORTED : REMOTE_SOURCE_INDICES_NOT_SUPPORTED + ); } listener.onResponse(context); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/RemoteClusterMinimumVersionValidationTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/RemoteClusterMinimumVersionValidationTests.java index 2781aa9d18c64..577bb0b586dec 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/RemoteClusterMinimumVersionValidationTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/RemoteClusterMinimumVersionValidationTests.java @@ -40,7 +40,7 @@ public class RemoteClusterMinimumVersionValidationTests extends ESTestCase { @Before public void setUpMocks() { - context = spy(new Context(null, null, null, null, null, null, null, null, null, null)); + context = spy(new Context(null, null, null, null, null, false, null, null, null, null, null)); doReturn(TransportVersions.V_8_10_X).when(context).getRemoteClusterVersion("cluster-A"); doReturn(TransportVersions.V_8_11_X).when(context).getRemoteClusterVersion("cluster-B"); doReturn(TransportVersions.V_8_12_0).when(context).getRemoteClusterVersion("cluster-C"); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidatorTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidatorTests.java index 8ba3fe338caa1..b2ab4f24528d6 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidatorTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidatorTests.java @@ -67,7 +67,9 @@ import static org.elasticsearch.xpack.core.common.validation.SourceDestValidator.DESTINATION_IN_SOURCE_VALIDATION; import static org.elasticsearch.xpack.core.common.validation.SourceDestValidator.DESTINATION_PIPELINE_MISSING_VALIDATION; import static org.elasticsearch.xpack.core.common.validation.SourceDestValidator.DESTINATION_SINGLE_INDEX_VALIDATION; +import static org.elasticsearch.xpack.core.common.validation.SourceDestValidator.REMOTE_SOURCE_NOT_SUPPORTED_VALIDATION; import static org.elasticsearch.xpack.core.common.validation.SourceDestValidator.SOURCE_MISSING_VALIDATION; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -123,6 +125,7 @@ public class SourceDestValidatorTests extends ESTestCase { remoteClusterService, null, ingestService, + false, "node_id", "license" ); @@ -649,6 +652,7 @@ public void testRemoteSourceBasic() throws InterruptedException { remoteClusterService, remoteClusterLicenseCheckerBasic, ingestService, + false, new String[] { REMOTE_BASIC + ":" + "SOURCE_1" }, "dest", null, @@ -667,6 +671,72 @@ public void testRemoteSourceBasic() throws InterruptedException { ); } + public void testRemoteSourceNotSupportedValidationWithLocalIndex() throws InterruptedException { + Context context = spy( + new SourceDestValidator.Context( + CLUSTER_STATE, + indexNameExpressionResolver, + remoteClusterService, + remoteClusterLicenseCheckerBasic, + ingestService, + false, + new String[] { SOURCE_1 }, + "dest", + null, + "node_id", + "license" + ) + ); + + assertValidationWithContext(listener -> REMOTE_SOURCE_NOT_SUPPORTED_VALIDATION.validate(context, listener), c -> { + assertNull(c.getValidationException()); + }, null); + } + + public void testRemoteSourceNotSupportedValidationWithRemoteIndex() throws InterruptedException { + Context context = spy( + new SourceDestValidator.Context( + CLUSTER_STATE, + indexNameExpressionResolver, + remoteClusterService, + remoteClusterLicenseCheckerBasic, + ingestService, + false, + new String[] { REMOTE_BASIC + ":" + SOURCE_1 }, + "dest", + null, + "node_id", + "license" + ) + ); + + assertValidationWithContext(listener -> REMOTE_SOURCE_NOT_SUPPORTED_VALIDATION.validate(context, listener), c -> { + assertThat(c.getValidationException().getMessage(), containsString("remote source indices are not supported")); + }, null); + } + + public void testRemoteSourceNotSupportedValidationWithCrossProjectIndex() throws InterruptedException { + Context context = spy( + new SourceDestValidator.Context( + CLUSTER_STATE, + indexNameExpressionResolver, + remoteClusterService, + remoteClusterLicenseCheckerBasic, + ingestService, + true, + new String[] { "project1:" + SOURCE_1 }, + "dest", + null, + "node_id", + "license" + ) + ); + + assertValidationWithContext(listener -> REMOTE_SOURCE_NOT_SUPPORTED_VALIDATION.validate(context, listener), c -> { + assertThat(c.getValidationException().getMessage(), containsString("cross-project indices are not supported")); + }, null); + } + public void testRemoteSourcePlatinum() throws InterruptedException { final Context context = spy( new SourceDestValidator.Context( @@ -675,6 +745,7 @@ public void testRemoteSourcePlatinum() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithBasicLicense, platinumFeature), ingestService, + false, new String[] { REMOTE_BASIC + ":" + "SOURCE_1" }, "dest", null, @@ -706,6 +777,7 @@ public void testRemoteSourcePlatinum() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithPlatinumLicense, platinumFeature), ingestService, + false, new String[] { REMOTE_PLATINUM + ":" + "SOURCE_1" }, "dest", null, @@ -728,6 +800,7 @@ public void testRemoteSourcePlatinum() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithPlatinumLicense, platinumFeature), ingestService, + false, new String[] { REMOTE_PLATINUM + ":" + "SOURCE_1" }, "dest", "node_id", @@ -751,6 +824,7 @@ public void testRemoteSourcePlatinum() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithTrialLicense, platinumFeature), ingestService, + false, new String[] { REMOTE_PLATINUM + ":" + "SOURCE_1" }, "dest", "node_id", @@ -776,6 +850,7 @@ public void testRemoteSourceLicenseInActive() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithExpiredBasicLicense, platinumFeature), ingestService, + false, new String[] { REMOTE_BASIC + ":" + "SOURCE_1" }, "dest", null, @@ -804,6 +879,7 @@ public void testRemoteSourceDoesNotExist() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithExpiredBasicLicense, platinumFeature), ingestService, + false, new String[] { "non_existing_remote:" + "SOURCE_1" }, "dest", null, diff --git a/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java b/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java new file mode 100644 index 0000000000000..99cd8b6303f02 --- /dev/null +++ b/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java @@ -0,0 +1,85 @@ +/* + * 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.ml.integration; + +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.plugins.ClusterPlugin; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.xpack.core.ml.action.PutDataFrameAnalyticsAction; +import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsConfig; +import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsDest; +import org.elasticsearch.xpack.core.ml.dataframe.DataFrameAnalyticsSource; +import org.elasticsearch.xpack.core.ml.dataframe.analyses.BoostedTreeParams; +import org.elasticsearch.xpack.core.ml.dataframe.analyses.Classification; +import org.elasticsearch.xpack.core.ml.utils.QueryProvider; +import org.elasticsearch.xpack.ml.MlSingleNodeTestCase; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.containsString; + +public class DataframeCpsIT extends MlSingleNodeTestCase { + @Override + protected Settings nodeSettings() { + return Settings.builder().put(super.nodeSettings()).put("serverless.cross_project.enabled", "true").build(); + } + + @Override + protected Collection> getPlugins() { + return Stream.concat(super.getPlugins().stream(), Stream.of(CpsPlugin.class)).toList(); + } + + public void testCrossProjectFailsForDataFrameAnalytics() throws IOException { + var id = "test-cross-project-fails"; + var sourceIndex = "project1:" + id + "_source_index"; + var destIndex = id + "_results"; + + var config = new DataFrameAnalyticsConfig.Builder().setId(id) + .setSource( + new DataFrameAnalyticsSource( + new String[] { sourceIndex }, + QueryProvider.fromParsedQuery(QueryBuilders.matchAllQuery()), + null, + Collections.emptyMap() + ) + ) + .setDest(new DataFrameAnalyticsDest(destIndex, null)) + .setAnalysis( + new Classification( + "keyword-field", + BoostedTreeParams.builder().setNumTopFeatureImportanceValues(1).build(), + null, + null, + null, + null, + null, + null, + null + ) + ) + .build(); + + var request = new PutDataFrameAnalyticsAction.Request(config); + var response = client().execute(PutDataFrameAnalyticsAction.INSTANCE, request); + var validationException = assertThrows(ValidationException.class, response::actionGet); + assertThat(validationException.getMessage(), containsString("cross-project indices are not supported")); + } + + public static class CpsPlugin extends Plugin implements ClusterPlugin { + public List> getSettings() { + return List.of(Setting.simpleString("serverless.cross_project.enabled", Setting.Property.NodeScope)); + } + } +} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java index a099c8619606a..5c508327887fe 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java @@ -28,6 +28,7 @@ import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.RemoteClusterLicenseChecker; import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; @@ -118,6 +119,7 @@ public TransportPutDataFrameAnalyticsAction( transportService.getRemoteClusterService(), null, null, + new CrossProjectModeDecider(clusterService.getSettings()).crossProjectEnabled(), clusterService.getNodeName(), License.OperationMode.PLATINUM.description() ); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java index be905caeacba0..1321be316f7a1 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java @@ -44,6 +44,7 @@ import org.elasticsearch.persistent.PersistentTasksCustomMetadata; import org.elasticsearch.persistent.PersistentTasksService; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.threadpool.ThreadPool; @@ -156,6 +157,7 @@ public TransportStartDataFrameAnalyticsAction( transportService.getRemoteClusterService(), null, null, + new CrossProjectModeDecider(clusterService.getSettings()).crossProjectEnabled(), clusterService.getNodeName(), License.OperationMode.PLATINUM.description() ); diff --git a/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportPreviewTransformAction.java b/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportPreviewTransformAction.java index 60f00da195974..e97bf0aaab24d 100644 --- a/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportPreviewTransformAction.java +++ b/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportPreviewTransformAction.java @@ -32,6 +32,7 @@ import org.elasticsearch.injection.guice.Inject; import org.elasticsearch.license.License; import org.elasticsearch.license.RemoteClusterLicenseChecker; +import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.threadpool.ThreadPool; @@ -116,6 +117,7 @@ public TransportPreviewTransformAction( ? new RemoteClusterLicenseChecker(client, null) : null, ingestService, + new CrossProjectModeDecider(clusterService.getSettings()).crossProjectEnabled(), clusterService.getNodeName(), License.OperationMode.BASIC.description() ); diff --git a/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportValidateTransformAction.java b/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportValidateTransformAction.java index 7fc7ec49797f7..8283e68ddc567 100644 --- a/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportValidateTransformAction.java +++ b/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportValidateTransformAction.java @@ -23,6 +23,7 @@ import org.elasticsearch.injection.guice.Inject; import org.elasticsearch.license.License; import org.elasticsearch.license.RemoteClusterLicenseChecker; +import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.transport.TransportService; @@ -72,6 +73,7 @@ public TransportValidateTransformAction( ? new RemoteClusterLicenseChecker(client, null) : null, ingestService, + new CrossProjectModeDecider(clusterService.getSettings()).crossProjectEnabled(), clusterService.getNodeName(), License.OperationMode.BASIC.description() ); From bba12ed879a922ba8725b8bef63f54423b3a8e9f Mon Sep 17 00:00:00 2001 From: Pat Whelan Date: Mon, 27 Oct 2025 17:33:28 -0400 Subject: [PATCH 2/5] combine error messages --- .../validation/SourceDestValidator.java | 26 +++---------- ...eClusterMinimumVersionValidationTests.java | 2 +- .../validation/SourceDestValidatorTests.java | 37 ++----------------- .../TransportPutDataFrameAnalyticsAction.java | 2 - ...ransportStartDataFrameAnalyticsAction.java | 2 - .../TransportPreviewTransformAction.java | 2 - .../TransportValidateTransformAction.java | 2 - 7 files changed, 11 insertions(+), 62 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java index 01b73344cf209..93a2d798c3bbb 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java @@ -69,8 +69,8 @@ public final class SourceDestValidator { + "alias [{0}], at least a [{1}] license is required, found license [{2}]"; public static final String REMOTE_CLUSTER_LICENSE_INACTIVE = "License check failed for remote cluster " + "alias [{0}], license is not active"; - public static final String REMOTE_SOURCE_INDICES_NOT_SUPPORTED = "remote source indices are not supported"; - public static final String CROSS_PROJECT_INDICES_NOT_SUPPORTED = "cross-project indices are not supported"; + public static final String REMOTE_SOURCE_AND_CROSS_PROJECT_INDICES_ARE_NOT_SUPPORTED = + "remote source and cross-project indices are not supported"; public static final String REMOTE_CLUSTERS_TRANSPORT_TOO_OLD = "remote clusters are expected to run at least version [{0}] (reason: [{1}])," + " but the following clusters were too old: [{2}]"; public static final String PIPELINE_MISSING = "Pipeline with id [{0}] could not be found"; @@ -79,7 +79,6 @@ public final class SourceDestValidator { private final RemoteClusterService remoteClusterService; private final RemoteClusterLicenseChecker remoteClusterLicenseChecker; private final IngestService ingestService; - private final boolean crossProjectEnabled; private final String nodeName; private final String license; @@ -92,7 +91,6 @@ static class Context { private final RemoteClusterService remoteClusterService; private final RemoteClusterLicenseChecker remoteClusterLicenseChecker; private final IngestService ingestService; - private final boolean crossProjectEnabled; private final String[] source; private final String destIndex; private final String destPipeline; @@ -110,7 +108,6 @@ static class Context { final RemoteClusterService remoteClusterService, final RemoteClusterLicenseChecker remoteClusterLicenseChecker, final IngestService ingestService, - boolean crossProjectEnabled, final String[] source, final String destIndex, final String destPipeline, @@ -122,7 +119,6 @@ static class Context { this.remoteClusterService = remoteClusterService; this.remoteClusterLicenseChecker = remoteClusterLicenseChecker; this.ingestService = ingestService; - this.crossProjectEnabled = crossProjectEnabled; this.source = source; this.destIndex = destIndex; this.destPipeline = destPipeline; @@ -170,10 +166,6 @@ public String getLicense() { return license; } - private boolean crossProjectEnabled() { - return crossProjectEnabled; - } - public SortedSet resolveSource() { if (resolvedSource == null) { resolveLocalAndRemoteSource(); @@ -275,18 +267,16 @@ public interface SourceDestValidation { * Create a new Source Dest Validator * * @param indexNameExpressionResolver A valid IndexNameExpressionResolver object - * @param remoteClusterService A valid RemoteClusterService object + * @param remoteClusterService A valid RemoteClusterService object * @param remoteClusterLicenseChecker A RemoteClusterLicenseChecker or null if CCS is disabled - * @param crossProjectEnabled Determines if cross-project is enabled for this cluster - * @param nodeName the name of this node - * @param license the license of the feature validated for + * @param nodeName the name of this node + * @param license the license of the feature validated for */ public SourceDestValidator( IndexNameExpressionResolver indexNameExpressionResolver, RemoteClusterService remoteClusterService, RemoteClusterLicenseChecker remoteClusterLicenseChecker, IngestService ingestService, - boolean crossProjectEnabled, String nodeName, String license ) { @@ -294,7 +284,6 @@ public SourceDestValidator( this.remoteClusterService = remoteClusterService; this.remoteClusterLicenseChecker = remoteClusterLicenseChecker; this.ingestService = ingestService; - this.crossProjectEnabled = crossProjectEnabled; this.nodeName = nodeName; this.license = license; } @@ -323,7 +312,6 @@ public void validate( remoteClusterService, remoteClusterLicenseChecker, ingestService, - crossProjectEnabled, source, destIndex, destPipeline, @@ -568,9 +556,7 @@ static class RemoteSourceNotSupportedValidation implements SourceDestValidation @Override public void validate(Context context, ActionListener listener) { if (context.resolveRemoteSource().isEmpty() == false) { - context.addValidationError( - context.crossProjectEnabled() ? CROSS_PROJECT_INDICES_NOT_SUPPORTED : REMOTE_SOURCE_INDICES_NOT_SUPPORTED - ); + context.addValidationError(REMOTE_SOURCE_AND_CROSS_PROJECT_INDICES_ARE_NOT_SUPPORTED); } listener.onResponse(context); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/RemoteClusterMinimumVersionValidationTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/RemoteClusterMinimumVersionValidationTests.java index 577bb0b586dec..2781aa9d18c64 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/RemoteClusterMinimumVersionValidationTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/RemoteClusterMinimumVersionValidationTests.java @@ -40,7 +40,7 @@ public class RemoteClusterMinimumVersionValidationTests extends ESTestCase { @Before public void setUpMocks() { - context = spy(new Context(null, null, null, null, null, false, null, null, null, null, null)); + context = spy(new Context(null, null, null, null, null, null, null, null, null, null)); doReturn(TransportVersions.V_8_10_X).when(context).getRemoteClusterVersion("cluster-A"); doReturn(TransportVersions.V_8_11_X).when(context).getRemoteClusterVersion("cluster-B"); doReturn(TransportVersions.V_8_12_0).when(context).getRemoteClusterVersion("cluster-C"); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidatorTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidatorTests.java index b2ab4f24528d6..81830dad58ef7 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidatorTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidatorTests.java @@ -125,7 +125,6 @@ public class SourceDestValidatorTests extends ESTestCase { remoteClusterService, null, ingestService, - false, "node_id", "license" ); @@ -652,7 +651,6 @@ public void testRemoteSourceBasic() throws InterruptedException { remoteClusterService, remoteClusterLicenseCheckerBasic, ingestService, - false, new String[] { REMOTE_BASIC + ":" + "SOURCE_1" }, "dest", null, @@ -679,7 +677,6 @@ public void testRemoteSourceNotSupportedValidationWithLocalIndex() throws Interr remoteClusterService, remoteClusterLicenseCheckerBasic, ingestService, - false, new String[] { SOURCE_1 }, "dest", null, @@ -701,7 +698,6 @@ public void testRemoteSourceNotSupportedValidationWithRemoteIndex() throws Inter remoteClusterService, remoteClusterLicenseCheckerBasic, ingestService, - false, new String[] { REMOTE_BASIC + ":" + SOURCE_1 }, "dest", null, @@ -711,29 +707,10 @@ public void testRemoteSourceNotSupportedValidationWithRemoteIndex() throws Inter ); assertValidationWithContext(listener -> REMOTE_SOURCE_NOT_SUPPORTED_VALIDATION.validate(context, listener), c -> { - assertThat(c.getValidationException().getMessage(), containsString("remote source indices are not supported")); - }, null); - } - - public void testRemoteSourceNotSupportedValidationWithCrossProjectIndex() throws InterruptedException { - Context context = spy( - new SourceDestValidator.Context( - CLUSTER_STATE, - indexNameExpressionResolver, - remoteClusterService, - remoteClusterLicenseCheckerBasic, - ingestService, - true, - new String[] { "project1:" + SOURCE_1 }, - "dest", - null, - "node_id", - "license" - ) - ); - - assertValidationWithContext(listener -> REMOTE_SOURCE_NOT_SUPPORTED_VALIDATION.validate(context, listener), c -> { - assertThat(c.getValidationException().getMessage(), containsString("cross-project indices are not supported")); + assertThat( + c.getValidationException().getMessage(), + containsString("remote source and cross-project indices are not supported") + ); }, null); } @@ -745,7 +722,6 @@ public void testRemoteSourcePlatinum() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithBasicLicense, platinumFeature), ingestService, - false, new String[] { REMOTE_BASIC + ":" + "SOURCE_1" }, "dest", null, @@ -777,7 +753,6 @@ public void testRemoteSourcePlatinum() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithPlatinumLicense, platinumFeature), ingestService, - false, new String[] { REMOTE_PLATINUM + ":" + "SOURCE_1" }, "dest", null, @@ -800,7 +775,6 @@ public void testRemoteSourcePlatinum() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithPlatinumLicense, platinumFeature), ingestService, - false, new String[] { REMOTE_PLATINUM + ":" + "SOURCE_1" }, "dest", "node_id", @@ -824,7 +798,6 @@ public void testRemoteSourcePlatinum() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithTrialLicense, platinumFeature), ingestService, - false, new String[] { REMOTE_PLATINUM + ":" + "SOURCE_1" }, "dest", "node_id", @@ -850,7 +823,6 @@ public void testRemoteSourceLicenseInActive() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithExpiredBasicLicense, platinumFeature), ingestService, - false, new String[] { REMOTE_BASIC + ":" + "SOURCE_1" }, "dest", null, @@ -879,7 +851,6 @@ public void testRemoteSourceDoesNotExist() throws InterruptedException { remoteClusterService, new RemoteClusterLicenseChecker(clientWithExpiredBasicLicense, platinumFeature), ingestService, - false, new String[] { "non_existing_remote:" + "SOURCE_1" }, "dest", null, diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java index 5c508327887fe..a099c8619606a 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java @@ -28,7 +28,6 @@ import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.RemoteClusterLicenseChecker; import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; @@ -119,7 +118,6 @@ public TransportPutDataFrameAnalyticsAction( transportService.getRemoteClusterService(), null, null, - new CrossProjectModeDecider(clusterService.getSettings()).crossProjectEnabled(), clusterService.getNodeName(), License.OperationMode.PLATINUM.description() ); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java index 1321be316f7a1..be905caeacba0 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java @@ -44,7 +44,6 @@ import org.elasticsearch.persistent.PersistentTasksCustomMetadata; import org.elasticsearch.persistent.PersistentTasksService; import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.threadpool.ThreadPool; @@ -157,7 +156,6 @@ public TransportStartDataFrameAnalyticsAction( transportService.getRemoteClusterService(), null, null, - new CrossProjectModeDecider(clusterService.getSettings()).crossProjectEnabled(), clusterService.getNodeName(), License.OperationMode.PLATINUM.description() ); diff --git a/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportPreviewTransformAction.java b/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportPreviewTransformAction.java index e97bf0aaab24d..60f00da195974 100644 --- a/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportPreviewTransformAction.java +++ b/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportPreviewTransformAction.java @@ -32,7 +32,6 @@ import org.elasticsearch.injection.guice.Inject; import org.elasticsearch.license.License; import org.elasticsearch.license.RemoteClusterLicenseChecker; -import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.threadpool.ThreadPool; @@ -117,7 +116,6 @@ public TransportPreviewTransformAction( ? new RemoteClusterLicenseChecker(client, null) : null, ingestService, - new CrossProjectModeDecider(clusterService.getSettings()).crossProjectEnabled(), clusterService.getNodeName(), License.OperationMode.BASIC.description() ); diff --git a/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportValidateTransformAction.java b/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportValidateTransformAction.java index 8283e68ddc567..7fc7ec49797f7 100644 --- a/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportValidateTransformAction.java +++ b/x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/action/TransportValidateTransformAction.java @@ -23,7 +23,6 @@ import org.elasticsearch.injection.guice.Inject; import org.elasticsearch.license.License; import org.elasticsearch.license.RemoteClusterLicenseChecker; -import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.transport.TransportService; @@ -73,7 +72,6 @@ public TransportValidateTransformAction( ? new RemoteClusterLicenseChecker(client, null) : null, ingestService, - new CrossProjectModeDecider(clusterService.getSettings()).crossProjectEnabled(), clusterService.getNodeName(), License.OperationMode.BASIC.description() ); From c2460f656cf1c3c3eb1c61a4294106d215539700 Mon Sep 17 00:00:00 2001 From: Pat Whelan Date: Mon, 27 Oct 2025 17:40:53 -0400 Subject: [PATCH 3/5] i forgot the copy pasta --- .../org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java b/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java index 99cd8b6303f02..bff35219b9a5d 100644 --- a/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java +++ b/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/DataframeCpsIT.java @@ -74,7 +74,7 @@ public void testCrossProjectFailsForDataFrameAnalytics() throws IOException { var request = new PutDataFrameAnalyticsAction.Request(config); var response = client().execute(PutDataFrameAnalyticsAction.INSTANCE, request); var validationException = assertThrows(ValidationException.class, response::actionGet); - assertThat(validationException.getMessage(), containsString("cross-project indices are not supported")); + assertThat(validationException.getMessage(), containsString("remote source and cross-project indices are not supported")); } public static class CpsPlugin extends Plugin implements ClusterPlugin { From 95b8da28124c2a64c3158fb1a09581ecebbfba11 Mon Sep 17 00:00:00 2001 From: Pat Whelan Date: Tue, 28 Oct 2025 11:22:49 -0400 Subject: [PATCH 4/5] update error message --- .../xpack/remotecluster/RemoteClusterSecurityMlIT.java | 2 +- .../rest-api-spec/test/ml/data_frame_analytics_crud.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityMlIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityMlIT.java index 5b5d4617b5c44..5210cfe7f7d8e 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityMlIT.java +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityMlIT.java @@ -200,7 +200,7 @@ public void testDataframeAnalyticsNotSupportForRemoteIndices() { """); final ResponseException e = expectThrows(ResponseException.class, () -> performRequestWithRemoteMlUser(putDataframeAnalytics)); assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(400)); - assertThat(e.getMessage(), containsString("remote source indices are not supported")); + assertThat(e.getMessage(), containsString("remote source and cross-project indices are not supported")); } private Response performRequestWithRemoteMlUser(final Request request) throws IOException { diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/data_frame_analytics_crud.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/data_frame_analytics_crud.yml index e3f1a274c0a5b..0ff6805b8806f 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/data_frame_analytics_crud.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/data_frame_analytics_crud.yml @@ -432,7 +432,7 @@ setup: "Test put config with remote source index": - do: - catch: /.*Validation Failed.* remote source indices are not supported/ + catch: /.*Validation Failed.* remote source and cross-project indices are not supported/ ml.put_data_frame_analytics: id: "config-with-missing-concrete-source-index" body: > From fc812d5cad8a9893c0096f6780a183f76363c622 Mon Sep 17 00:00:00 2001 From: Pat Whelan Date: Wed, 29 Oct 2025 10:31:11 -0400 Subject: [PATCH 5/5] Skip failing compat test --- x-pack/plugin/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/build.gradle b/x-pack/plugin/build.gradle index 90786f7c0c678..d8073cc28f5a0 100644 --- a/x-pack/plugin/build.gradle +++ b/x-pack/plugin/build.gradle @@ -143,6 +143,7 @@ tasks.named("yamlRestCompatTestTransform").configure({ task -> task.skipTest("ml/sparse_vector_search/Search on a sparse_vector field with dots in the field names", "Vectors are no longer returned by default") task.skipTest("ml/sparse_vector_search/Search on a nested sparse_vector field with dots in the field names and conflicting child fields", "Vectors are no longer returned by default") task.skipTest("esql/190_lookup_join/lookup-no-key-only-key", "Requires the fix") + task.skipTest("ml/data_frame_analytics_crud/Test put config with remote source index", "Error message changed") }) tasks.named('yamlRestCompatTest').configure {