23
23
import org .elasticsearch .cluster .ClusterState ;
24
24
import org .elasticsearch .cluster .service .ClusterService ;
25
25
import org .elasticsearch .common .Strings ;
26
- import org .elasticsearch .core .Tuple ;
27
26
import org .elasticsearch .common .inject .Inject ;
28
- import org .elasticsearch .common .xcontent .DeprecationHandler ;
29
- import org .elasticsearch .common .xcontent .NamedXContentRegistry ;
30
- import org .elasticsearch .common .xcontent .XContentFactory ;
31
- import org .elasticsearch .common .xcontent .XContentParser ;
32
- import org .elasticsearch .common .xcontent .XContentType ;
27
+ import org .elasticsearch .core .Tuple ;
33
28
import org .elasticsearch .index .query .BoolQueryBuilder ;
34
29
import org .elasticsearch .index .query .QueryBuilder ;
35
30
import org .elasticsearch .index .query .QueryBuilders ;
57
52
import org .elasticsearch .xpack .core .ml .utils .ExceptionsHelper ;
58
53
import org .elasticsearch .xpack .ml .utils .QueryBuilderHelper ;
59
54
60
- import java .io .IOException ;
61
- import java .io .InputStream ;
62
55
import java .util .ArrayList ;
63
- import java .util .Collection ;
64
- import java .util .EnumSet ;
65
- import java .util .HashSet ;
66
56
import java .util .List ;
67
57
import java .util .Set ;
68
58
import java .util .concurrent .TimeoutException ;
69
- import java .util .stream .Collectors ;
59
+ import java .util .stream .Stream ;
70
60
61
+ import static java .util .stream .Collectors .toSet ;
71
62
import static org .elasticsearch .xpack .core .ClientHelper .ML_ORIGIN ;
72
63
import static org .elasticsearch .xpack .core .ClientHelper .executeAsyncWithOrigin ;
73
64
@@ -80,8 +71,10 @@ public class TransportDeleteForecastAction extends HandledTransportAction<Delete
80
71
private final ClusterService clusterService ;
81
72
private static final int MAX_FORECAST_TO_SEARCH = 10_000 ;
82
73
83
- private static final Set <ForecastRequestStatus > DELETABLE_STATUSES =
84
- EnumSet .of (ForecastRequestStatus .FINISHED , ForecastRequestStatus .FAILED );
74
+ private static final Set <String > DELETABLE_STATUSES =
75
+ Stream .of (ForecastRequestStatus .FINISHED , ForecastRequestStatus .FAILED )
76
+ .map (ForecastRequestStatus ::toString )
77
+ .collect (toSet ());
85
78
86
79
@ Inject
87
80
public TransportDeleteForecastAction (TransportService transportService ,
@@ -105,47 +98,54 @@ protected void doExecute(Task task, DeleteForecastAction.Request request, Action
105
98
e -> handleFailure (e , request , listener )
106
99
);
107
100
108
- SearchSourceBuilder source = new SearchSourceBuilder ();
109
-
110
- BoolQueryBuilder builder = QueryBuilders .boolQuery ()
111
- .filter (QueryBuilders .termQuery (Result .RESULT_TYPE .getPreferredName (), ForecastRequestStats .RESULT_TYPE_VALUE ));
101
+ BoolQueryBuilder query =
102
+ QueryBuilders .boolQuery ()
103
+ .filter (QueryBuilders .termQuery (Result .RESULT_TYPE .getPreferredName (), ForecastRequestStats .RESULT_TYPE_VALUE ));
112
104
QueryBuilderHelper
113
105
.buildTokenFilterQuery (Forecast .FORECAST_ID .getPreferredName (), forecastIds )
114
- .ifPresent (builder ::filter );
115
- source .query (builder );
116
-
117
- SearchRequest searchRequest = new SearchRequest (AnomalyDetectorsIndex .jobResultsAliasedName (jobId ));
118
- searchRequest .source (source );
106
+ .ifPresent (query ::filter );
107
+ SearchSourceBuilder source =
108
+ new SearchSourceBuilder ()
109
+ .size (MAX_FORECAST_TO_SEARCH )
110
+ // We only need forecast id and status, there is no need fetching the whole source
111
+ .fetchSource (false )
112
+ .docValueField (ForecastRequestStats .FORECAST_ID .getPreferredName ())
113
+ .docValueField (ForecastRequestStats .STATUS .getPreferredName ())
114
+ .query (query );
115
+ SearchRequest searchRequest =
116
+ new SearchRequest (AnomalyDetectorsIndex .jobResultsAliasedName (jobId ))
117
+ .source (source );
119
118
120
119
executeAsyncWithOrigin (client , ML_ORIGIN , SearchAction .INSTANCE , searchRequest , forecastStatsHandler );
121
120
}
122
121
123
- static void validateForecastState (Collection <ForecastRequestStats > forecastsToDelete , JobState jobState , String jobId ) {
124
- List <String > badStatusForecasts = forecastsToDelete .stream ()
125
- .filter ((f ) -> DELETABLE_STATUSES .contains (f .getStatus ()) == false )
126
- .map (ForecastRequestStats ::getForecastId )
127
- .collect (Collectors .toList ());
128
- if (badStatusForecasts .size () > 0 && JobState .OPENED .equals (jobState )) {
122
+ static List <String > extractForecastIds (SearchHit [] forecastsToDelete , JobState jobState , String jobId ) {
123
+ List <String > forecastIds = new ArrayList <>(forecastsToDelete .length );
124
+ List <String > badStatusForecastIds = new ArrayList <>();
125
+ for (SearchHit hit : forecastsToDelete ) {
126
+ String forecastId = hit .field (ForecastRequestStats .FORECAST_ID .getPreferredName ()).getValue ();
127
+ String forecastStatus = hit .field (ForecastRequestStats .STATUS .getPreferredName ()).getValue ();
128
+ if (DELETABLE_STATUSES .contains (forecastStatus )) {
129
+ forecastIds .add (forecastId );
130
+ } else {
131
+ badStatusForecastIds .add (forecastId );
132
+ }
133
+ }
134
+ if (badStatusForecastIds .size () > 0 && JobState .OPENED .equals (jobState )) {
129
135
throw ExceptionsHelper .conflictStatusException (
130
- Messages .getMessage (Messages .REST_CANNOT_DELETE_FORECAST_IN_CURRENT_STATE , badStatusForecasts , jobId ));
136
+ Messages .getMessage (Messages .REST_CANNOT_DELETE_FORECAST_IN_CURRENT_STATE , badStatusForecastIds , jobId ));
131
137
}
138
+ return forecastIds ;
132
139
}
133
140
134
141
private void deleteForecasts (SearchResponse searchResponse ,
135
142
DeleteForecastAction .Request request ,
136
143
ActionListener <AcknowledgedResponse > listener ) {
137
144
final String jobId = request .getJobId ();
138
- Set <ForecastRequestStats > forecastsToDelete ;
139
- try {
140
- forecastsToDelete = parseForecastsFromSearch (searchResponse );
141
- } catch (IOException e ) {
142
- listener .onFailure (e );
143
- return ;
144
- }
145
+ SearchHits forecastsToDelete = searchResponse .getHits ();
145
146
146
- if (forecastsToDelete .isEmpty ()) {
147
- if (Strings .isAllOrWildcard (request .getForecastId ()) &&
148
- request .isAllowNoForecasts ()) {
147
+ if (forecastsToDelete .getHits ().length == 0 ) {
148
+ if (Strings .isAllOrWildcard (request .getForecastId ()) && request .isAllowNoForecasts ()) {
149
149
listener .onResponse (AcknowledgedResponse .TRUE );
150
150
} else {
151
151
listener .onFailure (
@@ -156,16 +156,15 @@ private void deleteForecasts(SearchResponse searchResponse,
156
156
final ClusterState state = clusterService .state ();
157
157
PersistentTasksCustomMetadata persistentTasks = state .metadata ().custom (PersistentTasksCustomMetadata .TYPE );
158
158
JobState jobState = MlTasks .getJobState (jobId , persistentTasks );
159
+ final List <String > forecastIds ;
159
160
try {
160
- validateForecastState (forecastsToDelete , jobState , jobId );
161
+ forecastIds = extractForecastIds (forecastsToDelete . getHits () , jobState , jobId );
161
162
} catch (ElasticsearchException ex ) {
162
163
listener .onFailure (ex );
163
164
return ;
164
165
}
165
166
166
- final List <String > forecastIds = forecastsToDelete .stream ().map (ForecastRequestStats ::getForecastId ).collect (Collectors .toList ());
167
167
DeleteByQueryRequest deleteByQueryRequest = buildDeleteByQuery (jobId , forecastIds );
168
-
169
168
executeAsyncWithOrigin (client , ML_ORIGIN , DeleteByQueryAction .INSTANCE , deleteByQueryRequest , ActionListener .wrap (
170
169
response -> {
171
170
if (response .isTimedOut ()) {
@@ -208,45 +207,29 @@ private static Tuple<RestStatus, Throwable> getStatusAndReason(final BulkByScrol
208
207
return new Tuple <>(status , reason );
209
208
}
210
209
211
- private static Set <ForecastRequestStats > parseForecastsFromSearch (SearchResponse searchResponse ) throws IOException {
212
- SearchHits hits = searchResponse .getHits ();
213
- List <ForecastRequestStats > allStats = new ArrayList <>(hits .getHits ().length );
214
- for (SearchHit hit : hits ) {
215
- try (InputStream stream = hit .getSourceRef ().streamInput ();
216
- XContentParser parser = XContentFactory .xContent (XContentType .JSON ).createParser (
217
- NamedXContentRegistry .EMPTY , DeprecationHandler .THROW_UNSUPPORTED_OPERATION , stream )) {
218
- allStats .add (ForecastRequestStats .STRICT_PARSER .apply (parser , null ));
219
- }
220
- }
221
- return new HashSet <>(allStats );
222
- }
223
-
224
210
private DeleteByQueryRequest buildDeleteByQuery (String jobId , List <String > forecastsToDelete ) {
225
- DeleteByQueryRequest request = new DeleteByQueryRequest ()
226
- .setAbortOnVersionConflict (false ) //since these documents are not updated, a conflict just means it was deleted previously
227
- .setMaxDocs (MAX_FORECAST_TO_SEARCH )
228
- .setSlices (AbstractBulkByScrollRequest .AUTO_SLICES );
229
-
230
- request .indices (AnomalyDetectorsIndex .jobResultsAliasedName (jobId ));
231
- BoolQueryBuilder innerBoolQuery = QueryBuilders .boolQuery ();
232
- innerBoolQuery
211
+ BoolQueryBuilder innerBoolQuery = QueryBuilders .boolQuery ()
233
212
.must (QueryBuilders .termsQuery (Result .RESULT_TYPE .getPreferredName (),
234
213
ForecastRequestStats .RESULT_TYPE_VALUE , Forecast .RESULT_TYPE_VALUE ))
235
214
.must (QueryBuilders .termsQuery (Forecast .FORECAST_ID .getPreferredName (),
236
215
forecastsToDelete ));
237
-
238
216
QueryBuilder query = QueryBuilders .boolQuery ().filter (innerBoolQuery );
239
- request .setQuery (query );
240
- request .setRefresh (true );
241
- return request ;
217
+
218
+ // We want *all* of the docs to be deleted. Hence, we rely on the default value of max_docs.
219
+ return new DeleteByQueryRequest ()
220
+ .setAbortOnVersionConflict (false ) // since these documents are not updated, a conflict just means it was deleted previously
221
+ .setSlices (AbstractBulkByScrollRequest .AUTO_SLICES )
222
+ .indices (AnomalyDetectorsIndex .jobResultsAliasedName (jobId ))
223
+ .setQuery (query )
224
+ .setRefresh (true );
242
225
}
243
226
244
227
private static void handleFailure (Exception e ,
245
228
DeleteForecastAction .Request request ,
246
229
ActionListener <AcknowledgedResponse > listener ) {
247
230
if (ExceptionsHelper .unwrapCause (e ) instanceof ResourceNotFoundException ) {
248
231
if (request .isAllowNoForecasts () && Strings .isAllOrWildcard (request .getForecastId ())) {
249
- listener .onResponse (AcknowledgedResponse .of ( true ) );
232
+ listener .onResponse (AcknowledgedResponse .TRUE );
250
233
} else {
251
234
listener .onFailure (new ResourceNotFoundException (
252
235
Messages .getMessage (Messages .REST_NO_SUCH_FORECAST , request .getForecastId (), request .getJobId ())
0 commit comments