summaryrefslogtreecommitdiffstats
path: root/flags
diff options
context:
space:
mode:
Diffstat (limited to 'flags')
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java75
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java112
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/JsonNodeRawFlag.java21
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java33
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/DimensionHelper.java11
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java62
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/ListCondition.java24
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java23
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/Rule.java43
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java206
10 files changed, 465 insertions, 145 deletions
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 c1877373ce2..5bcc1e67547 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FetchVector.java
@@ -3,10 +3,13 @@ package com.yahoo.vespa.flags;
import com.yahoo.vespa.flags.json.DimensionHelper;
+import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -20,20 +23,28 @@ public class FetchVector {
* Note: If this enum is changed, you must also change {@link DimensionHelper}.
*/
public enum Dimension {
- /** Value from TenantName::value, e.g. vespa-team */
- TENANT_ID,
-
/** Value from ApplicationId::serializedForm of the form tenant:applicationName:instance. */
APPLICATION_ID,
- /** Node type from com.yahoo.config.provision.NodeType::name, e.g. tenant, host, confighost, controller, etc. */
- NODE_TYPE,
+ /**
+ * Cloud from com.yahoo.config.provision.CloudName::value, e.g. yahoo, aws, gcp.
+ *
+ * <p><em>Eager resolution</em>: This dimension is resolved before putting the flag data to the config server
+ * or controller, unless controller and the flag has declared this dimension.
+ */
+ CLOUD,
+
+ /** Cluster ID from com.yahoo.config.provision.ClusterSpec.Id::value, e.g. cluster-controllers, logserver. */
+ CLUSTER_ID,
/** Cluster type from com.yahoo.config.provision.ClusterSpec.Type::name, e.g. content, container, admin */
CLUSTER_TYPE,
- /** Cluster ID from com.yahoo.config.provision.ClusterSpec.Id::value, e.g. cluster-controllers, logserver. */
- CLUSTER_ID,
+ /** Email address of user - provided by auth0 in console. */
+ CONSOLE_USER_EMAIL,
+
+ /** Hosted Vespa environment from com.yahoo.config.provision.Environment::value, e.g. prod, staging, test. */
+ ENVIRONMENT,
/**
* Fully qualified hostname.
@@ -44,6 +55,18 @@ public class FetchVector {
*/
HOSTNAME,
+ /** Node type from com.yahoo.config.provision.NodeType::name, e.g. tenant, host, confighost, controller, etc. */
+ NODE_TYPE,
+
+ /**
+ * Hosted Vespa system from com.yahoo.config.provision.SystemName::value, e.g. main, cd, public, publiccd.
+ * <em>Eager resolution</em>, see {@link #CLOUD}.
+ */
+ SYSTEM,
+
+ /** Value from TenantName::value, e.g. vespa-team */
+ TENANT_ID,
+
/**
* Vespa version from Version::toFullString of the form Major.Minor.Micro.
*
@@ -53,14 +76,9 @@ public class FetchVector {
*/
VESPA_VERSION,
- /** Email address of user - provided by auth0 in console. */
- CONSOLE_USER_EMAIL,
-
/**
- * Zone from ZoneId::value of the form environment.region.
- *
- * <p>NOTE: There is seldom any need to set ZONE_ID, as all flags are set per zone anyways. The controller
- * could PERHAPS use this where it handles multiple zones.
+ * Virtual zone ID from com.yahoo.config.provision.zone.ZoneId::value of the form environment.region,
+ * see com.yahoo.config.provision.zone.ZoneApi::getVirtualId. <em>Eager resolution</em>, see {@link #CLOUD}.
*/
ZONE_ID
}
@@ -83,15 +101,13 @@ public class FetchVector {
return Optional.ofNullable(map.get(dimension));
}
- public Map<Dimension, String> toMap() {
- return map;
- }
+ public Map<Dimension, String> toMap() { return map; }
public boolean isEmpty() { return map.isEmpty(); }
- public boolean hasDimension(FetchVector.Dimension dimension) {
- return map.containsKey(dimension);
- }
+ public boolean hasDimension(FetchVector.Dimension dimension) { return map.containsKey(dimension);}
+
+ public Set<Dimension> dimensions() { return map.keySet(); }
/**
* Returns a new FetchVector, identical to {@code this} except for its value in {@code dimension}.
@@ -107,13 +123,28 @@ public class FetchVector {
return makeFetchVector(vector -> vector.putAll(override.map));
}
- private FetchVector makeFetchVector(Consumer<EnumMap<Dimension, String>> mapModifier) {
- EnumMap<Dimension, String> mergedMap = new EnumMap<>(Dimension.class);
+ private FetchVector makeFetchVector(Consumer<Map<Dimension, String>> mapModifier) {
+ Map<Dimension, String> mergedMap = new EnumMap<>(Dimension.class);
mergedMap.putAll(map);
mapModifier.accept(mergedMap);
return new FetchVector(mergedMap);
}
+ public FetchVector without(Dimension dimension) {
+ return makeFetchVector(merged -> merged.remove(dimension));
+ }
+
+ public FetchVector without(Collection<Dimension> dimensions) {
+ return makeFetchVector(merged -> merged.keySet().removeAll(dimensions));
+ }
+
+ @Override
+ public String toString() {
+ return "FetchVector{" +
+ "map=" + map +
+ '}';
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 326e8f2dcae..73283958cc7 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -21,7 +21,6 @@ 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.TENANT_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.VESPA_VERSION;
-import static com.yahoo.vespa.flags.FetchVector.Dimension.ZONE_ID;
/**
* Definitions of feature flags.
@@ -53,23 +52,16 @@ public class Flags {
List.of("hakonhall", "baldersheim"), "2023-03-06", "2023-08-05",
"Drop caches on tenant hosts",
"Takes effect on next tick",
- ZONE_ID,
// The application ID is the exclusive application ID associated with the host,
// if any, or otherwise hosted-vespa:tenant-host:default.
APPLICATION_ID, TENANT_ID, CLUSTER_ID, CLUSTER_TYPE);
- public static final UnboundBooleanFlag SIMPLER_ACL = defineFeatureFlag(
- "simpler-acl", true,
- List.of("hakonhall"), "2023-07-04", "2023-08-04",
- "Simplify ACL in hosted Vespa",
- "Takes effect on the next fetch of ACL rules");
-
public static final UnboundDoubleFlag DEFAULT_TERM_WISE_LIMIT = defineDoubleFlag(
"default-term-wise-limit", 1.0,
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"Default limit for when to apply termwise query evaluation",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundStringFlag QUERY_DISPATCH_POLICY = defineStringFlag(
"query-dispatch-policy", "adaptive",
@@ -77,83 +69,83 @@ public class Flags {
"Select query dispatch policy, valid values are adaptive, round-robin, best-of-random-2," +
" latency-amortized-over-requests, latency-amortized-over-time",
"Takes effect at redeployment (requires restart)",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundStringFlag SUMMARY_DECODE_POLICY = defineStringFlag(
"summary-decode-policy", "eager",
List.of("baldersheim"), "2023-03-30", "2023-12-31",
"Select summary decoding policy, valid values are eager and on-demand/ondemand.",
"Takes effect at redeployment (requires restart)",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundStringFlag FEED_SEQUENCER_TYPE = defineStringFlag(
"feed-sequencer-type", "THROUGHPUT",
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"Selects type of sequenced executor used for feeding in proton, valid values are LATENCY, ADAPTIVE, THROUGHPUT",
"Takes effect at redeployment (requires restart)",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag MAX_UNCOMMITTED_MEMORY = defineIntFlag(
"max-uncommitted-memory", 130000,
List.of("geirst, baldersheim"), "2021-10-21", "2023-12-31",
"Max amount of memory holding updates to an attribute before we do a commit.",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundStringFlag RESPONSE_SEQUENCER_TYPE = defineStringFlag(
"response-sequencer-type", "ADAPTIVE",
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"Selects type of sequenced executor used for mbus responses, valid values are LATENCY, ADAPTIVE, THROUGHPUT",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag RESPONSE_NUM_THREADS = defineIntFlag(
"response-num-threads", 2,
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"Number of threads used for mbus responses, default is 2, negative number = numcores/4",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag SKIP_COMMUNICATIONMANAGER_THREAD = defineFeatureFlag(
"skip-communicationmanager-thread", false,
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"Should we skip the communicationmanager thread",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag SKIP_MBUS_REQUEST_THREAD = defineFeatureFlag(
"skip-mbus-request-thread", false,
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"Should we skip the mbus request thread",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag SKIP_MBUS_REPLY_THREAD = defineFeatureFlag(
"skip-mbus-reply-thread", false,
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"Should we skip the mbus reply thread",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag USE_ASYNC_MESSAGE_HANDLING_ON_SCHEDULE = defineFeatureFlag(
"async-message-handling-on-schedule", false,
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"Optionally deliver async messages in own thread",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundDoubleFlag FEED_CONCURRENCY = defineDoubleFlag(
"feed-concurrency", 0.5,
List.of("baldersheim"), "2020-12-02", "2023-12-31",
"How much concurrency should be allowed for feed",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundDoubleFlag FEED_NICENESS = defineDoubleFlag(
"feed-niceness", 0.0,
List.of("baldersheim"), "2022-06-24", "2023-12-31",
"How nice feeding shall be",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag MBUS_JAVA_NUM_TARGETS = defineIntFlag(
@@ -161,71 +153,71 @@ public class Flags {
List.of("baldersheim"), "2022-07-05", "2023-12-31",
"Number of rpc targets per service",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag MBUS_CPP_NUM_TARGETS = defineIntFlag(
"mbus-cpp-num-targets", 2,
List.of("baldersheim"), "2022-07-05", "2023-12-31",
"Number of rpc targets per service",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag RPC_NUM_TARGETS = defineIntFlag(
"rpc-num-targets", 2,
List.of("baldersheim"), "2022-07-05", "2023-12-31",
"Number of rpc targets per content node",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag MBUS_JAVA_EVENTS_BEFORE_WAKEUP = defineIntFlag(
"mbus-java-events-before-wakeup", 1,
List.of("baldersheim"), "2022-07-05", "2023-12-31",
"Number write events before waking up transport thread",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag MBUS_CPP_EVENTS_BEFORE_WAKEUP = defineIntFlag(
"mbus-cpp-events-before-wakeup", 1,
List.of("baldersheim"), "2022-07-05", "2023-12-31",
"Number write events before waking up transport thread",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag RPC_EVENTS_BEFORE_WAKEUP = defineIntFlag(
"rpc-events-before-wakeup", 1,
List.of("baldersheim"), "2022-07-05", "2023-12-31",
"Number write events before waking up transport thread",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag MBUS_NUM_NETWORK_THREADS = defineIntFlag(
"mbus-num-network-threads", 1,
List.of("baldersheim"), "2022-07-01", "2023-12-31",
"Number of threads used for mbus network",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag SHARED_STRING_REPO_NO_RECLAIM = defineFeatureFlag(
"shared-string-repo-no-reclaim", false,
List.of("baldersheim"), "2022-06-14", "2023-12-31",
"Controls whether we do track usage and reclaim unused enum values in shared string repo",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag CONTAINER_DUMP_HEAP_ON_SHUTDOWN_TIMEOUT = defineFeatureFlag(
"container-dump-heap-on-shutdown-timeout", false,
List.of("baldersheim"), "2021-09-25", "2023-12-31",
"Will trigger a heap dump during if container shutdown times out",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag LOAD_CODE_AS_HUGEPAGES = defineFeatureFlag(
"load-code-as-hugepages", false,
List.of("baldersheim"), "2022-05-13", "2023-12-31",
"Will try to map the code segment with huge (2M) pages",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundDoubleFlag CONTAINER_SHUTDOWN_TIMEOUT = defineDoubleFlag(
"container-shutdown-timeout", 50.0,
List.of("baldersheim"), "2021-09-25", "2023-12-31",
"Timeout for shutdown of a jdisc container",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
// TODO: Move to a permanent flag
public static final UnboundListFlag<String> ALLOWED_ATHENZ_PROXY_IDENTITIES = defineListFlag(
@@ -240,28 +232,28 @@ public class Flags {
"Allows replicas in up to N content groups to not be activated " +
"for query visibility if they are out of sync with a majority of other replicas",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundDoubleFlag MIN_NODE_RATIO_PER_GROUP = defineDoubleFlag(
"min-node-ratio-per-group", 0.0,
List.of("geirst", "vekterli"), "2021-07-16", "2023-09-01",
"Minimum ratio of nodes that have to be available (i.e. not Down) in any hierarchic content cluster group for the group to be Up",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundStringFlag SYSTEM_MEMORY_HIGH = defineStringFlag(
"system-memory-high", "",
List.of("baldersheim"), "2023-02-14", "2023-12-31",
"The value to write to /sys/fs/cgroup/system.slice/memory.high, if non-empty.",
"Takes effect on next tick.",
- ZONE_ID, NODE_TYPE);
+ NODE_TYPE);
public static final UnboundStringFlag SYSTEM_MEMORY_MAX = defineStringFlag(
"system-memory-max", "",
List.of("baldersheim"), "2023-02-14", "2023-12-31",
"The value to write to /sys/fs/cgroup/system.slice/memory.max, if non-empty.",
"Takes effect on next tick.",
- ZONE_ID, NODE_TYPE);
+ NODE_TYPE);
public static final UnboundBooleanFlag ENABLED_HORIZON_DASHBOARD = defineFeatureFlag(
"enabled-horizon-dashboard", false,
@@ -276,35 +268,35 @@ public class Flags {
List.of("arnej"), "2021-11-12", "2023-12-31",
"Whether C++ thread creation should ignore any requested stack size",
"Triggers restart, takes effect immediately",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag USE_V8_GEO_POSITIONS = defineFeatureFlag(
"use-v8-geo-positions", true,
List.of("arnej"), "2021-11-15", "2023-12-31",
"Use Vespa 8 types and formats for geographical positions",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag MAX_COMPACT_BUFFERS = defineIntFlag(
"max-compact-buffers", 1,
List.of("baldersheim", "geirst", "toregge"), "2021-12-15", "2023-12-31",
"Upper limit of buffers to compact in a data store at the same time for each reason (memory usage, address space usage)",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag USE_QRSERVER_SERVICE_NAME = defineFeatureFlag(
"use-qrserver-service-name", false,
List.of("arnej"), "2022-01-18", "2023-12-31",
"Use backwards-compatible 'qrserver' service name for containers with only 'search' API",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag AVOID_RENAMING_SUMMARY_FEATURES = defineFeatureFlag(
"avoid-renaming-summary-features", true,
List.of("arnej"), "2022-01-15", "2023-12-31",
"Tell backend about the original name of summary-features that were wrapped in a rankingExpression feature",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag ENABLE_PROXY_PROTOCOL_MIXED_MODE = defineFeatureFlag(
"enable-proxy-protocol-mixed-mode", true,
@@ -318,7 +310,7 @@ public class Flags {
List.of("arnej"), "2022-06-14", "2024-12-31",
"Which algorithm to use for compressing log files. Valid values: empty string (default), gzip, zstd",
"Takes effect immediately",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag SEPARATE_METRIC_CHECK_CONFIG = defineFeatureFlag(
"separate-metric-check-config", false,
@@ -354,7 +346,7 @@ public class Flags {
List.of("vekterli"), "2022-11-03", "2023-10-01",
"Specifies which public key to use for core dump encryption.",
"Takes effect on the next tick.",
- ZONE_ID, NODE_TYPE, HOSTNAME);
+ NODE_TYPE, HOSTNAME);
public static final UnboundBooleanFlag ENABLE_GLOBAL_PHASE = defineFeatureFlag(
"enable-global-phase", true,
@@ -364,11 +356,11 @@ public class Flags {
APPLICATION_ID);
public static final UnboundBooleanFlag NODE_ADMIN_TENANT_SERVICE_REGISTRY = defineFeatureFlag(
- "node-admin-tenant-service-registry", false,
- List.of("olaa"), "2023-04-12", "2023-08-01",
+ "node-admin-tenant-service-registry", true,
+ List.of("olaa"), "2023-04-12", "2023-08-07",
"Whether AthenzCredentialsMaintainer in node-admin should create tenant service identity certificate",
"Takes effect on next tick",
- ZONE_ID, HOSTNAME, VESPA_VERSION, APPLICATION_ID
+ HOSTNAME, VESPA_VERSION, APPLICATION_ID
);
public static final UnboundBooleanFlag ENABLE_CROWDSTRIKE = defineFeatureFlag(
@@ -380,7 +372,7 @@ public class Flags {
"allow-more-than-one-content-group-down", false, List.of("hmusum"), "2023-04-14", "2023-08-15",
"Whether to enable possible configuration of letting more than one content group down",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag RANDOMIZED_ENDPOINT_NAMES = defineFeatureFlag(
"randomized-endpoint-names", false, List.of("andreer"), "2023-04-26", "2023-08-30",
@@ -397,8 +389,7 @@ public class Flags {
public static final UnboundBooleanFlag ENABLE_THE_ONE_THAT_SHOULD_NOT_BE_NAMED = defineFeatureFlag(
"enable-the-one-that-should-not-be-named", false, List.of("hmusum"), "2023-05-08", "2023-08-15",
"Whether to enable the one program that should not be named",
- "Takes effect at next host-admin tick",
- ZONE_ID);
+ "Takes effect at next host-admin tick");
public static final UnboundListFlag<String> WEIGHTED_ENDPOINT_RECORD_TTL = defineListFlag(
"weighted-endpoint-record-ttl", List.of(), String.class, List.of("jonmv"), "2023-05-16", "2023-09-01",
@@ -413,42 +404,40 @@ public class Flags {
"will initiate a write-repair that evaluates the condition across all mutually inconsistent " +
"replicas, with the newest document version (if any) being authoritative",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag ENABLE_DATAPLANE_PROXY = defineFeatureFlag(
"enable-dataplane-proxy", false,
List.of("mortent", "olaa"), "2023-05-15", "2023-08-01",
"Whether to enable dataplane proxy",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID
+ APPLICATION_ID
);
public static final UnboundBooleanFlag ENABLE_NESTED_MULTIVALUE_GROUPING = defineFeatureFlag(
"enable-nested-multivalue-grouping", false,
List.of("baldersheim"), "2023-06-29", "2023-12-31",
"Should we enable proper nested multivalue grouping",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag USE_RECONFIGURABLE_DISPATCHER = defineFeatureFlag(
"use-reconfigurable-dispatcher", false,
List.of("jonmv"), "2023-07-14", "2023-10-01",
"Whether to set up a ReconfigurableDispatcher with config self-sub for backend nodes",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag WRITE_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB = defineFeatureFlag(
"write-config-server-session-data-as-blob", false,
List.of("hmuusm"), "2023-07-19", "2023-09-01",
"Whether to write config server session data in one blob or as individual paths",
- "Takes effect immediately",
- ZONE_ID);
+ "Takes effect immediately");
public static final UnboundBooleanFlag READ_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB = defineFeatureFlag(
"read-config-server-session-data-as-blob", false,
List.of("hmuusm"), "2023-07-19", "2023-09-01",
"Whether to read config server session data from sesion data blob or from individual paths",
- "Takes effect immediately",
- ZONE_ID);
+ "Takes effect immediately");
public static final UnboundBooleanFlag USE_VESPA_USER_EVERYWHERE = defineFeatureFlag(
"use-vespa-user-everywhere", false,
@@ -538,6 +527,15 @@ public class Flags {
* For instance, if APPLICATION is one of the dimensions here, you should make sure
* APPLICATION is set to the ApplicationId in the fetch vector when fetching the RawFlag
* from the FlagSource.
+ * SYSTEM, CLOUD, ENVIRONMENT, and ZONE_ID are special: These dimensions are resolved just
+ * before the flag data is published to a zone. This means there is never any need to set
+ * these dimensions when resolving a flag, and setting these dimensions just before resolving
+ * the flag will have no effect.
+ * There is one exception. If any of these dimensions are declared when defining a flag,
+ * then those dimensions are NOT resolved when published to the controllers. This allows
+ * the controller to resolve the flag to different values based on which cloud or zone
+ * it is operating on. Flags should NOT declare these dimensions unless they intend to
+ * use them in the controller in this way.
* @param <T> The boxed type of the flag value, e.g. Boolean for flags guarding features.
* @param <U> The type of the unbound flag, e.g. UnboundBooleanFlag.
* @return An unbound flag with {@link FetchVector.Dimension#HOSTNAME HOSTNAME} and
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/JsonNodeRawFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/JsonNodeRawFlag.java
index 753f19a44f6..27852790186 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/JsonNodeRawFlag.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/JsonNodeRawFlag.java
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collection;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -60,6 +61,26 @@ public class JsonNodeRawFlag implements RawFlag {
return jsonNode.toString();
}
+ @Override
+ public String toString() {
+ return "JsonNodeRawFlag{" +
+ "jsonNode=" + jsonNode +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ JsonNodeRawFlag that = (JsonNodeRawFlag) o;
+ return jsonNode.equals(that.jsonNode);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(jsonNode);
+ }
+
/** Initialize object mapper lazily */
private static ObjectMapper objectMapper() {
// ObjectMapper is a heavy-weight object so we construct it only when we need it
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
index b7e81f56599..18f5f5f860d 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
@@ -21,7 +21,6 @@ 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.TENANT_ID;
import static com.yahoo.vespa.flags.FetchVector.Dimension.VESPA_VERSION;
-import static com.yahoo.vespa.flags.FetchVector.Dimension.ZONE_ID;
/**
* Definition for permanent feature flags
@@ -43,19 +42,19 @@ public class PermanentFlags {
"jvm-gc-options", "",
"Sets default jvm gc options",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundIntFlag HEAP_SIZE_PERCENTAGE = defineIntFlag(
"heap-size-percentage", 70,
"Sets default jvm heap size percentage",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundDoubleFlag QUERY_DISPATCH_WARMUP = defineDoubleFlag(
"query-dispatch-warmup", 5,
"Warmup duration for query dispatcher",
"Takes effect at redeployment (requires restart)",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag FLEET_CANARY = defineFeatureFlag(
"fleet-canary", false,
@@ -126,13 +125,13 @@ public class PermanentFlags {
"min-disk-throughput-mb-s", 0,
"Minimum required disk throughput performance, 0 = default, Only when using remote disk",
"Takes effect when node is provisioned",
- ZONE_ID, APPLICATION_ID, TENANT_ID, CLUSTER_ID, CLUSTER_TYPE);
+ APPLICATION_ID, TENANT_ID, CLUSTER_ID, CLUSTER_TYPE);
public static final UnboundIntFlag MIN_DISK_IOPS_K = defineIntFlag(
"min-disk-iops-k", 0,
"Minimum required disk I/O operations per second, unit is kilo, 0 = default, Only when using remote disk",
"Takes effect when node is provisioned",
- ZONE_ID, APPLICATION_ID, TENANT_ID, CLUSTER_ID, CLUSTER_TYPE);
+ APPLICATION_ID, TENANT_ID, CLUSTER_ID, CLUSTER_TYPE);
public static final UnboundListFlag<String> DISABLED_HOST_ADMIN_TASKS = defineListFlag(
"disabled-host-admin-tasks", List.of(), String.class,
@@ -145,7 +144,7 @@ public class PermanentFlags {
"docker-image-repo", "",
"Override default docker image repo. Docker image version will be Vespa version.",
"Takes effect on next deployment from controller",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
private static final String VERSION_QUALIFIER_REGEX = "[a-zA-Z0-9_-]+";
private static final Pattern QUALIFIER_PATTERN = Pattern.compile("^" + VERSION_QUALIFIER_REGEX + "$");
@@ -250,29 +249,28 @@ public class PermanentFlags {
"A list of environment variables set for all services. " +
"Each item should be on the form <ENV_VAR>=<VALUE>",
"Takes effect on service restart",
- ZONE_ID, APPLICATION_ID
+ APPLICATION_ID
);
public static final UnboundStringFlag CONFIG_PROXY_JVM_ARGS = defineStringFlag(
"config-proxy-jvm-args", "",
"Sets jvm args for config proxy (added at the end of startup command, will override existing ones)",
"Takes effect on restart of Docker container",
- ZONE_ID, APPLICATION_ID
+ APPLICATION_ID
);
// This must be set in a feature flag to avoid flickering between the new and old value during config server upgrade
public static final UnboundDoubleFlag HOST_MEMORY = defineDoubleFlag(
"host-memory", 0.6,
"The memory in GB required by a host's management processes.",
- "Takes effect immediately",
- ZONE_ID
+ "Takes effect immediately"
);
public static final UnboundBooleanFlag FORWARD_ISSUES_AS_ERRORS = defineFeatureFlag(
"forward-issues-as-errors", true,
"When the backend detects a problematic issue with a query, it will by default send it as an error message to the QRS, which adds it in an ErrorHit in the result. May be disabled using this flag.",
"Takes effect immediately",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundBooleanFlag DEACTIVATE_ROUTING = defineFeatureFlag(
"deactivate-routing", false,
@@ -285,7 +283,7 @@ public class PermanentFlags {
"ignored-http-user-agents", List.of(), String.class,
"List of user agents to ignore (crawlers etc)",
"Takes effect immediately.",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundListFlag<String> INCOMPATIBLE_VERSIONS = defineListFlag(
"incompatible-versions", List.of("8"), String.class,
@@ -308,7 +306,7 @@ public class PermanentFlags {
"(logserver and clustercontroller clusters).",
"Takes effect on next redeployment",
value -> Set.of("any", "arm64", "x86_64").contains(value),
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundListFlag<String> CLOUD_ACCOUNTS = defineListFlag(
"cloud-accounts", List.of(), String.class,
@@ -320,7 +318,7 @@ public class PermanentFlags {
"fail-deployment-for-files-with-unknown-extension", "FAIL",
"Whether to log or fail for deployments when app has a file with unknown extension (valid values: LOG, FAIL)",
"Takes effect at redeployment",
- ZONE_ID, APPLICATION_ID);
+ APPLICATION_ID);
public static final UnboundListFlag<String> DISABLED_DEPLOYMENT_ZONES = defineListFlag(
"disabled-deployment-zones", List.of(), String.class,
@@ -339,8 +337,7 @@ public class PermanentFlags {
"config-server-session-expiry-time", 3600,
"Expiry time in seconds for remote sessions (session in ZooKeeper). Default should be equal to session lifetime, " +
"but can be lowered if there are incidents/bugs where one needs to delete sessions",
- "Takes effect immediately",
- ZONE_ID
+ "Takes effect immediately"
);
public static final UnboundBooleanFlag NOTIFICATION_DISPATCH_FLAG = defineFeatureFlag(
@@ -353,7 +350,7 @@ public class PermanentFlags {
"keep-file-references-on-tenant-nodes", 30,
"How many days to keep file references on tenant nodes (based on last modification time)",
"Takes effect on restart of Docker container",
- ZONE_ID, APPLICATION_ID
+ APPLICATION_ID
);
public static final UnboundIntFlag ENDPOINT_CONNECTION_TTL = defineIntFlag(
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 ad1242aa7e9..5e5506b616b 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
@@ -15,15 +15,18 @@ public class DimensionHelper {
private static final Map<FetchVector.Dimension, String> serializedDimensions = new HashMap<>();
static {
- serializedDimensions.put(FetchVector.Dimension.ZONE_ID, "zone");
- serializedDimensions.put(FetchVector.Dimension.HOSTNAME, "hostname");
serializedDimensions.put(FetchVector.Dimension.APPLICATION_ID, "application");
- serializedDimensions.put(FetchVector.Dimension.NODE_TYPE, "node-type");
+ serializedDimensions.put(FetchVector.Dimension.CLOUD, "cloud");
serializedDimensions.put(FetchVector.Dimension.CLUSTER_ID, "cluster-id");
serializedDimensions.put(FetchVector.Dimension.CLUSTER_TYPE, "cluster-type");
- serializedDimensions.put(FetchVector.Dimension.VESPA_VERSION, "vespa-version");
serializedDimensions.put(FetchVector.Dimension.CONSOLE_USER_EMAIL, "console-user-email");
+ serializedDimensions.put(FetchVector.Dimension.ENVIRONMENT, "environment");
+ serializedDimensions.put(FetchVector.Dimension.HOSTNAME, "hostname");
+ serializedDimensions.put(FetchVector.Dimension.NODE_TYPE, "node-type");
+ serializedDimensions.put(FetchVector.Dimension.SYSTEM, "system");
serializedDimensions.put(FetchVector.Dimension.TENANT_ID, "tenant");
+ serializedDimensions.put(FetchVector.Dimension.VESPA_VERSION, "vespa-version");
+ serializedDimensions.put(FetchVector.Dimension.ZONE_ID, "zone");
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/FlagData.java b/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
index 19837e7dbe1..acda3b9db42 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
@@ -13,9 +13,10 @@ import com.yahoo.vespa.flags.json.wire.WireRule;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -53,6 +54,27 @@ public class FlagData {
public boolean isEmpty() { return rules.isEmpty() && defaultFetchVector.isEmpty(); }
+ public FlagData partialResolve(FetchVector fetchVector) {
+ // Note: As a result of partialResolve, there could be e.g. two identical rules, and the latter will always be ignored by resolve().
+ // Consider deduping. Deduping is actually not specific to partialResolve and could be done e.g. at construction time.
+
+ List<Rule> newRules = new ArrayList<>();
+ for (var rule : rules) {
+ Optional<Rule> partialRule = rule.partialResolve(fetchVector);
+ if (partialRule.isPresent()) {
+ newRules.add(partialRule.get());
+ if (partialRule.get().conditions().isEmpty()) {
+ // Any following rule will always be ignored during resolution.
+ break;
+ }
+ }
+ }
+
+ FetchVector newDefaultFetchVector = defaultFetchVector.without(fetchVector.dimensions());
+
+ return new FlagData(id, newDefaultFetchVector, newRules);
+ }
+
public Optional<RawFlag> resolve(FetchVector fetchVector) {
return rules.stream()
.filter(rule -> rule.match(defaultFetchVector.with(fetchVector)))
@@ -91,6 +113,36 @@ public class FlagData {
return wireFlagData;
}
+ /** E.g. verify all RawFlag can be deserialized. */
+ public void validate(Deserializer<?> deserializer) {
+ rules.stream()
+ .flatMap(rule -> rule.getValueToApply().map(Stream::of).orElse(null))
+ .forEach(deserializer::deserialize);
+
+ }
+
+ @Override
+ public String toString() {
+ return "FlagData{" +
+ "id=" + id +
+ ", rules=" + rules +
+ ", defaultFetchVector=" + defaultFetchVector +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ FlagData flagData = (FlagData) o;
+ return id.equals(flagData.id) && rules.equals(flagData.rules) && defaultFetchVector.equals(flagData.defaultFetchVector);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, rules, defaultFetchVector);
+ }
+
public static FlagData deserializeUtf8Json(byte[] bytes) {
return fromWire(WireFlagData.deserialize(bytes));
}
@@ -138,13 +190,5 @@ public class FlagData {
if (wireRules == null) return List.of();
return wireRules.stream().map(Rule::fromWire).toList();
}
-
- /** E.g. verify all RawFlag can be deserialized. */
- public void validate(Deserializer<?> deserializer) {
- rules.stream()
- .flatMap(rule -> rule.getValueToApply().map(Stream::of).orElse(null))
- .forEach(deserializer::deserialize);
-
- }
}
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
index c4b2d9be117..483f6750a73 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/ListCondition.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/ListCondition.java
@@ -5,6 +5,7 @@ import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.json.wire.WireCondition;
import java.util.List;
+import java.util.Objects;
/**
* @author hakonhall
@@ -55,4 +56,27 @@ public abstract class ListCondition implements Condition {
condition.values = values.isEmpty() ? null : values;
return condition;
}
+
+ @Override
+ public String toString() {
+ return "ListCondition{" +
+ "type=" + type +
+ ", dimension=" + dimension +
+ ", values=" + values +
+ ", isWhitelist=" + isWhitelist +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ListCondition that = (ListCondition) o;
+ return isWhitelist == that.isWhitelist && type == that.type && dimension == that.dimension && values.equals(that.values);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, dimension, values, isWhitelist);
+ }
}
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
index 0efeb831f2c..749f6830870 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.json.wire.WireCondition;
+import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -75,4 +76,26 @@ public class RelationalCondition implements Condition {
condition.predicate = relationalPredicate.toWire();
return condition;
}
+
+ @Override
+ public String toString() {
+ return "RelationalCondition{" +
+ "relationalPredicate=" + relationalPredicate +
+ ", predicate=" + predicate +
+ ", dimension=" + dimension +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RelationalCondition that = (RelationalCondition) o;
+ return relationalPredicate.equals(that.relationalPredicate) && predicate.equals(that.predicate) && dimension == that.dimension;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(relationalPredicate, predicate, dimension);
+ }
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/Rule.java b/flags/src/main/java/com/yahoo/vespa/flags/json/Rule.java
index bddaf8c9e0e..127c2b4f9da 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/Rule.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/Rule.java
@@ -6,10 +6,11 @@ import com.yahoo.vespa.flags.JsonNodeRawFlag;
import com.yahoo.vespa.flags.RawFlag;
import com.yahoo.vespa.flags.json.wire.WireRule;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
-import java.util.stream.Collectors;
/**
* @author hakonhall
@@ -45,6 +46,25 @@ public class Rule {
.allMatch(condition -> !fetchVector.hasDimension(condition.dimension()) || condition.test(fetchVector));
}
+ /**
+ * Returns a copy of this rule without those conditions that can be resolved by the fetch vector. Returns empty
+ * if any of those conditions are false.
+ */
+ public Optional<Rule> partialResolve(FetchVector fetchVector) {
+ List<Condition> newConditions = new ArrayList<>();
+ for (var condition : andConditions) {
+ if (fetchVector.hasDimension(condition.dimension())) {
+ if (!condition.test(fetchVector)) {
+ return Optional.empty();
+ }
+ } else {
+ newConditions.add(condition);
+ }
+ }
+
+ return Optional.of(new Rule(valueToApply, newConditions));
+ }
+
public Optional<RawFlag> getValueToApply() {
return valueToApply;
}
@@ -68,4 +88,25 @@ public class Rule {
Optional<RawFlag> value = wireRule.value == null ? Optional.empty() : Optional.of(JsonNodeRawFlag.fromJsonNode(wireRule.value));
return new Rule(value, conditions);
}
+
+ @Override
+ public String toString() {
+ return "Rule{" +
+ "andConditions=" + andConditions +
+ ", valueToApply=" + valueToApply +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Rule rule = (Rule) o;
+ return andConditions.equals(rule.andConditions) && valueToApply.equals(rule.valueToApply);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(andConditions, valueToApply);
+ }
}
diff --git a/flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java b/flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java
index c89b5883fd1..c7da1abe7e2 100644
--- a/flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java
+++ b/flags/src/test/java/com/yahoo/vespa/flags/json/FlagDataTest.java
@@ -15,44 +15,45 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* @author hakonhall
*/
public class FlagDataTest {
- private final String json = "{\n" +
- " \"id\": \"id1\",\n" +
- " \"rules\": [\n" +
- " {\n" +
- " \"conditions\": [\n" +
- " {\n" +
- " \"type\": \"whitelist\",\n" +
- " \"dimension\": \"hostname\",\n" +
- " \"values\": [ \"host1\", \"host2\" ]\n" +
- " },\n" +
- " {\n" +
- " \"type\": \"blacklist\",\n" +
- " \"dimension\": \"application\",\n" +
- " \"values\": [ \"app1\", \"app2\" ]\n" +
- " }\n" +
- " ],\n" +
- " \"value\": true\n" +
- " },\n" +
- " {\n" +
- " \"conditions\": [\n" +
- " {\n" +
- " \"type\": \"whitelist\",\n" +
- " \"dimension\": \"zone\",\n" +
- " \"values\": [ \"zone1\", \"zone2\" ]\n" +
- " }\n" +
- " ],\n" +
- " \"value\": false\n" +
- " }\n" +
- " ],\n" +
- " \"attributes\": {\n" +
- " \"zone\": \"zone1\"\n" +
- " }\n" +
- "}";
+ private final String json = """
+ {
+ "id": "id1",
+ "rules": [
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "hostname",
+ "values": [ "host1", "host2" ]
+ },
+ {
+ "type": "blacklist",
+ "dimension": "application",
+ "values": [ "app1", "app2" ]
+ }
+ ],
+ "value": true
+ },
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "zone",
+ "values": [ "zone1", "zone2" ]
+ }
+ ],
+ "value": false
+ }
+ ],
+ "attributes": {
+ "zone": "zone1"
+ }
+ }""";
private final FetchVector vector = new FetchVector();
@Test
- void test() {
+ void testResolve() {
// Second rule matches with the default zone matching
verify(Optional.of("false"), vector);
@@ -74,6 +75,143 @@ public class FlagDataTest {
verify(Optional.empty(), vector.with(FetchVector.Dimension.ZONE_ID, "unknown zone"));
}
+ @Test
+ void testPartialResolve() {
+ FlagData data = FlagData.deserialize(json);
+ assertEquals(data.partialResolve(vector), data);
+ assertEquals(data.partialResolve(vector.with(FetchVector.Dimension.APPLICATION_ID, "app1")),
+ FlagData.deserialize("""
+ {
+ "id": "id1",
+ "rules": [
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "zone",
+ "values": [ "zone1", "zone2" ]
+ }
+ ],
+ "value": false
+ }
+ ],
+ "attributes": {
+ "zone": "zone1"
+ }
+ }"""));
+
+ assertEquals(data.partialResolve(vector.with(FetchVector.Dimension.APPLICATION_ID, "app1")),
+ FlagData.deserialize("""
+ {
+ "id": "id1",
+ "rules": [
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "zone",
+ "values": [ "zone1", "zone2" ]
+ }
+ ],
+ "value": false
+ }
+ ],
+ "attributes": {
+ "zone": "zone1"
+ }
+ }"""));
+
+ assertEquals(data.partialResolve(vector.with(FetchVector.Dimension.APPLICATION_ID, "app3")),
+ FlagData.deserialize("""
+ {
+ "id": "id1",
+ "rules": [
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "hostname",
+ "values": [ "host1", "host2" ]
+ }
+ ],
+ "value": true
+ },
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "zone",
+ "values": [ "zone1", "zone2" ]
+ }
+ ],
+ "value": false
+ }
+ ],
+ "attributes": {
+ "zone": "zone1"
+ }
+ }"""));
+
+ assertEquals(data.partialResolve(vector.with(FetchVector.Dimension.APPLICATION_ID, "app3")
+ .with(FetchVector.Dimension.HOSTNAME, "host1")),
+ FlagData.deserialize("""
+ {
+ "id": "id1",
+ "rules": [
+ {
+ "value": true
+ }
+ ],
+ "attributes": {
+ "zone": "zone1"
+ }
+ }"""));
+
+ assertEquals(data.partialResolve(vector.with(FetchVector.Dimension.APPLICATION_ID, "app3")
+ .with(FetchVector.Dimension.HOSTNAME, "host3")),
+ FlagData.deserialize("""
+ {
+ "id": "id1",
+ "rules": [
+ {
+ "conditions": [
+ {
+ "type": "whitelist",
+ "dimension": "zone",
+ "values": [ "zone1", "zone2" ]
+ }
+ ],
+ "value": false
+ }
+ ],
+ "attributes": {
+ "zone": "zone1"
+ }
+ }"""));
+
+ assertEquals(data.partialResolve(vector.with(FetchVector.Dimension.APPLICATION_ID, "app3")
+ .with(FetchVector.Dimension.HOSTNAME, "host3")
+ .with(FetchVector.Dimension.ZONE_ID, "zone2")),
+ FlagData.deserialize("""
+ {
+ "id": "id1",
+ "rules": [
+ {
+ "value": false
+ }
+ ]
+ }"""));
+
+ FlagData fullyResolved = data.partialResolve(vector.with(FetchVector.Dimension.APPLICATION_ID, "app3")
+ .with(FetchVector.Dimension.HOSTNAME, "host3")
+ .with(FetchVector.Dimension.ZONE_ID, "zone3"));
+ assertEquals(fullyResolved, FlagData.deserialize("""
+ {
+ "id": "id1"
+ }"""));
+ assertTrue(fullyResolved.isEmpty());
+ }
+
private void verify(Optional<String> expectedValue, FetchVector vector) {
FlagData data = FlagData.deserialize(json);
assertEquals("id1", data.id().toString());