Browse Source

[HLRC][ML] Add ML revert model snapshot API (#35750)

Relates to #29827
Ed Savage 6 years ago
parent
commit
4f857c4f8d

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

@@ -59,6 +59,7 @@ import org.elasticsearch.client.ml.PutCalendarRequest;
 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;
@@ -410,6 +411,21 @@ final class MLRequestConverters {
         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")

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

@@ -77,6 +77,8 @@ import org.elasticsearch.client.ml.PutFilterRequest;
 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;
@@ -515,6 +517,47 @@ public final class MachineLearningClient {
             Collections.emptySet());
     }
 
+    /**
+     * Reverts to a particular Machine Learning Model Snapshot
+     * <p>
+     * For additional info
+     * see <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-revert-snapshot.html">
+     *     ML Revert Model Snapshot documentation</a>
+     *
+     * @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
+     * <p>
+     * For additional info
+     * see <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-revert-snapshot.html">
+     *         ML Revert Model Snapshot documentation</a>
+     *
+     * @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<RevertModelSnapshotResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request,
+            MLRequestConverters::revertModelSnapshot,
+            options,
+            RevertModelSnapshotResponse::fromXContent,
+            listener,
+            Collections.emptySet());
+    }
+
     /**
      * Creates a new Machine Learning Datafeed
      * <p>

+ 120 - 0
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<RevertModelSnapshotRequest, Void> 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);
+    }
+}

+ 92 - 0
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<RevertModelSnapshotResponse, Void> 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;
+    }
+}

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

@@ -55,6 +55,7 @@ import org.elasticsearch.client.ml.PutCalendarRequest;
 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;
@@ -446,6 +447,24 @@ public class MLRequestConvertersTests extends ESTestCase {
         }
     }
 
+    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);

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

@@ -74,6 +74,8 @@ import org.elasticsearch.client.ml.PutFilterRequest;
 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;
@@ -98,6 +100,7 @@ import org.elasticsearch.client.ml.job.config.Job;
 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.client.ml.job.util.PageParams;
 import org.elasticsearch.common.unit.TimeValue;
@@ -1159,6 +1162,28 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase {
         highLevelClient().index(indexRequest, RequestOptions.DEFAULT);
     }
 
+    public void createModelSnapshots(String jobId, List<String> 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";
@@ -1210,4 +1235,36 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase {
         assertEquals("Updated description",
             getModelSnapshotsResponse2.snapshots().get(0).getDescription());
     }
+
+    public void testRevertModelSnapshot() throws IOException {
+        String jobId = "test-revert-model-snapshot";
+
+        List<String> 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());
+        }
+    }
 }

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

@@ -90,6 +90,8 @@ import org.elasticsearch.client.ml.PutFilterRequest;
 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;
@@ -2051,6 +2053,82 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    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<RevertModelSnapshotResponse> listener =
+                new ActionListener<RevertModelSnapshotResponse>() {
+                    @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();
 

+ 50 - 0
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<RevertModelSnapshotRequest> {
+
+    @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;
+    }
+}

+ 46 - 0
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<RevertModelSnapshotResponse> {
+
+    @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;
+    }
+}

+ 46 - 0
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`

+ 5 - 3
docs/java-rest/high-level/supported-apis.asciidoc

@@ -273,10 +273,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[]
@@ -311,11 +312,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