From d9d5ac2f6002602159b90980c9ab96b81daf2b03 Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Tue, 20 Nov 2018 17:41:55 +0000 Subject: [PATCH] [HLRC][ML] Add ML revert model snapshot API Relates to #29827 --- .../client/MLRequestConverters.java | 16 +++ .../client/MachineLearningClient.java | 43 +++++++ .../client/ml/RevertModelSnapshotRequest.java | 120 ++++++++++++++++++ .../ml/RevertModelSnapshotResponse.java | 92 ++++++++++++++ .../client/MLRequestConvertersTests.java | 19 +++ .../client/MachineLearningIT.java | 57 +++++++++ .../MlClientDocumentationIT.java | 78 ++++++++++++ .../ml/RevertModelSnapshotRequestTests.java | 50 ++++++++ .../ml/RevertModelSnapshotResponseTests.java | 46 +++++++ .../ml/revert-model-snapshot.asciidoc | 46 +++++++ .../high-level/supported-apis.asciidoc | 8 +- 11 files changed, 572 insertions(+), 3 deletions(-) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/RevertModelSnapshotRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/RevertModelSnapshotResponse.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/RevertModelSnapshotRequestTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/RevertModelSnapshotResponseTests.java create mode 100644 docs/java-rest/high-level/ml/revert-model-snapshot.asciidoc 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 2b6571272f1eb..e3fa747e9ed6f 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 @@ -58,6 +58,7 @@ import org.elasticsearch.client.ml.PutDatafeedRequest; import org.elasticsearch.client.ml.PutFilterRequest; import org.elasticsearch.client.ml.PutJobRequest; +import org.elasticsearch.client.ml.RevertModelSnapshotRequest; import org.elasticsearch.client.ml.StartDatafeedRequest; import org.elasticsearch.client.ml.StopDatafeedRequest; import org.elasticsearch.client.ml.UpdateDatafeedRequest; @@ -409,6 +410,21 @@ static Request updateModelSnapshot(UpdateModelSnapshotRequest updateModelSnapsho return request; } + static Request revertModelSnapshot(RevertModelSnapshotRequest revertModelSnapshotsRequest) throws IOException { + String endpoint = new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("ml") + .addPathPartAsIs("anomaly_detectors") + .addPathPart(revertModelSnapshotsRequest.getJobId()) + .addPathPartAsIs("model_snapshots") + .addPathPart(revertModelSnapshotsRequest.getSnapshotId()) + .addPathPart("_revert") + .build(); + Request request = new Request(HttpPost.METHOD_NAME, endpoint); + request.setEntity(createEntity(revertModelSnapshotsRequest, REQUEST_BODY_CONTENT_TYPE)); + return request; + } + static Request getOverallBuckets(GetOverallBucketsRequest getOverallBucketsRequest) 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 817f22fb4ce3e..a17791a751056 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 @@ -75,6 +75,8 @@ import org.elasticsearch.client.ml.PutFilterResponse; import org.elasticsearch.client.ml.PutJobRequest; import org.elasticsearch.client.ml.PutJobResponse; +import org.elasticsearch.client.ml.RevertModelSnapshotRequest; +import org.elasticsearch.client.ml.RevertModelSnapshotResponse; import org.elasticsearch.client.ml.StartDatafeedRequest; import org.elasticsearch.client.ml.StartDatafeedResponse; import org.elasticsearch.client.ml.StopDatafeedRequest; @@ -513,6 +515,47 @@ public void deleteModelSnapshotAsync(DeleteModelSnapshotRequest request, Request Collections.emptySet()); } + /** + * Reverts to a particular Machine Learning Model Snapshot + *

+ * For additional info + * see + * ML Revert Model Snapshot documentation + * + * @param request The request to revert to a previous model snapshot + * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return action acknowledgement + * @throws IOException when there is a serialization issue sending the request or receiving the response + */ + public RevertModelSnapshotResponse revertModelSnapshot(RevertModelSnapshotRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, + MLRequestConverters::revertModelSnapshot, + options, + RevertModelSnapshotResponse::fromXContent, + Collections.emptySet()); + } + + /** + * Reverts to a particular Machine Learning Model Snapshot asynchronously and notifies the listener on completion + *

+ * For additional info + * see + * ML Revert Model Snapshot documentation + * + * @param request The request to revert to a previous model snapshot + * @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 revertModelSnapshotAsync(RevertModelSnapshotRequest request, RequestOptions options, + ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, + MLRequestConverters::revertModelSnapshot, + options, + RevertModelSnapshotResponse::fromXContent, + listener, + Collections.emptySet()); + } + /** * Creates a new Machine Learning Datafeed *

diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/RevertModelSnapshotRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/RevertModelSnapshotRequest.java new file mode 100644 index 0000000000000..3a38cd86a0565 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/RevertModelSnapshotRequest.java @@ -0,0 +1,120 @@ +/* + * 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.client.ml.job.process.ModelSnapshot; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; + +/** + * A request to revert to a specific model snapshot for a given job + */ +public class RevertModelSnapshotRequest extends ActionRequest implements ToXContentObject { + + + public static final ParseField DELETE_INTERVENING = new ParseField("delete_intervening_results"); + + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "revert_model_snapshots_request", a -> new RevertModelSnapshotRequest((String) a[0], (String) a[1])); + + + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID); + PARSER.declareString(ConstructingObjectParser.constructorArg(), ModelSnapshot.SNAPSHOT_ID); + PARSER.declareBoolean(RevertModelSnapshotRequest::setDeleteInterveningResults, DELETE_INTERVENING); + } + + private final String jobId; + private final String snapshotId; + private Boolean deleteInterveningResults; + + /** + * Constructs a request to revert to a given model snapshot + * @param jobId id of the job for which to revert the model snapshot + * @param snapshotId id of the snapshot to which to revert + */ + public RevertModelSnapshotRequest(String jobId, String snapshotId) { + this.jobId = Objects.requireNonNull(jobId, "[" + Job.ID + "] must not be null"); + this.snapshotId = Objects.requireNonNull(snapshotId, "[" + ModelSnapshot.SNAPSHOT_ID + "] must not be null"); + } + + public String getJobId() { + return jobId; + } + + public String getSnapshotId() { + return snapshotId; + } + + public Boolean getDeleteInterveningResults() { + return deleteInterveningResults; + } + + /** + * Sets the request flag that indicates whether or not intervening results should be deleted. + * @param deleteInterveningResults Flag that indicates whether or not intervening results should be deleted. + */ + public void setDeleteInterveningResults(Boolean deleteInterveningResults) { + this.deleteInterveningResults = deleteInterveningResults; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(Job.ID.getPreferredName(), jobId); + builder.field(ModelSnapshot.SNAPSHOT_ID.getPreferredName(), snapshotId); + if (deleteInterveningResults != null) { + builder.field(DELETE_INTERVENING.getPreferredName(), deleteInterveningResults); + } + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RevertModelSnapshotRequest request = (RevertModelSnapshotRequest) obj; + return Objects.equals(jobId, request.jobId) + && Objects.equals(snapshotId, request.snapshotId) + && Objects.equals(deleteInterveningResults, request.deleteInterveningResults); + } + + @Override + public int hashCode() { + return Objects.hash(jobId, snapshotId, deleteInterveningResults); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/RevertModelSnapshotResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/RevertModelSnapshotResponse.java new file mode 100644 index 0000000000000..575f38043b3c8 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/RevertModelSnapshotResponse.java @@ -0,0 +1,92 @@ +/* + * 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.client.ml.job.process.ModelSnapshot; +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; + +/** + * A response containing the reverted model snapshot + */ +public class RevertModelSnapshotResponse extends ActionResponse implements ToXContentObject { + + private static final ParseField MODEL = new ParseField("model"); + + public static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("revert_model_snapshot_response", true, + a -> new RevertModelSnapshotResponse((ModelSnapshot.Builder) a[0])); + + static { + PARSER.declareObject(ConstructingObjectParser.constructorArg(), ModelSnapshot.PARSER, MODEL); + } + + public static RevertModelSnapshotResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + public RevertModelSnapshotResponse(ModelSnapshot.Builder modelSnapshot) { + this.model = modelSnapshot.build(); + } + + private final ModelSnapshot model; + + /** + * Get full information about the reverted model snapshot + * @return the reverted model snapshot. + */ + public ModelSnapshot getModel() { + return model; + } + + @Override + public int hashCode() { + return Objects.hash(model); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RevertModelSnapshotResponse other = (RevertModelSnapshotResponse) obj; + return Objects.equals(model, other.model); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (model != null) { + builder.field(MODEL.getPreferredName(), model); + } + 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 f741aef511f98..112a1d1ac82f0 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 @@ -54,6 +54,7 @@ import org.elasticsearch.client.ml.PutDatafeedRequest; import org.elasticsearch.client.ml.PutFilterRequest; import org.elasticsearch.client.ml.PutJobRequest; +import org.elasticsearch.client.ml.RevertModelSnapshotRequest; import org.elasticsearch.client.ml.StartDatafeedRequest; import org.elasticsearch.client.ml.StartDatafeedRequestTests; import org.elasticsearch.client.ml.StopDatafeedRequest; @@ -445,6 +446,24 @@ public void testUpdateModelSnapshot() throws IOException { } } + public void testRevertModelSnapshot() throws IOException { + String jobId = randomAlphaOfLength(10); + String snapshotId = randomAlphaOfLength(10); + RevertModelSnapshotRequest revertModelSnapshotRequest = new RevertModelSnapshotRequest(jobId, snapshotId); + if (randomBoolean()) { + revertModelSnapshotRequest.setDeleteInterveningResults(randomBoolean()); + } + + Request request = MLRequestConverters.revertModelSnapshot(revertModelSnapshotRequest); + assertEquals(HttpPost.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + "/model_snapshots/" + snapshotId + "/_revert", + request.getEndpoint()); + try (XContentParser parser = createParser(JsonXContent.jsonXContent, request.getEntity().getContent())) { + RevertModelSnapshotRequest parsedRequest = RevertModelSnapshotRequest.PARSER.apply(parser, null); + assertThat(parsedRequest, equalTo(revertModelSnapshotRequest)); + } + } + public void testGetOverallBuckets() throws IOException { String jobId = randomAlphaOfLength(10); GetOverallBucketsRequest getOverallBucketsRequest = new GetOverallBucketsRequest(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 d2def3ec68e31..dca9914412e18 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 @@ -72,6 +72,8 @@ import org.elasticsearch.client.ml.PutFilterResponse; import org.elasticsearch.client.ml.PutJobRequest; import org.elasticsearch.client.ml.PutJobResponse; +import org.elasticsearch.client.ml.RevertModelSnapshotRequest; +import org.elasticsearch.client.ml.RevertModelSnapshotResponse; import org.elasticsearch.client.ml.StartDatafeedRequest; import org.elasticsearch.client.ml.StartDatafeedResponse; import org.elasticsearch.client.ml.StopDatafeedRequest; @@ -96,6 +98,7 @@ import org.elasticsearch.client.ml.job.config.JobState; import org.elasticsearch.client.ml.job.config.JobUpdate; import org.elasticsearch.client.ml.job.config.MlFilter; +import org.elasticsearch.client.ml.job.process.ModelSnapshot; import org.elasticsearch.client.ml.job.stats.JobStats; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentType; @@ -1115,6 +1118,28 @@ public void createModelSnapshot(String jobId, String snapshotId) throws IOExcept highLevelClient().index(indexRequest, RequestOptions.DEFAULT); } + public void createModelSnapshots(String jobId, List snapshotIds) throws IOException { + Job job = MachineLearningIT.buildJob(jobId); + highLevelClient().machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT); + + for(String snapshotId : snapshotIds) { + String documentId = jobId + "_model_snapshot_" + snapshotId; + IndexRequest indexRequest = new IndexRequest(".ml-anomalies-shared", "doc", documentId); + indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + indexRequest.source("{\"job_id\":\"" + jobId + "\", \"timestamp\":1541587919000, " + + "\"description\":\"State persisted due to job close at 2018-11-07T10:51:59+0000\", " + + "\"snapshot_id\":\"" + snapshotId + "\", \"snapshot_doc_count\":1, \"model_size_stats\":{" + + "\"job_id\":\"" + jobId + "\", \"result_type\":\"model_size_stats\",\"model_bytes\":51722, " + + "\"total_by_field_count\":3, \"total_over_field_count\":0, \"total_partition_field_count\":2," + + "\"bucket_allocation_failures_count\":0, \"memory_status\":\"ok\", \"log_time\":1541587919000, " + + "\"timestamp\":1519930800000}, \"latest_record_time_stamp\":1519931700000," + + "\"latest_result_time_stamp\":1519930800000, \"retain\":false, " + + "\"quantiles\":{\"job_id\":\""+jobId+"\", \"timestamp\":1541587919000, " + + "\"quantile_state\":\"state\"}}", XContentType.JSON); + highLevelClient().index(indexRequest, RequestOptions.DEFAULT); + } + } + public void testDeleteModelSnapshot() throws IOException { String jobId = "test-delete-model-snapshot"; String snapshotId = "1541587919"; @@ -1166,4 +1191,36 @@ public void testUpdateModelSnapshot() throws Exception { assertEquals("Updated description", getModelSnapshotsResponse2.snapshots().get(0).getDescription()); } + + public void testRevertModelSnapshot() throws IOException { + String jobId = "test-revert-model-snapshot"; + + List snapshotIds = new ArrayList<>(); + + String snapshotId1 = "1541587919"; + String snapshotId2 = "1541588919"; + String snapshotId3 = "1541589919"; + + snapshotIds.add(snapshotId1); + snapshotIds.add(snapshotId2); + snapshotIds.add(snapshotId3); + + createModelSnapshots(jobId, snapshotIds); + + MachineLearningClient machineLearningClient = highLevelClient().machineLearning(); + + for (String snapshotId : snapshotIds){ + RevertModelSnapshotRequest request = new RevertModelSnapshotRequest(jobId, snapshotId); + if (randomBoolean()) { + request.setDeleteInterveningResults(randomBoolean()); + } + + RevertModelSnapshotResponse response = execute(request, machineLearningClient::revertModelSnapshot, + machineLearningClient::revertModelSnapshotAsync); + + ModelSnapshot model = response.getModel(); + + assertEquals(snapshotId, model.getSnapshotId()); + } + } } 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 82f1f6d176ac4..12c0035672a53 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 @@ -88,6 +88,8 @@ import org.elasticsearch.client.ml.PutFilterResponse; import org.elasticsearch.client.ml.PutJobRequest; import org.elasticsearch.client.ml.PutJobResponse; +import org.elasticsearch.client.ml.RevertModelSnapshotRequest; +import org.elasticsearch.client.ml.RevertModelSnapshotResponse; import org.elasticsearch.client.ml.StartDatafeedRequest; import org.elasticsearch.client.ml.StartDatafeedResponse; import org.elasticsearch.client.ml.StopDatafeedRequest; @@ -2049,6 +2051,82 @@ public void onFailure(Exception e) { } } + public void testRevertModelSnapshot() throws IOException, InterruptedException { + RestHighLevelClient client = highLevelClient(); + + String jobId = "test-revert-model-snapshot"; + String snapshotId = "1541587919"; + Job job = MachineLearningIT.buildJob(jobId); + client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT); + + // Let us index a snapshot + String documentId = jobId + "_model_snapshot_" + snapshotId; + IndexRequest indexRequest = new IndexRequest(".ml-anomalies-shared", "doc", documentId); + indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + indexRequest.source("{\"job_id\":\"test-revert-model-snapshot\", \"timestamp\":1541587919000, " + + "\"description\":\"State persisted due to job close at 2018-11-07T10:51:59+0000\", " + + "\"snapshot_id\":\"1541587919\", \"snapshot_doc_count\":1, \"model_size_stats\":{" + + "\"job_id\":\"test-revert-model-snapshot\", \"result_type\":\"model_size_stats\",\"model_bytes\":51722, " + + "\"total_by_field_count\":3, \"total_over_field_count\":0, \"total_partition_field_count\":2," + + "\"bucket_allocation_failures_count\":0, \"memory_status\":\"ok\", \"log_time\":1541587919000, " + + "\"timestamp\":1519930800000}, \"latest_record_time_stamp\":1519931700000," + + "\"latest_result_time_stamp\":1519930800000, \"retain\":false, " + + "\"quantiles\":{\"job_id\":\"test-revert-model-snapshot\", \"timestamp\":1541587919000, " + + "\"quantile_state\":\"state\"}}", XContentType.JSON); + client.index(indexRequest, RequestOptions.DEFAULT); + + { + // tag::revert-model-snapshot-request + RevertModelSnapshotRequest request = new RevertModelSnapshotRequest(jobId, snapshotId); // <1> + // end::revert-model-snapshot-request + + // tag::revert-model-snapshot-delete-intervening-results + request.setDeleteInterveningResults(true); // <1> + // end::revert-model-snapshot-delete-intervening-results + + // tag::revert-model-snapshot-execute + RevertModelSnapshotResponse response = client.machineLearning().revertModelSnapshot(request, RequestOptions.DEFAULT); + // end::revert-model-snapshot-execute + + // tag::revert-model-snapshot-response + ModelSnapshot modelSnapshot = response.getModel(); // <1> + // end::revert-model-snapshot-response + + assertEquals(snapshotId, modelSnapshot.getSnapshotId()); + assertEquals("State persisted due to job close at 2018-11-07T10:51:59+0000", modelSnapshot.getDescription()); + assertEquals(51722, modelSnapshot.getModelSizeStats().getModelBytes()); + } + { + RevertModelSnapshotRequest request = new RevertModelSnapshotRequest(jobId, snapshotId); + + // tag::revert-model-snapshot-execute-listener + ActionListener listener = + new ActionListener() { + @Override + public void onResponse(RevertModelSnapshotResponse revertModelSnapshotResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::revert-model-snapshot-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::revert-model-snapshot-execute-async + client.machineLearning().revertModelSnapshotAsync(request, RequestOptions.DEFAULT, listener); // <1> + // end::revert-model-snapshot-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } + + public void testUpdateModelSnapshot() throws IOException, InterruptedException { RestHighLevelClient client = highLevelClient(); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/RevertModelSnapshotRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/RevertModelSnapshotRequestTests.java new file mode 100644 index 0000000000000..5a9581df8f700 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/RevertModelSnapshotRequestTests.java @@ -0,0 +1,50 @@ +/* + * 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 RevertModelSnapshotRequestTests extends AbstractXContentTestCase { + + @Override + protected RevertModelSnapshotRequest createTestInstance() { + String jobId = randomAlphaOfLengthBetween(1, 20); + String snapshotId = randomAlphaOfLengthBetween(1, 20); + RevertModelSnapshotRequest request = new RevertModelSnapshotRequest(jobId, snapshotId); + if (randomBoolean()) { + request.setDeleteInterveningResults(randomBoolean()); + } + + return request; + } + + @Override + protected RevertModelSnapshotRequest doParseInstance(XContentParser parser) throws IOException { + return RevertModelSnapshotRequest.PARSER.apply(parser, null); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/RevertModelSnapshotResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/RevertModelSnapshotResponseTests.java new file mode 100644 index 0000000000000..9d46c51566b7e --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/RevertModelSnapshotResponseTests.java @@ -0,0 +1,46 @@ +/* + * 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.client.ml.job.process.ModelSnapshot; +import org.elasticsearch.client.ml.job.process.ModelSnapshotTests; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + + +public class RevertModelSnapshotResponseTests extends AbstractXContentTestCase { + + @Override + protected RevertModelSnapshotResponse createTestInstance() { + ModelSnapshot.Builder modelBuilder = ModelSnapshotTests.createRandomizedBuilder(); + return new RevertModelSnapshotResponse(modelBuilder); + } + + @Override + protected RevertModelSnapshotResponse doParseInstance(XContentParser parser) throws IOException { + return RevertModelSnapshotResponse.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } +} diff --git a/docs/java-rest/high-level/ml/revert-model-snapshot.asciidoc b/docs/java-rest/high-level/ml/revert-model-snapshot.asciidoc new file mode 100644 index 0000000000000..7c45ce8ebf0a0 --- /dev/null +++ b/docs/java-rest/high-level/ml/revert-model-snapshot.asciidoc @@ -0,0 +1,46 @@ +-- +:api: revert-model-snapshot +:request: RevertModelSnapshotRequest +:response: RevertModelSnapshotResponse +-- +[id="{upid}-{api}"] +=== Revert Model Snapshot API + +The Revert Model Snapshot API provides the ability to revert to a previous {ml} model snapshot. +It accepts a +{request}+ object and responds +with a +{response}+ object. + +[id="{upid}-{api}-request"] +==== Revert Model Snapshot Request + +A +{request}+ requires the following arguments: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-request] +-------------------------------------------------- +<1> Constructing a new request referencing existing `jobId` and `snapshotId` values. + +==== Optional Arguments + +The following arguments are optional: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-delete-intervening-results] +-------------------------------------------------- +<1> A flag indicating whether or not results in the period between the timestamp on the reverted snapshot and the latest results should be deleted + + +include::../execution.asciidoc[] + +[id="{upid}-{api}-response"] +==== Revert Job Response + +A +{response}+ contains the full representation of the reverted `ModelSnapshot`. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-response] +-------------------------------------------------- +<1> The reverted `ModelSnapshot` diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index c7420ee9b6eba..c54981d97c8dd 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -272,10 +272,11 @@ The Java High Level REST Client supports the following Machine Learning APIs: * <<{upid}-delete-calendar>> * <<{upid}-put-filter>> * <<{upid}-get-filters>> -* <<{upid}-delete-model-snapshot>> * <<{upid}-update-filter>> * <<{upid}-delete-filter>> * <<{upid}-get-model-snapshots>> +* <<{upid}-delete-model-snapshot>> +* <<{upid}-revert-model-snapshot>> * <<{upid}-update-model-snapshot>> include::ml/put-job.asciidoc[] @@ -309,11 +310,12 @@ include::ml/put-calendar-job.asciidoc[] include::ml/delete-calendar-job.asciidoc[] include::ml/delete-calendar.asciidoc[] include::ml/put-filter.asciidoc[] -include::ml/get-model-snapshots.asciidoc[] include::ml/get-filters.asciidoc[] -include::ml/delete-model-snapshot.asciidoc[] include::ml/update-filter.asciidoc[] include::ml/delete-filter.asciidoc[] +include::ml/get-model-snapshots.asciidoc[] +include::ml/delete-model-snapshot.asciidoc[] +include::ml/revert-model-snapshot.asciidoc[] include::ml/update-model-snapshot.asciidoc[] == Migration APIs