24
24
import org .elasticsearch .action .support .master .TransportMasterNodeAction ;
25
25
import org .elasticsearch .client .Client ;
26
26
import org .elasticsearch .client .ParentTaskAssigningClient ;
27
- import org .elasticsearch .cluster .AckedClusterStateUpdateTask ;
28
27
import org .elasticsearch .cluster .ClusterState ;
29
- import org .elasticsearch .cluster .ClusterStateUpdateTask ;
30
28
import org .elasticsearch .cluster .block .ClusterBlockException ;
31
29
import org .elasticsearch .cluster .block .ClusterBlockLevel ;
32
30
import org .elasticsearch .cluster .metadata .AliasMetaData ;
33
31
import org .elasticsearch .cluster .metadata .IndexNameExpressionResolver ;
34
- import org .elasticsearch .cluster .metadata .MetaData ;
35
32
import org .elasticsearch .cluster .service .ClusterService ;
36
33
import org .elasticsearch .common .CheckedConsumer ;
37
34
import org .elasticsearch .common .Nullable ;
52
49
import org .elasticsearch .tasks .TaskId ;
53
50
import org .elasticsearch .threadpool .ThreadPool ;
54
51
import org .elasticsearch .transport .TransportService ;
55
- import org .elasticsearch .xpack .core .ml .MlMetadata ;
56
52
import org .elasticsearch .xpack .core .ml .MlTasks ;
57
53
import org .elasticsearch .xpack .core .ml .action .DeleteJobAction ;
58
54
import org .elasticsearch .xpack .core .ml .action .GetModelSnapshotsAction ;
59
55
import org .elasticsearch .xpack .core .ml .action .KillProcessAction ;
60
56
import org .elasticsearch .xpack .core .ml .action .util .PageParams ;
61
57
import org .elasticsearch .xpack .core .ml .job .config .Job ;
58
+ import org .elasticsearch .xpack .core .ml .job .config .JobState ;
59
+ import org .elasticsearch .xpack .core .ml .job .config .JobTaskState ;
62
60
import org .elasticsearch .xpack .core .ml .job .messages .Messages ;
63
61
import org .elasticsearch .xpack .core .ml .job .persistence .AnomalyDetectorsIndex ;
64
62
import org .elasticsearch .xpack .core .ml .job .persistence .AnomalyDetectorsIndexFields ;
65
63
import org .elasticsearch .xpack .core .ml .job .process .autodetect .state .CategorizerState ;
66
64
import org .elasticsearch .xpack .core .ml .job .process .autodetect .state .ModelSnapshot ;
67
65
import org .elasticsearch .xpack .core .ml .job .process .autodetect .state .Quantiles ;
66
+ import org .elasticsearch .xpack .core .ml .utils .ExceptionsHelper ;
67
+ import org .elasticsearch .xpack .ml .datafeed .persistence .DatafeedConfigProvider ;
68
+ import org .elasticsearch .xpack .ml .job .persistence .JobConfigProvider ;
68
69
import org .elasticsearch .xpack .ml .job .persistence .JobDataDeleter ;
69
70
import org .elasticsearch .xpack .ml .job .persistence .JobResultsProvider ;
70
71
import org .elasticsearch .xpack .ml .notifications .Auditor ;
71
72
import org .elasticsearch .xpack .ml .utils .MlIndicesUtils ;
72
73
73
74
import java .util .ArrayList ;
75
+ import java .util .Collections ;
74
76
import java .util .HashMap ;
75
77
import java .util .HashSet ;
76
78
import java .util .List ;
77
79
import java .util .Map ;
78
80
import java .util .Set ;
81
+ import java .util .concurrent .atomic .AtomicReference ;
79
82
import java .util .function .Consumer ;
80
83
81
84
import static org .elasticsearch .xpack .core .ClientHelper .ML_ORIGIN ;
@@ -89,6 +92,8 @@ public class TransportDeleteJobAction extends TransportMasterNodeAction<DeleteJo
89
92
private final PersistentTasksService persistentTasksService ;
90
93
private final Auditor auditor ;
91
94
private final JobResultsProvider jobResultsProvider ;
95
+ private final JobConfigProvider jobConfigProvider ;
96
+ private final DatafeedConfigProvider datafeedConfigProvider ;
92
97
93
98
/**
94
99
* A map of task listeners by job_id.
@@ -102,13 +107,16 @@ public class TransportDeleteJobAction extends TransportMasterNodeAction<DeleteJo
102
107
public TransportDeleteJobAction (Settings settings , TransportService transportService , ClusterService clusterService ,
103
108
ThreadPool threadPool , ActionFilters actionFilters ,
104
109
IndexNameExpressionResolver indexNameExpressionResolver , PersistentTasksService persistentTasksService ,
105
- Client client , Auditor auditor , JobResultsProvider jobResultsProvider ) {
110
+ Client client , Auditor auditor , JobResultsProvider jobResultsProvider ,
111
+ JobConfigProvider jobConfigProvider , DatafeedConfigProvider datafeedConfigProvider ) {
106
112
super (settings , DeleteJobAction .NAME , transportService , clusterService , threadPool , actionFilters ,
107
113
indexNameExpressionResolver , DeleteJobAction .Request ::new );
108
114
this .client = client ;
109
115
this .persistentTasksService = persistentTasksService ;
110
116
this .auditor = auditor ;
111
117
this .jobResultsProvider = jobResultsProvider ;
118
+ this .jobConfigProvider = jobConfigProvider ;
119
+ this .datafeedConfigProvider = datafeedConfigProvider ;
112
120
this .listenersByJobId = new HashMap <>();
113
121
}
114
122
@@ -137,6 +145,10 @@ protected void masterOperation(Task task, DeleteJobAction.Request request, Clust
137
145
ActionListener <AcknowledgedResponse > listener ) {
138
146
logger .debug ("Deleting job '{}'" , request .getJobId ());
139
147
148
+ if (request .isForce () == false ) {
149
+ checkJobIsNotOpen (request .getJobId (), state );
150
+ }
151
+
140
152
TaskId taskId = new TaskId (clusterService .localNode ().getId (), task .getId ());
141
153
ParentTaskAssigningClient parentTaskClient = new ParentTaskAssigningClient (client , taskId );
142
154
@@ -175,7 +187,7 @@ protected void masterOperation(Task task, DeleteJobAction.Request request, Clust
175
187
finalListener .onFailure (e );
176
188
});
177
189
178
- markJobAsDeleting (request .getJobId (), markAsDeletingListener , request . isForce () );
190
+ markJobAsDeletingIfNotUsed (request .getJobId (), markAsDeletingListener );
179
191
}
180
192
181
193
private void notifyListeners (String jobId , @ Nullable AcknowledgedResponse ack , @ Nullable Exception error ) {
@@ -211,33 +223,15 @@ private void normalDeleteJob(ParentTaskAssigningClient parentTaskClient, DeleteJ
211
223
}
212
224
};
213
225
214
- // Step 3. When the physical storage has been deleted, remove from Cluster State
226
+ // Step 3. When the physical storage has been deleted, delete the job config document
215
227
// -------
216
- CheckedConsumer <Boolean , Exception > deleteJobStateHandler = response -> clusterService .submitStateUpdateTask (
217
- "delete-job-" + jobId ,
218
- new AckedClusterStateUpdateTask <Boolean >(request , ActionListener .wrap (apiResponseHandler , listener ::onFailure )) {
219
-
220
- @ Override
221
- protected Boolean newResponse (boolean acknowledged ) {
222
- return acknowledged && response ;
223
- }
224
-
225
- @ Override
226
- public ClusterState execute (ClusterState currentState ) {
227
- MlMetadata currentMlMetadata = MlMetadata .getMlMetadata (currentState );
228
- if (currentMlMetadata .getJobs ().containsKey (jobId ) == false ) {
229
- // We wouldn't have got here if the job never existed so
230
- // the Job must have been deleted by another action.
231
- // Don't error in this case
232
- return currentState ;
233
- }
234
-
235
- MlMetadata .Builder builder = new MlMetadata .Builder (currentMlMetadata );
236
- builder .deleteJob (jobId , currentState .getMetaData ().custom (PersistentTasksCustomMetaData .TYPE ));
237
- return buildNewClusterState (currentState , builder );
238
- }
239
- });
240
-
228
+ // Don't report an error if the document has already been deleted
229
+ CheckedConsumer <Boolean , Exception > deleteJobStateHandler = response -> jobConfigProvider .deleteJob (jobId , false ,
230
+ ActionListener .wrap (
231
+ deleteResponse -> apiResponseHandler .accept (Boolean .TRUE ),
232
+ listener ::onFailure
233
+ )
234
+ );
241
235
242
236
// Step 2. Remove the job from any calendars
243
237
CheckedConsumer <Boolean , Exception > removeFromCalendarsHandler = response -> jobResultsProvider .removeJobFromCalendars (jobId ,
@@ -251,26 +245,26 @@ public ClusterState execute(ClusterState currentState) {
251
245
private void deleteJobDocuments (ParentTaskAssigningClient parentTaskClient , String jobId ,
252
246
CheckedConsumer <Boolean , Exception > finishedHandler , Consumer <Exception > failureHandler ) {
253
247
254
- final String indexName = AnomalyDetectorsIndex .getPhysicalIndexFromState (clusterService .state (), jobId );
255
- final String indexPattern = indexName + "-*" ;
248
+ AtomicReference <String > indexName = new AtomicReference <>();
256
249
257
250
final ActionListener <AcknowledgedResponse > completionHandler = ActionListener .wrap (
258
251
response -> finishedHandler .accept (response .isAcknowledged ()),
259
252
failureHandler );
260
253
261
- // Step 7 . If we did not drop the index and after DBQ state done, we delete the aliases
254
+ // Step 8 . If we did not drop the index and after DBQ state done, we delete the aliases
262
255
ActionListener <BulkByScrollResponse > dbqHandler = ActionListener .wrap (
263
256
bulkByScrollResponse -> {
264
257
if (bulkByScrollResponse == null ) { // no action was taken by DBQ, assume Index was deleted
265
258
completionHandler .onResponse (new AcknowledgedResponse (true ));
266
259
} else {
267
260
if (bulkByScrollResponse .isTimedOut ()) {
268
- logger .warn ("[{}] DeleteByQuery for indices [{}, {}] timed out." , jobId , indexName , indexPattern );
261
+ logger .warn ("[{}] DeleteByQuery for indices [{}, {}] timed out." , jobId , indexName .get (),
262
+ indexName .get () + "-*" );
269
263
}
270
264
if (!bulkByScrollResponse .getBulkFailures ().isEmpty ()) {
271
265
logger .warn ("[{}] {} failures and {} conflicts encountered while running DeleteByQuery on indices [{}, {}]." ,
272
266
jobId , bulkByScrollResponse .getBulkFailures ().size (), bulkByScrollResponse .getVersionConflicts (),
273
- indexName , indexPattern );
267
+ indexName . get (), indexName . get () + "-*" );
274
268
for (BulkItemResponse .Failure failure : bulkByScrollResponse .getBulkFailures ()) {
275
269
logger .warn ("DBQ failure: " + failure );
276
270
}
@@ -280,12 +274,13 @@ private void deleteJobDocuments(ParentTaskAssigningClient parentTaskClient, Stri
280
274
},
281
275
failureHandler );
282
276
283
- // Step 6 . If we did not delete the index, we run a delete by query
277
+ // Step 7 . If we did not delete the index, we run a delete by query
284
278
ActionListener <Boolean > deleteByQueryExecutor = ActionListener .wrap (
285
279
response -> {
286
280
if (response ) {
287
- logger .info ("Running DBQ on [" + indexName + "," + indexPattern + "] for job [" + jobId + "]" );
288
- DeleteByQueryRequest request = new DeleteByQueryRequest (indexName , indexPattern );
281
+ String indexPattern = indexName .get () + "-*" ;
282
+ logger .info ("Running DBQ on [" + indexName .get () + "," + indexPattern + "] for job [" + jobId + "]" );
283
+ DeleteByQueryRequest request = new DeleteByQueryRequest (indexName .get (), indexPattern );
289
284
ConstantScoreQueryBuilder query =
290
285
new ConstantScoreQueryBuilder (new TermQueryBuilder (Job .ID .getPreferredName (), jobId ));
291
286
request .setQuery (query );
@@ -301,15 +296,15 @@ private void deleteJobDocuments(ParentTaskAssigningClient parentTaskClient, Stri
301
296
},
302
297
failureHandler );
303
298
304
- // Step 5 . If we have any hits, that means we are NOT the only job on this index, and should not delete it
299
+ // Step 6 . If we have any hits, that means we are NOT the only job on this index, and should not delete it
305
300
// if we do not have any hits, we can drop the index and then skip the DBQ and alias deletion
306
301
ActionListener <SearchResponse > customIndexSearchHandler = ActionListener .wrap (
307
302
searchResponse -> {
308
303
if (searchResponse == null || searchResponse .getHits ().totalHits > 0 ) {
309
304
deleteByQueryExecutor .onResponse (true ); // We need to run DBQ and alias deletion
310
305
} else {
311
- logger .info ("Running DELETE Index on [" + indexName + "] for job [" + jobId + "]" );
312
- DeleteIndexRequest request = new DeleteIndexRequest (indexName );
306
+ logger .info ("Running DELETE Index on [" + indexName . get () + "] for job [" + jobId + "]" );
307
+ DeleteIndexRequest request = new DeleteIndexRequest (indexName . get () );
313
308
request .indicesOptions (IndicesOptions .lenientExpandOpen ());
314
309
// If we have deleted the index, then we don't need to delete the aliases or run the DBQ
315
310
executeAsyncWithOrigin (
@@ -331,9 +326,11 @@ private void deleteJobDocuments(ParentTaskAssigningClient parentTaskClient, Stri
331
326
}
332
327
);
333
328
334
- // Step 4. Determine if we are on a shared index by looking at `.ml-anomalies-shared` or the custom index's aliases
335
- ActionListener <Boolean > deleteCategorizerStateHandler = ActionListener .wrap (
336
- response -> {
329
+ // Step 5. Determine if we are on a shared index by looking at `.ml-anomalies-shared` or the custom index's aliases
330
+ ActionListener <Job .Builder > getJobHandler = ActionListener .wrap (
331
+ builder -> {
332
+ Job job = builder .build ();
333
+ indexName .set (job .getResultsIndexName ());
337
334
if (indexName .equals (AnomalyDetectorsIndexFields .RESULTS_INDEX_PREFIX +
338
335
AnomalyDetectorsIndexFields .RESULTS_INDEX_DEFAULT )) {
339
336
//don't bother searching the index any further, we are on the default shared
@@ -344,14 +341,22 @@ private void deleteJobDocuments(ParentTaskAssigningClient parentTaskClient, Stri
344
341
.query (QueryBuilders .boolQuery ().filter (
345
342
QueryBuilders .boolQuery ().mustNot (QueryBuilders .termQuery (Job .ID .getPreferredName (), jobId ))));
346
343
347
- SearchRequest searchRequest = new SearchRequest (indexName );
344
+ SearchRequest searchRequest = new SearchRequest (indexName . get () );
348
345
searchRequest .source (source );
349
346
executeAsyncWithOrigin (parentTaskClient , ML_ORIGIN , SearchAction .INSTANCE , searchRequest , customIndexSearchHandler );
350
347
}
351
348
},
352
349
failureHandler
353
350
);
354
351
352
+ // Step 4. Get the job as the result index name is required
353
+ ActionListener <Boolean > deleteCategorizerStateHandler = ActionListener .wrap (
354
+ response -> {
355
+ jobConfigProvider .getJob (jobId , getJobHandler );
356
+ },
357
+ failureHandler
358
+ );
359
+
355
360
// Step 3. Delete quantiles done, delete the categorizer state
356
361
ActionListener <Boolean > deleteQuantilesHandler = ActionListener .wrap (
357
362
response -> deleteCategorizerState (parentTaskClient , jobId , 1 , deleteCategorizerStateHandler ),
@@ -554,36 +559,28 @@ public void onFailure(Exception e) {
554
559
}
555
560
}
556
561
557
- private void markJobAsDeleting (String jobId , ActionListener <Boolean > listener , boolean force ) {
558
- clusterService .submitStateUpdateTask ("mark-job-as-deleted" , new ClusterStateUpdateTask () {
559
- @ Override
560
- public ClusterState execute (ClusterState currentState ) {
561
- PersistentTasksCustomMetaData tasks = currentState .metaData ().custom (PersistentTasksCustomMetaData .TYPE );
562
- MlMetadata .Builder builder = new MlMetadata .Builder (MlMetadata .getMlMetadata (currentState ));
563
- builder .markJobAsDeleting (jobId , tasks , force );
564
- return buildNewClusterState (currentState , builder );
565
- }
566
-
567
- @ Override
568
- public void onFailure (String source , Exception e ) {
569
- listener .onFailure (e );
570
- }
571
-
572
- @ Override
573
- public void clusterStateProcessed (String source , ClusterState oldState , ClusterState newState ) {
574
- logger .debug ("Job [" + jobId + "] is successfully marked as deleted" );
575
- listener .onResponse (true );
576
- }
577
- });
562
+ private void checkJobIsNotOpen (String jobId , ClusterState state ) {
563
+ PersistentTasksCustomMetaData tasks = state .metaData ().custom (PersistentTasksCustomMetaData .TYPE );
564
+ PersistentTasksCustomMetaData .PersistentTask <?> jobTask = MlTasks .getJobTask (jobId , tasks );
565
+ if (jobTask != null ) {
566
+ JobTaskState jobTaskState = (JobTaskState ) jobTask .getState ();
567
+ throw ExceptionsHelper .conflictStatusException ("Cannot delete job [" + jobId + "] because the job is "
568
+ + ((jobTaskState == null ) ? JobState .OPENING : jobTaskState .getState ()));
569
+ }
578
570
}
579
571
580
- static boolean jobIsDeletedFromState (String jobId , ClusterState clusterState ) {
581
- return !MlMetadata .getMlMetadata (clusterState ).getJobs ().containsKey (jobId );
582
- }
572
+ private void markJobAsDeletingIfNotUsed (String jobId , ActionListener <Boolean > listener ) {
583
573
584
- private static ClusterState buildNewClusterState (ClusterState currentState , MlMetadata .Builder builder ) {
585
- ClusterState .Builder newState = ClusterState .builder (currentState );
586
- newState .metaData (MetaData .builder (currentState .getMetaData ()).putCustom (MlMetadata .TYPE , builder .build ()).build ());
587
- return newState .build ();
574
+ datafeedConfigProvider .findDatafeedsForJobIds (Collections .singletonList (jobId ), ActionListener .wrap (
575
+ datafeedIds -> {
576
+ if (datafeedIds .isEmpty () == false ) {
577
+ listener .onFailure (ExceptionsHelper .conflictStatusException ("Cannot delete job [" + jobId + "] because datafeed ["
578
+ + datafeedIds .iterator ().next () + "] refers to it" ));
579
+ return ;
580
+ }
581
+ jobConfigProvider .markJobAsDeleting (jobId , listener );
582
+ },
583
+ listener ::onFailure
584
+ ));
588
585
}
589
586
}
0 commit comments