diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-07-12 15:49:04 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2019-07-12 15:49:04 +0200 |
commit | c10ba446e46fe390b052958e3148cbf9bbb74a9b (patch) | |
tree | 7d422465c5c46c8e5ca8fedce4b8e1bb4f8cf68e /flags | |
parent | 12b55be208be4199178c7c1387c308fe8d80f947 (diff) |
Revert "Decouple flags REST API from config server"
This reverts commit b81b21546cdff92d360cbdf7dda27e6ed7bc7170.
Diffstat (limited to 'flags')
14 files changed, 1 insertions, 832 deletions
diff --git a/flags/pom.xml b/flags/pom.xml index 7ef082cc1bc..c1e9eca20ab 100644 --- a/flags/pom.xml +++ b/flags/pom.xml @@ -59,18 +59,7 @@ <classifier>no_aop</classifier> <scope>provided</scope> </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>container-dev</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>com.yahoo.vespa</groupId> - <artifactId>zkfacade</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> diff --git a/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlag.java deleted file mode 100644 index 8234e9df725..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlag.java +++ /dev/null @@ -1,47 +0,0 @@ -// 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.http; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.jdisc.Response; -import com.yahoo.vespa.flags.FlagDefinition; -import com.yahoo.vespa.flags.json.DimensionHelper; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * @author hakonhall - */ -public class DefinedFlag extends HttpResponse { - private static ObjectMapper mapper = new ObjectMapper(); - - private final FlagDefinition flagDefinition; - - public DefinedFlag(FlagDefinition flagDefinition) { - super(Response.Status.OK); - this.flagDefinition = flagDefinition; - } - - @Override - public void render(OutputStream outputStream) throws IOException { - ObjectNode rootNode = mapper.createObjectNode(); - renderFlagDefinition(flagDefinition, rootNode); - mapper.writeValue(outputStream, rootNode); - } - - static void renderFlagDefinition(FlagDefinition flagDefinition, ObjectNode definitionNode) { - definitionNode.put("description", flagDefinition.getDescription()); - definitionNode.put("modification-effect", flagDefinition.getModificationEffect()); - ArrayNode dimensionsNode = definitionNode.putArray("dimensions"); - flagDefinition.getDimensions().forEach(dimension -> dimensionsNode.add(DimensionHelper.toWire(dimension))); - } - - @Override - public String getContentType() { - return "application/json"; - } - -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlags.java deleted file mode 100644 index e1db7dda6e0..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlags.java +++ /dev/null @@ -1,43 +0,0 @@ -// 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.http; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.jdisc.Response; -import com.yahoo.vespa.flags.FlagDefinition; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Comparator; -import java.util.List; - -/** - * @author hakonhall - */ -public class DefinedFlags extends HttpResponse { - private static ObjectMapper mapper = new ObjectMapper(); - private static final Comparator<FlagDefinition> sortByFlagId = Comparator.comparing(flagDefinition -> flagDefinition.getUnboundFlag().id()); - - private final List<FlagDefinition> flags; - - public DefinedFlags(List<FlagDefinition> flags) { - super(Response.Status.OK); - this.flags = flags; - } - - @Override - public void render(OutputStream outputStream) throws IOException { - ObjectNode rootNode = mapper.createObjectNode(); - flags.stream().sorted(sortByFlagId).forEach(flagDefinition -> { - ObjectNode definitionNode = rootNode.putObject(flagDefinition.getUnboundFlag().id().toString()); - DefinedFlag.renderFlagDefinition(flagDefinition, definitionNode); - }); - mapper.writeValue(outputStream, rootNode); - } - - @Override - public String getContentType() { - return "application/json"; - } -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/http/ErrorResponse.java b/flags/src/main/java/com/yahoo/vespa/flags/http/ErrorResponse.java deleted file mode 100644 index 969903093a4..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/ErrorResponse.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.flags.http; - -import com.yahoo.slime.Cursor; -import com.yahoo.slime.Slime; - -import static com.yahoo.jdisc.Response.Status.BAD_REQUEST; -import static com.yahoo.jdisc.Response.Status.FORBIDDEN; -import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; -import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; -import static com.yahoo.jdisc.Response.Status.NOT_FOUND; -import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED; - -/** - * A HTTP JSON response containing an error code and a message - * - * @author bratseth - */ -public class ErrorResponse extends SlimeJsonResponse { - - public enum errorCodes { - NOT_FOUND, - BAD_REQUEST, - FORBIDDEN, - METHOD_NOT_ALLOWED, - INTERNAL_SERVER_ERROR, - UNAUTHORIZED - } - - public ErrorResponse(int statusCode, String errorType, String message) { - super(statusCode, asSlimeMessage(errorType, message)); - } - - private static Slime asSlimeMessage(String errorType, String message) { - Slime slime = new Slime(); - Cursor root = slime.setObject(); - root.setString("error-code", errorType); - root.setString("message", message); - return slime; - } - - public static ErrorResponse notFoundError(String message) { - return new ErrorResponse(NOT_FOUND, errorCodes.NOT_FOUND.name(), message); - } - - public static ErrorResponse internalServerError(String message) { - return new ErrorResponse(INTERNAL_SERVER_ERROR, errorCodes.INTERNAL_SERVER_ERROR.name(), message); - } - - public static ErrorResponse badRequest(String message) { - return new ErrorResponse(BAD_REQUEST, errorCodes.BAD_REQUEST.name(), message); - } - - public static ErrorResponse forbidden(String message) { - return new ErrorResponse(FORBIDDEN, errorCodes.FORBIDDEN.name(), message); - } - - public static ErrorResponse unauthorized(String message) { - return new ErrorResponse(UNAUTHORIZED, errorCodes.UNAUTHORIZED.name(), message); - } - - public static ErrorResponse methodNotAllowed(String message) { - return new ErrorResponse(METHOD_NOT_ALLOWED, errorCodes.METHOD_NOT_ALLOWED.name(), message); - } - -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataListResponse.java b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataListResponse.java deleted file mode 100644 index 5af97007997..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataListResponse.java +++ /dev/null @@ -1,58 +0,0 @@ -// 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.http; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.jdisc.Response; -import com.yahoo.vespa.flags.FlagId; -import com.yahoo.vespa.flags.json.FlagData; -import com.yahoo.vespa.flags.json.wire.WireFlagDataList; - -import java.io.OutputStream; -import java.util.Map; -import java.util.TreeMap; - -import static com.yahoo.yolean.Exceptions.uncheck; - -/** - * @author hakonhall - */ -public class FlagDataListResponse extends HttpResponse { - private static ObjectMapper mapper = new ObjectMapper(); - - private final String flagsV1Uri; - private final TreeMap<FlagId, FlagData> flags; - private final boolean recursive; - - public FlagDataListResponse(String flagsV1Uri, Map<FlagId, FlagData> flags, boolean recursive) { - super(Response.Status.OK); - this.flagsV1Uri = flagsV1Uri; - this.flags = new TreeMap<>(flags); - this.recursive = recursive; - } - - @Override - public void render(OutputStream outputStream) { - if (recursive) { - WireFlagDataList list = new WireFlagDataList(); - flags.values().forEach(flagData -> list.flags.add(flagData.toWire())); - list.serializeToOutputStream(outputStream); - } else { - ObjectNode rootNode = mapper.createObjectNode(); - ArrayNode flagsArray = rootNode.putArray("flags"); - flags.forEach((flagId, flagData) -> { - ObjectNode object = flagsArray.addObject(); - object.put("id", flagId.toString()); - object.put("url", flagsV1Uri + "/data/" + flagId.toString()); - }); - uncheck(() -> mapper.writeValue(outputStream, rootNode)); - } - } - - @Override - public String getContentType() { - return "application/json"; - } -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataResponse.java b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataResponse.java deleted file mode 100644 index f6e81e030c7..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataResponse.java +++ /dev/null @@ -1,30 +0,0 @@ -// 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.http; - -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.jdisc.Response; -import com.yahoo.vespa.flags.json.FlagData; - -import java.io.OutputStream; - -/** - * @author hakonhall - */ -public class FlagDataResponse extends HttpResponse { - private final FlagData data; - - FlagDataResponse(FlagData data) { - super(Response.Status.OK); - this.data = data; - } - - @Override - public void render(OutputStream outputStream) { - data.serializeToOutputStream(outputStream); - } - - @Override - public String getContentType() { - return "application/json"; - } -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/http/FlagsHandler.java b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagsHandler.java deleted file mode 100644 index 76f74cbe931..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/FlagsHandler.java +++ /dev/null @@ -1,138 +0,0 @@ -// 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.http; - -import com.google.inject.Inject; -import com.yahoo.container.jdisc.HttpRequest; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.container.jdisc.LoggingRequestHandler; -import com.yahoo.log.LogLevel; -import com.yahoo.restapi.Path; -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.vespa.flags.persistence.FlagsDb; -import com.yahoo.yolean.Exceptions; - -import java.io.UncheckedIOException; -import java.net.URI; -import java.util.Objects; - -/** - * Handles /flags/v1 requests - * - * @author hakonhall - */ -public class FlagsHandler extends LoggingRequestHandler { - - private final FlagsDb flagsDb; - - @Inject - public FlagsHandler(LoggingRequestHandler.Context context, FlagsDb flagsDb) { - super(context); - this.flagsDb = flagsDb; - } - - @Override - public HttpResponse handle(HttpRequest request) { - try { - switch (request.getMethod()) { - case GET: return handleGET(request); - case DELETE: return handleDELETE(request); - case PUT: return handlePUT(request); - default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); - } - } - catch (IllegalArgumentException e) { - return ErrorResponse.badRequest(Exceptions.toMessageString(e)); - } - catch (RuntimeException e) { - log.log(LogLevel.WARNING, "Unexpected error handling '" + request.getUri() + "'", e); - return ErrorResponse.internalServerError(Exceptions.toMessageString(e)); - } - } - - private HttpResponse handleGET(HttpRequest request) { - Path path = new Path(request.getUri()); - if (path.matches("/flags/v1")) return new V1Response(flagsV1Uri(request), "data", "defined"); - if (path.matches("/flags/v1/data")) return getFlagDataList(request); - if (path.matches("/flags/v1/data/{flagId}")) return getFlagData(findFlagId(request, path)); - if (path.matches("/flags/v1/defined")) return new DefinedFlags(Flags.getAllFlags()); - if (path.matches("/flags/v1/defined/{flagId}")) return getDefinedFlag(findFlagId(request, path)); - return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); - } - - private HttpResponse handlePUT(HttpRequest request) { - Path path = new Path(request.getUri()); - if (path.matches("/flags/v1/data/{flagId}")) return putFlagData(request, findFlagId(request, path)); - return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); - } - - private HttpResponse handleDELETE(HttpRequest request) { - Path path = new Path(request.getUri()); - if (path.matches("/flags/v1/data/{flagId}")) return deleteFlagData(findFlagId(request, path)); - return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); - } - - private String flagsV1Uri(HttpRequest request) { - URI uri = request.getUri(); - String port = uri.getPort() < 0 ? "" : ":" + uri.getPort(); - return uri.getScheme() + "://" + uri.getHost() + port + "/flags/v1"; - } - - private HttpResponse getDefinedFlag(FlagId flagId) { - var definedFlag = Flags.getFlag(flagId).map(DefinedFlag::new); - if (definedFlag.isPresent()) { - return definedFlag.get(); - } - return ErrorResponse.notFoundError("Flag " + flagId + " not defined"); - } - - private HttpResponse getFlagDataList(HttpRequest request) { - return new FlagDataListResponse(flagsV1Uri(request), flagsDb.getAllFlags(), - Objects.equals(request.getProperty("recursive"), "true")); - } - - private HttpResponse getFlagData(FlagId flagId) { - var data = flagsDb.getValue(flagId).map(FlagDataResponse::new); - if (data.isPresent()) { - return data.get(); - } - return ErrorResponse.notFoundError("Flag " + flagId + " not set"); - } - - private HttpResponse putFlagData(HttpRequest request, FlagId flagId) { - FlagData data; - try { - data = FlagData.deserialize(request.getData()); - } catch (UncheckedIOException e) { - return ErrorResponse.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(); - } - - private HttpResponse deleteFlagData(FlagId flagId) { - flagsDb.removeValue(flagId); - return new OKResponse(); - } - - private FlagId findFlagId(HttpRequest request, Path path) { - FlagId flagId = new FlagId(path.get("flagId")); - if (!isForce(request) && Flags.getFlag(flagId).isEmpty()) { - throw new IllegalArgumentException("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/flags/src/main/java/com/yahoo/vespa/flags/http/OKResponse.java b/flags/src/main/java/com/yahoo/vespa/flags/http/OKResponse.java deleted file mode 100644 index d094e2d5734..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/OKResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -// 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.http; - -import com.yahoo.container.jdisc.EmptyResponse; -import com.yahoo.jdisc.Response; - -/** - * @author hakonhall - */ -public class OKResponse extends EmptyResponse { - public OKResponse() { - super(Response.Status.OK); - } - - @Override - public String getContentType() { - return "application/json"; - } -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/http/SlimeJsonResponse.java b/flags/src/main/java/com/yahoo/vespa/flags/http/SlimeJsonResponse.java deleted file mode 100644 index dd71795ae43..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/SlimeJsonResponse.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.flags.http; - -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.slime.JsonFormat; -import com.yahoo.slime.Slime; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * A generic Json response using Slime for JSON encoding - * - * @author bratseth - */ -public class SlimeJsonResponse extends HttpResponse { - - private final Slime slime; - - public SlimeJsonResponse(Slime slime) { - super(200); - this.slime = slime; - } - - public SlimeJsonResponse(int statusCode, Slime slime) { - super(statusCode); - this.slime = slime; - } - - @Override - public void render(OutputStream stream) throws IOException { - new JsonFormat(true).encode(stream, slime); - } - - @Override - public String getContentType() { return "application/json"; } - -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/http/V1Response.java b/flags/src/main/java/com/yahoo/vespa/flags/http/V1Response.java deleted file mode 100644 index e8ff0bd99a4..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/http/V1Response.java +++ /dev/null @@ -1,46 +0,0 @@ -// 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.http; - -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.jdisc.Response; -import com.yahoo.slime.Cursor; -import com.yahoo.slime.JsonFormat; -import com.yahoo.slime.Slime; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; - -/** - * @author hakonhall - */ -public class V1Response extends HttpResponse { - - private final Slime slime; - - public V1Response(String flagsV1Uri, String... names) { - super(Response.Status.OK); - this.slime = generateBody(flagsV1Uri, List.of(names)); - } - - @Override - public void render(OutputStream stream) throws IOException { - new JsonFormat(true).encode(stream, slime); - } - - @Override - public String getContentType() { - return "application/json"; - } - - private static Slime generateBody(String flagsV1Uri, List<String> names) { - Slime slime = new Slime(); - Cursor root = slime.setObject(); - names.forEach(name -> { - Cursor data = root.setObject(name); - data.setString("url", flagsV1Uri + "/" + name); - }); - return slime; - } - -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/persistence/FlagsDb.java b/flags/src/main/java/com/yahoo/vespa/flags/persistence/FlagsDb.java deleted file mode 100644 index 2ed762f2895..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/persistence/FlagsDb.java +++ /dev/null @@ -1,68 +0,0 @@ -// 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.persistence; - -import com.google.inject.Inject; -import com.yahoo.path.Path; -import com.yahoo.vespa.curator.Curator; -import com.yahoo.vespa.flags.FlagId; -import com.yahoo.vespa.flags.json.FlagData; -import org.apache.curator.framework.recipes.cache.ChildData; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * @author hakonhall - */ -public class FlagsDb { - - private static final Path ROOT_PATH = Path.fromString("/flags/v1"); - - private final Curator curator; - private final Curator.DirectoryCache cache; - - @Inject - public FlagsDb(Curator curator) { - this.curator = curator; - curator.create(ROOT_PATH); - ExecutorService executorService = Executors.newFixedThreadPool(1); - this.cache = curator.createDirectoryCache(ROOT_PATH.getAbsolute(), true, false, executorService); - cache.start(); - } - - /** Get the String value of the flag. */ - public Optional<FlagData> getValue(FlagId flagId) { - return Optional.ofNullable(cache.getCurrentData(getZkPathFor(flagId))) - .map(ChildData::getData) - .map(FlagData::deserializeUtf8Json); - } - - /** Set the String value of the flag. */ - public void setValue(FlagId flagId, FlagData data) { - curator.set(getZkPathFor(flagId), data.serializeToUtf8Json()); - } - - /** Get all flags that have been set. */ - public Map<FlagId, FlagData> getAllFlags() { - List<ChildData> dataList = cache.getCurrentData(); - return dataList.stream() - .map(ChildData::getData) - .map(FlagData::deserializeUtf8Json) - .collect(Collectors.toMap(FlagData::id, Function.identity())); - } - - /** Remove the flag value if it exists. */ - public void removeValue(FlagId flagId) { - curator.delete(getZkPathFor(flagId)); - } - - private static Path getZkPathFor(FlagId flagId) { - return ROOT_PATH.append(flagId.toString()); - } - -} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/persistence/package-info.java b/flags/src/main/java/com/yahoo/vespa/flags/persistence/package-info.java deleted file mode 100644 index d4753ed1756..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/persistence/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author mpolden - */ -@ExportPackage -package com.yahoo.vespa.flags.persistence; - -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/flags/src/test/java/com/yahoo/vespa/flags/http/FlagsHandlerTest.java b/flags/src/test/java/com/yahoo/vespa/flags/http/FlagsHandlerTest.java deleted file mode 100644 index 8ae1008ba22..00000000000 --- a/flags/src/test/java/com/yahoo/vespa/flags/http/FlagsHandlerTest.java +++ /dev/null @@ -1,203 +0,0 @@ -// 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.http; - -import com.yahoo.container.jdisc.HttpRequest; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.jdisc.http.HttpRequest.Method; -import com.yahoo.text.Utf8; -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.UnboundBooleanFlag; -import com.yahoo.vespa.flags.persistence.FlagsDb; -import com.yahoo.yolean.Exceptions; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; - -/** - * @author hakonhall - */ -public class FlagsHandlerTest { - 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"; - - private final FlagsDb flagsDb = new FlagsDb(new MockCurator()); - private final FlagsHandler handler = new FlagsHandler(FlagsHandler.testOnlyContext(), flagsDb); - - @Test - public void testV1() { - String expectedResponse = "{" + - Stream.of("data", "defined") - .map(name -> "\"" + name + "\":{\"url\":\"https://foo.com:4443/flags/v1/" + name + "\"}") - .collect(Collectors.joining(",")) + - "}"; - verifySuccessfulRequest(Method.GET, "", "", expectedResponse); - verifySuccessfulRequest(Method.GET, "/", "", expectedResponse); - } - - @Test - public void testDefined() { - try (Flags.Replacer replacer = Flags.clearFlagsForTesting()) { - fixUnusedWarning(replacer); - Flags.defineFeatureFlag("id", false, "desc", "mod", FetchVector.Dimension.HOSTNAME); - verifySuccessfulRequest(Method.GET, "/defined", "", - "{\"id\":{\"description\":\"desc\",\"modification-effect\":\"mod\",\"dimensions\":[\"hostname\"]}}"); - - verifySuccessfulRequest(Method.GET, "/defined/id", "", - "{\"description\":\"desc\",\"modification-effect\":\"mod\",\"dimensions\":[\"hostname\"]}"); - } - } - - private void fixUnusedWarning(Flags.Replacer replacer) { } - - @Test - public void testData() { - // PUT flag with ID id1 - verifySuccessfulRequest(Method.PUT, "/data/" + FLAG1.id(), - "{\n" + - " \"id\": \"id1\",\n" + - " \"rules\": [\n" + - " {\n" + - " \"value\": true\n" + - " }\n" + - " ]\n" + - "}", - ""); - - // GET on ID id1 should return the same as the put. - verifySuccessfulRequest(Method.GET, "/data/" + FLAG1.id(), - "", "{\"id\":\"id1\",\"rules\":[{\"value\":true}]}"); - - // List all flags should list only id1 - verifySuccessfulRequest(Method.GET, "/data", - "", "{\"flags\":[{\"id\":\"id1\",\"url\":\"https://foo.com:4443/flags/v1/data/id1\"}]}"); - - // Should be identical to above: suffix / on path should be ignored - verifySuccessfulRequest(Method.GET, "/data/", - "", "{\"flags\":[{\"id\":\"id1\",\"url\":\"https://foo.com:4443/flags/v1/data/id1\"}]}"); - - // Verify absent port => absent in response - assertThat(handleWithPort(Method.GET, -1, "/data", "", 200), - is("{\"flags\":[{\"id\":\"id1\",\"url\":\"https://foo.com/flags/v1/data/id1\"}]}")); - - // PUT id2 - verifySuccessfulRequest(Method.PUT, "/data/" + FLAG2.id(), - "{\n" + - " \"id\": \"id2\",\n" + - " \"rules\": [\n" + - " {\n" + - " \"conditions\": [\n" + - " {\n" + - " \"type\": \"whitelist\",\n" + - " \"dimension\": \"hostname\",\n" + - " \"values\": [ \"host1\", \"host2\" ]\n" + - " },\n" + - " {\n" + - " \"type\": \"blacklist\",\n" + - " \"dimension\": \"application\",\n" + - " \"values\": [ \"app1\", \"app2\" ]\n" + - " }\n" + - " ],\n" + - " \"value\": true\n" + - " }\n" + - " ],\n" + - " \"attributes\": {\n" + - " \"zone\": \"zone1\"\n" + - " }\n" + - "}\n", - ""); - - // GET on id2 should now return what was put - verifySuccessfulRequest(Method.GET, "/data/" + FLAG2.id(), "", - "{\"id\":\"id2\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"hostname\",\"values\":[\"host1\",\"host2\"]},{\"type\":\"blacklist\",\"dimension\":\"application\",\"values\":[\"app1\",\"app2\"]}],\"value\":true}],\"attributes\":{\"zone\":\"zone1\"}}"); - - // The list of flag data should return id1 and id2 - verifySuccessfulRequest(Method.GET, "/data", - "", - "{\"flags\":[{\"id\":\"id1\",\"url\":\"https://foo.com:4443/flags/v1/data/id1\"},{\"id\":\"id2\",\"url\":\"https://foo.com:4443/flags/v1/data/id2\"}]}"); - - // Putting (overriding) id1 should work silently - verifySuccessfulRequest(Method.PUT, "/data/" + FLAG1.id(), - "{\n" + - " \"id\": \"id1\",\n" + - " \"rules\": [\n" + - " {\n" + - " \"value\": false\n" + - " }\n" + - " ]\n" + - "}\n", - ""); - - // Verify PUT - verifySuccessfulRequest(Method.GET, "/data/" + FLAG1.id(), "", "{\"id\":\"id1\",\"rules\":[{\"value\":false}]}"); - - // Get all recursivelly displays all flag data - verifySuccessfulRequest(Method.GET, "/data?recursive=true", "", - "{\"flags\":[{\"id\":\"id1\",\"rules\":[{\"value\":false}]},{\"id\":\"id2\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"hostname\",\"values\":[\"host1\",\"host2\"]},{\"type\":\"blacklist\",\"dimension\":\"application\",\"values\":[\"app1\",\"app2\"]}],\"value\":true}],\"attributes\":{\"zone\":\"zone1\"}}]}"); - - // Deleting both flags - verifySuccessfulRequest(Method.DELETE, "/data/" + FLAG1.id(), "", ""); - verifySuccessfulRequest(Method.DELETE, "/data/" + FLAG2.id(), "", ""); - - // And the list of data flags should now be empty - verifySuccessfulRequest(Method.GET, "/data", "", "{\"flags\":[]}"); - } - - @Test - public void testForcing() { - assertThat(handle(Method.PUT, "/data/" + new FlagId("undef"), "", 400), - containsString("There is no flag 'undef'")); - - 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")); - - assertThat(handle(Method.PUT, "/data/" + FLAG1.id(), "{\"id\": \"id1\",\"rules\": [{\"value\":\"string\"}]}", 400), - containsString("Wrong type of JsonNode: STRING")); - - assertThat(handle(Method.PUT, "/data/" + FLAG1.id() + "?force=true", "{\"id\": \"id1\",\"rules\": [{\"value\":\"string\"}]}", 200), - is("")); - } - - private void verifySuccessfulRequest(Method method, String pathSuffix, String requestBody, String expectedResponseBody) { - assertThat(handle(method, pathSuffix, requestBody, 200), is(expectedResponseBody)); - } - - private String handle(Method method, String pathSuffix, String requestBody, int expectedStatus) { - return handleWithPort(method, 4443, pathSuffix, requestBody, expectedStatus); - } - - private String handleWithPort(Method method, int port, String pathSuffix, String requestBody, int expectedStatus) { - String uri = "https://foo.com" + (port < 0 ? "" : ":" + port) + "/flags/v1" + pathSuffix; - HttpRequest request = HttpRequest.createTestRequest(uri, method, makeInputStream(requestBody)); - HttpResponse response = handler.handle(request); - assertEquals(expectedStatus, response.getStatus()); - assertEquals("application/json", response.getContentType()); - var outputStream = new ByteArrayOutputStream(); - Exceptions.uncheck(() -> response.render(outputStream)); - return outputStream.toString(StandardCharsets.UTF_8); - } - - private InputStream makeInputStream(String content) { - return new ByteArrayInputStream(Utf8.toBytes(content)); - } -} diff --git a/flags/src/test/java/com/yahoo/vespa/flags/persistence/FlagsDbTest.java b/flags/src/test/java/com/yahoo/vespa/flags/persistence/FlagsDbTest.java deleted file mode 100644 index 5102305af90..00000000000 --- a/flags/src/test/java/com/yahoo/vespa/flags/persistence/FlagsDbTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// 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.persistence; - -import com.yahoo.vespa.curator.mock.MockCurator; -import com.yahoo.vespa.flags.FetchVector; -import com.yahoo.vespa.flags.FlagId; -import com.yahoo.vespa.flags.JsonNodeRawFlag; -import com.yahoo.vespa.flags.json.Condition; -import com.yahoo.vespa.flags.json.FlagData; -import com.yahoo.vespa.flags.json.Rule; -import org.junit.Test; - -import java.util.Map; -import java.util.Optional; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * @author hakonhall - */ -public class FlagsDbTest { - @Test - public void test() { - MockCurator curator = new MockCurator(); - FlagsDb db = new FlagsDb(curator); - - Condition condition1 = new Condition(Condition.Type.WHITELIST, FetchVector.Dimension.HOSTNAME, "host1"); - Rule rule1 = new Rule(Optional.of(JsonNodeRawFlag.fromJson("13")), condition1); - FlagId flagId = new FlagId("id"); - FlagData data = new FlagData(flagId, new FetchVector().with(FetchVector.Dimension.ZONE_ID, "zone-a"), rule1); - db.setValue(flagId, data); - - Optional<FlagData> dataCopy = db.getValue(flagId); - assertTrue(dataCopy.isPresent()); - - assertEquals("{\"id\":\"id\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"hostname\"," + - "\"values\":[\"host1\"]}],\"value\":13}],\"attributes\":{\"zone\":\"zone-a\"}}", - dataCopy.get().serializeToJson()); - - FlagId flagId2 = new FlagId("id2"); - FlagData data2 = new FlagData(flagId2, new FetchVector().with(FetchVector.Dimension.ZONE_ID, "zone-a"), rule1); - db.setValue(flagId2, data2); - Map<FlagId, FlagData> flags = db.getAllFlags(); - assertThat(flags.size(), equalTo(2)); - assertThat(flags.get(flagId), notNullValue()); - assertThat(flags.get(flagId2), notNullValue()); - - db.removeValue(flagId2); - assertFalse(db.getValue(flagId2).isPresent()); - } -} |