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:
David Roberts 2017-01-30 17:10:22 +00:00 committed by GitHub
parent 4eab74ce29
commit ab957b6d91
11 changed files with 426 additions and 41 deletions

View File

@ -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),

View File

@ -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);

View File

@ -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));
}
}
}

View File

@ -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));
}
}
} }
} }

View File

@ -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

View File

@ -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));
}
}

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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
}
}
}

View File

@ -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": {

View File

@ -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"
}
}