Skip to content

Commit 4fad6e3

Browse files
authored
[ML] Throw an error when a datafeed needs CCS but it is not enabled for the node (#46044) (#46186)
Though we allow CCS within datafeeds, users could prevent nodes from accessing remote clusters. This can cause mysterious errors and difficult to troubleshoot. This commit adds a check to verify that `cluster.remote.connect` is enabled on the current node when a datafeed is configured with a remote index pattern.
1 parent 6ad0e5c commit 4fad6e3

File tree

6 files changed

+86
-5
lines changed

6 files changed

+86
-5
lines changed

docs/reference/ml/apis/put-datafeed.asciidoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ IMPORTANT: You must use {kib} or this API to create a {dfeed}. Do not put a {df
5959
`indices` (required)::
6060
(array) An array of index names. Wildcards are supported. For example:
6161
`["it_ops_metrics", "server*"]`.
62+
+
63+
--
64+
NOTE: If any indices are in remote clusters then `cluster.remote.connect` must
65+
not be set to `false` on any ML node.
66+
--
6267

6368
`job_id` (required)::
6469
(string) A numerical character string that uniquely identifies the job.

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/messages/Messages.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public final class Messages {
4949
public static final String DATAFEED_FREQUENCY_MUST_BE_MULTIPLE_OF_AGGREGATIONS_INTERVAL =
5050
"Datafeed frequency [{0}] must be a multiple of the aggregation interval [{1}]";
5151
public static final String DATAFEED_ID_ALREADY_TAKEN = "A datafeed with id [{0}] already exists";
52+
public static final String DATAFEED_NEEDS_REMOTE_CLUSTER_SEARCH = "Datafeed [{0}] is configured with a remote index pattern(s) {1}" +
53+
" but the current node [{2}] is not allowed to connect to remote clusters." +
54+
" Please enable cluster.remote.connect for all machine learning nodes.";
5255

5356
public static final String FILTER_CANNOT_DELETE = "Cannot delete filter [{0}] currently used by jobs {1}";
5457
public static final String FILTER_CONTAINS_TOO_MANY_ITEMS = "Filter [{0}] contains too many items; up to [{1}] items are allowed";

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ public Collection<Object> createComponents(Client client, ClusterService cluster
452452
normalizerFactory, xContentRegistry, auditor, clusterService);
453453
this.autodetectProcessManager.set(autodetectProcessManager);
454454
DatafeedJobBuilder datafeedJobBuilder = new DatafeedJobBuilder(client, settings, xContentRegistry,
455-
auditor, System::currentTimeMillis);
455+
auditor, System::currentTimeMillis, clusterService.getNodeName());
456456
DatafeedManager datafeedManager = new DatafeedManager(threadPool, client, clusterService, datafeedJobBuilder,
457457
System::currentTimeMillis, auditor, autodetectProcessManager);
458458
this.datafeedManager.set(datafeedManager);

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedAction.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.rest.RestStatus;
3636
import org.elasticsearch.tasks.TaskId;
3737
import org.elasticsearch.threadpool.ThreadPool;
38+
import org.elasticsearch.transport.RemoteClusterService;
3839
import org.elasticsearch.transport.TransportService;
3940
import org.elasticsearch.xpack.core.XPackField;
4041
import org.elasticsearch.xpack.core.ml.MlTasks;
@@ -44,6 +45,7 @@
4445
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
4546
import org.elasticsearch.xpack.core.ml.job.config.Job;
4647
import org.elasticsearch.xpack.core.ml.job.config.JobState;
48+
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
4749
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
4850
import org.elasticsearch.xpack.ml.MachineLearning;
4951
import org.elasticsearch.xpack.ml.MlConfigMigrationEligibilityCheck;
@@ -81,6 +83,7 @@ public class TransportStartDatafeedAction extends TransportMasterNodeAction<Star
8183
private final Auditor auditor;
8284
private final MlConfigMigrationEligibilityCheck migrationEligibilityCheck;
8385
private final NamedXContentRegistry xContentRegistry;
86+
private final boolean remoteClusterSearchSupported;
8487

8588
@Inject
8689
public TransportStartDatafeedAction(Settings settings, TransportService transportService, ThreadPool threadPool,
@@ -99,6 +102,7 @@ public TransportStartDatafeedAction(Settings settings, TransportService transpor
99102
this.auditor = auditor;
100103
this.migrationEligibilityCheck = new MlConfigMigrationEligibilityCheck(settings, clusterService);
101104
this.xContentRegistry = xContentRegistry;
105+
this.remoteClusterSearchSupported = RemoteClusterService.ENABLE_REMOTE_CLUSTERS.get(settings);
102106
}
103107

104108
static void validate(Job job,
@@ -176,7 +180,7 @@ public void onFailure(Exception e) {
176180
};
177181

178182
// Verify data extractor factory can be created, then start persistent task
179-
Consumer<Job> createDataExtrator = job -> {
183+
Consumer<Job> createDataExtractor = job -> {
180184
if (RemoteClusterLicenseChecker.containsRemoteIndex(params.getDatafeedIndices())) {
181185
final RemoteClusterLicenseChecker remoteClusterLicenseChecker =
182186
new RemoteClusterLicenseChecker(client, XPackLicenseState::isMachineLearningAllowedForOperationMode);
@@ -188,6 +192,13 @@ public void onFailure(Exception e) {
188192
response -> {
189193
if (response.isSuccess() == false) {
190194
listener.onFailure(createUnlicensedError(params.getDatafeedId(), response));
195+
} else if (remoteClusterSearchSupported == false) {
196+
listener.onFailure(
197+
ExceptionsHelper.badRequestException(Messages.getMessage(
198+
Messages.DATAFEED_NEEDS_REMOTE_CLUSTER_SEARCH,
199+
datafeedConfigHolder.get().getId(),
200+
RemoteClusterLicenseChecker.remoteIndices(datafeedConfigHolder.get().getIndices()),
201+
clusterService.getNodeName())));
191202
} else {
192203
createDataExtractor(job, datafeedConfigHolder.get(), params, waitForTaskListener);
193204
}
@@ -208,7 +219,7 @@ public void onFailure(Exception e) {
208219
try {
209220
validate(job, datafeedConfigHolder.get(), tasks, xContentRegistry);
210221
auditDeprecations(datafeedConfigHolder.get(), job, auditor, xContentRegistry);
211-
createDataExtrator.accept(job);
222+
createDataExtractor.accept(job);
212223
} catch (Exception e) {
213224
listener.onFailure(e);
214225
}

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/DatafeedJobBuilder.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@
1212
import org.elasticsearch.common.settings.Settings;
1313
import org.elasticsearch.common.unit.TimeValue;
1414
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
15+
import org.elasticsearch.license.RemoteClusterLicenseChecker;
16+
import org.elasticsearch.transport.RemoteClusterService;
1517
import org.elasticsearch.xpack.core.ml.MlMetadata;
1618
import org.elasticsearch.xpack.core.ml.action.util.QueryPage;
1719
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
1820
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedJobValidator;
1921
import org.elasticsearch.xpack.core.ml.job.config.DataDescription;
2022
import org.elasticsearch.xpack.core.ml.job.config.Job;
23+
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
2124
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.DataCounts;
2225
import org.elasticsearch.xpack.core.ml.job.results.Bucket;
2326
import org.elasticsearch.xpack.core.ml.job.results.Result;
27+
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
2428
import org.elasticsearch.xpack.ml.datafeed.delayeddatacheck.DelayedDataDetector;
2529
import org.elasticsearch.xpack.ml.datafeed.delayeddatacheck.DelayedDataDetectorFactory;
2630
import org.elasticsearch.xpack.ml.datafeed.extractor.DataExtractorFactory;
@@ -30,6 +34,7 @@
3034
import org.elasticsearch.xpack.ml.notifications.Auditor;
3135

3236
import java.util.Collections;
37+
import java.util.List;
3338
import java.util.Objects;
3439
import java.util.concurrent.atomic.AtomicReference;
3540
import java.util.function.Consumer;
@@ -42,14 +47,18 @@ public class DatafeedJobBuilder {
4247
private final NamedXContentRegistry xContentRegistry;
4348
private final Auditor auditor;
4449
private final Supplier<Long> currentTimeSupplier;
50+
private final boolean remoteClusterSearchSupported;
51+
private final String nodeName;
4552

4653
public DatafeedJobBuilder(Client client, Settings settings, NamedXContentRegistry xContentRegistry,
47-
Auditor auditor, Supplier<Long> currentTimeSupplier) {
54+
Auditor auditor, Supplier<Long> currentTimeSupplier, String nodeName) {
4855
this.client = client;
4956
this.settings = Objects.requireNonNull(settings);
5057
this.xContentRegistry = Objects.requireNonNull(xContentRegistry);
5158
this.auditor = Objects.requireNonNull(auditor);
5259
this.currentTimeSupplier = Objects.requireNonNull(currentTimeSupplier);
60+
this.remoteClusterSearchSupported = RemoteClusterService.ENABLE_REMOTE_CLUSTERS.get(settings);
61+
this.nodeName = nodeName;
5362
}
5463

5564
void build(String datafeedId, ClusterState state, ActionListener<DatafeedJob> listener) {
@@ -152,6 +161,18 @@ void build(String datafeedId, JobResultsProvider jobResultsProvider, JobConfigPr
152161
datafeedConfig -> {
153162
try {
154163
datafeedConfigHolder.set(datafeedConfig);
164+
if (remoteClusterSearchSupported == false) {
165+
List<String> remoteIndices = RemoteClusterLicenseChecker.remoteIndices(datafeedConfig.getIndices());
166+
if (remoteIndices.isEmpty() == false) {
167+
listener.onFailure(
168+
ExceptionsHelper.badRequestException(Messages.getMessage(
169+
Messages.DATAFEED_NEEDS_REMOTE_CLUSTER_SEARCH,
170+
datafeedConfig.getId(),
171+
remoteIndices,
172+
nodeName)));
173+
return;
174+
}
175+
}
155176
// Is the job in the cluster state?
156177
Job job = MlMetadata.getMlMetadata(state).getJobs().get(datafeedConfig.getJobId());
157178
if (job != null) {

x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/datafeed/DatafeedJobBuilderTests.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
import org.elasticsearch.mock.orig.Mockito;
1616
import org.elasticsearch.test.ESTestCase;
1717
import org.elasticsearch.threadpool.ThreadPool;
18+
import org.elasticsearch.transport.RemoteClusterService;
1819
import org.elasticsearch.xpack.core.ml.action.util.QueryPage;
1920
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
2021
import org.elasticsearch.xpack.core.ml.job.config.DataDescription;
2122
import org.elasticsearch.xpack.core.ml.job.config.Job;
23+
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
2224
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.DataCounts;
2325
import org.elasticsearch.xpack.core.ml.job.results.Bucket;
2426
import org.elasticsearch.xpack.ml.job.persistence.JobConfigProvider;
@@ -61,7 +63,8 @@ public void init() {
6163
when(client.settings()).thenReturn(Settings.EMPTY);
6264
auditor = mock(Auditor.class);
6365
taskHandler = mock(Consumer.class);
64-
datafeedJobBuilder = new DatafeedJobBuilder(client, Settings.EMPTY, xContentRegistry(), auditor, System::currentTimeMillis);
66+
datafeedJobBuilder =
67+
new DatafeedJobBuilder(client, Settings.EMPTY, xContentRegistry(), auditor, System::currentTimeMillis, "test_node");
6568

6669
jobResultsProvider = mock(JobResultsProvider.class);
6770
Mockito.doAnswer(invocationOnMock -> {
@@ -202,6 +205,44 @@ public void testBuild_GivenBucketsRequestFails() {
202205
verify(taskHandler).accept(error);
203206
}
204207

208+
public void testBuildGivenRemoteIndicesButNoRemoteSearching() throws Exception {
209+
Settings settings = Settings.builder().put(RemoteClusterService.ENABLE_REMOTE_CLUSTERS.getKey(), false).build();
210+
datafeedJobBuilder =
211+
new DatafeedJobBuilder(
212+
client,
213+
settings,
214+
xContentRegistry(),
215+
auditor,
216+
System::currentTimeMillis,
217+
"test_node");
218+
DataDescription.Builder dataDescription = new DataDescription.Builder();
219+
dataDescription.setTimeField("time");
220+
Job.Builder jobBuilder = DatafeedManagerTests.createDatafeedJob();
221+
jobBuilder.setDataDescription(dataDescription);
222+
jobBuilder.setCreateTime(new Date());
223+
DatafeedConfig.Builder datafeed = DatafeedManagerTests.createDatafeedConfig("datafeed1", jobBuilder.getId());
224+
datafeed.setIndices(Collections.singletonList("remotecluster:index-*"));
225+
226+
AtomicBoolean wasHandlerCalled = new AtomicBoolean(false);
227+
ActionListener<DatafeedJob> datafeedJobHandler = ActionListener.wrap(
228+
datafeedJob -> fail("datafeed builder did not fail when remote index was given and remote clusters were not enabled"),
229+
e -> {
230+
assertThat(e.getMessage(), equalTo(Messages.getMessage(Messages.DATAFEED_NEEDS_REMOTE_CLUSTER_SEARCH,
231+
"datafeed1",
232+
"[remotecluster:index-*]",
233+
"test_node")));
234+
wasHandlerCalled.compareAndSet(false, true);
235+
}
236+
);
237+
238+
givenJob(jobBuilder);
239+
givenDatafeed(datafeed);
240+
ClusterState clusterState = ClusterState.builder(new ClusterName("datafeedjobbuildertest-cluster")).build();
241+
datafeedJobBuilder.build("datafeed1", jobResultsProvider, jobConfigProvider, datafeedConfigReader,
242+
clusterState, datafeedJobHandler);
243+
assertBusy(() -> wasHandlerCalled.get());
244+
}
245+
205246
private void givenJob(Job.Builder job) {
206247
Mockito.doAnswer(invocationOnMock -> {
207248
@SuppressWarnings("unchecked")

0 commit comments

Comments
 (0)