Merge branch 'master' into feature/sql_2

Original commit: elastic/x-pack-elasticsearch@82985d6481
This commit is contained in:
Nik Everett 2017-12-19 13:43:49 -05:00
commit 7e11a1b388
48 changed files with 1261 additions and 298 deletions

View File

@ -33,6 +33,7 @@ subprojects {
*/
Object esCheckoutPath = """${->
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
esCheckoutDir.mkdirs()
NodeInfo.getShortPathName(esCheckoutDir.toString())
} else {
esCheckoutDir.toString()
@ -41,6 +42,7 @@ subprojects {
File xpackCheckoutDir = file("${esCheckoutDir}-extra/x-pack-elasticsearch")
Object xpackCheckoutPath = """${->
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
xpackCheckoutDir.mkdirs()
NodeInfo.getShortPathName(xpackCheckoutDir.toString())
} else {
xpackCheckoutDir.toString()

View File

@ -31,7 +31,6 @@ import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@ -53,6 +52,7 @@ import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.ml.action.CloseJobAction;
import org.elasticsearch.xpack.ml.action.DeleteCalendarAction;
import org.elasticsearch.xpack.ml.action.UpdateCalendarJobAction;
import org.elasticsearch.xpack.ml.action.DeleteDatafeedAction;
import org.elasticsearch.xpack.ml.action.DeleteExpiredDataAction;
import org.elasticsearch.xpack.ml.action.DeleteFilterAction;
@ -118,8 +118,10 @@ import org.elasticsearch.xpack.ml.notifications.AuditMessage;
import org.elasticsearch.xpack.ml.notifications.Auditor;
import org.elasticsearch.xpack.ml.rest.RestDeleteExpiredDataAction;
import org.elasticsearch.xpack.ml.rest.calendar.RestDeleteCalendarAction;
import org.elasticsearch.xpack.ml.rest.calendar.RestDeleteCalendarJobAction;
import org.elasticsearch.xpack.ml.rest.calendar.RestGetCalendarsAction;
import org.elasticsearch.xpack.ml.rest.calendar.RestPutCalendarAction;
import org.elasticsearch.xpack.ml.rest.calendar.RestPutCalendarJobAction;
import org.elasticsearch.xpack.ml.rest.datafeeds.RestDeleteDatafeedAction;
import org.elasticsearch.xpack.ml.rest.datafeeds.RestGetDatafeedStatsAction;
import org.elasticsearch.xpack.ml.rest.datafeeds.RestGetDatafeedsAction;
@ -467,7 +469,9 @@ public class MachineLearning implements ActionPlugin {
new RestForecastJobAction(settings, restController),
new RestGetCalendarsAction(settings, restController),
new RestPutCalendarAction(settings, restController),
new RestDeleteCalendarAction(settings, restController)
new RestDeleteCalendarAction(settings, restController),
new RestDeleteCalendarJobAction(settings, restController),
new RestPutCalendarJobAction(settings, restController)
);
}
@ -516,7 +520,8 @@ public class MachineLearning implements ActionPlugin {
new ActionHandler<>(ForecastJobAction.INSTANCE, ForecastJobAction.TransportAction.class),
new ActionHandler<>(GetCalendarsAction.INSTANCE, GetCalendarsAction.TransportAction.class),
new ActionHandler<>(PutCalendarAction.INSTANCE, PutCalendarAction.TransportAction.class),
new ActionHandler<>(DeleteCalendarAction.INSTANCE, DeleteCalendarAction.TransportAction.class)
new ActionHandler<>(DeleteCalendarAction.INSTANCE, DeleteCalendarAction.TransportAction.class),
new ActionHandler<>(UpdateCalendarJobAction.INSTANCE, UpdateCalendarJobAction.TransportAction.class)
);
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.ml;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.calendars.SpecialEvent;
import org.elasticsearch.xpack.ml.job.persistence.ElasticsearchMappings;
@ -32,6 +33,12 @@ public final class MlMetaIndex {
builder.startObject(TYPE);
ElasticsearchMappings.addDefaultMapping(builder);
builder.startObject(ElasticsearchMappings.PROPERTIES)
.startObject(Calendar.ID.getPreferredName())
.field(ElasticsearchMappings.TYPE, ElasticsearchMappings.KEYWORD)
.endObject()
.startObject(Calendar.JOB_IDS.getPreferredName())
.field(ElasticsearchMappings.TYPE, ElasticsearchMappings.KEYWORD)
.endObject()
.startObject(SpecialEvent.START_TIME.getPreferredName())
.field(ElasticsearchMappings.TYPE, ElasticsearchMappings.DATE)
.endObject()

View File

@ -81,6 +81,10 @@ public class MlMetadata implements MetaData.Custom {
return jobs;
}
public boolean isGroupOrJob(String id) {
return groupOrJobLookup.isGroupOrJob(id);
}
public Set<String> expandJobIds(String expression, boolean allowNoJobs) {
return groupOrJobLookup.expandJobIds(expression, allowNoJobs);
}

View File

@ -42,6 +42,7 @@ import org.elasticsearch.xpack.ml.MlMetaIndex;
import org.elasticsearch.xpack.ml.action.util.PageParams;
import org.elasticsearch.xpack.ml.action.util.QueryPage;
import org.elasticsearch.xpack.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.job.persistence.CalendarQueryBuilder;
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
import java.io.IOException;
@ -212,16 +213,16 @@ public class GetCalendarsAction extends Action<GetCalendarsAction.Request, GetCa
public static class TransportAction extends HandledTransportAction<Request, Response> {
private final Client client;
private final JobProvider jobProvider;
@Inject
public TransportAction(Settings settings, ThreadPool threadPool,
TransportService transportService, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver,
Client client) {
JobProvider jobProvider) {
super(settings, NAME, threadPool, transportService, actionFilters,
indexNameExpressionResolver, Request::new);
this.client = client;
this.jobProvider = jobProvider;
}
@Override
@ -239,76 +240,24 @@ public class GetCalendarsAction extends Action<GetCalendarsAction.Request, GetCa
}
private void getCalendar(String calendarId, ActionListener<Response> listener) {
GetRequest getRequest = new GetRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, Calendar.documentId(calendarId));
executeAsyncWithOrigin(client, ML_ORIGIN, GetAction.INSTANCE, getRequest, new ActionListener<GetResponse>() {
@Override
public void onResponse(GetResponse getDocResponse) {
try {
QueryPage<Calendar> calendars;
if (getDocResponse.isExists()) {
BytesReference docSource = getDocResponse.getSourceAsBytesRef();
try (XContentParser parser =
XContentFactory.xContent(docSource).createParser(NamedXContentRegistry.EMPTY, docSource)) {
Calendar calendar = Calendar.PARSER.apply(parser, null).build();
calendars = new QueryPage<>(Collections.singletonList(calendar), 1, Calendar.RESULTS_FIELD);
Response response = new Response(calendars);
listener.onResponse(response);
}
} else {
this.onFailure(QueryPage.emptyQueryPage(Calendar.RESULTS_FIELD));
}
} catch (Exception e) {
this.onFailure(e);
}
}
@Override
public void onFailure(Exception e) {
listener.onFailure(e);
}
});
jobProvider.calendar(calendarId, ActionListener.wrap(
calendar -> {
QueryPage<Calendar> page = new QueryPage<>(Collections.singletonList(calendar), 1, Calendar.RESULTS_FIELD);
listener.onResponse(new Response(page));
},
listener::onFailure
));
}
private void getCalendars(PageParams pageParams, ActionListener<Response> listener) {
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
.from(pageParams.getFrom())
.size(pageParams.getSize())
.sort(Calendar.ID.getPreferredName())
.query(QueryBuilders.termQuery(Calendar.TYPE.getPreferredName(), Calendar.CALENDAR_TYPE));
SearchRequest searchRequest = new SearchRequest(MlMetaIndex.INDEX_NAME)
.indicesOptions(JobProvider.addIgnoreUnavailable(SearchRequest.DEFAULT_INDICES_OPTIONS))
.source(sourceBuilder);
executeAsyncWithOrigin(client.threadPool().getThreadContext(), ML_ORIGIN, searchRequest, new ActionListener<SearchResponse>() {
@Override
public void onResponse(SearchResponse response) {
List<Calendar> docs = new ArrayList<>();
for (SearchHit hit : response.getHits().getHits()) {
BytesReference docSource = hit.getSourceRef();
try (XContentParser parser = XContentFactory.xContent(docSource).createParser(
NamedXContentRegistry.EMPTY, docSource)) {
docs.add(Calendar.PARSER.apply(parser, null).build());
} catch (IOException e) {
this.onFailure(e);
}
}
Response getResponse = new Response(
new QueryPage<>(docs, docs.size(), Calendar.RESULTS_FIELD));
listener.onResponse(getResponse);
}
@Override
public void onFailure(Exception e) {
listener.onFailure(e);
}
},
client::search);
CalendarQueryBuilder query = new CalendarQueryBuilder().pageParams(pageParams).sort(true);
jobProvider.calendars(query, ActionListener.wrap(
calendars -> {
listener.onResponse(new Response(calendars));
},
listener::onFailure
));
}
}
}

View File

@ -20,7 +20,9 @@ import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
@ -34,13 +36,18 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.ml.MlMetaIndex;
import org.elasticsearch.xpack.ml.MlMetadata;
import org.elasticsearch.xpack.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.job.messages.Messages;
import org.elasticsearch.xpack.ml.utils.ExceptionsHelper;
import org.elasticsearch.xpack.watcher.support.Exceptions;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.xpack.ClientHelper.ML_ORIGIN;
@ -162,36 +169,62 @@ public class PutCalendarAction extends Action<PutCalendarAction.Request, PutCale
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
readAcknowledged(in);
calendar = new Calendar(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
writeAcknowledged(out);
calendar.writeTo(out);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return calendar.toXContent(builder, params);
}
@Override
public int hashCode() {
return Objects.hash(isAcknowledged(), calendar);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Response other = (Response) obj;
return Objects.equals(isAcknowledged(), other.isAcknowledged()) && Objects.equals(calendar, other.calendar);
}
}
public static class TransportAction extends HandledTransportAction<Request, Response> {
private final Client client;
private final ClusterService clusterService;
@Inject
public TransportAction(Settings settings, ThreadPool threadPool,
TransportService transportService, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver, Client client) {
IndexNameExpressionResolver indexNameExpressionResolver,
Client client, ClusterService clusterService) {
super(settings, NAME, threadPool, transportService, actionFilters,
indexNameExpressionResolver, Request::new);
this.client = client;
this.clusterService = clusterService;
}
@Override
protected void doExecute(Request request, ActionListener<Response> listener) {
final Calendar calendar = request.getCalendar();
Calendar calendar = request.getCalendar();
checkJobsExist(calendar.getJobIds(), listener::onFailure);
IndexRequest indexRequest = new IndexRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, calendar.documentId());
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
indexRequest.source(calendar.toXContent(builder,
@ -218,5 +251,17 @@ public class PutCalendarAction extends Action<PutCalendarAction.Request, PutCale
}
});
}
private void checkJobsExist(List<String> jobIds, Consumer<Exception> errorHandler) {
ClusterState state = clusterService.state();
MlMetadata mlMetadata = state.getMetaData().custom(MlMetadata.TYPE);
for (String jobId: jobIds) {
Set<String> jobs = mlMetadata.expandJobIds(jobId, true);
if (jobs.isEmpty()) {
errorHandler.accept(ExceptionsHelper.missingJobException(jobId));
return;
}
}
}
}
}

View File

@ -0,0 +1,168 @@
/*
* 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.client.ElasticsearchClient;
import org.elasticsearch.cluster.ClusterState;
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.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.ml.MlMetadata;
import org.elasticsearch.xpack.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
import org.elasticsearch.xpack.ml.utils.ExceptionsHelper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class UpdateCalendarJobAction extends Action<UpdateCalendarJobAction.Request, PutCalendarAction.Response,
UpdateCalendarJobAction.RequestBuilder> {
public static final UpdateCalendarJobAction INSTANCE = new UpdateCalendarJobAction();
public static final String NAME = "cluster:admin/xpack/ml/calendars/jobs/update";
private UpdateCalendarJobAction() {
super(NAME);
}
@Override
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new RequestBuilder(client);
}
@Override
public PutCalendarAction.Response newResponse() {
return new PutCalendarAction.Response();
}
public static class Request extends ActionRequest {
private String calendarId;
private Set<String> jobIdsToAdd;
private Set<String> jobIdsToRemove;
Request() {
}
public Request(String calendarId, Set<String> jobIdsToAdd, Set<String> jobIdsToRemove) {
this.calendarId = ExceptionsHelper.requireNonNull(calendarId, Calendar.ID.getPreferredName());
this.jobIdsToAdd = ExceptionsHelper.requireNonNull(jobIdsToAdd, "job_ids_to_add");
this.jobIdsToRemove = ExceptionsHelper.requireNonNull(jobIdsToRemove, "job_ids_to_remove");
}
public String getCalendarId() {
return calendarId;
}
public Set<String> getJobIdsToAdd() {
return jobIdsToAdd;
}
public Set<String> getJobIdsToRemove() {
return jobIdsToRemove;
}
@Override
public ActionRequestValidationException validate() {
return null;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
calendarId = in.readString();
jobIdsToAdd = new HashSet<>(in.readList(StreamInput::readString));
jobIdsToRemove = new HashSet<>(in.readList(StreamInput::readString));
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(calendarId);
out.writeStringList(new ArrayList<>(jobIdsToAdd));
out.writeStringList(new ArrayList<>(jobIdsToRemove));
}
@Override
public int hashCode() {
return Objects.hash(calendarId, jobIdsToAdd, jobIdsToRemove);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Request other = (Request) obj;
return Objects.equals(calendarId, other.calendarId) && Objects.equals(jobIdsToAdd, other.jobIdsToAdd)
&& Objects.equals(jobIdsToRemove, other.jobIdsToRemove);
}
}
public static class RequestBuilder extends ActionRequestBuilder<Request, PutCalendarAction.Response, RequestBuilder> {
public RequestBuilder(ElasticsearchClient client) {
super(client, INSTANCE, new Request());
}
}
public static class TransportAction extends HandledTransportAction<Request, PutCalendarAction.Response> {
private final ClusterService clusterService;
private final JobProvider jobProvider;
@Inject
public TransportAction(Settings settings, ThreadPool threadPool,
TransportService transportService, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver,
ClusterService clusterService, JobProvider jobProvider) {
super(settings, NAME, threadPool, transportService, actionFilters,
indexNameExpressionResolver, Request::new);
this.clusterService = clusterService;
this.jobProvider = jobProvider;
}
@Override
protected void doExecute(Request request, ActionListener<PutCalendarAction.Response> listener) {
ClusterState state = clusterService.state();
MlMetadata mlMetadata = state.getMetaData().custom(MlMetadata.TYPE);
for (String jobToAdd: request.getJobIdsToAdd()) {
if (mlMetadata.isGroupOrJob(jobToAdd) == false) {
listener.onFailure(ExceptionsHelper.missingJobException(jobToAdd));
return;
}
}
for (String jobToRemove: request.getJobIdsToRemove()) {
if (mlMetadata.isGroupOrJob(jobToRemove) == false) {
listener.onFailure(ExceptionsHelper.missingJobException(jobToRemove));
return;
}
}
jobProvider.updateCalendar(request.getCalendarId(), request.getJobIdsToAdd(), request.getJobIdsToRemove(),
c -> listener.onResponse(new PutCalendarAction.Response(c)), listener::onFailure);
}
}
}

View File

@ -13,10 +13,8 @@ import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.ml.MlMetaIndex;
import org.elasticsearch.xpack.ml.job.config.MlFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -70,7 +68,7 @@ public class Calendar implements ToXContentObject, Writeable {
}
public List<String> getJobIds() {
return new ArrayList<>(jobIds);
return Collections.unmodifiableList(jobIds);
}
@Override

View File

@ -150,7 +150,7 @@ public class SpecialEvent implements ToXContentObject, Writeable {
conditions.add(RuleCondition.createTime(Operator.LT, bucketEndTime));
DetectionRule.Builder builder = new DetectionRule.Builder(conditions);
builder.setRuleAction(RuleAction.SKIP_SAMPLING_AND_FILTER_RESULTS);
builder.setActions(RuleAction.FILTER_RESULTS, RuleAction.SKIP_SAMPLING);
builder.setConditionsConnective(Connective.AND);
return builder.build();
}

View File

@ -313,7 +313,7 @@ public class JobManager extends AbstractComponent {
String jobId = request.getJobId();
logger.debug("Deleting job '" + jobId + "'");
// Step 3. When the job has been removed from the cluster state, return a response
// Step 4. When the job has been removed from the cluster state, return a response
// -------
CheckedConsumer<Boolean, Exception> apiResponseHandler = jobDeleted -> {
if (jobDeleted) {
@ -325,7 +325,7 @@ public class JobManager extends AbstractComponent {
}
};
// Step 2. When the physical storage has been deleted, remove from Cluster State
// Step 3. When the physical storage has been deleted, remove from Cluster State
// -------
CheckedConsumer<Boolean, Exception> deleteJobStateHandler = response -> clusterService.submitStateUpdateTask("delete-job-" + jobId,
new AckedClusterStateUpdateTask<Boolean>(request, ActionListener.wrap(apiResponseHandler, actionListener::onFailure)) {
@ -351,11 +351,18 @@ public class JobManager extends AbstractComponent {
}
});
// Step 2. Remove the job from any calendars
CheckedConsumer<Boolean, Exception> removeFromCalendarsHandler = response -> {
jobProvider.removeJobFromCalendars(jobId, ActionListener.<Boolean>wrap(deleteJobStateHandler::accept,
actionListener::onFailure ));
};
// Step 1. Delete the physical storage
// This task manages the physical deletion of the job state and results
task.delete(jobId, client, clusterService.state(), deleteJobStateHandler::accept, actionListener::onFailure);
task.delete(jobId, client, clusterService.state(), removeFromCalendarsHandler, actionListener::onFailure);
}
public void revertSnapshot(RevertModelSnapshotAction.Request request, ActionListener<RevertModelSnapshotAction.Response> actionListener,

View File

@ -21,8 +21,10 @@ import org.elasticsearch.xpack.ml.utils.ExceptionsHelper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -32,11 +34,11 @@ import java.util.stream.Collectors;
public class DetectionRule implements ToXContentObject, Writeable {
public static final ParseField DETECTION_RULE_FIELD = new ParseField("detection_rule");
public static final ParseField RULE_ACTION_FIELD = new ParseField("rule_action");
public static final ParseField ACTIONS_FIELD = new ParseField("actions");
public static final ParseField TARGET_FIELD_NAME_FIELD = new ParseField("target_field_name");
public static final ParseField TARGET_FIELD_VALUE_FIELD = new ParseField("target_field_value");
public static final ParseField CONDITIONS_CONNECTIVE_FIELD = new ParseField("conditions_connective");
public static final ParseField RULE_CONDITIONS_FIELD = new ParseField("rule_conditions");
public static final ParseField CONDITIONS_FIELD = new ParseField("conditions");
// These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
public static final ObjectParser<Builder, Void> METADATA_PARSER =
@ -51,12 +53,7 @@ public class DetectionRule implements ToXContentObject, Writeable {
for (MlParserType parserType : MlParserType.values()) {
ObjectParser<Builder, Void> parser = PARSERS.get(parserType);
assert parser != null;
parser.declareField(Builder::setRuleAction, p -> {
if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
return RuleAction.fromString(p.text());
}
throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
}, RULE_ACTION_FIELD, ValueType.STRING);
parser.declareStringArray(Builder::setActions, ACTIONS_FIELD);
parser.declareString(Builder::setTargetFieldName, TARGET_FIELD_NAME_FIELD);
parser.declareString(Builder::setTargetFieldValue, TARGET_FIELD_VALUE_FIELD);
parser.declareField(Builder::setConditionsConnective, p -> {
@ -65,33 +62,38 @@ public class DetectionRule implements ToXContentObject, Writeable {
}
throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
}, CONDITIONS_CONNECTIVE_FIELD, ValueType.STRING);
parser.declareObjectArray(Builder::setRuleConditions, (p, c) ->
RuleCondition.PARSERS.get(parserType).apply(p, c), RULE_CONDITIONS_FIELD);
parser.declareObjectArray(Builder::setConditions, (p, c) ->
RuleCondition.PARSERS.get(parserType).apply(p, c), CONDITIONS_FIELD);
}
}
private final RuleAction ruleAction;
private final EnumSet<RuleAction> actions;
private final String targetFieldName;
private final String targetFieldValue;
private final Connective conditionsConnective;
private final List<RuleCondition> ruleConditions;
private final List<RuleCondition> conditions;
private DetectionRule(RuleAction ruleAction, @Nullable String targetFieldName, @Nullable String targetFieldValue,
Connective conditionsConnective, List<RuleCondition> ruleConditions) {
this.ruleAction = Objects.requireNonNull(ruleAction);
private DetectionRule(EnumSet<RuleAction> actions, @Nullable String targetFieldName, @Nullable String targetFieldValue,
Connective conditionsConnective, List<RuleCondition> conditions) {
this.actions = Objects.requireNonNull(actions);
this.targetFieldName = targetFieldName;
this.targetFieldValue = targetFieldValue;
this.conditionsConnective = Objects.requireNonNull(conditionsConnective);
this.ruleConditions = Collections.unmodifiableList(ruleConditions);
this.conditions = Collections.unmodifiableList(conditions);
}
public DetectionRule(StreamInput in) throws IOException {
ruleAction = RuleAction.readFromStream(in);
int actionsCount = in.readVInt();
actions = EnumSet.noneOf(RuleAction.class);
for (int i = 0; i < actionsCount; ++i) {
actions.add(RuleAction.readFromStream(in));
}
conditionsConnective = Connective.readFromStream(in);
int size = in.readVInt();
ruleConditions = new ArrayList<>(size);
conditions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
ruleConditions.add(new RuleCondition(in));
conditions.add(new RuleCondition(in));
}
targetFieldName = in.readOptionalString();
targetFieldValue = in.readOptionalString();
@ -99,10 +101,14 @@ public class DetectionRule implements ToXContentObject, Writeable {
@Override
public void writeTo(StreamOutput out) throws IOException {
ruleAction.writeTo(out);
out.writeVInt(actions.size());
for (RuleAction action : actions) {
action.writeTo(out);
}
conditionsConnective.writeTo(out);
out.writeVInt(ruleConditions.size());
for (RuleCondition condition : ruleConditions) {
out.writeVInt(conditions.size());
for (RuleCondition condition : conditions) {
condition.writeTo(out);
}
out.writeOptionalString(targetFieldName);
@ -112,9 +118,9 @@ public class DetectionRule implements ToXContentObject, Writeable {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(RULE_ACTION_FIELD.getPreferredName(), ruleAction);
builder.field(ACTIONS_FIELD.getPreferredName(), actions);
builder.field(CONDITIONS_CONNECTIVE_FIELD.getPreferredName(), conditionsConnective);
builder.field(RULE_CONDITIONS_FIELD.getPreferredName(), ruleConditions);
builder.field(CONDITIONS_FIELD.getPreferredName(), conditions);
if (targetFieldName != null) {
builder.field(TARGET_FIELD_NAME_FIELD.getPreferredName(), targetFieldName);
}
@ -125,8 +131,8 @@ public class DetectionRule implements ToXContentObject, Writeable {
return builder;
}
public RuleAction getRuleAction() {
return ruleAction;
public EnumSet<RuleAction> getActions() {
return actions;
}
@Nullable
@ -143,12 +149,12 @@ public class DetectionRule implements ToXContentObject, Writeable {
return conditionsConnective;
}
public List<RuleCondition> getRuleConditions() {
return ruleConditions;
public List<RuleCondition> getConditions() {
return conditions;
}
public Set<String> extractReferencedFilters() {
return ruleConditions.stream().map(RuleCondition::getValueFilter).filter(Objects::nonNull).collect(Collectors.toSet());
return conditions.stream().map(RuleCondition::getFilterId).filter(Objects::nonNull).collect(Collectors.toSet());
}
@Override
@ -162,34 +168,46 @@ public class DetectionRule implements ToXContentObject, Writeable {
}
DetectionRule other = (DetectionRule) obj;
return Objects.equals(ruleAction, other.ruleAction)
return Objects.equals(actions, other.actions)
&& Objects.equals(targetFieldName, other.targetFieldName)
&& Objects.equals(targetFieldValue, other.targetFieldValue)
&& Objects.equals(conditionsConnective, other.conditionsConnective)
&& Objects.equals(ruleConditions, other.ruleConditions);
&& Objects.equals(conditions, other.conditions);
}
@Override
public int hashCode() {
return Objects.hash(ruleAction, targetFieldName, targetFieldValue, conditionsConnective, ruleConditions);
return Objects.hash(actions, targetFieldName, targetFieldValue, conditionsConnective, conditions);
}
public static class Builder {
private RuleAction ruleAction = RuleAction.FILTER_RESULTS;
private EnumSet<RuleAction> actions = EnumSet.of(RuleAction.FILTER_RESULTS);
private String targetFieldName;
private String targetFieldValue;
private Connective conditionsConnective = Connective.OR;
private List<RuleCondition> ruleConditions = Collections.emptyList();
private List<RuleCondition> conditions = Collections.emptyList();
public Builder(List<RuleCondition> ruleConditions) {
this.ruleConditions = ExceptionsHelper.requireNonNull(ruleConditions, RULE_CONDITIONS_FIELD.getPreferredName());
public Builder(List<RuleCondition> conditions) {
this.conditions = ExceptionsHelper.requireNonNull(conditions, CONDITIONS_FIELD.getPreferredName());
}
private Builder() {
}
public Builder setRuleAction(RuleAction ruleAction) {
this.ruleAction = ExceptionsHelper.requireNonNull(ruleAction, RULE_ACTION_FIELD.getPreferredName());
public Builder setActions(List<String> actions) {
this.actions.clear();
actions.stream().map(RuleAction::fromString).forEach(this.actions::add);
return this;
}
public Builder setActions(EnumSet<RuleAction> actions) {
this.actions = Objects.requireNonNull(actions, ACTIONS_FIELD.getPreferredName());
return this;
}
public Builder setActions(RuleAction... actions) {
this.actions.clear();
Arrays.stream(actions).forEach(this.actions::add);
return this;
}
@ -208,8 +226,8 @@ public class DetectionRule implements ToXContentObject, Writeable {
return this;
}
public Builder setRuleConditions(List<RuleCondition> ruleConditions) {
this.ruleConditions = ExceptionsHelper.requireNonNull(ruleConditions, RULE_ACTION_FIELD.getPreferredName());
public Builder setConditions(List<RuleCondition> conditions) {
this.conditions = ExceptionsHelper.requireNonNull(conditions, CONDITIONS_FIELD.getPreferredName());
return this;
}
@ -218,18 +236,18 @@ public class DetectionRule implements ToXContentObject, Writeable {
String msg = Messages.getMessage(Messages.JOB_CONFIG_DETECTION_RULE_MISSING_TARGET_FIELD_NAME, targetFieldValue);
throw ExceptionsHelper.badRequestException(msg);
}
if (ruleConditions == null || ruleConditions.isEmpty()) {
if (conditions == null || conditions.isEmpty()) {
String msg = Messages.getMessage(Messages.JOB_CONFIG_DETECTION_RULE_REQUIRES_AT_LEAST_ONE_CONDITION);
throw ExceptionsHelper.badRequestException(msg);
}
for (RuleCondition condition : ruleConditions) {
if (condition.getConditionType() == RuleConditionType.CATEGORICAL && targetFieldName != null) {
for (RuleCondition condition : conditions) {
if (condition.getType() == RuleConditionType.CATEGORICAL && targetFieldName != null) {
String msg = Messages.getMessage(Messages.JOB_CONFIG_DETECTION_RULE_CONDITION_CATEGORICAL_INVALID_OPTION,
DetectionRule.TARGET_FIELD_NAME_FIELD.getPreferredName());
throw ExceptionsHelper.badRequestException(msg);
}
}
return new DetectionRule(ruleAction, targetFieldName, targetFieldValue, conditionsConnective, ruleConditions);
return new DetectionRule(actions, targetFieldName, targetFieldValue, conditionsConnective, conditions);
}
}
}

View File

@ -83,7 +83,7 @@ public class Detector implements ToXContentObject, Writeable {
public static final ParseField PARTITION_FIELD_NAME_FIELD = new ParseField("partition_field_name");
public static final ParseField USE_NULL_FIELD = new ParseField("use_null");
public static final ParseField EXCLUDE_FREQUENT_FIELD = new ParseField("exclude_frequent");
public static final ParseField DETECTOR_RULES_FIELD = new ParseField("detector_rules");
public static final ParseField RULES_FIELD = new ParseField("rules");
public static final ParseField DETECTOR_INDEX = new ParseField("detector_index");
// These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
@ -110,8 +110,8 @@ public class Detector implements ToXContentObject, Writeable {
}
throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
}, EXCLUDE_FREQUENT_FIELD, ObjectParser.ValueType.STRING);
parser.declareObjectArray(Builder::setDetectorRules, (p, c) ->
DetectionRule.PARSERS.get(parserType).apply(p, c).build(), DETECTOR_RULES_FIELD);
parser.declareObjectArray(Builder::setRules, (p, c) ->
DetectionRule.PARSERS.get(parserType).apply(p, c).build(), RULES_FIELD);
parser.declareInt(Builder::setDetectorIndex, DETECTOR_INDEX);
}
}
@ -329,7 +329,7 @@ public class Detector implements ToXContentObject, Writeable {
private final String partitionFieldName;
private final boolean useNull;
private final ExcludeFrequent excludeFrequent;
private final List<DetectionRule> detectorRules;
private final List<DetectionRule> rules;
private final int detectorIndex;
public Detector(StreamInput in) throws IOException {
@ -341,7 +341,7 @@ public class Detector implements ToXContentObject, Writeable {
partitionFieldName = in.readOptionalString();
useNull = in.readBoolean();
excludeFrequent = in.readBoolean() ? ExcludeFrequent.readFromStream(in) : null;
detectorRules = in.readList(DetectionRule::new);
rules = in.readList(DetectionRule::new);
if (in.getVersion().onOrAfter(Version.V_5_5_0)) {
detectorIndex = in.readInt();
} else {
@ -365,7 +365,7 @@ public class Detector implements ToXContentObject, Writeable {
} else {
out.writeBoolean(false);
}
out.writeList(detectorRules);
out.writeList(rules);
if (out.getVersion().onOrAfter(Version.V_5_5_0)) {
out.writeInt(detectorIndex);
}
@ -394,7 +394,7 @@ public class Detector implements ToXContentObject, Writeable {
if (excludeFrequent != null) {
builder.field(EXCLUDE_FREQUENT_FIELD.getPreferredName(), excludeFrequent);
}
builder.field(DETECTOR_RULES_FIELD.getPreferredName(), detectorRules);
builder.field(RULES_FIELD.getPreferredName(), rules);
// negative means "unknown", which should only happen for a 5.4 job
if (detectorIndex >= 0
// no point writing this to cluster state, as the indexes will get reassigned on reload anyway
@ -406,7 +406,7 @@ public class Detector implements ToXContentObject, Writeable {
}
private Detector(String detectorDescription, String function, String fieldName, String byFieldName, String overFieldName,
String partitionFieldName, boolean useNull, ExcludeFrequent excludeFrequent, List<DetectionRule> detectorRules,
String partitionFieldName, boolean useNull, ExcludeFrequent excludeFrequent, List<DetectionRule> rules,
int detectorIndex) {
this.function = function;
this.fieldName = fieldName;
@ -415,7 +415,7 @@ public class Detector implements ToXContentObject, Writeable {
this.partitionFieldName = partitionFieldName;
this.useNull = useNull;
this.excludeFrequent = excludeFrequent;
this.detectorRules = Collections.unmodifiableList(detectorRules);
this.rules = Collections.unmodifiableList(rules);
this.detectorDescription = detectorDescription != null ? detectorDescription : DefaultDetectorDescription.of(this);
this.detectorIndex = detectorIndex;
}
@ -491,8 +491,8 @@ public class Detector implements ToXContentObject, Writeable {
return excludeFrequent;
}
public List<DetectionRule> getDetectorRules() {
return detectorRules;
public List<DetectionRule> getRules() {
return rules;
}
/**
@ -514,8 +514,8 @@ public class Detector implements ToXContentObject, Writeable {
}
public Set<String> extractReferencedFilters() {
return detectorRules == null ? Collections.emptySet()
: detectorRules.stream().map(DetectionRule::extractReferencedFilters)
return rules == null ? Collections.emptySet()
: rules.stream().map(DetectionRule::extractReferencedFilters)
.flatMap(Set::stream).collect(Collectors.toSet());
}
@ -556,15 +556,14 @@ public class Detector implements ToXContentObject, Writeable {
Objects.equals(this.partitionFieldName, that.partitionFieldName) &&
Objects.equals(this.useNull, that.useNull) &&
Objects.equals(this.excludeFrequent, that.excludeFrequent) &&
Objects.equals(this.detectorRules, that.detectorRules) &&
Objects.equals(this.rules, that.rules) &&
this.detectorIndex == that.detectorIndex;
}
@Override
public int hashCode() {
return Objects.hash(detectorDescription, function, fieldName, byFieldName,
overFieldName, partitionFieldName, useNull, excludeFrequent,
detectorRules, detectorIndex);
return Objects.hash(detectorDescription, function, fieldName, byFieldName, overFieldName, partitionFieldName, useNull,
excludeFrequent, rules, detectorIndex);
}
public static class Builder {
@ -587,7 +586,7 @@ public class Detector implements ToXContentObject, Writeable {
private String partitionFieldName;
private boolean useNull = false;
private ExcludeFrequent excludeFrequent;
private List<DetectionRule> detectorRules = Collections.emptyList();
private List<DetectionRule> rules = Collections.emptyList();
// negative means unknown, and is expected for v5.4 jobs
private int detectorIndex = -1;
@ -603,8 +602,7 @@ public class Detector implements ToXContentObject, Writeable {
partitionFieldName = detector.partitionFieldName;
useNull = detector.useNull;
excludeFrequent = detector.excludeFrequent;
detectorRules = new ArrayList<>(detector.detectorRules.size());
detectorRules.addAll(detector.getDetectorRules());
rules = new ArrayList<>(detector.getRules());
detectorIndex = detector.detectorIndex;
}
@ -645,8 +643,8 @@ public class Detector implements ToXContentObject, Writeable {
this.excludeFrequent = excludeFrequent;
}
public void setDetectorRules(List<DetectionRule> detectorRules) {
this.detectorRules = detectorRules;
public void setRules(List<DetectionRule> rules) {
this.rules = rules;
}
public void setDetectorIndex(int detectorIndex) {
@ -704,12 +702,12 @@ public class Detector implements ToXContentObject, Writeable {
}
String function = this.function == null ? Detector.METRIC : this.function;
if (detectorRules.isEmpty() == false) {
if (rules.isEmpty() == false) {
if (FUNCTIONS_WITHOUT_RULE_SUPPORT.contains(function)) {
String msg = Messages.getMessage(Messages.JOB_CONFIG_DETECTION_RULE_NOT_SUPPORTED_BY_FUNCTION, function);
throw ExceptionsHelper.badRequestException(msg);
}
for (DetectionRule rule : detectorRules) {
for (DetectionRule rule : rules) {
checkScoping(rule);
}
}
@ -764,7 +762,7 @@ public class Detector implements ToXContentObject, Writeable {
}
return new Detector(detectorDescription, function, fieldName, byFieldName, overFieldName, partitionFieldName,
useNull, excludeFrequent, detectorRules, detectorIndex);
useNull, excludeFrequent, rules, detectorIndex);
}
public List<String> extractAnalysisFields() {
@ -802,7 +800,7 @@ public class Detector implements ToXContentObject, Writeable {
String targetFieldName = rule.getTargetFieldName();
checkTargetFieldNameIsValid(extractAnalysisFields(), targetFieldName);
List<String> validOptions = getValidFieldNameOptions(rule);
for (RuleCondition condition : rule.getRuleConditions()) {
for (RuleCondition condition : rule.getConditions()) {
if (!validOptions.contains(condition.getFieldName())) {
String msg = Messages.getMessage(Messages.JOB_CONFIG_DETECTION_RULE_CONDITION_INVALID_FIELD_NAME, validOptions,
condition.getFieldName());

View File

@ -315,7 +315,7 @@ public class JobUpdate implements Writeable, ToXContentObject {
detectorbuilder.setDetectorDescription(dd.getDescription());
}
if (dd.getRules() != null) {
detectorbuilder.setDetectorRules(dd.getRules());
detectorbuilder.setRules(dd.getRules());
}
ac.getDetectors().set(dd.getDetectorIndex(), detectorbuilder.build());
}
@ -435,13 +435,11 @@ public class JobUpdate implements Writeable, ToXContentObject {
new ConstructingObjectParser<>("detector_update", a -> new DetectorUpdate((int) a[0], (String) a[1],
(List<DetectionRule>) a[2]));
public static final ParseField RULES = new ParseField("rules");
static {
PARSER.declareInt(ConstructingObjectParser.optionalConstructorArg(), Detector.DETECTOR_INDEX);
PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), Job.DESCRIPTION);
PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(),
(parser, parseFieldMatcher) -> DetectionRule.CONFIG_PARSER.apply(parser, parseFieldMatcher).build(), RULES);
PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), (parser, parseFieldMatcher) ->
DetectionRule.CONFIG_PARSER.apply(parser, parseFieldMatcher).build(), Detector.RULES_FIELD);
}
private int detectorIndex;
@ -495,7 +493,7 @@ public class JobUpdate implements Writeable, ToXContentObject {
builder.field(Job.DESCRIPTION.getPreferredName(), description);
}
if (rules != null) {
builder.field(RULES.getPreferredName(), rules);
builder.field(Detector.RULES_FIELD.getPreferredName(), rules);
}
builder.endObject();

View File

@ -14,8 +14,7 @@ import java.util.Locale;
public enum RuleAction implements Writeable {
FILTER_RESULTS,
SKIP_SAMPLING,
SKIP_SAMPLING_AND_FILTER_RESULTS;
SKIP_SAMPLING;
/**
* Case-insensitive from string method.

View File

@ -26,11 +26,10 @@ import java.util.Map;
import java.util.Objects;
public class RuleCondition implements ToXContentObject, Writeable {
public static final ParseField CONDITION_TYPE_FIELD = new ParseField("condition_type");
public static final ParseField TYPE_FIELD = new ParseField("type");
public static final ParseField RULE_CONDITION_FIELD = new ParseField("rule_condition");
public static final ParseField FIELD_NAME_FIELD = new ParseField("field_name");
public static final ParseField FIELD_VALUE_FIELD = new ParseField("field_value");
public static final ParseField VALUE_FILTER_FIELD = new ParseField("value_filter");
// These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
public static final ConstructingObjectParser<RuleCondition, Void> METADATA_PARSER =
@ -53,60 +52,60 @@ public class RuleCondition implements ToXContentObject, Writeable {
return RuleConditionType.fromString(p.text());
}
throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
}, CONDITION_TYPE_FIELD, ValueType.STRING);
}, TYPE_FIELD, ValueType.STRING);
parser.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), FIELD_NAME_FIELD);
parser.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), FIELD_VALUE_FIELD);
parser.declareObject(ConstructingObjectParser.optionalConstructorArg(), Condition.PARSER, Condition.CONDITION_FIELD);
parser.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), VALUE_FILTER_FIELD);
parser.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), MlFilter.ID);
}
}
private final RuleConditionType conditionType;
private final RuleConditionType type;
private final String fieldName;
private final String fieldValue;
private final Condition condition;
private final String valueFilter;
private final String filterId;
public RuleCondition(StreamInput in) throws IOException {
conditionType = RuleConditionType.readFromStream(in);
type = RuleConditionType.readFromStream(in);
condition = in.readOptionalWriteable(Condition::new);
fieldName = in.readOptionalString();
fieldValue = in.readOptionalString();
valueFilter = in.readOptionalString();
filterId = in.readOptionalString();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
conditionType.writeTo(out);
type.writeTo(out);
out.writeOptionalWriteable(condition);
out.writeOptionalString(fieldName);
out.writeOptionalString(fieldValue);
out.writeOptionalString(valueFilter);
out.writeOptionalString(filterId);
}
RuleCondition(RuleConditionType conditionType, String fieldName, String fieldValue, Condition condition, String valueFilter) {
this.conditionType = conditionType;
RuleCondition(RuleConditionType type, String fieldName, String fieldValue, Condition condition, String filterId) {
this.type = type;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
this.condition = condition;
this.valueFilter = valueFilter;
this.filterId = filterId;
verifyFieldsBoundToType(this);
verifyFieldValueRequiresFieldName(this);
}
public RuleCondition(RuleCondition ruleCondition) {
this.conditionType = ruleCondition.conditionType;
this.type = ruleCondition.type;
this.fieldName = ruleCondition.fieldName;
this.fieldValue = ruleCondition.fieldValue;
this.condition = ruleCondition.condition;
this.valueFilter = ruleCondition.valueFilter;
this.filterId = ruleCondition.filterId;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(CONDITION_TYPE_FIELD.getPreferredName(), conditionType);
builder.field(TYPE_FIELD.getPreferredName(), type);
if (condition != null) {
builder.field(Condition.CONDITION_FIELD.getPreferredName(), condition);
}
@ -116,15 +115,15 @@ public class RuleCondition implements ToXContentObject, Writeable {
if (fieldValue != null) {
builder.field(FIELD_VALUE_FIELD.getPreferredName(), fieldValue);
}
if (valueFilter != null) {
builder.field(VALUE_FILTER_FIELD.getPreferredName(), valueFilter);
if (filterId != null) {
builder.field(MlFilter.ID.getPreferredName(), filterId);
}
builder.endObject();
return builder;
}
public RuleConditionType getConditionType() {
return conditionType;
public RuleConditionType getType() {
return type;
}
/**
@ -153,8 +152,8 @@ public class RuleCondition implements ToXContentObject, Writeable {
* The unique identifier of a filter. Required when the rule type is
* categorical. Should be null for all other types.
*/
public String getValueFilter() {
return valueFilter;
public String getFilterId() {
return filterId;
}
@Override
@ -168,14 +167,14 @@ public class RuleCondition implements ToXContentObject, Writeable {
}
RuleCondition other = (RuleCondition) obj;
return Objects.equals(conditionType, other.conditionType) && Objects.equals(fieldName, other.fieldName)
return Objects.equals(type, other.type) && Objects.equals(fieldName, other.fieldName)
&& Objects.equals(fieldValue, other.fieldValue) && Objects.equals(condition, other.condition)
&& Objects.equals(valueFilter, other.valueFilter);
&& Objects.equals(filterId, other.filterId);
}
@Override
public int hashCode() {
return Objects.hash(conditionType, fieldName, fieldValue, condition, valueFilter);
return Objects.hash(type, fieldName, fieldValue, condition, filterId);
}
public static RuleCondition createCategorical(String fieldName, String valueFilter) {
@ -195,7 +194,7 @@ public class RuleCondition implements ToXContentObject, Writeable {
}
private static void verifyFieldsBoundToType(RuleCondition ruleCondition) throws ElasticsearchParseException {
switch (ruleCondition.getConditionType()) {
switch (ruleCondition.getType()) {
case CATEGORICAL:
verifyCategorical(ruleCondition);
break;
@ -215,7 +214,7 @@ public class RuleCondition implements ToXContentObject, Writeable {
private static void verifyCategorical(RuleCondition ruleCondition) throws ElasticsearchParseException {
checkCategoricalHasNoField(Condition.CONDITION_FIELD.getPreferredName(), ruleCondition.getCondition());
checkCategoricalHasNoField(RuleCondition.FIELD_VALUE_FIELD.getPreferredName(), ruleCondition.getFieldValue());
checkCategoricalHasField(RuleCondition.VALUE_FILTER_FIELD.getPreferredName(), ruleCondition.getValueFilter());
checkCategoricalHasField(MlFilter.ID.getPreferredName(), ruleCondition.getFilterId());
}
private static void checkCategoricalHasNoField(String fieldName, Object fieldValue) throws ElasticsearchParseException {
@ -233,7 +232,7 @@ public class RuleCondition implements ToXContentObject, Writeable {
}
private static void verifyNumerical(RuleCondition ruleCondition) throws ElasticsearchParseException {
checkNumericalHasNoField(RuleCondition.VALUE_FILTER_FIELD.getPreferredName(), ruleCondition.getValueFilter());
checkNumericalHasNoField(MlFilter.ID.getPreferredName(), ruleCondition.getFilterId());
checkNumericalHasField(Condition.CONDITION_FIELD.getPreferredName(), ruleCondition.getCondition());
if (ruleCondition.getFieldName() != null && ruleCondition.getFieldValue() == null) {
String msg = Messages.getMessage(Messages.JOB_CONFIG_DETECTION_RULE_CONDITION_NUMERICAL_WITH_FIELD_NAME_REQUIRES_FIELD_VALUE);

View File

@ -59,6 +59,10 @@ public class GroupOrJobLookup {
return new GroupOrJobResolver().expand(expression, allowNoJobs);
}
public boolean isGroupOrJob(String id) {
return groupOrJobLookup.containsKey(id);
}
private class GroupOrJobResolver extends NameResolver {
private GroupOrJobResolver() {

View File

@ -0,0 +1,71 @@
/*
* 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.job.persistence;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.xpack.ml.action.util.PageParams;
import org.elasticsearch.xpack.ml.calendars.Calendar;
public class CalendarQueryBuilder {
private PageParams pageParams = new PageParams(0, 10000);
private String jobId;
private boolean sort = false;
/**
* Page the query result
* @param params The page parameters
* @return this
*/
public CalendarQueryBuilder pageParams(PageParams params) {
this.pageParams = params;
return this;
}
/**
* Query only calendars used by this job
* @param jobId The job Id
* @return this
*/
public CalendarQueryBuilder jobId(String jobId) {
this.jobId = jobId;
return this;
}
/**
* Sort results by calendar_id
* @param sort Sort if true
* @return this
*/
public CalendarQueryBuilder sort(boolean sort) {
this.sort = sort;
return this;
}
public SearchSourceBuilder build() {
QueryBuilder qb;
if (jobId != null) {
qb = new BoolQueryBuilder()
.filter(new TermsQueryBuilder(Calendar.TYPE.getPreferredName(), Calendar.CALENDAR_TYPE))
.filter(new TermsQueryBuilder(Calendar.JOB_IDS.getPreferredName(), jobId));
} else {
qb = new TermsQueryBuilder(Calendar.TYPE.getPreferredName(), Calendar.CALENDAR_TYPE);
}
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(qb);
if (sort) {
sourceBuilder.sort(Calendar.ID.getPreferredName());
}
sourceBuilder.from(pageParams.getFrom()).size(pageParams.getSize());
return sourceBuilder;
}
}

View File

@ -18,6 +18,11 @@ import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.bulk.BulkAction;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.MultiSearchRequestBuilder;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequest;
@ -25,6 +30,9 @@ import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlock;
@ -38,6 +46,7 @@ import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
@ -62,6 +71,7 @@ import org.elasticsearch.xpack.ml.action.GetCategoriesAction;
import org.elasticsearch.xpack.ml.action.GetInfluencersAction;
import org.elasticsearch.xpack.ml.action.GetRecordsAction;
import org.elasticsearch.xpack.ml.action.util.QueryPage;
import org.elasticsearch.xpack.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.calendars.SpecialEvent;
import org.elasticsearch.xpack.ml.job.config.Job;
import org.elasticsearch.xpack.ml.job.config.MlFilter;
@ -87,13 +97,16 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.elasticsearch.xpack.ClientHelper.ML_ORIGIN;
import static org.elasticsearch.xpack.ClientHelper.clientWithOrigin;
@ -998,6 +1011,136 @@ public class JobProvider {
, client::search);
}
public void updateCalendar(String calendarId, Set<String> jobIdsToAdd, Set<String> jobIdsToRemove,
Consumer<Calendar> handler, Consumer<Exception> errorHandler) {
ActionListener<Calendar> getCalendarListener = ActionListener.wrap(
calendar -> {
Set<String> currentJobs = new HashSet<>(calendar.getJobIds());
currentJobs.addAll(jobIdsToAdd);
currentJobs.removeAll(jobIdsToRemove);
Calendar updatedCalendar = new Calendar(calendar.getId(), new ArrayList<>(currentJobs));
UpdateRequest updateRequest = new UpdateRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, updatedCalendar.documentId());
updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
updateRequest.doc(updatedCalendar.toXContent(builder, ToXContent.EMPTY_PARAMS));
} catch (IOException e) {
throw new IllegalStateException("Failed to serialise calendar with id [" + updatedCalendar.getId() + "]", e);
}
executeAsyncWithOrigin(client.threadPool().getThreadContext(), ML_ORIGIN, updateRequest,
ActionListener.<UpdateResponse>wrap(
response -> {
handler.accept(updatedCalendar);
},
errorHandler)
, client::update);
},
errorHandler
);
calendar(calendarId, getCalendarListener);
}
public void calendars(CalendarQueryBuilder queryBuilder, ActionListener<QueryPage<Calendar>> listener) {
SearchRequest searchRequest = client.prepareSearch(MlMetaIndex.INDEX_NAME)
.setIndicesOptions(IndicesOptions.lenientExpandOpen())
.setSource(queryBuilder.build()).request();
executeAsyncWithOrigin(client.threadPool().getThreadContext(), ML_ORIGIN, searchRequest,
ActionListener.<SearchResponse>wrap(
response -> {
List<Calendar> calendars = new ArrayList<>();
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
calendars.add(parseSearchHit(hit, Calendar.PARSER, listener::onFailure).build());
}
listener.onResponse(new QueryPage<Calendar>(calendars, response.getHits().getTotalHits(),
Calendar.RESULTS_FIELD));
},
listener::onFailure)
, client::search);
}
public void removeJobFromCalendars(String jobId, ActionListener<Boolean> listener) {
ActionListener<BulkResponse> updateCalandarsListener = ActionListener.wrap(
r -> {
if (r.hasFailures()) {
listener.onResponse(false);
}
listener.onResponse(true);
},
listener::onFailure
);
ActionListener<QueryPage<Calendar>> getCalendarsListener = ActionListener.wrap(
r -> {
BulkRequestBuilder bulkUpdate = client.prepareBulk();
bulkUpdate.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
r.results().stream()
.map(c -> {
Set<String> ids = new HashSet<>(c.getJobIds());
ids.remove(jobId);
return new Calendar(c.getId(), new ArrayList<>(ids));
}).forEach(c -> {
UpdateRequest updateRequest = new UpdateRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE,
c.documentId());
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
updateRequest.doc(c.toXContent(builder, ToXContent.EMPTY_PARAMS));
} catch (IOException e) {
throw new IllegalStateException("Failed to serialise calendar with id [" + c.getId() + "]", e);
}
bulkUpdate.add(updateRequest);
});
if (bulkUpdate.numberOfActions() > 0) {
executeAsyncWithOrigin(client, ML_ORIGIN, BulkAction.INSTANCE, bulkUpdate.request(), updateCalandarsListener);
} else {
listener.onResponse(true);
}
},
listener::onFailure
);
CalendarQueryBuilder query = new CalendarQueryBuilder().jobId(jobId);
calendars(query, getCalendarsListener);
}
public void calendar(String calendarId, ActionListener<Calendar> listener) {
GetRequest getRequest = new GetRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, Calendar.documentId(calendarId));
executeAsyncWithOrigin(client.threadPool().getThreadContext(), ML_ORIGIN, getRequest, new ActionListener<GetResponse>() {
@Override
public void onResponse(GetResponse getDocResponse) {
try {
if (getDocResponse.isExists()) {
BytesReference docSource = getDocResponse.getSourceAsBytesRef();
try (XContentParser parser =
XContentFactory.xContent(docSource).createParser(NamedXContentRegistry.EMPTY, docSource)) {
Calendar calendar = Calendar.PARSER.apply(parser, null).build();
listener.onResponse(calendar);
}
} else {
this.onFailure(new ResourceNotFoundException("No calendar with id [" + calendarId + "]"));
}
} catch (Exception e) {
this.onFailure(e);
}
}
@Override
public void onFailure(Exception e) {
listener.onFailure(e);
}
},
client::get);
}
private void handleLatestModelSizeStats(String jobId, ModelSizeStats latestModelSizeStats, Consumer<Long> handler,
Consumer<Exception> errorHandler) {
if (latestModelSizeStats != null) {

View File

@ -107,8 +107,8 @@ public class FieldConfigWriter {
StringBuilder contents) throws IOException {
List<DetectionRule> rules = new ArrayList<>();
if (detector.getDetectorRules() != null) {
rules.addAll(detector.getDetectorRules());
if (detector.getRules() != null) {
rules.addAll(detector.getRules());
}
rules.addAll(specialEvents);

View File

@ -35,5 +35,4 @@ public class RestDeleteCalendarAction extends BaseRestHandler {
DeleteCalendarAction.Request request = new DeleteCalendarAction.Request(restRequest.param(Calendar.ID.getPreferredName()));
return channel -> client.execute(DeleteCalendarAction.INSTANCE, request, new AcknowledgedRestListener<>(channel));
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.calendar;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
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.MachineLearning;
import org.elasticsearch.xpack.ml.action.UpdateCalendarJobAction;
import org.elasticsearch.xpack.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.job.config.Job;
import java.io.IOException;
import java.util.Collections;
public class RestDeleteCalendarJobAction extends BaseRestHandler {
public RestDeleteCalendarJobAction(Settings settings, RestController controller) {
super(settings);
controller.registerHandler(RestRequest.Method.DELETE,
MachineLearning.BASE_PATH + "calendars/{" + Calendar.ID.getPreferredName() + "}/jobs/{" +
Job.ID.getPreferredName() + "}", this);
}
@Override
public String getName() {
return "xpack_ml_delete_calendar_job_action";
}
@Override
protected BaseRestHandler.RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
String calendarId = restRequest.param(Calendar.ID.getPreferredName());
String jobId = restRequest.param(Job.ID.getPreferredName());
UpdateCalendarJobAction.Request request =
new UpdateCalendarJobAction.Request(calendarId, Collections.emptySet(), Collections.singleton(jobId));
return channel -> client.execute(UpdateCalendarJobAction.INSTANCE, request, new AcknowledgedRestListener<>(channel));
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.calendar;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestToXContentListener;
import org.elasticsearch.xpack.ml.MachineLearning;
import org.elasticsearch.xpack.ml.action.UpdateCalendarJobAction;
import org.elasticsearch.xpack.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.job.config.Job;
import java.io.IOException;
import java.util.Collections;
public class RestPutCalendarJobAction extends BaseRestHandler {
public RestPutCalendarJobAction(Settings settings, RestController controller) {
super(settings);
controller.registerHandler(RestRequest.Method.PUT,
MachineLearning.BASE_PATH + "calendars/{" + Calendar.ID.getPreferredName() + "}/jobs/{" +
Job.ID.getPreferredName() + "}", this);
}
@Override
public String getName() {
return "xpack_ml_put_calendar_job_action";
}
@Override
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
String calendarId = restRequest.param(Calendar.ID.getPreferredName());
String jobId = restRequest.param(Job.ID.getPreferredName());
UpdateCalendarJobAction.Request putCalendarRequest =
new UpdateCalendarJobAction.Request(calendarId, Collections.singleton(jobId), Collections.emptySet());
return channel -> client.execute(UpdateCalendarJobAction.INSTANCE, putCalendarRequest, new RestToXContentListener<>(channel));
}
}

View File

@ -19,6 +19,9 @@
"type": "date",
"format": "date_time"
},
"interval_ms": {
"type": "long"
},
"type": {
"type": "keyword"
},

View File

@ -19,6 +19,9 @@
"type": "date",
"format": "date_time"
},
"interval_ms": {
"type": "long"
},
"type": {
"type": "keyword"
},

View File

@ -19,6 +19,9 @@
"type": "date",
"format": "date_time"
},
"interval_ms": {
"type": "long"
},
"type": {
"type": "keyword"
},

View File

@ -17,10 +17,12 @@ import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.action.rolemapping.PutRoleMappingRequestBuilder;
import org.elasticsearch.xpack.security.action.rolemapping.PutRoleMappingResponse;
import org.elasticsearch.xpack.security.authc.ldap.LdapRealm;
@ -42,6 +44,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -191,6 +194,13 @@ public abstract class AbstractAdLdapRealmTestCase extends SecurityIntegTestCase
super.deleteSecurityIndex();
}
@Override
public Set<String> excludeTemplates() {
Set<String> templates = Sets.newHashSet(super.excludeTemplates());
templates.add(SecurityLifecycleService.SECURITY_TEMPLATE_NAME); // don't remove the security index template
return templates;
}
private List<String> getRoleMappingContent(Function<RoleMappingEntry, String> contentFunction) {
return getRoleMappingContent(contentFunction, AbstractAdLdapRealmTestCase.roleMappings);
}

View File

@ -15,6 +15,8 @@ import org.elasticsearch.client.RestClient;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.security.client.SecurityClient;
import org.elasticsearch.xpack.security.user.ElasticUser;
@ -26,6 +28,7 @@ import org.junit.Before;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
/**
* Test case with method to handle the starting and stopping the stores for native users and roles
@ -59,6 +62,13 @@ public abstract class NativeRealmIntegTestCase extends SecurityIntegTestCase {
.build();
}
@Override
public Set<String> excludeTemplates() {
Set<String> templates = Sets.newHashSet(super.excludeTemplates());
templates.add(SecurityLifecycleService.SECURITY_TEMPLATE_NAME); // don't remove the security index template
return templates;
}
private SecureString reservedPassword = SecuritySettingsSource.TEST_PASSWORD_SECURE_STRING;
protected SecureString getReservedPassword() {

View File

@ -0,0 +1,22 @@
/*
* 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.test.AbstractStreamableTestCase;
import org.elasticsearch.xpack.ml.calendars.CalendarTests;
public class PutCalendarActionResponseTests extends AbstractStreamableTestCase<PutCalendarAction.Response> {
@Override
protected PutCalendarAction.Response createTestInstance() {
return new PutCalendarAction.Response(CalendarTests.testInstance());
}
@Override
protected PutCalendarAction.Response createBlankInstance() {
return new PutCalendarAction.Response();
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.test.AbstractStreamableTestCase;
import java.util.HashSet;
import java.util.Set;
public class UpdateCalendarJobActionResquestTests extends AbstractStreamableTestCase<UpdateCalendarJobAction.Request> {
@Override
protected UpdateCalendarJobAction.Request createTestInstance() {
int addSize = randomIntBetween(0, 2);
Set<String> toAdd = new HashSet<>();
for (int i=0; i<addSize; i++) {
toAdd.add(randomAlphaOfLength(10));
}
int removeSize = randomIntBetween(0, 2);
Set<String> toRemove = new HashSet<>();
for (int i=0; i<removeSize; i++) {
toRemove.add(randomAlphaOfLength(10));
}
return new UpdateCalendarJobAction.Request(randomAlphaOfLength(10), toAdd, toRemove);
}
@Override
protected UpdateCalendarJobAction.Request createBlankInstance() {
return new UpdateCalendarJobAction.Request();
}
}

View File

@ -18,8 +18,7 @@ import static org.hamcrest.Matchers.equalTo;
public class CalendarTests extends AbstractSerializingTestCase<Calendar> {
@Override
protected Calendar createTestInstance() {
public static Calendar testInstance() {
int size = randomInt(10);
List<String> items = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
@ -28,6 +27,11 @@ public class CalendarTests extends AbstractSerializingTestCase<Calendar> {
return new Calendar(randomAlphaOfLengthBetween(1, 20), items);
}
@Override
protected Calendar createTestInstance() {
return testInstance();
}
@Override
protected Writeable.Reader<Calendar> instanceReader() {
return Calendar::new;

View File

@ -22,6 +22,7 @@ import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
public class SpecialEventTests extends AbstractSerializingTestCase<SpecialEvent> {
@ -60,14 +61,14 @@ public class SpecialEventTests extends AbstractSerializingTestCase<SpecialEvent>
DetectionRule rule = event.toDetectionRule(TimeValue.timeValueSeconds(bucketSpanSecs));
assertEquals(Connective.AND, rule.getConditionsConnective());
assertEquals(RuleAction.SKIP_SAMPLING_AND_FILTER_RESULTS, rule.getRuleAction());
assertEquals(rule.getActions(), EnumSet.of(RuleAction.FILTER_RESULTS, RuleAction.SKIP_SAMPLING));
assertNull(rule.getTargetFieldName());
assertNull(rule.getTargetFieldValue());
List<RuleCondition> conditions = rule.getRuleConditions();
List<RuleCondition> conditions = rule.getConditions();
assertEquals(2, conditions.size());
assertEquals(RuleConditionType.TIME, conditions.get(0).getConditionType());
assertEquals(RuleConditionType.TIME, conditions.get(1).getConditionType());
assertEquals(RuleConditionType.TIME, conditions.get(0).getType());
assertEquals(RuleConditionType.TIME, conditions.get(1).getType());
assertEquals(Operator.GTE, conditions.get(0).getCondition().getOperator());
assertEquals(Operator.LT, conditions.get(1).getCondition().getOperator());

View File

@ -61,7 +61,7 @@ public class DetectionRulesIT extends MlNativeAutodetectIntegTestCase {
DetectionRule rule = new DetectionRule.Builder(Arrays.asList(condition1, condition2, condition3)).build();
Detector.Builder detector = new Detector.Builder("max", "value");
detector.setDetectorRules(Arrays.asList(rule));
detector.setRules(Arrays.asList(rule));
detector.setByFieldName("by_field");
AnalysisConfig.Builder analysisConfig = new AnalysisConfig.Builder(

View File

@ -25,6 +25,8 @@ import org.elasticsearch.xpack.XPackSingleNodeTestCase;
import org.elasticsearch.xpack.ml.MachineLearning;
import org.elasticsearch.xpack.ml.MlMetaIndex;
import org.elasticsearch.xpack.ml.action.PutJobAction;
import org.elasticsearch.xpack.ml.action.util.QueryPage;
import org.elasticsearch.xpack.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.calendars.SpecialEvent;
import org.elasticsearch.xpack.ml.job.config.AnalysisConfig;
import org.elasticsearch.xpack.ml.job.config.Connective;
@ -36,6 +38,7 @@ import org.elasticsearch.xpack.ml.job.config.MlFilter;
import org.elasticsearch.xpack.ml.job.config.RuleAction;
import org.elasticsearch.xpack.ml.job.config.RuleCondition;
import org.elasticsearch.xpack.ml.job.persistence.AnomalyDetectorsIndex;
import org.elasticsearch.xpack.ml.job.persistence.CalendarQueryBuilder;
import org.elasticsearch.xpack.ml.job.persistence.JobDataCountsPersister;
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
import org.elasticsearch.xpack.ml.job.persistence.JobResultsPersister;
@ -56,9 +59,21 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.isIn;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.hamcrest.core.Is.is;
public class JobProviderIT extends XPackSingleNodeTestCase {
@ -97,6 +112,160 @@ public class JobProviderIT extends XPackSingleNodeTestCase {
});
}
public void testGetCalandarByJobId() throws Exception {
List<Calendar> calendars = new ArrayList<>();
calendars.add(new Calendar("empty calendar", Collections.emptyList()));
calendars.add(new Calendar("foo calendar", Collections.singletonList("foo")));
calendars.add(new Calendar("foo bar calendar", Arrays.asList("foo", "bar")));
calendars.add(new Calendar("cat calendar", Collections.singletonList("cat")));
calendars.add(new Calendar("cat foo calendar", Arrays.asList("cat", "foo")));
indexCalendars(calendars);
List<Calendar> queryResult = getCalendars("ted");
assertThat(queryResult, is(empty()));
queryResult = getCalendars("foo");
assertThat(queryResult, hasSize(3));
Long matchedCount = queryResult.stream().filter(
c -> c.getId().equals("foo calendar") || c.getId().equals("foo bar calendar") || c.getId().equals("cat foo calendar"))
.collect(Collectors.counting());
assertEquals(new Long(3), matchedCount);
queryResult = getCalendars("bar");
assertThat(queryResult, hasSize(1));
assertEquals("foo bar calendar", queryResult.get(0).getId());
}
public void testUpdateCalendar() throws Exception {
String calendarId = "empty calendar";
Calendar emptyCal = new Calendar(calendarId, Collections.emptyList());
indexCalendars(Collections.singletonList(emptyCal));
Set<String> addedIds = new HashSet<>();
addedIds.add("foo");
addedIds.add("bar");
updateCalendar(calendarId, addedIds, Collections.emptySet());
Calendar updated = getCalendar(calendarId);
assertEquals(calendarId, updated.getId());
assertEquals(addedIds, new HashSet<>(updated.getJobIds()));
Set<String> removedIds = new HashSet<>();
removedIds.add("foo");
updateCalendar(calendarId, Collections.emptySet(), removedIds);
updated = getCalendar(calendarId);
assertEquals(calendarId, updated.getId());
assertEquals(1, updated.getJobIds().size());
assertEquals("bar", updated.getJobIds().get(0));
}
public void testRemoveJobFromCalendar() throws Exception {
List<Calendar> calendars = new ArrayList<>();
calendars.add(new Calendar("empty calendar", Collections.emptyList()));
calendars.add(new Calendar("foo calendar", Collections.singletonList("foo")));
calendars.add(new Calendar("foo bar calendar", Arrays.asList("foo", "bar")));
calendars.add(new Calendar("cat calendar", Collections.singletonList("cat")));
calendars.add(new Calendar("cat foo calendar", Arrays.asList("cat", "foo")));
indexCalendars(calendars);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Exception> exceptionHolder = new AtomicReference<>();
jobProvider.removeJobFromCalendars("bar", ActionListener.wrap(
r -> latch.countDown(),
exceptionHolder::set));
latch.await();
if (exceptionHolder.get() != null) {
throw exceptionHolder.get();
}
List<Calendar> updatedCalendars = getCalendars(null);
assertEquals(5, updatedCalendars.size());
for (Calendar cal: updatedCalendars) {
assertThat("bar", not(isIn(cal.getJobIds())));
}
Calendar catFoo = getCalendar("cat foo calendar");
assertThat(catFoo.getJobIds(), contains("cat", "foo"));
CountDownLatch latch2 = new CountDownLatch(1);
exceptionHolder = new AtomicReference<>();
jobProvider.removeJobFromCalendars("cat", ActionListener.wrap(
r -> latch2.countDown(),
exceptionHolder::set));
latch2.await();
if (exceptionHolder.get() != null) {
throw exceptionHolder.get();
}
updatedCalendars = getCalendars(null);
assertEquals(5, updatedCalendars.size());
for (Calendar cal: updatedCalendars) {
assertThat("bar", not(isIn(cal.getJobIds())));
assertThat("cat", not(isIn(cal.getJobIds())));
}
}
private List<Calendar> getCalendars(String jobId) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Exception> exceptionHolder = new AtomicReference<>();
AtomicReference<QueryPage<Calendar>> result = new AtomicReference<>();
CalendarQueryBuilder query = new CalendarQueryBuilder();
if (jobId != null) {
query.jobId(jobId);
}
jobProvider.calendars(query, ActionListener.wrap(
r -> {
latch.countDown();
result.set(r);
},
exceptionHolder::set));
latch.await();
if (exceptionHolder.get() != null) {
throw exceptionHolder.get();
}
return result.get().results();
}
private void updateCalendar(String calendarId, Set<String> idsToAdd, Set<String> idsToRemove) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Exception> exceptionHolder = new AtomicReference<>();
jobProvider.updateCalendar(calendarId, idsToAdd, idsToRemove,
r -> latch.countDown(),
exceptionHolder::set);
latch.await();
if (exceptionHolder.get() != null) {
throw exceptionHolder.get();
}
client().admin().indices().prepareRefresh(MlMetaIndex.INDEX_NAME).get();
}
private Calendar getCalendar(String calendarId) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Exception> exceptionHolder = new AtomicReference<>();
AtomicReference<Calendar> calendarHolder = new AtomicReference<>();
jobProvider.calendar(calendarId, ActionListener.wrap(
c -> { latch.countDown(); calendarHolder.set(c); },
exceptionHolder::set)
);
latch.await();
if (exceptionHolder.get() != null) {
throw exceptionHolder.get();
}
return calendarHolder.get();
}
public void testSpecialEvents() throws Exception {
List<SpecialEvent> events = new ArrayList<>();
events.add(new SpecialEvent("A_and_B_downtime", "downtime", createZonedDateTime(1000L), createZonedDateTime(2000L),
@ -262,10 +431,10 @@ public class JobProviderIT extends XPackSingleNodeTestCase {
}
DetectionRule.Builder rule = new DetectionRule.Builder(conditions)
.setRuleAction(RuleAction.FILTER_RESULTS)
.setActions(RuleAction.FILTER_RESULTS)
.setConditionsConnective(Connective.OR);
detector.setDetectorRules(Collections.singletonList(rule.build()));
detector.setRules(Collections.singletonList(rule.build()));
}
return new AnalysisConfig.Builder(Collections.singletonList(detector.build()));
@ -321,7 +490,8 @@ public class JobProviderIT extends XPackSingleNodeTestCase {
for (MlFilter filter : filters) {
IndexRequest indexRequest = new IndexRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, filter.documentId());
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
indexRequest.source(filter.toXContent(builder, ToXContent.EMPTY_PARAMS));
ToXContent.MapParams params = new ToXContent.MapParams(Collections.singletonMap(MlMetaIndex.INCLUDE_TYPE_KEY, "true"));
indexRequest.source(filter.toXContent(builder, params));
bulkRequest.add(indexRequest);
}
}
@ -341,7 +511,21 @@ public class JobProviderIT extends XPackSingleNodeTestCase {
private void indexQuantiles(Quantiles quantiles) {
JobResultsPersister persister = new JobResultsPersister(nodeSettings(), client());
persister.persistQuantiles(quantiles);
}
private void indexCalendars(List<Calendar> calendars) throws IOException {
BulkRequestBuilder bulkRequest = client().prepareBulk();
bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
for (Calendar calendar: calendars) {
IndexRequest indexRequest = new IndexRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, calendar.documentId());
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
ToXContent.MapParams params = new ToXContent.MapParams(Collections.singletonMap(MlMetaIndex.INCLUDE_TYPE_KEY, "true"));
indexRequest.source(calendar.toXContent(builder, params));
bulkRequest.add(indexRequest);
}
}
bulkRequest.execute().actionGet();
}
private ZonedDateTime createZonedDateTime(long epochMs) {

View File

@ -461,9 +461,9 @@ public class AnalysisConfigTests extends AbstractSerializingTestCase<AnalysisCon
"filter2"))).build();
Detector.Builder detector1 = new Detector.Builder("count", null);
detector1.setByFieldName("foo");
detector1.setDetectorRules(Collections.singletonList(rule1));
detector1.setRules(Collections.singletonList(rule1));
Detector.Builder detector2 = new Detector.Builder("count", null);
detector2.setDetectorRules(Collections.singletonList(rule2));
detector2.setRules(Collections.singletonList(rule2));
detector2.setByFieldName("foo");
AnalysisConfig config = new AnalysisConfig.Builder(
Arrays.asList(detector1.build(), detector2.build(), new Detector.Builder("count", null).build())).build();

View File

@ -13,6 +13,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
@ -63,7 +64,7 @@ public class DetectionRuleTests extends AbstractSerializingTestCase<DetectionRul
public void testEqualsGivenRules() {
DetectionRule rule1 = createFullyPopulated().build();
DetectionRule rule2 = createFullyPopulated().setRuleConditions(createRule("10")).build();
DetectionRule rule2 = createFullyPopulated().setConditions(createRule("10")).build();
assertFalse(rule1.equals(rule2));
assertFalse(rule2.equals(rule1));
}
@ -78,7 +79,7 @@ public class DetectionRuleTests extends AbstractSerializingTestCase<DetectionRul
private static DetectionRule.Builder createFullyPopulated() {
return new DetectionRule.Builder(createRule("5"))
.setRuleAction(RuleAction.FILTER_RESULTS)
.setActions(EnumSet.of(RuleAction.FILTER_RESULTS, RuleAction.SKIP_SAMPLING))
.setTargetFieldName("targetField")
.setTargetFieldValue("targetValue")
.setConditionsConnective(Connective.AND);
@ -91,26 +92,33 @@ public class DetectionRuleTests extends AbstractSerializingTestCase<DetectionRul
@Override
protected DetectionRule createTestInstance() {
RuleAction ruleAction = randomFrom(RuleAction.values());
String targetFieldName = null;
String targetFieldValue = null;
Connective connective = randomFrom(Connective.values());
if (randomBoolean()) {
targetFieldName = randomAlphaOfLengthBetween(1, 20);
targetFieldValue = randomAlphaOfLengthBetween(1, 20);
}
int size = 1 + randomInt(20);
List<RuleCondition> ruleConditions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
// no need for random condition (it is already tested)
ruleConditions.addAll(createRule(Double.toString(randomDouble())));
}
return new DetectionRule.Builder(ruleConditions)
.setRuleAction(ruleAction)
.setTargetFieldName(targetFieldName)
.setTargetFieldValue(targetFieldValue)
.setConditionsConnective(connective)
.build();
DetectionRule.Builder builder = new DetectionRule.Builder(ruleConditions);
if (randomBoolean()) {
EnumSet<RuleAction> actions = EnumSet.noneOf(RuleAction.class);
int actionsCount = randomIntBetween(1, RuleAction.values().length);
for (int i = 0; i < actionsCount; ++i) {
actions.add(randomFrom(RuleAction.values()));
}
builder.setActions(actions);
}
if (randomBoolean()) {
builder.setConditionsConnective(randomFrom(Connective.values()));
}
if (randomBoolean()) {
builder.setTargetFieldName(randomAlphaOfLengthBetween(1, 20));
builder.setTargetFieldValue(randomAlphaOfLengthBetween(1, 20));
}
return builder.build();
}
@Override
@ -125,16 +133,16 @@ public class DetectionRuleTests extends AbstractSerializingTestCase<DetectionRul
@Override
protected DetectionRule mutateInstance(DetectionRule instance) throws IOException {
List<RuleCondition> ruleConditions = instance.getRuleConditions();
RuleAction ruleAction = instance.getRuleAction();
List<RuleCondition> conditions = instance.getConditions();
EnumSet<RuleAction> actions = instance.getActions();
String targetFieldName = instance.getTargetFieldName();
String targetFieldValue = instance.getTargetFieldValue();
Connective connective = instance.getConditionsConnective();
switch (between(0, 3)) {
case 0:
ruleConditions = new ArrayList<>(ruleConditions);
ruleConditions.addAll(createRule(Double.toString(randomDouble())));
conditions = new ArrayList<>(conditions);
conditions.addAll(createRule(Double.toString(randomDouble())));
break;
case 1:
targetFieldName = randomAlphaOfLengthBetween(5, 10);
@ -156,7 +164,7 @@ public class DetectionRuleTests extends AbstractSerializingTestCase<DetectionRul
throw new AssertionError("Illegal randomisation branch");
}
return new DetectionRule.Builder(ruleConditions).setRuleAction(ruleAction).setTargetFieldName(targetFieldName)
return new DetectionRule.Builder(conditions).setActions(actions).setTargetFieldName(targetFieldName)
.setTargetFieldValue(targetFieldValue).setConditionsConnective(connective).build();
}
}

View File

@ -64,11 +64,11 @@ public class DetectorTests extends AbstractSerializingTestCase<Detector> {
Condition condition = new Condition(Operator.GT, "5");
DetectionRule rule = new DetectionRule.Builder(
Collections.singletonList(new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, "by2", "val", condition, null)))
.setRuleAction(RuleAction.FILTER_RESULTS).setTargetFieldName("over_field")
.setActions(RuleAction.FILTER_RESULTS).setTargetFieldName("over_field")
.setTargetFieldValue("targetValue")
.setConditionsConnective(Connective.AND)
.build();
builder.setDetectorRules(Collections.singletonList(rule));
builder.setRules(Collections.singletonList(rule));
detector2 = builder.build();
assertFalse(detector1.equals(detector2));
}
@ -84,22 +84,22 @@ public class DetectorTests extends AbstractSerializingTestCase<Detector> {
Condition condition = new Condition(Operator.GT, "5");
DetectionRule rule = new DetectionRule.Builder(
Collections.singletonList(new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, null, null, condition, null)))
.setRuleAction(RuleAction.FILTER_RESULTS)
.setActions(RuleAction.FILTER_RESULTS)
.setTargetFieldName("over_field")
.setTargetFieldValue("targetValue")
.setConditionsConnective(Connective.AND)
.build();
builder.setDetectorRules(Collections.singletonList(rule));
builder.setRules(Collections.singletonList(rule));
builder.setByFieldName(null);
detector = builder.build();
assertEquals(Collections.singletonList("over_field"), detector.extractAnalysisFields());
builder = new Detector.Builder(detector);
rule = new DetectionRule.Builder(
Collections.singletonList(new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, null, null, condition, null)))
.setRuleAction(RuleAction.FILTER_RESULTS)
.setActions(RuleAction.FILTER_RESULTS)
.setConditionsConnective(Connective.AND)
.build();
builder.setDetectorRules(Collections.singletonList(rule));
builder.setRules(Collections.singletonList(rule));
builder.setOverFieldName(null);
detector = builder.build();
assertTrue(detector.extractAnalysisFields().isEmpty());
@ -107,7 +107,7 @@ public class DetectorTests extends AbstractSerializingTestCase<Detector> {
public void testExtractReferencedLists() {
Detector.Builder builder = createDetector();
builder.setDetectorRules(Arrays.asList(
builder.setRules(Arrays.asList(
new DetectionRule.Builder(Collections.singletonList(RuleCondition.createCategorical("by_field", "list1"))).build(),
new DetectionRule.Builder(Collections.singletonList(RuleCondition.createCategorical("by_field", "list2"))).build()));
@ -140,12 +140,12 @@ public class DetectorTests extends AbstractSerializingTestCase<Detector> {
Condition condition = new Condition(Operator.GT, "5");
DetectionRule rule = new DetectionRule.Builder(
Collections.singletonList(new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, "by_field", "val", condition, null)))
.setRuleAction(RuleAction.FILTER_RESULTS)
.setActions(RuleAction.FILTER_RESULTS)
.setTargetFieldName("over_field")
.setTargetFieldValue("targetValue")
.setConditionsConnective(Connective.AND)
.build();
detector.setDetectorRules(Collections.singletonList(rule));
detector.setRules(Collections.singletonList(rule));
return detector;
}
@ -176,15 +176,15 @@ public class DetectorTests extends AbstractSerializingTestCase<Detector> {
}
if (randomBoolean()) {
int size = randomInt(10);
List<DetectionRule> detectorRules = new ArrayList<>(size);
List<DetectionRule> rules = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
// no need for random DetectionRule (it is already tested)
Condition condition = new Condition(Operator.GT, "5");
detectorRules.add(new DetectionRule.Builder(
rules.add(new DetectionRule.Builder(
Collections.singletonList(new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, null, null, condition, null)))
.setTargetFieldName(fieldName).build());
}
detector.setDetectorRules(detectorRules);
detector.setRules(rules);
}
if (randomBoolean()) {
detector.setUseNull(randomBoolean());
@ -508,14 +508,14 @@ public class DetectorTests extends AbstractSerializingTestCase<Detector> {
}
}
public void testVerify_GivenInvalidDetectionRuleTargetFieldName() {
public void testVerify_GivenInvalidRuleTargetFieldName() {
Detector.Builder detector = new Detector.Builder("mean", "metricVale");
detector.setByFieldName("metricName");
detector.setPartitionFieldName("instance");
RuleCondition ruleCondition =
new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, "metricName", "metricVale", new Condition(Operator.LT, "5"), null);
DetectionRule rule = new DetectionRule.Builder(Collections.singletonList(ruleCondition)).setTargetFieldName("instancE").build();
detector.setDetectorRules(Collections.singletonList(rule));
detector.setRules(Collections.singletonList(rule));
ElasticsearchException e = ESTestCase.expectThrows(ElasticsearchException.class, detector::build);
@ -524,14 +524,14 @@ public class DetectorTests extends AbstractSerializingTestCase<Detector> {
e.getMessage());
}
public void testVerify_GivenValidDetectionRule() {
public void testVerify_GivenValidRule() {
Detector.Builder detector = new Detector.Builder("mean", "metricVale");
detector.setByFieldName("metricName");
detector.setPartitionFieldName("instance");
RuleCondition ruleCondition =
new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, "metricName", "CPU", new Condition(Operator.LT, "5"), null);
DetectionRule rule = new DetectionRule.Builder(Collections.singletonList(ruleCondition)).setTargetFieldName("instance").build();
detector.setDetectorRules(Collections.singletonList(rule));
detector.setRules(Collections.singletonList(rule));
detector.build();
}

View File

@ -165,7 +165,7 @@ public class JobUpdateTests extends AbstractSerializingTestCase<JobUpdate> {
updatedJob.getAnalysisConfig().getDetectors().get(detectorUpdate.getDetectorIndex()).getDetectorDescription());
assertNotNull(updatedJob.getAnalysisConfig().getDetectors().get(detectorUpdate.getDetectorIndex()).getDetectorDescription());
assertEquals(detectorUpdate.getRules(),
updatedJob.getAnalysisConfig().getDetectors().get(detectorUpdate.getDetectorIndex()).getDetectorRules());
updatedJob.getAnalysisConfig().getDetectors().get(detectorUpdate.getDetectorIndex()).getRules());
}
}

View File

@ -11,14 +11,12 @@ import org.elasticsearch.test.ESTestCase;
import static org.hamcrest.Matchers.equalTo;
public class
RuleActionTests extends ESTestCase {
public class RuleActionTests extends ESTestCase {
public void testForString() {
assertEquals(RuleAction.FILTER_RESULTS, RuleAction.fromString("filter_results"));
assertEquals(RuleAction.FILTER_RESULTS, RuleAction.fromString("FILTER_RESULTS"));
assertEquals(RuleAction.SKIP_SAMPLING, RuleAction.fromString("SKip_sampLing"));
assertEquals(RuleAction.SKIP_SAMPLING_AND_FILTER_RESULTS, RuleAction.fromString("skip_sampling_and_filter_results"));
}
public void testToString() {
@ -34,9 +32,9 @@ RuleActionTests extends ESTestCase {
}
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeVInt(2);
out.writeVInt(1);
try (StreamInput in = out.bytes().streamInput()) {
assertThat(RuleAction.readFromStream(in), equalTo(RuleAction.SKIP_SAMPLING_AND_FILTER_RESULTS));
assertThat(RuleAction.readFromStream(in), equalTo(RuleAction.SKIP_SAMPLING));
}
}
}

View File

@ -52,7 +52,7 @@ public class RuleConditionTests extends AbstractSerializingTestCase<RuleConditio
public void testConstructor() {
RuleCondition condition = new RuleCondition(RuleConditionType.CATEGORICAL, null, null, null, "valueFilter");
assertEquals(RuleConditionType.CATEGORICAL, condition.getConditionType());
assertEquals(RuleConditionType.CATEGORICAL, condition.getType());
assertNull(condition.getFieldName());
assertNull(condition.getFieldValue());
assertNull(condition.getCondition());
@ -122,16 +122,16 @@ public class RuleConditionTests extends AbstractSerializingTestCase<RuleConditio
assertEquals("Invalid detector rule: a categorical rule_condition does not support field_value", e.getMessage());
}
public void testVerify_GivenCategoricalWithoutValueFilter() {
public void testVerify_GivenCategoricalWithoutFilterId() {
ElasticsearchException e = expectThrows(ElasticsearchException.class,
() -> new RuleCondition(RuleConditionType.CATEGORICAL, null, null, null, null));
assertEquals("Invalid detector rule: a categorical rule_condition requires value_filter to be set", e.getMessage());
assertEquals("Invalid detector rule: a categorical rule_condition requires filter_id to be set", e.getMessage());
}
public void testVerify_GivenNumericalActualWithValueFilter() {
public void testVerify_GivenNumericalActualWithFilterId() {
ElasticsearchException e = expectThrows(ElasticsearchException.class,
() -> new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, null, null, null, "myFilter"));
assertEquals("Invalid detector rule: a numerical rule_condition does not support value_filter", e.getMessage());
assertEquals("Invalid detector rule: a numerical rule_condition does not support filter_id", e.getMessage());
}
public void testVerify_GivenNumericalActualWithoutCondition() {
@ -146,10 +146,10 @@ public class RuleConditionTests extends AbstractSerializingTestCase<RuleConditio
assertEquals("Invalid detector rule: a numerical rule_condition with field_name requires that field_value is set", e.getMessage());
}
public void testVerify_GivenNumericalTypicalWithValueFilter() {
public void testVerify_GivenNumericalTypicalWithFilterId() {
ElasticsearchException e = expectThrows(ElasticsearchException.class,
() -> new RuleCondition(RuleConditionType.NUMERICAL_ACTUAL, null, null, null, "myFilter"));
assertEquals("Invalid detector rule: a numerical rule_condition does not support value_filter", e.getMessage());
assertEquals("Invalid detector rule: a numerical rule_condition does not support filter_id", e.getMessage());
}
public void testVerify_GivenNumericalTypicalWithoutCondition() {
@ -158,10 +158,10 @@ public class RuleConditionTests extends AbstractSerializingTestCase<RuleConditio
assertEquals("Invalid detector rule: a numerical rule_condition requires condition to be set", e.getMessage());
}
public void testVerify_GivenNumericalDiffAbsWithValueFilter() {
public void testVerify_GivenNumericalDiffAbsWithFilterId() {
ElasticsearchException e = expectThrows(ElasticsearchException.class,
() -> new RuleCondition(RuleConditionType.NUMERICAL_DIFF_ABS, null, null, null, "myFilter"));
assertEquals("Invalid detector rule: a numerical rule_condition does not support value_filter", e.getMessage());
assertEquals("Invalid detector rule: a numerical rule_condition does not support filter_id", e.getMessage());
}
public void testVerify_GivenNumericalDiffAbsWithoutCondition() {
@ -220,12 +220,12 @@ public class RuleConditionTests extends AbstractSerializingTestCase<RuleConditio
public void testCreateTimeBased() {
RuleCondition timeBased = RuleCondition.createTime(Operator.GTE, 100L);
assertEquals(RuleConditionType.TIME, timeBased.getConditionType());
assertEquals(RuleConditionType.TIME, timeBased.getType());
assertEquals(Operator.GTE, timeBased.getCondition().getOperator());
assertEquals("100", timeBased.getCondition().getValue());
assertNull(timeBased.getFieldName());
assertNull(timeBased.getFieldValue());
assertNull(timeBased.getValueFilter());
assertNull(timeBased.getFilterId());
}
public void testCreateTimeBased_GivenOperatorMatch() {
@ -237,11 +237,11 @@ public class RuleConditionTests extends AbstractSerializingTestCase<RuleConditio
public void testCreateNumerical() {
RuleCondition ruleCondition = RuleCondition.createNumerical(RuleConditionType.NUMERICAL_ACTUAL, "foo", "bar",
new Condition(Operator.GTE, "100"));
assertEquals(RuleConditionType.NUMERICAL_ACTUAL, ruleCondition.getConditionType());
assertEquals(RuleConditionType.NUMERICAL_ACTUAL, ruleCondition.getType());
assertEquals(Operator.GTE, ruleCondition.getCondition().getOperator());
assertEquals("100", ruleCondition.getCondition().getValue());
assertEquals("foo", ruleCondition.getFieldName());
assertEquals("bar", ruleCondition.getFieldValue());
assertNull(ruleCondition.getValueFilter());
assertNull(ruleCondition.getFilterId());
}
}

View File

@ -89,6 +89,20 @@ public class GroupOrJobLookupTests extends ESTestCase {
assertThat(groupOrJobLookup.expandJobIds("foo-group,*-2", false), contains("bar-2", "foo-1", "foo-2"));
}
public void testIsGroupOrJob() {
List<Job> jobs = new ArrayList<>();
jobs.add(mockJob("foo-1", Arrays.asList("foo-group", "ones")));
jobs.add(mockJob("foo-2", Arrays.asList("foo-group", "twos")));
jobs.add(mockJob("bar-1", Arrays.asList("bar-group", "ones")));
jobs.add(mockJob("nogroup", Collections.emptyList()));
GroupOrJobLookup groupOrJobLookup = new GroupOrJobLookup(jobs);
assertTrue(groupOrJobLookup.isGroupOrJob("foo-1"));
assertTrue(groupOrJobLookup.isGroupOrJob("twos"));
assertTrue(groupOrJobLookup.isGroupOrJob("nogroup"));
assertFalse(groupOrJobLookup.isGroupOrJob("missing"));
}
private static Job mockJob(String jobId, List<String> groups) {
Job job = mock(Job.class);
when(job.getId()).thenReturn(jobId);

View File

@ -6,7 +6,6 @@
package org.elasticsearch.xpack.ml.job.process.autodetect.writer;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.ml.calendars.SpecialEvent;
import org.elasticsearch.xpack.ml.job.config.Condition;
import org.elasticsearch.xpack.ml.job.config.Connective;
import org.elasticsearch.xpack.ml.job.config.DetectionRule;
@ -22,13 +21,8 @@ import org.mockito.InOrder;
import org.mockito.Mockito;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
@ -202,11 +196,11 @@ public class ControlMsgToProcessWriterTests extends ESTestCase {
inOrder.verify(lengthEncodedWriter).writeNumFields(4);
inOrder.verify(lengthEncodedWriter, times(3)).writeField("");
inOrder.verify(lengthEncodedWriter).writeField("u[detectorRules]\ndetectorIndex=2\n" +
"rulesJson=[{\"rule_action\":\"filter_results\",\"conditions_connective\":\"and\",\"rule_conditions\":" +
"[{\"condition_type\":\"numerical_actual\",\"condition\":{\"operator\":\"gt\",\"value\":\"5\"}}]," +
"rulesJson=[{\"actions\":[\"filter_results\"],\"conditions_connective\":\"and\",\"conditions\":" +
"[{\"type\":\"numerical_actual\",\"condition\":{\"operator\":\"gt\",\"value\":\"5\"}}]," +
"\"target_field_name\":\"targetField1\",\"target_field_value\":\"targetValue\"}," +
"{\"rule_action\":\"filter_results\",\"conditions_connective\":\"and\",\"rule_conditions\":[" +
"{\"condition_type\":\"numerical_actual\",\"condition\":{\"operator\":\"gt\",\"value\":\"5\"}}]," +
"{\"actions\":[\"filter_results\"],\"conditions_connective\":\"and\",\"conditions\":[" +
"{\"type\":\"numerical_actual\",\"condition\":{\"operator\":\"gt\",\"value\":\"5\"}}]," +
"\"target_field_name\":\"targetField2\",\"target_field_value\":\"targetValue\"}]");
verifyNoMoreInteractions(lengthEncodedWriter);
}

View File

@ -26,7 +26,6 @@ import org.mockito.ArgumentCaptor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
@ -36,7 +35,6 @@ import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@ -195,7 +193,7 @@ public class FieldConfigWriterTests extends ESTestCase {
RuleCondition ruleCondition = RuleCondition.createNumerical
(RuleConditionType.NUMERICAL_ACTUAL, "metricName", "metricValue", new Condition(Operator.LT, "5"));
DetectionRule rule = new DetectionRule.Builder(Arrays.asList(ruleCondition)).setTargetFieldName("instance").build();
detector.setDetectorRules(Arrays.asList(rule));
detector.setRules(Arrays.asList(rule));
AnalysisConfig.Builder builder = new AnalysisConfig.Builder(Arrays.asList(detector.build()));
analysisConfig = builder.build();
@ -251,12 +249,12 @@ public class FieldConfigWriterTests extends ESTestCase {
createFieldConfigWriter().write();
verify(writer).write("detector.0.clause = count\n" +
"detector.0.rules = [{\"rule_action\":\"skip_sampling_and_filter_results\",\"conditions_connective\":\"and\"," +
"\"rule_conditions\":[{\"condition_type\":\"time\",\"condition\":{\"operator\":\"gte\",\"value\":\"1511395200\"}}," +
"{\"condition_type\":\"time\",\"condition\":{\"operator\":\"lt\",\"value\":\"1515369600\"}}]}," +
"{\"rule_action\":\"skip_sampling_and_filter_results\",\"conditions_connective\":\"and\"," +
"\"rule_conditions\":[{\"condition_type\":\"time\",\"condition\":{\"operator\":\"gte\",\"value\":\"1519603200\"}}," +
"{\"condition_type\":\"time\",\"condition\":{\"operator\":\"lt\",\"value\":\"1519862400\"}}]}]" +
"detector.0.rules = [{\"actions\":[\"filter_results\",\"skip_sampling\"],\"conditions_connective\":\"and\"," +
"\"conditions\":[{\"type\":\"time\",\"condition\":{\"operator\":\"gte\",\"value\":\"1511395200\"}}," +
"{\"type\":\"time\",\"condition\":{\"operator\":\"lt\",\"value\":\"1515369600\"}}]}," +
"{\"actions\":[\"filter_results\",\"skip_sampling\"],\"conditions_connective\":\"and\"," +
"\"conditions\":[{\"type\":\"time\",\"condition\":{\"operator\":\"gte\",\"value\":\"1519603200\"}}," +
"{\"type\":\"time\",\"condition\":{\"operator\":\"lt\",\"value\":\"1519862400\"}}]}]" +
"\n");
verifyNoMoreInteractions(writer);

View File

@ -0,0 +1,22 @@
{
"xpack.ml.delete_calendar_job": {
"methods": [ "DELETE" ],
"url": {
"path": "/_xpack/ml/calendars/{calendar_id}/jobs/{job_id}",
"paths": [ "/_xpack/ml/calendars/{calendar_id}/jobs/{job_id}" ],
"parts": {
"calendar_id": {
"type" : "string",
"required" : true,
"description" : "The ID of the calendar to modify"
},
"job_id": {
"type": "string",
"required": true,
"description": "The ID of the job to remove from the calendar"
}
}
},
"body": null
}
}

View File

@ -0,0 +1,22 @@
{
"xpack.ml.put_calendar_job": {
"methods": [ "PUT" ],
"url": {
"path": "/_xpack/ml/calendars/{calendar_id}/jobs/{job_id}",
"paths": [ "/_xpack/ml/calendars/{calendar_id}/jobs/{job_id}" ],
"parts": {
"calendar_id": {
"type": "string",
"required": true,
"description": "The ID of the calendar to modify"
},
"job_id": {
"type": "string",
"required": true,
"description": "The ID of the job to add to the calendar"
}
}
},
"body": null
}
}

View File

@ -1,25 +1,52 @@
---
"Test calendar CRUD":
- do:
xpack.ml.put_job:
job_id: cal-job
body: >
{
"analysis_config" : {
"detectors" :[{"function":"metric","field_name":"responsetime","by_field_name":"airline"}]
},
"data_description" : {
}
}
- match: { job_id: "cal-job" }
- do:
xpack.ml.put_job:
job_id: cal-job2
body: >
{
"analysis_config" : {
"detectors" :[{"function":"metric","field_name":"responsetime","by_field_name":"airline"}]
},
"data_description" : {
}
}
- match: { job_id: "cal-job2" }
- do:
xpack.ml.put_calendar:
calendar_id: "advent"
body: >
{
"job_ids": ["abc", "xyz"]
"job_ids": ["cal-job", "cal-job2"]
}
- match: { calendar_id: advent }
- match: { job_ids.0: abc }
- match: { job_ids.1: xyz }
- match: { job_ids.0: cal-job }
- match: { job_ids.1: cal-job2 }
- do:
xpack.ml.get_calendars:
calendar_id: "advent"
- match: { count: 1 }
- match: { count: 1 }
- match:
calendars.0:
calendar_id: "advent"
job_ids: ["abc", "xyz"]
job_ids: ["cal-job", "cal-job2"]
- is_false: type
- do:
@ -27,7 +54,7 @@
calendar_id: "Dogs of the Year"
body: >
{
"job_ids": ["abc2"]
"job_ids": ["cal-job"]
}
- do:
@ -51,6 +78,15 @@
xpack.ml.get_calendars:
calendar_id: "Dogs of the Year"
- do:
catch: missing
xpack.ml.put_calendar:
calendar_id: "new cal with unknown job"
body: >
{
"job_ids": ["cal-job", "unknown-job"]
}
---
"Test PageParams":
- do:
@ -66,14 +102,16 @@
- do:
xpack.ml.get_calendars:
from: 2
- match: { count: 1 }
- match: { count: 3 }
- length: { calendars: 1}
- match: { calendars.0.calendar_id: Calendar3 }
- do:
xpack.ml.get_calendars:
from: 1
size: 1
- match: { count: 1 }
- match: { count: 3 }
- length: { calendars: 1}
- match: { calendars.0.calendar_id: Calendar2 }
---
@ -90,10 +128,6 @@
- do:
xpack.ml.put_calendar:
calendar_id: "Mayan"
body: >
{
"job_ids": ["apocalypse"]
}
- do:
catch: /version_conflict_engine_exception/
@ -106,3 +140,97 @@
catch: bad_request
xpack.ml.put_calendar:
calendar_id: "_all"
---
"Test deleted job is removed from calendar":
- do:
xpack.ml.put_job:
job_id: cal-crud-test-delete
body: >
{
"analysis_config" : {
"detectors" :[{"function":"metric","field_name":"responsetime","by_field_name":"airline"}]
},
"data_description" : {
}
}
- match: { job_id: "cal-crud-test-delete" }
- do:
xpack.ml.put_calendar:
calendar_id: "delete-test"
body: >
{
"job_ids": ["cal-crud-test-delete"]
}
- do:
xpack.ml.delete_job:
job_id: cal-crud-test-delete
- match: { acknowledged: true }
- do:
xpack.ml.get_calendars:
calendar_id: "delete-test"
- match: { count: 1 }
- match: { calendars.0.job_ids: [] }
---
"Test update calendar":
- do:
xpack.ml.put_calendar:
calendar_id: "Wildlife"
- do:
xpack.ml.put_job:
job_id: tiger
body: >
{
"analysis_config" : {
"detectors" :[{"function":"metric","field_name":"responsetime","by_field_name":"airline"}]
},
"data_description" : {
}
}
- match: { job_id: "tiger" }
- do:
xpack.ml.put_calendar_job:
calendar_id: "Wildlife"
job_id: "tiger"
- match: { calendar_id: "Wildlife" }
- match: { job_ids.0: "tiger" }
- do:
xpack.ml.get_calendars:
calendar_id: "Wildlife"
- match: { count: 1 }
- match: { calendars.0.calendar_id: "Wildlife" }
- length: { calendars.0.job_ids: 1 }
- match: { calendars.0.job_ids.0: "tiger" }
- do:
xpack.ml.delete_calendar_job:
calendar_id: "Wildlife"
job_id: "tiger"
- do:
xpack.ml.get_calendars:
calendar_id: "Wildlife"
- match: { count: 1 }
- match: { calendars.0.calendar_id: "Wildlife" }
- length: { calendars.0.job_ids: 0 }
- do:
catch: missing
xpack.ml.put_calendar_job:
calendar_id: "Wildlife"
job_id: "missing job"
- do:
catch: missing
xpack.ml.delete_calendar_job:
calendar_id: "Wildlife"
job_id: "missing job"

View File

@ -152,12 +152,12 @@ setup:
"analysis_config" : {
"bucket_span": "3600s",
"detectors" :[{"function":"mean","field_name":"airline",
"detector_rules": [
"rules": [
{
"rule_conditions": [
"conditions": [
{
"condition_type": "categorical",
"value_filter": "filter-foo"
"type": "categorical",
"filter_id": "filter-foo"
}
]
}

View File

@ -301,8 +301,8 @@
"groups": ["group-1", "group-2"],
"description":"Post update description",
"detectors": [{"detector_index": 0, "rules": {"target_field_name": "airline",
"rule_conditions": [ { "condition_type": "numerical_actual",
"condition": {"operator": "gt", "value": "10" } } ] } },
"conditions": [ { "type": "numerical_actual",
"condition": {"operator": "gt", "value": "10" } } ] } },
{"detector_index": 1, "description": "updated description"}],
"model_plot_config": {
"enabled": false,
@ -327,7 +327,7 @@
- match: { model_plot_config.terms: "foobar" }
- match: { analysis_limits.model_memory_limit: "20mb" }
- match: { analysis_config.categorization_filters: ["cat3.*"] }
- match: { analysis_config.detectors.0.detector_rules.0.target_field_name: "airline" }
- match: { analysis_config.detectors.0.rules.0.target_field_name: "airline" }
- match: { analysis_config.detectors.0.detector_index: 0 }
- match: { analysis_config.detectors.1.detector_description: "updated description" }
- match: { analysis_config.detectors.1.detector_index: 1 }

View File

@ -20,6 +20,7 @@ import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.IndexNotFoundException;
@ -141,7 +142,7 @@ public class SecurityTribeTests extends NativeRealmIntegTestCase {
public void tearDownTribeNodeAndWipeCluster() throws Exception {
if (cluster2 != null) {
try {
cluster2.wipe(Collections.emptySet());
cluster2.wipe(Collections.singleton(SecurityLifecycleService.SECURITY_TEMPLATE_NAME));
try {
// this is a hack to clean up the .security index since only the XPackSecurity user or superusers can delete it
final Client cluster2Client = cluster2.client().filterWithHeader(Collections.singletonMap("Authorization",