Adjust validation endpoints (elastic/elasticsearch#812)
Changes are: 1. The detector validation endpoint is changed from /_xpack/ml/_validate/detector to /_xpack/ml/anomaly_detectors/_validate/detector 2. A new endpoint is added for validating an entire job config: /_xpack/ml/anomaly_detectors/_validate Relates elastic/elasticsearch#630 Original commit: elastic/x-pack-elasticsearch@7b2031e746
This commit is contained in:
parent
4eab74ce29
commit
ab957b6d91
|
@ -25,6 +25,8 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.settings.SettingsFilter;
|
import org.elasticsearch.common.settings.SettingsFilter;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.xpack.ml.action.ValidateJobConfigAction;
|
||||||
|
import org.elasticsearch.xpack.ml.rest.validate.RestValidateJobConfigAction;
|
||||||
import org.elasticsearch.xpack.persistent.RemovePersistentTaskAction;
|
import org.elasticsearch.xpack.persistent.RemovePersistentTaskAction;
|
||||||
import org.elasticsearch.xpack.persistent.PersistentActionCoordinator;
|
import org.elasticsearch.xpack.persistent.PersistentActionCoordinator;
|
||||||
import org.elasticsearch.xpack.persistent.PersistentActionRegistry;
|
import org.elasticsearch.xpack.persistent.PersistentActionRegistry;
|
||||||
|
@ -271,6 +273,7 @@ public class MlPlugin extends Plugin implements ActionPlugin {
|
||||||
new RestCloseJobAction(settings, restController),
|
new RestCloseJobAction(settings, restController),
|
||||||
new RestFlushJobAction(settings, restController),
|
new RestFlushJobAction(settings, restController),
|
||||||
new RestValidateDetectorAction(settings, restController),
|
new RestValidateDetectorAction(settings, restController),
|
||||||
|
new RestValidateJobConfigAction(settings, restController),
|
||||||
new RestGetCategoriesAction(settings, restController),
|
new RestGetCategoriesAction(settings, restController),
|
||||||
new RestGetModelSnapshotsAction(settings, restController),
|
new RestGetModelSnapshotsAction(settings, restController),
|
||||||
new RestRevertModelSnapshotAction(settings, restController),
|
new RestRevertModelSnapshotAction(settings, restController),
|
||||||
|
@ -309,6 +312,7 @@ public class MlPlugin extends Plugin implements ActionPlugin {
|
||||||
new ActionHandler<>(CloseJobAction.INSTANCE, CloseJobAction.TransportAction.class),
|
new ActionHandler<>(CloseJobAction.INSTANCE, CloseJobAction.TransportAction.class),
|
||||||
new ActionHandler<>(FlushJobAction.INSTANCE, FlushJobAction.TransportAction.class),
|
new ActionHandler<>(FlushJobAction.INSTANCE, FlushJobAction.TransportAction.class),
|
||||||
new ActionHandler<>(ValidateDetectorAction.INSTANCE, ValidateDetectorAction.TransportAction.class),
|
new ActionHandler<>(ValidateDetectorAction.INSTANCE, ValidateDetectorAction.TransportAction.class),
|
||||||
|
new ActionHandler<>(ValidateJobConfigAction.INSTANCE, ValidateJobConfigAction.TransportAction.class),
|
||||||
new ActionHandler<>(GetCategoriesAction.INSTANCE, GetCategoriesAction.TransportAction.class),
|
new ActionHandler<>(GetCategoriesAction.INSTANCE, GetCategoriesAction.TransportAction.class),
|
||||||
new ActionHandler<>(GetModelSnapshotsAction.INSTANCE, GetModelSnapshotsAction.TransportAction.class),
|
new ActionHandler<>(GetModelSnapshotsAction.INSTANCE, GetModelSnapshotsAction.TransportAction.class),
|
||||||
new ActionHandler<>(RevertModelSnapshotAction.INSTANCE, RevertModelSnapshotAction.TransportAction.class),
|
new ActionHandler<>(RevertModelSnapshotAction.INSTANCE, RevertModelSnapshotAction.TransportAction.class),
|
||||||
|
|
|
@ -34,7 +34,7 @@ public class ValidateDetectorAction
|
||||||
extends Action<ValidateDetectorAction.Request, ValidateDetectorAction.Response, ValidateDetectorAction.RequestBuilder> {
|
extends Action<ValidateDetectorAction.Request, ValidateDetectorAction.Response, ValidateDetectorAction.RequestBuilder> {
|
||||||
|
|
||||||
public static final ValidateDetectorAction INSTANCE = new ValidateDetectorAction();
|
public static final ValidateDetectorAction INSTANCE = new ValidateDetectorAction();
|
||||||
public static final String NAME = "cluster:admin/ml/validate/detector";
|
public static final String NAME = "cluster:admin/ml/anomaly_detectors/validate/detector";
|
||||||
|
|
||||||
protected ValidateDetectorAction() {
|
protected ValidateDetectorAction() {
|
||||||
super(NAME);
|
super(NAME);
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* 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.xpack.ml.action;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.Action;
|
||||||
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.action.ActionRequest;
|
||||||
|
import org.elasticsearch.action.ActionRequestBuilder;
|
||||||
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
|
import org.elasticsearch.action.support.ActionFilters;
|
||||||
|
import org.elasticsearch.action.support.HandledTransportAction;
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
|
import org.elasticsearch.client.ElasticsearchClient;
|
||||||
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
import org.elasticsearch.xpack.ml.job.config.Job;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ValidateJobConfigAction
|
||||||
|
extends Action<ValidateJobConfigAction.Request, ValidateJobConfigAction.Response, ValidateJobConfigAction.RequestBuilder> {
|
||||||
|
|
||||||
|
public static final ValidateJobConfigAction INSTANCE = new ValidateJobConfigAction();
|
||||||
|
public static final String NAME = "cluster:admin/ml/anomaly_detectors/validate";
|
||||||
|
|
||||||
|
protected ValidateJobConfigAction() {
|
||||||
|
super(NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||||
|
return new RequestBuilder(client, INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response newResponse() {
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder> {
|
||||||
|
|
||||||
|
protected RequestBuilder(ElasticsearchClient client, ValidateJobConfigAction action) {
|
||||||
|
super(client, action, new Request());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Request extends ActionRequest implements ToXContent {
|
||||||
|
|
||||||
|
private Job job;
|
||||||
|
|
||||||
|
public static Request parseRequest(XContentParser parser) {
|
||||||
|
Job.Builder job = Job.PARSER.apply(parser, null);
|
||||||
|
// When jobs are PUT their ID must be supplied in the URL - assume this will
|
||||||
|
// be valid unless an invalid job ID is specified in the JSON to be validated
|
||||||
|
return new Request(job.build(true, (job.getId() != null) ? job.getId() : "ok"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Request() {
|
||||||
|
this.job = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request(Job job) {
|
||||||
|
this.job = job;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Job getJob() {
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionRequestValidationException validate() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
job.writeTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
|
super.readFrom(in);
|
||||||
|
job = new Job(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
job.toXContent(builder, params);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Request other = (Request) obj;
|
||||||
|
return Objects.equals(job, other.job);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response extends AcknowledgedResponse {
|
||||||
|
|
||||||
|
public Response() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response(boolean acknowledged) {
|
||||||
|
super(acknowledged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
|
super.readFrom(in);
|
||||||
|
readAcknowledged(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
writeAcknowledged(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TransportAction extends HandledTransportAction<Request, Response> {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TransportAction(Settings settings, TransportService transportService, ClusterService clusterService, ThreadPool threadPool,
|
||||||
|
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
|
||||||
|
super(settings, ValidateJobConfigAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||||
|
Request::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doExecute(Request request, ActionListener<Response> listener) {
|
||||||
|
listener.onResponse(new Response(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,6 +71,9 @@ public class Job extends AbstractDiffable<Job> implements Writeable, ToXContent
|
||||||
|
|
||||||
public static final ObjectParser<Builder, Void> PARSER = new ObjectParser<>("job_details", Builder::new);
|
public static final ObjectParser<Builder, Void> PARSER = new ObjectParser<>("job_details", Builder::new);
|
||||||
|
|
||||||
|
public static final int MAX_JOB_ID_LENGTH = 64;
|
||||||
|
public static final long MIN_BACKGROUND_PERSIST_INTERVAL = 3600;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
PARSER.declareString(Builder::setId, ID);
|
PARSER.declareString(Builder::setId, ID);
|
||||||
PARSER.declareStringOrNull(Builder::setDescription, DESCRIPTION);
|
PARSER.declareStringOrNull(Builder::setDescription, DESCRIPTION);
|
||||||
|
@ -140,6 +143,30 @@ public class Job extends AbstractDiffable<Job> implements Writeable, ToXContent
|
||||||
ModelDebugConfig modelDebugConfig, IgnoreDowntime ignoreDowntime,
|
ModelDebugConfig modelDebugConfig, IgnoreDowntime ignoreDowntime,
|
||||||
Long renormalizationWindowDays, Long backgroundPersistInterval, Long modelSnapshotRetentionDays, Long resultsRetentionDays,
|
Long renormalizationWindowDays, Long backgroundPersistInterval, Long modelSnapshotRetentionDays, Long resultsRetentionDays,
|
||||||
Map<String, Object> customSettings, String modelSnapshotId, String indexName) {
|
Map<String, Object> customSettings, String modelSnapshotId, String indexName) {
|
||||||
|
|
||||||
|
if (analysisConfig == null) {
|
||||||
|
throw new IllegalArgumentException(Messages.getMessage(Messages.JOB_CONFIG_MISSING_ANALYSISCONFIG));
|
||||||
|
}
|
||||||
|
|
||||||
|
checkValueNotLessThan(0, "timeout", timeout);
|
||||||
|
checkValueNotLessThan(0, "renormalizationWindowDays", renormalizationWindowDays);
|
||||||
|
checkValueNotLessThan(MIN_BACKGROUND_PERSIST_INTERVAL, "backgroundPersistInterval", backgroundPersistInterval);
|
||||||
|
checkValueNotLessThan(0, "modelSnapshotRetentionDays", modelSnapshotRetentionDays);
|
||||||
|
checkValueNotLessThan(0, "resultsRetentionDays", resultsRetentionDays);
|
||||||
|
|
||||||
|
if (!MlStrings.isValidId(jobId)) {
|
||||||
|
throw new IllegalArgumentException(Messages.getMessage(Messages.INVALID_ID, ID.getPreferredName(), jobId));
|
||||||
|
}
|
||||||
|
if (jobId.length() > MAX_JOB_ID_LENGTH) {
|
||||||
|
throw new IllegalArgumentException(Messages.getMessage(Messages.JOB_CONFIG_ID_TOO_LONG, MAX_JOB_ID_LENGTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Strings.isNullOrEmpty(indexName)) {
|
||||||
|
indexName = jobId;
|
||||||
|
} else if (!MlStrings.isValidId(indexName)) {
|
||||||
|
throw new IllegalArgumentException(Messages.getMessage(Messages.INVALID_ID, INDEX_NAME.getPreferredName()));
|
||||||
|
}
|
||||||
|
|
||||||
this.jobId = jobId;
|
this.jobId = jobId;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.createTime = createTime;
|
this.createTime = createTime;
|
||||||
|
@ -496,10 +523,14 @@ public class Job extends AbstractDiffable<Job> implements Writeable, ToXContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void checkValueNotLessThan(long minVal, String name, Long value) {
|
||||||
|
if (value != null && value < minVal) {
|
||||||
|
throw new IllegalArgumentException(Messages.getMessage(Messages.JOB_CONFIG_FIELD_VALUE_TOO_LOW, name, minVal, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
public static final int MAX_JOB_ID_LENGTH = 64;
|
|
||||||
public static final long MIN_BACKGROUND_PERSIST_INTERVAL = 3600;
|
|
||||||
public static final long DEFAULT_TIMEOUT = 600;
|
public static final long DEFAULT_TIMEOUT = 600;
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
@ -641,15 +672,6 @@ public class Job extends AbstractDiffable<Job> implements Writeable, ToXContent
|
||||||
}
|
}
|
||||||
|
|
||||||
public Job build(boolean fromApi, String urlJobId) {
|
public Job build(boolean fromApi, String urlJobId) {
|
||||||
if (analysisConfig == null) {
|
|
||||||
throw new IllegalArgumentException(Messages.getMessage(Messages.JOB_CONFIG_MISSING_ANALYSISCONFIG));
|
|
||||||
}
|
|
||||||
|
|
||||||
checkValueNotLessThan(0, "timeout", timeout);
|
|
||||||
checkValueNotLessThan(0, "renormalizationWindowDays", renormalizationWindowDays);
|
|
||||||
checkValueNotLessThan(MIN_BACKGROUND_PERSIST_INTERVAL, "backgroundPersistInterval", backgroundPersistInterval);
|
|
||||||
checkValueNotLessThan(0, "modelSnapshotRetentionDays", modelSnapshotRetentionDays);
|
|
||||||
checkValueNotLessThan(0, "resultsRetentionDays", resultsRetentionDays);
|
|
||||||
|
|
||||||
Date createTime;
|
Date createTime;
|
||||||
Date finishedTime;
|
Date finishedTime;
|
||||||
|
@ -672,19 +694,6 @@ public class Job extends AbstractDiffable<Job> implements Writeable, ToXContent
|
||||||
modelSnapshotId = this.modelSnapshotId;
|
modelSnapshotId = this.modelSnapshotId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MlStrings.isValidId(id)) {
|
|
||||||
throw new IllegalArgumentException(Messages.getMessage(Messages.INVALID_ID, ID.getPreferredName(), id));
|
|
||||||
}
|
|
||||||
if (id.length() > MAX_JOB_ID_LENGTH) {
|
|
||||||
throw new IllegalArgumentException(Messages.getMessage(Messages.JOB_CONFIG_ID_TOO_LONG, MAX_JOB_ID_LENGTH));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(indexName)) {
|
|
||||||
indexName = id;
|
|
||||||
} else if (!MlStrings.isValidId(indexName)) {
|
|
||||||
throw new IllegalArgumentException(Messages.getMessage(Messages.INVALID_ID, INDEX_NAME.getPreferredName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Job(
|
return new Job(
|
||||||
id, description, createTime, finishedTime, lastDataTime, timeout, analysisConfig, analysisLimits,
|
id, description, createTime, finishedTime, lastDataTime, timeout, analysisConfig, analysisLimits,
|
||||||
dataDescription, modelDebugConfig, ignoreDowntime, renormalizationWindowDays,
|
dataDescription, modelDebugConfig, ignoreDowntime, renormalizationWindowDays,
|
||||||
|
@ -692,11 +701,5 @@ public class Job extends AbstractDiffable<Job> implements Writeable, ToXContent
|
||||||
indexName
|
indexName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkValueNotLessThan(long minVal, String name, Long value) {
|
|
||||||
if (value != null && value < minVal) {
|
|
||||||
throw new IllegalArgumentException(Messages.getMessage(Messages.JOB_CONFIG_FIELD_VALUE_TOO_LOW, name, minVal, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ public class RestValidateDetectorAction extends BaseRestHandler {
|
||||||
|
|
||||||
public RestValidateDetectorAction(Settings settings, RestController controller) {
|
public RestValidateDetectorAction(Settings settings, RestController controller) {
|
||||||
super(settings);
|
super(settings);
|
||||||
controller.registerHandler(RestRequest.Method.POST, MlPlugin.BASE_PATH + "_validate/detector", this);
|
controller.registerHandler(RestRequest.Method.POST, MlPlugin.BASE_PATH + "anomaly_detectors/_validate/detector", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* 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.xpack.ml.rest.validate;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.node.NodeClient;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.rest.BaseRestHandler;
|
||||||
|
import org.elasticsearch.rest.RestController;
|
||||||
|
import org.elasticsearch.rest.RestRequest;
|
||||||
|
import org.elasticsearch.rest.action.AcknowledgedRestListener;
|
||||||
|
import org.elasticsearch.xpack.ml.MlPlugin;
|
||||||
|
import org.elasticsearch.xpack.ml.action.ValidateJobConfigAction;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class RestValidateJobConfigAction extends BaseRestHandler {
|
||||||
|
|
||||||
|
public RestValidateJobConfigAction(Settings settings, RestController controller) {
|
||||||
|
super(settings);
|
||||||
|
controller.registerHandler(RestRequest.Method.POST, MlPlugin.BASE_PATH + "anomaly_detectors/_validate", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
|
||||||
|
XContentParser parser = restRequest.contentOrSourceParamParser();
|
||||||
|
ValidateJobConfigAction.Request validateConfigRequest = ValidateJobConfigAction.Request.parseRequest(parser);
|
||||||
|
return channel ->
|
||||||
|
client.execute(ValidateJobConfigAction.INSTANCE, validateConfigRequest, new AcknowledgedRestListener<>(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -31,8 +31,8 @@ public class GetJobsActionResponseTests extends AbstractStreamableTestCase<GetJo
|
||||||
int listSize = randomInt(10);
|
int listSize = randomInt(10);
|
||||||
List<Job> jobList = new ArrayList<>(listSize);
|
List<Job> jobList = new ArrayList<>(listSize);
|
||||||
for (int j = 0; j < listSize; j++) {
|
for (int j = 0; j < listSize; j++) {
|
||||||
String jobId = randomAsciiOfLength(10);
|
String jobId = "job" + j;
|
||||||
String description = randomBoolean() ? randomAsciiOfLength(10) : null;
|
String description = randomBoolean() ? randomAsciiOfLength(100) : null;
|
||||||
Date createTime = new Date(randomNonNegativeLong());
|
Date createTime = new Date(randomNonNegativeLong());
|
||||||
Date finishedTime = randomBoolean() ? new Date(randomNonNegativeLong()) : null;
|
Date finishedTime = randomBoolean() ? new Date(randomNonNegativeLong()) : null;
|
||||||
Date lastDataTime = randomBoolean() ? new Date(randomNonNegativeLong()) : null;
|
Date lastDataTime = randomBoolean() ? new Date(randomNonNegativeLong()) : null;
|
||||||
|
@ -43,14 +43,14 @@ public class GetJobsActionResponseTests extends AbstractStreamableTestCase<GetJo
|
||||||
DataDescription dataDescription = randomBoolean() ? new DataDescription.Builder().build() : null;
|
DataDescription dataDescription = randomBoolean() ? new DataDescription.Builder().build() : null;
|
||||||
ModelDebugConfig modelDebugConfig = randomBoolean() ? new ModelDebugConfig(randomDouble(), randomAsciiOfLength(10)) : null;
|
ModelDebugConfig modelDebugConfig = randomBoolean() ? new ModelDebugConfig(randomDouble(), randomAsciiOfLength(10)) : null;
|
||||||
IgnoreDowntime ignoreDowntime = randomFrom(IgnoreDowntime.values());
|
IgnoreDowntime ignoreDowntime = randomFrom(IgnoreDowntime.values());
|
||||||
Long normalizationWindowDays = randomBoolean() ? randomLong() : null;
|
Long normalizationWindowDays = randomBoolean() ? Long.valueOf(randomIntBetween(0, 365)) : null;
|
||||||
Long backgroundPersistInterval = randomBoolean() ? randomLong() : null;
|
Long backgroundPersistInterval = randomBoolean() ? Long.valueOf(randomIntBetween(3600, 86400)) : null;
|
||||||
Long modelSnapshotRetentionDays = randomBoolean() ? randomLong() : null;
|
Long modelSnapshotRetentionDays = randomBoolean() ? Long.valueOf(randomIntBetween(0, 365)) : null;
|
||||||
Long resultsRetentionDays = randomBoolean() ? randomLong() : null;
|
Long resultsRetentionDays = randomBoolean() ? Long.valueOf(randomIntBetween(0, 365)) : null;
|
||||||
Map<String, Object> customConfig = randomBoolean() ? Collections.singletonMap(randomAsciiOfLength(10), randomAsciiOfLength(10))
|
Map<String, Object> customConfig = randomBoolean() ? Collections.singletonMap(randomAsciiOfLength(10), randomAsciiOfLength(10))
|
||||||
: null;
|
: null;
|
||||||
String modelSnapshotId = randomBoolean() ? randomAsciiOfLength(10) : null;
|
String modelSnapshotId = randomBoolean() ? randomAsciiOfLength(10) : null;
|
||||||
String indexName = randomAsciiOfLength(10);
|
String indexName = randomBoolean() ? "index" + j : null;
|
||||||
Job job = new Job(jobId, description, createTime, finishedTime, lastDataTime,
|
Job job = new Job(jobId, description, createTime, finishedTime, lastDataTime,
|
||||||
timeout, analysisConfig, analysisLimits, dataDescription,
|
timeout, analysisConfig, analysisLimits, dataDescription,
|
||||||
modelDebugConfig, ignoreDowntime, normalizationWindowDays, backgroundPersistInterval,
|
modelDebugConfig, ignoreDowntime, normalizationWindowDays, backgroundPersistInterval,
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* 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.xpack.ml.action;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.xpack.ml.action.ValidateJobConfigAction.Request;
|
||||||
|
import org.elasticsearch.xpack.ml.job.config.AnalysisConfig;
|
||||||
|
import org.elasticsearch.xpack.ml.job.config.AnalysisLimits;
|
||||||
|
import org.elasticsearch.xpack.ml.job.config.DataDescription;
|
||||||
|
import org.elasticsearch.xpack.ml.job.config.Detector;
|
||||||
|
import org.elasticsearch.xpack.ml.job.config.Job;
|
||||||
|
import org.elasticsearch.xpack.ml.support.AbstractStreamableXContentTestCase;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ValidateJobConfigActionRequestTests extends AbstractStreamableXContentTestCase<ValidateJobConfigAction.Request> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Request createTestInstance() {
|
||||||
|
List<Detector> detectors = new ArrayList<>();
|
||||||
|
detectors.add(new Detector.Builder(randomFrom(Detector.FIELD_NAME_FUNCTIONS), randomAsciiOfLengthBetween(1, 20)).build());
|
||||||
|
detectors.add(new Detector.Builder(randomFrom(Detector.COUNT_WITHOUT_FIELD_FUNCTIONS), null).build());
|
||||||
|
AnalysisConfig.Builder analysisConfigBuilder = new AnalysisConfig.Builder(detectors);
|
||||||
|
analysisConfigBuilder.setBucketSpan(randomIntBetween(60, 86400));
|
||||||
|
if (randomBoolean()) {
|
||||||
|
analysisConfigBuilder.setLatency(randomIntBetween(0, 12));
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
analysisConfigBuilder.setCategorizationFieldName(randomAsciiOfLengthBetween(1, 20));
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
analysisConfigBuilder.setSummaryCountFieldName(randomAsciiOfLengthBetween(1, 20));
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
List<String> influencers = new ArrayList<>();
|
||||||
|
for (int i = randomIntBetween(1, 5); i > 0; --i) {
|
||||||
|
influencers.add(randomAsciiOfLengthBetween(1, 20));
|
||||||
|
}
|
||||||
|
analysisConfigBuilder.setInfluencers(influencers);
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
analysisConfigBuilder.setOverlappingBuckets(randomBoolean());
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
analysisConfigBuilder.setMultivariateByFields(randomBoolean());
|
||||||
|
}
|
||||||
|
Job.Builder job = new Job.Builder("ok");
|
||||||
|
job.setAnalysisConfig(analysisConfigBuilder);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
DataDescription.Builder dataDescription = new DataDescription.Builder();
|
||||||
|
if (randomBoolean()) {
|
||||||
|
dataDescription.setFormat(DataDescription.DataFormat.DELIMITED);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
dataDescription.setFieldDelimiter(';');
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
dataDescription.setQuoteCharacter('\'');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dataDescription.setFormat(DataDescription.DataFormat.JSON);
|
||||||
|
}
|
||||||
|
dataDescription.setTimeField(randomAsciiOfLengthBetween(1, 20));
|
||||||
|
if (randomBoolean()) {
|
||||||
|
dataDescription.setTimeFormat("yyyy-MM-dd HH:mm:ssX");
|
||||||
|
}
|
||||||
|
job.setDataDescription(dataDescription);
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
job.setAnalysisLimits(new AnalysisLimits(randomNonNegativeLong(), randomNonNegativeLong()));
|
||||||
|
}
|
||||||
|
return new Request(job.build(true, "ok"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Request createBlankInstance() {
|
||||||
|
return new Request();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Request parseInstance(XContentParser parser) {
|
||||||
|
return Request.parseRequest(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"xpack.ml.validate": {
|
||||||
|
"methods": [ "POST" ],
|
||||||
|
"url": {
|
||||||
|
"path": "/_xpack/ml/anomaly_detectors/_validate",
|
||||||
|
"paths": [ "/_xpack/ml/anomaly_detectors/_validate" ],
|
||||||
|
"params": {}
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"description" : "The job config",
|
||||||
|
"required" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,8 @@
|
||||||
"xpack.ml.validate_detector": {
|
"xpack.ml.validate_detector": {
|
||||||
"methods": [ "POST" ],
|
"methods": [ "POST" ],
|
||||||
"url": {
|
"url": {
|
||||||
"path": "/_xpack/ml/_validate/detector",
|
"path": "/_xpack/ml/anomaly_detectors/_validate/detector",
|
||||||
"paths": [ "/_xpack/ml/_validate/detector" ],
|
"paths": [ "/_xpack/ml/anomaly_detectors/_validate/detector" ],
|
||||||
"params": {}
|
"params": {}
|
||||||
},
|
},
|
||||||
"body": {
|
"body": {
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
---
|
||||||
|
"Test valid job config":
|
||||||
|
- do:
|
||||||
|
xpack.ml.validate:
|
||||||
|
body: >
|
||||||
|
{
|
||||||
|
"analysis_config": {
|
||||||
|
"bucket_span": 3600,
|
||||||
|
"detectors": [{"function": "metric", "field_name": "responsetime", "by_field_name": "airline"}]
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"format": "delimited",
|
||||||
|
"field_delimiter": ",",
|
||||||
|
"time_field": "time",
|
||||||
|
"time_format": "yyyy-MM-dd HH:mm:ssX"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- match: { acknowledged: true }
|
||||||
|
|
||||||
|
---
|
||||||
|
"Test invalid job config":
|
||||||
|
- do:
|
||||||
|
catch: /.data_description. failed to parse field .format./
|
||||||
|
xpack.ml.validate:
|
||||||
|
body: >
|
||||||
|
{
|
||||||
|
"analysis_config": {
|
||||||
|
"bucket_span": 3600,
|
||||||
|
"detectors": [{"function": "metric", "field_name": "responsetime", "by_field_name": "airline"}]
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"format": "wrong",
|
||||||
|
"field_delimiter": ",",
|
||||||
|
"time_field": "time",
|
||||||
|
"time_format": "yyyy-MM-dd HH:mm:ssX"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
"Test valid job config with job ID":
|
||||||
|
- do:
|
||||||
|
xpack.ml.validate:
|
||||||
|
body: >
|
||||||
|
{
|
||||||
|
"job_id": "farequote",
|
||||||
|
"analysis_config": {
|
||||||
|
"bucket_span": 3600,
|
||||||
|
"detectors": [{"function": "metric", "field_name": "responsetime", "by_field_name": "airline"}]
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"format": "delimited",
|
||||||
|
"field_delimiter": ",",
|
||||||
|
"time_field": "time",
|
||||||
|
"time_format": "yyyy-MM-dd HH:mm:ssX"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- match: { acknowledged: true }
|
||||||
|
|
||||||
|
---
|
||||||
|
"Test job config that's invalid only because of the job ID":
|
||||||
|
- do:
|
||||||
|
catch: /Invalid job_id; '_' must be lowercase alphanumeric, may contain hyphens or underscores, may not start with underscore/
|
||||||
|
xpack.ml.validate:
|
||||||
|
body: >
|
||||||
|
{
|
||||||
|
"job_id": "_",
|
||||||
|
"analysis_config": {
|
||||||
|
"bucket_span": 3600,
|
||||||
|
"detectors": [{"function": "metric", "field_name": "responsetime", "by_field_name": "airline"}]
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"format": "delimited",
|
||||||
|
"field_delimiter": ",",
|
||||||
|
"time_field": "time",
|
||||||
|
"time_format": "yyyy-MM-dd HH:mm:ssX"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue