aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImplTest.java6
-rw-r--r--flags/pom.xml6
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java32
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java8
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/BlacklistCondition.java11
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/Condition.java72
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java1
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/ListCondition.java39
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java58
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/RelationalOperator.java39
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java43
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/WhitelistCondition.java11
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireCondition.java1
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/json/ConditionTest.java40
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/json/SerializationTest.java12
15 files changed, 328 insertions, 51 deletions
diff --git a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImplTest.java b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImplTest.java
index 7460e42c866..e366c012a9e 100644
--- a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImplTest.java
+++ b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImplTest.java
@@ -8,12 +8,15 @@ import com.yahoo.vespa.flags.JsonNodeRawFlag;
import com.yahoo.vespa.flags.json.Condition;
import com.yahoo.vespa.flags.json.FlagData;
import com.yahoo.vespa.flags.json.Rule;
+import com.yahoo.vespa.flags.json.WhitelistCondition;
import org.junit.Test;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
@@ -29,7 +32,8 @@ public class FlagsDbImplTest {
MockCurator curator = new MockCurator();
FlagsDbImpl db = new FlagsDbImpl(curator);
- Condition condition1 = new Condition(Condition.Type.WHITELIST, FetchVector.Dimension.HOSTNAME, "host1");
+ var params = new Condition.CreateParams(FetchVector.Dimension.HOSTNAME, List.of("host1"), Optional.empty());
+ Condition condition1 = new WhitelistCondition(params);
Rule rule1 = new Rule(Optional.of(JsonNodeRawFlag.fromJson("13")), condition1);
FlagId flagId = new FlagId("id");
FlagData data = new FlagData(flagId, new FetchVector().with(FetchVector.Dimension.ZONE_ID, "zone-a"), rule1);
diff --git a/flags/pom.xml b/flags/pom.xml
index c1e9eca20ab..6afa920e261 100644
--- a/flags/pom.xml
+++ b/flags/pom.xml
@@ -27,6 +27,12 @@
</dependency>
<dependency>
<groupId>com.yahoo.vespa</groupId>
+ <artifactId>component</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
<artifactId>defaults</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
index d2228c58c51..a4fbec4922f 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
@@ -24,16 +24,6 @@ public class FetchVector {
* Note: If this enum is changed, you must also change {@link DimensionHelper}.
*/
public enum Dimension {
- /**
- * WARNING: DO NOT USE
- *
- * <p>ALL flags can be set differently in different zones: This dimension is ONLY useful for the controller
- * that needs to handle multiple zones.
- *
- * <p>Value from ZoneId::value is of the form environment.region.
- */
- ZONE_ID,
-
/** Value from ApplicationId::serializedForm of the form tenant:applicationName:instance. */
APPLICATION_ID,
@@ -44,7 +34,27 @@ public class FetchVector {
NODE_TYPE,
/** Cluster type from com.yahoo.config.provision.ClusterSpec.Type::name, e.g. content, container, admin */
- CLUSTER_TYPE
+ CLUSTER_TYPE,
+
+ /**
+ * WARNING: DO SET THIS DIMENSION FOR A FLAG
+ *
+ * <p>ALL flags can be set differently in different zones: This dimension is ONLY useful for the controller
+ * that needs to handle multiple zones.
+ *
+ * <p>Value from ZoneId::value is of the form environment.region.
+ */
+ ZONE_ID,
+
+ /**
+ * WARNING: DO SET THIS DIMENSION FOR A FLAG
+ *
+ * <p>The Vespa version is always fetched implicitly from {@link com.yahoo.component.Vtag#currentVersion}.
+ *
+ * <p>Value from Version::toFullString is of the form Major.Minor.Micro[.qualifier]. When ordering
+ * versions, note that 7.3 == 7.3.0.
+ */
+ VESPA_VERSION
}
private final Map<Dimension, String> map;
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index a27171f29a2..ef5e8451650 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -1,6 +1,7 @@
// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
+import com.yahoo.component.Vtag;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.flags.custom.PreprovisionCapacity;
@@ -11,6 +12,7 @@ import java.util.TreeMap;
import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.HOSTNAME;
import static com.yahoo.vespa.flags.FetchVector.Dimension.NODE_TYPE;
+import static com.yahoo.vespa.flags.FetchVector.Dimension.VESPA_VERSION;
/**
* Definitions of feature flags.
@@ -245,7 +247,11 @@ public class Flags {
String modificationEffect,
FetchVector.Dimension[] dimensions) {
FlagId id = new FlagId(flagId);
- FetchVector vector = new FetchVector().with(HOSTNAME, Defaults.getDefaults().vespaHostname());
+ FetchVector vector = new FetchVector()
+ .with(HOSTNAME, Defaults.getDefaults().vespaHostname())
+ // Warning: In unit tests and outside official Vespa releases, the currentVersion is e.g. 7.0.0
+ // (determined by the current major version). Consider not setting VESPA_VERSION if minor = micro = 0.
+ .with(VESPA_VERSION, Vtag.currentVersion.toFullString());
U unboundFlag = factory.create(id, defaultValue, vector);
FlagDefinition definition = new FlagDefinition(unboundFlag, description, modificationEffect, dimensions);
flags.put(id, definition);
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/BlacklistCondition.java b/flags/src/main/java/com/yahoo/vespa/flags/json/BlacklistCondition.java
new file mode 100644
index 00000000000..22f907a7e0d
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/BlacklistCondition.java
@@ -0,0 +1,11 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.json;
+
+/**
+ * @author hakonhall
+ */
+public class BlacklistCondition extends ListCondition {
+ public BlacklistCondition(CreateParams params) {
+ super(Type.BLACKLIST, params);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/Condition.java b/flags/src/main/java/com/yahoo/vespa/flags/json/Condition.java
index a0ad08fb0b3..11d0fbbfb97 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/Condition.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/Condition.java
@@ -1,62 +1,72 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags.json;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.json.wire.WireCondition;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Predicate;
/**
* @author hakonhall
*/
-public class Condition implements Predicate<FetchVector> {
- public enum Type { WHITELIST, BLACKLIST }
+public interface Condition extends Predicate<FetchVector> {
+ enum Type {
+ WHITELIST,
+ BLACKLIST,
+ RELATIONAL;
- private final Type type;
- private final FetchVector.Dimension dimension;
- private final List<String> values;
+ public String toWire() { return name().toLowerCase(); }
- public Condition(Type type, FetchVector.Dimension dimension, String... values) {
- this(type, dimension, Arrays.asList(values));
- }
+ public static Type fromWire(String typeString) {
+ for (Type type : values()) {
+ if (type.name().toLowerCase().equals(typeString.toLowerCase())) {
+ return type;
+ }
+ }
- public Condition(Type type, FetchVector.Dimension dimension, List<String> values) {
- this.type = type;
- this.dimension = dimension;
- this.values = values;
+ throw new IllegalArgumentException("Unknown type: '" + typeString + "'");
+ }
}
- @Override
- public boolean test(FetchVector vector) {
- boolean isMember = vector.getValue(dimension).filter(values::contains).isPresent();
+ class CreateParams {
+ private final FetchVector.Dimension dimension;
+ private final List<String> values;
+ private final Optional<String> predicate;
- switch (type) {
- case WHITELIST: return isMember;
- case BLACKLIST: return !isMember;
- default: throw new IllegalArgumentException("Unknown type " + type);
+ public CreateParams(FetchVector.Dimension dimension, List<String> values, Optional<String> predicate) {
+ this.dimension = Objects.requireNonNull(dimension);
+ this.values = Objects.requireNonNull(values);
+ this.predicate = Objects.requireNonNull(predicate);
}
+
+ public FetchVector.Dimension dimension() { return dimension; }
+ public List<String> values() { return values; }
+ public Optional<String> predicate() { return predicate; }
}
- public static Condition fromWire(WireCondition wireCondition) {
+ static Condition fromWire(WireCondition wireCondition) {
Objects.requireNonNull(wireCondition.type);
- Type type = Type.valueOf(wireCondition.type.toUpperCase());
+ Condition.Type type = Condition.Type.fromWire(wireCondition.type);
Objects.requireNonNull(wireCondition.dimension);
FetchVector.Dimension dimension = DimensionHelper.fromWire(wireCondition.dimension);
List<String> values = wireCondition.values == null ? List.of() : wireCondition.values;
+ Optional<String> predicate = Optional.ofNullable(wireCondition.predicate);
- return new Condition(type, dimension, values);
- }
+ var params = new CreateParams(dimension, values, predicate);
- public WireCondition toWire() {
- WireCondition wire = new WireCondition();
- wire.type = type.name().toLowerCase();
- wire.dimension = DimensionHelper.toWire(dimension);
- wire.values = values.isEmpty() ? null : values;
- return wire;
+ switch (type) {
+ case WHITELIST: return new WhitelistCondition(params);
+ case BLACKLIST: return new BlacklistCondition(params);
+ case RELATIONAL: return RelationalCondition.create(params);
+ }
+
+ throw new IllegalArgumentException("Unknown type '" + type + "'");
}
+
+ WireCondition toWire();
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java b/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
index 4fe27e81f2b..c7081ca72ab 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java
@@ -18,6 +18,7 @@ public class DimensionHelper {
serializedDimensions.put(FetchVector.Dimension.APPLICATION_ID, "application");
serializedDimensions.put(FetchVector.Dimension.NODE_TYPE, "node-type");
serializedDimensions.put(FetchVector.Dimension.CLUSTER_TYPE, "cluster-type");
+ serializedDimensions.put(FetchVector.Dimension.VESPA_VERSION, "vespa-version");
if (serializedDimensions.size() != FetchVector.Dimension.values().length) {
throw new IllegalStateException(FetchVectorHelper.class.getName() + " is not in sync with " +
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/ListCondition.java b/flags/src/main/java/com/yahoo/vespa/flags/json/ListCondition.java
new file mode 100644
index 00000000000..948f1fc6c3e
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/ListCondition.java
@@ -0,0 +1,39 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.json;
+
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.json.wire.WireCondition;
+
+import java.util.List;
+
+/**
+ * @author hakonhall
+ */
+public abstract class ListCondition implements Condition {
+ private final Condition.Type type;
+ private final FetchVector.Dimension dimension;
+ private final List<String> values;
+ private final boolean isWhitelist;
+
+ protected ListCondition(Type type, CreateParams params) {
+ this.type = type;
+ this.dimension = params.dimension();
+ this.values = List.copyOf(params.values());
+ this.isWhitelist = type == Type.WHITELIST;
+ }
+
+ @Override
+ public boolean test(FetchVector fetchVector) {
+ boolean listContainsValue = fetchVector.getValue(dimension).map(values::contains).orElse(false);
+ return isWhitelist == listContainsValue;
+ }
+
+ @Override
+ public WireCondition toWire() {
+ var condition = new WireCondition();
+ condition.type = type.toWire();
+ condition.dimension = DimensionHelper.toWire(dimension);
+ condition.values = values.isEmpty() ? null : values;
+ return condition;
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java
new file mode 100644
index 00000000000..996f1f925f7
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java
@@ -0,0 +1,58 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.json;
+
+import com.yahoo.component.Version;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.json.wire.WireCondition;
+
+import java.util.function.Predicate;
+
+/**
+ * @author hakonhall
+ */
+public class RelationalCondition implements Condition {
+ private final RelationalPredicate relationalPredicate;
+ private final Predicate<String> predicate;
+ private final FetchVector.Dimension dimension;
+
+ public static RelationalCondition create(CreateParams params) {
+ String predicate = params.predicate().orElseThrow(() ->
+ new IllegalArgumentException(RelationalCondition.class.getSimpleName() +
+ " requires the predicate field in the condition"));
+ RelationalPredicate relationalPredicate = RelationalPredicate.fromWire(predicate);
+
+ switch (params.dimension()) {
+ case VESPA_VERSION:
+ final Version rightVersion = Version.fromString(relationalPredicate.rightOperand());
+ Predicate<String> p = (String leftString) -> {
+ Version leftVersion = Version.fromString(leftString);
+ return relationalPredicate.operator().evaluate(leftVersion, rightVersion);
+ };
+ return new RelationalCondition(relationalPredicate, p, params.dimension());
+ default:
+ throw new IllegalArgumentException(RelationalCondition.class.getSimpleName() +
+ " not supported for dimension " + FetchVector.Dimension.VESPA_VERSION.name());
+ }
+ }
+
+ private RelationalCondition(RelationalPredicate relationalPredicate, Predicate<String> predicate,
+ FetchVector.Dimension dimension) {
+ this.relationalPredicate = relationalPredicate;
+ this.predicate = predicate;
+ this.dimension = dimension;
+ }
+
+ @Override
+ public boolean test(FetchVector fetchVector) {
+ return fetchVector.getValue(dimension).map(predicate::test).orElse(false);
+ }
+
+ @Override
+ public WireCondition toWire() {
+ var condition = new WireCondition();
+ condition.type = Condition.Type.RELATIONAL.toWire();
+ condition.dimension = DimensionHelper.toWire(dimension);
+ condition.predicate = relationalPredicate.toWire();
+ return condition;
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalOperator.java b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalOperator.java
new file mode 100644
index 00000000000..ca7a997f447
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalOperator.java
@@ -0,0 +1,39 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.json;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * @author hakonhall
+ */
+public enum RelationalOperator {
+ EQUAL ("==", compareToValue -> compareToValue == 0),
+ NOT_EQUAL ("!=", compareToValue -> compareToValue != 0),
+ LESS_EQUAL ("<=", compareToValue -> compareToValue <= 0),
+ LESS ("<" , compareToValue -> compareToValue < 0),
+ GREATER_EQUAL(">=", compareToValue -> compareToValue >= 0),
+ GREATER (">" , compareToValue -> compareToValue > 0);
+
+ private String text;
+ private final Function<Integer, Boolean> compareToValuePredicate;
+
+ RelationalOperator(String text, Function<Integer, Boolean> compareToValuePredicate) {
+ this.text = text;
+ this.compareToValuePredicate = compareToValuePredicate;
+ }
+
+ public String toText() { return text; }
+
+ /** Returns true if 'left op right' is true, with 'op' being the operator represented by this. */
+ public <T extends Comparable<T>> boolean evaluate(T left, T right) {
+ Objects.requireNonNull(left);
+ Objects.requireNonNull(right);
+ return evaluate(left.compareTo(right));
+ }
+
+ public boolean evaluate(int compareToValue) {
+ return compareToValuePredicate.apply(compareToValue);
+ }
+}
+
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java
new file mode 100644
index 00000000000..c5ad195e0d2
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java
@@ -0,0 +1,43 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.json;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author hakonhall
+ */
+public class RelationalPredicate {
+ private final String originalPredicateString;
+ private final RelationalOperator operator;
+ private final String rightOperand;
+
+ /** @param predicateString is e.g. "&gt; SUFFIX" or "&lt;=SUFFIX". The first part is {@link RelationalOperator}. */
+ public static RelationalPredicate fromWire(String predicateString) {
+ // Make sure we try to match e.g. "<=" before "<" as the prefix of predicateString.
+ List<RelationalOperator> operatorsByDecendingLength = Stream.of(RelationalOperator.values())
+ .sorted(Comparator.comparing(operator -> - operator.toText().length()))
+ .collect(Collectors.toList());
+
+ for (var operator : operatorsByDecendingLength) {
+ if (predicateString.startsWith(operator.toText())) {
+ String suffix = predicateString.substring(operator.toText().length());
+ return new RelationalPredicate(predicateString, operator, suffix);
+ }
+ }
+
+ throw new IllegalArgumentException("Predicate string '" + predicateString + "' does not start with a relation operator");
+ }
+
+ private RelationalPredicate(String originalPredicateString, RelationalOperator operator, String rightOperand) {
+ this.originalPredicateString = originalPredicateString;
+ this.operator = operator;
+ this.rightOperand = rightOperand;
+ }
+
+ public RelationalOperator operator() { return operator; }
+ public String rightOperand() { return rightOperand; }
+ public String toWire() { return originalPredicateString; }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/WhitelistCondition.java b/flags/src/main/java/com/yahoo/vespa/flags/json/WhitelistCondition.java
new file mode 100644
index 00000000000..5c3f8bd3ade
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/WhitelistCondition.java
@@ -0,0 +1,11 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags.json;
+
+/**
+ * @author hakonhall
+ */
+public class WhitelistCondition extends ListCondition {
+ public WhitelistCondition(CreateParams params) {
+ super(Type.WHITELIST, params);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireCondition.java b/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireCondition.java
index 2020ce1e49f..1729444fcf2 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireCondition.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireCondition.java
@@ -16,4 +16,5 @@ public class WireCondition {
@JsonProperty("type") public String type;
@JsonProperty("dimension") public String dimension;
@JsonProperty("values") public List<String> values;
+ @JsonProperty("predicate") public String predicate;
}
diff --git a/flags/src/test/java/com/yahoo/vespa/flags/json/ConditionTest.java b/flags/src/test/java/com/yahoo/vespa/flags/json/ConditionTest.java
index d19442ae0f0..cf8d06fd312 100644
--- a/flags/src/test/java/com/yahoo/vespa/flags/json/ConditionTest.java
+++ b/flags/src/test/java/com/yahoo/vespa/flags/json/ConditionTest.java
@@ -4,9 +4,10 @@ package com.yahoo.vespa.flags.json;
import com.yahoo.vespa.flags.FetchVector;
import org.junit.Test;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.util.List;
+import java.util.Optional;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -17,8 +18,8 @@ public class ConditionTest {
@Test
public void testWhitelist() {
String hostname1 = "host1";
- Condition condition = new Condition(Condition.Type.WHITELIST, FetchVector.Dimension.HOSTNAME,
- Stream.of(hostname1).collect(Collectors.toList()));
+ var params = new Condition.CreateParams(FetchVector.Dimension.HOSTNAME, List.of(hostname1), Optional.empty());
+ Condition condition = new WhitelistCondition(params);
assertFalse(condition.test(new FetchVector()));
assertFalse(condition.test(new FetchVector().with(FetchVector.Dimension.APPLICATION_ID, "foo")));
assertFalse(condition.test(new FetchVector().with(FetchVector.Dimension.HOSTNAME, "bar")));
@@ -28,11 +29,38 @@ public class ConditionTest {
@Test
public void testBlacklist() {
String hostname1 = "host1";
- Condition condition = new Condition(Condition.Type.BLACKLIST, FetchVector.Dimension.HOSTNAME,
- Stream.of(hostname1).collect(Collectors.toList()));
+ var params = new Condition.CreateParams(FetchVector.Dimension.HOSTNAME, List.of(hostname1), Optional.empty());
+ Condition condition = new BlacklistCondition(params);
assertTrue(condition.test(new FetchVector()));
assertTrue(condition.test(new FetchVector().with(FetchVector.Dimension.APPLICATION_ID, "foo")));
assertTrue(condition.test(new FetchVector().with(FetchVector.Dimension.HOSTNAME, "bar")));
assertFalse(condition.test(new FetchVector().with(FetchVector.Dimension.HOSTNAME, hostname1)));
}
+
+ @Test
+ public void testRelational() {
+ verifyVespaVersionFor("<", true, false, false);
+ verifyVespaVersionFor("<=", true, true, false);
+ verifyVespaVersionFor(">", false, false, true);
+ verifyVespaVersionFor(">=", false, true, true);
+
+ // Test with empty fetch vector along vespa version dimension (this should never happen as the
+ // version is always available through Vtag, although Vtag has a dummy version number for e.g.
+ // locally run unit tests that hasn't set the release Vespa version).
+ var params = new Condition.CreateParams(FetchVector.Dimension.VESPA_VERSION, List.of(), Optional.of(">=7.1.2"));
+ Condition condition = RelationalCondition.create(params);
+ assertFalse(condition.test(new FetchVector()));
+ }
+
+ private void verifyVespaVersionFor(String operator, boolean whenLess, boolean whenEqual, boolean whenGreater) {
+ assertEquals(whenLess, vespaVersionCondition("7.2.4", operator + "7.3.4"));
+ assertEquals(whenEqual, vespaVersionCondition("7.3.4", operator + "7.3.4"));
+ assertEquals(whenGreater, vespaVersionCondition("7.4.4", operator + "7.3.4"));
+ }
+
+ private boolean vespaVersionCondition(String vespaVersion, String predicate) {
+ var params = new Condition.CreateParams(FetchVector.Dimension.VESPA_VERSION, List.of(), Optional.of(predicate));
+ Condition condition = RelationalCondition.create(params);
+ return condition.test(new FetchVector().with(FetchVector.Dimension.VESPA_VERSION, vespaVersion));
+ }
}
diff --git a/flags/src/test/java/com/yahoo/vespa/flags/json/SerializationTest.java b/flags/src/test/java/com/yahoo/vespa/flags/json/SerializationTest.java
index b0e4cd0f682..8326b14fcbf 100644
--- a/flags/src/test/java/com/yahoo/vespa/flags/json/SerializationTest.java
+++ b/flags/src/test/java/com/yahoo/vespa/flags/json/SerializationTest.java
@@ -48,6 +48,11 @@ public class SerializationTest {
" \"type\": \"blacklist\",\n" +
" \"dimension\": \"hostname\",\n" +
" \"values\": [ \"h1\" ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"type\": \"relational\",\n" +
+ " \"dimension\": \"vespa-version\",\n" +
+ " \"predicate\": \">=7.3.4\"\n" +
" }\n" +
" ],\n" +
" \"value\": true\n" +
@@ -66,7 +71,7 @@ public class SerializationTest {
assertThat(wireData.id, equalTo("id2"));
// rule
assertThat(wireData.rules.size(), equalTo(1));
- assertThat(wireData.rules.get(0).andConditions.size(), equalTo(2));
+ assertThat(wireData.rules.get(0).andConditions.size(), equalTo(3));
assertThat(wireData.rules.get(0).value.getNodeType(), equalTo(JsonNodeType.BOOLEAN));
assertThat(wireData.rules.get(0).value.asBoolean(), equalTo(true));
// first condition
@@ -79,6 +84,11 @@ public class SerializationTest {
assertThat(blacklistCondition.type, equalTo("blacklist"));
assertThat(blacklistCondition.dimension, equalTo("hostname"));
assertThat(blacklistCondition.values, equalTo(List.of("h1")));
+ // third condition
+ WireCondition relationalCondition = wireData.rules.get(0).andConditions.get(2);
+ assertThat(relationalCondition.type, equalTo("relational"));
+ assertThat(relationalCondition.dimension, equalTo("vespa-version"));
+ assertThat(relationalCondition.predicate, equalTo(">=7.3.4"));
// attributes
assertThat(wireData.defaultFetchVector, notNullValue());