HLRC: ML Update Job (#33392)

* HLRC: ML Update Job
This commit is contained in:
Benjamin Trent 2018-09-06 07:18:09 -05:00 committed by GitHub
parent ef207edbf0
commit 9b6bbc0182
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 974 additions and 0 deletions

View File

@ -35,6 +35,7 @@ import org.elasticsearch.client.ml.GetOverallBucketsRequest;
import org.elasticsearch.client.ml.GetRecordsRequest;
import org.elasticsearch.client.ml.OpenJobRequest;
import org.elasticsearch.client.ml.PutJobRequest;
import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.common.Strings;
import java.io.IOException;
@ -146,6 +147,19 @@ final class MLRequestConverters {
return request;
}
static Request updateJob(UpdateJobRequest updateJobRequest) throws IOException {
String endpoint = new EndpointBuilder()
.addPathPartAsIs("_xpack")
.addPathPartAsIs("ml")
.addPathPartAsIs("anomaly_detectors")
.addPathPart(updateJobRequest.getJobUpdate().getJobId())
.addPathPartAsIs("_update")
.build();
Request request = new Request(HttpPost.METHOD_NAME, endpoint);
request.setEntity(createEntity(updateJobRequest.getJobUpdate(), REQUEST_BODY_CONTENT_TYPE));
return request;
}
static Request getBuckets(GetBucketsRequest getBucketsRequest) throws IOException {
String endpoint = new EndpointBuilder()
.addPathPartAsIs("_xpack")

View File

@ -19,6 +19,7 @@
package org.elasticsearch.client;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.CloseJobRequest;
import org.elasticsearch.client.ml.CloseJobResponse;
import org.elasticsearch.client.ml.DeleteJobRequest;
@ -319,6 +320,7 @@ public final class MachineLearningClient {
*
* @param request The {@link FlushJobRequest} object enclosing the `jobId` and additional request options
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @throws IOException when there is a serialization issue sending the request or receiving the response
*/
public FlushJobResponse flushJob(FlushJobRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request,
@ -356,6 +358,38 @@ public final class MachineLearningClient {
Collections.emptySet());
}
/**
* Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job}
*
* @param request the {@link UpdateJobRequest} object enclosing the desired updates
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return a PutJobResponse object containing the updated job object
* @throws IOException when there is a serialization issue sending the request or receiving the response
*/
public PutJobResponse updateJob(UpdateJobRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request,
MLRequestConverters::updateJob,
options,
PutJobResponse::fromXContent,
Collections.emptySet());
}
/**
* Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job} asynchronously
*
* @param request the {@link UpdateJobRequest} object enclosing the desired updates
* @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 updateJobAsync(UpdateJobRequest request, RequestOptions options, ActionListener<PutJobResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(request,
MLRequestConverters::updateJob,
options,
PutJobResponse::fromXContent,
listener,
Collections.emptySet());
}
/**
* Gets the buckets for a Machine Learning Job.
* <p>

View File

@ -0,0 +1,80 @@
/*
* 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.JobUpdate;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Objects;
/**
* Updates a {@link org.elasticsearch.client.ml.job.config.Job} with the passed {@link JobUpdate}
* settings
*/
public class UpdateJobRequest extends ActionRequest implements ToXContentObject {
private final JobUpdate update;
public UpdateJobRequest(JobUpdate update) {
this.update = update;
}
public JobUpdate getJobUpdate() {
return update;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return update.toXContent(builder, params);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UpdateJobRequest that = (UpdateJobRequest) o;
return Objects.equals(update, that.update);
}
@Override
public int hashCode() {
return Objects.hash(update);
}
@Override
public final String toString() {
return Strings.toString(this);
}
@Override
public ActionRequestValidationException validate() {
return null;
}
}

View File

@ -0,0 +1,454 @@
/*
* 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.job.config;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* POJO for updating an existing Machine Learning {@link Job}
*/
public class JobUpdate implements ToXContentObject {
public static final ParseField DETECTORS = new ParseField("detectors");
public static final ConstructingObjectParser<Builder, Void> PARSER = new ConstructingObjectParser<>(
"job_update", true, args -> new Builder((String) args[0]));
static {
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), Job.ID);
PARSER.declareStringArray(Builder::setGroups, Job.GROUPS);
PARSER.declareStringOrNull(Builder::setDescription, Job.DESCRIPTION);
PARSER.declareObjectArray(Builder::setDetectorUpdates, DetectorUpdate.PARSER, DETECTORS);
PARSER.declareObject(Builder::setModelPlotConfig, ModelPlotConfig.PARSER, Job.MODEL_PLOT_CONFIG);
PARSER.declareObject(Builder::setAnalysisLimits, AnalysisLimits.PARSER, Job.ANALYSIS_LIMITS);
PARSER.declareString((builder, val) -> builder.setBackgroundPersistInterval(
TimeValue.parseTimeValue(val, Job.BACKGROUND_PERSIST_INTERVAL.getPreferredName())), Job.BACKGROUND_PERSIST_INTERVAL);
PARSER.declareLong(Builder::setRenormalizationWindowDays, Job.RENORMALIZATION_WINDOW_DAYS);
PARSER.declareLong(Builder::setResultsRetentionDays, Job.RESULTS_RETENTION_DAYS);
PARSER.declareLong(Builder::setModelSnapshotRetentionDays, Job.MODEL_SNAPSHOT_RETENTION_DAYS);
PARSER.declareStringArray(Builder::setCategorizationFilters, AnalysisConfig.CATEGORIZATION_FILTERS);
PARSER.declareField(Builder::setCustomSettings, (p, c) -> p.map(), Job.CUSTOM_SETTINGS, ObjectParser.ValueType.OBJECT);
}
private final String jobId;
private final List<String> groups;
private final String description;
private final List<DetectorUpdate> detectorUpdates;
private final ModelPlotConfig modelPlotConfig;
private final AnalysisLimits analysisLimits;
private final Long renormalizationWindowDays;
private final TimeValue backgroundPersistInterval;
private final Long modelSnapshotRetentionDays;
private final Long resultsRetentionDays;
private final List<String> categorizationFilters;
private final Map<String, Object> customSettings;
private JobUpdate(String jobId, @Nullable List<String> groups, @Nullable String description,
@Nullable List<DetectorUpdate> detectorUpdates, @Nullable ModelPlotConfig modelPlotConfig,
@Nullable AnalysisLimits analysisLimits, @Nullable TimeValue backgroundPersistInterval,
@Nullable Long renormalizationWindowDays, @Nullable Long resultsRetentionDays,
@Nullable Long modelSnapshotRetentionDays, @Nullable List<String> categorisationFilters,
@Nullable Map<String, Object> customSettings) {
this.jobId = jobId;
this.groups = groups;
this.description = description;
this.detectorUpdates = detectorUpdates;
this.modelPlotConfig = modelPlotConfig;
this.analysisLimits = analysisLimits;
this.renormalizationWindowDays = renormalizationWindowDays;
this.backgroundPersistInterval = backgroundPersistInterval;
this.modelSnapshotRetentionDays = modelSnapshotRetentionDays;
this.resultsRetentionDays = resultsRetentionDays;
this.categorizationFilters = categorisationFilters;
this.customSettings = customSettings;
}
public String getJobId() {
return jobId;
}
public List<String> getGroups() {
return groups;
}
public String getDescription() {
return description;
}
public List<DetectorUpdate> getDetectorUpdates() {
return detectorUpdates;
}
public ModelPlotConfig getModelPlotConfig() {
return modelPlotConfig;
}
public AnalysisLimits getAnalysisLimits() {
return analysisLimits;
}
public Long getRenormalizationWindowDays() {
return renormalizationWindowDays;
}
public TimeValue getBackgroundPersistInterval() {
return backgroundPersistInterval;
}
public Long getModelSnapshotRetentionDays() {
return modelSnapshotRetentionDays;
}
public Long getResultsRetentionDays() {
return resultsRetentionDays;
}
public List<String> getCategorizationFilters() {
return categorizationFilters;
}
public Map<String, Object> getCustomSettings() {
return customSettings;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Job.ID.getPreferredName(), jobId);
if (groups != null) {
builder.field(Job.GROUPS.getPreferredName(), groups);
}
if (description != null) {
builder.field(Job.DESCRIPTION.getPreferredName(), description);
}
if (detectorUpdates != null) {
builder.field(DETECTORS.getPreferredName(), detectorUpdates);
}
if (modelPlotConfig != null) {
builder.field(Job.MODEL_PLOT_CONFIG.getPreferredName(), modelPlotConfig);
}
if (analysisLimits != null) {
builder.field(Job.ANALYSIS_LIMITS.getPreferredName(), analysisLimits);
}
if (renormalizationWindowDays != null) {
builder.field(Job.RENORMALIZATION_WINDOW_DAYS.getPreferredName(), renormalizationWindowDays);
}
if (backgroundPersistInterval != null) {
builder.field(Job.BACKGROUND_PERSIST_INTERVAL.getPreferredName(), backgroundPersistInterval);
}
if (modelSnapshotRetentionDays != null) {
builder.field(Job.MODEL_SNAPSHOT_RETENTION_DAYS.getPreferredName(), modelSnapshotRetentionDays);
}
if (resultsRetentionDays != null) {
builder.field(Job.RESULTS_RETENTION_DAYS.getPreferredName(), resultsRetentionDays);
}
if (categorizationFilters != null) {
builder.field(AnalysisConfig.CATEGORIZATION_FILTERS.getPreferredName(), categorizationFilters);
}
if (customSettings != null) {
builder.field(Job.CUSTOM_SETTINGS.getPreferredName(), customSettings);
}
builder.endObject();
return builder;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
JobUpdate that = (JobUpdate) other;
return Objects.equals(this.jobId, that.jobId)
&& Objects.equals(this.groups, that.groups)
&& Objects.equals(this.description, that.description)
&& Objects.equals(this.detectorUpdates, that.detectorUpdates)
&& Objects.equals(this.modelPlotConfig, that.modelPlotConfig)
&& Objects.equals(this.analysisLimits, that.analysisLimits)
&& Objects.equals(this.renormalizationWindowDays, that.renormalizationWindowDays)
&& Objects.equals(this.backgroundPersistInterval, that.backgroundPersistInterval)
&& Objects.equals(this.modelSnapshotRetentionDays, that.modelSnapshotRetentionDays)
&& Objects.equals(this.resultsRetentionDays, that.resultsRetentionDays)
&& Objects.equals(this.categorizationFilters, that.categorizationFilters)
&& Objects.equals(this.customSettings, that.customSettings);
}
@Override
public int hashCode() {
return Objects.hash(jobId, groups, description, detectorUpdates, modelPlotConfig, analysisLimits, renormalizationWindowDays,
backgroundPersistInterval, modelSnapshotRetentionDays, resultsRetentionDays, categorizationFilters, customSettings);
}
public static class DetectorUpdate implements ToXContentObject {
@SuppressWarnings("unchecked")
public static final ConstructingObjectParser<DetectorUpdate, Void> PARSER =
new ConstructingObjectParser<>("detector_update", true, a -> new DetectorUpdate((int) a[0], (String) a[1],
(List<DetectionRule>) a[2]));
static {
PARSER.declareInt(ConstructingObjectParser.optionalConstructorArg(), Detector.DETECTOR_INDEX);
PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), Job.DESCRIPTION);
PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), (parser, parseFieldMatcher) ->
DetectionRule.PARSER.apply(parser, parseFieldMatcher).build(), Detector.CUSTOM_RULES_FIELD);
}
private final int detectorIndex;
private final String description;
private final List<DetectionRule> rules;
/**
* A detector update to apply to the Machine Learning Job
*
* @param detectorIndex The identifier of the detector to update.
* @param description The new description for the detector.
* @param rules The new list of rules for the detector.
*/
public DetectorUpdate(int detectorIndex, String description, List<DetectionRule> rules) {
this.detectorIndex = detectorIndex;
this.description = description;
this.rules = rules;
}
public int getDetectorIndex() {
return detectorIndex;
}
public String getDescription() {
return description;
}
public List<DetectionRule> getRules() {
return rules;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Detector.DETECTOR_INDEX.getPreferredName(), detectorIndex);
if (description != null) {
builder.field(Job.DESCRIPTION.getPreferredName(), description);
}
if (rules != null) {
builder.field(Detector.CUSTOM_RULES_FIELD.getPreferredName(), rules);
}
builder.endObject();
return builder;
}
@Override
public int hashCode() {
return Objects.hash(detectorIndex, description, rules);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
DetectorUpdate that = (DetectorUpdate) other;
return this.detectorIndex == that.detectorIndex && Objects.equals(this.description, that.description)
&& Objects.equals(this.rules, that.rules);
}
}
public static class Builder {
private final String jobId;
private List<String> groups;
private String description;
private List<DetectorUpdate> detectorUpdates;
private ModelPlotConfig modelPlotConfig;
private AnalysisLimits analysisLimits;
private Long renormalizationWindowDays;
private TimeValue backgroundPersistInterval;
private Long modelSnapshotRetentionDays;
private Long resultsRetentionDays;
private List<String> categorizationFilters;
private Map<String, Object> customSettings;
/**
* New {@link JobUpdate.Builder} object for the existing job
*
* @param jobId non-null `jobId` for referencing an exising {@link Job}
*/
public Builder(String jobId) {
this.jobId = jobId;
}
/**
* Set the job groups
*
* Updates the {@link Job#groups} setting
*
* @param groups A list of group names
*/
public Builder setGroups(List<String> groups) {
this.groups = groups;
return this;
}
/**
* Set the job description
*
* Updates the {@link Job#description} setting
*
* @param description the desired Machine Learning job description
*/
public Builder setDescription(String description) {
this.description = description;
return this;
}
/**
* The detector updates to apply to the job
*
* Updates the {@link AnalysisConfig#detectors} setting
*
* @param detectorUpdates list of {@link JobUpdate.DetectorUpdate} objects
*/
public Builder setDetectorUpdates(List<DetectorUpdate> detectorUpdates) {
this.detectorUpdates = detectorUpdates;
return this;
}
/**
* Enables/disables the model plot config setting through {@link ModelPlotConfig#enabled}
*
* Updates the {@link Job#modelPlotConfig} setting
*
* @param modelPlotConfig {@link ModelPlotConfig} object with updated fields
*/
public Builder setModelPlotConfig(ModelPlotConfig modelPlotConfig) {
this.modelPlotConfig = modelPlotConfig;
return this;
}
/**
* Sets new {@link AnalysisLimits} for the {@link Job}
*
* Updates the {@link Job#analysisLimits} setting
*
* @param analysisLimits Updates to {@link AnalysisLimits}
*/
public Builder setAnalysisLimits(AnalysisLimits analysisLimits) {
this.analysisLimits = analysisLimits;
return this;
}
/**
* Advanced configuration option. The period over which adjustments to the score are applied, as new data is seen
*
* Updates the {@link Job#renormalizationWindowDays} setting
*
* @param renormalizationWindowDays number of renormalization window days
*/
public Builder setRenormalizationWindowDays(Long renormalizationWindowDays) {
this.renormalizationWindowDays = renormalizationWindowDays;
return this;
}
/**
* Advanced configuration option. The time between each periodic persistence of the model
*
* Updates the {@link Job#backgroundPersistInterval} setting
*
* @param backgroundPersistInterval the time between background persistence
*/
public Builder setBackgroundPersistInterval(TimeValue backgroundPersistInterval) {
this.backgroundPersistInterval = backgroundPersistInterval;
return this;
}
/**
* The time in days that model snapshots are retained for the job.
*
* Updates the {@link Job#modelSnapshotRetentionDays} setting
*
* @param modelSnapshotRetentionDays number of days to keep a model snapshot
*/
public Builder setModelSnapshotRetentionDays(Long modelSnapshotRetentionDays) {
this.modelSnapshotRetentionDays = modelSnapshotRetentionDays;
return this;
}
/**
* Advanced configuration option. The number of days for which job results are retained
*
* Updates the {@link Job#resultsRetentionDays} setting
*
* @param resultsRetentionDays number of days to keep results.
*/
public Builder setResultsRetentionDays(Long resultsRetentionDays) {
this.resultsRetentionDays = resultsRetentionDays;
return this;
}
/**
* Sets the categorization filters on the {@link Job}
*
* Updates the {@link AnalysisConfig#categorizationFilters} setting.
* Requires {@link AnalysisConfig#categorizationFieldName} to have been set on the existing Job.
*
* @param categorizationFilters list of categorization filters for the Job's {@link AnalysisConfig}
*/
public Builder setCategorizationFilters(List<String> categorizationFilters) {
this.categorizationFilters = categorizationFilters;
return this;
}
/**
* Contains custom meta data about the job.
*
* Updates the {@link Job#customSettings} setting
*
* @param customSettings custom settings map for the job
*/
public Builder setCustomSettings(Map<String, Object> customSettings) {
this.customSettings = customSettings;
return this;
}
public JobUpdate build() {
return new JobUpdate(jobId, groups, description, detectorUpdates, modelPlotConfig, analysisLimits, backgroundPersistInterval,
renormalizationWindowDays, resultsRetentionDays, modelSnapshotRetentionDays, categorizationFilters, customSettings);
}
}
}

View File

@ -34,9 +34,12 @@ import org.elasticsearch.client.ml.GetOverallBucketsRequest;
import org.elasticsearch.client.ml.GetRecordsRequest;
import org.elasticsearch.client.ml.OpenJobRequest;
import org.elasticsearch.client.ml.PutJobRequest;
import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.job.config.AnalysisConfig;
import org.elasticsearch.client.ml.job.config.Detector;
import org.elasticsearch.client.ml.job.config.Job;
import org.elasticsearch.client.ml.job.config.JobUpdate;
import org.elasticsearch.client.ml.job.config.JobUpdateTests;
import org.elasticsearch.client.ml.job.util.PageParams;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentParser;
@ -166,6 +169,20 @@ public class MLRequestConvertersTests extends ESTestCase {
requestEntityToString(request));
}
public void testUpdateJob() throws Exception {
String jobId = randomAlphaOfLength(10);
JobUpdate updates = JobUpdateTests.createRandom(jobId);
UpdateJobRequest updateJobRequest = new UpdateJobRequest(updates);
Request request = MLRequestConverters.updateJob(updateJobRequest);
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + "/_update", request.getEndpoint());
try (XContentParser parser = createParser(JsonXContent.jsonXContent, request.getEntity().getContent())) {
JobUpdate.Builder parsedRequest = JobUpdate.PARSER.apply(parser, null);
assertThat(parsedRequest.build(), equalTo(updates));
}
}
public void testGetBuckets() throws IOException {
String jobId = randomAlphaOfLength(10);
GetBucketsRequest getBucketsRequest = new GetBucketsRequest(jobId);

View File

@ -20,6 +20,8 @@ package org.elasticsearch.client;
import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.job.config.JobUpdate;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.client.ml.GetJobStatsRequest;
import org.elasticsearch.client.ml.GetJobStatsResponse;
@ -218,6 +220,23 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase {
assertThat(exception.status().getStatus(), equalTo(404));
}
public void testUpdateJob() throws Exception {
String jobId = randomValidJobId();
Job job = buildJob(jobId);
MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
machineLearningClient.putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
UpdateJobRequest request = new UpdateJobRequest(new JobUpdate.Builder(jobId).setDescription("Updated description").build());
PutJobResponse response = execute(request, machineLearningClient::updateJob, machineLearningClient::updateJobAsync);
assertEquals("Updated description", response.getResponse().getDescription());
GetJobRequest getRequest = new GetJobRequest(jobId);
GetJobResponse getResponse = machineLearningClient.getJob(getRequest, RequestOptions.DEFAULT);
assertEquals("Updated description", getResponse.jobs().get(0).getDescription());
}
public static String randomValidJobId() {
CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray());
return generator.ofCodePointsLength(random(), 10, 10);

View File

@ -51,10 +51,17 @@ import org.elasticsearch.client.ml.OpenJobRequest;
import org.elasticsearch.client.ml.OpenJobResponse;
import org.elasticsearch.client.ml.PutJobRequest;
import org.elasticsearch.client.ml.PutJobResponse;
import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.job.config.AnalysisConfig;
import org.elasticsearch.client.ml.job.config.AnalysisLimits;
import org.elasticsearch.client.ml.job.config.DataDescription;
import org.elasticsearch.client.ml.job.config.DetectionRule;
import org.elasticsearch.client.ml.job.config.Detector;
import org.elasticsearch.client.ml.job.config.Job;
import org.elasticsearch.client.ml.job.config.JobUpdate;
import org.elasticsearch.client.ml.job.config.ModelPlotConfig;
import org.elasticsearch.client.ml.job.config.Operator;
import org.elasticsearch.client.ml.job.config.RuleCondition;
import org.elasticsearch.client.ml.job.results.AnomalyRecord;
import org.elasticsearch.client.ml.job.results.Bucket;
import org.elasticsearch.client.ml.job.results.Influencer;
@ -66,9 +73,12 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.junit.After;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -372,6 +382,93 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
}
}
public void testUpdateJob() throws Exception {
RestHighLevelClient client = highLevelClient();
String jobId = "test-update-job";
Job tempJob = MachineLearningIT.buildJob(jobId);
Job job = new Job.Builder(tempJob)
.setAnalysisConfig(new AnalysisConfig.Builder(tempJob.getAnalysisConfig())
.setCategorizationFieldName("categorization-field")
.setDetector(0,
new Detector.Builder().setFieldName("total")
.setFunction("sum")
.setPartitionFieldName("mlcategory")
.setDetectorDescription(randomAlphaOfLength(10))
.build()))
.build();
client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
{
List<DetectionRule> detectionRules = Arrays.asList(
new DetectionRule.Builder(Arrays.asList(RuleCondition.createTime(Operator.GT, 100L))).build());
Map<String, Object> customSettings = new HashMap<>();
customSettings.put("custom-setting-1", "custom-value");
//tag::x-pack-ml-update-job-detector-options
JobUpdate.DetectorUpdate detectorUpdate = new JobUpdate.DetectorUpdate(0, //<1>
"detector description", //<2>
detectionRules); //<3>
//end::x-pack-ml-update-job-detector-options
//tag::x-pack-ml-update-job-options
JobUpdate update = new JobUpdate.Builder(jobId) //<1>
.setDescription("My description") //<2>
.setAnalysisLimits(new AnalysisLimits(1000L, null)) //<3>
.setBackgroundPersistInterval(TimeValue.timeValueHours(3)) //<4>
.setCategorizationFilters(Arrays.asList("categorization-filter")) //<5>
.setDetectorUpdates(Arrays.asList(detectorUpdate)) //<6>
.setGroups(Arrays.asList("job-group-1")) //<7>
.setResultsRetentionDays(10L) //<8>
.setModelPlotConfig(new ModelPlotConfig(true, null)) //<9>
.setModelSnapshotRetentionDays(7L) //<10>
.setCustomSettings(customSettings) //<11>
.setRenormalizationWindowDays(3L) //<12>
.build();
//end::x-pack-ml-update-job-options
//tag::x-pack-ml-update-job-request
UpdateJobRequest updateJobRequest = new UpdateJobRequest(update); //<1>
//end::x-pack-ml-update-job-request
//tag::x-pack-ml-update-job-execute
PutJobResponse updateJobResponse = client.machineLearning().updateJob(updateJobRequest, RequestOptions.DEFAULT);
//end::x-pack-ml-update-job-execute
//tag::x-pack-ml-update-job-response
Job updatedJob = updateJobResponse.getResponse(); //<1>
//end::x-pack-ml-update-job-response
assertEquals(update.getDescription(), updatedJob.getDescription());
}
{
//tag::x-pack-ml-update-job-listener
ActionListener<PutJobResponse> listener = new ActionListener<PutJobResponse>() {
@Override
public void onResponse(PutJobResponse updateJobResponse) {
//<1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
//end::x-pack-ml-update-job-listener
UpdateJobRequest updateJobRequest = new UpdateJobRequest(new JobUpdate.Builder(jobId).build());
// Replace the empty listener by a blocking listener in test
final CountDownLatch latch = new CountDownLatch(1);
listener = new LatchedActionListener<>(listener, latch);
// tag::x-pack-ml-update-job-execute-async
client.machineLearning().updateJobAsync(updateJobRequest, RequestOptions.DEFAULT, listener); //<1>
// end::x-pack-ml-update-job-execute-async
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
public void testGetBuckets() throws IOException, InterruptedException {
RestHighLevelClient client = highLevelClient();

View File

@ -0,0 +1,44 @@
/*
* 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.config.JobTests;
import org.elasticsearch.client.ml.job.config.JobUpdate;
import org.elasticsearch.client.ml.job.config.JobUpdateTests;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
public class UpdateJobRequestTests extends AbstractXContentTestCase<UpdateJobRequest> {
@Override
protected UpdateJobRequest createTestInstance() {
return new UpdateJobRequest(JobUpdateTests.createRandom(JobTests.randomValidJobId()));
}
@Override
protected UpdateJobRequest doParseInstance(XContentParser parser) {
return new UpdateJobRequest(JobUpdate.PARSER.apply(parser, null).build());
}
@Override
protected boolean supportsUnknownFields() {
return false;
}
}

View File

@ -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.job.config;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
public class JobUpdateTests extends AbstractXContentTestCase<JobUpdate> {
@Override
protected JobUpdate createTestInstance() {
return createRandom(randomAlphaOfLength(4));
}
/**
* Creates a completely random update when the job is null
* or a random update that is is valid for the given job
*/
public static JobUpdate createRandom(String jobId) {
JobUpdate.Builder update = new JobUpdate.Builder(jobId);
if (randomBoolean()) {
int groupsNum = randomIntBetween(0, 10);
List<String> groups = new ArrayList<>(groupsNum);
for (int i = 0; i < groupsNum; i++) {
groups.add(JobTests.randomValidJobId());
}
update.setGroups(groups);
}
if (randomBoolean()) {
update.setDescription(randomAlphaOfLength(20));
}
if (randomBoolean()) {
update.setDetectorUpdates(createRandomDetectorUpdates());
}
if (randomBoolean()) {
update.setModelPlotConfig(new ModelPlotConfig(randomBoolean(), randomAlphaOfLength(10)));
}
if (randomBoolean()) {
update.setAnalysisLimits(AnalysisLimitsTests.createRandomized());
}
if (randomBoolean()) {
update.setRenormalizationWindowDays(randomNonNegativeLong());
}
if (randomBoolean()) {
update.setBackgroundPersistInterval(TimeValue.timeValueHours(randomIntBetween(1, 24)));
}
if (randomBoolean()) {
update.setModelSnapshotRetentionDays(randomNonNegativeLong());
}
if (randomBoolean()) {
update.setResultsRetentionDays(randomNonNegativeLong());
}
if (randomBoolean()) {
update.setCategorizationFilters(Arrays.asList(generateRandomStringArray(10, 10, false)));
}
if (randomBoolean()) {
update.setCustomSettings(Collections.singletonMap(randomAlphaOfLength(10), randomAlphaOfLength(10)));
}
return update.build();
}
private static List<JobUpdate.DetectorUpdate> createRandomDetectorUpdates() {
int size = randomInt(10);
List<JobUpdate.DetectorUpdate> detectorUpdates = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
String detectorDescription = null;
if (randomBoolean()) {
detectorDescription = randomAlphaOfLength(12);
}
List<DetectionRule> detectionRules = null;
if (randomBoolean()) {
detectionRules = new ArrayList<>();
detectionRules.add(new DetectionRule.Builder(
Collections.singletonList(new RuleCondition(RuleCondition.AppliesTo.ACTUAL, Operator.GT, 5))).build());
}
detectorUpdates.add(new JobUpdate.DetectorUpdate(i, detectorDescription, detectionRules));
}
return detectorUpdates;
}
@Override
protected JobUpdate doParseInstance(XContentParser parser) {
return JobUpdate.PARSER.apply(parser, null).build();
}
@Override
protected boolean supportsUnknownFields() {
return true;
}
@Override
protected Predicate<String> getRandomFieldsExcludeFilter() {
return field -> !field.isEmpty();
}
}

View File

@ -0,0 +1,93 @@
[[java-rest-high-x-pack-ml-update-job]]
=== Update Job API
The Update Job API provides the ability to update a {ml} job.
It accepts a `UpdateJobRequest` object and responds
with a `PutJobResponse` object.
[[java-rest-high-x-pack-ml-update-job-request]]
==== Update Job Request
An `UpdateJobRequest` object gets created with a `JobUpdate` object.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-update-job-request]
--------------------------------------------------
<1> Constructing a new request referencing a `JobUpdate` object
==== Optional Arguments
The `JobUpdate` object has many optional arguments with which to update an existing {ml}
job. An existing, non-null `jobId` must be referenced in its creation.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-update-job-options]
--------------------------------------------------
<1> Mandatory, non-null `jobId` referencing an existing {ml} job
<2> Updated description
<3> Updated analysis limits
<4> Updated background persistence interval
<5> Updated analysis config's categorization filters
<6> Updated detectors through the `JobUpdate.DetectorUpdate` object
<7> Updated group membership
<8> Updated result retention
<9> Updated model plot configuration
<10> Updated model snapshot retention setting
<11> Updated custom settings
<12> Updated renormalization window
Included with these options are specific optional `JobUpdate.DetectorUpdate` updates.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-update-job-detector-options]
--------------------------------------------------
<1> The index of the detector. `O` means unknown
<2> The optional description of the detector
<3> The `DetectionRule` rules that apply to this detector
[[java-rest-high-x-pack-ml-update-job-execution]]
==== Execution
The request can be executed through the `MachineLearningClient` contained
in the `RestHighLevelClient` object, accessed via the `machineLearningClient()` method.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-update-job-execute]
--------------------------------------------------
[[java-rest-high-x-pack-ml-update-job-execution-async]]
==== Asynchronous Execution
The request can also be executed asynchronously:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-update-job-execute-async]
--------------------------------------------------
<1> The `UpdateJobRequest` to execute and the `ActionListener` to use when
the execution completes
The method does not block and returns immediately. The passed `ActionListener` is used
to notify the caller of completion. A typical `ActionListener` for `PutJobResponse` may
look like
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-update-job-listener]
--------------------------------------------------
<1> `onResponse` is called back when the action is completed successfully
<2> `onFailure` is called back when some unexpected error occurs
[[java-rest-high-x-pack-ml-update-job-response]]
==== Update Job Response
A `PutJobResponse` contains the updated `Job` object
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-update-job-response]
--------------------------------------------------
<1> `getResponse()` returns the updated `Job` object

View File

@ -216,6 +216,7 @@ The Java High Level REST Client supports the following Machine Learning APIs:
* <<java-rest-high-x-pack-ml-open-job>>
* <<java-rest-high-x-pack-ml-close-job>>
* <<java-rest-high-x-pack-ml-flush-job>>
* <<java-rest-high-x-pack-ml-update-job>>
* <<java-rest-high-x-pack-ml-get-job-stats>>
* <<java-rest-high-x-pack-ml-get-buckets>>
* <<java-rest-high-x-pack-ml-get-overall-buckets>>
@ -227,6 +228,7 @@ include::ml/get-job.asciidoc[]
include::ml/delete-job.asciidoc[]
include::ml/open-job.asciidoc[]
include::ml/close-job.asciidoc[]
include::ml/update-job.asciidoc[]
include::ml/flush-job.asciidoc[]
include::ml/get-job-stats.asciidoc[]
include::ml/get-buckets.asciidoc[]