// Copyright Yahoo. 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 java.time.Instant; import java.time.LocalDate; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; import java.util.TreeMap; import java.util.function.Predicate; import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID; import static com.yahoo.vespa.flags.FetchVector.Dimension.CONSOLE_USER_EMAIL; 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. * *

To use feature flags, define the flag in this class as an "unbound" flag, e.g. {@link UnboundBooleanFlag} * or {@link UnboundStringFlag}. At the location you want to get the value of the flag, you need the following:

* *
    *
  1. The unbound flag
  2. *
  3. A {@link FlagSource}. The flag source is typically available as an injectable component. Binding * an unbound flag to a flag source produces a (bound) flag, e.g. {@link BooleanFlag} and {@link StringFlag}.
  4. *
  5. If you would like your flag value to be dependent on e.g. the application ID, then 1. you should * declare this in the unbound flag definition in this file (referring to * {@link FetchVector.Dimension#APPLICATION_ID}), and 2. specify the application ID when retrieving the value, e.g. * {@link BooleanFlag#with(FetchVector.Dimension, String)}. See {@link FetchVector} for more info.
  6. *
* *

Once the code is in place, you can override the flag value. This depends on the flag source, but typically * there is a REST API for updating the flags in the config server, which is the root of all flag sources in the zone.

* * @author hakonhall */ public class Flags { private static volatile TreeMap flags = new TreeMap<>(); public static final UnboundBooleanFlag MAIN_CHAIN_GRAPH = defineFeatureFlag( "main-chain-graph", true, List.of("hakonhall"), "2022-07-06", "2022-10-05", "Whether to run all tasks in the main task chain up to the one failing to converge (false), or " + "run all tasks in the main task chain whose dependencies have converged (true). And when suspending, " + "whether to run the tasks in sequence (false) or in reverse sequence (true).", "On first tick of the main chain after (re)start of host admin.", ZONE_ID, NODE_TYPE, HOSTNAME); public static final UnboundDoubleFlag DEFAULT_TERM_WISE_LIMIT = defineDoubleFlag( "default-term-wise-limit", 1.0, List.of("baldersheim"), "2020-12-02", "2023-01-01", "Default limit for when to apply termwise query evaluation", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundStringFlag QUERY_DISPATCH_POLICY = defineStringFlag( "query-dispatch-policy", "adaptive", List.of("baldersheim"), "2022-08-20", "2023-01-01", "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); public static final UnboundStringFlag FEED_SEQUENCER_TYPE = defineStringFlag( "feed-sequencer-type", "THROUGHPUT", List.of("baldersheim"), "2020-12-02", "2023-01-01", "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); public static final UnboundBooleanFlag KEEP_STORAGE_NODE_UP = defineFeatureFlag( "keep-storage-node-up", true, List.of("hakonhall"), "2022-07-07", "2022-10-07", "Whether to leave the storage node (with wanted state) UP while the node is permanently down.", "Takes effect immediately for nodes transitioning to permanently down.", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag MAX_UNCOMMITTED_MEMORY = defineIntFlag( "max-uncommitted-memory", 130000, List.of("geirst, baldersheim"), "2021-10-21", "2023-01-01", "Max amount of memory holding updates to an attribute before we do a commit.", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundStringFlag RESPONSE_SEQUENCER_TYPE = defineStringFlag( "response-sequencer-type", "ADAPTIVE", List.of("baldersheim"), "2020-12-02", "2023-01-01", "Selects type of sequenced executor used for mbus responses, valid values are LATENCY, ADAPTIVE, THROUGHPUT", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag RESPONSE_NUM_THREADS = defineIntFlag( "response-num-threads", 2, List.of("baldersheim"), "2020-12-02", "2023-01-01", "Number of threads used for mbus responses, default is 2, negative number = numcores/4", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag SKIP_COMMUNICATIONMANAGER_THREAD = defineFeatureFlag( "skip-communicationmanager-thread", false, List.of("baldersheim"), "2020-12-02", "2023-01-01", "Should we skip the communicationmanager thread", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag SKIP_MBUS_REQUEST_THREAD = defineFeatureFlag( "skip-mbus-request-thread", false, List.of("baldersheim"), "2020-12-02", "2023-01-01", "Should we skip the mbus request thread", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag SKIP_MBUS_REPLY_THREAD = defineFeatureFlag( "skip-mbus-reply-thread", false, List.of("baldersheim"), "2020-12-02", "2023-01-01", "Should we skip the mbus reply thread", "Takes effect at redeployment", ZONE_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-01-01", "Optionally deliver async messages in own thread", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundDoubleFlag FEED_CONCURRENCY = defineDoubleFlag( "feed-concurrency", 0.5, List.of("baldersheim"), "2020-12-02", "2023-01-01", "How much concurrency should be allowed for feed", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundDoubleFlag FEED_NICENESS = defineDoubleFlag( "feed-niceness", 0.0, List.of("baldersheim"), "2022-06-24", "2023-01-01", "How nice feeding shall be", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag MBUS_JAVA_NUM_TARGETS = defineIntFlag( "mbus-java-num-targets", 1, List.of("baldersheim"), "2022-07-05", "2023-01-01", "Number of rpc targets per service", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag MBUS_CPP_NUM_TARGETS = defineIntFlag( "mbus-cpp-num-targets", 1, List.of("baldersheim"), "2022-07-05", "2023-01-01", "Number of rpc targets per service", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag RPC_NUM_TARGETS = defineIntFlag( "rpc-num-targets", 1, List.of("baldersheim"), "2022-07-05", "2023-01-01", "Number of rpc targets per content node", "Takes effect at redeployment", ZONE_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-01-01", "Number write events before waking up transport thread", "Takes effect at redeployment", ZONE_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-01-01", "Number write events before waking up transport thread", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag RPC_EVENTS_BEFORE_WAKEUP = defineIntFlag( "rpc-events-before-wakeup", 1, List.of("baldersheim"), "2022-07-05", "2023-01-01", "Number write events before waking up transport thread", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag MBUS_NUM_NETWORK_THREADS = defineIntFlag( "mbus-num-network-threads", 1, List.of("baldersheim"), "2022-07-01", "2023-01-01", "Number of threads used for mbus network", "Takes effect at redeployment", ZONE_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-01-01", "Controls whether we do track usage and reclaim unused enum values in shared string repo", "Takes effect at redeployment", ZONE_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-01-01", "Will trigger a heap dump during if container shutdown times out", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag LOAD_CODE_AS_HUGEPAGES = defineFeatureFlag( "load-code-as-hugepages", false, List.of("baldersheim"), "2022-05-13", "2023-01-01", "Will try to map the code segment with huge (2M) pages", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundDoubleFlag CONTAINER_SHUTDOWN_TIMEOUT = defineDoubleFlag( "container-shutdown-timeout", 50.0, List.of("baldersheim"), "2021-09-25", "2023-05-01", "Timeout for shutdown of a jdisc container", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundListFlag ALLOWED_ATHENZ_PROXY_IDENTITIES = defineListFlag( "allowed-athenz-proxy-identities", List.of(), String.class, List.of("bjorncs", "tokle"), "2021-02-10", "2022-11-01", "Allowed Athenz proxy identities", "takes effect at redeployment"); public static final UnboundIntFlag MAX_ACTIVATION_INHIBITED_OUT_OF_SYNC_GROUPS = defineIntFlag( "max-activation-inhibited-out-of-sync-groups", 0, List.of("vekterli"), "2021-02-19", "2022-12-01", "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); public static final UnboundIntFlag MAX_CONCURRENT_MERGES_PER_NODE = defineIntFlag( "max-concurrent-merges-per-node", 16, List.of("balder", "vekterli"), "2021-06-06", "2022-12-01", "Specifies max concurrent merges per content node.", "Takes effect at redeploy", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag MAX_MERGE_QUEUE_SIZE = defineIntFlag( "max-merge-queue-size", 100, List.of("balder", "vekterli"), "2021-06-06", "2022-12-01", "Specifies max size of merge queue.", "Takes effect at redeploy", ZONE_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", "2022-11-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); public static final UnboundBooleanFlag ENABLED_HORIZON_DASHBOARD = defineFeatureFlag( "enabled-horizon-dashboard", false, List.of("olaa"), "2021-09-13", "2023-01-01", "Enable Horizon dashboard", "Takes effect immediately", TENANT_ID, CONSOLE_USER_EMAIL ); public static final UnboundBooleanFlag UNORDERED_MERGE_CHAINING = defineFeatureFlag( "unordered-merge-chaining", true, List.of("vekterli", "geirst"), "2021-11-15", "2022-11-01", "Enables the use of unordered merge chains for data merge operations", "Takes effect at redeploy", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag IGNORE_THREAD_STACK_SIZES = defineFeatureFlag( "ignore-thread-stack-sizes", false, List.of("arnej"), "2021-11-12", "2022-12-01", "Whether C++ thread creation should ignore any requested stack size", "Triggers restart, takes effect immediately", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag USE_V8_GEO_POSITIONS = defineFeatureFlag( "use-v8-geo-positions", true, List.of("arnej"), "2021-11-15", "2022-12-31", "Use Vespa 8 types and formats for geographical positions", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag MAX_COMPACT_BUFFERS = defineIntFlag( "max-compact-buffers", 1, List.of("baldersheim", "geirst", "toregge"), "2021-12-15", "2023-01-01", "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); public static final UnboundStringFlag MERGE_THROTTLING_POLICY = defineStringFlag( "merge-throttling-policy", "STATIC", List.of("vekterli"), "2022-01-25", "2022-12-01", "Sets the policy used for merge throttling on the content nodes. " + "Valid values: STATIC, DYNAMIC", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundDoubleFlag PERSISTENCE_THROTTLING_WS_DECREMENT_FACTOR = defineDoubleFlag( "persistence-throttling-ws-decrement-factor", 1.2, List.of("vekterli"), "2022-01-27", "2022-12-01", "Sets the dynamic throttle policy window size decrement factor for persistence " + "async throttling. Only applies if DYNAMIC policy is used.", "Takes effect on redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundDoubleFlag PERSISTENCE_THROTTLING_WS_BACKOFF = defineDoubleFlag( "persistence-throttling-ws-backoff", 0.95, List.of("vekterli"), "2022-01-27", "2022-12-01", "Sets the dynamic throttle policy window size backoff for persistence " + "async throttling. Only applies if DYNAMIC policy is used. Valid range [0, 1]", "Takes effect on redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag PERSISTENCE_THROTTLING_WINDOW_SIZE = defineIntFlag( "persistence-throttling-window-size", -1, List.of("vekterli"), "2022-02-23", "2022-11-01", "If greater than zero, sets both min and max window size to the given number, effectively " + "turning dynamic throttling into a static throttling policy. " + "Only applies if DYNAMIC policy is used.", "Takes effect on redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundDoubleFlag PERSISTENCE_THROTTLING_WS_RESIZE_RATE = defineDoubleFlag( "persistence-throttling-ws-resize-rate", 3.0, List.of("vekterli"), "2022-02-23", "2022-11-01", "Sets the dynamic throttle policy resize rate. Only applies if DYNAMIC policy is used.", "Takes effect on redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag PERSISTENCE_THROTTLING_OF_MERGE_FEED_OPS = defineFeatureFlag( "persistence-throttling-of-merge-feed-ops", true, List.of("vekterli"), "2022-02-24", "2022-11-01", "If true, each put/remove contained within a merge is individually throttled as if it " + "were a put/remove from a client. If false, merges are throttled at a persistence thread " + "level, i.e. per ApplyBucketDiff message, regardless of how many document operations " + "are contained within. Only applies if DYNAMIC policy is used.", "Takes effect on redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag USE_QRSERVER_SERVICE_NAME = defineFeatureFlag( "use-qrserver-service-name", false, List.of("arnej"), "2022-01-18", "2022-12-31", "Use backwards-compatible 'qrserver' service name for containers with only 'search' API", "Takes effect at redeployment", ZONE_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); public static final UnboundBooleanFlag NOTIFICATION_DISPATCH_FLAG = defineFeatureFlag( "dispatch-notifications", false, List.of("enygaard"), "2022-05-02", "2022-12-30", "Whether we should send notification for a given tenant", "Takes effect immediately", TENANT_ID); public static final UnboundBooleanFlag ENABLE_PROXY_PROTOCOL_MIXED_MODE = defineFeatureFlag( "enable-proxy-protocol-mixed-mode", true, List.of("tokle"), "2022-05-09", "2022-11-01", "Enable or disable proxy protocol mixed mode", "Takes effect on redeployment", APPLICATION_ID); public static final UnboundListFlag FILE_DISTRIBUTION_ACCEPTED_COMPRESSION_TYPES = defineListFlag( "file-distribution-accepted-compression-types", List.of("gzip", "lz4"), String.class, List.of("hmusum"), "2022-07-05", "2022-11-01", "´List of accepted compression types used when asking for a file reference. Valid values: gzip, lz4", "Takes effect on restart of service", APPLICATION_ID); public static final UnboundListFlag FILE_DISTRIBUTION_COMPRESSION_TYPES_TO_SERVE = defineListFlag( "file-distribution-compression-types-to-use", List.of("lz4", "gzip"), String.class, List.of("hmusum"), "2022-07-05", "2022-11-01", "List of compression types to use (in preferred order), matched with accepted compression types when serving file references. Valid values: gzip, lz4", "Takes effect on restart of service", APPLICATION_ID); public static final UnboundBooleanFlag USE_YUM_PROXY_V2 = defineFeatureFlag( "use-yumproxy-v2", false, List.of("tokle"), "2022-05-05", "2022-11-01", "Use yumproxy-v2", "Takes effect on host admin restart", HOSTNAME); public static final UnboundStringFlag LOG_FILE_COMPRESSION_ALGORITHM = defineStringFlag( "log-file-compression-algorithm", "", 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); public static final UnboundBooleanFlag SEPARATE_METRIC_CHECK_CONFIG = defineFeatureFlag( "separate-metric-check-config", false, List.of("olaa"), "2022-07-04", "2022-11-01", "Determines whether one metrics config check should be written per Vespa node", "Takes effect on next tick", HOSTNAME); public static final UnboundStringFlag TLS_CAPABILITIES_ENFORCEMENT_MODE = defineStringFlag( "tls-capabilities-enforcement-mode", "disable", List.of("bjorncs", "vekterli"), "2022-07-21", "2024-01-01", "Configure Vespa TLS capability enforcement mode", "Takes effect on restart of Docker container", APPLICATION_ID,HOSTNAME,NODE_TYPE,TENANT_ID,VESPA_VERSION ); public static final UnboundBooleanFlag CLEANUP_TENANT_ROLES = defineFeatureFlag( "cleanup-tenant-roles", false, List.of("olaa"), "2022-08-10", "2023-01-01", "Determines whether old tenant roles should be deleted", "Takes effect next maintenance run" ); public static final UnboundBooleanFlag USE_TWO_PHASE_DOCUMENT_GC = defineFeatureFlag( "use-two-phase-document-gc", false, List.of("vekterli"), "2022-08-24", "2022-11-01", "Use two-phase document GC in content clusters", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag RESTRICT_DATA_PLANE_BINDINGS = defineFeatureFlag( "restrict-data-plane-bindings", false, List.of("mortent"), "2022-09-08", "2022-11-01", "Use restricted data plane bindings", "Takes effect at redeployment", APPLICATION_ID); public static final UnboundStringFlag CSRF_MODE = defineStringFlag( "csrf-mode", "disabled", List.of("bjorncs", "tokle"), "2022-09-22", "2023-06-01", "Set mode for CSRF filter ('disabled', 'log_only', 'enabled')", "Takes effect on controller restart/redeployment"); public static final UnboundBooleanFlag SOFT_REBUILD = defineFeatureFlag( "soft-rebuild", false, List.of("mpolden"), "2022-09-27", "2022-12-01", "Whether soft rebuild can be used to rebuild hosts with remote disk", "Takes effect on next run of OsUpgradeActivator" ); public static final UnboundListFlag CSRF_USERS = defineListFlag( "csrf-users", List.of(), String.class, List.of("bjorncs", "tokle"), "2022-09-22", "2023-06-01", "List of users to enable CSRF filter for. Use empty list for everyone.", "Takes effect on controller restart/redeployment"); public static final UnboundBooleanFlag ENABLE_OTELCOL = defineFeatureFlag( "enable-otel-collector", false, List.of("olaa"), "2022-09-23", "2023-01-01", "Whether an OpenTelemetry collector should be enabled", "Takes effect at next tick", APPLICATION_ID); public static final UnboundBooleanFlag CONSOLE_CSRF = defineFeatureFlag( "console-csrf", false, List.of("bjorncs", "tokle"), "2022-09-26", "2023-06-01", "Enable CSRF token in console", "Takes effect immediately", CONSOLE_USER_EMAIL); public static final UnboundBooleanFlag USE_WIREGUARD_ON_CONFIGSERVERS = defineFeatureFlag( "use-wireguard-on-configservers", false, List.of("andreer", "gjoranv"), "2022-09-28", "2023-04-01", "Set up a WireGuard endpoint on config servers", "Takes effect on configserver restart", ZONE_ID, NODE_TYPE); /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List owners, String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { return define(UnboundBooleanFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List owners, String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { return defineStringFlag(flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, value -> true, dimensions); } /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List owners, String createdAt, String expiresAt, String description, String modificationEffect, Predicate validator, FetchVector.Dimension... dimensions) { return define((i, d, v) -> new UnboundStringFlag(i, d, v, validator), flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundIntFlag defineIntFlag(String flagId, int defaultValue, List owners, String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { return define(UnboundIntFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundLongFlag defineLongFlag(String flagId, long defaultValue, List owners, String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { return define(UnboundLongFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundDoubleFlag defineDoubleFlag(String flagId, double defaultValue, List owners, String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { return define(UnboundDoubleFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundJacksonFlag defineJacksonFlag(String flagId, T defaultValue, Class jacksonClass, List owners, String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { return define((id2, defaultValue2, vector2) -> new UnboundJacksonFlag<>(id2, defaultValue2, vector2, jacksonClass), flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundListFlag defineListFlag(String flagId, List defaultValue, Class elementClass, List owners, String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { return define((fid, dval, fvec) -> new UnboundListFlag<>(fid, dval, elementClass, fvec), flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } @FunctionalInterface private interface TypedUnboundFlagFactory> { U create(FlagId id, T defaultValue, FetchVector defaultFetchVector); } /** * Defines a Flag. * * @param factory Factory for creating unbound flag of type U * @param flagId The globally unique FlagId. * @param defaultValue The default value if none is present after resolution. * @param description Description of how the flag is used. * @param modificationEffect What is required for the flag to take effect? A restart of process? immediately? etc. * @param dimensions What dimensions will be set in the {@link FetchVector} when fetching * the flag value in * {@link FlagSource#fetch(FlagId, FetchVector) FlagSource::fetch}. * 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. * @param The boxed type of the flag value, e.g. Boolean for flags guarding features. * @param The type of the unbound flag, e.g. UnboundBooleanFlag. * @return An unbound flag with {@link FetchVector.Dimension#HOSTNAME HOSTNAME} and * {@link FetchVector.Dimension#VESPA_VERSION VESPA_VERSION} already set. The ZONE environment * is typically implicit. */ private static > U define(TypedUnboundFlagFactory factory, String flagId, T defaultValue, List owners, String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension[] dimensions) { FlagId id = new FlagId(flagId); 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, owners, parseDate(createdAt), parseDate(expiresAt), description, modificationEffect, dimensions); flags.put(id, definition); return unboundFlag; } private static Instant parseDate(String rawDate) { return DateTimeFormatter.ISO_DATE.parse(rawDate, LocalDate::from).atStartOfDay().toInstant(ZoneOffset.UTC); } public static List getAllFlags() { return List.copyOf(flags.values()); } public static Optional getFlag(FlagId flagId) { return Optional.ofNullable(flags.get(flagId)); } /** * Allows the statically defined flags to be controlled in a test. * *

Returns a Replacer instance to be used with e.g. a try-with-resources block. Within the block, * the flags starts out as cleared. Flags can be defined, etc. When leaving the block, the flags from * before the block is reinserted. * *

NOT thread-safe. Tests using this cannot run in parallel. */ public static Replacer clearFlagsForTesting(FlagId... flagsToKeep) { return new Replacer(flagsToKeep); } public static class Replacer implements AutoCloseable { private static volatile boolean flagsCleared = false; private final TreeMap savedFlags; private Replacer(FlagId... flagsToKeep) { verifyAndSetFlagsCleared(true); this.savedFlags = Flags.flags; Flags.flags = new TreeMap<>(); List.of(flagsToKeep).forEach(id -> Flags.flags.put(id, savedFlags.get(id))); } @Override public void close() { verifyAndSetFlagsCleared(false); Flags.flags = savedFlags; } /** * Used to implement a simple verification that Replacer is not used by multiple threads. * For instance two different tests running in parallel cannot both use Replacer. */ private static void verifyAndSetFlagsCleared(boolean newValue) { if (flagsCleared == newValue) { throw new IllegalStateException("clearFlagsForTesting called while already cleared - running tests in parallell!?"); } flagsCleared = newValue; } } }