Skip to content

Commit aec6f23

Browse files
committed
[HLRC][ML] Add ML update model snapshot API (#35537) (#35694)
Relates to #29827
1 parent 6ea10f1 commit aec6f23

12 files changed

+599
-1
lines changed

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

+16
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.elasticsearch.client.ml.UpdateDatafeedRequest;
6262
import org.elasticsearch.client.ml.UpdateFilterRequest;
6363
import org.elasticsearch.client.ml.UpdateJobRequest;
64+
import org.elasticsearch.client.ml.UpdateModelSnapshotRequest;
6465
import org.elasticsearch.client.ml.job.util.PageParams;
6566
import org.elasticsearch.common.Strings;
6667
import org.elasticsearch.common.bytes.BytesReference;
@@ -391,6 +392,21 @@ static Request getModelSnapshots(GetModelSnapshotsRequest getModelSnapshotsReque
391392
return request;
392393
}
393394

395+
static Request updateModelSnapshot(UpdateModelSnapshotRequest updateModelSnapshotRequest) throws IOException {
396+
String endpoint = new EndpointBuilder()
397+
.addPathPartAsIs("_xpack")
398+
.addPathPartAsIs("ml")
399+
.addPathPartAsIs("anomaly_detectors")
400+
.addPathPart(updateModelSnapshotRequest.getJobId())
401+
.addPathPartAsIs("model_snapshots")
402+
.addPathPart(updateModelSnapshotRequest.getSnapshotId())
403+
.addPathPartAsIs("_update")
404+
.build();
405+
Request request = new Request(HttpPost.METHOD_NAME, endpoint);
406+
request.setEntity(createEntity(updateModelSnapshotRequest, REQUEST_BODY_CONTENT_TYPE));
407+
return request;
408+
}
409+
394410
static Request getOverallBuckets(GetOverallBucketsRequest getOverallBucketsRequest) throws IOException {
395411
String endpoint = new EndpointBuilder()
396412
.addPathPartAsIs("_xpack")

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

+43
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
import org.elasticsearch.client.ml.UpdateDatafeedRequest;
8080
import org.elasticsearch.client.ml.UpdateFilterRequest;
8181
import org.elasticsearch.client.ml.UpdateJobRequest;
82+
import org.elasticsearch.client.ml.UpdateModelSnapshotRequest;
83+
import org.elasticsearch.client.ml.UpdateModelSnapshotResponse;
8284
import org.elasticsearch.client.ml.job.stats.JobStats;
8385

8486
import java.io.IOException;
@@ -984,6 +986,47 @@ public void getModelSnapshotsAsync(GetModelSnapshotsRequest request, RequestOpti
984986
Collections.emptySet());
985987
}
986988

989+
/**
990+
* Updates a snapshot for a Machine Learning Job.
991+
* <p>
992+
* For additional info
993+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-snapshot.html">
994+
* ML UPDATE model snapshots documentation</a>
995+
*
996+
* @param request The request
997+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
998+
* @throws IOException when there is a serialization issue sending the request or receiving the response
999+
*/
1000+
public UpdateModelSnapshotResponse updateModelSnapshot(UpdateModelSnapshotRequest request,
1001+
RequestOptions options) throws IOException {
1002+
return restHighLevelClient.performRequestAndParseEntity(request,
1003+
MLRequestConverters::updateModelSnapshot,
1004+
options,
1005+
UpdateModelSnapshotResponse::fromXContent,
1006+
Collections.emptySet());
1007+
}
1008+
1009+
/**
1010+
* Updates a snapshot for a Machine Learning Job, notifies listener once the requested snapshots are retrieved.
1011+
* <p>
1012+
* For additional info
1013+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-snapshot.html">
1014+
* ML UPDATE model snapshots documentation</a>
1015+
*
1016+
* @param request The request
1017+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
1018+
* @param listener Listener to be notified upon request completion
1019+
*/
1020+
public void updateModelSnapshotAsync(UpdateModelSnapshotRequest request, RequestOptions options,
1021+
ActionListener<UpdateModelSnapshotResponse> listener) {
1022+
restHighLevelClient.performRequestAsyncAndParseEntity(request,
1023+
MLRequestConverters::updateModelSnapshot,
1024+
options,
1025+
UpdateModelSnapshotResponse::fromXContent,
1026+
listener,
1027+
Collections.emptySet());
1028+
}
1029+
9871030
/**
9881031
* Gets overall buckets for a set of Machine Learning Jobs.
9891032
* <p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
26+
import org.elasticsearch.common.xcontent.ToXContentObject;
27+
import org.elasticsearch.common.xcontent.XContentBuilder;
28+
29+
import java.io.IOException;
30+
import java.util.Objects;
31+
32+
/**
33+
* A request to update information about an existing model snapshot for a given job
34+
*/
35+
public class UpdateModelSnapshotRequest extends ActionRequest implements ToXContentObject {
36+
37+
38+
public static final ConstructingObjectParser<UpdateModelSnapshotRequest, Void> PARSER = new ConstructingObjectParser<>(
39+
"update_model_snapshot_request", a -> new UpdateModelSnapshotRequest((String) a[0], (String) a[1]));
40+
41+
42+
static {
43+
PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID);
44+
PARSER.declareString(ConstructingObjectParser.constructorArg(), ModelSnapshot.SNAPSHOT_ID);
45+
PARSER.declareStringOrNull(UpdateModelSnapshotRequest::setDescription, ModelSnapshot.DESCRIPTION);
46+
PARSER.declareBoolean(UpdateModelSnapshotRequest::setRetain, ModelSnapshot.RETAIN);
47+
}
48+
49+
private final String jobId;
50+
private String snapshotId;
51+
private String description;
52+
private Boolean retain;
53+
54+
/**
55+
* Constructs a request to update information for a snapshot of given job
56+
* @param jobId id of the job from which to retrieve results
57+
* @param snapshotId id of the snapshot from which to retrieve results
58+
*/
59+
public UpdateModelSnapshotRequest(String jobId, String snapshotId) {
60+
this.jobId = Objects.requireNonNull(jobId, "[" + Job.ID + "] must not be null");
61+
this.snapshotId = Objects.requireNonNull(snapshotId, "[" + ModelSnapshot.SNAPSHOT_ID + "] must not be null");
62+
}
63+
64+
public String getJobId() {
65+
return jobId;
66+
}
67+
68+
public String getSnapshotId() {
69+
return snapshotId;
70+
}
71+
72+
public String getDescription() {
73+
return description;
74+
}
75+
76+
/**
77+
* The new description of the snapshot.
78+
* @param description the updated snapshot description
79+
*/
80+
public void setDescription(String description) {
81+
this.description = description;
82+
}
83+
84+
public Boolean getRetain() {
85+
return retain;
86+
}
87+
88+
/**
89+
* The new value of the "retain" property of the snapshot
90+
* @param retain the updated retain property
91+
*/
92+
public void setRetain(boolean retain) {
93+
this.retain = retain;
94+
}
95+
96+
@Override
97+
public ActionRequestValidationException validate() {
98+
return null;
99+
}
100+
101+
@Override
102+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
103+
builder.startObject();
104+
builder.field(Job.ID.getPreferredName(), jobId);
105+
builder.field(ModelSnapshot.SNAPSHOT_ID.getPreferredName(), snapshotId);
106+
if (description != null) {
107+
builder.field(ModelSnapshot.DESCRIPTION.getPreferredName(), description);
108+
}
109+
if (retain != null) {
110+
builder.field(ModelSnapshot.RETAIN.getPreferredName(), retain);
111+
}
112+
builder.endObject();
113+
return builder;
114+
}
115+
116+
@Override
117+
public boolean equals(Object obj) {
118+
if (obj == null) {
119+
return false;
120+
}
121+
if (getClass() != obj.getClass()) {
122+
return false;
123+
}
124+
UpdateModelSnapshotRequest request = (UpdateModelSnapshotRequest) obj;
125+
return Objects.equals(jobId, request.jobId)
126+
&& Objects.equals(snapshotId, request.snapshotId)
127+
&& Objects.equals(description, request.description)
128+
&& Objects.equals(retain, request.retain);
129+
}
130+
131+
@Override
132+
public int hashCode() {
133+
return Objects.hash(jobId, snapshotId, description, retain);
134+
}
135+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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.ActionResponse;
22+
import org.elasticsearch.client.ml.job.process.ModelSnapshot;
23+
import org.elasticsearch.common.ParseField;
24+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
25+
import org.elasticsearch.common.xcontent.ToXContent;
26+
import org.elasticsearch.common.xcontent.ToXContentObject;
27+
import org.elasticsearch.common.xcontent.XContentBuilder;
28+
import org.elasticsearch.common.xcontent.XContentParser;
29+
30+
import java.io.IOException;
31+
import java.util.Objects;
32+
33+
/**
34+
* A response acknowledging the update of information for an existing model snapshot for a given job
35+
*/
36+
public class UpdateModelSnapshotResponse extends ActionResponse implements ToXContentObject {
37+
38+
private static final ParseField ACKNOWLEDGED = new ParseField("acknowledged");
39+
private static final ParseField MODEL = new ParseField("model");
40+
41+
public UpdateModelSnapshotResponse(boolean acknowledged, ModelSnapshot.Builder modelSnapshot) {
42+
this.acknowledged = acknowledged;
43+
this.model = modelSnapshot.build();
44+
}
45+
46+
public static final ConstructingObjectParser<UpdateModelSnapshotResponse, Void> PARSER =
47+
new ConstructingObjectParser<>("update_model_snapshot_response", true,
48+
a -> new UpdateModelSnapshotResponse((Boolean) a[0], ((ModelSnapshot.Builder) a[1])));
49+
50+
static {
51+
PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), ACKNOWLEDGED);
52+
PARSER.declareObject(ConstructingObjectParser.constructorArg(), ModelSnapshot.PARSER, MODEL);
53+
}
54+
55+
public static UpdateModelSnapshotResponse fromXContent(XContentParser parser) throws IOException {
56+
return PARSER.parse(parser, null);
57+
}
58+
59+
private final Boolean acknowledged;
60+
private final ModelSnapshot model;
61+
62+
/**
63+
* Get the action acknowledgement
64+
* @return a {@code boolean} that indicates whether the model snapshot was updated successfully.
65+
*/
66+
public Boolean getAcknowledged() {
67+
return acknowledged;
68+
}
69+
70+
/**
71+
* Get the updated snapshot of the model
72+
* @return the updated model snapshot.
73+
*/
74+
public ModelSnapshot getModel() {
75+
return model;
76+
}
77+
78+
@Override
79+
public int hashCode() {
80+
return Objects.hash(acknowledged, model);
81+
}
82+
83+
@Override
84+
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
85+
builder.startObject();
86+
if (acknowledged != null) {
87+
builder.field(ACKNOWLEDGED.getPreferredName(), acknowledged);
88+
}
89+
if (model != null) {
90+
builder.field(MODEL.getPreferredName(), model);
91+
}
92+
builder.endObject();
93+
return builder;
94+
}
95+
96+
97+
@Override
98+
public boolean equals(Object obj) {
99+
if (obj == null) {
100+
return false;
101+
}
102+
if (getClass() != obj.getClass()) {
103+
return false;
104+
}
105+
UpdateModelSnapshotResponse request = (UpdateModelSnapshotResponse) obj;
106+
return Objects.equals(acknowledged, request.acknowledged)
107+
&& Objects.equals(model, request.model);
108+
}
109+
}

client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/process/ModelSnapshot.java

+4
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ public Quantiles getQuantiles() {
161161
return quantiles;
162162
}
163163

164+
public boolean getRetain() {
165+
return retain;
166+
}
167+
164168
public Date getLatestRecordTimeStamp() {
165169
return latestRecordTimeStamp;
166170
}

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

+17
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.elasticsearch.client.ml.StopDatafeedRequest;
5858
import org.elasticsearch.client.ml.UpdateFilterRequest;
5959
import org.elasticsearch.client.ml.UpdateJobRequest;
60+
import org.elasticsearch.client.ml.UpdateModelSnapshotRequest;
6061
import org.elasticsearch.client.ml.calendars.Calendar;
6162
import org.elasticsearch.client.ml.calendars.CalendarTests;
6263
import org.elasticsearch.client.ml.datafeed.DatafeedConfig;
@@ -422,6 +423,22 @@ public void testGetModelSnapshots() throws IOException {
422423
}
423424
}
424425

426+
public void testUpdateModelSnapshot() throws IOException {
427+
String jobId = randomAlphaOfLength(10);
428+
String snapshotId = randomAlphaOfLength(10);
429+
UpdateModelSnapshotRequest updateModelSnapshotRequest = new UpdateModelSnapshotRequest(jobId, snapshotId);
430+
updateModelSnapshotRequest.setDescription("My First Snapshot");
431+
updateModelSnapshotRequest.setRetain(true);
432+
433+
Request request = MLRequestConverters.updateModelSnapshot(updateModelSnapshotRequest);
434+
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
435+
assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + "/model_snapshots/" + snapshotId + "/_update", request.getEndpoint());
436+
try (XContentParser parser = createParser(JsonXContent.jsonXContent, request.getEntity().getContent())) {
437+
UpdateModelSnapshotRequest parsedRequest = UpdateModelSnapshotRequest.PARSER.apply(parser, null);
438+
assertThat(parsedRequest, equalTo(updateModelSnapshotRequest));
439+
}
440+
}
441+
425442
public void testGetOverallBuckets() throws IOException {
426443
String jobId = randomAlphaOfLength(10);
427444
GetOverallBucketsRequest getOverallBucketsRequest = new GetOverallBucketsRequest(jobId);

0 commit comments

Comments
 (0)