aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@oath.com>2019-01-03 19:54:11 +0100
committerHåkon Hallingstad <hakon@oath.com>2019-01-03 19:54:11 +0100
commitcf70bfb260fa8731617a327b56be433128aa0450 (patch)
treeddf55127282b97088ce301c51b46737c5ca7692e
parent5d9957af203d16c448ca34a9d167e3587b3c7820 (diff)
Typed flag classes
This reintroduces the non-generic flag classes: - a value() returns the primitive type for flags wrapping a primitive type - easier to use in testing - Serializer is moved to internals of typed class Defines the flag backed by boolean BooleanFlag instead of FeatureFlag since not all boolean flags are necessarily guarding a feature.
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/Application.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java6
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java29
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java50
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/BooleanFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flag.java47
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java15
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/FlagImpl.java47
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java123
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java1
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java18
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundBooleanFlag.java27
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java45
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundFlagImpl.java58
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundIntFlag.java23
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundJacksonFlag.java21
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundLongFlag.java23
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java23
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java17
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java30
-rw-r--r--flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java19
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/health/HealthMonitorManager.java8
-rw-r--r--service-monitor/src/test/java/com/yahoo/vespa/service/health/HealthMonitorManagerTest.java5
26 files changed, 513 insertions, 198 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/Application.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/Application.java
index 2d5d5296762..2463cca190a 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/Application.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/Application.java
@@ -23,9 +23,9 @@ import com.yahoo.vespa.config.server.rpc.ConfigResponseFactory;
import com.yahoo.vespa.config.server.rpc.UncompressedConfigResponseFactory;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
import com.yahoo.vespa.config.util.ConfigUtils;
+import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FileFlagSource;
-import com.yahoo.vespa.flags.Flag;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
@@ -49,7 +49,7 @@ public class Application implements ModelResult {
private final ServerCache cache;
private final MetricUpdater metricUpdater;
private final ApplicationId app;
- private final Flag<Boolean> useConfigServerCache;
+ private final BooleanFlag useConfigServerCache;
public Application(Model model, ServerCache cache, long appGeneration, boolean internalRedeploy,
Version vespaVersion, MetricUpdater metricUpdater, ApplicationId app) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java
index 9f0e39b7d43..6d5d0df8f1a 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java
@@ -20,12 +20,12 @@ import java.util.List;
*/
public class DefinedFlags extends HttpResponse {
private static ObjectMapper mapper = new ObjectMapper();
- private static final Comparator<FlagDefinition<?>> sortByFlagId =
+ private static final Comparator<FlagDefinition> sortByFlagId =
(left, right) -> left.getUnboundFlag().id().compareTo(right.getUnboundFlag().id());
- private final List<FlagDefinition<?>> flags;
+ private final List<FlagDefinition> flags;
- public DefinedFlags(List<FlagDefinition<?>> flags) {
+ public DefinedFlags(List<FlagDefinition> flags) {
super(Response.Status.OK);
this.flags = flags;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
index e64c356aa96..e5efc74fb75 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
@@ -6,13 +6,17 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.restapi.Path;
+import com.yahoo.vespa.config.server.http.HttpErrorResponse;
import com.yahoo.vespa.config.server.http.HttpHandler;
import com.yahoo.vespa.config.server.http.NotFoundException;
import com.yahoo.vespa.configserver.flags.FlagsDb;
+import com.yahoo.vespa.flags.FlagDefinition;
import com.yahoo.vespa.flags.FlagId;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.json.FlagData;
+import com.yahoo.yolean.Exceptions;
+import java.io.UncheckedIOException;
import java.net.URI;
import java.util.Objects;
@@ -74,7 +78,19 @@ public class FlagsHandler extends HttpHandler {
}
private HttpResponse putFlagData(HttpRequest request, FlagId flagId) {
- flagsDb.setValue(flagId, FlagData.deserialize(request.getData()));
+ FlagData data;
+ try {
+ data = FlagData.deserialize(request.getData());
+ } catch (UncheckedIOException e) {
+ return HttpErrorResponse.badRequest("Failed to deserialize request data: " + Exceptions.toMessageString(e));
+ }
+
+ if (!isForce(request)) {
+ FlagDefinition definition = Flags.getFlag(flagId).get(); // FlagId has been validated in findFlagId()
+ data.validate(definition.getUnboundFlag().serializer());
+ }
+
+ flagsDb.setValue(flagId, data);
return new OKResponse();
}
@@ -86,12 +102,15 @@ public class FlagsHandler extends HttpHandler {
private FlagId findFlagId(HttpRequest request, Path path) {
FlagId flagId = new FlagId(path.get("flagId"));
- if (!Objects.equals(request.getProperty("force"), "true")) {
- if (Flags.getAllFlags().stream().noneMatch(definition -> flagId.equals(definition.getUnboundFlag().id()))) {
- throw new NotFoundException("There is no flag '" + flagId + "' (use ?force=true to override)");
- }
+ if (!isForce(request)) {
+ Flags.getFlag(flagId).orElseThrow(() ->
+ new NotFoundException("There is no flag '" + flagId + "' (use ?force=true to override)"));
}
return flagId;
}
+
+ private boolean isForce(HttpRequest request) {
+ return Objects.equals(request.getProperty("force"), "true");
+ }
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java
index 8781ec413d2..71caed77dbe 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java
@@ -11,7 +11,7 @@ import com.yahoo.vespa.curator.mock.MockCurator;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagId;
import com.yahoo.vespa.flags.Flags;
-import com.yahoo.vespa.flags.UnboundFlag;
+import com.yahoo.vespa.flags.UnboundBooleanFlag;
import org.junit.Test;
import java.io.ByteArrayInputStream;
@@ -21,6 +21,7 @@ import java.util.stream.Stream;
import static com.yahoo.yolean.Exceptions.uncheck;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
@@ -28,11 +29,11 @@ import static org.junit.Assert.assertEquals;
* @author hakonhall
*/
public class FlagsHandlerTest {
- private static final UnboundFlag<Boolean> FLAG1 =
- Flags.defineBoolean("id1", false, "desc1", "mod1");
- private static final UnboundFlag<Boolean> FLAG2 =
- Flags.defineBoolean("id2", true, "desc2", "mod2",
- FetchVector.Dimension.HOSTNAME, FetchVector.Dimension.APPLICATION_ID);
+ private static final UnboundBooleanFlag FLAG1 = Flags.defineFeatureFlag(
+ "id1", false, "desc1", "mod1");
+ private static final UnboundBooleanFlag FLAG2 = Flags.defineFeatureFlag(
+ "id2", true, "desc2", "mod2",
+ FetchVector.Dimension.HOSTNAME, FetchVector.Dimension.APPLICATION_ID);
private static final String FLAGS_V1_URL = "https://foo.com:4443/flags/v1";
@@ -54,7 +55,7 @@ public class FlagsHandlerTest {
public void testDefined() {
try (Flags.Replacer replacer = Flags.clearFlagsForTesting()) {
fixUnusedWarning(replacer);
- Flags.defineBoolean("id", false, "desc", "mod", FetchVector.Dimension.HOSTNAME);
+ Flags.defineFeatureFlag("id", false, "desc", "mod", FetchVector.Dimension.HOSTNAME);
verifySuccessfulRequest(Method.GET, "/defined", "",
"{\"id\":{\"description\":\"desc\",\"modification-effect\":\"mod\",\"dimensions\":[\"hostname\"]}}");
}
@@ -153,32 +154,33 @@ public class FlagsHandlerTest {
@Test
public void testForcing() {
- FlagId undefinedFlagId = new FlagId("undef");
- HttpResponse response = handle(Method.PUT, "/data/" + undefinedFlagId, "");
+ assertThat(handle(Method.PUT, "/data/" + new FlagId("undef"), "", 404),
+ containsString("There is no flag 'undef'"));
- assertEquals(404, response.getStatus());
- assertEquals("application/json", response.getContentType());
+ assertThat(handle(Method.PUT, "/data/" + new FlagId("undef") + "?force=true", "", 400),
+ containsString("No content to map due to end-of-input"));
- }
+ assertThat(handle(Method.PUT, "/data/" + FLAG1.id(), "{}", 400),
+ containsString("Flag ID missing"));
- private void verifySuccessfulRequest(Method method, String pathSuffix, String requestBody, String expectedResponseBody) {
- HttpResponse response = handle(method, pathSuffix, requestBody);
+ assertThat(handle(Method.PUT, "/data/" + FLAG1.id(), "{\"id\": \"id1\",\"rules\": [{\"value\":\"string\"}]}", 400),
+ containsString("Wrong type of JsonNode: STRING"));
- assertEquals(200, response.getStatus());
- assertEquals("application/json", response.getContentType());
- String actualResponse = uncheck(() -> SessionHandlerTest.getRenderedString(response));
+ assertThat(handle(Method.PUT, "/data/" + FLAG1.id() + "?force=true", "{\"id\": \"id1\",\"rules\": [{\"value\":\"string\"}]}", 200),
+ is(""));
+ }
- assertThat(actualResponse, is(expectedResponseBody));
+ private void verifySuccessfulRequest(Method method, String pathSuffix, String requestBody, String expectedResponseBody) {
+ assertThat(handle(method, pathSuffix, requestBody, 200), is(expectedResponseBody));
}
- private HttpResponse handle(Method method, String pathSuffix, String requestBody) {
+ private String handle(Method method, String pathSuffix, String requestBody, int expectedStatus) {
String uri = FLAGS_V1_URL + pathSuffix;
HttpRequest request = HttpRequest.createTestRequest(uri, method, makeInputStream(requestBody));
- return handler.handle(request);
- }
-
- private String makeUrl(String component) {
- return FLAGS_V1_URL + "/" + component;
+ HttpResponse response = handler.handle(request);
+ assertEquals(expectedStatus, response.getStatus());
+ assertEquals("application/json", response.getContentType());
+ return uncheck(() -> SessionHandlerTest.getRenderedString(response));
}
private InputStream makeInputStream(String content) {
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/BooleanFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/BooleanFlag.java
new file mode 100644
index 00000000000..1245aca65b8
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/BooleanFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class BooleanFlag extends FlagImpl<Boolean, BooleanFlag> {
+ public BooleanFlag(FlagId id, boolean defaultValue, FetchVector vector, FlagSerializer<Boolean> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, BooleanFlag::new);
+ }
+
+ public boolean value() {
+ return boxedValue();
+ }
+}
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 18c1db0d756..7bf491a39c7 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flag.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flag.java
@@ -1,44 +1,23 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
-import javax.annotation.concurrent.Immutable;
-
/**
+ * Interface for flag.
+ *
+ * @param <T> The type of the flag value (boxed for primitives)
+ * @param <F> The concrete subclass type of the flag
* @author hakonhall
*/
-@Immutable
-public class Flag<T> {
- private final FlagId id;
- private final T defaultValue;
- private final FlagSource source;
- private final Deserializer<T> deserializer;
- private final FetchVector fetchVector;
-
- public Flag(String flagId, T defaultValue, FlagSource source, Deserializer<T> deserializer) {
- this(new FlagId(flagId), defaultValue, source, deserializer);
- }
-
- public Flag(FlagId id, T defaultValue, FlagSource source, Deserializer<T> deserializer) {
- this(id, defaultValue, deserializer, new FetchVector(), source);
- }
-
- public Flag(FlagId id, T defaultValue, Deserializer<T> deserializer, FetchVector fetchVector, FlagSource source) {
- this.id = id;
- this.defaultValue = defaultValue;
- this.source = source;
- this.deserializer = deserializer;
- this.fetchVector = fetchVector;
- }
+public interface Flag<T, F> {
+ /** The flag ID. */
+ FlagId id();
- public FlagId id() {
- return id;
- }
+ /** Returns the flag serializer. */
+ FlagSerializer<T> serializer();
- public Flag<T> with(FetchVector.Dimension dimension, String value) {
- return new Flag<>(id, defaultValue, deserializer, fetchVector.with(dimension, value), source);
- }
+ /** Returns an immutable clone of the current object, except with the dimension set accordingly. */
+ F with(FetchVector.Dimension dimension, String dimensionValue);
- public T value() {
- return source.fetch(id, fetchVector).map(deserializer::deserialize).orElse(defaultValue);
- }
+ /** Returns the value, boxed if the flag wraps a primitive type. */
+ T boxedValue();
}
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 a3f490e2f96..0dce61cf0bb 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagDefinition.java
@@ -1,7 +1,8 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
import javax.annotation.concurrent.Immutable;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -9,21 +10,21 @@ import java.util.List;
* @author hakonhall
*/
@Immutable
-public class FlagDefinition<T> {
- private final UnboundFlag<T> unboundFlag;
+public class FlagDefinition {
+ private final UnboundFlag<?, ?, ?> unboundFlag;
private final String description;
private final String modificationEffect;
private final List<FetchVector.Dimension> dimensions;
- public FlagDefinition(UnboundFlag<T> unboundFlag, String description, String modificationEffect,
- List<FetchVector.Dimension> dimensions) {
+ public FlagDefinition(UnboundFlag<?, ?, ?> unboundFlag, String description, String modificationEffect,
+ FetchVector.Dimension... dimensions) {
this.unboundFlag = unboundFlag;
this.description = description;
this.modificationEffect = modificationEffect;
- this.dimensions = Collections.unmodifiableList(dimensions);
+ this.dimensions = Collections.unmodifiableList(Arrays.asList(dimensions));
}
- public UnboundFlag<T> getUnboundFlag() {
+ public UnboundFlag<?, ?, ?> getUnboundFlag() {
return unboundFlag;
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FlagImpl.java b/flags/src/main/java/com/yahoo/vespa/flags/FlagImpl.java
new file mode 100644
index 00000000000..79da03e4982
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagImpl.java
@@ -0,0 +1,47 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+/**
+ * @author hakonhall
+ */
+public abstract class FlagImpl<T, F extends FlagImpl<T, F>> implements Flag<T, F> {
+ private final FlagId id;
+ private final T defaultValue;
+ private final FlagSerializer<T> serializer;
+ private final FetchVector fetchVector;
+ private final UnboundFlagImpl.FlagFactory<T, F> factory;
+ private final FlagSource source;
+
+ protected FlagImpl(FlagId id,
+ T defaultValue,
+ FetchVector fetchVector, FlagSerializer<T> serializer,
+ FlagSource source,
+ UnboundFlagImpl.FlagFactory<T, F> factory) {
+ this.id = id;
+ this.defaultValue = defaultValue;
+ this.serializer = serializer;
+ this.fetchVector = fetchVector;
+ this.factory = factory;
+ this.source = source;
+ }
+
+ @Override
+ public FlagId id() {
+ return id;
+ }
+
+ @Override
+ public F with(FetchVector.Dimension dimension, String dimensionValue) {
+ return factory.create(id, defaultValue, fetchVector.with(dimension, dimensionValue), serializer, source);
+ }
+
+ @Override
+ public T boxedValue() {
+ return source.fetch(id, fetchVector).map(serializer::deserialize).orElse(defaultValue);
+ }
+
+ @Override
+ public FlagSerializer<T> serializer() {
+ return serializer;
+ }
+}
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 50cb5ffdf7c..50f65e5ce13 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -1,53 +1,33 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.BooleanNode;
-import com.fasterxml.jackson.databind.node.IntNode;
-import com.fasterxml.jackson.databind.node.LongNode;
-import com.fasterxml.jackson.databind.node.TextNode;
import com.yahoo.vespa.defaults.Defaults;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
- * Definitions of most/all flags.
- *
- * <p>The flags are centrally defined in this module to allow 1. all code to access flags that may be used in
- * quite different modules and processes, and 2. in particular allow the config server to access all flags
- * so operators have a nicer UI for setting, modifying, or removing flag values.
- *
- * <p>This class should have been an enum, but unfortunately enums cannot be generic, which will eventually be
- * fixed with <a href="https://openjdk.java.net/jeps/301">JEP 301: Enhanced Enums</a>.
- *
* @author hakonhall
*/
public class Flags {
- public static final FlagSerializer<Boolean> BOOLEAN_SERIALIZER = new SimpleFlagSerializer<>(BooleanNode::valueOf, JsonNode::isBoolean, JsonNode::asBoolean);
- public static final FlagSerializer<String> STRING_SERIALIZER = new SimpleFlagSerializer<>(TextNode::new, JsonNode::isTextual, JsonNode::asText);
- public static final FlagSerializer<Integer> INT_SERIALIZER = new SimpleFlagSerializer<>(IntNode::new, JsonNode::isIntegralNumber, JsonNode::asInt);
- public static final FlagSerializer<Long> LONG_SERIALIZER = new SimpleFlagSerializer<>(LongNode::new, JsonNode::isIntegralNumber, JsonNode::asLong);
+ private static volatile ConcurrentHashMap<FlagId, FlagDefinition> flags = new ConcurrentHashMap<>();
- private static volatile ConcurrentHashMap<FlagId, FlagDefinition<?>> flags = new ConcurrentHashMap<>();
-
- public static final UnboundFlag<Boolean> HEALTHMONITOR_MONITOR_INFRA = defineBoolean(
+ public static final UnboundBooleanFlag HEALTHMONITOR_MONITOR_INFRA = defineFeatureFlag(
"healthmonitor-monitorinfra", true,
- "Whether the health monitor in service monitor monitors the health of infrastructure applications.",
- "Affects all applications activated after the value is changed.",
+ "Whether the health monitor in service monitor monitors the health of infrastructure applications.",
+ "Affects all applications activated after the value is changed.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> DUPERMODEL_CONTAINS_INFRA = defineBoolean(
+ public static final UnboundBooleanFlag DUPERMODEL_CONTAINS_INFRA = defineFeatureFlag(
"dupermodel-contains-infra", true,
"Whether the DuperModel in config server/controller includes active infrastructure applications " +
"(except from controller/config apps).",
"Requires restart of config server/controller to take effect.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> DUPERMODEL_USE_CONFIGSERVERCONFIG = defineBoolean(
+ public static final UnboundBooleanFlag DUPERMODEL_USE_CONFIGSERVERCONFIG = defineFeatureFlag(
"dupermodel-use-configserverconfig", true,
"For historical reasons, the ApplicationInfo in the DuperModel for controllers and config servers " +
"is based on the ConfigserverConfig (this flag is true). We want to transition to use the " +
@@ -55,75 +35,86 @@ public class Flags {
"Requires restart of config server/controller to take effect.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> USE_CONFIG_SERVER_CACHE = defineBoolean(
+ public static final UnboundBooleanFlag USE_CONFIG_SERVER_CACHE = defineFeatureFlag(
"use-config-server-cache", true,
"Whether config server will use cache to answer config requests.",
"Takes effect immediately when changed.",
FetchVector.Dimension.HOSTNAME, FetchVector.Dimension.APPLICATION_ID);
- public static final UnboundFlag<Boolean> CONFIG_SERVER_BOOTSTRAP_IN_SEPARATE_THREAD = defineBoolean(
+ public static final UnboundBooleanFlag CONFIG_SERVER_BOOTSTRAP_IN_SEPARATE_THREAD = defineFeatureFlag(
"config-server-bootstrap-in-separate-thread", true,
"Whether to run config server/controller bootstrap in a separate thread.",
"Takes effect only at bootstrap of config server/controller",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> PROXYHOST_USES_REAL_ORCHESTRATOR = defineBoolean(
+ public static final UnboundBooleanFlag PROXYHOST_USES_REAL_ORCHESTRATOR = defineFeatureFlag(
"proxyhost-uses-real-orchestrator", true,
"Whether proxy hosts uses the real Orchestrator when suspending/resuming, or a synthetic.",
"Takes effect immediately when changed.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> CONFIGHOST_USES_REAL_ORCHESTRATOR = defineBoolean(
+ public static final UnboundBooleanFlag CONFIGHOST_USES_REAL_ORCHESTRATOR = defineFeatureFlag(
"confighost-uses-real-orchestrator", false,
"Whether the config server hosts uses the real Orchestrator when suspending/resuming, or a synthetic.",
"Takes effect immediately when changed.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> ENABLE_DOCKER_1_13 = defineBoolean(
+ public static final UnboundBooleanFlag ENABLE_DOCKER_1_13 = defineFeatureFlag(
"enable-docker-1.13", true,
"Whether to upgrade to Docker version 1.13.",
"Takes effect on next host admin tick.",
FetchVector.Dimension.HOSTNAME);
- public static final UnboundFlag<Boolean> ENABLE_CROWDSTRIKE = defineBoolean(
+ public static final UnboundBooleanFlag ENABLE_CROWDSTRIKE = defineFeatureFlag(
"enable-crowdstrike", true,
"Whether to enable CrowdStrike.", "Takes effect on next host admin tick");
- public static final UnboundFlag<Boolean> ENABLE_NESSUS = defineBoolean(
+ public static final UnboundBooleanFlag ENABLE_NESSUS = defineFeatureFlag(
"enable-nessus", true,
"Whether to enable Nessus.", "Takes effect on next host admin tick");
- public static UnboundFlag<Boolean> defineBoolean(String flagId, boolean defaultValue, String description,
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description,
+ String modificationEffect, FetchVector.Dimension... dimensions) {
+ return define(UnboundBooleanFlag::new, flagId, defaultValue, 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,
String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, BOOLEAN_SERIALIZER, description, modificationEffect, dimensions);
+ return define(UnboundStringFlag::new, flagId, defaultValue, description, modificationEffect, dimensions);
}
- public static UnboundFlag<String> defineString(String flagId, String defaultValue, String description,
- String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, STRING_SERIALIZER, 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,
+ String modificationEffect, FetchVector.Dimension... dimensions) {
+ return define(UnboundIntFlag::new, flagId, defaultValue, description, modificationEffect, dimensions);
}
- public static UnboundFlag<Integer> defineInt(String flagId, Integer defaultValue, String description,
+ /** WARNING: public for testing: All flags should be defined in {@link Flags}. */
+ public static UnboundLongFlag defineLongFlag(String flagId, long defaultValue, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, INT_SERIALIZER, description, modificationEffect, dimensions);
+ return define(UnboundLongFlag::new, flagId, defaultValue, description, modificationEffect, dimensions);
}
- public static UnboundFlag<Long> defineLong(String flagId, Long defaultValue, String description,
- String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, LONG_SERIALIZER, 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,
+ String modificationEffect, FetchVector.Dimension... dimensions) {
+ return define((id2, defaultValue2, vector2) -> new UnboundJacksonFlag<>(id2, defaultValue2, vector2, jacksonClass),
+ flagId, defaultValue, description, modificationEffect, dimensions);
}
- public static <T> UnboundFlag<T> defineJackson(String flagId, Class<T> jacksonClass, T defaultValue, String description,
- String modificationEffect, FetchVector.Dimension... dimensions) {
- return define(flagId, defaultValue, new JacksonSerializer<>(jacksonClass), description, modificationEffect, dimensions);
+ @FunctionalInterface
+ private interface TypedUnboundFlagFactory<T, U extends UnboundFlag<?, ?, ?>> {
+ U create(FlagId id, T defaultVale, 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 deserializer Deserialize JSON to value type.
* @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
@@ -132,34 +123,46 @@ public class Flags {
* For instance, if APPLICATION is one of the dimensions here, you should make sure
* APPLICATION is set to the ApplicationId in the fetch vector when fetching the RawFlag
* from the FlagSource.
- * @param <T> The type of the flag value, typically Boolean for flags guarding features.
+ * @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 FetchVector.Dimension#HOSTNAME HOSTNAME} environment. The ZONE environment
* is typically implicit.
*/
- private static <T> UnboundFlag<T> define(String flagId, T defaultValue, Deserializer<T> deserializer,
- String description, String modificationEffect,
- FetchVector.Dimension... dimensions) {
- UnboundFlag<T> flag = new UnboundFlag<>(flagId, defaultValue, deserializer)
- .with(FetchVector.Dimension.HOSTNAME, Defaults.getDefaults().vespaHostname());
- FlagDefinition<T> definition = new FlagDefinition<>(flag, description, modificationEffect, Arrays.asList(dimensions));
- flags.put(flag.id(), definition);
- return flag;
+ private static <T, U extends UnboundFlag<?, ?, ?>> U define(TypedUnboundFlagFactory<T, U> factory,
+ String flagId,
+ T defaultValue,
+ String description,
+ String modificationEffect,
+ FetchVector.Dimension[] dimensions) {
+ FlagId id = new FlagId(flagId);
+ FetchVector vector = new FetchVector().with(FetchVector.Dimension.HOSTNAME, Defaults.getDefaults().vespaHostname());
+ U unboundFlag = factory.create(id, defaultValue, vector);
+ FlagDefinition definition = new FlagDefinition(unboundFlag, description, modificationEffect, dimensions);
+ flags.put(id, definition);
+ return unboundFlag;
}
- public static List<FlagDefinition<?>> getAllFlags() {
+ public static List<FlagDefinition> getAllFlags() {
return new ArrayList<>(flags.values());
}
- public static Optional<FlagDefinition<?>> getFlag(FlagId flagId) {
+ 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.
+ * */
public static Replacer clearFlagsForTesting() {
return new Replacer();
}
public static class Replacer implements AutoCloseable {
- private final ConcurrentHashMap<FlagId, FlagDefinition<?>> savedFlags;
+ private final ConcurrentHashMap<FlagId, FlagDefinition> savedFlags;
private Replacer() {
this.savedFlags = Flags.flags;
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java
new file mode 100644
index 00000000000..4a59c34e8d0
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class IntFlag extends FlagImpl<Integer, IntFlag> {
+ public IntFlag(FlagId id, int defaultValue, FetchVector vector, FlagSerializer<Integer> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, IntFlag::new);
+ }
+
+ public int value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java
new file mode 100644
index 00000000000..e4afbd231a1
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class JacksonFlag<T> extends FlagImpl<T, JacksonFlag<T>> {
+ public JacksonFlag(FlagId id, T defaultValue, FetchVector vector, FlagSerializer<T> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, JacksonFlag::new);
+ }
+
+ public T value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java
new file mode 100644
index 00000000000..8a1707c9e6a
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class LongFlag extends FlagImpl<Long, LongFlag> {
+ public LongFlag(FlagId id, Long defaultValue, FetchVector vector, FlagSerializer<Long> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, LongFlag::new);
+ }
+
+ public long value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java b/flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java
index 6ca74715999..1c6e113f489 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/OrderedFlagSource.java
@@ -15,7 +15,6 @@ public class OrderedFlagSource implements FlagSource {
private final List<FlagSource> sources;
/**
- *
* @param sources Flag sources in descending priority order.
*/
public OrderedFlagSource(FlagSource... sources) {
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java
new file mode 100644
index 00000000000..925af130b95
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java
@@ -0,0 +1,18 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class StringFlag extends FlagImpl<String, StringFlag> {
+ public StringFlag(FlagId id, String defaultValue, FetchVector vector, FlagSerializer<String> serializer, FlagSource source) {
+ super(id, defaultValue, vector, serializer, source, StringFlag::new);
+ }
+
+ public String value() {
+ return boxedValue();
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundBooleanFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundBooleanFlag.java
new file mode 100644
index 00000000000..c228364434c
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundBooleanFlag.java
@@ -0,0 +1,27 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundBooleanFlag extends UnboundFlagImpl<Boolean, BooleanFlag, UnboundBooleanFlag> {
+ public UnboundBooleanFlag(FlagId id) {
+ this(id, false);
+ }
+
+ public UnboundBooleanFlag(FlagId id, boolean defaultValue) {
+ this(id, defaultValue, new FetchVector());
+ }
+
+ public UnboundBooleanFlag(FlagId id, boolean defaultValue, FetchVector defaultFetchVector) {
+ super(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(BooleanNode::valueOf, JsonNode::isBoolean, JsonNode::asBoolean),
+ UnboundBooleanFlag::new, BooleanFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java
index 597e19080ab..28307536233 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlag.java
@@ -1,38 +1,27 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.flags;
-import javax.annotation.concurrent.Immutable;
-
/**
* @author hakonhall
*/
-@Immutable
-public class UnboundFlag<T> {
- private final FlagId id;
- private final T defaultValue;
- private final Deserializer<T> deserializer;
- private final FetchVector fetchVector;
-
- public UnboundFlag(String flagId, T defaultValue, Deserializer<T> deserializer) {
- this(new FlagId(flagId), defaultValue, deserializer, new FetchVector());
- }
- public UnboundFlag(FlagId id, T defaultValue, Deserializer<T> deserializer, FetchVector fetchVector) {
- this.id = id;
- this.defaultValue = defaultValue;
- this.deserializer = deserializer;
- this.fetchVector = fetchVector;
- }
+/**
+ * Interface of an unbound flag.
+ *
+ * @param <T> Type of boxed value, e.g. Integer
+ * @param <F> Type of flag, e.g. IntFlag
+ * @param <U> Type of unbound flag, e.g. UnboundIntFlag
+ */
+public interface UnboundFlag<T, F extends Flag<T, F>, U extends UnboundFlag<T, F, U>> {
+ /** The flag ID. */
+ FlagId id();
- public FlagId id() {
- return id;
- }
+ /** Returns the flag serializer. */
+ FlagSerializer<T> serializer();
- public UnboundFlag<T> with(FetchVector.Dimension dimension, String value) {
- return new UnboundFlag<>(id, defaultValue, deserializer, fetchVector.with(dimension, value));
- }
+ /** Returns a clone of the unbound flag, but with the dimension set accordingly. */
+ U with(FetchVector.Dimension dimension, String dimensionValue);
- public Flag<T> bindTo(FlagSource source) {
- return new Flag<>(id, defaultValue, deserializer, fetchVector, source);
- }
+ /** Binds to a flag source, returning a (bound) flag. */
+ F bindTo(FlagSource source);
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlagImpl.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlagImpl.java
new file mode 100644
index 00000000000..513712d8753
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundFlagImpl.java
@@ -0,0 +1,58 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+/**
+ * @author hakonhall
+ */
+public abstract class UnboundFlagImpl<T, F extends Flag<T, F>, U extends UnboundFlag<T, F, U>>
+ implements UnboundFlag<T, F, U> {
+
+ private final FlagId id;
+ private final T defaultValue;
+ private final FlagSerializer<T> serializer;
+ private final FetchVector defaultFetchVector;
+ private final UnboundFlagFactory<T, F, U> unboundFlagFactory;
+ private final FlagFactory<T, F> flagFactory;
+
+ public interface UnboundFlagFactory<T1, F1 extends Flag<T1, F1>, U1 extends UnboundFlag<T1, F1, U1>> {
+ U1 create(FlagId id, T1 defaultValue, FetchVector fetchVector);
+ }
+
+ public interface FlagFactory<T2, F2 extends Flag<T2, F2>> {
+ F2 create(FlagId id, T2 defaultValue, FetchVector fetchVector, FlagSerializer<T2> serializer, FlagSource source);
+ }
+
+ protected UnboundFlagImpl(FlagId id,
+ T defaultValue,
+ FetchVector defaultFetchVector,
+ FlagSerializer<T> serializer,
+ UnboundFlagFactory<T, F, U> unboundFlagFactory,
+ FlagFactory<T, F> flagFactory) {
+ this.id = id;
+ this.defaultValue = defaultValue;
+ this.serializer = serializer;
+ this.defaultFetchVector = defaultFetchVector;
+ this.unboundFlagFactory = unboundFlagFactory;
+ this.flagFactory = flagFactory;
+ }
+
+ @Override
+ public FlagId id() {
+ return id;
+ }
+
+ @Override
+ public U with(FetchVector.Dimension dimension, String dimensionValue) {
+ return unboundFlagFactory.create(id, defaultValue, defaultFetchVector.with(dimension, dimensionValue));
+ }
+
+ @Override
+ public F bindTo(FlagSource source) {
+ return flagFactory.create(id, defaultValue, defaultFetchVector, serializer, source);
+ }
+
+ @Override
+ public FlagSerializer<T> serializer() {
+ return serializer;
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundIntFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundIntFlag.java
new file mode 100644
index 00000000000..01b660fe625
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundIntFlag.java
@@ -0,0 +1,23 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.IntNode;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundIntFlag extends UnboundFlagImpl<Integer, IntFlag, UnboundIntFlag> {
+ public UnboundIntFlag(FlagId id, int defaultValue) {
+ this(id, defaultValue, new FetchVector());
+ }
+
+ public UnboundIntFlag(FlagId id, int defaultValue, FetchVector defaultFetchVector) {
+ super(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(IntNode::new, JsonNode::isIntegralNumber, JsonNode::asInt),
+ UnboundIntFlag::new, IntFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundJacksonFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundJacksonFlag.java
new file mode 100644
index 00000000000..1cd303d1ede
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundJacksonFlag.java
@@ -0,0 +1,21 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundJacksonFlag<T> extends UnboundFlagImpl<T, JacksonFlag<T>, UnboundJacksonFlag<T>> {
+ public UnboundJacksonFlag(FlagId id, T defaultValue, Class<T> jacksonClass) {
+ this(id, defaultValue, new FetchVector(), jacksonClass);
+ }
+
+ public UnboundJacksonFlag(FlagId id, T defaultValue, FetchVector defaultFetchVector, Class<T> jacksonClass) {
+ super(id, defaultValue, defaultFetchVector,
+ new JacksonSerializer<>(jacksonClass),
+ (id2, defaultValue2, vector2) -> new UnboundJacksonFlag<>(id2, defaultValue2, vector2, jacksonClass),
+ JacksonFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundLongFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundLongFlag.java
new file mode 100644
index 00000000000..732a9073fc8
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundLongFlag.java
@@ -0,0 +1,23 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.LongNode;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundLongFlag extends UnboundFlagImpl<Long, LongFlag, UnboundLongFlag> {
+ public UnboundLongFlag(FlagId id, long defaultValue) {
+ this(id, defaultValue, new FetchVector());
+ }
+
+ public UnboundLongFlag(FlagId id, Long defaultValue, FetchVector defaultFetchVector) {
+ super(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(LongNode::new, JsonNode::isIntegralNumber, JsonNode::asLong),
+ UnboundLongFlag::new, LongFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
new file mode 100644
index 00000000000..67c4bceb3e9
--- /dev/null
+++ b/flags/src/main/java/com/yahoo/vespa/flags/UnboundStringFlag.java
@@ -0,0 +1,23 @@
+// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.flags;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * @author hakonhall
+ */
+@Immutable
+public class UnboundStringFlag extends UnboundFlagImpl<String, StringFlag, UnboundStringFlag> {
+ public UnboundStringFlag(FlagId id, String defaultValue) {
+ this(id, defaultValue, new FetchVector());
+ }
+
+ public UnboundStringFlag(FlagId id, String defaultValue, FetchVector defaultFetchVector) {
+ super(id, defaultValue, defaultFetchVector,
+ new SimpleFlagSerializer<>(TextNode::new, JsonNode::isTextual, JsonNode::asText),
+ UnboundStringFlag::new, StringFlag::new);
+ }
+}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java b/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
index bfd959e82f9..b5bc2032bae 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/FlagData.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.flags.json;
import com.fasterxml.jackson.databind.JsonNode;
+import com.yahoo.vespa.flags.Deserializer;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagId;
import com.yahoo.vespa.flags.FlagSource;
@@ -16,9 +17,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* A data structure containing all data for a single flag, that can be serialized to/from JSON,
@@ -100,8 +101,12 @@ public class FlagData {
}
private static FlagData fromWire(WireFlagData wireFlagData) {
+ if (wireFlagData.id == null) {
+ throw new IllegalArgumentException("Flag ID missing");
+ }
+
return new FlagData(
- new FlagId(Objects.requireNonNull(wireFlagData.id)),
+ new FlagId(wireFlagData.id),
FetchVectorHelper.fromWire(wireFlagData.defaultFetchVector),
rulesFromWire(wireFlagData.rules)
);
@@ -111,5 +116,13 @@ public class FlagData {
if (wireRules == null) return Collections.emptyList();
return wireRules.stream().map(Rule::fromWire).collect(Collectors.toList());
}
+
+ /** E.g. verify all RawFlag can be deserialized. */
+ public void validate(Deserializer<?> deserializer) {
+ rules.stream()
+ .flatMap(rule -> rule.getValueToApply().map(Stream::of).orElse(null))
+ .forEach(deserializer::deserialize);
+
+ }
}
diff --git a/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java b/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java
index 7d9d7868308..e19436069b5 100644
--- a/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java
+++ b/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java
@@ -24,44 +24,44 @@ public class FileFlagSourceTest {
@Test
public void testFeatureLikeFlags() throws IOException {
- Flag<Boolean> featureFlag = new Flag<>(id, false, source, Flags.BOOLEAN_SERIALIZER);
- Flag<Boolean> byDefaultTrue = new Flag<>(id, true, source, Flags.BOOLEAN_SERIALIZER);
+ BooleanFlag booleanFlag = new UnboundBooleanFlag(id).bindTo(source);
+ BooleanFlag byDefaultTrue = new UnboundBooleanFlag(id, true).bindTo(source);
- assertFalse(featureFlag.value());
+ assertFalse(booleanFlag.value());
assertTrue(byDefaultTrue.value());
writeFlag(id.toString(), "true\n");
- assertTrue(featureFlag.value());
+ assertTrue(booleanFlag.value());
assertTrue(byDefaultTrue.value());
writeFlag(id.toString(), "false\n");
- assertFalse(featureFlag.value());
+ assertFalse(booleanFlag.value());
assertFalse(byDefaultTrue.value());
}
@Test
public void testIntegerLikeFlags() throws IOException {
- Flag<Integer> intFlag = new Flag<>(id, -1, source, Flags.INT_SERIALIZER);
- Flag<Long> longFlag = new Flag<>(id, -2L, source, Flags.LONG_SERIALIZER);
+ IntFlag intFlag = new UnboundIntFlag(id, -1).bindTo(source);
+ LongFlag longFlag = new UnboundLongFlag(id, -2L).bindTo(source);
assertFalse(fetch().isPresent());
assertFalse(fetch().isPresent());
- assertEquals(-1, (int) intFlag.value());
- assertEquals(-2L, (long) longFlag.value());
+ assertEquals(-1, intFlag.value());
+ assertEquals(-2L, longFlag.value());
writeFlag(id.toString(), "1\n");
assertTrue(fetch().isPresent());
assertTrue(fetch().isPresent());
- assertEquals(1, (int) intFlag.value());
- assertEquals(1L, (long) longFlag.value());
+ assertEquals(1, intFlag.value());
+ assertEquals(1L, longFlag.value());
}
@Test
public void testStringFlag() throws IOException {
- Flag<String> stringFlag = new Flag<>(id, "default", source, Flags.STRING_SERIALIZER);
+ StringFlag stringFlag = new UnboundStringFlag(id, "default").bindTo(source);
assertFalse(fetch().isPresent());
assertEquals("default", stringFlag.value());
@@ -71,11 +71,11 @@ public class FileFlagSourceTest {
@Test
public void parseFailure() throws IOException {
- Flag<Boolean> featureFlag = new Flag<>(id, false, source, Flags.BOOLEAN_SERIALIZER);
- writeFlag(featureFlag.id().toString(), "garbage");
+ BooleanFlag booleanFlag = new UnboundBooleanFlag(id).bindTo(source);
+ writeFlag(booleanFlag.id().toString(), "garbage");
try {
- featureFlag.value();
+ booleanFlag.value();
} catch (UncheckedIOException e) {
assertThat(e.getMessage(), containsString("garbage"));
}
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 4f7d797e07d..591198f5e50 100644
--- a/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java
+++ b/flags/src/test/java/com/yahoo/vespa/flags/FlagsTest.java
@@ -31,7 +31,7 @@ public class FlagsTest {
public void testBoolean() {
final boolean defaultValue = false;
FlagSource source = mock(FlagSource.class);
- Flag<Boolean> booleanFlag = Flags.defineBoolean("id", defaultValue, "description",
+ BooleanFlag booleanFlag = Flags.defineFeatureFlag("id", defaultValue, "description",
"modification effect", FetchVector.Dimension.ZONE_ID, FetchVector.Dimension.HOSTNAME)
.with(FetchVector.Dimension.ZONE_ID, "a-zone")
.bindTo(source);
@@ -65,19 +65,19 @@ public class FlagsTest {
@Test
public void testString() {
- testGeneric(Flags.defineString("string-id", "default value", "description",
+ testGeneric(Flags.defineStringFlag("string-id", "default value", "description",
"modification effect", FetchVector.Dimension.ZONE_ID, FetchVector.Dimension.HOSTNAME),
"default value", "other value");
}
@Test
public void testInt() {
- testGeneric(Flags.defineInt("int-id", 2, "desc", "mod"), 2, 3);
+ testGeneric(Flags.defineIntFlag("int-id", 2, "desc", "mod"), 2, 3);
}
@Test
public void testLong() {
- testGeneric(Flags.defineLong("long-id", 1L, "desc", "mod"), 1L, 2L);
+ testGeneric(Flags.defineLongFlag("long-id", 1L, "desc", "mod"), 1L, 2L);
}
@Test
@@ -87,25 +87,24 @@ public class FlagsTest {
instance.integer = -2;
instance.string = "foo";
- testGeneric(Flags.defineJackson("jackson-id", ExampleJacksonClass.class, defaultInstance,
+ testGeneric(Flags.defineJacksonFlag("jackson-id", defaultInstance, ExampleJacksonClass.class,
"description", "modification effect", FetchVector.Dimension.HOSTNAME),
defaultInstance, instance);
}
- private <T> void testGeneric(UnboundFlag<T> unboundFlag, T defaultValue, T value) {
+ private <T> void testGeneric(UnboundFlag<?, ?, ?> unboundFlag, T defaultValue, T value) {
FlagSource source = mock(FlagSource.class);
- Flag<T> flag = unboundFlag.bindTo(source);
+ Flag<?, ?> flag = unboundFlag.bindTo(source);
when(source.fetch(any(), any())).thenReturn(Optional.empty());
- assertThat(flag.value(), equalTo(defaultValue));
+ assertThat(flag.boxedValue(), equalTo(defaultValue));
when(source.fetch(any(), any())).thenReturn(Optional.of(JsonNodeRawFlag.fromJacksonClass(value)));
- assertThat(flag.value(), equalTo(value));
+ assertThat(flag.boxedValue(), equalTo(value));
assertTrue(Flags.getFlag(unboundFlag.id()).isPresent());
}
-
@JsonIgnoreProperties(ignoreUnknown = true)
private static class ExampleJacksonClass {
@JsonProperty("integer")
diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/health/HealthMonitorManager.java b/service-monitor/src/main/java/com/yahoo/vespa/service/health/HealthMonitorManager.java
index 340d30e9f41..682f677a626 100644
--- a/service-monitor/src/main/java/com/yahoo/vespa/service/health/HealthMonitorManager.java
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/health/HealthMonitorManager.java
@@ -8,7 +8,7 @@ import com.yahoo.vespa.applicationmodel.ClusterId;
import com.yahoo.vespa.applicationmodel.ConfigId;
import com.yahoo.vespa.applicationmodel.ServiceStatus;
import com.yahoo.vespa.applicationmodel.ServiceType;
-import com.yahoo.vespa.flags.Flag;
+import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.service.duper.DuperModelManager;
@@ -50,7 +50,7 @@ public class HealthMonitorManager implements MonitorManager {
private final ConcurrentHashMap<ApplicationId, ApplicationHealthMonitor> healthMonitors = new ConcurrentHashMap<>();
private final DuperModelManager duperModel;
private final ApplicationHealthMonitorFactory applicationHealthMonitorFactory;
- private final Flag<Boolean> monitorInfra;
+ private final BooleanFlag monitorInfra;
@Inject
public HealthMonitorManager(DuperModelManager duperModel, FlagSource flagSource) {
@@ -60,13 +60,13 @@ public class HealthMonitorManager implements MonitorManager {
}
private HealthMonitorManager(DuperModelManager duperModel,
- Flag<Boolean> monitorInfra,
+ BooleanFlag monitorInfra,
StateV1HealthModel healthModel) {
this(duperModel, monitorInfra, id -> new ApplicationHealthMonitor(id, healthModel));
}
HealthMonitorManager(DuperModelManager duperModel,
- Flag<Boolean> monitorInfra,
+ BooleanFlag monitorInfra,
ApplicationHealthMonitorFactory applicationHealthMonitorFactory) {
this.duperModel = duperModel;
this.monitorInfra = monitorInfra;
diff --git a/service-monitor/src/test/java/com/yahoo/vespa/service/health/HealthMonitorManagerTest.java b/service-monitor/src/test/java/com/yahoo/vespa/service/health/HealthMonitorManagerTest.java
index 39147955d4c..78b0bed0e6f 100644
--- a/service-monitor/src/test/java/com/yahoo/vespa/service/health/HealthMonitorManagerTest.java
+++ b/service-monitor/src/test/java/com/yahoo/vespa/service/health/HealthMonitorManagerTest.java
@@ -7,7 +7,7 @@ import com.yahoo.vespa.applicationmodel.ClusterId;
import com.yahoo.vespa.applicationmodel.ConfigId;
import com.yahoo.vespa.applicationmodel.ServiceStatus;
import com.yahoo.vespa.applicationmodel.ServiceType;
-import com.yahoo.vespa.flags.Flag;
+import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.service.duper.ConfigServerApplication;
import com.yahoo.vespa.service.duper.ControllerHostApplication;
import com.yahoo.vespa.service.duper.DuperModelManager;
@@ -33,8 +33,7 @@ import static org.mockito.Mockito.when;
public class HealthMonitorManagerTest {
private final ConfigServerApplication configServerApplication = new ConfigServerApplication();
private final DuperModelManager duperModel = mock(DuperModelManager.class);
- @SuppressWarnings("unchecked")
- private final Flag<Boolean> monitorInfra = (Flag<Boolean>) mock(Flag.class);
+ private final BooleanFlag monitorInfra = mock(BooleanFlag.class);
private final ApplicationHealthMonitor monitor = mock(ApplicationHealthMonitor.class);
private final ApplicationHealthMonitorFactory monitorFactory = mock(ApplicationHealthMonitorFactory.class);
private final HealthMonitorManager manager = new HealthMonitorManager(duperModel, monitorInfra, monitorFactory);