diff options
author | Øyvind Grønnesby <oyving@verizonmedia.com> | 2020-12-03 19:33:21 +0100 |
---|---|---|
committer | Øyvind Grønnesby <oyving@verizonmedia.com> | 2020-12-03 19:33:21 +0100 |
commit | 2b01b49d4f9fa0338c00a113daf7e61e10041646 (patch) | |
tree | 2c970f2cae118746977147fbaca49e5f0c02c09a /flags | |
parent | f0771b2201f06684f66c7261c528167adb8f4c7b (diff) | |
parent | 5ad34990803dc859ee107ba46b3e60ef877898ca (diff) |
Merge remote-tracking branch 'origin/master' into ogronnesby/trial-tenant-limit
Conflicts:
controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java
flags/src/main/java/com/yahoo/vespa/flags/Flags.java
Diffstat (limited to 'flags')
9 files changed, 400 insertions, 170 deletions
diff --git a/flags/pom.xml b/flags/pom.xml index ba0e4a94692..4f1bdcb61e3 100644 --- a/flags/pom.xml +++ b/flags/pom.xml @@ -88,6 +88,11 @@ <version>${project.version}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> </dependencies> <build> <plugins> 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 ab9dcd6415d..1279e11393d 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -3,18 +3,18 @@ package com.yahoo.vespa.flags; import com.yahoo.component.Vtag; 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; 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; @@ -42,226 +42,173 @@ import static com.yahoo.vespa.flags.FetchVector.Dimension.ZONE_ID; public class Flags { private static volatile TreeMap<FlagId, FlagDefinition> flags = new TreeMap<>(); - public static final UnboundBooleanFlag FLEET_CANARY = defineFeatureFlag( - "fleet-canary", false, - "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 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", - HOSTNAME, NODE_TYPE); - - public static final UnboundStringFlag DOCKER_VERSION = defineStringFlag( - "docker-version", "1.13.1-102.git7f2769b", - "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.", - "Takes effect on next tick.", - HOSTNAME); - - public static final UnboundDoubleFlag CONTAINER_CPU_CAP = defineDoubleFlag( - "container-cpu-cap", 0, - "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", - HOSTNAME, APPLICATION_ID); - - public static final UnboundIntFlag REBOOT_INTERVAL_IN_DAYS = defineIntFlag( - "reboot-interval-in-days", 30, - "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("hakonhall"), "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.", HOSTNAME); - public static final UnboundListFlag<ClusterCapacity> PREPROVISION_CAPACITY = defineListFlag( - "preprovision-capacity", List.of(), ClusterCapacity.class, - "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.", - "Takes effect on next iteration of DynamicProvisioningMaintainer."); - - public static final UnboundBooleanFlag COMPACT_PREPROVISION_CAPACITY = defineFeatureFlag( - "compact-preprovision-capacity", true, - "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, - "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, - "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("baldersheim"), "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", "", - "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("baldersheim"), "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("baldersheim"), "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("baldersheim"), "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("baldersheim"), "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("baldersheim"), "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("baldersheim"), "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("vekterli"), "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("geirst"), "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("geirst"), "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("hakonhall"), "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("andreer"), "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", - "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("hmusum"), "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("hmusum"), "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("andreer"), "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("andreer"), "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("andreer"), "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("valerijf"), "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); + public static final UnboundStringFlag YUM_DIST_HOST = defineStringFlag( + "yum-dist-host", "", + List.of("aressem"), "2020-12-02", "2021-02-01", + "Override the default dist host for yum.", + "Takes effect on next tick or on host-admin restart (may vary where used)."); + public static final UnboundBooleanFlag ENDPOINT_CERT_IN_SHARED_ROUTING = defineFeatureFlag( "endpoint-cert-in-shared-routing", false, + List.of("andreer"), "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, - "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("tokle"), "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("tokle"), "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, - "Show the public signup flow for a user in the console", - "takes effect on browser reload of api/user/v1/user", - CONSOLE_USER_EMAIL - ); - public static final UnboundIntFlag MAX_TRIAL_TENANTS = defineIntFlag( "max-trial-tenants", -1, + List.of("ogronnesby"), "2020-12-03", "2021-04-01", "The maximum nr. of tenants with trial plan, -1 is unlimited", "Takes effect immediately" ); public static final UnboundBooleanFlag CONTROLLER_PROVISION_LB = defineFeatureFlag( "controller-provision-lb", false, + List.of("mpolden"), "2020-12-02", "2021-02-01", "Provision load balancer for controller cluster", "Takes effect when controller application is redeployed", ZONE_ID @@ -269,142 +216,132 @@ public class Flags { public static final UnboundIntFlag TENANT_NODE_QUOTA = defineIntFlag( "tenant-node-quota", 5, + List.of("andreer"), "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 ); - public static final UnboundIntFlag TENANT_BUDGET_QUOTA = defineIntFlag( - "tenant-budget-quota", -1, - "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 - ); - public static final UnboundBooleanFlag ONLY_PUBLIC_ACCESS = defineFeatureFlag( "enable-public-only", false, + List.of("ogronnesby"), "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 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 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("tokle"), "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, - "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("tokle"), "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("baldersheim"), "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("baldersheim"), "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, - "Size of merge buffer in service layer", + List.of("baldersheim"), "2020-12-02", "2021-02-01", + "Size of baldersheim 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("baldersheim"), "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("bjorncs", "jonmv"), "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("tokle"), "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("hmusum"), "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 +372,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 +385,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..a3e2a11a79c --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java @@ -0,0 +1,172 @@ +// 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 com.yahoo.vespa.flags.custom.ClusterCapacity; +import com.yahoo.vespa.flags.custom.SharedHost; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +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.ZONE_ID; + +/** + * 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 = ZonedDateTime.of(2100, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant(); + + public static final UnboundBooleanFlag USE_ALTERNATIVE_ENDPOINT_CERTIFICATE_PROVIDER = defineFeatureFlag( + "use-alternative-endpoint-certificate-provider", false, + "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 JVM_GC_OPTIONS = defineStringFlag( + "jvm-gc-options", "", + "Sets deafult jvm gc options", + "Takes effect at redeployment", + ZONE_ID, APPLICATION_ID); + + public static final UnboundStringFlag DOCKER_VERSION = defineStringFlag( + "docker-version", "1.13.1-102.git7f2769b", + "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.", + "Takes effect on next tick.", + HOSTNAME); + + public static final UnboundBooleanFlag FLEET_CANARY = defineFeatureFlag( + "fleet-canary", false, + "Whether the host is a fleet canary.", + "Takes effect on next host admin tick.", + HOSTNAME); + + public static final UnboundListFlag<ClusterCapacity> PREPROVISION_CAPACITY = defineListFlag( + "preprovision-capacity", List.of(), ClusterCapacity.class, + "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.", + "Takes effect on next iteration of DynamicProvisioningMaintainer."); + + public static final UnboundIntFlag REBOOT_INTERVAL_IN_DAYS = defineIntFlag( + "reboot-interval-in-days", 30, + "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 UnboundJacksonFlag<SharedHost> SHARED_HOST = defineJacksonFlag( + "shared-host", SharedHost.createDisabled(), SharedHost.class, + "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 UnboundBooleanFlag SKIP_MAINTENANCE_DEPLOYMENT = defineFeatureFlag( + "node-repository-skip-maintenance-deployment", false, + "Whether PeriodicApplicationMaintainer should skip deployment for an application", + "Takes effect at next run of maintainer", + APPLICATION_ID); + + public static final UnboundListFlag<String> INACTIVE_MAINTENANCE_JOBS = defineListFlag( + "inactive-maintenance-jobs", List.of(), String.class, + "The list of maintenance jobs that are inactive.", + "Takes effect immediately, but any currently running jobs will run until completion."); + + public static final UnboundListFlag<String> OUTBOUND_BLOCKED_IPV4 = defineListFlag( + "container-outbound-blocked-ipv4", List.of(), String.class, + "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 IPs or CIDRs that are blocked for outbound connections", + "Takes effect on next tick"); + + public static final UnboundIntFlag TENANT_BUDGET_QUOTA = defineIntFlag( + "tenant-budget-quota", -1, + "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); + + public static final UnboundDoubleFlag CONTAINER_CPU_CAP = defineDoubleFlag( + "container-cpu-cap", 0, + "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", + HOSTNAME, APPLICATION_ID); + + public static final UnboundListFlag<String> DISABLED_HOST_ADMIN_TASKS = defineListFlag( + "disabled-host-admin-tasks", List.of(), String.class, + "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", + HOSTNAME, NODE_TYPE); + + public static final UnboundStringFlag DOCKER_IMAGE_REPO = defineStringFlag( + "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); + + public static final UnboundStringFlag ZOOKEEPER_SERVER_VERSION = defineStringFlag( + "zookeeper-server-version", "3.5.6", + "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 UnboundBooleanFlag ENABLE_PUBLIC_SIGNUP_FLOW = defineFeatureFlag( + "enable-public-signup-flow", false, + "Show the public signup flow for a user in the console", + "takes effect on browser reload of api/user/v1/user", + CONSOLE_USER_EMAIL); + + 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.withZone(ZoneOffset.UTC).format(instant); } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/custom/HostResources.java b/flags/src/main/java/com/yahoo/vespa/flags/custom/HostResources.java index c0b5d7a523c..129e1020b04 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/custom/HostResources.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/custom/HostResources.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; -import java.util.Optional; import java.util.Set; /** @@ -45,7 +44,7 @@ public class HostResources { this.vcpu = requirePositive("vcpu", vcpu); this.memoryGb = requirePositive("memoryGb", memoryGb); this.diskGb = requirePositive("diskGb", diskGb); - this.bandwidthGbps = requirePositive("bandwidthGbps", Optional.ofNullable(bandwidthGbps).orElse(0.3)); + this.bandwidthGbps = requirePositive("bandwidthGbps", bandwidthGbps); this.diskSpeed = validateEnum("diskSpeed", validDiskSpeeds, diskSpeed); this.storageType = validateEnum("storageType", validStorageTypes, storageType); this.containers = requirePositive("containers", containers); diff --git a/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java b/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java index e463159eb8f..c952161cf72 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/custom/SharedHost.java @@ -2,46 +2,68 @@ package com.yahoo.vespa.flags.custom; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.yahoo.vespa.flags.Flags; +import com.yahoo.vespa.flags.PermanentFlags; import java.util.List; import java.util.Objects; /** - * Defines properties related to shared hosts, see {@link Flags#SHARED_HOST}. + * Defines properties related to shared hosts, see {@link PermanentFlags#SHARED_HOST}. * * @author hakon */ @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class SharedHost { + private final int DEFAULT_MIN_COUNT = 0; + private final List<HostResources> resources; + private final int minCount; public static SharedHost createDisabled() { - return new SharedHost(null); + return new SharedHost(null, null); } + /** + * @param resourcesOrNull the resources of the shared host (or several to support e.g. tiers or + * fast/slow disks separately) + * @param minCountOrNull the minimum number of shared hosts + */ @JsonCreator - public SharedHost(@JsonProperty("resources") List<HostResources> resources) { - this.resources = resources == null ? List.of() : List.copyOf(resources); + public SharedHost(@JsonProperty("resources") List<HostResources> resourcesOrNull, + @JsonProperty("min-count") Integer minCountOrNull) { + this.resources = resourcesOrNull == null ? List.of() : List.copyOf(resourcesOrNull); + this.minCount = requireNonNegative(minCountOrNull, DEFAULT_MIN_COUNT, "min-count"); } - @JsonProperty("resources") + @JsonGetter("resources") public List<HostResources> getResourcesOrNull() { return resources.isEmpty() ? null : resources; } + @JsonGetter("min-count") + public Integer getMinCountOrNull() { + return minCount == DEFAULT_MIN_COUNT ? null : minCount; + } + + @JsonIgnore + public boolean isEnabled() { + return resources.size() > 0; + } + @JsonIgnore public List<HostResources> getHostResources() { return resources; } - public boolean isEnabled() { - return resources.size() > 0; + @JsonIgnore + public int getMinCount() { + return minCount; } @Override @@ -61,4 +83,16 @@ public class SharedHost { public int hashCode() { return Objects.hash(resources); } + + private int requireNonNegative(Integer integerOrNull, int defaultValue, String fieldName) { + if (integerOrNull == null) { + return defaultValue; + } + + if (integerOrNull < 0) { + throw new IllegalArgumentException(fieldName + " cannot be negative"); + } + + return integerOrNull; + } } 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 28e84bcf3e5..48ed318af41 100644 --- a/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java +++ b/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java @@ -4,8 +4,6 @@ package com.yahoo.vespa.flags; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.node.BooleanNode; -import com.yahoo.vespa.flags.custom.HostResources; -import com.yahoo.vespa.flags.custom.SharedHost; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -34,7 +32,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 +67,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,23 +100,14 @@ 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)); } - @Test - public void testSharedHostFlag() { - SharedHost sharedHost = new SharedHost(List.of(new HostResources( - 4.0, 16.0, 50.0, null, - "fast", "local", - 10))); - testGeneric(Flags.SHARED_HOST, sharedHost); - } - - private <T> void testGeneric(UnboundFlag<T, ?, ?> unboundFlag, T value) { + static <T> void testGeneric(UnboundFlag<T, ?, ?> unboundFlag, T value) { FlagSource source = mock(FlagSource.class); Flag<T, ?> flag = unboundFlag.bindTo(source); diff --git a/flags/src/test/java/com/yahoo/vespa/flags/PermanentFlagsTest.java b/flags/src/test/java/com/yahoo/vespa/flags/PermanentFlagsTest.java new file mode 100644 index 00000000000..3f43682cfb9 --- /dev/null +++ b/flags/src/test/java/com/yahoo/vespa/flags/PermanentFlagsTest.java @@ -0,0 +1,25 @@ +package com.yahoo.vespa.flags;// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +import com.yahoo.vespa.flags.custom.HostResources; +import com.yahoo.vespa.flags.custom.SharedHost; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.yahoo.vespa.flags.FlagsTest.testGeneric; + +/** + * @author bjorncs + */ +class PermanentFlagsTest { + @Test + public void testSharedHostFlag() { + SharedHost sharedHost = new SharedHost(List.of(new HostResources( + 4.0, 16.0, 50.0, 0.3, + "fast", "local", + 10)), + null); + testGeneric(PermanentFlags.SHARED_HOST, sharedHost); + } + +}
\ No newline at end of file diff --git a/flags/src/test/java/com/yahoo/vespa/flags/custom/SharedHostTest.java b/flags/src/test/java/com/yahoo/vespa/flags/custom/SharedHostTest.java new file mode 100644 index 00000000000..f0a11f244a4 --- /dev/null +++ b/flags/src/test/java/com/yahoo/vespa/flags/custom/SharedHostTest.java @@ -0,0 +1,25 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags.custom; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class SharedHostTest { + @Test + public void serialization() throws IOException { + verifySerialization(new SharedHost(List.of(new HostResources(1.0, 2.0, 3.0, 4.0, "fast", "remote", 5)), 6)); + verifySerialization(new SharedHost(List.of(new HostResources(1.0, 2.0, 3.0, 4.0, "fast", "remote", 5)), null)); + } + + private void verifySerialization(SharedHost sharedHost) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(sharedHost); + SharedHost deserialized = mapper.readValue(json, SharedHost.class); + assertEquals(sharedHost, deserialized); + } +}
\ No newline at end of file |