diff options
Diffstat (limited to 'flags/src/main/java/com')
6 files changed, 192 insertions, 49 deletions
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flag.java b/flags/src/main/java/com/yahoo/vespa/flags/Flag.java index 7ca5066969f..4d49d52fa73 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flag.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flag.java @@ -1,6 +1,20 @@ // Copyright Vespa.ai. 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.Version; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.CloudAccount; +import com.yahoo.config.provision.CloudName; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.NodeResources.Architecture; +import com.yahoo.config.provision.NodeType; +import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.TenantName; +import com.yahoo.config.provision.Zone; +import com.yahoo.config.provision.zone.ZoneApi; +import com.yahoo.config.provision.zone.ZoneId; + import java.util.Optional; /** @@ -10,7 +24,7 @@ import java.util.Optional; * @param <F> The concrete subclass type of the flag * @author hakonhall */ -public interface Flag<T, F> { +public interface Flag<T, F extends Flag<T, F>> { /** The flag ID. */ FlagId id(); @@ -28,6 +42,54 @@ public interface Flag<T, F> { return dimensionValue.map(value -> with(dimension, value)).orElse(self()); } + /** Sets the tenant, application, and instance dimensions. */ + default F with(ApplicationId applicationId) { + return with(Dimension.TENANT_ID, applicationId.tenant().value()) + .with(Dimension.APPLICATION, applicationId.toSerializedFormWithoutInstance()) + .with(Dimension.INSTANCE_ID, applicationId.serializedForm()); + } + + /** architecture MUST NOT be 'any'. */ + default F with(Architecture architecture) { return with(Dimension.ARCHITECTURE, architecture.name()); } + /** + * Sets the cloud-account dimension ONLY IF it is an enclave account.<br/> + * Sets the clave dimension to "enclave" if it is an enclave account, otherwise "noclave". + */ + default F with(CloudAccount cloudAccount, Zone zone) { + return cloudAccount.isEnclave(zone) + ? with(Dimension.CLOUD_ACCOUNT, Optional.of(cloudAccount).map(CloudAccount::value)) + .with(Dimension.CLAVE, "enclave") + : with(Dimension.CLAVE, "noclave"); + } + default F with(CloudName cloud) { return with(Dimension.CLOUD, cloud.value()); } + default F with(ClusterSpec.Id clusterId) { return with(Dimension.CLUSTER_ID, clusterId.value()); } + default F with(ClusterSpec.Type clusterType) { return with(Dimension.CLUSTER_TYPE, clusterType.name()); } + default F with(Environment environment) { return with(Dimension.ENVIRONMENT, environment.value()); } + default F with(NodeType nodeType) { return with(Dimension.NODE_TYPE, nodeType.name()); } + default F with(SystemName systemName) { return with(Dimension.SYSTEM, systemName.value()); } + default F with(TenantName tenantName) { return with(Dimension.TENANT_ID, tenantName.value()); } + default F with(Version vespaVersion) { return with(Dimension.VESPA_VERSION, vespaVersion.toFullString()); } + default F with(ZoneId zoneId) { return with(Dimension.ZONE_ID, zoneId.value()); } + default F with(Zone zone) { return with(Dimension.ZONE_ID, zone.systemLocalValue()); } + default F with(ZoneApi zoneApi) { return with(zoneApi.getVirtualId()); } + + /** Sets the tenant, application, and instance dimensions. */ + default F withApplicationId(Optional<ApplicationId> applicationId) { return applicationId.map(this::with).orElse(self()); } + /** architecture MUST NOT be 'any'. */ + default F withArchitecture(Optional<Architecture> architecture) { return architecture.map(this::with).orElse(self()); } + default F withCloudAccount(Optional<CloudAccount> cloudAccount, Zone zone) { return cloudAccount.map(account -> with(account, zone)).orElse(self()); } + default F withCloudName(Optional<CloudName> cloud) { return cloud.map(this::with).orElse(self()); } + default F withClusterId(Optional<ClusterSpec.Id> clusterId) { return clusterId.map(this::with).orElse(self()); } + default F withClusterType(Optional<ClusterSpec.Type> clusterType) { return clusterType.map(this::with).orElse(self()); } + default F withEnvironment(Optional<Environment> environment) { return environment.map(this::with).orElse(self()); } + default F withNodeType(Optional<NodeType> nodeType) { return nodeType.map(this::with).orElse(self()); } + default F withSystemName(Optional<SystemName> systemName) { return systemName.map(this::with).orElse(self()); } + default F withTenantName(Optional<TenantName> tenantName) { return tenantName.map(this::with).orElse(self()); } + default F withVersion(Optional<Version> vespaVersion) { return vespaVersion.map(this::with).orElse(self()); } + default F withZoneId(Optional<ZoneId> zoneId) { return zoneId.map(this::with).orElse(self()); } + default F withZone(Optional<Zone> zone) { return zone.map(this::with).orElse(self()); } + default F withZoneApi(Optional<ZoneApi> zoneApi) { return zoneApi.map(this::with).orElse(self()); } + /** Returns the value, boxed if the flag wraps a primitive type. */ T boxedValue(); } 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 a3fe010c65b..de52dfe612b 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -84,18 +84,6 @@ public class Flags { "Takes effect at redeployment", INSTANCE_ID); - public static final UnboundBooleanFlag NEW_RESOURCES_FORMULA = defineFeatureFlag( - "new-resources-formula", true, - List.of("hakonhall"), "2024-04-25", "2024-05-25", - "Use an easier to understand formula for calculating the memory and disk resources", - "Takes effect on next deployment of an applications."); - - public static final UnboundBooleanFlag FIX_CONFIG_SERVER_HEAP = defineFeatureFlag( - "fix-config-server-heap", false, - List.of("hakonhall"), "2024-04-23", "2024-05-23", - "Base the calculation of the config server JVM heap size on the amount of memory available to the container.", - "Takes effect on start of config server Podman container"); - public static final UnboundStringFlag RESPONSE_SEQUENCER_TYPE = defineStringFlag( "response-sequencer-type", "ADAPTIVE", List.of("baldersheim"), "2020-12-02", "2024-12-31", @@ -212,7 +200,7 @@ public class Flags { 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", "2024-06-01", + List.of("vekterli"), "2021-02-19", "2025-02-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", @@ -220,7 +208,7 @@ public class Flags { public static final UnboundDoubleFlag MIN_NODE_RATIO_PER_GROUP = defineDoubleFlag( "min-node-ratio-per-group", 0.0, - List.of("geirst", "vekterli"), "2021-07-16", "2024-06-01", + List.of("geirst", "vekterli"), "2021-07-16", "2025-02-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", INSTANCE_ID); @@ -243,13 +231,6 @@ public class Flags { "Takes effect on next tick.", NODE_TYPE); - public static final UnboundStringFlag DIST_HOST = defineStringFlag( - "dist-host", "", - List.of("freva"), "2024-04-15", "2024-05-31", - "Sets dist_host YUM variable, empty means old behavior. Only effective in Public.", - "Provisioning of instance or next host-admin tick", - HOSTNAME, NODE_TYPE, CLOUD_ACCOUNT); - public static final UnboundBooleanFlag ENABLED_HORIZON_DASHBOARD = defineFeatureFlag( "enabled-horizon-dashboard", false, List.of("olaa"), "2021-09-13", "2024-09-01", @@ -318,32 +299,32 @@ public class Flags { public static final UnboundStringFlag CORE_ENCRYPTION_PUBLIC_KEY_ID = defineStringFlag( "core-encryption-public-key-id", "", - List.of("vekterli"), "2022-11-03", "2024-06-01", + List.of("vekterli"), "2022-11-03", "2025-02-01", "Specifies which public key to use for core dump encryption.", "Takes effect on the next tick.", NODE_TYPE, HOSTNAME); public static final UnboundListFlag<String> ZONAL_WEIGHTED_ENDPOINT_RECORDS = defineListFlag( - "zonal-weighted-endpoint-records", List.of(), String.class, List.of("jonmv"), "2023-12-15", "2024-06-01", + "zonal-weighted-endpoint-records", List.of(), String.class, List.of("jonmv"), "2023-12-15", "2024-12-01", "A list of weighted (application) endpoint fqdns for which we should use zonal endpoints as targets, not LBs.", "Takes effect at redeployment from controller"); public static final UnboundListFlag<String> WEIGHTED_ENDPOINT_RECORD_TTL = defineListFlag( - "weighted-endpoint-record-ttl", List.of(), String.class, List.of("jonmv"), "2023-05-16", "2024-06-01", + "weighted-endpoint-record-ttl", List.of(), String.class, List.of("jonmv"), "2023-05-16", "2024-12-01", "A list of endpoints and custom TTLs, on the form \"endpoint-fqdn:TTL-seconds\". " + "Where specified, CNAME records are used instead of the default ALIAS records, which have a default 60s TTL.", "Takes effect at redeployment from controller"); public static final UnboundBooleanFlag SORT_BLUEPRINTS_BY_COST = defineFeatureFlag( "sort-blueprints-by-cost", false, - List.of("baldersheim"), "2023-12-19", "2024-05-31", + List.of("baldersheim"), "2023-12-19", "2024-10-31", "If true blueprints are sorted based on cost estimate, rather that absolute estimated hits", "Takes effect at redeployment", INSTANCE_ID); public static final UnboundBooleanFlag ALWAYS_MARK_PHRASE_EXPENSIVE = defineFeatureFlag( "always-mark-phrase-expensive", false, - List.of("baldersheim"), "2023-11-20", "2024-05-31", + List.of("baldersheim"), "2023-11-20", "2024-10-31", "If true all phrases will be marked expensive, independent of parents", "Takes effect at redeployment", INSTANCE_ID); @@ -383,7 +364,7 @@ public class Flags { public static final UnboundIntFlag CONTENT_LAYER_METADATA_FEATURE_LEVEL = defineIntFlag( "content-layer-metadata-feature-level", 0, - List.of("vekterli"), "2022-09-12", "2024-06-01", + List.of("vekterli"), "2022-09-12", "2024-12-01", "Value semantics: 0) legacy behavior, 1) operation cancellation, 2) operation " + "cancellation and ephemeral content node sequence numbers for bucket replicas", "Takes effect at redeployment", @@ -405,7 +386,7 @@ public class Flags { public static final UnboundStringFlag ENDPOINT_CONFIG = defineStringFlag( "endpoint-config", "legacy", - List.of("mpolden", "tokle"), "2023-10-06", "2024-06-01", + List.of("mpolden", "tokle"), "2023-10-06", "2024-09-01", "Set the endpoint config to use for an application. Must be 'legacy', 'combined' or 'generated'. See EndpointConfig for further details", "Takes effect on next deployment through controller", TENANT_ID, APPLICATION, INSTANCE_ID); @@ -418,13 +399,13 @@ public class Flags { public static UnboundBooleanFlag CALYPSO_ENABLED = defineFeatureFlag( "calypso-enabled", true, - List.of("mortent"), "2024-02-19", "2024-05-01", + List.of("mortent"), "2024-02-19", "2024-08-01", "Whether to enable calypso for host", "Takes effect immediately", HOSTNAME); public static UnboundBooleanFlag ATHENZ_PROVIDER = defineFeatureFlag( "athenz-provider", false, - List.of("mortent"), "2024-02-19", "2024-05-01", + List.of("mortent"), "2024-02-19", "2024-08-01", "Whether to use athenz as node identity provider", "Takes effect on next identity refresh", HOSTNAME); @@ -435,7 +416,7 @@ public class Flags { "Takes effect immediately"); public static final UnboundIntFlag PERSISTENCE_THREAD_MAX_FEED_OP_BATCH_SIZE = defineIntFlag( - "persistence-thread-max-feed-op-batch-size", 1, + "persistence-thread-max-feed-op-batch-size", 64, List.of("vekterli"), "2024-04-12", "2025-01-01", "Maximum number of enqueued feed operations (put/update/remove) bound "+ "towards the same bucket that can be async dispatched as part of the " + @@ -449,6 +430,61 @@ public class Flags { "Whether logserver container should run otel agent", "Takes effect at redeployment", INSTANCE_ID); + public static UnboundBooleanFlag HUBSPOT_SYNC_TENANTS = defineFeatureFlag( + "hubspot-sync-tenants", false, + List.of("bjorncs"), "2024-05-07", "2025-01-01", + "Whether to sync tenants to HubSpot", + "Takes effect immediately"); + + public static final UnboundBooleanFlag REMOVE_ORPHANED_DNS_RECORDS = defineFeatureFlag( + "remove-orphaned-dns-records", false, + List.of("mpolden"), "2024-05-07", "2024-10-01", + "Whether EndpointDnsMaintainer should remove orphaned records instead of logging them", + "Takes effect on next maintenance run"); + + public static final UnboundBooleanFlag SYMMETRIC_PUT_AND_ACTIVATE_REPLICA_SELECTION = defineFeatureFlag( + "symmetric-put-and-activate-replica-selection", false, + List.of("vekterli"), "2024-05-23", "2024-08-01", + "Iff true there will be an 1-1 symmetry between the replicas chosen as feed targets " + + "for Put operations and the replica selection logic for bucket activation. If false, " + + "legacy feed behavior is used.", + "Takes effect immediately", + INSTANCE_ID); + + public static final UnboundBooleanFlag ENFORCE_STRICTLY_INCREASING_CLUSTER_STATE_VERSIONS = defineFeatureFlag( + "enforce-strictly-increasing-cluster-state-versions", false, + List.of("vekterli"), "2024-06-03", "2024-08-01", + "Iff true, received cluster state versions that are lower than the current active " + + "state version on the node will be explicitly rejected.", + "Takes effect immediately", + INSTANCE_ID); + + public static final UnboundBooleanFlag USE_VESPA_ATHENZ_HOST_IDENTITY = defineFeatureFlag( + "use-vespa-athenz-host-identity", false, + List.of("freva"), "2024-06-12", "2024-08-01", + "Whether the host should get identity from Vespa Athenz. Only valid in public systems, noclave, AWS. Vespa version dimension refers to OS version.", + "Takes effect on next provisioning", + INSTANCE_ID, NODE_TYPE, VESPA_VERSION); + + public static final UnboundBooleanFlag LAUNCH_APPLICATION_ATHENZ_SERVICE = defineFeatureFlag( + "launch-application-athenz-service", false, + List.of("jonmv"), "2024-06-11", "2024-09-01", + "Whether to launch an Athenz service unique to the application. Only valid in public systems!", + "Takes effect on next deployment", + INSTANCE_ID); + + public static final UnboundBooleanFlag HUBSPOT_SYNC_CONTACTS = defineFeatureFlag( + "hubspot-sync-contacts", false, + List.of("bjorncs"), "2024-05-27", "2025-01-01", + "Whether to sync contacts to HubSpot", + "Takes effect immediately"); + + public static final UnboundBooleanFlag DELETE_EXPIRED_CONFIG_SESSIONS_NEW_PROCEDURE = defineFeatureFlag( + "delete-expired-config-sessions-new-procedure", false, + List.of("hmusum"), "2024-06-10", "2024-08-10", + "Whether to delete remote and local config sessions at the same time", + "Takes effect immediately"); + /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners, String createdAt, String expiresAt, String description, @@ -512,6 +548,15 @@ public class Flags { 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, + Predicate<List<T>> validator, Dimension... dimensions) { + return define((fid, dval, fvec) -> new UnboundListFlag<>(fid, dval, elementClass, fvec, validator), + flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions); + } + @FunctionalInterface private interface TypedUnboundFlagFactory<T, U extends UnboundFlag<?, ?, ?>> { U create(FlagId id, T defaultValue, FetchVector defaultFetchVector); diff --git a/flags/src/main/java/com/yahoo/vespa/flags/JacksonArraySerializer.java b/flags/src/main/java/com/yahoo/vespa/flags/JacksonArraySerializer.java index 8f7b7d71734..38e3b57d28b 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/JacksonArraySerializer.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/JacksonArraySerializer.java @@ -4,20 +4,27 @@ package com.yahoo.vespa.flags; import com.fasterxml.jackson.databind.JavaType; import java.util.List; +import java.util.function.Predicate; /** * @author freva */ public class JacksonArraySerializer<T> implements FlagSerializer<List<T>> { private final JavaType type; + private final Predicate<List<T>> validator; - public JacksonArraySerializer(Class<T> clazz) { + public JacksonArraySerializer(Class<T> clazz, Predicate<List<T>> validator) { type = JsonNodeRawFlag.constructCollectionType(List.class, clazz); + this.validator = validator; } @Override public List<T> deserialize(RawFlag rawFlag) { - return JsonNodeRawFlag.fromJsonNode(rawFlag.asJsonNode()).toJacksonClass(type); + var list = JsonNodeRawFlag.fromJsonNode(rawFlag.asJsonNode()).<List<T>>toJacksonClass(type); + if (!validator.test(list)) { + throw new IllegalArgumentException("Invalid value: " + list); + } + return list; } @Override diff --git a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java index 3b73d9d6013..7d1c6bcecdb 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java @@ -18,12 +18,12 @@ import static com.yahoo.vespa.flags.Dimension.ARCHITECTURE; import static com.yahoo.vespa.flags.Dimension.CERTIFICATE_PROVIDER; import static com.yahoo.vespa.flags.Dimension.CLAVE; import static com.yahoo.vespa.flags.Dimension.CLOUD_ACCOUNT; -import static com.yahoo.vespa.flags.Dimension.FLAVOR; -import static com.yahoo.vespa.flags.Dimension.INSTANCE_ID; import static com.yahoo.vespa.flags.Dimension.CLUSTER_ID; import static com.yahoo.vespa.flags.Dimension.CLUSTER_TYPE; import static com.yahoo.vespa.flags.Dimension.CONSOLE_USER_EMAIL; +import static com.yahoo.vespa.flags.Dimension.FLAVOR; import static com.yahoo.vespa.flags.Dimension.HOSTNAME; +import static com.yahoo.vespa.flags.Dimension.INSTANCE_ID; import static com.yahoo.vespa.flags.Dimension.NODE_TYPE; import static com.yahoo.vespa.flags.Dimension.TENANT_ID; import static com.yahoo.vespa.flags.Dimension.VESPA_VERSION; @@ -178,7 +178,7 @@ public class PermanentFlags { HOSTNAME, NODE_TYPE, TENANT_ID, INSTANCE_ID, CLUSTER_TYPE, CLUSTER_ID, VESPA_VERSION); public static final UnboundStringFlag ZOOKEEPER_SERVER_VERSION = defineStringFlag( - "zookeeper-server-version", "3.9.1", + "zookeeper-server-version", "3.9.2", "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, INSTANCE_ID, HOSTNAME); @@ -195,12 +195,6 @@ public class PermanentFlags { "Takes effect on next api request" ); - public static final UnboundBooleanFlag JVM_OMIT_STACK_TRACE_IN_FAST_THROW = defineFeatureFlag( - "jvm-omit-stack-trace-in-fast-throw", true, - "Controls JVM option OmitStackTraceInFastThrow (default feature flag value is true, which is the default JVM option value as well)", - "takes effect on JVM restart", - CLUSTER_TYPE, INSTANCE_ID); - public static final UnboundIntFlag MAX_TRIAL_TENANTS = defineIntFlag( "max-trial-tenants", -1, "The maximum nr. of tenants with trial plan, -1 is unlimited", @@ -372,6 +366,13 @@ public class PermanentFlags { "Takes effect immediately" ); + public static final UnboundLongFlag CONFIG_SERVER_SESSION_LIFETIME = defineLongFlag( + "config-server-session-lifetime", 3600, + "Lifetime / expiry time in seconds for config sessions. " + + "This can be lowered if there are incidents/bugs where one needs to delete sessions quickly", + "Takes effect immediately" + ); + public static final UnboundBooleanFlag NOTIFICATION_DISPATCH_FLAG = defineFeatureFlag( "dispatch-notifications", true, "Whether we should send notification for a given tenant", @@ -404,6 +405,12 @@ public class PermanentFlags { "Takes effect immediately", INSTANCE_ID); + public static final UnboundBooleanFlag AUTOSCALING_DETAILED_LOGGING = defineFeatureFlag( + "autoscaling-detailed-logging", false, + "Whether to log autoscaling decision data", + "Takes effect immediately", + INSTANCE_ID); + public static final UnboundIntFlag MAX_HOSTS_PER_HOUR = defineIntFlag( "max-hosts-per-hour", 40, "The number of hosts that can be provisioned per hour in a zone, before throttling is " + @@ -455,6 +462,13 @@ public class PermanentFlags { HOSTNAME ); + public static final UnboundListFlag<String> LOG_REQUEST_CONTENT = defineListFlag( + "log-request-content", List.of(), String.class, + "Include request content in access log for paths starting with any of these prefixes", + "Takes effect on next redeployment", + list -> list.stream().allMatch(s -> s.matches("^[a-zA-Z/.0-9-]+:(0(\\.\\d+)?|1(\\.0+)?):\\d+(B|kB|MB|GB)?$")), + INSTANCE_ID); + private PermanentFlags() {} private static UnboundBooleanFlag defineFeatureFlag( @@ -497,5 +511,10 @@ public class PermanentFlags { return Flags.defineListFlag(flagId, defaultValue, elementClass, 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, Predicate<List<T>> validator, Dimension... dimensions) { + return Flags.defineListFlag(flagId, defaultValue, elementClass, OWNERS, toString(CREATED_AT), toString(EXPIRES_AT), description, modificationEffect, validator, 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/UnboundListFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundListFlag.java index f88a0648021..8411a286d54 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/UnboundListFlag.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundListFlag.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.flags; import java.util.List; +import java.util.function.Predicate; /** * @author freva @@ -12,8 +13,13 @@ public class UnboundListFlag<T> extends UnboundFlagImpl<List<T>, ListFlag<T>, Un } public UnboundListFlag(FlagId id, List<T> defaultValue, Class<T> clazz, FetchVector defaultFetchVector) { + this(id, defaultValue, clazz, defaultFetchVector, __ -> true); + } + + public UnboundListFlag(FlagId id, List<T> defaultValue, Class<T> clazz, FetchVector defaultFetchVector, + Predicate<List<T>> validator) { super(id, defaultValue, defaultFetchVector, - new JacksonArraySerializer<T>(clazz), + new JacksonArraySerializer<T>(clazz, validator), (flagId, defVal, fetchVector) -> new UnboundListFlag<>(flagId, defVal, clazz, fetchVector), ListFlag::new); } 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 66356d979a4..3f229862d7a 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 @@ -7,6 +7,8 @@ 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.config.provision.ClusterSpec; +import com.yahoo.config.provision.SharedHosts; import com.yahoo.vespa.flags.PermanentFlags; import java.util.List; @@ -19,7 +21,7 @@ import java.util.Objects; */ @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) -public class SharedHost { +public class SharedHost implements SharedHosts { private final List<HostResources> resources; @@ -43,14 +45,16 @@ public class SharedHost { /** Whether there are any shared hosts specifically for the given cluster type, or without a cluster type restriction. */ @JsonIgnore - public boolean supportsClusterType(String clusterType) { - return resources.stream().anyMatch(resource -> resource.clusterType().map(clusterType::equalsIgnoreCase).orElse(true)); + @Override + public boolean supportsClusterType(ClusterSpec.Type clusterType) { + return resources.stream().anyMatch(resource -> resource.clusterType().map(type -> clusterType.name().equalsIgnoreCase(type)).orElse(true)); } /** Whether there are any shared hosts specifically for the given cluster type. */ @JsonIgnore - public boolean hasClusterType(String clusterType) { - return resources.stream().anyMatch(resource -> resource.clusterType().map(clusterType::equalsIgnoreCase).orElse(false)); + @Override + public boolean hasClusterType(ClusterSpec.Type clusterType) { + return resources.stream().anyMatch(resource -> resource.clusterType().map(type -> clusterType.name().equalsIgnoreCase(type)).orElse(false)); } @JsonIgnore |