From 052e928a802391e0e5886e965088a9e2ab14b1f6 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Tue, 4 Sep 2018 13:40:07 -0500 Subject: [PATCH 1/3] HLRC: ML Update Job --- .../client/MLRequestConverters.java | 14 + .../client/MachineLearningClient.java | 34 ++ .../client/ml/UpdateJobRequest.java | 80 +++ .../client/ml/job/config/JobUpdate.java | 454 ++++++++++++++++++ .../client/MLRequestConvertersTests.java | 17 + .../client/MachineLearningIT.java | 19 + .../MlClientDocumentationIT.java | 97 ++++ .../client/ml/UpdateJobRequestTests.java | 44 ++ .../client/ml/job/config/JobUpdateTests.java | 107 +++++ .../high-level/ml/update-job.asciidoc | 93 ++++ .../high-level/supported-apis.asciidoc | 2 + 11 files changed, 961 insertions(+) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/UpdateJobRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/config/JobUpdate.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/UpdateJobRequestTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/config/JobUpdateTests.java create mode 100644 docs/java-rest/high-level/ml/update-job.asciidoc diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java index 35898a8a8e4d8..b8604d070357b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java @@ -34,6 +34,7 @@ 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; @@ -145,6 +146,19 @@ static Request flushJob(FlushJobRequest flushJobRequest) throws IOException { 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") diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java index 4757ec3182b40..36a283ea01b9b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java @@ -23,6 +23,7 @@ import org.elasticsearch.client.ml.FlushJobResponse; import org.elasticsearch.client.ml.GetJobStatsRequest; import org.elasticsearch.client.ml.GetJobStatsResponse; +import org.elasticsearch.client.ml.UpdateJobRequest; import org.elasticsearch.client.ml.job.stats.JobStats; import org.elasticsearch.client.ml.CloseJobRequest; import org.elasticsearch.client.ml.CloseJobResponse; @@ -317,6 +318,7 @@ public void closeJobAsync(CloseJobRequest request, RequestOptions options, Actio * * @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, @@ -354,6 +356,38 @@ public void flushJobAsync(FlushJobRequest request, RequestOptions options, Actio Collections.emptySet()); } + /** + * Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job} properties + * + * @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} properties 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 listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, + MLRequestConverters::updateJob, + options, + PutJobResponse::fromXContent, + listener, + Collections.emptySet()); + } + /** * Gets the buckets for a Machine Learning Job. *

diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/UpdateJobRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/UpdateJobRequest.java new file mode 100644 index 0000000000000..3204bd9ecdf64 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/UpdateJobRequest.java @@ -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 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; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/config/JobUpdate.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/config/JobUpdate.java new file mode 100644 index 0000000000000..15499a650439d --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/config/JobUpdate.java @@ -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 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 groups; + private final String description; + private final List 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 categorizationFilters; + private final Map customSettings; + + private JobUpdate(String jobId, @Nullable List groups, @Nullable String description, + @Nullable List detectorUpdates, @Nullable ModelPlotConfig modelPlotConfig, + @Nullable AnalysisLimits analysisLimits, @Nullable TimeValue backgroundPersistInterval, + @Nullable Long renormalizationWindowDays, @Nullable Long resultsRetentionDays, + @Nullable Long modelSnapshotRetentionDays, @Nullable List categorisationFilters, + @Nullable Map 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 getGroups() { + return groups; + } + + public String getDescription() { + return description; + } + + public List 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 getCategorizationFilters() { + return categorizationFilters; + } + + public Map 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 PARSER = + new ConstructingObjectParser<>("detector_update", true, a -> new DetectorUpdate((int) a[0], (String) a[1], + (List) 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 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 rules) { + this.detectorIndex = detectorIndex; + this.description = description; + this.rules = rules; + } + + public int getDetectorIndex() { + return detectorIndex; + } + + public String getDescription() { + return description; + } + + public List 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 groups; + private String description; + private List detectorUpdates; + private ModelPlotConfig modelPlotConfig; + private AnalysisLimits analysisLimits; + private Long renormalizationWindowDays; + private TimeValue backgroundPersistInterval; + private Long modelSnapshotRetentionDays; + private Long resultsRetentionDays; + private List categorizationFilters; + private Map 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 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 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 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 customSettings) { + this.customSettings = customSettings; + return this; + } + + public JobUpdate build() { + return new JobUpdate(jobId, groups, description, detectorUpdates, modelPlotConfig, analysisLimits, backgroundPersistInterval, + renormalizationWindowDays, resultsRetentionDays, modelSnapshotRetentionDays, categorizationFilters, customSettings); + } + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java index bd997224bebda..75eab47444925 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java @@ -33,9 +33,12 @@ 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; @@ -165,6 +168,20 @@ public void testFlushJob() throws Exception { 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); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java index cd4b6ffc7691f..bf25d9d1c0fb3 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java @@ -20,6 +20,8 @@ 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 void testGetJobStats() throws Exception { 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); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java index 427f75a80d029..9feeb90ff3a60 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java @@ -49,10 +49,17 @@ 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.OverallBucket; @@ -63,9 +70,12 @@ 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; @@ -369,6 +379,93 @@ public void onFailure(Exception e) { } } + 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 detectionRules = Arrays.asList( + new DetectionRule.Builder(Arrays.asList(RuleCondition.createTime(Operator.GT, 100L))).build()); + Map 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 listener = new ActionListener() { + @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(); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/UpdateJobRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/UpdateJobRequestTests.java new file mode 100644 index 0000000000000..4d2bbb2e2006d --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/UpdateJobRequestTests.java @@ -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 { + + @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; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/config/JobUpdateTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/config/JobUpdateTests.java new file mode 100644 index 0000000000000..bb70ba4d9a78d --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/config/JobUpdateTests.java @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic 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 { + + @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 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 createRandomDetectorUpdates() { + int size = randomInt(10); + List detectorUpdates = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String detectorDescription = null; + if (randomBoolean()) { + detectorDescription = randomAlphaOfLength(12); + } + List 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 getRandomFieldsExcludeFilter() { + return field -> !field.isEmpty(); + } +} diff --git a/docs/java-rest/high-level/ml/update-job.asciidoc b/docs/java-rest/high-level/ml/update-job.asciidoc new file mode 100644 index 0000000000000..41a98af7628db --- /dev/null +++ b/docs/java-rest/high-level/ml/update-job.asciidoc @@ -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 + +A `UpdateJobRequest` object gets created with an `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 diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 481a2470aa2d7..81a3e5335558d 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -216,6 +216,7 @@ The Java High Level REST Client supports the following Machine Learning APIs: * <> * <> * <> +* <> * <> * <> * <> @@ -226,6 +227,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[] From 8097ff5e270de403df531e3c1effdbc4ce07010a Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Tue, 4 Sep 2018 15:19:22 -0500 Subject: [PATCH 2/3] fixing license header --- .../client/ml/job/config/JobUpdateTests.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/config/JobUpdateTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/config/JobUpdateTests.java index bb70ba4d9a78d..b159fedb95d44 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/config/JobUpdateTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/config/JobUpdateTests.java @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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; From acffff973113a51376841af88a78fef887c9da7d Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Wed, 5 Sep 2018 15:50:03 -0500 Subject: [PATCH 3/3] addressing comments --- .../java/org/elasticsearch/client/MachineLearningClient.java | 4 ++-- .../java/org/elasticsearch/client/ml/UpdateJobRequest.java | 2 +- docs/java-rest/high-level/ml/update-job.asciidoc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java index 64a95ae0d6a16..bdfc34ad997d6 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java @@ -359,7 +359,7 @@ public void flushJobAsync(FlushJobRequest request, RequestOptions options, Actio } /** - * Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job} properties + * 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 @@ -375,7 +375,7 @@ public PutJobResponse updateJob(UpdateJobRequest request, RequestOptions options } /** - * Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job} properties asynchronously + * 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 diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/UpdateJobRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/UpdateJobRequest.java index 3204bd9ecdf64..6e050f8adcf90 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/UpdateJobRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/UpdateJobRequest.java @@ -34,7 +34,7 @@ */ public class UpdateJobRequest extends ActionRequest implements ToXContentObject { - private JobUpdate update; + private final JobUpdate update; public UpdateJobRequest(JobUpdate update) { this.update = update; diff --git a/docs/java-rest/high-level/ml/update-job.asciidoc b/docs/java-rest/high-level/ml/update-job.asciidoc index 41a98af7628db..3e1d1e2313b72 100644 --- a/docs/java-rest/high-level/ml/update-job.asciidoc +++ b/docs/java-rest/high-level/ml/update-job.asciidoc @@ -8,7 +8,7 @@ with a `PutJobResponse` object. [[java-rest-high-x-pack-ml-update-job-request]] ==== Update Job Request -A `UpdateJobRequest` object gets created with an `JobUpdate` object. +An `UpdateJobRequest` object gets created with a `JobUpdate` object. ["source","java",subs="attributes,callouts,macros"] --------------------------------------------------