diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2020-12-02 16:02:48 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-02 16:02:48 +0100 |
commit | e0ff48f98757a80ce516518f86a7d013f7e18aa1 (patch) | |
tree | 45f620649f0f2d5d197f46723e59751ddf3caa39 | |
parent | 9d59f4b003b10ae5a5a7f02874fb1b4b7e0ccccc (diff) | |
parent | 94f0cf42981ea4e8213c9497eec4c1a898289bad (diff) |
Merge pull request #15598 from vespa-engine/bjorncs/feature-flags
Bjorncs/feature flags
6 files changed, 201 insertions, 39 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index 6c7bd330159..843e850d55a 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -51,7 +51,7 @@ public interface ModelContext { Version wantedNodeVespaVersion(); interface FeatureFlags { - @ModelFeatureFlag(owner = "bjorncs") default boolean enableAutomaticReindexing() { return false; } + @ModelFeatureFlag(owners = {"bjorncs", "jonmv"}) default boolean enableAutomaticReindexing() { return false; } } /** Warning: As elsewhere in this package, do not make backwards incompatible changes that will break old config models! */ @@ -139,7 +139,7 @@ public interface ModelContext { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface ModelFeatureFlag { - String owner(); + String[] owners(); String comment() default ""; } diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlag.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlag.java index c706a2b1e51..c4e784e5717 100644 --- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlag.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlag.java @@ -16,7 +16,7 @@ import java.io.OutputStream; * @author hakonhall */ public class DefinedFlag extends HttpResponse { - private static ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = new ObjectMapper(); private final FlagDefinition flagDefinition; @@ -35,6 +35,11 @@ public class DefinedFlag extends HttpResponse { static void renderFlagDefinition(FlagDefinition flagDefinition, ObjectNode definitionNode) { definitionNode.put("description", flagDefinition.getDescription()); definitionNode.put("modification-effect", flagDefinition.getModificationEffect()); + ArrayNode ownersNode = mapper.createArrayNode(); + flagDefinition.getOwners().forEach(ownersNode::add); + definitionNode.set("owners", ownersNode); + definitionNode.put("createdAt", flagDefinition.getCreatedAt().toString()); + definitionNode.put("expiresAt", flagDefinition.getExpiresAt().toString()); ArrayNode dimensionsNode = definitionNode.putArray("dimensions"); flagDefinition.getDimensions().forEach(dimension -> dimensionsNode.add(DimensionHelper.toWire(dimension))); } diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java b/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java index 0dce61cf0bb..d01ca64cb9f 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.flags; import javax.annotation.concurrent.Immutable; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -12,13 +13,26 @@ import java.util.List; @Immutable public class FlagDefinition { private final UnboundFlag<?, ?, ?> unboundFlag; + private final List<String> owners; + private final Instant createdAt; + private final Instant expiresAt; private final String description; private final String modificationEffect; private final List<FetchVector.Dimension> dimensions; - public FlagDefinition(UnboundFlag<?, ?, ?> unboundFlag, String description, String modificationEffect, - FetchVector.Dimension... dimensions) { + public FlagDefinition( + UnboundFlag<?, ?, ?> unboundFlag, + List<String> owners, + Instant createdAt, + Instant expiresAt, + String description, + String modificationEffect, + FetchVector.Dimension... dimensions) { + validate(owners, createdAt, expiresAt); this.unboundFlag = unboundFlag; + this.owners = owners; + this.createdAt = createdAt; + this.expiresAt = expiresAt; this.description = description; this.modificationEffect = modificationEffect; this.dimensions = Collections.unmodifiableList(Arrays.asList(dimensions)); @@ -39,4 +53,26 @@ public class FlagDefinition { public String getModificationEffect() { return modificationEffect; } + + public List<String> getOwners() { return owners; } + + public Instant getCreatedAt() { return createdAt; } + + public Instant getExpiresAt() { return expiresAt; } + + private static void validate(List<String> owners, Instant createdAt, Instant expiresAt) { + if (expiresAt.isBefore(createdAt)) { + throw new IllegalArgumentException( + String.format( + "Flag cannot expire before its creation date (createdAt='%s', expiresAt='%s')", + createdAt, expiresAt)); + } + if (owners == PermanentFlags.OWNERS) { + if (!createdAt.equals(PermanentFlags.CREATED_AT) || !expiresAt.equals(PermanentFlags.EXPIRES_AT)) { + throw new IllegalArgumentException("Invalid creation or expiration date for permanent flag"); + } + } else if (owners.isEmpty()) { + throw new IllegalArgumentException("Owner(s) must be specified"); + } + } } 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 2054333c533..9706f374732 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -6,6 +6,10 @@ import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.flags.custom.ClusterCapacity; import com.yahoo.vespa.flags.custom.SharedHost; +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; @@ -44,12 +48,14 @@ public class Flags { public static final UnboundBooleanFlag FLEET_CANARY = defineFeatureFlag( "fleet-canary", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether the host is a fleet canary.", "Takes effect on next host admin tick.", HOSTNAME); public static final UnboundListFlag<String> DISABLED_HOST_ADMIN_TASKS = defineListFlag( "disabled-host-admin-tasks", List.of(), String.class, + List.of("nobody"), "2020-12-02", "2021-02-01", "List of host-admin task names (as they appear in the log, e.g. root>main>UpgradeTask), or some node-agent " + "functionality (see NodeAgentTask), that should be skipped", "Takes effect on next host admin tick", @@ -57,6 +63,7 @@ public class Flags { public static final UnboundStringFlag DOCKER_VERSION = defineStringFlag( "docker-version", "1.13.1-102.git7f2769b", + List.of("nobody"), "2020-12-02", "2021-02-01", "The version of the docker to use of the format VERSION-REL: The YUM package to be installed will be " + "2:docker-VERSION-REL.el7.centos.x86_64 in AWS (and without '.centos' otherwise). " + "If docker-version is not of this format, it must be parseable by YumPackageName::fromString.", @@ -65,6 +72,7 @@ public class Flags { public static final UnboundDoubleFlag CONTAINER_CPU_CAP = defineDoubleFlag( "container-cpu-cap", 0, + List.of("nobody"), "2020-12-02", "2021-02-01", "Hard limit on how many CPUs a container may use. This value is multiplied by CPU allocated to node, so " + "to cap CPU at 200%, set this to 2, etc.", "Takes effect on next node agent tick. Change is orchestrated, but does NOT require container restart", @@ -72,12 +80,14 @@ public class Flags { public static final UnboundIntFlag REBOOT_INTERVAL_IN_DAYS = defineIntFlag( "reboot-interval-in-days", 30, + List.of("nobody"), "2020-12-02", "2021-02-01", "No reboots are scheduled 0x-1x reboot intervals after the previous reboot, while reboot is " + "scheduled evenly distributed in the 1x-2x range (and naturally guaranteed at the 2x boundary).", "Takes effect on next run of NodeRebooter"); public static final UnboundBooleanFlag RETIRE_WITH_PERMANENTLY_DOWN = defineFeatureFlag( "retire-with-permanently-down", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "If enabled, retirement will end with setting the host status to PERMANENTLY_DOWN, " + "instead of ALLOWED_TO_BE_DOWN (old behavior).", "Takes effect on the next run of RetiredExpirer.", @@ -85,6 +95,7 @@ public class Flags { public static final UnboundListFlag<ClusterCapacity> PREPROVISION_CAPACITY = defineListFlag( "preprovision-capacity", List.of(), ClusterCapacity.class, + List.of("nobody"), "2020-12-02", "2021-02-01", "Specifies the resources that ought to be immediately available for additional cluster " + "allocations. If the resources are not available, additional hosts will be provisioned. " + "Only applies to dynamically provisioned zones.", @@ -92,134 +103,157 @@ public class Flags { public static final UnboundBooleanFlag COMPACT_PREPROVISION_CAPACITY = defineFeatureFlag( "compact-preprovision-capacity", true, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether preprovision capacity can be satisfied with available capacity on hosts with " + "existing allocations. Historically preprovision-capacity referred to empty hosts.", "Takes effect on next iteration of DynamicProvisioningMaintainer."); public static final UnboundJacksonFlag<SharedHost> SHARED_HOST = defineJacksonFlag( "shared-host", SharedHost.createDisabled(), SharedHost.class, + List.of("nobody"), "2020-12-02", "2021-02-01", "Specifies whether shared hosts can be provisioned, and if so, the advertised " + "node resources of the host, the maximum number of containers, etc.", "Takes effect on next iteration of DynamicProvisioningMaintainer."); public static final UnboundListFlag<String> INACTIVE_MAINTENANCE_JOBS = defineListFlag( "inactive-maintenance-jobs", List.of(), String.class, + List.of("nobody"), "2020-12-02", "2021-02-01", "The list of maintenance jobs that are inactive.", "Takes effect immediately, but any currently running jobs will run until completion."); public static final UnboundDoubleFlag DEFAULT_TERM_WISE_LIMIT = defineDoubleFlag( "default-term-wise-limit", 1.0, + List.of("nobody"), "2020-12-02", "2021-02-01", "Default limit for when to apply termwise query evaluation", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundStringFlag JVM_GC_OPTIONS = defineStringFlag( "jvm-gc-options", "", + List.of("nobody"), "2020-12-02", "2021-02-01", "Sets deafult jvm gc options", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundStringFlag FEED_SEQUENCER_TYPE = defineStringFlag( "feed-sequencer-type", "LATENCY", + List.of("nobody"), "2020-12-02", "2021-02-01", "Selects type of sequenced executor used for feeding, valid values are LATENCY, ADAPTIVE, THROUGHPUT", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundStringFlag RESPONSE_SEQUENCER_TYPE = defineStringFlag( "response-sequencer-type", "ADAPTIVE", + List.of("nobody"), "2020-12-02", "2021-02-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("nobody"), "2020-12-02", "2021-02-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-communicatiomanager-thread", false, + List.of("nobody"), "2020-12-02", "2021-02-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("nobody"), "2020-12-02", "2021-02-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("nobody"), "2020-12-02", "2021-02-01", "Should we skip the mbus reply thread", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag USE_THREE_PHASE_UPDATES = defineFeatureFlag( "use-three-phase-updates", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to enable the use of three-phase updates when bucket replicas are out of sync.", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag USE_DIRECT_STORAGE_API_RPC = defineFeatureFlag( "use-direct-storage-api-rpc", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to use direct RPC for Storage API communication between content cluster nodes.", "Takes effect at restart of distributor and content node process", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag USE_FAST_VALUE_TENSOR_IMPLEMENTATION = defineFeatureFlag( "use-fast-value-tensor-implementation", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to use FastValueBuilderFactory as the tensor implementation on all content nodes.", "Takes effect at restart of content node process", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag HOST_HARDENING = defineFeatureFlag( "host-hardening", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to enable host hardening Linux baseline.", "Takes effect on next tick or on host-admin restart (may vary where used).", HOSTNAME); public static final UnboundBooleanFlag TCP_ABORT_ON_OVERFLOW = defineFeatureFlag( "tcp-abort-on-overflow", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to set /proc/sys/net/ipv4/tcp_abort_on_overflow to 0 (false) or 1 (true)", "Takes effect on next host-admin tick.", HOSTNAME); public static final UnboundStringFlag ZOOKEEPER_SERVER_VERSION = defineStringFlag( "zookeeper-server-version", "3.5.6", + List.of("nobody"), "2020-12-02", "2021-02-01", "ZooKeeper server version, a jar file zookeeper-server-<ZOOKEEPER_SERVER_VERSION>-jar-with-dependencies.jar must exist", "Takes effect on restart of Docker container", NODE_TYPE, APPLICATION_ID, HOSTNAME); public static final UnboundStringFlag TLS_FOR_ZOOKEEPER_CLIENT_SERVER_COMMUNICATION = defineStringFlag( "tls-for-zookeeper-client-server-communication", "OFF", + List.of("nobody"), "2020-12-02", "2021-02-01", "How to setup TLS for ZooKeeper client/server communication. Valid values are OFF, PORT_UNIFICATION, TLS_WITH_PORT_UNIFICATION, TLS_ONLY", "Takes effect on restart of config server", NODE_TYPE, HOSTNAME); public static final UnboundBooleanFlag USE_TLS_FOR_ZOOKEEPER_CLIENT = defineFeatureFlag( "use-tls-for-zookeeper-client", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to use TLS for ZooKeeper clients", "Takes effect on restart of process", NODE_TYPE, HOSTNAME); public static final UnboundBooleanFlag VALIDATE_ENDPOINT_CERTIFICATES = defineFeatureFlag( "validate-endpoint-certificates", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether endpoint certificates should be validated before use", "Takes effect on the next deployment of the application"); public static final UnboundStringFlag DELETE_UNUSED_ENDPOINT_CERTIFICATES = defineStringFlag( "delete-unused-endpoint-certificates", "disable", + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether the endpoint certificate maintainer should delete unused certificates in cameo/zk", "Takes effect on next scheduled run of maintainer - set to \"disable\", \"dryrun\" or \"enable\""); public static final UnboundBooleanFlag USE_ALTERNATIVE_ENDPOINT_CERTIFICATE_PROVIDER = defineFeatureFlag( "use-alternative-endpoint-certificate-provider", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to use an alternative CA when provisioning new certificates", "Takes effect only on initial application deployment - not on later certificate refreshes!"); public static final UnboundStringFlag DOCKER_IMAGE_REPO = defineStringFlag( "docker-image-repo", "", + List.of("nobody"), "2020-12-02", "2021-02-01", "Override default docker image repo. Docker image version will be Vespa version.", "Takes effect on next deployment from controller", ZONE_ID, APPLICATION_ID); @@ -232,29 +266,34 @@ public class Flags { public static final UnboundBooleanFlag ENDPOINT_CERT_IN_SHARED_ROUTING = defineFeatureFlag( "endpoint-cert-in-shared-routing", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to provision and use endpoint certs for apps in shared routing zones", "Takes effect on next deployment of the application", APPLICATION_ID); public static final UnboundBooleanFlag USE_CLOUD_INIT_FORMAT = defineFeatureFlag( "use-cloud-init", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Use the cloud-init format when provisioning hosts", "Takes effect immediately", ZONE_ID); public static final UnboundBooleanFlag PROVISION_APPLICATION_ROLES = defineFeatureFlag( "provision-application-roles", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether application roles should be provisioned", "Takes effect on next deployment (controller)", ZONE_ID); public static final UnboundBooleanFlag APPLICATION_IAM_ROLE = defineFeatureFlag( "application-iam-roles", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Allow separate iam roles when provisioning/assigning hosts", "Takes effect immediately on new hosts, on next redeploy for applications", APPLICATION_ID); public static final UnboundBooleanFlag ENABLE_PUBLIC_SIGNUP_FLOW = defineFeatureFlag( "enable-public-signup-flow", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Show the public signup flow for a user in the console", "takes effect on browser reload of api/user/v1/user", CONSOLE_USER_EMAIL @@ -262,6 +301,7 @@ public class Flags { public static final UnboundBooleanFlag CONTROLLER_PROVISION_LB = defineFeatureFlag( "controller-provision-lb", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Provision load balancer for controller cluster", "Takes effect when controller application is redeployed", ZONE_ID @@ -269,6 +309,7 @@ public class Flags { public static final UnboundIntFlag TENANT_NODE_QUOTA = defineIntFlag( "tenant-node-quota", 5, + List.of("nobody"), "2020-12-02", "2021-02-01", "The number of nodes a tenant is allowed to request per cluster", "Only takes effect on next deployment, if set to a value other than the default for flag!", APPLICATION_ID @@ -276,6 +317,7 @@ public class Flags { public static final UnboundIntFlag TENANT_BUDGET_QUOTA = defineIntFlag( "tenant-budget-quota", -1, + List.of("nobody"), "2020-12-02", "2021-02-01", "The budget in cents/hr a tenant is allowed spend per instance, as calculated by NodeResources", "Only takes effect on next deployment, if set to a value other than the default for flag!", TENANT_ID @@ -283,128 +325,142 @@ public class Flags { public static final UnboundBooleanFlag ONLY_PUBLIC_ACCESS = defineFeatureFlag( "enable-public-only", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Only access public hosts from container", "Takes effect on next tick" ); public static final UnboundListFlag<String> OUTBOUND_BLOCKED_IPV4 = defineListFlag( "container-outbound-blocked-ipv4", List.of(), String.class, + List.of("nobody"), "2020-12-02", "2021-02-01", "List of IPs or CIDRs that are blocked for outbound connections", "Takes effect on next tick" ); public static final UnboundListFlag<String> OUTBOUND_BLOCKED_IPV6 = defineListFlag( "container-outbound-blocked-ipv6", List.of(), String.class, + List.of("nobody"), "2020-12-02", "2021-02-01", "List of IPs or CIDRs that are blocked for outbound connections", "Takes effect on next tick" ); public static final UnboundBooleanFlag HIDE_SHARED_ROUTING_ENDPOINT = defineFeatureFlag( - "hide-shared-routing-endpoint", - false, + "hide-shared-routing-endpoint", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether the controller should hide shared routing layer endpoint", "Takes effect immediately", APPLICATION_ID ); public static final UnboundBooleanFlag SKIP_MAINTENANCE_DEPLOYMENT = defineFeatureFlag( - "node-repository-skip-maintenance-deployment", - false, + "node-repository-skip-maintenance-deployment", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether PeriodicApplicationMaintainer should skip deployment for an application", "Takes effect at next run of maintainer", APPLICATION_ID); public static final UnboundBooleanFlag USE_ACCESS_CONTROL_CLIENT_AUTHENTICATION = defineFeatureFlag( - "use-access-control-client-authentication", - false, + "use-access-control-client-authentication", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether application container should set up client authentication on default port based on access control element", "Takes effect on next internal redeployment", APPLICATION_ID); public static final UnboundBooleanFlag USE_ASYNC_MESSAGE_HANDLING_ON_SCHEDULE = defineFeatureFlag( "async-message-handling-on-schedule", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Optionally deliver async messages in own thread", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag CONTENT_NODE_BUCKET_DB_STRIPE_BITS = defineIntFlag( "content-node-bucket-db-stripe-bits", 0, + List.of("nobody"), "2020-12-02", "2021-02-01", "Number of bits used for striping the bucket DB in service layer", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundIntFlag MERGE_CHUNK_SIZE = defineIntFlag( "merge-chunk-size", 0x400000, + List.of("nobody"), "2020-12-02", "2021-02-01", "Size of merge buffer in service layer", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundDoubleFlag FEED_CONCURRENCY = defineDoubleFlag( "feed-concurrency", 0.5, + List.of("nobody"), "2020-12-02", "2021-02-01", "How much concurrency should be allowed for feed", "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); public static final UnboundBooleanFlag ENABLE_AUTOMATIC_REINDEXING = defineFeatureFlag( - "enable-automatic-reindexing", - false, + "enable-automatic-reindexing", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to automatically trigger reindexing from config change", "Takes effect on next internal redeployment", APPLICATION_ID); public static final UnboundBooleanFlag USE_POWER_OF_TWO_CHOICES_LOAD_BALANCING = defineFeatureFlag( - "use-power-of-two-choices-load-balancing", - false, + "use-power-of-two-choices-load-balancing", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to use Power of two load balancing algorithm for application", "Takes effect on next internal redeployment", APPLICATION_ID); public static final UnboundBooleanFlag DYNAMIC_RECONFIGURATION_OF_ZOOKEEPER_CLUSTER = defineFeatureFlag( - "dynamic-reconfiguration-of-zookeeper-cluster", - false, + "dynamic-reconfiguration-of-zookeeper-cluster", false, + List.of("nobody"), "2020-12-02", "2021-02-01", "Whether to allow dynamic reconfiguration of zookeeper cluster", "Takes effect on next deployment", APPLICATION_ID); /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ - public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description, + public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners, + String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { - return define(UnboundBooleanFlag::new, flagId, defaultValue, description, modificationEffect, 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, String description, + public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List<String> owners, + String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { - return define(UnboundStringFlag::new, flagId, defaultValue, description, modificationEffect, dimensions); + return define(UnboundStringFlag::new, 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, String description, + public static UnboundIntFlag defineIntFlag(String flagId, int defaultValue, List<String> owners, + String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { - return define(UnboundIntFlag::new, flagId, defaultValue, description, modificationEffect, 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, String description, + public static UnboundLongFlag defineLongFlag(String flagId, long defaultValue, List<String> owners, + String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { - return define(UnboundLongFlag::new, flagId, defaultValue, description, modificationEffect, 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, String description, + public static UnboundDoubleFlag defineDoubleFlag(String flagId, double defaultValue, List<String> owners, + String createdAt, String expiresAt, String description, String modificationEffect, FetchVector.Dimension... dimensions) { - return define(UnboundDoubleFlag::new, flagId, defaultValue, description, modificationEffect, 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 <T> UnboundJacksonFlag<T> defineJacksonFlag(String flagId, T defaultValue, Class<T> jacksonClass, String description, + public static <T> UnboundJacksonFlag<T> defineJacksonFlag(String flagId, T defaultValue, Class<T> jacksonClass, List<String> 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, description, modificationEffect, dimensions); + flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static <T> UnboundListFlag<T> defineListFlag(String flagId, List<T> defaultValue, Class<T> elementClass, + List<String> 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, description, modificationEffect, dimensions); + flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); } @FunctionalInterface @@ -435,6 +491,9 @@ public class Flags { private static <T, U extends UnboundFlag<?, ?, ?>> U define(TypedUnboundFlagFactory<T, U> factory, String flagId, T defaultValue, + List<String> owners, + String createdAt, + String expiresAt, String description, String modificationEffect, FetchVector.Dimension[] dimensions) { @@ -445,11 +504,16 @@ public class Flags { // (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); + 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<FlagDefinition> getAllFlags() { return List.copyOf(flags.values()); } diff --git a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java new file mode 100644 index 00000000000..c1f0f269f73 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java @@ -0,0 +1,57 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * Definition for permanent feature flags + * + * @author bjorncs + */ +public class PermanentFlags { + + static final List<String> OWNERS = List.of(); + static final Instant CREATED_AT = Instant.EPOCH; + static final Instant EXPIRES_AT = Instant.MAX; + + private PermanentFlags() {} + + private static UnboundBooleanFlag defineFeatureFlag( + String flagId, boolean defaultValue, String description, String modificationEffect, FetchVector.Dimension... dimensions) { + return Flags.defineFeatureFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions); + } + + private static UnboundStringFlag defineStringFlag( + String flagId, String defaultValue, String description, String modificationEffect, FetchVector.Dimension... dimensions) { + return Flags.defineStringFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions); + } + + private static UnboundIntFlag defineIntFlag( + String flagId, int defaultValue, String description, String modificationEffect, FetchVector.Dimension... dimensions) { + return Flags.defineIntFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions); + } + + private static UnboundLongFlag defineLongFlag( + String flagId, long defaultValue, String description, String modificationEffect, FetchVector.Dimension... dimensions) { + return Flags.defineLongFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions); + } + + private static UnboundDoubleFlag defineDoubleFlag( + String flagId, double defaultValue, String description, String modificationEffect, FetchVector.Dimension... dimensions) { + return Flags.defineDoubleFlag(flagId, defaultValue, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions); + } + + private static <T> UnboundJacksonFlag<T> defineJacksonFlag( + String flagId, T defaultValue, Class<T> jacksonClass, String description, String modificationEffect, FetchVector.Dimension... dimensions) { + return Flags.defineJacksonFlag(flagId, defaultValue, jacksonClass, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions); + } + + private static <T> UnboundListFlag<T> defineListFlag( + String flagId, List<T> defaultValue, Class<T> elementClass, String description, String modificationEffect, FetchVector.Dimension... dimensions) { + return Flags.defineListFlag(flagId, defaultValue, elementClass, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, dimensions); + } + + private static String toString(Instant instant) { return DateTimeFormatter.ISO_DATE.format(instant); } +} diff --git a/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java b/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java index 6b043ea5a89..8882a15e526 100644 --- a/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java +++ b/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java @@ -34,7 +34,7 @@ public class FlagsTest { public void testBoolean() { final boolean defaultValue = false; FlagSource source = mock(FlagSource.class); - BooleanFlag booleanFlag = Flags.defineFeatureFlag("id", defaultValue, "description", + BooleanFlag booleanFlag = Flags.defineFeatureFlag("id", defaultValue, List.of("owner"), "1970-01-01", "2100-01-01", "description", "modification effect", FetchVector.Dimension.ZONE_ID, FetchVector.Dimension.HOSTNAME) .with(FetchVector.Dimension.ZONE_ID, "a-zone") .bindTo(source); @@ -69,29 +69,29 @@ public class FlagsTest { @Test public void testString() { - testGeneric(Flags.defineStringFlag("string-id", "default value", "description", + testGeneric(Flags.defineStringFlag("string-id", "default value", List.of("owner"), "1970-01-01", "2100-01-01", "description", "modification effect", FetchVector.Dimension.ZONE_ID, FetchVector.Dimension.HOSTNAME), "other value"); } @Test public void testInt() { - testGeneric(Flags.defineIntFlag("int-id", 2, "desc", "mod"), 3); + testGeneric(Flags.defineIntFlag("int-id", 2, List.of("owner"), "1970-01-01", "2100-01-01", "desc", "mod"), 3); } @Test public void testLong() { - testGeneric(Flags.defineLongFlag("long-id", 1L, "desc", "mod"), 2L); + testGeneric(Flags.defineLongFlag("long-id", 1L, List.of("owner"), "1970-01-01", "2100-01-01", "desc", "mod"), 2L); } @Test public void testDouble() { - testGeneric(Flags.defineDoubleFlag("double-id", 3.142, "desc", "mod"), 2.718); + testGeneric(Flags.defineDoubleFlag("double-id", 3.142, List.of("owner"), "1970-01-01", "2100-01-01", "desc", "mod"), 2.718); } @Test public void testList() { - testGeneric(Flags.defineListFlag("list-id", List.of("a"), String.class, "desc", "mod"), List.of("a", "b", "c")); + testGeneric(Flags.defineListFlag("list-id", List.of("a"), String.class, List.of("owner"), "1970-01-01", "2100-01-01", "desc", "mod"), List.of("a", "b", "c")); } @Test @@ -102,10 +102,10 @@ public class FlagsTest { instance.string = "foo"; testGeneric(Flags.defineJacksonFlag("jackson-id", defaultInstance, ExampleJacksonClass.class, - "description", "modification effect", FetchVector.Dimension.HOSTNAME), + List.of("owner"), "1970-01-01", "2100-01-01", "description", "modification effect", FetchVector.Dimension.HOSTNAME), instance); - testGeneric(Flags.defineListFlag("jackson-list-id", List.of(defaultInstance), ExampleJacksonClass.class, "desc", "mod"), + testGeneric(Flags.defineListFlag("jackson-list-id", List.of(defaultInstance), ExampleJacksonClass.class, List.of("owner"), "1970-01-01", "2100-01-01", "desc", "mod"), List.of(instance)); } |