diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-07-11 14:13:23 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2019-07-12 09:37:46 +0200 |
commit | b81b21546cdff92d360cbdf7dda27e6ed7bc7170 (patch) | |
tree | 1eead4f28363772088c56fbb7a89c76360c6fc7c | |
parent | 1c79079945c56fa91de8427fbc8f2170eec9ed8c (diff) |
Decouple flags REST API from config server
20 files changed, 261 insertions, 136 deletions
diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java index b1ffc05e70c..90709951dec 100644 --- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSource.java @@ -5,6 +5,7 @@ import com.google.inject.Inject; import com.yahoo.vespa.configserver.flags.db.BootstrapFlagSource; import com.yahoo.vespa.configserver.flags.db.ZooKeeperFlagSource; import com.yahoo.vespa.flags.OrderedFlagSource; +import com.yahoo.vespa.flags.persistence.FlagsDb; import java.nio.file.FileSystem; import java.nio.file.FileSystems; diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/FlagsDb.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/FlagsDb.java deleted file mode 100644 index 2c29ae0b818..00000000000 --- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/FlagsDb.java +++ /dev/null @@ -1,25 +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.configserver.flags; - -import com.yahoo.vespa.flags.FlagId; -import com.yahoo.vespa.flags.json.FlagData; - -import java.util.Map; -import java.util.Optional; - -/** - * @author hakonhall - */ -public interface FlagsDb { - /** Get the String value of the flag. */ - Optional<FlagData> getValue(FlagId flagId); - - /** Set the String value of the flag. */ - void setValue(FlagId flagId, FlagData data); - - /** Remove the flag value if it exists. */ - void removeValue(FlagId flagId); - - /** Get all flags that have been set. */ - Map<FlagId, FlagData> getAllFlags(); -} diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java index 4a9d604b4bd..e6ab3f5b387 100644 --- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/ZooKeeperFlagSource.java @@ -1,7 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.configserver.flags.db; -import com.yahoo.vespa.configserver.flags.FlagsDb; +import com.yahoo.vespa.flags.persistence.FlagsDb; import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.FlagId; import com.yahoo.vespa.flags.FlagSource; diff --git a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java index d0d1d61628c..b2f891326fc 100644 --- a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java +++ b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java @@ -7,6 +7,7 @@ import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.FlagId; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.RawFlag; +import com.yahoo.vespa.flags.persistence.FlagsDb; import com.yahoo.vespa.test.file.TestFileSystem; import org.junit.After; import org.junit.Before; @@ -103,4 +104,4 @@ public class ConfigServerFlagSourceTest { assertFalse(rawFlag2.isPresent()); verify(flagsDb, times(1)).getValue(flagId2); } -}
\ No newline at end of file +} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/V1Response.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/V1Response.java deleted file mode 100644 index 3594c801ca8..00000000000 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/V1Response.java +++ /dev/null @@ -1,34 +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.config.server.http.flags; - -import com.yahoo.jdisc.Response; -import com.yahoo.slime.Cursor; -import com.yahoo.slime.Slime; -import com.yahoo.text.Utf8; -import com.yahoo.vespa.config.SlimeUtils; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; -import com.yahoo.vespa.config.server.http.StaticResponse; - -import java.util.Arrays; -import java.util.List; - -import static com.yahoo.yolean.Exceptions.uncheck; - -/** - * @author hakonhall - */ -public class V1Response extends StaticResponse { - public V1Response(String flagsV1Uri, String... names) { - super(Response.Status.OK, HttpConfigResponse.JSON_CONTENT_TYPE, generateBody(flagsV1Uri, Arrays.asList(names))); - } - - private static String 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 Utf8.toString(uncheck(() -> SlimeUtils.toJsonBytes(slime))); - } -} diff --git a/configserver/src/main/resources/configserver-app/services.xml b/configserver/src/main/resources/configserver-app/services.xml index cfacd6ff8c9..b429e220c33 100644 --- a/configserver/src/main/resources/configserver-app/services.xml +++ b/configserver/src/main/resources/configserver-app/services.xml @@ -54,7 +54,7 @@ <preprocess:include file='model-integration.xml' required='true' /> <component id="com.yahoo.vespa.configserver.flags.ConfigServerFlagSource" bundle="configserver-flags"/> - <component id="com.yahoo.vespa.configserver.flags.db.FlagsDbImpl" bundle="configserver-flags"/> + <component id="com.yahoo.vespa.flags.persistence.FlagsDb" bundle="flags"/> <preprocess:include file='metrics-packets.xml' required='false' /> <preprocess:include file='container.xml' required='false' /> @@ -92,10 +92,6 @@ <handler id='com.yahoo.vespa.config.server.http.status.StatusHandler' bundle='configserver'> <binding>http://*/status</binding> </handler> - <handler id='com.yahoo.vespa.config.server.http.flags.FlagsHandler' bundle='configserver'> - <binding>http://*/flags/v1</binding> - <binding>http://*/flags/v1/*</binding> - </handler> <handler id='com.yahoo.vespa.config.server.http.v2.TenantHandler' bundle='configserver'> <binding>http://*/application/v2/tenant/</binding> <binding>http://*/application/v2/tenant/*</binding> diff --git a/flags/pom.xml b/flags/pom.xml index c1e9eca20ab..7ef082cc1bc 100644 --- a/flags/pom.xml +++ b/flags/pom.xml @@ -59,7 +59,18 @@ <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/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlag.java index 92397fc84a7..8234e9df725 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlag.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlag.java @@ -1,12 +1,11 @@ // Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http.flags; +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.config.server.http.HttpConfigResponse; import com.yahoo.vespa.flags.FlagDefinition; import com.yahoo.vespa.flags.json.DimensionHelper; @@ -42,6 +41,7 @@ public class DefinedFlag extends HttpResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + return "application/json"; } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlags.java index 9604c51ee4b..e1db7dda6e0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/DefinedFlags.java @@ -1,11 +1,10 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http.flags; +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.config.server.http.HttpConfigResponse; import com.yahoo.vespa.flags.FlagDefinition; import java.io.IOException; @@ -18,8 +17,7 @@ import java.util.List; */ public class DefinedFlags extends HttpResponse { private static ObjectMapper mapper = new ObjectMapper(); - private static final Comparator<FlagDefinition> sortByFlagId = - (left, right) -> left.getUnboundFlag().id().compareTo(right.getUnboundFlag().id()); + private static final Comparator<FlagDefinition> sortByFlagId = Comparator.comparing(flagDefinition -> flagDefinition.getUnboundFlag().id()); private final List<FlagDefinition> flags; @@ -40,6 +38,6 @@ public class DefinedFlags extends HttpResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + 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 new file mode 100644 index 00000000000..969903093a4 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/ErrorResponse.java @@ -0,0 +1,66 @@ +// 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/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataListResponse.java index b33fc7c2b04..5af97007997 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataListResponse.java @@ -1,12 +1,11 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http.flags; +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.config.server.http.HttpConfigResponse; import com.yahoo.vespa.flags.FlagId; import com.yahoo.vespa.flags.json.FlagData; import com.yahoo.vespa.flags.json.wire.WireFlagDataList; @@ -54,6 +53,6 @@ public class FlagDataListResponse extends HttpResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + return "application/json"; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataResponse.java b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataResponse.java index 054b218ff2d..f6e81e030c7 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataResponse.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagDataResponse.java @@ -1,9 +1,8 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http.flags; +package com.yahoo.vespa.flags.http; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.jdisc.Response; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; import com.yahoo.vespa.flags.json.FlagData; import java.io.OutputStream; @@ -26,6 +25,6 @@ public class FlagDataResponse extends HttpResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + return "application/json"; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagsHandler.java index 00f3d457d3d..76f74cbe931 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/FlagsHandler.java @@ -1,19 +1,17 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http.flags; +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.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.vespa.flags.persistence.FlagsDb; import com.yahoo.yolean.Exceptions; import java.io.UncheckedIOException; @@ -25,7 +23,8 @@ import java.util.Objects; * * @author hakonhall */ -public class FlagsHandler extends HttpHandler { +public class FlagsHandler extends LoggingRequestHandler { + private final FlagsDb flagsDb; @Inject @@ -35,28 +34,44 @@ public class FlagsHandler extends HttpHandler { } @Override - protected HttpResponse handleGET(HttpRequest request) { + 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)); - throw new NotFoundException("Nothing at path '" + path + "'"); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); } - @Override - protected HttpResponse handlePUT(HttpRequest request) { + private HttpResponse handlePUT(HttpRequest request) { Path path = new Path(request.getUri()); if (path.matches("/flags/v1/data/{flagId}")) return putFlagData(request, findFlagId(request, path)); - throw new NotFoundException("Nothing at path '" + path + "'"); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); } - @Override - protected HttpResponse handleDELETE(HttpRequest request) { + private HttpResponse handleDELETE(HttpRequest request) { Path path = new Path(request.getUri()); if (path.matches("/flags/v1/data/{flagId}")) return deleteFlagData(findFlagId(request, path)); - throw new NotFoundException("Nothing at path '" + path + "'"); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); } private String flagsV1Uri(HttpRequest request) { @@ -66,9 +81,11 @@ public class FlagsHandler extends HttpHandler { } private HttpResponse getDefinedFlag(FlagId flagId) { - FlagDefinition definition = Flags.getFlag(flagId) - .orElseThrow(() -> new NotFoundException("Flag " + flagId + " not defined")); - return new DefinedFlag(definition); + 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) { @@ -77,8 +94,11 @@ public class FlagsHandler extends HttpHandler { } private HttpResponse getFlagData(FlagId flagId) { - FlagData data = flagsDb.getValue(flagId).orElseThrow(() -> new NotFoundException("Flag " + flagId + " not set")); - return new FlagDataResponse(data); + 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) { @@ -86,7 +106,7 @@ public class FlagsHandler extends HttpHandler { try { data = FlagData.deserialize(request.getData()); } catch (UncheckedIOException e) { - return HttpErrorResponse.badRequest("Failed to deserialize request data: " + Exceptions.toMessageString(e)); + return ErrorResponse.badRequest("Failed to deserialize request data: " + Exceptions.toMessageString(e)); } if (!isForce(request)) { @@ -105,16 +125,14 @@ public class FlagsHandler extends HttpHandler { private FlagId findFlagId(HttpRequest request, Path path) { FlagId flagId = new FlagId(path.get("flagId")); - - if (!isForce(request)) { - Flags.getFlag(flagId).orElseThrow(() -> - new NotFoundException("There is no flag '" + flagId + "' (use ?force=true to override)")); + 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/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/OKResponse.java b/flags/src/main/java/com/yahoo/vespa/flags/http/OKResponse.java index 87c02ae56f1..d094e2d5734 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/OKResponse.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/OKResponse.java @@ -1,9 +1,8 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http.flags; +package com.yahoo.vespa.flags.http; import com.yahoo.container.jdisc.EmptyResponse; import com.yahoo.jdisc.Response; -import com.yahoo.vespa.config.server.http.HttpConfigResponse; /** * @author hakonhall @@ -15,6 +14,6 @@ public class OKResponse extends EmptyResponse { @Override public String getContentType() { - return HttpConfigResponse.JSON_CONTENT_TYPE; + 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 new file mode 100644 index 00000000000..dd71795ae43 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/SlimeJsonResponse.java @@ -0,0 +1,38 @@ +// 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 new file mode 100644 index 00000000000..e8ff0bd99a4 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/http/V1Response.java @@ -0,0 +1,46 @@ +// 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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImpl.java b/flags/src/main/java/com/yahoo/vespa/flags/persistence/FlagsDb.java index 5058358ba03..2ed762f2895 100644 --- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImpl.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/persistence/FlagsDb.java @@ -1,9 +1,8 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.configserver.flags.db; +package com.yahoo.vespa.flags.persistence; import com.google.inject.Inject; import com.yahoo.path.Path; -import com.yahoo.vespa.configserver.flags.FlagsDb; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.flags.FlagId; import com.yahoo.vespa.flags.json.FlagData; @@ -20,14 +19,15 @@ import java.util.stream.Collectors; /** * @author hakonhall */ -public class FlagsDbImpl implements FlagsDb { +public class FlagsDb { + private static final Path ROOT_PATH = Path.fromString("/flags/v1"); private final Curator curator; private final Curator.DirectoryCache cache; @Inject - public FlagsDbImpl(Curator curator) { + public FlagsDb(Curator curator) { this.curator = curator; curator.create(ROOT_PATH); ExecutorService executorService = Executors.newFixedThreadPool(1); @@ -35,28 +35,28 @@ public class FlagsDbImpl implements FlagsDb { cache.start(); } - @Override + /** 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); + .map(ChildData::getData) + .map(FlagData::deserializeUtf8Json); } - @Override + /** Set the String value of the flag. */ public void setValue(FlagId flagId, FlagData data) { curator.set(getZkPathFor(flagId), data.serializeToUtf8Json()); } - @Override + /** 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())); + .map(ChildData::getData) + .map(FlagData::deserializeUtf8Json) + .collect(Collectors.toMap(FlagData::id, Function.identity())); } - @Override + /** Remove the flag value if it exists. */ public void removeValue(FlagId flagId) { curator.delete(getZkPathFor(flagId)); } @@ -64,4 +64,5 @@ public class FlagsDbImpl implements FlagsDb { 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 new file mode 100644 index 00000000000..d4753ed1756 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/persistence/package-info.java @@ -0,0 +1,8 @@ +// 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/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java b/flags/src/test/java/com/yahoo/vespa/flags/http/FlagsHandlerTest.java index 5ae6ce9820b..8ae1008ba22 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java +++ b/flags/src/test/java/com/yahoo/vespa/flags/http/FlagsHandlerTest.java @@ -1,25 +1,26 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http.flags; +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.config.server.http.SessionHandlerTest; -import com.yahoo.vespa.configserver.flags.db.FlagsDbImpl; 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 com.yahoo.yolean.Exceptions.uncheck; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -37,7 +38,7 @@ public class FlagsHandlerTest { private static final String FLAGS_V1_URL = "https://foo.com:4443/flags/v1"; - private final FlagsDbImpl flagsDb = new FlagsDbImpl(new MockCurator()); + private final FlagsDb flagsDb = new FlagsDb(new MockCurator()); private final FlagsHandler handler = new FlagsHandler(FlagsHandler.testOnlyContext(), flagsDb); @Test @@ -161,7 +162,7 @@ public class FlagsHandlerTest { @Test public void testForcing() { - assertThat(handle(Method.PUT, "/data/" + new FlagId("undef"), "", 404), + 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), @@ -191,10 +192,12 @@ public class FlagsHandlerTest { HttpResponse response = handler.handle(request); assertEquals(expectedStatus, response.getStatus()); assertEquals("application/json", response.getContentType()); - return uncheck(() -> SessionHandlerTest.getRenderedString(response)); + 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)); } -}
\ No newline at end of file +} diff --git a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImplTest.java b/flags/src/test/java/com/yahoo/vespa/flags/persistence/FlagsDbTest.java index ecc9bacb081..5102305af90 100644 --- a/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/db/FlagsDbImplTest.java +++ b/flags/src/test/java/com/yahoo/vespa/flags/persistence/FlagsDbTest.java @@ -1,5 +1,5 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.configserver.flags.db; +package com.yahoo.vespa.flags.persistence; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.flags.FetchVector; @@ -23,11 +23,11 @@ import static org.junit.Assert.assertTrue; /** * @author hakonhall */ -public class FlagsDbImplTest { +public class FlagsDbTest { @Test public void test() { MockCurator curator = new MockCurator(); - FlagsDbImpl db = new FlagsDbImpl(curator); + 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); @@ -39,8 +39,8 @@ public class FlagsDbImplTest { assertTrue(dataCopy.isPresent()); assertEquals("{\"id\":\"id\",\"rules\":[{\"conditions\":[{\"type\":\"whitelist\",\"dimension\":\"hostname\"," + - "\"values\":[\"host1\"]}],\"value\":13}],\"attributes\":{\"zone\":\"zone-a\"}}", - dataCopy.get().serializeToJson()); + "\"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); @@ -53,4 +53,4 @@ public class FlagsDbImplTest { db.removeValue(flagId2); assertFalse(db.getValue(flagId2).isPresent()); } -}
\ No newline at end of file +} |