Skip to content

Commit 2d948a0

Browse files
authored
[HLRC][ML] Add ML delete model snapshot API (#35537)
Relates to #29827
1 parent 68d9b56 commit 2d948a0

File tree

9 files changed

+315
-0
lines changed

9 files changed

+315
-0
lines changed

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

+13
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.elasticsearch.client.ml.DeleteDatafeedRequest;
3333
import org.elasticsearch.client.ml.DeleteForecastRequest;
3434
import org.elasticsearch.client.ml.DeleteJobRequest;
35+
import org.elasticsearch.client.ml.DeleteModelSnapshotRequest;
3536
import org.elasticsearch.client.ml.FlushJobRequest;
3637
import org.elasticsearch.client.ml.ForecastJobRequest;
3738
import org.elasticsearch.client.ml.GetBucketsRequest;
@@ -335,6 +336,18 @@ static Request deleteForecast(DeleteForecastRequest deleteForecastRequest) {
335336
return request;
336337
}
337338

339+
static Request deleteModelSnapshot(DeleteModelSnapshotRequest deleteModelSnapshotRequest) {
340+
String endpoint = new EndpointBuilder()
341+
.addPathPartAsIs("_xpack")
342+
.addPathPartAsIs("ml")
343+
.addPathPartAsIs("anomaly_detectors")
344+
.addPathPart(deleteModelSnapshotRequest.getJobId())
345+
.addPathPartAsIs("model_snapshots")
346+
.addPathPart(deleteModelSnapshotRequest.getSnapshotId())
347+
.build();
348+
return new Request(HttpDelete.METHOD_NAME, endpoint);
349+
}
350+
338351
static Request getBuckets(GetBucketsRequest getBucketsRequest) throws IOException {
339352
String endpoint = new EndpointBuilder()
340353
.addPathPartAsIs("_xpack")

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

+42
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.client.ml.DeleteForecastRequest;
2828
import org.elasticsearch.client.ml.DeleteJobRequest;
2929
import org.elasticsearch.client.ml.DeleteJobResponse;
30+
import org.elasticsearch.client.ml.DeleteModelSnapshotRequest;
3031
import org.elasticsearch.client.ml.FlushJobRequest;
3132
import org.elasticsearch.client.ml.FlushJobResponse;
3233
import org.elasticsearch.client.ml.ForecastJobRequest;
@@ -464,6 +465,47 @@ public void deleteForecastAsync(DeleteForecastRequest request, RequestOptions op
464465
Collections.emptySet());
465466
}
466467

468+
/**
469+
* Deletes Machine Learning Model Snapshots
470+
* <p>
471+
* For additional info
472+
* see <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-snapshot.html">
473+
* ML Delete Model Snapshot documentation</a>
474+
*
475+
* @param request The request to delete the model snapshot
476+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
477+
* @return action acknowledgement
478+
* @throws IOException when there is a serialization issue sending the request or receiving the response
479+
*/
480+
public AcknowledgedResponse deleteModelSnapshot(DeleteModelSnapshotRequest request, RequestOptions options) throws IOException {
481+
return restHighLevelClient.performRequestAndParseEntity(request,
482+
MLRequestConverters::deleteModelSnapshot,
483+
options,
484+
AcknowledgedResponse::fromXContent,
485+
Collections.emptySet());
486+
}
487+
488+
/**
489+
* Deletes Machine Learning Model Snapshots asynchronously and notifies the listener on completion
490+
* <p>
491+
* For additional info
492+
* see <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-snapshot.html">
493+
* ML Delete Model Snapshot documentation</a>
494+
*
495+
* @param request The request to delete the model snapshot
496+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
497+
* @param listener Listener to be notified upon request completion
498+
*/
499+
public void deleteModelSnapshotAsync(DeleteModelSnapshotRequest request, RequestOptions options,
500+
ActionListener<AcknowledgedResponse> listener) {
501+
restHighLevelClient.performRequestAsyncAndParseEntity(request,
502+
MLRequestConverters::deleteModelSnapshot,
503+
options,
504+
AcknowledgedResponse::fromXContent,
505+
listener,
506+
Collections.emptySet());
507+
}
508+
467509
/**
468510
* Creates a new Machine Learning Datafeed
469511
* <p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client.ml;
20+
21+
import org.elasticsearch.action.ActionRequest;
22+
import org.elasticsearch.action.ActionRequestValidationException;
23+
import org.elasticsearch.client.ml.job.config.Job;
24+
import org.elasticsearch.client.ml.job.process.ModelSnapshot;
25+
26+
27+
import java.util.Objects;
28+
29+
/**
30+
* Request to delete a Machine Learning Model Snapshot Job via its Job and Snapshot IDs
31+
*/
32+
public class DeleteModelSnapshotRequest extends ActionRequest {
33+
34+
private final String jobId;
35+
private final String snapshotId;
36+
37+
public DeleteModelSnapshotRequest(String jobId, String snapshotId) {
38+
this.jobId = Objects.requireNonNull(jobId, "[" + Job.ID + "] must not be null");
39+
this.snapshotId = Objects.requireNonNull(snapshotId, "[" + ModelSnapshot.SNAPSHOT_ID + "] must not be null");
40+
}
41+
42+
public String getJobId() {
43+
return jobId;
44+
}
45+
46+
public String getSnapshotId() {
47+
return snapshotId;
48+
}
49+
50+
@Override
51+
public ActionRequestValidationException validate() {
52+
return null;
53+
}
54+
55+
@Override
56+
public int hashCode() {
57+
return Objects.hash(jobId, snapshotId);
58+
}
59+
60+
@Override
61+
public boolean equals(Object obj) {
62+
if (this == obj) {
63+
return true;
64+
}
65+
66+
if (obj == null || obj.getClass() != getClass()) {
67+
return false;
68+
}
69+
70+
DeleteModelSnapshotRequest other = (DeleteModelSnapshotRequest) obj;
71+
return Objects.equals(jobId, other.jobId) && Objects.equals(snapshotId, other.snapshotId);
72+
}
73+
74+
}

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

+11
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.client.ml.DeleteDatafeedRequest;
2929
import org.elasticsearch.client.ml.DeleteForecastRequest;
3030
import org.elasticsearch.client.ml.DeleteJobRequest;
31+
import org.elasticsearch.client.ml.DeleteModelSnapshotRequest;
3132
import org.elasticsearch.client.ml.FlushJobRequest;
3233
import org.elasticsearch.client.ml.ForecastJobRequest;
3334
import org.elasticsearch.client.ml.GetBucketsRequest;
@@ -362,6 +363,16 @@ public void testDeleteForecast() {
362363
request.getParameters().get(DeleteForecastRequest.ALLOW_NO_FORECASTS.getPreferredName()));
363364
}
364365

366+
public void testDeleteModelSnapshot() {
367+
String jobId = randomAlphaOfLength(10);
368+
String snapshotId = randomAlphaOfLength(10);
369+
DeleteModelSnapshotRequest deleteModelSnapshotRequest = new DeleteModelSnapshotRequest(jobId, snapshotId);
370+
371+
Request request = MLRequestConverters.deleteModelSnapshot(deleteModelSnapshotRequest);
372+
assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
373+
assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + "/model_snapshots/" + snapshotId, request.getEndpoint());
374+
}
375+
365376
public void testGetBuckets() throws IOException {
366377
String jobId = randomAlphaOfLength(10);
367378
GetBucketsRequest getBucketsRequest = new GetBucketsRequest(jobId);

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

+35
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.elasticsearch.client.ml.DeleteForecastRequest;
3535
import org.elasticsearch.client.ml.DeleteJobRequest;
3636
import org.elasticsearch.client.ml.DeleteJobResponse;
37+
import org.elasticsearch.client.ml.DeleteModelSnapshotRequest;
3738
import org.elasticsearch.client.ml.FlushJobRequest;
3839
import org.elasticsearch.client.ml.FlushJobResponse;
3940
import org.elasticsearch.client.ml.ForecastJobRequest;
@@ -996,4 +997,38 @@ private String createAndPutDatafeed(String jobId, String indexName) throws IOExc
996997
highLevelClient().machineLearning().putDatafeed(new PutDatafeedRequest(datafeed), RequestOptions.DEFAULT);
997998
return datafeedId;
998999
}
1000+
1001+
public void createModelSnapshot(String jobId, String snapshotId) throws IOException {
1002+
Job job = MachineLearningIT.buildJob(jobId);
1003+
highLevelClient().machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
1004+
1005+
IndexRequest indexRequest = new IndexRequest(".ml-anomalies-shared", "doc");
1006+
indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
1007+
indexRequest.source("{\"job_id\":\"" + jobId + "\", \"timestamp\":1541587919000, " +
1008+
"\"description\":\"State persisted due to job close at 2018-11-07T10:51:59+0000\", " +
1009+
"\"snapshot_id\":\"" + snapshotId + "\", \"snapshot_doc_count\":1, \"model_size_stats\":{" +
1010+
"\"job_id\":\"" + jobId + "\", \"result_type\":\"model_size_stats\",\"model_bytes\":51722, " +
1011+
"\"total_by_field_count\":3, \"total_over_field_count\":0, \"total_partition_field_count\":2," +
1012+
"\"bucket_allocation_failures_count\":0, \"memory_status\":\"ok\", \"log_time\":1541587919000, " +
1013+
"\"timestamp\":1519930800000}, \"latest_record_time_stamp\":1519931700000," +
1014+
"\"latest_result_time_stamp\":1519930800000, \"retain\":false}", XContentType.JSON);
1015+
1016+
highLevelClient().index(indexRequest, RequestOptions.DEFAULT);
1017+
}
1018+
1019+
public void testDeleteModelSnapshot() throws IOException {
1020+
String jobId = "test-delete-model-snapshot";
1021+
String snapshotId = "1541587919";
1022+
1023+
createModelSnapshot(jobId, snapshotId);
1024+
1025+
MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
1026+
1027+
DeleteModelSnapshotRequest request = new DeleteModelSnapshotRequest(jobId, snapshotId);
1028+
1029+
AcknowledgedResponse response = execute(request, machineLearningClient::deleteModelSnapshot,
1030+
machineLearningClient::deleteModelSnapshotAsync);
1031+
1032+
assertTrue(response.isAcknowledged());
1033+
}
9991034
}

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

+68
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.client.ml.DeleteForecastRequest;
4141
import org.elasticsearch.client.ml.DeleteJobRequest;
4242
import org.elasticsearch.client.ml.DeleteJobResponse;
43+
import org.elasticsearch.client.ml.DeleteModelSnapshotRequest;
4344
import org.elasticsearch.client.ml.FlushJobRequest;
4445
import org.elasticsearch.client.ml.FlushJobResponse;
4546
import org.elasticsearch.client.ml.ForecastJobRequest;
@@ -1867,6 +1868,73 @@ public void onFailure(Exception e) {
18671868
}
18681869
}
18691870

1871+
public void testDeleteModelSnapshot() throws IOException, InterruptedException {
1872+
RestHighLevelClient client = highLevelClient();
1873+
1874+
String jobId = "test-delete-model-snapshot";
1875+
String snapshotId = "1541587919";
1876+
Job job = MachineLearningIT.buildJob(jobId);
1877+
client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
1878+
1879+
// Let us index a snapshot
1880+
IndexRequest indexRequest = new IndexRequest(".ml-anomalies-shared", "doc");
1881+
indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
1882+
indexRequest.source("{\"job_id\":\"" + jobId + "\", \"timestamp\":1541587919000, " +
1883+
"\"description\":\"State persisted due to job close at 2018-11-07T10:51:59+0000\", " +
1884+
"\"snapshot_id\":\"" + snapshotId + "\", \"snapshot_doc_count\":1, \"model_size_stats\":{" +
1885+
"\"job_id\":\"" + jobId + "\", \"result_type\":\"model_size_stats\",\"model_bytes\":51722, " +
1886+
"\"total_by_field_count\":3, \"total_over_field_count\":0, \"total_partition_field_count\":2," +
1887+
"\"bucket_allocation_failures_count\":0, \"memory_status\":\"ok\", \"log_time\":1541587919000, " +
1888+
"\"timestamp\":1519930800000}, \"latest_record_time_stamp\":1519931700000," +
1889+
"\"latest_result_time_stamp\":1519930800000, \"retain\":false}", XContentType.JSON);
1890+
{
1891+
client.index(indexRequest, RequestOptions.DEFAULT);
1892+
1893+
// tag::delete-model-snapshot-request
1894+
DeleteModelSnapshotRequest request = new DeleteModelSnapshotRequest(jobId, snapshotId); // <1>
1895+
// end::delete-model-snapshot-request
1896+
1897+
// tag::delete-model-snapshot-execute
1898+
AcknowledgedResponse response = client.machineLearning().deleteModelSnapshot(request, RequestOptions.DEFAULT);
1899+
// end::delete-model-snapshot-execute
1900+
1901+
// tag::delete-model-snapshot-response
1902+
boolean isAcknowledged = response.isAcknowledged(); // <1>
1903+
// end::delete-model-snapshot-response
1904+
1905+
assertTrue(isAcknowledged);
1906+
}
1907+
{
1908+
client.index(indexRequest, RequestOptions.DEFAULT);
1909+
1910+
// tag::delete-model-snapshot-execute-listener
1911+
ActionListener<AcknowledgedResponse> listener = new ActionListener<AcknowledgedResponse>() {
1912+
@Override
1913+
public void onResponse(AcknowledgedResponse acknowledgedResponse) {
1914+
// <1>
1915+
}
1916+
1917+
@Override
1918+
public void onFailure(Exception e) {
1919+
// <2>
1920+
}
1921+
};
1922+
// end::delete-model-snapshot-execute-listener
1923+
1924+
// Replace the empty listener by a blocking listener in test
1925+
final CountDownLatch latch = new CountDownLatch(1);
1926+
listener = new LatchedActionListener<>(listener, latch);
1927+
1928+
DeleteModelSnapshotRequest deleteModelSnapshotRequest = new DeleteModelSnapshotRequest(jobId, "1541587919");
1929+
1930+
// tag::delete-model-snapshot-execute-async
1931+
client.machineLearning().deleteModelSnapshotAsync(deleteModelSnapshotRequest, RequestOptions.DEFAULT, listener); // <1>
1932+
// end::delete-model-snapshot-execute-async
1933+
1934+
assertTrue(latch.await(30L, TimeUnit.SECONDS));
1935+
}
1936+
}
1937+
18701938
public void testGetModelSnapshots() throws IOException, InterruptedException {
18711939
RestHighLevelClient client = highLevelClient();
18721940

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client.ml;
20+
21+
import org.elasticsearch.test.ESTestCase;
22+
23+
public class DeleteModelSnapshotRequestTests extends ESTestCase {
24+
25+
public void test_WithNullJobId() {
26+
NullPointerException ex = expectThrows(NullPointerException.class, () ->
27+
new DeleteModelSnapshotRequest(null, randomAlphaOfLength(10)));
28+
assertEquals("[job_id] must not be null", ex.getMessage());
29+
}
30+
31+
public void test_WithNullSnapshotId() {
32+
NullPointerException ex = expectThrows(NullPointerException.class, ()
33+
-> new DeleteModelSnapshotRequest(randomAlphaOfLength(10), null));
34+
assertEquals("[snapshot_id] must not be null", ex.getMessage());
35+
}
36+
37+
private DeleteModelSnapshotRequest createTestInstance() {
38+
return new DeleteModelSnapshotRequest(randomAlphaOfLength(10), randomAlphaOfLength(10));
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--
2+
:api: delete-model-snapshot
3+
:request: DeleteModelSnapshotRequest
4+
:response: AcknowledgedResponse
5+
--
6+
[id="{upid}-{api}"]
7+
=== Delete Model Snapshot API
8+
9+
[id="{upid}-{api}-request"]
10+
==== Delete Model Snapshot Request
11+
12+
A +{request}+ object requires both a non-null `jobId` and a non-null `snapshotId`.
13+
14+
["source","java",subs="attributes,callouts,macros"]
15+
---------------------------------------------------
16+
include-tagged::{doc-tests-file}[{api}-request]
17+
---------------------------------------------------
18+
<1> Constructing a new request referencing existing `jobId` and `snapshotId`.
19+
20+
include::../execution.asciidoc[]
21+
22+
[id="{upid}-{api}-response"]
23+
==== Delete Model Snapshot Response
24+
25+
The returned +{response}+ object indicates the acknowledgement of the request:
26+
["source","java",subs="attributes,callouts,macros"]
27+
---------------------------------------------------
28+
include-tagged::{doc-tests-file}[{api}-response]
29+
---------------------------------------------------
30+
<1> `isAcknowledged` was the deletion request acknowledged or not

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

+2
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ The Java High Level REST Client supports the following Machine Learning APIs:
267267
* <<{upid}-put-filter>>
268268
* <<{upid}-get-model-snapshots>>
269269
* <<{upid}-get-filters>>
270+
* <<{upid}-delete-model-snapshot>>
270271
* <<{upid}-update-filter>>
271272

272273
include::ml/put-job.asciidoc[]
@@ -299,6 +300,7 @@ include::ml/delete-calendar.asciidoc[]
299300
include::ml/put-filter.asciidoc[]
300301
include::ml/get-model-snapshots.asciidoc[]
301302
include::ml/get-filters.asciidoc[]
303+
include::ml/delete-model-snapshot.asciidoc[]
302304
include::ml/update-filter.asciidoc[]
303305

304306
== Migration APIs

0 commit comments

Comments
 (0)