aboutsummaryrefslogtreecommitdiffstats
path: root/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
blob: a3fe010c65b0c1edd06b8bc43146f87fb22760c7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
// 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.Vtag;
import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.flags.custom.RoleList;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Predicate;

import static com.yahoo.vespa.flags.Dimension.APPLICATION;
import static com.yahoo.vespa.flags.Dimension.CLOUD_ACCOUNT;
import static com.yahoo.vespa.flags.Dimension.CONSOLE_USER_EMAIL;
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;

/**
 * Definitions of feature flags.
 *
 * <p>To use feature flags, define the flag in this class as an "unbound" flag, e.g. {@link UnboundBooleanFlag}
 * or {@link UnboundStringFlag}. At the location you want to get the value of the flag, you need the following:</p>
 *
 * <ol>
 *     <li>The unbound flag</li>
 *     <li>A {@link FlagSource}. The flag source is typically available as an injectable component. Binding
 *     an unbound flag to a flag source produces a (bound) flag, e.g. {@link BooleanFlag} and {@link StringFlag}.</li>
 *     <li>If you would like your flag value to be dependent on e.g. the application ID, then 1. you should
 *     declare this in the unbound flag definition in this file (referring to
 *     {@link Dimension#INSTANCE_ID}), and 2. specify the application ID when retrieving the value, e.g.
 *     {@link BooleanFlag#with(Dimension, String)}. See {@link FetchVector} for more info.</li>
 * </ol>
 *
 * <p>Once the code is in place, you can override the flag value. This depends on the flag source, but typically
 * there is a REST API for updating the flags in the config server, which is the root of all flag sources in the zone.</p>
 *
 * @author hakonhall
 */
public class Flags {

    private static volatile TreeMap<FlagId, FlagDefinition> flags = new TreeMap<>();

    public static final UnboundDoubleFlag DEFAULT_TERM_WISE_LIMIT = defineDoubleFlag(
            "default-term-wise-limit", 1.0,
            List.of("baldersheim"), "2020-12-02", "2024-12-31",
            "Default limit for when to apply termwise query evaluation",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundStringFlag QUERY_DISPATCH_POLICY = defineStringFlag(
            "query-dispatch-policy", "adaptive",
            List.of("baldersheim"), "2022-08-20", "2024-12-31",
            "Select query dispatch policy, valid values are adaptive, round-robin, best-of-random-2," +
                    " latency-amortized-over-requests, latency-amortized-over-time",
            "Takes effect at redeployment (requires restart)",
            INSTANCE_ID);

    public static final UnboundStringFlag SUMMARY_DECODE_POLICY = defineStringFlag(
            "summary-decode-policy", "eager",
            List.of("baldersheim"), "2023-03-30", "2024-12-31",
            "Select summary decoding policy, valid values are eager and on-demand/ondemand.",
            "Takes effect at redeployment (requires restart)",
            INSTANCE_ID);

    public static final UnboundStringFlag FEED_SEQUENCER_TYPE = defineStringFlag(
            "feed-sequencer-type", "THROUGHPUT",
            List.of("baldersheim"), "2020-12-02", "2024-12-31",
            "Selects type of sequenced executor used for feeding in proton, valid values are LATENCY, ADAPTIVE, THROUGHPUT",
            "Takes effect at redeployment (requires restart)",
            INSTANCE_ID);

    public static final UnboundIntFlag MAX_UNCOMMITTED_MEMORY = defineIntFlag(
            "max-uncommitted-memory", 130000,
            List.of("geirst, baldersheim"), "2021-10-21", "2024-12-31",
            "Max amount of memory holding updates to an attribute before we do a commit.",
            "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",
            "Selects type of sequenced executor used for mbus responses, valid values are LATENCY, ADAPTIVE, THROUGHPUT",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundIntFlag RESPONSE_NUM_THREADS = defineIntFlag(
            "response-num-threads", 2,
            List.of("baldersheim"), "2020-12-02", "2024-12-31",
            "Number of threads used for mbus responses, default is 2, negative number = numcores/4",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundBooleanFlag USE_ASYNC_MESSAGE_HANDLING_ON_SCHEDULE = defineFeatureFlag(
            "async-message-handling-on-schedule", false,
            List.of("baldersheim"), "2020-12-02", "2024-12-31",
            "Optionally deliver async messages in own thread",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundDoubleFlag FEED_CONCURRENCY = defineDoubleFlag(
            "feed-concurrency", 0.5,
            List.of("baldersheim"), "2020-12-02", "2024-12-31",
            "How much concurrency should be allowed for feed",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundDoubleFlag FEED_NICENESS = defineDoubleFlag(
            "feed-niceness", 0.0,
            List.of("baldersheim"), "2022-06-24", "2024-12-31",
            "How nice feeding shall be",
            "Takes effect at redeployment",
            INSTANCE_ID);


    public static final UnboundIntFlag MBUS_JAVA_NUM_TARGETS = defineIntFlag(
            "mbus-java-num-targets", 2,
            List.of("baldersheim"), "2022-07-05", "2024-12-31",
            "Number of rpc targets per service",
            "Takes effect at redeployment",
            INSTANCE_ID);
    public static final UnboundIntFlag MBUS_CPP_NUM_TARGETS = defineIntFlag(
            "mbus-cpp-num-targets", 2,
            List.of("baldersheim"), "2022-07-05", "2024-12-31",
            "Number of rpc targets per service",
            "Takes effect at redeployment",
            INSTANCE_ID);
    public static final UnboundIntFlag RPC_NUM_TARGETS = defineIntFlag(
            "rpc-num-targets", 2,
            List.of("baldersheim"), "2022-07-05", "2024-12-31",
            "Number of rpc targets per content node",
            "Takes effect at redeployment",
            INSTANCE_ID);
    public static final UnboundIntFlag MBUS_JAVA_EVENTS_BEFORE_WAKEUP = defineIntFlag(
            "mbus-java-events-before-wakeup", 1,
            List.of("baldersheim"), "2022-07-05", "2024-12-31",
            "Number write events before waking up transport thread",
            "Takes effect at redeployment",
            INSTANCE_ID);
    public static final UnboundIntFlag MBUS_CPP_EVENTS_BEFORE_WAKEUP = defineIntFlag(
            "mbus-cpp-events-before-wakeup", 1,
            List.of("baldersheim"), "2022-07-05", "2024-12-31",
            "Number write events before waking up transport thread",
            "Takes effect at redeployment",
            INSTANCE_ID);
    public static final UnboundIntFlag RPC_EVENTS_BEFORE_WAKEUP = defineIntFlag(
            "rpc-events-before-wakeup", 1,
            List.of("baldersheim"), "2022-07-05", "2024-12-31",
            "Number write events before waking up transport thread",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundIntFlag MBUS_NUM_NETWORK_THREADS = defineIntFlag(
            "mbus-num-network-threads", 1,
            List.of("baldersheim"), "2022-07-01", "2024-12-31",
            "Number of threads used for mbus network",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundBooleanFlag SHARED_STRING_REPO_NO_RECLAIM = defineFeatureFlag(
            "shared-string-repo-no-reclaim", false,
            List.of("baldersheim"), "2022-06-14", "2024-12-31",
            "Controls whether we do track usage and reclaim unused enum values in shared string repo",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundBooleanFlag CONTAINER_DUMP_HEAP_ON_SHUTDOWN_TIMEOUT = defineFeatureFlag(
            "container-dump-heap-on-shutdown-timeout", false,
            List.of("baldersheim"), "2021-09-25", "2024-12-31",
            "Will trigger a heap dump during if container shutdown times out",
            "Takes effect at redeployment",
            INSTANCE_ID);
    public static final UnboundBooleanFlag LOAD_CODE_AS_HUGEPAGES = defineFeatureFlag(
            "load-code-as-hugepages", false,
            List.of("baldersheim"), "2022-05-13", "2024-12-31",
            "Will try to map the code segment with huge (2M) pages",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundDoubleFlag CONTAINER_SHUTDOWN_TIMEOUT = defineDoubleFlag(
            "container-shutdown-timeout", 50.0,
            List.of("baldersheim"), "2021-09-25", "2024-12-31",
            "Timeout for shutdown of a jdisc container",
            "Takes effect at redeployment",
            INSTANCE_ID);

    // TODO: Move to a permanent flag
    public static final UnboundListFlag<String> ALLOWED_ATHENZ_PROXY_IDENTITIES = defineListFlag(
            "allowed-athenz-proxy-identities", List.of(), String.class,
            List.of("bjorncs", "tokle"), "2021-02-10", "2024-10-01",
            "Allowed Athenz proxy identities",
            "takes effect at redeployment");

    public static final UnboundIntFlag MAX_ACTIVATION_INHIBITED_OUT_OF_SYNC_GROUPS = defineIntFlag(
            "max-activation-inhibited-out-of-sync-groups", 0,
            List.of("vekterli"), "2021-02-19", "2024-06-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",
            INSTANCE_ID);

    public static final UnboundDoubleFlag MIN_NODE_RATIO_PER_GROUP = defineDoubleFlag(
            "min-node-ratio-per-group", 0.0,
            List.of("geirst", "vekterli"), "2021-07-16", "2024-06-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);

    public static final UnboundStringFlag SYSTEM_MEMORY_HIGH = defineStringFlag(
            "system-memory-high", "",
            List.of("baldersheim"), "2023-02-14", "2024-12-31",
            "The value to write to /sys/fs/cgroup/system.slice/memory.high, if non-empty. " +
            "You may want lower memory.high before lowering memory.max, " +
            "and raise memory.high after raising memory.max.",
            "Takes effect on next tick.",
            NODE_TYPE);

    public static final UnboundStringFlag SYSTEM_MEMORY_MAX = defineStringFlag(
            "system-memory-max", "",
            List.of("baldersheim"), "2023-02-14", "2024-12-31",
            "The value to write to /sys/fs/cgroup/system.slice/memory.max, if non-empty. " +
            "You may want lower memory.high before lowering memory.max, " +
            "and raise memory.high after raising memory.max.",
            "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",
            "Enable Horizon dashboard",
            "Takes effect immediately",
            TENANT_ID, CONSOLE_USER_EMAIL
    );

    public static final UnboundBooleanFlag USE_V8_GEO_POSITIONS = defineFeatureFlag(
            "use-v8-geo-positions", true,
            List.of("arnej"), "2021-11-15", "2024-12-31",
            "Use Vespa 8 types and formats for geographical positions",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundIntFlag MAX_COMPACT_BUFFERS = defineIntFlag(
                "max-compact-buffers", 1,
                List.of("baldersheim", "geirst", "toregge"), "2021-12-15", "2024-12-31",
                "Upper limit of buffers to compact in a data store at the same time for each reason (memory usage, address space usage)",
                "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundBooleanFlag ENABLE_PROXY_PROTOCOL_MIXED_MODE = defineFeatureFlag(
            "enable-proxy-protocol-mixed-mode", true,
            List.of("tokle"), "2022-05-09", "2024-10-01",
            "Enable or disable proxy protocol mixed mode",
            "Takes effect on redeployment",
            INSTANCE_ID);

    public static final UnboundStringFlag LOG_FILE_COMPRESSION_ALGORITHM = defineStringFlag(
            "log-file-compression-algorithm", "",
            List.of("arnej"), "2022-06-14", "2024-12-31",
            "Which algorithm to use for compressing log files. Valid values: empty string (default), gzip, zstd",
            "Takes effect immediately",
            INSTANCE_ID);

    public static final UnboundBooleanFlag SEPARATE_METRIC_CHECK_CONFIG = defineFeatureFlag(
            "separate-metric-check-config", false,
            List.of("olaa"), "2022-07-04", "2024-09-01",
            "Determines whether one metrics config check should be written per Vespa node",
            "Takes effect on next tick",
            HOSTNAME);

    public static final UnboundStringFlag TLS_CAPABILITIES_ENFORCEMENT_MODE = defineStringFlag(
            "tls-capabilities-enforcement-mode", "disable",
            List.of("bjorncs", "vekterli"), "2022-07-21", "2025-01-01",
            "Configure Vespa TLS capability enforcement mode",
            "Takes effect on restart of Docker container",
            INSTANCE_ID,HOSTNAME,NODE_TYPE,TENANT_ID,VESPA_VERSION
    );

    public static final UnboundBooleanFlag ENABLE_OTELCOL = defineFeatureFlag(
            "enable-otel-collector", false,
            List.of("olaa"), "2022-09-23", "2024-09-01",
            "Whether an OpenTelemetry collector should be enabled",
            "Takes effect at next tick",
            INSTANCE_ID);

    public static final UnboundListFlag<String> OTELCOL_LOGS = defineListFlag(
            "otelcol-logs", List.of(), String.class,
            List.of("olaa"), "2024-01-15", "2024-09-01",
            "Determines log files handled by the OpenTelemetry collector",
            "Takes effect at next tick",
            INSTANCE_ID, HOSTNAME
    );

    public static final UnboundStringFlag CORE_ENCRYPTION_PUBLIC_KEY_ID = defineStringFlag(
            "core-encryption-public-key-id", "",
            List.of("vekterli"), "2022-11-03", "2024-06-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",
            "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",
            "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",
            "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",
            "If true all phrases will be marked expensive, independent of parents",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundBooleanFlag WRITE_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB = defineFeatureFlag(
            "write-config-server-session-data-as-blob", false,
            List.of("hmusum"), "2023-07-19", "2024-09-01",
            "Whether to write config server session data in one blob or as individual paths",
            "Takes effect immediately");

    public static final UnboundBooleanFlag READ_CONFIG_SERVER_SESSION_DATA_AS_ONE_BLOB = defineFeatureFlag(
            "read-config-server-session-data-as-blob", false,
            List.of("hmusum"), "2023-07-19", "2024-09-01",
            "Whether to read config server session data from session data blob or from individual paths",
            "Takes effect immediately");

    public static final UnboundBooleanFlag MORE_WIREGUARD = defineFeatureFlag(
            "more-wireguard", false,
            List.of("andreer"), "2023-08-21", "2025-01-01",
            "Use wireguard in INternal enCLAVES",
            "Takes effect on next host-admin run",
            HOSTNAME, CLOUD_ACCOUNT);

    public static final UnboundBooleanFlag IPV6_AWS_TARGET_GROUPS = defineFeatureFlag(
            "ipv6-aws-target-groups", false,
            List.of("andreer"), "2023-08-28", "2025-01-01",
            "Always use IPv6 target groups for load balancers in aws",
            "Takes effect on next load-balancer provisioning",
            HOSTNAME, CLOUD_ACCOUNT);

    public static final UnboundBooleanFlag PROVISION_IPV6_ONLY_AWS = defineFeatureFlag(
            "provision-ipv6-only", false,
            List.of("andreer"), "2023-08-28", "2025-01-01",
            "Provision without private IPv4 addresses in INternal enCLAVES in AWS",
            "Takes effect on next host provisioning / run of host-admin",
            HOSTNAME, CLOUD_ACCOUNT);

    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",
            "Value semantics: 0) legacy behavior, 1) operation cancellation, 2) operation " +
            "cancellation and ephemeral content node sequence numbers for bucket replicas",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundStringFlag UNKNOWN_CONFIG_DEFINITION = defineStringFlag(
            "unknown-config-definition", "warn",
            List.of("hmusum"), "2023-09-25", "2024-09-01",
            "How to handle user config referencing unknown config definitions. Valid values are 'warn' and 'fail'",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static final UnboundIntFlag SEARCH_HANDLER_THREADPOOL = defineIntFlag(
            "search-handler-threadpool", 2,
            List.of("bjorncs", "baldersheim"), "2023-10-01", "2025-01-01",
            "Adjust search handler threadpool size",
            "Takes effect at redeployment",
            APPLICATION);

    public static final UnboundStringFlag ENDPOINT_CONFIG = defineStringFlag(
            "endpoint-config", "legacy",
            List.of("mpolden", "tokle"), "2023-10-06", "2024-06-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);

    public static final UnboundBooleanFlag CLOUD_TRIAL_NOTIFICATIONS = defineFeatureFlag(
            "cloud-trial-notifications", false,
            List.of("bjorncs", "oyving"), "2023-10-13", "2024-12-31",
            "Whether to send cloud trial email notifications",
            "Takes effect immediately");

    public static UnboundBooleanFlag CALYPSO_ENABLED = defineFeatureFlag(
            "calypso-enabled", true,
            List.of("mortent"), "2024-02-19", "2024-05-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",
            "Whether to use athenz as node identity provider",
            "Takes effect on next identity refresh", HOSTNAME);

    public static UnboundJacksonFlag<RoleList> ROLE_DEFINITIONS = defineJacksonFlag(
            "role-definitions", RoleList.empty(), RoleList.class,
            List.of("mortent"), "2024-04-05", "2024-10-01",
            "Role definitions for the system",
            "Takes effect immediately");

    public static final UnboundIntFlag PERSISTENCE_THREAD_MAX_FEED_OP_BATCH_SIZE = defineIntFlag(
            "persistence-thread-max-feed-op-batch-size", 1,
            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 " +
            "same write-locked batch by a persistence thread.",
            "Takes effect at redeployment",
            INSTANCE_ID);

    public static UnboundBooleanFlag LOGSERVER_OTELCOL_AGENT = defineFeatureFlag(
            "logserver-otelcol-agent", false,
            List.of("olaa"), "2024-04-03", "2024-12-31",
            "Whether logserver container should run otel agent",
            "Takes effect at redeployment", INSTANCE_ID);

    /** 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,
                                                       String modificationEffect, Dimension... dimensions) {
        return define(UnboundBooleanFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
    }

    /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
    public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List<String> owners,
                                                     String createdAt, String expiresAt, String description,
                                                     String modificationEffect, Dimension... dimensions) {
        return defineStringFlag(flagId, defaultValue, owners,
                                createdAt, expiresAt, description,
                                modificationEffect, value -> true,
                                dimensions);
    }

    /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
    public static UnboundStringFlag defineStringFlag(String flagId, String defaultValue, List<String> owners,
                                                     String createdAt, String expiresAt, String description,
                                                     String modificationEffect, Predicate<String> validator,
                                                     Dimension... dimensions) {
        return define((i, d, v) -> new UnboundStringFlag(i, d, v, validator),
                      flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
    }

    /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
    public static UnboundIntFlag defineIntFlag(String flagId, int defaultValue, List<String> owners,
                                               String createdAt, String expiresAt, String description,
                                               String modificationEffect, Dimension... dimensions) {
        return define(UnboundIntFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
    }

    /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
    public static UnboundLongFlag defineLongFlag(String flagId, long defaultValue, List<String> owners,
                                                 String createdAt, String expiresAt, String description,
                                                 String modificationEffect, Dimension... dimensions) {
        return define(UnboundLongFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
    }

    /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
    public static UnboundDoubleFlag defineDoubleFlag(String flagId, double defaultValue, List<String> owners,
                                                     String createdAt, String expiresAt, String description,
                                                     String modificationEffect, Dimension... dimensions) {
        return define(UnboundDoubleFlag::new, flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
    }

    /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
    public static <T> UnboundJacksonFlag<T> defineJacksonFlag(String flagId, T defaultValue, Class<T> jacksonClass, List<String> owners,
                                                              String createdAt, String expiresAt, String description,
                                                              String modificationEffect, Dimension... dimensions) {
        return define((id2, defaultValue2, vector2) -> new UnboundJacksonFlag<>(id2, defaultValue2, vector2, jacksonClass),
                flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
    }

    /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
    public static <T> UnboundListFlag<T> defineListFlag(String flagId, List<T> defaultValue, Class<T> elementClass,
                                                        List<String> owners, String createdAt, String expiresAt,
                                                        String description, String modificationEffect, Dimension... dimensions) {
        return define((fid, dval, fvec) -> new UnboundListFlag<>(fid, dval, elementClass, fvec),
                flagId, defaultValue, owners, createdAt, expiresAt, description, modificationEffect, dimensions);
    }

    @FunctionalInterface
    private interface TypedUnboundFlagFactory<T, U extends UnboundFlag<?, ?, ?>> {
        U create(FlagId id, T defaultValue, FetchVector defaultFetchVector);
    }

    /**
     * Defines a Flag.
     *
     * @param factory            Factory for creating unbound flag of type U
     * @param flagId             The globally unique FlagId.
     * @param defaultValue       The default value if none is present after resolution.
     * @param description        Description of how the flag is used.
     * @param modificationEffect What is required for the flag to take effect? A restart of process? immediately? etc.
     * @param dimensions         What dimensions will be set in the {@link FetchVector} when fetching
     *                           the flag value in
     *                           {@link FlagSource#fetch(FlagId, FetchVector) FlagSource::fetch}.
     *                           For instance, if APPLICATION is one of the dimensions here, you should make sure
     *                           APPLICATION is set to the ApplicationId in the fetch vector when fetching the RawFlag
     *                           from the FlagSource.
     *                           SYSTEM, CLOUD, ENVIRONMENT, and ZONE_ID are special:  These dimensions are resolved just
     *                           before the flag data is published to a zone.  This means there is never any need to set
     *                           these dimensions when resolving a flag, and setting these dimensions just before resolving
     *                           the flag will have no effect.
     *                           There is one exception.  If any of these dimensions are declared when defining a flag,
     *                           then those dimensions are NOT resolved when published to the controllers.  This allows
     *                           the controller to resolve the flag to different values based on which cloud or zone
     *                           it is operating on.  Flags should NOT declare these dimensions unless they intend to
     *                           use them in the controller in this way.
     * @param <T>                The boxed type of the flag value, e.g. Boolean for flags guarding features.
     * @param <U>                The type of the unbound flag, e.g. UnboundBooleanFlag.
     * @return An unbound flag with {@link Dimension#HOSTNAME HOSTNAME} and
     *         {@link Dimension#VESPA_VERSION VESPA_VERSION} already set. The ZONE environment
     *         is typically implicit.
     */
    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,
                                                                Dimension[] dimensions) {
        FlagId id = new FlagId(flagId);
        FetchVector vector = new FetchVector()
                .with(HOSTNAME, Defaults.getDefaults().vespaHostname())
                // Warning: In unit tests and outside official Vespa releases, the currentVersion is e.g. 7.0.0
                // (determined by the current major version). Consider not setting VESPA_VERSION if minor = micro = 0.
                .with(VESPA_VERSION, Vtag.currentVersion.toFullString());
        U unboundFlag = factory.create(id, defaultValue, vector);
        FlagDefinition definition = new FlagDefinition(
                unboundFlag, owners, parseDate(createdAt), parseDate(expiresAt), description, modificationEffect, dimensions);
        flags.put(id, definition);
        return unboundFlag;
    }

    private static Instant parseDate(String rawDate) {
        return DateTimeFormatter.ISO_DATE.parse(rawDate, LocalDate::from).atStartOfDay().toInstant(ZoneOffset.UTC);
    }

    public static List<FlagDefinition> getAllFlags() {
        return List.copyOf(flags.values());
    }

    public static Optional<FlagDefinition> getFlag(FlagId flagId) {
        return Optional.ofNullable(flags.get(flagId));
    }

    /**
     * Allows the statically defined flags to be controlled in a test.
     *
     * <p>Returns a Replacer instance to be used with e.g. a try-with-resources block. Within the block,
     * the flags starts out as cleared. Flags can be defined, etc. When leaving the block, the flags from
     * before the block is reinserted.
     *
     * <p>NOT thread-safe. Tests using this cannot run in parallel.
     */
    public static Replacer clearFlagsForTesting(FlagId... flagsToKeep) {
        return new Replacer(flagsToKeep);
    }

    public static class Replacer implements AutoCloseable {
        private static volatile boolean flagsCleared = false;

        private final TreeMap<FlagId, FlagDefinition> savedFlags;

        private Replacer(FlagId... flagsToKeep) {
            verifyAndSetFlagsCleared(true);
            this.savedFlags = Flags.flags;
            Flags.flags = new TreeMap<>();
            List.of(flagsToKeep).forEach(id -> Flags.flags.put(id, savedFlags.get(id)));
        }

        @Override
        public void close() {
            verifyAndSetFlagsCleared(false);
            Flags.flags = savedFlags;
        }

        /**
         * Used to implement a simple verification that Replacer is not used by multiple threads.
         * For instance two different tests running in parallel cannot both use Replacer.
         */
        private static void verifyAndSetFlagsCleared(boolean newValue) {
            if (flagsCleared == newValue) {
                throw new IllegalStateException("clearFlagsForTesting called while already cleared - running tests in parallell!?");
            }
            flagsCleared = newValue;
        }
    }
}