From d4b319221e75d0e2b9e032ddfac547b03bd4468e Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Tue, 4 Sep 2018 14:52:14 -0500 Subject: [PATCH 1/5] HLRC: ML Forecast job --- .../client/MLRequestConverters.java | 14 ++ .../client/MachineLearningClient.java | 46 ++++++ .../client/ml/ForecastJobRequest.java | 140 ++++++++++++++++++ .../client/ml/ForecastJobResponse.java | 100 +++++++++++++ .../client/MLRequestConvertersTests.java | 16 ++ .../client/MachineLearningIT.java | 11 ++ .../client/ml/ForecastJobRequestTests.java | 51 +++++++ .../client/ml/ForecastJobResponseTests.java | 42 ++++++ 8 files changed, 420 insertions(+) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobResponse.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/ForecastJobRequestTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/ForecastJobResponseTests.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java index 35898a8a8e4d8..82d1edd21e1ec 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java @@ -27,6 +27,7 @@ import org.elasticsearch.client.ml.CloseJobRequest; import org.elasticsearch.client.ml.DeleteJobRequest; import org.elasticsearch.client.ml.FlushJobRequest; +import org.elasticsearch.client.ml.ForecastJobRequest; import org.elasticsearch.client.ml.GetBucketsRequest; import org.elasticsearch.client.ml.GetJobRequest; import org.elasticsearch.client.ml.GetJobStatsRequest; @@ -145,6 +146,19 @@ static Request flushJob(FlushJobRequest flushJobRequest) throws IOException { return request; } + static Request forecastJob(ForecastJobRequest forecastJobRequest) throws IOException { + String endpoint = new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("ml") + .addPathPartAsIs("anomaly_detectors") + .addPathPart(forecastJobRequest.getJobId()) + .addPathPartAsIs("_forecast") + .build(); + Request request = new Request(HttpPost.METHOD_NAME, endpoint); + request.setEntity(createEntity(forecastJobRequest, REQUEST_BODY_CONTENT_TYPE)); + return request; + } + static Request getBuckets(GetBucketsRequest getBucketsRequest) throws IOException { String endpoint = new EndpointBuilder() .addPathPartAsIs("_xpack") diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java index 4757ec3182b40..b40b3a58aa1e3 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java @@ -21,6 +21,8 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.ml.FlushJobRequest; import org.elasticsearch.client.ml.FlushJobResponse; +import org.elasticsearch.client.ml.ForecastJobRequest; +import org.elasticsearch.client.ml.ForecastJobResponse; import org.elasticsearch.client.ml.GetJobStatsRequest; import org.elasticsearch.client.ml.GetJobStatsResponse; import org.elasticsearch.client.ml.job.stats.JobStats; @@ -354,6 +356,50 @@ public void flushJobAsync(FlushJobRequest request, RequestOptions options, Actio Collections.emptySet()); } + /** + * Creates a forecast of an existing, opened Machine Learning Job + * + * This predicts the future behavior of a time series by using its historical behavior. + * + *

+ * For additional info + * see Forecast ML Job Documentation + *

+ * @param request ForecastJobRequest with forecasting options + * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return response containing forecast acknowledgement and new forecast's ID + * @throws IOException when there is a serialization issue sending the request or receiving the response + */ + public ForecastJobResponse forecastJob(ForecastJobRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, + MLRequestConverters::forecastJob, + options, + ForecastJobResponse::fromXContent, + Collections.emptySet()); + } + + /** + * Creates a forecast of an existing, opened Machine Learning Job + * + * This predicts the future behavior of a time series by using its historical behavior. + * + *

+ * For additional info + * see Forecast ML Job Documentation + *

+ * @param request ForecastJobRequest with forecasting options + * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener Listener to be notified upon request completion + */ + public void forecastJobAsync(ForecastJobRequest request, RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, + MLRequestConverters::forecastJob, + options, + ForecastJobResponse::fromXContent, + listener, + Collections.emptySet()); + } + /** * Gets the buckets for a Machine Learning Job. *

diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobRequest.java new file mode 100644 index 0000000000000..67d290c37f08b --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobRequest.java @@ -0,0 +1,140 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.ml; + +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.client.ml.job.config.Job; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; + +/** + * Pojo for forecasting an existing and open Machine Learning Job + */ +public class ForecastJobRequest extends ActionRequest implements ToXContentObject { + + public static final ParseField DURATION = new ParseField("duration"); + public static final ParseField EXPIRES_IN = new ParseField("expires_in"); + + public static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("forecast_job_request", (a) -> new ForecastJobRequest((String)a[0])); + + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID); + PARSER.declareString( + (request, val) -> request.setDuration(TimeValue.parseTimeValue(val, DURATION.getPreferredName())), DURATION); + PARSER.declareString( + (request, val) -> request.setExpiresIn(TimeValue.parseTimeValue(val, EXPIRES_IN.getPreferredName())), EXPIRES_IN); + } + + private final String jobId; + private TimeValue duration; + private TimeValue expiresIn; + + /** + * A new forecast request + * + * @param jobId the non-null, existing, and opened jobId to forecast + */ + public ForecastJobRequest(String jobId) { + this.jobId = jobId; + } + + public String getJobId() { + return jobId; + } + + public TimeValue getDuration() { + return duration; + } + + /** + * Set the forecast duration + * + * A period of time that indicates how far into the future to forecast. + * The default value is 1 day. The forecast starts at the last record that was processed. + * + * @param duration TimeValue for the duration of the forecast + */ + public void setDuration(TimeValue duration) { + this.duration = duration; + } + + public TimeValue getExpiresIn() { + return expiresIn; + } + + /** + * Set the forecast expiration + * + * The period of time that forecast results are retained. + * After a forecast expires, the results are deleted. The default value is 14 days. + * If set to a value of 0, the forecast is never automatically deleted. + * + * @param expiresIn TimeValue for the forecast expiration + */ + public void setExpiresIn(TimeValue expiresIn) { + this.expiresIn = expiresIn; + } + + @Override + public int hashCode() { + return Objects.hash(jobId, duration, expiresIn); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ForecastJobRequest other = (ForecastJobRequest) obj; + return Objects.equals(jobId, other.jobId) + && Objects.equals(duration, other.duration) + && Objects.equals(expiresIn, other.expiresIn); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { + builder.startObject(); + builder.field(Job.ID.getPreferredName(), jobId); + if (duration != null) { + builder.field(DURATION.getPreferredName(), duration.getStringRep()); + } + if (expiresIn != null) { + builder.field(EXPIRES_IN.getPreferredName(), expiresIn.getStringRep()); + } + builder.endObject(); + return builder; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobResponse.java new file mode 100644 index 0000000000000..fa6d565890f5c --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobResponse.java @@ -0,0 +1,100 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.ml; + +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Objects; + +/** + * Forecast response object + */ +public class ForecastJobResponse extends ActionResponse implements ToXContentObject { + + public static final ParseField ACKNOWLEDGED = new ParseField("acknowledged"); + public static final ParseField FORECAST_ID = new ParseField("forecast_id"); + + public static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("forecast_job_response", true, (a) -> new ForecastJobResponse((Boolean)a[0], (String)a[1])); + + static { + PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), ACKNOWLEDGED); + PARSER.declareString(ConstructingObjectParser.constructorArg(), FORECAST_ID); + } + + public static ForecastJobResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + private final boolean acknowledged; + private final String forecastId; + + public ForecastJobResponse(boolean acknowledged, String forecastId) { + this.acknowledged = acknowledged; + this.forecastId = forecastId; + } + + /** + * Forecast creating acknowledgement + * @return {@code true} indicates success, {@code false} otherwise + */ + public boolean isAcknowledged() { + return acknowledged; + } + + /** + * The created forecast ID + */ + public String getForecastId() { + return forecastId; + } + + @Override + public int hashCode() { + return Objects.hash(acknowledged, forecastId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ForecastJobResponse other = (ForecastJobResponse) obj; + return Objects.equals(acknowledged, other.acknowledged) + && Objects.equals(forecastId, other.forecastId); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(ACKNOWLEDGED.getPreferredName(), acknowledged); + builder.field(FORECAST_ID.getPreferredName(), forecastId); + builder.endObject(); + return builder; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java index bd997224bebda..f12de430b4681 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java @@ -26,6 +26,7 @@ import org.elasticsearch.client.ml.CloseJobRequest; import org.elasticsearch.client.ml.DeleteJobRequest; import org.elasticsearch.client.ml.FlushJobRequest; +import org.elasticsearch.client.ml.ForecastJobRequest; import org.elasticsearch.client.ml.GetBucketsRequest; import org.elasticsearch.client.ml.GetJobRequest; import org.elasticsearch.client.ml.GetJobStatsRequest; @@ -165,6 +166,21 @@ public void testFlushJob() throws Exception { requestEntityToString(request)); } + public void testForecastJob() throws Exception { + String jobId = randomAlphaOfLength(10); + ForecastJobRequest forecastJobRequest = new ForecastJobRequest(jobId); + + forecastJobRequest.setDuration(TimeValue.timeValueHours(10)); + forecastJobRequest.setExpiresIn(TimeValue.timeValueHours(12)); + Request request = MLRequestConverters.forecastJob(forecastJobRequest); + assertEquals(HttpPost.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + "/_forecast", request.getEndpoint()); + try (XContentParser parser = createParser(JsonXContent.jsonXContent, request.getEntity().getContent())) { + ForecastJobRequest parsedRequest = ForecastJobRequest.PARSER.apply(parser, null); + assertThat(parsedRequest, equalTo(forecastJobRequest)); + } + } + public void testGetBuckets() throws IOException { String jobId = randomAlphaOfLength(10); GetBucketsRequest getBucketsRequest = new GetBucketsRequest(jobId); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java index cd4b6ffc7691f..d26209d5094cc 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java @@ -43,6 +43,7 @@ import org.elasticsearch.client.ml.FlushJobRequest; import org.elasticsearch.client.ml.FlushJobResponse; import org.junit.After; +import org.junit.Ignore; import java.io.IOException; import java.util.Arrays; @@ -218,6 +219,16 @@ public void testGetJobStats() throws Exception { assertThat(exception.status().getStatus(), equalTo(404)); } + @Ignore("Awaiting post data endpoint to be created") + public void testForecastJob() throws Exception { + String jobId = "ml-forecast-job-test"; + + Job job = buildJob(jobId); + MachineLearningClient machineLearningClient = highLevelClient().machineLearning(); + machineLearningClient.putJob(new PutJobRequest(job), RequestOptions.DEFAULT); + machineLearningClient.openJob(new OpenJobRequest(jobId), RequestOptions.DEFAULT); + } + public static String randomValidJobId() { CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray()); return generator.ofCodePointsLength(random(), 10, 10); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/ForecastJobRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/ForecastJobRequestTests.java new file mode 100644 index 0000000000000..c6a33dad609ca --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/ForecastJobRequestTests.java @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.ml; + +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + +public class ForecastJobRequestTests extends AbstractXContentTestCase { + + @Override + protected ForecastJobRequest createTestInstance() { + ForecastJobRequest request = new ForecastJobRequest(randomAlphaOfLengthBetween(1, 20)); + + if (randomBoolean()) { + request.setExpiresIn(TimeValue.timeValueHours(randomInt(10))); + } + if (randomBoolean()) { + request.setDuration(TimeValue.timeValueHours(randomIntBetween(24, 72))); + } + return request; + } + + @Override + protected ForecastJobRequest doParseInstance(XContentParser parser) throws IOException { + return ForecastJobRequest.PARSER.apply(parser, null); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/ForecastJobResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/ForecastJobResponseTests.java new file mode 100644 index 0000000000000..c7833a79cba32 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/ForecastJobResponseTests.java @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.ml; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + +public class ForecastJobResponseTests extends AbstractXContentTestCase { + + @Override + protected ForecastJobResponse createTestInstance() { + return new ForecastJobResponse(randomBoolean(),randomAlphaOfLength(10)); + } + + @Override + protected ForecastJobResponse doParseInstance(XContentParser parser) throws IOException { + return ForecastJobResponse.PARSER.apply(parser, null); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } +} From c0fa98ac131c3ec1cd58bd6b209031308c82a6ba Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Thu, 6 Sep 2018 14:12:03 -0500 Subject: [PATCH 2/5] adding docs and integration tests --- .../client/MachineLearningClient.java | 2 +- .../client/ml/ForecastJobResponse.java | 4 +- .../client/MachineLearningIT.java | 10 +++ .../MlClientDocumentationIT.java | 59 ++++++++++++++ .../high-level/ml/forecast-job.asciidoc | 76 +++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 docs/java-rest/high-level/ml/forecast-job.asciidoc diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java index b40b3a58aa1e3..ebb81e973cdf8 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java @@ -379,7 +379,7 @@ public ForecastJobResponse forecastJob(ForecastJobRequest request, RequestOption } /** - * Creates a forecast of an existing, opened Machine Learning Job + * Creates a forecast of an existing, opened Machine Learning Job asynchronously * * This predicts the future behavior of a time series by using its historical behavior. * diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobResponse.java index fa6d565890f5c..b45275c5e59ad 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/ForecastJobResponse.java @@ -37,7 +37,9 @@ public class ForecastJobResponse extends ActionResponse implements ToXContentObj public static final ParseField FORECAST_ID = new ParseField("forecast_id"); public static final ConstructingObjectParser PARSER = - new ConstructingObjectParser<>("forecast_job_response", true, (a) -> new ForecastJobResponse((Boolean)a[0], (String)a[1])); + new ConstructingObjectParser<>("forecast_job_response", + true, + (a) -> new ForecastJobResponse((Boolean)a[0], (String)a[1])); static { PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), ACKNOWLEDGED); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java index d26209d5094cc..d942a01e07ffe 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java @@ -20,6 +20,8 @@ import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator; import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.client.ml.ForecastJobRequest; +import org.elasticsearch.client.ml.ForecastJobResponse; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.client.ml.GetJobStatsRequest; import org.elasticsearch.client.ml.GetJobStatsResponse; @@ -227,6 +229,14 @@ public void testForecastJob() throws Exception { MachineLearningClient machineLearningClient = highLevelClient().machineLearning(); machineLearningClient.putJob(new PutJobRequest(job), RequestOptions.DEFAULT); machineLearningClient.openJob(new OpenJobRequest(jobId), RequestOptions.DEFAULT); + + //post data + + ForecastJobRequest request = new ForecastJobRequest(jobId); + ForecastJobResponse response = execute(request, machineLearningClient::forecastJob, machineLearningClient::forecastJobAsync); + + assertTrue(response.isAcknowledged()); + assertNotNull(response.getForecastId()); } public static String randomValidJobId() { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java index 427f75a80d029..917d57d38c6f6 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java @@ -35,6 +35,8 @@ import org.elasticsearch.client.ml.DeleteJobResponse; import org.elasticsearch.client.ml.FlushJobRequest; import org.elasticsearch.client.ml.FlushJobResponse; +import org.elasticsearch.client.ml.ForecastJobRequest; +import org.elasticsearch.client.ml.ForecastJobResponse; import org.elasticsearch.client.ml.GetBucketsRequest; import org.elasticsearch.client.ml.GetBucketsResponse; import org.elasticsearch.client.ml.GetJobRequest; @@ -591,6 +593,63 @@ public void onFailure(Exception e) { } } + public void testForecastJob() throws Exception { + RestHighLevelClient client = highLevelClient(); + + Job job = MachineLearningIT.buildJob("forecasting-my-first-machine-learning-job"); + client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT); + client.machineLearning().openJob(new OpenJobRequest(job.getId()), RequestOptions.DEFAULT); + + //post data + + { + //tag::x-pack-ml-forecast-job-request + ForecastJobRequest forecastJobRequest = new ForecastJobRequest("forecasting-my-first-machine-learning-job"); //<1> + //end::x-pack-ml-forecast-job-request + + //tag::x-pack-ml-forecast-job-request-options + forecastJobRequest.setExpiresIn(TimeValue.timeValueHours(48)); //<1> + forecastJobRequest.setDuration(TimeValue.timeValueHours(24)); //<2> + //end::x-pack-ml-forecast-job-request-options + + //tag::x-pack-ml-forecast-job-execute + ForecastJobResponse forecastJobResponse = client.machineLearning().forecastJob(forecastJobRequest, RequestOptions.DEFAULT); + //end::x-pack-ml-forecast-job-execute + + //tag::x-pack-ml-forecast-job-response + boolean isAcknowledged = forecastJobResponse.isAcknowledged(); //<1> + String forecastId = forecastJobResponse.getForecastId(); //<2> + //end::x-pack-ml-forecast-job-response + + } + { + //tag::x-pack-ml-forecast-job-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(ForecastJobResponse forecastJobResponse) { + //<1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + //end::x-pack-ml-forecast-job-listener + ForecastJobRequest forecastJobRequest = new ForecastJobRequest("forecasting-my-first-machine-learning-job"); + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::x-pack-ml-forecast-job-execute-async + client.machineLearning().forecastJobAsync(forecastJobRequest, RequestOptions.DEFAULT, listener); //<1> + // end::x-pack-ml-forecast-job-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } + public void testGetOverallBuckets() throws IOException, InterruptedException { RestHighLevelClient client = highLevelClient(); diff --git a/docs/java-rest/high-level/ml/forecast-job.asciidoc b/docs/java-rest/high-level/ml/forecast-job.asciidoc new file mode 100644 index 0000000000000..88bd5fdb532dd --- /dev/null +++ b/docs/java-rest/high-level/ml/forecast-job.asciidoc @@ -0,0 +1,76 @@ +[[java-rest-high-x-pack-ml-forecast-job]] +=== Forecast Job API + +The Forecast Job API provides the ability to forecast a {ml} job's behavior based +on historical data. +It accepts a `ForecastJobRequest` object and responds +with a `ForecastJobResponse` object. + +[[java-rest-high-x-pack-ml-forecast-job-request]] +==== Forecast Job Request + +A `ForecastJobRequest` object gets created with an existing non-null `jobId`. +All other fields are optional for the request. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-forecast-job-request] +-------------------------------------------------- +<1> Constructing a new request referencing an existing `jobId` + +==== Optional Arguments + +The following arguments are optional. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-forecast-job-request-options] +-------------------------------------------------- +<1> Set when the forecast for the job should expire +<2> Set how far into the future should the forecast predict + +[[java-rest-high-x-pack-ml-forecast-job-execution]] +==== Execution + +The request can be executed through the `MachineLearningClient` contained +in the `RestHighLevelClient` object, accessed via the `machineLearningClient()` method. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-forecast-job-execute] +-------------------------------------------------- + +[[java-rest-high-x-pack-ml-forecast-job-execution-async]] +==== Asynchronous Execution + +The request can also be executed asynchronously: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-forecast-job-execute-async] +-------------------------------------------------- +<1> The `ForecastJobRequest` to execute and the `ActionListener` to use when +the execution completes + +The method does not block and returns immediately. The passed `ActionListener` is used +to notify the caller of completion. A typical `ActionListener` for `ForecastJobResponse` may +look like + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-forecast-job-listener] +-------------------------------------------------- +<1> `onResponse` is called back when the action is completed successfully +<2> `onFailure` is called back when some unexpected error occurs + +[[java-rest-high-x-pack-ml-forecast-job-response]] +==== Forecast Job Response + +A `ForecastJobResponse` contains an acknowledgement and the forecast ID + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-forecast-job-response] +-------------------------------------------------- +<1> `isAcknowledged()` indicates if the forecast was successful +<2> `getForecastId()` provides the ID of the forecast that was created \ No newline at end of file From 35848067464b6f7937e6875fe5b52c073ff244af Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Thu, 6 Sep 2018 14:13:08 -0500 Subject: [PATCH 3/5] fixing docs --- docs/java-rest/high-level/supported-apis.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 481a2470aa2d7..c0d8dd798779b 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -217,6 +217,7 @@ The Java High Level REST Client supports the following Machine Learning APIs: * <> * <> * <> +* <> * <> * <> * <> @@ -228,6 +229,7 @@ include::ml/open-job.asciidoc[] include::ml/close-job.asciidoc[] include::ml/flush-job.asciidoc[] include::ml/get-job-stats.asciidoc[] +include::ml/forecast-job.asciidoc[] include::ml/get-buckets.asciidoc[] include::ml/get-overall-buckets.asciidoc[] include::ml/get-records.asciidoc[] From e61f627df170cb08c987a24470ed9f2610e42cb2 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Fri, 7 Sep 2018 08:36:55 -0500 Subject: [PATCH 4/5] HLRC: ML Forecast job --- .../elasticsearch/client/MachineLearningIT.java | 13 ++++++++++--- .../documentation/MlClientDocumentationIT.java | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java index ab7abfc940dde..fb715683b2709 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java @@ -48,7 +48,6 @@ import org.elasticsearch.client.ml.FlushJobRequest; import org.elasticsearch.client.ml.FlushJobResponse; import org.junit.After; -import org.junit.Ignore; import java.io.IOException; import java.util.Arrays; @@ -226,7 +225,6 @@ public void testGetJobStats() throws Exception { assertThat(exception.status().getStatus(), equalTo(404)); } - @Ignore("Awaiting post data endpoint to be created") public void testForecastJob() throws Exception { String jobId = "ml-forecast-job-test"; Job job = buildJob(jobId); @@ -234,7 +232,16 @@ public void testForecastJob() throws Exception { machineLearningClient.putJob(new PutJobRequest(job), RequestOptions.DEFAULT); machineLearningClient.openJob(new OpenJobRequest(jobId), RequestOptions.DEFAULT); - //post data + PostDataRequest.JsonBuilder builder = new PostDataRequest.JsonBuilder(); + for(int i = 0; i < 30; i++) { + Map hashMap = new HashMap<>(); + hashMap.put("total", randomInt(1000)); + hashMap.put("timestamp", (i+1)*1000); + builder.addDoc(hashMap); + } + PostDataRequest postDataRequest = new PostDataRequest(jobId, builder); + machineLearningClient.postData(postDataRequest, RequestOptions.DEFAULT); + machineLearningClient.flushJob(new FlushJobRequest(jobId), RequestOptions.DEFAULT); ForecastJobRequest request = new ForecastJobRequest(jobId); ForecastJobResponse response = execute(request, machineLearningClient::forecastJob, machineLearningClient::forecastJobAsync); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java index c6f51c9b89d72..9abef54d0d24f 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java @@ -703,7 +703,16 @@ public void testForecastJob() throws Exception { client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT); client.machineLearning().openJob(new OpenJobRequest(job.getId()), RequestOptions.DEFAULT); - //post data + PostDataRequest.JsonBuilder builder = new PostDataRequest.JsonBuilder(); + for(int i = 0; i < 30; i++) { + Map hashMap = new HashMap<>(); + hashMap.put("total", randomInt(1000)); + hashMap.put("timestamp", (i+1)*1000); + builder.addDoc(hashMap); + } + PostDataRequest postDataRequest = new PostDataRequest(job.getId(), builder); + client.machineLearning().postData(postDataRequest, RequestOptions.DEFAULT); + client.machineLearning().flushJob(new FlushJobRequest(job.getId()), RequestOptions.DEFAULT); { //tag::x-pack-ml-forecast-job-request @@ -723,7 +732,8 @@ public void testForecastJob() throws Exception { boolean isAcknowledged = forecastJobResponse.isAcknowledged(); //<1> String forecastId = forecastJobResponse.getForecastId(); //<2> //end::x-pack-ml-forecast-job-response - + assertTrue(isAcknowledged); + assertNotNull(forecastId); } { //tag::x-pack-ml-forecast-job-listener From dd71e3d168770e8db3f80ebc459db4edbb9e9fb9 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Fri, 7 Sep 2018 08:41:30 -0500 Subject: [PATCH 5/5] fixing comment --- .../java/org/elasticsearch/client/MachineLearningClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java index 6ea546456631a..85c5771f3450b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java @@ -363,7 +363,6 @@ public void flushJobAsync(FlushJobRequest request, RequestOptions options, Actio } /** -<<<<<<< HEAD * Creates a forecast of an existing, opened Machine Learning Job * * This predicts the future behavior of a time series by using its historical behavior. @@ -384,6 +383,7 @@ public ForecastJobResponse forecastJob(ForecastJobRequest request, RequestOption ForecastJobResponse::fromXContent, Collections.emptySet()); } + /** * Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job} *