Skip to content

Commit 3fbaae1

Browse files
authored
HLRC: ML Close Job (elastic#32943)
* HLRC: Adding ML Close Job API HLRC: Adding ML Close Job API * reconciling request converters * Adding serialization tests and addressing PR comments * Changing constructor order
1 parent 9050c7e commit 3fbaae1

File tree

12 files changed

+623
-2
lines changed

12 files changed

+623
-2
lines changed

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.apache.http.client.methods.HttpPost;
2424
import org.apache.http.client.methods.HttpPut;
2525
import org.elasticsearch.client.RequestConverters.EndpointBuilder;
26+
import org.elasticsearch.common.Strings;
27+
import org.elasticsearch.protocol.xpack.ml.CloseJobRequest;
2628
import org.elasticsearch.protocol.xpack.ml.DeleteJobRequest;
2729
import org.elasticsearch.protocol.xpack.ml.OpenJobRequest;
2830
import org.elasticsearch.protocol.xpack.ml.PutJobRequest;
@@ -61,6 +63,30 @@ static Request openJob(OpenJobRequest openJobRequest) throws IOException {
6163
return request;
6264
}
6365

66+
static Request closeJob(CloseJobRequest closeJobRequest) {
67+
String endpoint = new EndpointBuilder()
68+
.addPathPartAsIs("_xpack")
69+
.addPathPartAsIs("ml")
70+
.addPathPartAsIs("anomaly_detectors")
71+
.addPathPart(Strings.collectionToCommaDelimitedString(closeJobRequest.getJobIds()))
72+
.addPathPartAsIs("_close")
73+
.build();
74+
Request request = new Request(HttpPost.METHOD_NAME, endpoint);
75+
76+
RequestConverters.Params params = new RequestConverters.Params(request);
77+
if (closeJobRequest.isForce() != null) {
78+
params.putParam("force", Boolean.toString(closeJobRequest.isForce()));
79+
}
80+
if (closeJobRequest.isAllowNoJobs() != null) {
81+
params.putParam("allow_no_jobs", Boolean.toString(closeJobRequest.isAllowNoJobs()));
82+
}
83+
if (closeJobRequest.getTimeout() != null) {
84+
params.putParam("timeout", closeJobRequest.getTimeout().getStringRep());
85+
}
86+
87+
return request;
88+
}
89+
6490
static Request deleteJob(DeleteJobRequest deleteJobRequest) {
6591
String endpoint = new EndpointBuilder()
6692
.addPathPartAsIs("_xpack")

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

Lines changed: 38 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.protocol.xpack.ml.CloseJobRequest;
23+
import org.elasticsearch.protocol.xpack.ml.CloseJobResponse;
2224
import org.elasticsearch.protocol.xpack.ml.DeleteJobRequest;
2325
import org.elasticsearch.protocol.xpack.ml.DeleteJobResponse;
2426
import org.elasticsearch.protocol.xpack.ml.OpenJobRequest;
@@ -166,4 +168,40 @@ public void openJobAsync(OpenJobRequest request, RequestOptions options, ActionL
166168
listener,
167169
Collections.emptySet());
168170
}
171+
172+
/**
173+
* Closes one or more Machine Learning Jobs. A job can be opened and closed multiple times throughout its lifecycle.
174+
*
175+
* A closed job cannot receive data or perform analysis operations, but you can still explore and navigate results.
176+
*
177+
* @param request request containing job_ids and additional options. See {@link CloseJobRequest}
178+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
179+
* @return response containing if the job was successfully closed or not.
180+
* @throws IOException when there is a serialization issue sending the request or receiving the response
181+
*/
182+
public CloseJobResponse closeJob(CloseJobRequest request, RequestOptions options) throws IOException {
183+
return restHighLevelClient.performRequestAndParseEntity(request,
184+
MLRequestConverters::closeJob,
185+
options,
186+
CloseJobResponse::fromXContent,
187+
Collections.emptySet());
188+
}
189+
190+
/**
191+
* Closes one or more Machine Learning Jobs asynchronously, notifies listener on completion
192+
*
193+
* A closed job cannot receive data or perform analysis operations, but you can still explore and navigate results.
194+
*
195+
* @param request request containing job_ids and additional options. See {@link CloseJobRequest}
196+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
197+
* @param listener Listener to be notified upon request completion
198+
*/
199+
public void closeJobAsync(CloseJobRequest request, RequestOptions options, ActionListener<CloseJobResponse> listener) {
200+
restHighLevelClient.performRequestAsyncAndParseEntity(request,
201+
MLRequestConverters::closeJob,
202+
options,
203+
CloseJobResponse::fromXContent,
204+
listener,
205+
Collections.emptySet());
206+
}
169207
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.common.unit.TimeValue;
2525
import org.elasticsearch.common.xcontent.XContentParser;
2626
import org.elasticsearch.common.xcontent.json.JsonXContent;
27+
import org.elasticsearch.protocol.xpack.ml.CloseJobRequest;
2728
import org.elasticsearch.protocol.xpack.ml.DeleteJobRequest;
2829
import org.elasticsearch.protocol.xpack.ml.OpenJobRequest;
2930
import org.elasticsearch.protocol.xpack.ml.PutJobRequest;
@@ -66,6 +67,29 @@ public void testOpenJob() throws Exception {
6667
assertEquals(bos.toString("UTF-8"), "{\"job_id\":\""+ jobId +"\",\"timeout\":\"10m\"}");
6768
}
6869

70+
public void testCloseJob() {
71+
String jobId = "somejobid";
72+
CloseJobRequest closeJobRequest = new CloseJobRequest(jobId);
73+
74+
Request request = MLRequestConverters.closeJob(closeJobRequest);
75+
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
76+
assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + "/_close", request.getEndpoint());
77+
assertFalse(request.getParameters().containsKey("force"));
78+
assertFalse(request.getParameters().containsKey("allow_no_jobs"));
79+
assertFalse(request.getParameters().containsKey("timeout"));
80+
81+
closeJobRequest = new CloseJobRequest(jobId, "otherjobs*");
82+
closeJobRequest.setForce(true);
83+
closeJobRequest.setAllowNoJobs(false);
84+
closeJobRequest.setTimeout(TimeValue.timeValueMinutes(10));
85+
request = MLRequestConverters.closeJob(closeJobRequest);
86+
87+
assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + ",otherjobs*/_close", request.getEndpoint());
88+
assertEquals(Boolean.toString(true), request.getParameters().get("force"));
89+
assertEquals(Boolean.toString(false), request.getParameters().get("allow_no_jobs"));
90+
assertEquals("10m", request.getParameters().get("timeout"));
91+
}
92+
6993
public void testDeleteJob() {
7094
String jobId = randomAlphaOfLength(10);
7195
DeleteJobRequest deleteJobRequest = new DeleteJobRequest(jobId);
@@ -87,4 +111,4 @@ private static Job createValidJob(String jobId) {
87111
jobBuilder.setAnalysisConfig(analysisConfig);
88112
return jobBuilder.build();
89113
}
90-
}
114+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator;
2222
import org.apache.lucene.util.LuceneTestCase.AwaitsFix;
2323
import org.elasticsearch.common.unit.TimeValue;
24+
import org.elasticsearch.protocol.xpack.ml.CloseJobRequest;
25+
import org.elasticsearch.protocol.xpack.ml.CloseJobResponse;
2426
import org.elasticsearch.protocol.xpack.ml.DeleteJobRequest;
2527
import org.elasticsearch.protocol.xpack.ml.DeleteJobResponse;
2628
import org.elasticsearch.protocol.xpack.ml.OpenJobRequest;
@@ -77,6 +79,19 @@ public void testOpenJob() throws Exception {
7779
assertTrue(response.isOpened());
7880
}
7981

82+
public void testCloseJob() throws Exception {
83+
String jobId = randomValidJobId();
84+
Job job = buildJob(jobId);
85+
MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
86+
machineLearningClient.putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
87+
machineLearningClient.openJob(new OpenJobRequest(jobId), RequestOptions.DEFAULT);
88+
89+
CloseJobResponse response = execute(new CloseJobRequest(jobId),
90+
machineLearningClient::closeJob,
91+
machineLearningClient::closeJobAsync);
92+
assertTrue(response.isClosed());
93+
}
94+
8095
public static String randomValidJobId() {
8196
CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray());
8297
return generator.ofCodePointsLength(random(), 10, 10);

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.elasticsearch.client.RequestOptions;
2626
import org.elasticsearch.client.RestHighLevelClient;
2727
import org.elasticsearch.common.unit.TimeValue;
28+
import org.elasticsearch.protocol.xpack.ml.CloseJobRequest;
29+
import org.elasticsearch.protocol.xpack.ml.CloseJobResponse;
2830
import org.elasticsearch.protocol.xpack.ml.DeleteJobRequest;
2931
import org.elasticsearch.protocol.xpack.ml.DeleteJobResponse;
3032
import org.elasticsearch.protocol.xpack.ml.OpenJobRequest;
@@ -221,4 +223,56 @@ public void onFailure(Exception e) {
221223
assertTrue(latch.await(30L, TimeUnit.SECONDS));
222224
}
223225
}
226+
227+
public void testCloseJob() throws Exception {
228+
RestHighLevelClient client = highLevelClient();
229+
230+
{
231+
Job job = MachineLearningIT.buildJob("closing-my-first-machine-learning-job");
232+
client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
233+
client.machineLearning().openJob(new OpenJobRequest(job.getId()), RequestOptions.DEFAULT);
234+
235+
//tag::x-pack-ml-close-job-request
236+
CloseJobRequest closeJobRequest = new CloseJobRequest("closing-my-first-machine-learning-job", "otherjobs*"); //<1>
237+
closeJobRequest.setForce(false); //<2>
238+
closeJobRequest.setAllowNoJobs(true); //<3>
239+
closeJobRequest.setTimeout(TimeValue.timeValueMinutes(10)); //<4>
240+
//end::x-pack-ml-close-job-request
241+
242+
//tag::x-pack-ml-close-job-execute
243+
CloseJobResponse closeJobResponse = client.machineLearning().closeJob(closeJobRequest, RequestOptions.DEFAULT);
244+
boolean isClosed = closeJobResponse.isClosed(); //<1>
245+
//end::x-pack-ml-close-job-execute
246+
247+
}
248+
{
249+
Job job = MachineLearningIT.buildJob("closing-my-second-machine-learning-job");
250+
client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
251+
client.machineLearning().openJob(new OpenJobRequest(job.getId()), RequestOptions.DEFAULT);
252+
253+
//tag::x-pack-ml-close-job-listener
254+
ActionListener<CloseJobResponse> listener = new ActionListener<CloseJobResponse>() {
255+
@Override
256+
public void onResponse(CloseJobResponse closeJobResponse) {
257+
//<1>
258+
}
259+
260+
@Override
261+
public void onFailure(Exception e) {
262+
// <2>
263+
}
264+
};
265+
//end::x-pack-ml-close-job-listener
266+
CloseJobRequest closeJobRequest = new CloseJobRequest("closing-my-second-machine-learning-job");
267+
// Replace the empty listener by a blocking listener in test
268+
final CountDownLatch latch = new CountDownLatch(1);
269+
listener = new LatchedActionListener<>(listener, latch);
270+
271+
// tag::x-pack-ml-close-job-execute-async
272+
client.machineLearning().closeJobAsync(closeJobRequest, RequestOptions.DEFAULT, listener); //<1>
273+
// end::x-pack-ml-close-job-execute-async
274+
275+
assertTrue(latch.await(30L, TimeUnit.SECONDS));
276+
}
277+
}
224278
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[[java-rest-high-x-pack-ml-close-job]]
2+
=== Close Job API
3+
4+
The Close Job API provides the ability to close {ml} jobs in the cluster.
5+
It accepts a `CloseJobRequest` object and responds
6+
with a `CloseJobResponse` object.
7+
8+
[[java-rest-high-x-pack-ml-close-job-request]]
9+
==== Close Job Request
10+
11+
A `CloseJobRequest` object gets created with an existing non-null `jobId`.
12+
13+
["source","java",subs="attributes,callouts,macros"]
14+
--------------------------------------------------
15+
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-close-job-request]
16+
--------------------------------------------------
17+
<1> Constructing a new request referencing existing job IDs
18+
<2> Optionally used to close a failed job, or to forcefully close a job
19+
which has not responded to its initial close request.
20+
<3> Optionally set to ignore if a wildcard expression matches no jobs.
21+
(This includes `_all` string or when no jobs have been specified)
22+
<4> Optionally setting the `timeout` value for how long the
23+
execution should wait for the job to be closed.
24+
25+
[[java-rest-high-x-pack-ml-close-job-execution]]
26+
==== Execution
27+
28+
The request can be executed through the `MachineLearningClient` contained
29+
in the `RestHighLevelClient` object, accessed via the `machineLearningClient()` method.
30+
31+
["source","java",subs="attributes,callouts,macros"]
32+
--------------------------------------------------
33+
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-close-job-execute]
34+
--------------------------------------------------
35+
<1> `isClosed()` from the `CloseJobResponse` indicates if the job was successfully
36+
closed or not.
37+
38+
[[java-rest-high-x-pack-ml-close-job-execution-async]]
39+
==== Asynchronous Execution
40+
41+
The request can also be executed asynchronously:
42+
43+
["source","java",subs="attributes,callouts,macros"]
44+
--------------------------------------------------
45+
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-close-job-execute-async]
46+
--------------------------------------------------
47+
<1> The `CloseJobRequest` to execute and the `ActionListener` to use when
48+
the execution completes
49+
50+
The method does not block and returns immediately. The passed `ActionListener` is used
51+
to notify the caller of completion. A typical `ActionListener` for `CloseJobResponse` may
52+
look like
53+
54+
["source","java",subs="attributes,callouts,macros"]
55+
--------------------------------------------------
56+
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-close-job-listener]
57+
--------------------------------------------------
58+
<1> `onResponse` is called back when the action is completed successfully
59+
<2> `onFailure` is called back when some unexpected error occurs

docs/java-rest/high-level/ml/open-job.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-open-job-exec
4444
the execution completes
4545

4646
The method does not block and returns immediately. The passed `ActionListener` is used
47-
to notify the caller of completion. A typical `ActionListner` for `OpenJobResponse` may
47+
to notify the caller of completion. A typical `ActionListener` for `OpenJobResponse` may
4848
look like
4949

5050
["source","java",subs="attributes,callouts,macros"]

docs/java-rest/high-level/supported-apis.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,12 @@ The Java High Level REST Client supports the following Machine Learning APIs:
207207
* <<java-rest-high-x-pack-ml-put-job>>
208208
* <<java-rest-high-x-pack-ml-delete-job>>
209209
* <<java-rest-high-x-pack-ml-open-job>>
210+
* <<java-rest-high-x-pack-ml-close-job>>
210211

211212
include::ml/put-job.asciidoc[]
212213
include::ml/delete-job.asciidoc[]
213214
include::ml/open-job.asciidoc[]
215+
include::ml/close-job.asciidoc[]
214216

215217
== Migration APIs
216218

0 commit comments

Comments
 (0)