diff options
17 files changed, 419 insertions, 18 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java index ae72660ce9f..b33fc7c2b04 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagDataListResponse.java @@ -9,6 +9,7 @@ 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; import java.io.OutputStream; import java.util.Map; @@ -23,31 +24,32 @@ public class FlagDataListResponse extends HttpResponse { private static ObjectMapper mapper = new ObjectMapper(); private final String flagsV1Uri; - private final Map<FlagId, FlagData> flags; + 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 = flags; + this.flags = new TreeMap<>(flags); this.recursive = recursive; } @Override public void render(OutputStream outputStream) { - ObjectNode rootNode = mapper.createObjectNode(); - ArrayNode flagsArray = rootNode.putArray("flags"); - // Order flags by ID - new TreeMap<>(this.flags).forEach((flagId, flagData) -> { - if (recursive) { - flagsArray.add(flagData.toJsonNode()); - } else { + 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)); + }); + uncheck(() -> mapper.writeValue(outputStream, rootNode)); + } } @Override diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java index e5efc74fb75..1b1595eb897 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/flags/FlagsHandler.java @@ -64,7 +64,8 @@ public class FlagsHandler extends HttpHandler { private String flagsV1Uri(HttpRequest request) { URI uri = request.getUri(); - return uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort() + "/flags/v1"; + String port = uri.getPort() < 0 ? "" : ":" + uri.getPort(); + return uri.getScheme() + "://" + uri.getHost() + port + "/flags/v1"; } private HttpResponse getFlagDataList(HttpRequest request) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java index 71caed77dbe..11958454a70 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/flags/FlagsHandlerTest.java @@ -89,6 +89,10 @@ public class FlagsHandlerTest { 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" + @@ -175,7 +179,11 @@ public class FlagsHandlerTest { } private String handle(Method method, String pathSuffix, String requestBody, int expectedStatus) { - String uri = FLAGS_V1_URL + pathSuffix; + 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()); diff --git a/flags/pom.xml b/flags/pom.xml index 99fde3faebb..fc38676ff20 100644 --- a/flags/pom.xml +++ b/flags/pom.xml @@ -38,6 +38,12 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>vespalog</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <scope>provided</scope> diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java b/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java index f4e23144449..1a31ecd713e 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java @@ -1,7 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.flags; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Inject; import com.yahoo.vespa.flags.json.FlagData; import com.yahoo.vespa.flags.json.Rule; @@ -21,8 +20,6 @@ import java.util.Optional; * @author hakonhall */ public class FileFlagSource implements FlagSource { - private static final ObjectMapper mapper = new ObjectMapper(); - static final String FLAGS_DIRECTORY = "/etc/vespa/flags"; private final Path flagsDirectory; diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java b/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java index da8e6b29cab..182ab85858c 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java @@ -4,8 +4,11 @@ package com.yahoo.vespa.flags; import java.util.Optional; /** + * A source of raw flag values that can be converted to typed flag values elsewhere. + * * @author hakonhall */ public interface FlagSource { + /** Get raw flag for the given vector (specifying hostname, application id, etc). */ Optional<RawFlag> fetch(FlagId id, FetchVector vector); } diff --git a/flags/src/main/java/com/yahoo/vespa/flags/file/FlagDirectory.java b/flags/src/main/java/com/yahoo/vespa/flags/file/FlagDirectory.java new file mode 100644 index 00000000000..7dd61a18ad1 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/file/FlagDirectory.java @@ -0,0 +1,156 @@ +// 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.file; + +import com.yahoo.log.LogLevel; +import com.yahoo.vespa.defaults.Defaults; +import com.yahoo.vespa.flags.FlagId; +import com.yahoo.vespa.flags.json.FlagData; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.NotDirectoryException; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * Java API for a local file-based flag repository. + * + * @author hakonhall + */ +public class FlagDirectory { + private static final Logger logger = Logger.getLogger(FlagDirectory.class.getName()); + + private final Path flagDirectory; + + public FlagDirectory() { + this(FileSystems.getDefault()); + } + + FlagDirectory(FileSystem fileSystem) { + this(fileSystem.getPath(Defaults.getDefaults().vespaHome() + "/var/vespa/flags")); + } + + public FlagDirectory(Path flagDirectory) { + this.flagDirectory = flagDirectory; + } + + public Path getPath() { + return flagDirectory; + } + + public Map<FlagId, FlagData> read() { + return getAllRegularFilesStream() + .map(path -> { + FlagId flagId = new FlagId(getFilenameOf(path)); + Optional<FlagData> flagData = readFlagData(flagId); + if (!flagData.isPresent()) return null; + if (!Objects.equals(flagData.get().id(), flagId)) { + logger.log(LogLevel.WARNING, "Flag file " + path + " contains conflicting id " + + flagData.get().id() + ", ignoring flag"); + return null; + } + return flagData.get(); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(FlagData::id, Function.identity())); + } + + public Optional<FlagData> readFlagData(FlagId flagId) { + return readUtf8File(getPathFor(flagId)).map(FlagData::deserialize); + } + + /** + * Modify the flag directory as necessary, such that a later {@link #read()} will return {@code flagData}. + * + * @return true if any modifications were done. + */ + public boolean sync(Map<FlagId, FlagData> flagData) { + boolean modified = false; + + Set<Path> pathsToDelete = getAllRegularFilesStream().collect(Collectors.toCollection(HashSet::new)); + + uncheck(() -> Files.createDirectories(flagDirectory)); + for (Map.Entry<FlagId, FlagData> entry : flagData.entrySet()) { + FlagId flagId = entry.getKey(); + FlagData data = entry.getValue(); + Path path = getPathFor(flagId); + + pathsToDelete.remove(path); + + String serializedData = data.serializeToJson(); + Optional<String> fileContent = readUtf8File(path); + if (fileContent.isPresent()) { + if (!Objects.equals(fileContent.get(), serializedData)) { + logger.log(LogLevel.INFO, "Updating flag " + flagId + " from " + fileContent.get() + + " to " + serializedData); + writeUtf8File(path, serializedData); + modified = true; + } + } else { + logger.log(LogLevel.INFO, "New flag " + flagId + ": " + serializedData); + writeUtf8File(path, serializedData); + modified = true; + } + } + + for (Path path : pathsToDelete) { + logger.log(LogLevel.INFO, "Removing flag file " + path); + uncheck(() -> Files.deleteIfExists(path)); + modified = true; + } + + return modified; + } + + private Stream<Path> getAllRegularFilesStream() { + try { + return Files.list(flagDirectory).filter(Files::isRegularFile); + } catch (NotDirectoryException | NoSuchFileException e) { + return Stream.empty(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static String getFilenameOf(Path path) { + return path.getName(path.getNameCount() - 1).toString(); + } + + private Path getPathFor(FlagId flagId) { + return flagDirectory.resolve(flagId.toString()); + } + + private Optional<String> readUtf8File(Path path) { + try { + return Optional.of(new String(Files.readAllBytes(path), StandardCharsets.UTF_8)); + } catch (NoSuchFileException e) { + return Optional.empty(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void writeUtf8File(Path path, String content) { + uncheck(() -> Files.write(path, content.getBytes(StandardCharsets.UTF_8))); + } + + @Override + public String toString() { + return "FlagDirectory{" + flagDirectory + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/file/package-info.java b/flags/src/main/java/com/yahoo/vespa/flags/file/package-info.java new file mode 100644 index 00000000000..27ad44f938e --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/file/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.vespa.flags.file; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireFlagDataList.java b/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireFlagDataList.java new file mode 100644 index 00000000000..09a7f635dd4 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/json/wire/WireFlagDataList.java @@ -0,0 +1,29 @@ +// 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.json.wire; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * @author hakonhall + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class WireFlagDataList { + @JsonProperty("flags") + public List<WireFlagData> flags = new ArrayList<>(); + + private static final ObjectMapper mapper = new ObjectMapper(); + + public void serializeToOutputStream(OutputStream outputStream) { + uncheck(() -> mapper.writeValue(outputStream, this)); + } +} diff --git a/flags/src/test/java/com/yahoo/vespa/flags/file/FlagDirectoryTest.java b/flags/src/test/java/com/yahoo/vespa/flags/file/FlagDirectoryTest.java new file mode 100644 index 00000000000..fec98fab164 --- /dev/null +++ b/flags/src/test/java/com/yahoo/vespa/flags/file/FlagDirectoryTest.java @@ -0,0 +1,86 @@ +// 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.file; + +import com.yahoo.vespa.flags.FetchVector; +import com.yahoo.vespa.flags.FlagId; +import com.yahoo.vespa.flags.json.FlagData; +import com.yahoo.vespa.test.file.TestFileSystem; +import org.hamcrest.collection.IsMapContaining; +import org.hamcrest.collection.IsMapWithSize; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; + +import static com.yahoo.yolean.Exceptions.uncheck; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +/** + * @author hakonhall + */ +public class FlagDirectoryTest { + private final FileSystem fileSystem = TestFileSystem.create(); + private final FlagDirectory flagDirectory = new FlagDirectory(fileSystem); + + @Test + public void testReadingOnly() { + Map<FlagId, FlagData> data = flagDirectory.read(); + assertThat(data, IsMapWithSize.anEmptyMap()); + + FlagId id1 = new FlagId("id1"); + String json1 = "{\"id\":\"id1\"}"; + writeUtf8FlagFile(id1.toString(), json1); + data = flagDirectory.read(); + assertThat(data, IsMapWithSize.aMapWithSize(1)); + assertThat(data, IsMapContaining.hasKey(id1)); + assertThat(data.get(id1).id(), equalTo(id1)); + assertThat(data.get(id1).serializeToJson(), equalTo(json1)); + } + + @Test + public void testSync() { + Map<FlagId, FlagData> dataMap = new HashMap<>(); + FlagId id1 = new FlagId("id1"); + FlagData data1 = new FlagData(id1, new FetchVector()); + dataMap.put(id1, data1); + FlagId id2 = new FlagId("id2"); + FlagData data2 = new FlagData(id2, new FetchVector()); + dataMap.put(id2, data2); + + // Non-existing directory => empty map + assertThat(flagDirectory.read(), IsMapWithSize.anEmptyMap()); + + // sync() will create directory with map content + assertThat(flagDirectory.sync(dataMap), equalTo(true)); + Map<FlagId, FlagData> readDataMap = flagDirectory.read(); + assertThat(readDataMap, IsMapWithSize.aMapWithSize(2)); + assertThat(readDataMap, IsMapContaining.hasKey(id1)); + assertThat(readDataMap, IsMapContaining.hasKey(id2)); + + // another sync with the same data is a no-op + assertThat(flagDirectory.sync(dataMap), equalTo(false)); + + // Changing value of id1, removing id2, adding id3 + dataMap.remove(id2); + FlagData newData1 = new FlagData(id1, new FetchVector().with(FetchVector.Dimension.HOSTNAME, "h1")); + dataMap.put(id1, newData1); + FlagId id3 = new FlagId("id3"); + FlagData data3 = new FlagData(id3, new FetchVector()); + dataMap.put(id3, data3); + assertThat(flagDirectory.sync(dataMap), equalTo(true)); + Map<FlagId, FlagData> anotherReadDataMap = flagDirectory.read(); + assertThat(anotherReadDataMap, IsMapWithSize.aMapWithSize(2)); + assertThat(anotherReadDataMap, IsMapContaining.hasKey(id1)); + assertThat(anotherReadDataMap, IsMapContaining.hasKey(id3)); + assertThat(anotherReadDataMap.get(id1).serializeToJson(), equalTo("{\"id\":\"id1\",\"attributes\":{\"hostname\":\"h1\"}}")); + } + + private void writeUtf8FlagFile(String flagIdAkaFilename, String content) { + uncheck(() -> Files.createDirectories(flagDirectory.getPath())); + uncheck(() -> Files.write(flagDirectory.getPath().resolve(flagIdAkaFilename), content.getBytes(StandardCharsets.UTF_8))); + } +}
\ No newline at end of file diff --git a/node-admin/pom.xml b/node-admin/pom.xml index 476902e400c..e1231f2585d 100644 --- a/node-admin/pom.xml +++ b/node-admin/pom.xml @@ -49,6 +49,12 @@ <version>${project.version}</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>flags</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> <!-- Compile --> <dependency> diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java index 4a2496f4d3e..ab899f9f919 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java @@ -1,6 +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.hosted.node.admin.configserver; +import com.yahoo.vespa.hosted.node.admin.configserver.flags.FlagRepository; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; import com.yahoo.vespa.hosted.node.admin.configserver.state.State; @@ -18,7 +19,10 @@ public interface ConfigServerClients { Orchestrator orchestrator(); /** Get handle to the /state/v1 REST API */ - default State state() { throw new UnsupportedOperationException(); } + State state(); + + /** Get handle to the /flags/v1 REST API */ + FlagRepository flagRepository(); void stop(); } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java index 6c982bfa71c..af11c300c2b 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java @@ -1,6 +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.hosted.node.admin.configserver; +import com.yahoo.vespa.hosted.node.admin.configserver.flags.FlagRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.flags.RealFlagRepository; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.RealNodeRepository; import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; @@ -19,6 +21,7 @@ public class RealConfigServerClients implements ConfigServerClients { private final NodeRepository nodeRepository; private final Orchestrator orchestrator; private final State state; + private final RealFlagRepository flagRepository; /** * @param configServerApi the backend API to use - will be closed at {@link #stop()}. @@ -28,6 +31,7 @@ public class RealConfigServerClients implements ConfigServerClients { nodeRepository = new RealNodeRepository(configServerApi); orchestrator = new OrchestratorImpl(configServerApi); state = new StateImpl(configServerApi); + flagRepository = new RealFlagRepository(configServerApi); } @Override @@ -46,6 +50,11 @@ public class RealConfigServerClients implements ConfigServerClients { } @Override + public FlagRepository flagRepository() { + return flagRepository; + } + + @Override public void stop() { configServerApi.close(); } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/FlagRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/FlagRepository.java new file mode 100644 index 00000000000..8407d42131b --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/FlagRepository.java @@ -0,0 +1,15 @@ +// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver.flags; + +import com.yahoo.vespa.flags.FlagId; +import com.yahoo.vespa.flags.json.FlagData; + +import java.util.List; +import java.util.Map; + +/** + * @author hakonhall + */ +public interface FlagRepository { + Map<FlagId, FlagData> getAllFlagData(); +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepository.java new file mode 100644 index 00000000000..28a080251ab --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepository.java @@ -0,0 +1,28 @@ +// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver.flags; + +import com.yahoo.vespa.flags.FlagId; +import com.yahoo.vespa.flags.json.FlagData; +import com.yahoo.vespa.flags.json.wire.WireFlagDataList; +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author hakonhall + */ +public class RealFlagRepository implements FlagRepository { + private final ConfigServerApi configServerApi; + + public RealFlagRepository(ConfigServerApi configServerApi) { + this.configServerApi = configServerApi; + } + + @Override + public Map<FlagId, FlagData> getAllFlagData() { + WireFlagDataList list = configServerApi.get("/flags/v1/data?recursive=true", WireFlagDataList.class); + return list.flags.stream().map(FlagData::fromWire).collect(Collectors.toMap(FlagData::id, Function.identity())); + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/package-info.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/package-info.java new file mode 100644 index 00000000000..b991adfc639 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.vespa.hosted.node.admin.configserver.flags; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepositoryTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepositoryTest.java new file mode 100644 index 00000000000..c9e4e33f8bb --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/flags/RealFlagRepositoryTest.java @@ -0,0 +1,41 @@ +// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver.flags; + +import com.yahoo.vespa.flags.FlagId; +import com.yahoo.vespa.flags.json.FlagData; +import com.yahoo.vespa.flags.json.wire.WireFlagData; +import com.yahoo.vespa.flags.json.wire.WireFlagDataList; +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; +import org.hamcrest.collection.IsMapContaining; +import org.hamcrest.collection.IsMapWithSize; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author hakonhall + */ +public class RealFlagRepositoryTest { + private final ConfigServerApi configServerApi = mock(ConfigServerApi.class); + private final RealFlagRepository repository = new RealFlagRepository(configServerApi); + + @Test + public void test() { + WireFlagDataList list = new WireFlagDataList(); + list.flags = new ArrayList<>(); + list.flags.add(new WireFlagData()); + list.flags.get(0).id = "id1"; + + when(configServerApi.get(any(), eq(WireFlagDataList.class))).thenReturn(list); + Map<FlagId, FlagData> allFlagData = repository.getAllFlagData(); + assertThat(allFlagData, IsMapWithSize.aMapWithSize(1)); + assertThat(allFlagData, IsMapContaining.hasKey(new FlagId("id1"))); + } +}
\ No newline at end of file |