Skip to content

Commit 27e07ec

Browse files
authored
HLRC: ML Delete Forecast API (#33526)
* HLRC: ML Delete Forecast API
1 parent 9f8dff9 commit 27e07ec

File tree

9 files changed

+582
-0
lines changed

9 files changed

+582
-0
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.lucene.util.BytesRef;
2929
import org.elasticsearch.client.RequestConverters.EndpointBuilder;
3030
import org.elasticsearch.client.ml.CloseJobRequest;
31+
import org.elasticsearch.client.ml.DeleteForecastRequest;
3132
import org.elasticsearch.client.ml.DeleteJobRequest;
3233
import org.elasticsearch.client.ml.FlushJobRequest;
3334
import org.elasticsearch.client.ml.ForecastJobRequest;
@@ -181,6 +182,26 @@ static Request updateJob(UpdateJobRequest updateJobRequest) throws IOException {
181182
return request;
182183
}
183184

185+
static Request deleteForecast(DeleteForecastRequest deleteForecastRequest) throws IOException {
186+
String endpoint = new EndpointBuilder()
187+
.addPathPartAsIs("_xpack")
188+
.addPathPartAsIs("ml")
189+
.addPathPartAsIs("anomaly_detectors")
190+
.addPathPart(deleteForecastRequest.getJobId())
191+
.addPathPartAsIs("_forecast")
192+
.addPathPart(Strings.collectionToCommaDelimitedString(deleteForecastRequest.getForecastIds()))
193+
.build();
194+
Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
195+
RequestConverters.Params params = new RequestConverters.Params(request);
196+
if (deleteForecastRequest.isAllowNoForecasts() != null) {
197+
params.putParam("allow_no_forecasts", Boolean.toString(deleteForecastRequest.isAllowNoForecasts()));
198+
}
199+
if (deleteForecastRequest.timeout() != null) {
200+
params.putParam("timeout", deleteForecastRequest.timeout().getStringRep());
201+
}
202+
return request;
203+
}
204+
184205
static Request getBuckets(GetBucketsRequest getBucketsRequest) throws IOException {
185206
String endpoint = new EndpointBuilder()
186207
.addPathPartAsIs("_xpack")

client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package org.elasticsearch.client;
2020

2121
import org.elasticsearch.action.ActionListener;
22+
import org.elasticsearch.action.support.master.AcknowledgedResponse;
23+
import org.elasticsearch.client.ml.DeleteForecastRequest;
2224
import org.elasticsearch.client.ml.ForecastJobRequest;
2325
import org.elasticsearch.client.ml.ForecastJobResponse;
2426
import org.elasticsearch.client.ml.PostDataRequest;
@@ -389,6 +391,11 @@ public ForecastJobResponse forecastJob(ForecastJobRequest request, RequestOption
389391
/**
390392
* Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job}
391393
*
394+
* <p>
395+
* For additional info
396+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-job.html"></a>
397+
* </p>
398+
*
392399
* @param request the {@link UpdateJobRequest} object enclosing the desired updates
393400
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
394401
* @return a PutJobResponse object containing the updated job object
@@ -427,6 +434,10 @@ public void forecastJobAsync(ForecastJobRequest request, RequestOptions options,
427434
/**
428435
* Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job} asynchronously
429436
*
437+
* <p>
438+
* For additional info
439+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-job.html"></a>
440+
* </p>
430441
* @param request the {@link UpdateJobRequest} object enclosing the desired updates
431442
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
432443
* @param listener Listener to be notified upon request completion
@@ -440,6 +451,48 @@ public void updateJobAsync(UpdateJobRequest request, RequestOptions options, Act
440451
Collections.emptySet());
441452
}
442453

454+
/**
455+
* Deletes Machine Learning Job Forecasts
456+
*
457+
* <p>
458+
* For additional info
459+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-forecast.html"></a>
460+
* </p>
461+
*
462+
* @param request the {@link DeleteForecastRequest} object enclosing the desired jobId, forecastIDs, and other options
463+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
464+
* @return a AcknowledgedResponse object indicating request success
465+
* @throws IOException when there is a serialization issue sending the request or receiving the response
466+
*/
467+
public AcknowledgedResponse deleteForecast(DeleteForecastRequest request, RequestOptions options) throws IOException {
468+
return restHighLevelClient.performRequestAndParseEntity(request,
469+
MLRequestConverters::deleteForecast,
470+
options,
471+
AcknowledgedResponse::fromXContent,
472+
Collections.emptySet());
473+
}
474+
475+
/**
476+
* Deletes Machine Learning Job Forecasts asynchronously
477+
*
478+
* <p>
479+
* For additional info
480+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-forecast.html"></a>
481+
* </p>
482+
*
483+
* @param request the {@link DeleteForecastRequest} object enclosing the desired jobId, forecastIDs, and other options
484+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
485+
* @param listener Listener to be notified upon request completion
486+
*/
487+
public void deleteForecastAsync(DeleteForecastRequest request, RequestOptions options, ActionListener<AcknowledgedResponse> listener) {
488+
restHighLevelClient.performRequestAsyncAndParseEntity(request,
489+
MLRequestConverters::deleteForecast,
490+
options,
491+
AcknowledgedResponse::fromXContent,
492+
listener,
493+
Collections.emptySet());
494+
}
495+
443496
/**
444497
* Gets the buckets for a Machine Learning Job.
445498
* <p>
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client.ml;
20+
21+
import org.elasticsearch.action.ActionRequest;
22+
import org.elasticsearch.action.ActionRequestValidationException;
23+
import org.elasticsearch.client.ml.job.config.Job;
24+
import org.elasticsearch.common.ParseField;
25+
import org.elasticsearch.common.Strings;
26+
import org.elasticsearch.common.unit.TimeValue;
27+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
28+
import org.elasticsearch.common.xcontent.ToXContentObject;
29+
import org.elasticsearch.common.xcontent.XContentBuilder;
30+
31+
import java.io.IOException;
32+
import java.util.ArrayList;
33+
import java.util.Arrays;
34+
import java.util.List;
35+
import java.util.Objects;
36+
37+
/**
38+
* POJO for a delete forecast request
39+
*/
40+
public class DeleteForecastRequest extends ActionRequest implements ToXContentObject {
41+
42+
public static final ParseField FORECAST_ID = new ParseField("forecast_id");
43+
public static final ParseField ALLOW_NO_FORECASTS = new ParseField("allow_no_forecasts");
44+
public static final ParseField TIMEOUT = new ParseField("timeout");
45+
public static final String ALL = "_all";
46+
47+
public static final ConstructingObjectParser<DeleteForecastRequest, Void> PARSER =
48+
new ConstructingObjectParser<>("delete_forecast_request", (a) -> new DeleteForecastRequest((String) a[0]));
49+
50+
static {
51+
PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID);
52+
PARSER.declareStringOrNull(
53+
(c, p) -> c.setForecastIds(Strings.commaDelimitedListToStringArray(p)), FORECAST_ID);
54+
PARSER.declareBoolean(DeleteForecastRequest::setAllowNoForecasts, ALLOW_NO_FORECASTS);
55+
PARSER.declareString(DeleteForecastRequest::timeout, TIMEOUT);
56+
}
57+
58+
/**
59+
* Create a new {@link DeleteForecastRequest} that explicitly deletes all forecasts
60+
*
61+
* @param jobId the jobId of the Job whose forecasts to delete
62+
*/
63+
public static DeleteForecastRequest deleteAllForecasts(String jobId) {
64+
DeleteForecastRequest request = new DeleteForecastRequest(jobId);
65+
request.setForecastIds(ALL);
66+
return request;
67+
}
68+
69+
private final String jobId;
70+
private List<String> forecastIds = new ArrayList<>();
71+
private Boolean allowNoForecasts;
72+
private TimeValue timeout;
73+
74+
/**
75+
* Create a new DeleteForecastRequest for the given Job ID
76+
*
77+
* @param jobId the jobId of the Job whose forecast(s) to delete
78+
*/
79+
public DeleteForecastRequest(String jobId) {
80+
this.jobId = Objects.requireNonNull(jobId, Job.ID.getPreferredName());
81+
}
82+
83+
public String getJobId() {
84+
return jobId;
85+
}
86+
87+
public List<String> getForecastIds() {
88+
return forecastIds;
89+
}
90+
91+
/**
92+
* The forecast IDs to delete. Can be also be {@link DeleteForecastRequest#ALL} to explicitly delete ALL forecasts
93+
*
94+
* @param forecastIds forecast IDs to delete
95+
*/
96+
public void setForecastIds(String... forecastIds) {
97+
setForecastIds(Arrays.asList(forecastIds));
98+
}
99+
100+
void setForecastIds(List<String> forecastIds) {
101+
if (forecastIds.stream().anyMatch(Objects::isNull)) {
102+
throw new NullPointerException("forecastIds must not contain null values");
103+
}
104+
this.forecastIds = new ArrayList<>(forecastIds);
105+
}
106+
107+
public Boolean isAllowNoForecasts() {
108+
return allowNoForecasts;
109+
}
110+
111+
/**
112+
* Sets the `allow_no_forecasts` field.
113+
*
114+
* @param allowNoForecasts when {@code true} no error is thrown when {@link DeleteForecastRequest#ALL} does not find any forecasts
115+
*/
116+
public void setAllowNoForecasts(boolean allowNoForecasts) {
117+
this.allowNoForecasts = allowNoForecasts;
118+
}
119+
120+
/**
121+
* Allows to set the timeout
122+
* @param timeout timeout as a string (e.g. 1s)
123+
*/
124+
public void timeout(String timeout) {
125+
this.timeout = TimeValue.parseTimeValue(timeout, this.timeout, getClass().getSimpleName() + ".timeout");
126+
}
127+
128+
/**
129+
* Allows to set the timeout
130+
* @param timeout timeout as a {@link TimeValue}
131+
*/
132+
public void timeout(TimeValue timeout) {
133+
this.timeout = timeout;
134+
}
135+
136+
public TimeValue timeout() {
137+
return timeout;
138+
}
139+
140+
@Override
141+
public boolean equals(Object other) {
142+
if (this == other) {
143+
return true;
144+
}
145+
146+
if (other == null || getClass() != other.getClass()) {
147+
return false;
148+
}
149+
150+
DeleteForecastRequest that = (DeleteForecastRequest) other;
151+
return Objects.equals(jobId, that.jobId) &&
152+
Objects.equals(forecastIds, that.forecastIds) &&
153+
Objects.equals(allowNoForecasts, that.allowNoForecasts) &&
154+
Objects.equals(timeout, that.timeout);
155+
}
156+
157+
@Override
158+
public int hashCode() {
159+
return Objects.hash(jobId, forecastIds, allowNoForecasts, timeout);
160+
}
161+
162+
@Override
163+
public ActionRequestValidationException validate() {
164+
return null;
165+
}
166+
167+
@Override
168+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
169+
builder.startObject();
170+
builder.field(Job.ID.getPreferredName(), jobId);
171+
if (forecastIds != null) {
172+
builder.field(FORECAST_ID.getPreferredName(), Strings.collectionToCommaDelimitedString(forecastIds));
173+
}
174+
if (allowNoForecasts != null) {
175+
builder.field(ALLOW_NO_FORECASTS.getPreferredName(), allowNoForecasts);
176+
}
177+
if (timeout != null) {
178+
builder.field(TIMEOUT.getPreferredName(), timeout.getStringRep());
179+
}
180+
builder.endObject();
181+
return builder;
182+
}
183+
}

client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.http.client.methods.HttpPost;
2525
import org.apache.http.client.methods.HttpPut;
2626
import org.elasticsearch.client.ml.CloseJobRequest;
27+
import org.elasticsearch.client.ml.DeleteForecastRequest;
2728
import org.elasticsearch.client.ml.DeleteJobRequest;
2829
import org.elasticsearch.client.ml.FlushJobRequest;
2930
import org.elasticsearch.client.ml.ForecastJobRequest;
@@ -44,6 +45,7 @@
4445
import org.elasticsearch.client.ml.job.config.JobUpdate;
4546
import org.elasticsearch.client.ml.job.config.JobUpdateTests;
4647
import org.elasticsearch.client.ml.job.util.PageParams;
48+
import org.elasticsearch.common.Strings;
4749
import org.elasticsearch.common.unit.TimeValue;
4850
import org.elasticsearch.common.xcontent.XContentParser;
4951
import org.elasticsearch.common.xcontent.XContentType;
@@ -204,6 +206,33 @@ public void testUpdateJob() throws Exception {
204206
}
205207
}
206208

209+
public void testDeleteForecast() throws Exception {
210+
String jobId = randomAlphaOfLength(10);
211+
DeleteForecastRequest deleteForecastRequest = new DeleteForecastRequest(jobId);
212+
213+
Request request = MLRequestConverters.deleteForecast(deleteForecastRequest);
214+
assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
215+
assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + "/_forecast", request.getEndpoint());
216+
assertFalse(request.getParameters().containsKey("timeout"));
217+
assertFalse(request.getParameters().containsKey("allow_no_forecasts"));
218+
219+
deleteForecastRequest.setForecastIds(randomAlphaOfLength(10), randomAlphaOfLength(10));
220+
deleteForecastRequest.timeout("10s");
221+
deleteForecastRequest.setAllowNoForecasts(true);
222+
223+
request = MLRequestConverters.deleteForecast(deleteForecastRequest);
224+
assertEquals(
225+
"/_xpack/ml/anomaly_detectors/" +
226+
jobId +
227+
"/_forecast/" +
228+
Strings.collectionToCommaDelimitedString(deleteForecastRequest.getForecastIds()),
229+
request.getEndpoint());
230+
assertEquals("10s",
231+
request.getParameters().get(DeleteForecastRequest.TIMEOUT.getPreferredName()));
232+
assertEquals(Boolean.toString(true),
233+
request.getParameters().get(DeleteForecastRequest.ALLOW_NO_FORECASTS.getPreferredName()));
234+
}
235+
207236
public void testGetBuckets() throws IOException {
208237
String jobId = randomAlphaOfLength(10);
209238
GetBucketsRequest getBucketsRequest = new GetBucketsRequest(jobId);

0 commit comments

Comments
 (0)