summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2019-07-16 10:30:03 +0200
committerGitHub <noreply@github.com>2019-07-16 10:30:03 +0200
commited66c3239ed498be526b1e7543d2d768beca3910 (patch)
treefe3e03ce911010653b974ca5316b96777b7aca50
parent5d95bfb487229d05d67554a6890e056db0db001f (diff)
parent67e2766163bf1dba13c8cce56e2bbb3af7198ea3 (diff)
Merge pull request #10041 from vespa-engine/mpolden/move-flags-api
Audit log /flags/v1 on controller
-rw-r--r--configserver-flags/pom.xml6
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlag.java (renamed from configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlag.java)6
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlags.java (renamed from configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java)8
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/ErrorResponse.java66
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagDataListResponse.java (renamed from configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java)5
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagDataResponse.java (renamed from configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataResponse.java)5
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java (renamed from configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java)68
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/OKResponse.java (renamed from configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/OKResponse.java)5
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/SlimeJsonResponse.java38
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/V1Response.java46
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/package-info.java8
-rw-r--r--configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java2
-rw-r--r--configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/ConfigServerFlagSourceTest.java2
-rw-r--r--configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java (renamed from configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java)18
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/V1Response.java34
-rw-r--r--configserver/src/main/resources/configserver-app/services.xml4
-rw-r--r--controller-server/pom.xml7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java30
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java57
21 files changed, 331 insertions, 91 deletions
diff --git a/configserver-flags/pom.xml b/configserver-flags/pom.xml
index 8c96512c4c0..11ef9b6c950 100644
--- a/configserver-flags/pom.xml
+++ b/configserver-flags/pom.xml
@@ -20,6 +20,12 @@
<!-- provided -->
<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>
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlag.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlag.java
index 92397fc84a7..c706a2b1e51 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlag.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/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.configserver.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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/DefinedFlags.java
index 9604c51ee4b..26d590593c0 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/DefinedFlags.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/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.configserver.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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/ErrorResponse.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/ErrorResponse.java
new file mode 100644
index 00000000000..b9e5c75fe22
--- /dev/null
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/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.configserver.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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagDataListResponse.java
index b33fc7c2b04..efc78cb7930 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/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.configserver.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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagDataResponse.java
index 054b218ff2d..8ff4085df8d 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataResponse.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/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.configserver.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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java
index 00f3d457d3d..40bb69111e0 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java
@@ -1,14 +1,12 @@
// 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.configserver.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;
@@ -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,19 +81,24 @@ 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) {
return new FlagDataListResponse(flagsV1Uri(request), flagsDb.getAllFlags(),
- Objects.equals(request.getProperty("recursive"), "true"));
+ Objects.equals(request.getProperty("recursive"), "true"));
}
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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/OKResponse.java
index 87c02ae56f1..f41940f692b 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/OKResponse.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/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.configserver.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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/SlimeJsonResponse.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/SlimeJsonResponse.java
new file mode 100644
index 00000000000..e5568514894
--- /dev/null
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/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.configserver.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/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/V1Response.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/V1Response.java
new file mode 100644
index 00000000000..ac1e9514700
--- /dev/null
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/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.configserver.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/http/package-info.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/package-info.java
new file mode 100644
index 00000000000..87b63114b73
--- /dev/null
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/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.configserver.flags.http;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java
index 97e66d95715..d6f078326a3 100644
--- a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java
+++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/package-info.java
@@ -3,5 +3,3 @@
package com.yahoo.vespa.configserver.flags;
import com.yahoo.osgi.annotation.ExportPackage;
-
-/** The node repository controls and allocates the nodes available in a hosted Vespa zone */
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..c46677bfc10 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
@@ -103,4 +103,4 @@ public class ConfigServerFlagSourceTest {
assertFalse(rawFlag2.isPresent());
verify(flagsDb, times(1)).getValue(flagId2);
}
-} \ No newline at end of file
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java
index 5ae6ce9820b..cbd37c8a5cf 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java
+++ b/configserver-flags/src/test/java/com/yahoo/vespa/configserver/flags/http/FlagsHandlerTest.java
@@ -1,25 +1,27 @@
// 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.configserver.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.FlagsDb;
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.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 +39,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 FlagsDbImpl(new MockCurator());
private final FlagsHandler handler = new FlagsHandler(FlagsHandler.testOnlyContext(), flagsDb);
@Test
@@ -161,7 +163,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 +193,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/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..e57024413c9 100644
--- a/configserver/src/main/resources/configserver-app/services.xml
+++ b/configserver/src/main/resources/configserver-app/services.xml
@@ -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/controller-server/pom.xml b/controller-server/pom.xml
index c6c6acafe15..ae756eae1fb 100644
--- a/controller-server/pom.xml
+++ b/controller-server/pom.xml
@@ -107,6 +107,13 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>configserver-flags</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
<!-- compile -->
<dependency>
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java
new file mode 100644
index 00000000000..31058a71816
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java
@@ -0,0 +1,30 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.restapi.flags;
+
+import com.yahoo.container.jdisc.HttpRequest;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.vespa.configserver.flags.FlagsDb;
+import com.yahoo.vespa.configserver.flags.http.FlagsHandler;
+import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.auditlog.AuditLogger;
+
+/**
+ * An extension of {@link FlagsHandler} which logs requests to the audit log.
+ *
+ * @author mpolden
+ */
+public class AuditedFlagsHandler extends FlagsHandler {
+
+ private final AuditLogger auditLogger;
+
+ public AuditedFlagsHandler(Context context, Controller controller, FlagsDb flagsDb) {
+ super(context, flagsDb);
+ auditLogger = controller.auditLogger();
+ }
+
+ @Override
+ public HttpResponse handle(HttpRequest request) {
+ return super.handle(auditLogger.log(request));
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
index c7be543dd00..b32cbbcb926 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
@@ -7,7 +7,6 @@ import com.yahoo.application.container.handler.Response;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.Version;
import com.yahoo.config.provision.zone.ZoneApi;
-import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.http.filter.FilterChainRepository;
import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
index 11aa132b478..83a43287880 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
@@ -60,6 +60,8 @@ public class ControllerContainerTest {
" </rotations>\n" +
" </config>\n" +
" <component id='com.yahoo.vespa.flags.InMemoryFlagSource'/>\n" +
+ " <component id='com.yahoo.vespa.configserver.flags.db.FlagsDbImpl'/>\n" +
+ " <component id='com.yahoo.vespa.curator.mock.MockCurator'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService'/>\n" +
@@ -112,6 +114,10 @@ public class ControllerContainerTest {
" <binding>http://*/zone/v2</binding>\n" +
" <binding>http://*/zone/v2/*</binding>\n" +
" </handler>\n" +
+ " <handler id='com.yahoo.vespa.hosted.controller.restapi.flags.AuditedFlagsHandler'>\n" +
+ " <binding>http://*/flags/v1</binding>\n" +
+ " <binding>http://*/flags/v1/*</binding>\n" +
+ " </handler>\n" +
variablePartXml() +
"</container>";
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java
new file mode 100644
index 00000000000..b4ef98cc7f6
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java
@@ -0,0 +1,57 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.restapi.flags;
+
+import com.yahoo.application.container.handler.Request;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzUser;
+import com.yahoo.vespa.hosted.controller.auditlog.AuditLog;
+import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
+import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author mpolden
+ */
+public class AuditedFlagsApiTest extends ControllerContainerTest {
+
+ private static final String responses = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/responses/";
+ private static final AthenzIdentity operator = AthenzUser.fromUserId("operatorUser");
+
+ private ContainerControllerTester tester;
+
+ @Before
+ public void before() {
+ addUserToHostedOperatorRole(operator);
+ tester = new ContainerControllerTester(container, responses);
+ }
+
+ @Test
+ public void test_audit_logging() {
+ var body = "{\n" +
+ " \"id\": \"id1\",\n" +
+ " \"rules\": [\n" +
+ " {\n" +
+ " \"value\": true\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+ assertResponse(new Request("http://localhost:8080/flags/v1/data/id1?force=true", body, Request.Method.PUT),
+ "", 200);
+ var log = tester.controller().auditLogger().readLog();
+ assertEquals(1, log.entries().size());
+ var entry = log.entries().get(0);
+ assertEquals(operator.getFullName(), entry.principal());
+ assertEquals(AuditLog.Entry.Method.PUT, entry.method());
+ assertEquals("/flags/v1/data/id1?force=true", entry.resource());
+ assertEquals(body, log.entries().get(0).data().get());
+ }
+
+ private void assertResponse(Request request, String body, int statusCode) {
+ addIdentityToRequest(request, operator);
+ tester.assertResponse(request, body, statusCode);
+ }
+
+}