Skip to content

Commit 6ea1051

Browse files
authored
Execute EnrichPolicyRunner on a non dedicated master node. (#77164)
Backporting #76881 to 7.x branch. Introduce an internal action that the execute policy action delegates to. This to ensure that the actual policy execution is never executed on the elected master node or dedicated master nodes. In case the cluster consists out of a single node then the internal action will attempt to execute on the current/local node. The actual enrich policy execution is encapsulated in the `EnrichPolicyRunner` class. This class manages the execution of several API calls, so this itself isn't doing anything heavy. However the coordination of these api calls (in particular the reindex api call) may involve some non-neglectable work/overhead and this shouldn't be performed on the elected master or any other dedicated master node. Closes #70436
1 parent a2678b5 commit 6ea1051

File tree

9 files changed

+525
-318
lines changed

9 files changed

+525
-318
lines changed

x-pack/plugin/enrich/src/internalClusterTest/java/org/elasticsearch/xpack/enrich/EnrichMultiNodeIT.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import org.apache.lucene.search.TotalHits;
1010
import org.elasticsearch.ResourceNotFoundException;
1111
import org.elasticsearch.action.ActionFuture;
12+
import org.elasticsearch.action.admin.cluster.node.tasks.get.GetTaskRequest;
1213
import org.elasticsearch.action.admin.cluster.node.tasks.get.GetTaskResponse;
14+
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
1315
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
1416
import org.elasticsearch.action.bulk.BulkItemResponse;
1517
import org.elasticsearch.action.bulk.BulkRequest;
@@ -20,6 +22,7 @@
2022
import org.elasticsearch.action.ingest.PutPipelineRequest;
2123
import org.elasticsearch.action.search.SearchRequest;
2224
import org.elasticsearch.action.search.SearchResponse;
25+
import org.elasticsearch.cluster.node.DiscoveryNodes;
2326
import org.elasticsearch.cluster.service.ClusterService;
2427
import org.elasticsearch.common.bytes.BytesArray;
2528
import org.elasticsearch.common.settings.Settings;
@@ -48,12 +51,17 @@
4851
import java.util.Set;
4952

5053
import static org.elasticsearch.test.NodeRoles.ingestOnlyNode;
54+
import static org.elasticsearch.test.NodeRoles.masterOnlyNode;
5155
import static org.elasticsearch.test.NodeRoles.nonIngestNode;
56+
import static org.elasticsearch.test.NodeRoles.nonMasterNode;
57+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
5258
import static org.elasticsearch.xpack.enrich.MatchProcessorTests.mapOf;
5359
import static org.hamcrest.Matchers.equalTo;
5460
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
5561
import static org.hamcrest.Matchers.is;
62+
import static org.hamcrest.Matchers.not;
5663
import static org.hamcrest.Matchers.notNullValue;
64+
import static org.hamcrest.Matchers.nullValue;
5765

5866
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0, numClientNodes = 0)
5967
public class EnrichMultiNodeIT extends ESIntegTestCase {
@@ -150,6 +158,67 @@ public void testEnrichNoIngestNodes() {
150158
assertThat(e.getMessage(), equalTo("no ingest nodes in this cluster"));
151159
}
152160

161+
public void testExecutePolicyWithDedicatedMasterNodes() throws Exception {
162+
internalCluster().startNodes(3, masterOnlyNode());
163+
internalCluster().startNodes(2, nonMasterNode());
164+
ensureStableCluster(5, (String) null);
165+
166+
assertAcked(prepareCreate(SOURCE_INDEX_NAME).addMapping("_doc", MATCH_FIELD, "type=keyword"));
167+
EnrichPolicy enrichPolicy = new EnrichPolicy(
168+
EnrichPolicy.MATCH_TYPE,
169+
null,
170+
Collections.singletonList(SOURCE_INDEX_NAME),
171+
MATCH_FIELD,
172+
Arrays.asList(DECORATE_FIELDS)
173+
);
174+
PutEnrichPolicyAction.Request putPolicyRequest = new PutEnrichPolicyAction.Request(POLICY_NAME, enrichPolicy);
175+
assertAcked(client().execute(PutEnrichPolicyAction.INSTANCE, putPolicyRequest).actionGet());
176+
ExecuteEnrichPolicyAction.Request executePolicyRequest = new ExecuteEnrichPolicyAction.Request(POLICY_NAME);
177+
executePolicyRequest.setWaitForCompletion(false); // From tne returned taks id the node that executes the policy can be determined
178+
ExecuteEnrichPolicyAction.Response executePolicyResponse = client().execute(
179+
ExecuteEnrichPolicyAction.INSTANCE,
180+
executePolicyRequest
181+
).actionGet();
182+
assertThat(executePolicyResponse.getStatus(), nullValue());
183+
assertThat(executePolicyResponse.getTaskId(), notNullValue());
184+
185+
GetTaskRequest getTaskRequest = new GetTaskRequest().setTaskId(executePolicyResponse.getTaskId()).setWaitForCompletion(true);
186+
client().admin().cluster().getTask(getTaskRequest).actionGet();
187+
188+
DiscoveryNodes discoNodes = client().admin().cluster().state(new ClusterStateRequest()).actionGet().getState().nodes();
189+
assertThat(discoNodes.get(executePolicyResponse.getTaskId().getNodeId()).isMasterNode(), is(false));
190+
}
191+
192+
public void testExecutePolicyNeverOnElectedMaster() throws Exception {
193+
internalCluster().startNodes(3);
194+
ensureStableCluster(3, (String) null);
195+
196+
assertAcked(prepareCreate(SOURCE_INDEX_NAME).addMapping("_doc", MATCH_FIELD, "type=keyword"));
197+
EnrichPolicy enrichPolicy = new EnrichPolicy(
198+
EnrichPolicy.MATCH_TYPE,
199+
null,
200+
Collections.singletonList(SOURCE_INDEX_NAME),
201+
MATCH_FIELD,
202+
Arrays.asList(DECORATE_FIELDS)
203+
);
204+
PutEnrichPolicyAction.Request putPolicyRequest = new PutEnrichPolicyAction.Request(POLICY_NAME, enrichPolicy);
205+
assertAcked(client().execute(PutEnrichPolicyAction.INSTANCE, putPolicyRequest).actionGet());
206+
ExecuteEnrichPolicyAction.Request executePolicyRequest = new ExecuteEnrichPolicyAction.Request(POLICY_NAME);
207+
executePolicyRequest.setWaitForCompletion(false); // From tne returned taks id the node that executes the policy can be determined
208+
ExecuteEnrichPolicyAction.Response executePolicyResponse = client().execute(
209+
ExecuteEnrichPolicyAction.INSTANCE,
210+
executePolicyRequest
211+
).actionGet();
212+
assertThat(executePolicyResponse.getStatus(), nullValue());
213+
assertThat(executePolicyResponse.getTaskId(), notNullValue());
214+
215+
GetTaskRequest getTaskRequest = new GetTaskRequest().setTaskId(executePolicyResponse.getTaskId()).setWaitForCompletion(true);
216+
client().admin().cluster().getTask(getTaskRequest).actionGet();
217+
218+
DiscoveryNodes discoNodes = client().admin().cluster().state(new ClusterStateRequest()).actionGet().getState().nodes();
219+
assertThat(executePolicyResponse.getTaskId().getNodeId(), not(equalTo(discoNodes.getMasterNodeId())));
220+
}
221+
153222
private static void enrich(List<String> keys, String coordinatingNode) {
154223
int numDocs = 256;
155224
BulkRequest bulkRequest = new BulkRequest("my-index");

x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.elasticsearch.xpack.enrich.action.EnrichCoordinatorStatsAction;
5050
import org.elasticsearch.xpack.enrich.action.EnrichReindexAction;
5151
import org.elasticsearch.xpack.enrich.action.EnrichShardMultiSearchAction;
52+
import org.elasticsearch.xpack.enrich.action.InternalExecutePolicyAction;
5253
import org.elasticsearch.xpack.enrich.action.TransportDeleteEnrichPolicyAction;
5354
import org.elasticsearch.xpack.enrich.action.TransportEnrichReindexAction;
5455
import org.elasticsearch.xpack.enrich.action.TransportEnrichStatsAction;
@@ -155,7 +156,8 @@ protected XPackLicenseState getLicenseState() {
155156
new ActionHandler<>(EnrichCoordinatorProxyAction.INSTANCE, EnrichCoordinatorProxyAction.TransportAction.class),
156157
new ActionHandler<>(EnrichShardMultiSearchAction.INSTANCE, EnrichShardMultiSearchAction.TransportAction.class),
157158
new ActionHandler<>(EnrichCoordinatorStatsAction.INSTANCE, EnrichCoordinatorStatsAction.TransportAction.class),
158-
new ActionHandler<>(EnrichReindexAction.INSTANCE, TransportEnrichReindexAction.class)
159+
new ActionHandler<>(EnrichReindexAction.INSTANCE, TransportEnrichReindexAction.class),
160+
new ActionHandler<>(InternalExecutePolicyAction.INSTANCE, InternalExecutePolicyAction.Transport.class)
159161
);
160162
}
161163

@@ -196,6 +198,15 @@ public Collection<Object> createComponents(
196198
}
197199

198200
EnrichPolicyLocks enrichPolicyLocks = new EnrichPolicyLocks();
201+
EnrichPolicyExecutor enrichPolicyExecutor = new EnrichPolicyExecutor(
202+
settings,
203+
clusterService,
204+
client,
205+
threadPool,
206+
expressionResolver,
207+
enrichPolicyLocks,
208+
System::currentTimeMillis
209+
);
199210
EnrichPolicyMaintenanceService enrichPolicyMaintenanceService = new EnrichPolicyMaintenanceService(
200211
settings,
201212
client,
@@ -207,7 +218,8 @@ public Collection<Object> createComponents(
207218
return Arrays.asList(
208219
enrichPolicyLocks,
209220
new EnrichCoordinatorProxyAction.Coordinator(client, settings),
210-
enrichPolicyMaintenanceService
221+
enrichPolicyMaintenanceService,
222+
enrichPolicyExecutor
211223
);
212224
}
213225

x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPolicyExecutor.java

Lines changed: 45 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,19 @@
88
package org.elasticsearch.xpack.enrich;
99

1010
import org.elasticsearch.action.ActionListener;
11+
import org.elasticsearch.action.admin.cluster.node.tasks.get.GetTaskRequest;
1112
import org.elasticsearch.client.Client;
1213
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
1314
import org.elasticsearch.cluster.service.ClusterService;
1415
import org.elasticsearch.common.settings.Settings;
1516
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
16-
import org.elasticsearch.tasks.Task;
17-
import org.elasticsearch.tasks.TaskAwareRequest;
18-
import org.elasticsearch.tasks.TaskId;
19-
import org.elasticsearch.tasks.TaskListener;
20-
import org.elasticsearch.tasks.TaskManager;
2117
import org.elasticsearch.threadpool.ThreadPool;
2218
import org.elasticsearch.xpack.core.enrich.EnrichPolicy;
2319
import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction;
2420
import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyStatus;
21+
import org.elasticsearch.xpack.enrich.action.InternalExecutePolicyAction;
2522

26-
import java.util.Map;
2723
import java.util.concurrent.Semaphore;
28-
import java.util.function.BiConsumer;
2924
import java.util.function.LongSupplier;
3025

3126
public class EnrichPolicyExecutor {
@@ -34,7 +29,6 @@ public class EnrichPolicyExecutor {
3429

3530
private final ClusterService clusterService;
3631
private final Client client;
37-
private final TaskManager taskManager;
3832
private final ThreadPool threadPool;
3933
private final IndexNameExpressionResolver indexNameExpressionResolver;
4034
private final LongSupplier nowSupplier;
@@ -48,15 +42,13 @@ public EnrichPolicyExecutor(
4842
Settings settings,
4943
ClusterService clusterService,
5044
Client client,
51-
TaskManager taskManager,
5245
ThreadPool threadPool,
5346
IndexNameExpressionResolver indexNameExpressionResolver,
5447
EnrichPolicyLocks policyLocks,
5548
LongSupplier nowSupplier
5649
) {
5750
this.clusterService = clusterService;
5851
this.client = client;
59-
this.taskManager = taskManager;
6052
this.threadPool = threadPool;
6153
this.indexNameExpressionResolver = indexNameExpressionResolver;
6254
this.nowSupplier = nowSupplier;
@@ -67,6 +59,43 @@ public EnrichPolicyExecutor(
6759
this.policyExecutionPermits = new Semaphore(maximumConcurrentPolicyExecutions);
6860
}
6961

62+
public void coordinatePolicyExecution(
63+
ExecuteEnrichPolicyAction.Request request,
64+
ActionListener<ExecuteEnrichPolicyAction.Response> listener
65+
) {
66+
tryLockingPolicy(request.getName());
67+
try {
68+
client.execute(InternalExecutePolicyAction.INSTANCE, request, ActionListener.wrap(response -> {
69+
if (response.getStatus() != null) {
70+
releasePolicy(request.getName());
71+
listener.onResponse(response);
72+
} else {
73+
waitAndThenRelease(request.getName(), response);
74+
listener.onResponse(response);
75+
}
76+
}, e -> {
77+
releasePolicy(request.getName());
78+
listener.onFailure(e);
79+
}));
80+
} catch (Exception e) {
81+
// Be sure to unlock if submission failed.
82+
releasePolicy(request.getName());
83+
throw e;
84+
}
85+
}
86+
87+
public void runPolicyLocally(ExecuteEnrichPolicyTask task, String policyName, ActionListener<ExecuteEnrichPolicyStatus> listener) {
88+
try {
89+
EnrichPolicy policy = EnrichStore.getPolicy(policyName, clusterService.state());
90+
task.setStatus(new ExecuteEnrichPolicyStatus(ExecuteEnrichPolicyStatus.PolicyPhases.SCHEDULED));
91+
Runnable runnable = createPolicyRunner(policyName, policy, task, listener);
92+
threadPool.executor(ThreadPool.Names.GENERIC).execute(runnable);
93+
} catch (Exception e) {
94+
task.setStatus(new ExecuteEnrichPolicyStatus(ExecuteEnrichPolicyStatus.PolicyPhases.FAILED));
95+
throw e;
96+
}
97+
}
98+
7099
private void tryLockingPolicy(String policyName) {
71100
policyLocks.lockPolicy(policyName);
72101
if (policyExecutionPermits.tryAcquire() == false) {
@@ -91,49 +120,14 @@ private void releasePolicy(String policyName) {
91120
}
92121
}
93122

94-
private class PolicyCompletionListener implements ActionListener<ExecuteEnrichPolicyStatus> {
95-
private final String policyName;
96-
private final ExecuteEnrichPolicyTask task;
97-
private final BiConsumer<Task, ExecuteEnrichPolicyStatus> onResponse;
98-
private final BiConsumer<Task, Exception> onFailure;
99-
100-
PolicyCompletionListener(
101-
String policyName,
102-
ExecuteEnrichPolicyTask task,
103-
BiConsumer<Task, ExecuteEnrichPolicyStatus> onResponse,
104-
BiConsumer<Task, Exception> onFailure
105-
) {
106-
this.policyName = policyName;
107-
this.task = task;
108-
this.onResponse = onResponse;
109-
this.onFailure = onFailure;
110-
}
111-
112-
@Override
113-
public void onResponse(ExecuteEnrichPolicyStatus status) {
114-
assert ExecuteEnrichPolicyStatus.PolicyPhases.COMPLETE.equals(status.getPhase()) : "incomplete task returned";
115-
releasePolicy(policyName);
116-
try {
117-
taskManager.unregister(task);
118-
} finally {
119-
onResponse.accept(task, status);
120-
}
121-
}
122-
123-
@Override
124-
public void onFailure(Exception e) {
125-
// Set task status to failed to avoid having to catch and rethrow exceptions everywhere
126-
task.setStatus(new ExecuteEnrichPolicyStatus(ExecuteEnrichPolicyStatus.PolicyPhases.FAILED));
127-
releasePolicy(policyName);
128-
try {
129-
taskManager.unregister(task);
130-
} finally {
131-
onFailure.accept(task, e);
132-
}
133-
}
123+
private void waitAndThenRelease(String policyName, ExecuteEnrichPolicyAction.Response response) {
124+
GetTaskRequest getTaskRequest = new GetTaskRequest();
125+
getTaskRequest.setTaskId(response.getTaskId());
126+
getTaskRequest.setWaitForCompletion(true);
127+
client.admin().cluster().getTask(getTaskRequest, ActionListener.wrap(() -> releasePolicy(policyName)));
134128
}
135129

136-
protected Runnable createPolicyRunner(
130+
private Runnable createPolicyRunner(
137131
String policyName,
138132
EnrichPolicy policy,
139133
ExecuteEnrichPolicyTask task,
@@ -153,94 +147,4 @@ protected Runnable createPolicyRunner(
153147
);
154148
}
155149

156-
private EnrichPolicy getPolicy(ExecuteEnrichPolicyAction.Request request) {
157-
// Look up policy in policy store and execute it
158-
EnrichPolicy policy = EnrichStore.getPolicy(request.getName(), clusterService.state());
159-
if (policy == null) {
160-
throw new IllegalArgumentException("Policy execution failed. Could not locate policy with id [" + request.getName() + "]");
161-
}
162-
return policy;
163-
}
164-
165-
public Task runPolicy(ExecuteEnrichPolicyAction.Request request, ActionListener<ExecuteEnrichPolicyStatus> listener) {
166-
return runPolicy(request, getPolicy(request), listener);
167-
}
168-
169-
public Task runPolicy(ExecuteEnrichPolicyAction.Request request, TaskListener<ExecuteEnrichPolicyStatus> listener) {
170-
return runPolicy(request, getPolicy(request), listener);
171-
}
172-
173-
public Task runPolicy(
174-
ExecuteEnrichPolicyAction.Request request,
175-
EnrichPolicy policy,
176-
ActionListener<ExecuteEnrichPolicyStatus> listener
177-
) {
178-
return runPolicy(request, policy, (t, r) -> listener.onResponse(r), (t, e) -> listener.onFailure(e));
179-
}
180-
181-
public Task runPolicy(
182-
ExecuteEnrichPolicyAction.Request request,
183-
EnrichPolicy policy,
184-
TaskListener<ExecuteEnrichPolicyStatus> listener
185-
) {
186-
return runPolicy(request, policy, listener::onResponse, listener::onFailure);
187-
}
188-
189-
private Task runPolicy(
190-
ExecuteEnrichPolicyAction.Request request,
191-
EnrichPolicy policy,
192-
BiConsumer<Task, ExecuteEnrichPolicyStatus> onResponse,
193-
BiConsumer<Task, Exception> onFailure
194-
) {
195-
tryLockingPolicy(request.getName());
196-
try {
197-
return runPolicyTask(request, policy, onResponse, onFailure);
198-
} catch (Exception e) {
199-
// Be sure to unlock if submission failed.
200-
releasePolicy(request.getName());
201-
throw e;
202-
}
203-
}
204-
205-
private Task runPolicyTask(
206-
final ExecuteEnrichPolicyAction.Request request,
207-
EnrichPolicy policy,
208-
BiConsumer<Task, ExecuteEnrichPolicyStatus> onResponse,
209-
BiConsumer<Task, Exception> onFailure
210-
) {
211-
Task asyncTask = taskManager.register("enrich", TASK_ACTION, new TaskAwareRequest() {
212-
@Override
213-
public void setParentTask(TaskId taskId) {
214-
request.setParentTask(taskId);
215-
}
216-
217-
@Override
218-
public TaskId getParentTask() {
219-
return request.getParentTask();
220-
}
221-
222-
@Override
223-
public Task createTask(long id, String type, String action, TaskId parentTaskId, Map<String, String> headers) {
224-
return new ExecuteEnrichPolicyTask(id, type, action, getDescription(), parentTaskId, headers);
225-
}
226-
227-
@Override
228-
public String getDescription() {
229-
return request.getName();
230-
}
231-
});
232-
ExecuteEnrichPolicyTask task = (ExecuteEnrichPolicyTask) asyncTask;
233-
try {
234-
task.setStatus(new ExecuteEnrichPolicyStatus(ExecuteEnrichPolicyStatus.PolicyPhases.SCHEDULED));
235-
PolicyCompletionListener completionListener = new PolicyCompletionListener(request.getName(), task, onResponse, onFailure);
236-
Runnable runnable = createPolicyRunner(request.getName(), policy, task, completionListener);
237-
threadPool.executor(ThreadPool.Names.GENERIC).execute(runnable);
238-
return asyncTask;
239-
} catch (Exception e) {
240-
// Unregister task in case of exception
241-
task.setStatus(new ExecuteEnrichPolicyStatus(ExecuteEnrichPolicyStatus.PolicyPhases.FAILED));
242-
taskManager.unregister(asyncTask);
243-
throw e;
244-
}
245-
}
246150
}

0 commit comments

Comments
 (0)