[ML] Add Detector config classes to protocol library (#32495)

This commit adds the Detector class and its dependencies to the
X-Pack protocol library used by the high level REST client.

(Future commits will add the remaining config classes, plus results
and stats classes.)

These classes:

- Are immutable, with builders, but the builders do no validation
  beyond null checks
- Are convertible to and from X-Content, but NOT wire transportable
- Have lenient parsers to maximize compatibility across versions
- Have the same class names, member names and getter/setter names
  as the corresponding classes in X-Pack core to ease migration
  for transport client users
- Don't reproduce all the methods that do calculations or
  transformations that the the corresponding classes in X-Pack core
  have
This commit is contained in:
David Roberts 2018-08-03 10:39:29 +01:00 committed by GitHub
parent 937dcfd716
commit eb17128b9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1870 additions and 0 deletions

View File

@ -0,0 +1,96 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.Strings;
public final class DefaultDetectorDescription {
private static final String BY_TOKEN = " by ";
private static final String OVER_TOKEN = " over ";
private static final String USE_NULL_OPTION = " usenull=";
private static final String PARTITION_FIELD_OPTION = " partitionfield=";
private static final String EXCLUDE_FREQUENT_OPTION = " excludefrequent=";
private DefaultDetectorDescription() {
}
/**
* Returns the default description for the given {@code detector}
*
* @param detector the {@code Detector} for which a default description is requested
* @return the default description
*/
public static String of(Detector detector) {
StringBuilder sb = new StringBuilder();
appendOn(detector, sb);
return sb.toString();
}
/**
* Appends to the given {@code StringBuilder} the default description
* for the given {@code detector}
*
* @param detector the {@code Detector} for which a default description is requested
* @param sb the {@code StringBuilder} to append to
*/
public static void appendOn(Detector detector, StringBuilder sb) {
if (isNotNullOrEmpty(detector.getFunction().getFullName())) {
sb.append(detector.getFunction());
if (isNotNullOrEmpty(detector.getFieldName())) {
sb.append('(').append(quoteField(detector.getFieldName()))
.append(')');
}
} else if (isNotNullOrEmpty(detector.getFieldName())) {
sb.append(quoteField(detector.getFieldName()));
}
if (isNotNullOrEmpty(detector.getByFieldName())) {
sb.append(BY_TOKEN).append(quoteField(detector.getByFieldName()));
}
if (isNotNullOrEmpty(detector.getOverFieldName())) {
sb.append(OVER_TOKEN).append(quoteField(detector.getOverFieldName()));
}
if (detector.isUseNull()) {
sb.append(USE_NULL_OPTION).append(detector.isUseNull());
}
if (isNotNullOrEmpty(detector.getPartitionFieldName())) {
sb.append(PARTITION_FIELD_OPTION).append(quoteField(detector.getPartitionFieldName()));
}
if (detector.getExcludeFrequent() != null) {
sb.append(EXCLUDE_FREQUENT_OPTION).append(detector.getExcludeFrequent());
}
}
private static String quoteField(String field) {
if (field.matches("\\w*")) {
return field;
} else {
return "\"" + field.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
}
}
private static boolean isNotNullOrEmpty(String arg) {
return !Strings.isNullOrEmpty(arg);
}
}

View File

@ -0,0 +1,153 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
public class DetectionRule implements ToXContentObject {
public static final ParseField DETECTION_RULE_FIELD = new ParseField("detection_rule");
public static final ParseField ACTIONS_FIELD = new ParseField("actions");
public static final ParseField SCOPE_FIELD = new ParseField("scope");
public static final ParseField CONDITIONS_FIELD = new ParseField("conditions");
public static final ObjectParser<Builder, Void> PARSER =
new ObjectParser<>(DETECTION_RULE_FIELD.getPreferredName(), true, Builder::new);;
static {
PARSER.declareStringArray(Builder::setActions, ACTIONS_FIELD);
PARSER.declareObject(Builder::setScope, RuleScope.parser(), SCOPE_FIELD);
PARSER.declareObjectArray(Builder::setConditions, RuleCondition.PARSER, CONDITIONS_FIELD);
}
private final EnumSet<RuleAction> actions;
private final RuleScope scope;
private final List<RuleCondition> conditions;
private DetectionRule(EnumSet<RuleAction> actions, RuleScope scope, List<RuleCondition> conditions) {
this.actions = Objects.requireNonNull(actions);
this.scope = Objects.requireNonNull(scope);
this.conditions = Collections.unmodifiableList(conditions);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(ACTIONS_FIELD.getPreferredName(), actions);
if (scope.isEmpty() == false) {
builder.field(SCOPE_FIELD.getPreferredName(), scope);
}
if (conditions.isEmpty() == false) {
builder.field(CONDITIONS_FIELD.getPreferredName(), conditions);
}
builder.endObject();
return builder;
}
public EnumSet<RuleAction> getActions() {
return actions;
}
public RuleScope getScope() {
return scope;
}
public List<RuleCondition> getConditions() {
return conditions;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof DetectionRule == false) {
return false;
}
DetectionRule other = (DetectionRule) obj;
return Objects.equals(actions, other.actions)
&& Objects.equals(scope, other.scope)
&& Objects.equals(conditions, other.conditions);
}
@Override
public int hashCode() {
return Objects.hash(actions, scope, conditions);
}
public static class Builder {
private EnumSet<RuleAction> actions = EnumSet.of(RuleAction.SKIP_RESULT);
private RuleScope scope = new RuleScope();
private List<RuleCondition> conditions = Collections.emptyList();
public Builder(RuleScope.Builder scope) {
this.scope = scope.build();
}
public Builder(List<RuleCondition> conditions) {
this.conditions = Objects.requireNonNull(conditions);
}
Builder() {
}
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;
}
public Builder setScope(RuleScope scope) {
this.scope = Objects.requireNonNull(scope);
return this;
}
public Builder setConditions(List<RuleCondition> conditions) {
this.conditions = Objects.requireNonNull(conditions);
return this;
}
public DetectionRule build() {
return new DetectionRule(actions, scope, conditions);
}
}
}

View File

@ -0,0 +1,362 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* Defines the fields and functions used in the analysis. A combination of <code>field_name</code>,
* <code>by_field_name</code> and <code>over_field_name</code> can be used depending on the specific
* function chosen. For more information see
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-job-resource.html#ml-detectorconfig">configuring
* detectors</a> and <a href="https://www.elastic.co/guide/en/elastic-stack-overview/current/ml-functions.html">detector functions</a>.
*/
public class Detector implements ToXContentObject {
public enum ExcludeFrequent {
ALL,
NONE,
BY,
OVER;
/**
* Case-insensitive from string method.
* Works with either ALL, All, etc.
*
* @param value String representation
* @return The data format
*/
public static ExcludeFrequent forString(String value) {
return valueOf(value.toUpperCase(Locale.ROOT));
}
@Override
public String toString() {
return name().toLowerCase(Locale.ROOT);
}
}
public static final ParseField DETECTOR_DESCRIPTION_FIELD = new ParseField("detector_description");
public static final ParseField FUNCTION_FIELD = new ParseField("function");
public static final ParseField FIELD_NAME_FIELD = new ParseField("field_name");
public static final ParseField BY_FIELD_NAME_FIELD = new ParseField("by_field_name");
public static final ParseField OVER_FIELD_NAME_FIELD = new ParseField("over_field_name");
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 CUSTOM_RULES_FIELD = new ParseField("custom_rules");
public static final ParseField DETECTOR_INDEX = new ParseField("detector_index");
public static final ObjectParser<Builder, Void> PARSER = new ObjectParser<>("detector", true, Builder::new);
static {
PARSER.declareString(Builder::setDetectorDescription, DETECTOR_DESCRIPTION_FIELD);
PARSER.declareString(Builder::setFunction, FUNCTION_FIELD);
PARSER.declareString(Builder::setFieldName, FIELD_NAME_FIELD);
PARSER.declareString(Builder::setByFieldName, BY_FIELD_NAME_FIELD);
PARSER.declareString(Builder::setOverFieldName, OVER_FIELD_NAME_FIELD);
PARSER.declareString(Builder::setPartitionFieldName, PARTITION_FIELD_NAME_FIELD);
PARSER.declareBoolean(Builder::setUseNull, USE_NULL_FIELD);
PARSER.declareField(Builder::setExcludeFrequent, p -> {
if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
return ExcludeFrequent.forString(p.text());
}
throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
}, EXCLUDE_FREQUENT_FIELD, ObjectParser.ValueType.STRING);
PARSER.declareObjectArray(Builder::setRules, (p, c) -> DetectionRule.PARSER.apply(p, c).build(), CUSTOM_RULES_FIELD);
PARSER.declareInt(Builder::setDetectorIndex, DETECTOR_INDEX);
}
private final String detectorDescription;
private final DetectorFunction function;
private final String fieldName;
private final String byFieldName;
private final String overFieldName;
private final String partitionFieldName;
private final boolean useNull;
private final ExcludeFrequent excludeFrequent;
private final List<DetectionRule> rules;
private final int detectorIndex;
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(DETECTOR_DESCRIPTION_FIELD.getPreferredName(), detectorDescription);
builder.field(FUNCTION_FIELD.getPreferredName(), function);
if (fieldName != null) {
builder.field(FIELD_NAME_FIELD.getPreferredName(), fieldName);
}
if (byFieldName != null) {
builder.field(BY_FIELD_NAME_FIELD.getPreferredName(), byFieldName);
}
if (overFieldName != null) {
builder.field(OVER_FIELD_NAME_FIELD.getPreferredName(), overFieldName);
}
if (partitionFieldName != null) {
builder.field(PARTITION_FIELD_NAME_FIELD.getPreferredName(), partitionFieldName);
}
if (useNull) {
builder.field(USE_NULL_FIELD.getPreferredName(), useNull);
}
if (excludeFrequent != null) {
builder.field(EXCLUDE_FREQUENT_FIELD.getPreferredName(), excludeFrequent);
}
if (rules.isEmpty() == false) {
builder.field(CUSTOM_RULES_FIELD.getPreferredName(), rules);
}
// negative means unknown
if (detectorIndex >= 0) {
builder.field(DETECTOR_INDEX.getPreferredName(), detectorIndex);
}
builder.endObject();
return builder;
}
private Detector(String detectorDescription, DetectorFunction function, String fieldName, String byFieldName, String overFieldName,
String partitionFieldName, boolean useNull, ExcludeFrequent excludeFrequent, List<DetectionRule> rules,
int detectorIndex) {
this.function = function;
this.fieldName = fieldName;
this.byFieldName = byFieldName;
this.overFieldName = overFieldName;
this.partitionFieldName = partitionFieldName;
this.useNull = useNull;
this.excludeFrequent = excludeFrequent;
this.rules = Collections.unmodifiableList(rules);
this.detectorDescription = detectorDescription != null ? detectorDescription : DefaultDetectorDescription.of(this);
this.detectorIndex = detectorIndex;
}
public String getDetectorDescription() {
return detectorDescription;
}
/**
* The analysis function used e.g. count, rare, min etc.
*
* @return The function or <code>null</code> if not set
*/
public DetectorFunction getFunction() {
return function;
}
/**
* The Analysis field
*
* @return The field to analyse
*/
public String getFieldName() {
return fieldName;
}
/**
* The 'by' field or <code>null</code> if not set.
*
* @return The 'by' field
*/
public String getByFieldName() {
return byFieldName;
}
/**
* The 'over' field or <code>null</code> if not set.
*
* @return The 'over' field
*/
public String getOverFieldName() {
return overFieldName;
}
/**
* Segments the analysis along another field to have completely
* independent baselines for each instance of partitionfield
*
* @return The Partition Field
*/
public String getPartitionFieldName() {
return partitionFieldName;
}
/**
* Where there isn't a value for the 'by' or 'over' field should a new
* series be used as the 'null' series.
*
* @return true if the 'null' series should be created
*/
public boolean isUseNull() {
return useNull;
}
/**
* Excludes frequently-occuring metrics from the analysis;
* can apply to 'by' field, 'over' field, or both
*
* @return the value that the user set
*/
public ExcludeFrequent getExcludeFrequent() {
return excludeFrequent;
}
public List<DetectionRule> getRules() {
return rules;
}
/**
* @return the detector index or a negative number if unknown
*/
public int getDetectorIndex() {
return detectorIndex;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other instanceof Detector == false) {
return false;
}
Detector that = (Detector) other;
return Objects.equals(this.detectorDescription, that.detectorDescription) &&
Objects.equals(this.function, that.function) &&
Objects.equals(this.fieldName, that.fieldName) &&
Objects.equals(this.byFieldName, that.byFieldName) &&
Objects.equals(this.overFieldName, that.overFieldName) &&
Objects.equals(this.partitionFieldName, that.partitionFieldName) &&
Objects.equals(this.useNull, that.useNull) &&
Objects.equals(this.excludeFrequent, that.excludeFrequent) &&
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, rules, detectorIndex);
}
public static class Builder {
private String detectorDescription;
private DetectorFunction function;
private String fieldName;
private String byFieldName;
private String overFieldName;
private String partitionFieldName;
private boolean useNull = false;
private ExcludeFrequent excludeFrequent;
private List<DetectionRule> rules = Collections.emptyList();
// negative means unknown
private int detectorIndex = -1;
public Builder() {
}
public Builder(Detector detector) {
detectorDescription = detector.detectorDescription;
function = detector.function;
fieldName = detector.fieldName;
byFieldName = detector.byFieldName;
overFieldName = detector.overFieldName;
partitionFieldName = detector.partitionFieldName;
useNull = detector.useNull;
excludeFrequent = detector.excludeFrequent;
rules = new ArrayList<>(detector.rules);
detectorIndex = detector.detectorIndex;
}
public Builder(String function, String fieldName) {
this(DetectorFunction.fromString(function), fieldName);
}
public Builder(DetectorFunction function, String fieldName) {
this.function = function;
this.fieldName = fieldName;
}
public Builder setDetectorDescription(String detectorDescription) {
this.detectorDescription = detectorDescription;
return this;
}
public Builder setFunction(String function) {
this.function = DetectorFunction.fromString(function);
return this;
}
public Builder setFieldName(String fieldName) {
this.fieldName = fieldName;
return this;
}
public Builder setByFieldName(String byFieldName) {
this.byFieldName = byFieldName;
return this;
}
public Builder setOverFieldName(String overFieldName) {
this.overFieldName = overFieldName;
return this;
}
public Builder setPartitionFieldName(String partitionFieldName) {
this.partitionFieldName = partitionFieldName;
return this;
}
public Builder setUseNull(boolean useNull) {
this.useNull = useNull;
return this;
}
public Builder setExcludeFrequent(ExcludeFrequent excludeFrequent) {
this.excludeFrequent = excludeFrequent;
return this;
}
public Builder setRules(List<DetectionRule> rules) {
this.rules = rules;
return this;
}
public Builder setDetectorIndex(int detectorIndex) {
this.detectorIndex = detectorIndex;
return this;
}
public Detector build() {
return new Detector(detectorDescription, function, fieldName, byFieldName, overFieldName, partitionFieldName,
useNull, excludeFrequent, rules, detectorIndex);
}
}
}

View File

@ -0,0 +1,95 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
public enum DetectorFunction {
COUNT,
LOW_COUNT,
HIGH_COUNT,
NON_ZERO_COUNT("nzc"),
LOW_NON_ZERO_COUNT("low_nzc"),
HIGH_NON_ZERO_COUNT("high_nzc"),
DISTINCT_COUNT("dc"),
LOW_DISTINCT_COUNT("low_dc"),
HIGH_DISTINCT_COUNT("high_dc"),
RARE,
FREQ_RARE,
INFO_CONTENT,
LOW_INFO_CONTENT,
HIGH_INFO_CONTENT,
METRIC,
MEAN,
LOW_MEAN,
HIGH_MEAN,
AVG,
LOW_AVG,
HIGH_AVG,
MEDIAN,
LOW_MEDIAN,
HIGH_MEDIAN,
MIN,
MAX,
SUM,
LOW_SUM,
HIGH_SUM,
NON_NULL_SUM,
LOW_NON_NULL_SUM,
HIGH_NON_NULL_SUM,
VARP,
LOW_VARP,
HIGH_VARP,
TIME_OF_DAY,
TIME_OF_WEEK,
LAT_LONG;
private Set<String> shortcuts;
DetectorFunction() {
shortcuts = Collections.emptySet();
}
DetectorFunction(String... shortcuts) {
this.shortcuts = Arrays.stream(shortcuts).collect(Collectors.toSet());
}
public String getFullName() {
return name().toLowerCase(Locale.ROOT);
}
@Override
public String toString() {
return getFullName();
}
public static DetectorFunction fromString(String op) {
for (DetectorFunction function : values()) {
if (function.getFullName().equals(op) || function.shortcuts.contains(op)) {
return function;
}
}
throw new IllegalArgumentException("Unknown detector function [" + op + "]");
}
}

View File

@ -0,0 +1,107 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
public class FilterRef implements ToXContentObject {
public static final ParseField FILTER_REF_FIELD = new ParseField("filter_ref");
public static final ParseField FILTER_ID = new ParseField("filter_id");
public static final ParseField FILTER_TYPE = new ParseField("filter_type");
public enum FilterType {
INCLUDE, EXCLUDE;
public static FilterType fromString(String value) {
return valueOf(value.toUpperCase(Locale.ROOT));
}
@Override
public String toString() {
return name().toLowerCase(Locale.ROOT);
}
}
public static final ConstructingObjectParser<FilterRef, Void> PARSER =
new ConstructingObjectParser<>(FILTER_REF_FIELD.getPreferredName(), true, a -> new FilterRef((String) a[0], (FilterType) a[1]));
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), FILTER_ID);
PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(), p -> {
if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
return FilterType.fromString(p.text());
}
throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
}, FILTER_TYPE, ObjectParser.ValueType.STRING);
}
private final String filterId;
private final FilterType filterType;
public FilterRef(String filterId, FilterType filterType) {
this.filterId = Objects.requireNonNull(filterId);
this.filterType = filterType == null ? FilterType.INCLUDE : filterType;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FILTER_ID.getPreferredName(), filterId);
builder.field(FILTER_TYPE.getPreferredName(), filterType);
builder.endObject();
return builder;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof FilterRef == false) {
return false;
}
FilterRef other = (FilterRef) obj;
return Objects.equals(filterId, other.filterId) && Objects.equals(filterType, other.filterType);
}
@Override
public int hashCode() {
return Objects.hash(filterId, filterType);
}
public String getFilterId() {
return filterId;
}
public FilterType getFilterType() {
return filterType;
}
}

View File

@ -0,0 +1,155 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
public class MlFilter implements ToXContentObject {
public static final ParseField TYPE = new ParseField("type");
public static final ParseField ID = new ParseField("filter_id");
public static final ParseField DESCRIPTION = new ParseField("description");
public static final ParseField ITEMS = new ParseField("items");
// For QueryPage
public static final ParseField RESULTS_FIELD = new ParseField("filters");
public static final ObjectParser<Builder, Void> PARSER = new ObjectParser<>(TYPE.getPreferredName(), true, Builder::new);
static {
PARSER.declareString((builder, s) -> {}, TYPE);
PARSER.declareString(Builder::setId, ID);
PARSER.declareStringOrNull(Builder::setDescription, DESCRIPTION);
PARSER.declareStringArray(Builder::setItems, ITEMS);
}
private final String id;
private final String description;
private final SortedSet<String> items;
private MlFilter(String id, String description, SortedSet<String> items) {
this.id = Objects.requireNonNull(id);
this.description = description;
this.items = Collections.unmodifiableSortedSet(items);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(ID.getPreferredName(), id);
if (description != null) {
builder.field(DESCRIPTION.getPreferredName(), description);
}
builder.field(ITEMS.getPreferredName(), items);
// Don't include TYPE as it's fixed
builder.endObject();
return builder;
}
public String getId() {
return id;
}
public String getDescription() {
return description;
}
public SortedSet<String> getItems() {
return items;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof MlFilter == false) {
return false;
}
MlFilter other = (MlFilter) obj;
return id.equals(other.id) && Objects.equals(description, other.description) && items.equals(other.items);
}
@Override
public int hashCode() {
return Objects.hash(id, description, items);
}
public static Builder builder(String filterId) {
return new Builder().setId(filterId);
}
public static class Builder {
private String id;
private String description;
private SortedSet<String> items = new TreeSet<>();
private Builder() {
}
public Builder setId(String id) {
this.id = Objects.requireNonNull(id);
return this;
}
@Nullable
public String getId() {
return id;
}
public Builder setDescription(String description) {
this.description = description;
return this;
}
public Builder setItems(SortedSet<String> items) {
this.items = Objects.requireNonNull(items);
return this;
}
public Builder setItems(List<String> items) {
this.items = new TreeSet<>(items);
return this;
}
public Builder setItems(String... items) {
setItems(Arrays.asList(items));
return this;
}
public MlFilter build() {
return new MlFilter(id, description, items);
}
}
}

View File

@ -0,0 +1,71 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.ParseField;
import java.util.Locale;
/**
* Enum representing logical comparisons on doubles
*/
public enum Operator {
GT {
@Override
public boolean test(double lhs, double rhs) {
return Double.compare(lhs, rhs) > 0;
}
},
GTE {
@Override
public boolean test(double lhs, double rhs) {
return Double.compare(lhs, rhs) >= 0;
}
},
LT {
@Override
public boolean test(double lhs, double rhs) {
return Double.compare(lhs, rhs) < 0;
}
},
LTE {
@Override
public boolean test(double lhs, double rhs) {
return Double.compare(lhs, rhs) <= 0;
}
};
// EQ was considered but given the oddity of such a
// condition and the fact that it would be a numerically
// unstable condition, it was rejected.
public static final ParseField OPERATOR_FIELD = new ParseField("operator");
public boolean test(double lhs, double rhs) {
return false;
}
public static Operator fromString(String name) {
return valueOf(name.trim().toUpperCase(Locale.ROOT));
}
@Override
public String toString() {
return name().toLowerCase(Locale.ROOT);
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import java.util.Locale;
public enum RuleAction {
SKIP_RESULT,
SKIP_MODEL_UPDATE;
/**
* Case-insensitive from string method.
*
* @param value String representation
* @return The rule action
*/
public static RuleAction fromString(String value) {
return RuleAction.valueOf(value.toUpperCase(Locale.ROOT));
}
@Override
public String toString() {
return name().toLowerCase(Locale.ROOT);
}
}

View File

@ -0,0 +1,129 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
public class RuleCondition implements ToXContentObject {
public static final ParseField RULE_CONDITION_FIELD = new ParseField("rule_condition");
public static final ParseField APPLIES_TO_FIELD = new ParseField("applies_to");
public static final ParseField VALUE_FIELD = new ParseField("value");
public static final ConstructingObjectParser<RuleCondition, Void> PARSER =
new ConstructingObjectParser<>(RULE_CONDITION_FIELD.getPreferredName(), true,
a -> new RuleCondition((AppliesTo) a[0], (Operator) a[1], (double) a[2]));
static {
PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> {
if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
return AppliesTo.fromString(p.text());
}
throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
}, APPLIES_TO_FIELD, ValueType.STRING);
PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> {
if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
return Operator.fromString(p.text());
}
throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
}, Operator.OPERATOR_FIELD, ValueType.STRING);
PARSER.declareDouble(ConstructingObjectParser.constructorArg(), VALUE_FIELD);
}
private final AppliesTo appliesTo;
private final Operator operator;
private final double value;
public RuleCondition(AppliesTo appliesTo, Operator operator, double value) {
this.appliesTo = appliesTo;
this.operator = operator;
this.value = value;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(APPLIES_TO_FIELD.getPreferredName(), appliesTo);
builder.field(Operator.OPERATOR_FIELD.getPreferredName(), operator);
builder.field(VALUE_FIELD.getPreferredName(), value);
builder.endObject();
return builder;
}
public AppliesTo getAppliesTo() {
return appliesTo;
}
public Operator getOperator() {
return operator;
}
public double getValue() {
return value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof RuleCondition == false) {
return false;
}
RuleCondition other = (RuleCondition) obj;
return appliesTo == other.appliesTo && operator == other.operator && value == other.value;
}
@Override
public int hashCode() {
return Objects.hash(appliesTo, operator, value);
}
public static RuleCondition createTime(Operator operator, long epochSeconds) {
return new RuleCondition(AppliesTo.TIME, operator, epochSeconds);
}
public enum AppliesTo {
ACTUAL,
TYPICAL,
DIFF_FROM_TYPICAL,
TIME;
public static AppliesTo fromString(String value) {
return valueOf(value.toUpperCase(Locale.ROOT));
}
@Override
public String toString() {
return name().toLowerCase(Locale.ROOT);
}
}
}

View File

@ -0,0 +1,133 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ContextParser;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
public class RuleScope implements ToXContentObject {
public static ContextParser<Void, RuleScope> parser() {
return (p, c) -> {
Map<String, Object> unparsedScope = p.map();
if (unparsedScope.isEmpty()) {
return new RuleScope();
}
Map<String, FilterRef> scope = new HashMap<>();
for (Map.Entry<String, Object> entry : unparsedScope.entrySet()) {
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
@SuppressWarnings("unchecked")
Map<String, ?> value = (Map<String, ?>) entry.getValue();
builder.map(value);
try (XContentParser scopeParser = XContentFactory.xContent(builder.contentType()).createParser(
NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, Strings.toString(builder))) {
scope.put(entry.getKey(), FilterRef.PARSER.parse(scopeParser, null));
}
}
}
return new RuleScope(scope);
};
}
private final Map<String, FilterRef> scope;
public RuleScope() {
scope = Collections.emptyMap();
}
public RuleScope(Map<String, FilterRef> scope) {
this.scope = Collections.unmodifiableMap(scope);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.map(scope);
}
public boolean isEmpty() {
return scope.isEmpty();
}
public Set<String> getReferencedFilters() {
return scope.values().stream().map(FilterRef::getFilterId).collect(Collectors.toSet());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof RuleScope == false) {
return false;
}
RuleScope other = (RuleScope) obj;
return Objects.equals(scope, other.scope);
}
@Override
public int hashCode() {
return Objects.hash(scope);
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Map<String, FilterRef> scope = new HashMap<>();
public Builder() {
}
public Builder(RuleScope otherScope) {
scope = new HashMap<>(otherScope.scope);
}
public Builder exclude(String field, String filterId) {
scope.put(field, new FilterRef(filterId, FilterRef.FilterType.EXCLUDE));
return this;
}
public Builder include(String field, String filterId) {
scope.put(field, new FilterRef(filterId, FilterRef.FilterType.INCLUDE));
return this;
}
public RuleScope build() {
return new RuleScope(scope);
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Request and Response objects for the default distribution's Machine
* Learning APIs.
*/
package org.elasticsearch.protocol.xpack.ml;

View File

@ -0,0 +1,93 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DetectionRuleTests extends AbstractXContentTestCase<DetectionRule> {
@Override
protected DetectionRule createTestInstance() {
DetectionRule.Builder builder = new DetectionRule.Builder();
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);
}
boolean hasScope = randomBoolean();
boolean hasConditions = randomBoolean();
if (!hasScope && !hasConditions) {
// at least one of the two should be present
if (randomBoolean()) {
hasScope = true;
} else {
hasConditions = true;
}
}
if (hasScope) {
Map<String, FilterRef> scope = new HashMap<>();
int scopeSize = randomIntBetween(1, 3);
for (int i = 0; i < scopeSize; i++) {
scope.put(randomAlphaOfLength(20), new FilterRef(randomAlphaOfLength(20), randomFrom(FilterRef.FilterType.values())));
}
builder.setScope(new RuleScope(scope));
}
if (hasConditions) {
int size = randomIntBetween(1, 5);
List<RuleCondition> ruleConditions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
// no need for random condition (it is already tested)
ruleConditions.addAll(createCondition(randomDouble()));
}
builder.setConditions(ruleConditions);
}
return builder.build();
}
@Override
protected DetectionRule doParseInstance(XContentParser parser) {
return DetectionRule.PARSER.apply(parser, null).build();
}
private static List<RuleCondition> createCondition(double value) {
return Collections.singletonList(new RuleCondition(RuleCondition.AppliesTo.ACTUAL, Operator.GT, value));
}
@Override
protected boolean supportsUnknownFields() {
return false;
}
}

View File

@ -0,0 +1,137 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
public class DetectorTests extends AbstractXContentTestCase<Detector> {
public void testEquals_GivenEqual() {
Detector.Builder builder = new Detector.Builder("mean", "field");
builder.setByFieldName("by_field");
builder.setOverFieldName("over_field");
builder.setPartitionFieldName("partition");
builder.setUseNull(false);
Detector detector1 = builder.build();
builder = new Detector.Builder("mean", "field");
builder.setByFieldName("by_field");
builder.setOverFieldName("over_field");
builder.setPartitionFieldName("partition");
builder.setUseNull(false);
Detector detector2 = builder.build();
assertTrue(detector1.equals(detector2));
assertTrue(detector2.equals(detector1));
assertEquals(detector1.hashCode(), detector2.hashCode());
}
public void testEquals_GivenDifferentDetectorDescription() {
Detector detector1 = createDetector().build();
Detector.Builder builder = createDetector();
builder.setDetectorDescription("bar");
Detector detector2 = builder.build();
assertFalse(detector1.equals(detector2));
}
public void testEquals_GivenDifferentByFieldName() {
Detector detector1 = createDetector().build();
Detector detector2 = createDetector().build();
assertEquals(detector1, detector2);
Detector.Builder builder = new Detector.Builder(detector2);
builder.setByFieldName("by2");
detector2 = builder.build();
assertFalse(detector1.equals(detector2));
}
private Detector.Builder createDetector() {
Detector.Builder detector = new Detector.Builder("mean", "field");
detector.setByFieldName("by_field");
detector.setOverFieldName("over_field");
detector.setPartitionFieldName("partition");
detector.setUseNull(true);
DetectionRule rule = new DetectionRule.Builder(RuleScope.builder().exclude("partition", "partition_filter"))
.setActions(RuleAction.SKIP_RESULT)
.build();
detector.setRules(Collections.singletonList(rule));
return detector;
}
@Override
protected Detector createTestInstance() {
DetectorFunction function = randomFrom(EnumSet.allOf(DetectorFunction.class));
Detector.Builder detector = new Detector.Builder(function, randomBoolean() ? null : randomAlphaOfLengthBetween(1, 20));
if (randomBoolean()) {
detector.setDetectorDescription(randomAlphaOfLengthBetween(1, 20));
}
if (randomBoolean()) {
detector.setPartitionFieldName(randomAlphaOfLengthBetween(6, 20));
} else if (randomBoolean()) {
detector.setOverFieldName(randomAlphaOfLengthBetween(6, 20));
} else if (randomBoolean()) {
detector.setByFieldName(randomAlphaOfLengthBetween(6, 20));
}
if (randomBoolean()) {
detector.setExcludeFrequent(randomFrom(Detector.ExcludeFrequent.values()));
}
if (randomBoolean()) {
int size = randomInt(10);
List<DetectionRule> rules = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
// no need for random DetectionRule (it is already tested)
rules.add(new DetectionRule.Builder(Collections.singletonList(RuleConditionTests.createRandom())).build());
}
detector.setRules(rules);
}
if (randomBoolean()) {
detector.setUseNull(randomBoolean());
}
return detector.build();
}
@Override
protected Detector doParseInstance(XContentParser parser) {
return Detector.PARSER.apply(parser, null).build();
}
public void testExcludeFrequentForString() {
assertEquals(Detector.ExcludeFrequent.ALL, Detector.ExcludeFrequent.forString("all"));
assertEquals(Detector.ExcludeFrequent.ALL, Detector.ExcludeFrequent.forString("ALL"));
assertEquals(Detector.ExcludeFrequent.NONE, Detector.ExcludeFrequent.forString("none"));
assertEquals(Detector.ExcludeFrequent.NONE, Detector.ExcludeFrequent.forString("NONE"));
assertEquals(Detector.ExcludeFrequent.BY, Detector.ExcludeFrequent.forString("by"));
assertEquals(Detector.ExcludeFrequent.BY, Detector.ExcludeFrequent.forString("BY"));
assertEquals(Detector.ExcludeFrequent.OVER, Detector.ExcludeFrequent.forString("over"));
assertEquals(Detector.ExcludeFrequent.OVER, Detector.ExcludeFrequent.forString("OVER"));
}
@Override
protected boolean supportsUnknownFields() {
return true;
}
}

View File

@ -0,0 +1,42 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.io.IOException;
public class FilterRefTests extends AbstractXContentTestCase<FilterRef> {
@Override
protected FilterRef createTestInstance() {
return new FilterRef(randomAlphaOfLength(20), randomFrom(FilterRef.FilterType.values()));
}
@Override
protected FilterRef doParseInstance(XContentParser parser) throws IOException {
return FilterRef.PARSER.parse(parser, null);
}
@Override
protected boolean supportsUnknownFields() {
return true;
}
}

View File

@ -0,0 +1,86 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.util.SortedSet;
import java.util.TreeSet;
import static org.hamcrest.Matchers.contains;
public class MlFilterTests extends AbstractXContentTestCase<MlFilter> {
public static MlFilter createTestFilter() {
return new MlFilterTests().createTestInstance();
}
@Override
protected MlFilter createTestInstance() {
return createRandom();
}
public static MlFilter createRandom() {
return createRandom(randomAlphaOfLength(10));
}
public static MlFilter createRandom(String filterId) {
String description = null;
if (randomBoolean()) {
description = randomAlphaOfLength(20);
}
int size = randomInt(10);
SortedSet<String> items = new TreeSet<>();
for (int i = 0; i < size; i++) {
items.add(randomAlphaOfLengthBetween(1, 20));
}
return MlFilter.builder(filterId).setDescription(description).setItems(items).build();
}
@Override
protected MlFilter doParseInstance(XContentParser parser) {
return MlFilter.PARSER.apply(parser, null).build();
}
public void testNullId() {
expectThrows(NullPointerException.class, () -> MlFilter.builder(null).build());
}
public void testNullItems() {
expectThrows(NullPointerException.class,
() -> MlFilter.builder(randomAlphaOfLength(10)).setItems((SortedSet<String>) null).build());
}
public void testItemsAreSorted() {
MlFilter filter = MlFilter.builder("foo").setItems("c", "b", "a").build();
assertThat(filter.getItems(), contains("a", "b", "c"));
}
public void testGetItemsReturnsUnmodifiable() {
MlFilter filter = MlFilter.builder("foo").setItems("c", "b", "a").build();
expectThrows(UnsupportedOperationException.class, () -> filter.getItems().add("x"));
}
@Override
protected boolean supportsUnknownFields() {
return true;
}
}

View File

@ -0,0 +1,80 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
public class RuleConditionTests extends AbstractXContentTestCase<RuleCondition> {
@Override
protected RuleCondition createTestInstance() {
return createRandom();
}
public static RuleCondition createRandom() {
RuleCondition.AppliesTo appliesTo = randomFrom(RuleCondition.AppliesTo.values());
Operator operator = randomFrom(Operator.LT, Operator.LTE, Operator.GT, Operator.GTE);
return new RuleCondition(appliesTo, operator, randomDouble());
}
@Override
protected RuleCondition doParseInstance(XContentParser parser) {
return RuleCondition.PARSER.apply(parser, null);
}
public void testEqualsGivenSameObject() {
RuleCondition condition = createRandom();
assertTrue(condition.equals(condition));
}
public void testEqualsGivenString() {
assertFalse(createRandom().equals("a string"));
}
public void testCreateTimeBased() {
RuleCondition timeBased = RuleCondition.createTime(Operator.GTE, 100L);
assertEquals(RuleCondition.AppliesTo.TIME, timeBased.getAppliesTo());
assertEquals(Operator.GTE, timeBased.getOperator());
assertEquals(100.0, timeBased.getValue(), 0.000001);
}
public void testAppliesToFromString() {
assertEquals(RuleCondition.AppliesTo.ACTUAL, RuleCondition.AppliesTo.fromString("actual"));
assertEquals(RuleCondition.AppliesTo.ACTUAL, RuleCondition.AppliesTo.fromString("ACTUAL"));
assertEquals(RuleCondition.AppliesTo.TYPICAL, RuleCondition.AppliesTo.fromString("typical"));
assertEquals(RuleCondition.AppliesTo.TYPICAL, RuleCondition.AppliesTo.fromString("TYPICAL"));
assertEquals(RuleCondition.AppliesTo.DIFF_FROM_TYPICAL, RuleCondition.AppliesTo.fromString("diff_from_typical"));
assertEquals(RuleCondition.AppliesTo.DIFF_FROM_TYPICAL, RuleCondition.AppliesTo.fromString("DIFF_FROM_TYPICAL"));
assertEquals(RuleCondition.AppliesTo.TIME, RuleCondition.AppliesTo.fromString("time"));
assertEquals(RuleCondition.AppliesTo.TIME, RuleCondition.AppliesTo.fromString("TIME"));
}
public void testAppliesToToString() {
assertEquals("actual", RuleCondition.AppliesTo.ACTUAL.toString());
assertEquals("typical", RuleCondition.AppliesTo.TYPICAL.toString());
assertEquals("diff_from_typical", RuleCondition.AppliesTo.DIFF_FROM_TYPICAL.toString());
assertEquals("time", RuleCondition.AppliesTo.TIME.toString());
}
@Override
protected boolean supportsUnknownFields() {
return true;
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.protocol.xpack.ml.job.config;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.io.IOException;
import static org.hamcrest.Matchers.contains;
public class RuleScopeTests extends AbstractXContentTestCase<RuleScope> {
@Override
protected RuleScope createTestInstance() {
RuleScope.Builder scope = RuleScope.builder();
int count = randomIntBetween(0, 3);
for (int i = 0; i < count; ++i) {
if (randomBoolean()) {
scope.include(randomAlphaOfLength(20), randomAlphaOfLength(20));
} else {
scope.exclude(randomAlphaOfLength(20), randomAlphaOfLength(20));
}
}
return scope.build();
}
public void testGetReferencedFilters_GivenEmpty() {
assertTrue(RuleScope.builder().build().getReferencedFilters().isEmpty());
}
public void testGetReferencedFilters_GivenMultipleFields() {
RuleScope scope = RuleScope.builder()
.include("foo", "filter1")
.exclude("bar", "filter2")
.include("foobar", "filter3")
.build();
assertThat(scope.getReferencedFilters(), contains("filter1", "filter2", "filter3"));
}
@Override
protected RuleScope doParseInstance(XContentParser parser) throws IOException {
return RuleScope.parser().parse(parser, null);
}
@Override
protected boolean supportsUnknownFields() {
return false;
}
}