Skip to content

Commit 94a9b25

Browse files
authored
Adding ML HLRC wrapper and put_job API call (#32726)
* Adding ML HLRC wrapper and put_job API call * Changing integration test job to have consistent stucture
1 parent 40b0a3a commit 94a9b25

File tree

10 files changed

+425
-6
lines changed

10 files changed

+425
-6
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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;
20+
21+
import org.elasticsearch.action.ActionListener;
22+
import org.elasticsearch.protocol.xpack.ml.PutJobRequest;
23+
import org.elasticsearch.protocol.xpack.ml.PutJobResponse;
24+
25+
import java.io.IOException;
26+
import java.util.Collections;
27+
28+
/**
29+
* Machine Learning API client wrapper for the {@link RestHighLevelClient}
30+
*
31+
* <p>
32+
* See the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-apis.html">
33+
* X-Pack Machine Learning APIs </a> for additional information.
34+
*/
35+
public final class MachineLearningClient {
36+
37+
private final RestHighLevelClient restHighLevelClient;
38+
39+
MachineLearningClient(RestHighLevelClient restHighLevelClient) {
40+
this.restHighLevelClient = restHighLevelClient;
41+
}
42+
43+
/**
44+
* Creates a new Machine Learning Job
45+
* <p>
46+
* For additional info
47+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-job.html">ML PUT job documentation</a>
48+
*
49+
* @param request the PutJobRequest containing the {@link org.elasticsearch.protocol.xpack.ml.job.config.Job} settings
50+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
51+
* @return PutJobResponse with enclosed {@link org.elasticsearch.protocol.xpack.ml.job.config.Job} object
52+
* @throws IOException when there is a serialization issue sending the request or receiving the response
53+
*/
54+
public PutJobResponse putJob(PutJobRequest request, RequestOptions options) throws IOException {
55+
return restHighLevelClient.performRequestAndParseEntity(request,
56+
RequestConverters::putMachineLearningJob,
57+
options,
58+
PutJobResponse::fromXContent,
59+
Collections.emptySet());
60+
}
61+
62+
/**
63+
* Creates a new Machine Learning Job asynchronously and notifies listener on completion
64+
* <p>
65+
* For additional info
66+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-job.html">ML PUT job documentation</a>
67+
*
68+
* @param request the request containing the {@link org.elasticsearch.protocol.xpack.ml.job.config.Job} settings
69+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
70+
* @param listener Listener to be notified upon request completion
71+
*/
72+
public void putJobAsync(PutJobRequest request, RequestOptions options, ActionListener<PutJobResponse> listener) {
73+
restHighLevelClient.performRequestAsyncAndParseEntity(request,
74+
RequestConverters::putMachineLearningJob,
75+
options,
76+
PutJobResponse::fromXContent,
77+
listener,
78+
Collections.emptySet());
79+
}
80+
}

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@
3939
import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest;
4040
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
4141
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
42+
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
4243
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
4344
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest;
45+
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest;
4446
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
4547
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
46-
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
47-
import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest;
4848
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
4949
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
5050
import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
@@ -78,8 +78,8 @@
7878
import org.elasticsearch.action.index.IndexRequest;
7979
import org.elasticsearch.action.ingest.DeletePipelineRequest;
8080
import org.elasticsearch.action.ingest.GetPipelineRequest;
81-
import org.elasticsearch.action.ingest.SimulatePipelineRequest;
8281
import org.elasticsearch.action.ingest.PutPipelineRequest;
82+
import org.elasticsearch.action.ingest.SimulatePipelineRequest;
8383
import org.elasticsearch.action.search.ClearScrollRequest;
8484
import org.elasticsearch.action.search.MultiSearchRequest;
8585
import org.elasticsearch.action.search.SearchRequest;
@@ -107,11 +107,12 @@
107107
import org.elasticsearch.index.VersionType;
108108
import org.elasticsearch.index.rankeval.RankEvalRequest;
109109
import org.elasticsearch.protocol.xpack.XPackInfoRequest;
110+
import org.elasticsearch.protocol.xpack.XPackUsageRequest;
110111
import org.elasticsearch.protocol.xpack.license.GetLicenseRequest;
111112
import org.elasticsearch.protocol.xpack.license.PutLicenseRequest;
113+
import org.elasticsearch.protocol.xpack.ml.PutJobRequest;
112114
import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest;
113115
import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest;
114-
import org.elasticsearch.protocol.xpack.XPackUsageRequest;
115116
import org.elasticsearch.rest.action.search.RestSearchAction;
116117
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
117118
import org.elasticsearch.script.mustache.SearchTemplateRequest;
@@ -1182,6 +1183,19 @@ static Request getLicense(GetLicenseRequest getLicenseRequest) {
11821183
return request;
11831184
}
11841185

1186+
static Request putMachineLearningJob(PutJobRequest putJobRequest) throws IOException {
1187+
String endpoint = new EndpointBuilder()
1188+
.addPathPartAsIs("_xpack")
1189+
.addPathPartAsIs("ml")
1190+
.addPathPartAsIs("anomaly_detectors")
1191+
.addPathPart(putJobRequest.getJob().getId())
1192+
.build();
1193+
1194+
Request request = new Request(HttpPut.METHOD_NAME, endpoint);
1195+
request.setEntity(createEntity(putJobRequest, REQUEST_BODY_CONTENT_TYPE));
1196+
return request;
1197+
}
1198+
11851199
private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
11861200
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
11871201
return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ public class RestHighLevelClient implements Closeable {
210210
private final XPackClient xPackClient = new XPackClient(this);
211211
private final WatcherClient watcherClient = new WatcherClient(this);
212212
private final LicenseClient licenseClient = new LicenseClient(this);
213+
private final MachineLearningClient machineLearningClient = new MachineLearningClient(this);
213214

214215
/**
215216
* Creates a {@link RestHighLevelClient} given the low level {@link RestClientBuilder} that allows to build the
@@ -333,6 +334,20 @@ public final XPackClient xpack() {
333334
*/
334335
public LicenseClient license() { return licenseClient; }
335336

337+
/**
338+
* Provides methods for accessing the Elastic Licensed Machine Learning APIs that
339+
* are shipped with the Elastic Stack distribution of Elasticsearch. All of
340+
* these APIs will 404 if run against the OSS distribution of Elasticsearch.
341+
* <p>
342+
* See the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-apis.html">
343+
* Machine Learning APIs on elastic.co</a> for more information.
344+
*
345+
* @return the client wrapper for making Machine Learning API calls
346+
*/
347+
public MachineLearningClient machineLearning() {
348+
return machineLearningClient;
349+
}
350+
336351
/**
337352
* Executes a bulk request using the Bulk API.
338353
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on elastic.co</a>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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;
20+
21+
import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator;
22+
import org.elasticsearch.common.unit.TimeValue;
23+
import org.elasticsearch.protocol.xpack.ml.PutJobRequest;
24+
import org.elasticsearch.protocol.xpack.ml.PutJobResponse;
25+
import org.elasticsearch.protocol.xpack.ml.job.config.AnalysisConfig;
26+
import org.elasticsearch.protocol.xpack.ml.job.config.DataDescription;
27+
import org.elasticsearch.protocol.xpack.ml.job.config.Detector;
28+
import org.elasticsearch.protocol.xpack.ml.job.config.Job;
29+
30+
import java.util.Arrays;
31+
import java.util.concurrent.TimeUnit;
32+
33+
import static org.hamcrest.Matchers.is;
34+
35+
public class MachineLearningIT extends ESRestHighLevelClientTestCase {
36+
37+
public void testPutJob() throws Exception {
38+
String jobId = randomValidJobId();
39+
Job job = buildJob(jobId);
40+
MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
41+
42+
PutJobResponse putJobResponse = execute(new PutJobRequest(job), machineLearningClient::putJob, machineLearningClient::putJobAsync);
43+
Job createdJob = putJobResponse.getResponse();
44+
45+
assertThat(createdJob.getId(), is(jobId));
46+
assertThat(createdJob.getJobType(), is(Job.ANOMALY_DETECTOR_JOB_TYPE));
47+
}
48+
49+
public static String randomValidJobId() {
50+
CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray());
51+
return generator.ofCodePointsLength(random(), 10, 10);
52+
}
53+
54+
private static Job buildJob(String jobId) {
55+
Job.Builder builder = new Job.Builder(jobId);
56+
builder.setDescription(randomAlphaOfLength(10));
57+
58+
Detector detector = new Detector.Builder()
59+
.setFieldName("total")
60+
.setFunction("sum")
61+
.setDetectorDescription(randomAlphaOfLength(10))
62+
.build();
63+
AnalysisConfig.Builder configBuilder = new AnalysisConfig.Builder(Arrays.asList(detector));
64+
configBuilder.setBucketSpan(new TimeValue(randomIntBetween(1, 10), TimeUnit.SECONDS));
65+
builder.setAnalysisConfig(configBuilder);
66+
67+
DataDescription.Builder dataDescription = new DataDescription.Builder();
68+
dataDescription.setTimeFormat(randomFrom(DataDescription.EPOCH_MS, DataDescription.EPOCH));
69+
dataDescription.setTimeField(randomAlphaOfLength(10));
70+
builder.setDataDescription(dataDescription);
71+
72+
return builder.build();
73+
}
74+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
package org.elasticsearch.client;
2121

2222
import com.fasterxml.jackson.core.JsonParseException;
23-
2423
import org.apache.http.HttpEntity;
2524
import org.apache.http.HttpHost;
2625
import org.apache.http.HttpResponse;
@@ -757,6 +756,7 @@ public void testApiNamingConventions() throws Exception {
757756
//TODO xpack api are currently ignored, we need to load xpack yaml spec too
758757
if (apiName.startsWith("xpack.") == false &&
759758
apiName.startsWith("license.") == false &&
759+
apiName.startsWith("machine_learning.") == false &&
760760
apiName.startsWith("watcher.") == false) {
761761
apiNotFound.add(apiName);
762762
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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.protocol.xpack.ml;
20+
21+
import org.elasticsearch.action.ActionRequest;
22+
import org.elasticsearch.action.ActionRequestValidationException;
23+
import org.elasticsearch.common.Strings;
24+
import org.elasticsearch.common.xcontent.ToXContentObject;
25+
import org.elasticsearch.common.xcontent.XContentBuilder;
26+
import org.elasticsearch.protocol.xpack.ml.job.config.Job;
27+
28+
import java.io.IOException;
29+
import java.util.Objects;
30+
31+
public class PutJobRequest extends ActionRequest implements ToXContentObject {
32+
33+
private final Job job;
34+
35+
public PutJobRequest(Job job) {
36+
this.job = job;
37+
}
38+
39+
public Job getJob() {
40+
return job;
41+
}
42+
43+
@Override
44+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
45+
return job.toXContent(builder, params);
46+
}
47+
48+
@Override
49+
public boolean equals(Object object) {
50+
if (this == object) {
51+
return true;
52+
}
53+
54+
if (object == null || getClass() != object.getClass()) {
55+
return false;
56+
}
57+
58+
PutJobRequest request = (PutJobRequest) object;
59+
return Objects.equals(job, request.job);
60+
}
61+
62+
@Override
63+
public int hashCode() {
64+
return Objects.hash(job);
65+
}
66+
67+
@Override
68+
public final String toString() {
69+
return Strings.toString(this);
70+
}
71+
72+
@Override
73+
public ActionRequestValidationException validate() {
74+
return null;
75+
}
76+
}

0 commit comments

Comments
 (0)