summaryrefslogtreecommitdiffstats
path: root/controller-api
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2019-11-08 14:23:12 +0100
committerGitHub <noreply@github.com>2019-11-08 14:23:12 +0100
commit1ee969c38c200a2faf7f586d0e3077655dffb79e (patch)
treecbfd7a28962df812fd64ee15f4b5442730dec067 /controller-api
parent1e44092efcff240afc7c57948dd1d4bad28a2a04 (diff)
parent4b632e8a210a6487975ffd9b2959c4f4af673b5d (diff)
Merge pull request #11238 from vespa-engine/bjorncs/system-flags-handler
Bjorncs/system flags handler
Diffstat (limited to 'controller-api')
-rw-r--r--controller-api/pom.xml6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/ConfigServerFlagsTarget.java51
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/ControllerFlagsTarget.java38
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/FlagsTarget.java68
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java138
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/package-info.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/SystemFlagsV1Api.java29
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/WireErrorResponse.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/WireSystemFlagsDeployResult.java32
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/package-info.java8
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java89
-rw-r--r--controller-api/src/test/resources/system-flags/flags/my-test-flag/default.json8
-rw-r--r--controller-api/src/test/resources/system-flags/flags/my-test-flag/main.controller.json8
-rw-r--r--controller-api/src/test/resources/system-flags/flags/my-test-flag/main.json8
-rw-r--r--controller-api/src/test/resources/system-flags/flags/my-test-flag/main.prod.json8
-rw-r--r--controller-api/src/test/resources/system-flags/flags/my-test-flag/main.prod.us-west-1.json8
16 files changed, 525 insertions, 0 deletions
diff --git a/controller-api/pom.xml b/controller-api/pom.xml
index 6b7c01a863c..680ad35cace 100644
--- a/controller-api/pom.xml
+++ b/controller-api/pom.xml
@@ -83,6 +83,12 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
<build>
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/ConfigServerFlagsTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/ConfigServerFlagsTarget.java
new file mode 100644
index 00000000000..0304d1a5949
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/ConfigServerFlagsTarget.java
@@ -0,0 +1,51 @@
+// 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.api.systemflags.v1;
+
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget.defaultFile;
+import static com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget.environmentFile;
+import static com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget.systemFile;
+import static com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget.zoneFile;
+
+/**
+ * @author bjorncs
+ */
+class ConfigServerFlagsTarget implements FlagsTarget {
+ private final SystemName system;
+ private final ZoneId zone;
+ private final URI endpoint;
+ private final AthenzIdentity identity;
+
+ ConfigServerFlagsTarget(SystemName system, ZoneId zone, URI endpoint, AthenzIdentity identity) {
+ this.system = Objects.requireNonNull(system);
+ this.zone = Objects.requireNonNull(zone);
+ this.endpoint = Objects.requireNonNull(endpoint);
+ this.identity = Objects.requireNonNull(identity);
+ }
+
+ @Override public List<String> flagDataFilesPrioritized() { return List.of(zoneFile(system, zone), environmentFile(system, zone.environment()), systemFile(system), defaultFile()); }
+ @Override public URI endpoint() { return endpoint; }
+ @Override public Optional<AthenzIdentity> athenzHttpsIdentity() { return Optional.of(identity); }
+ @Override public String asString() { return String.format("%s.%s", system.value(), zone.value()); }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ConfigServerFlagsTarget that = (ConfigServerFlagsTarget) o;
+ return system == that.system &&
+ Objects.equals(zone, that.zone) &&
+ Objects.equals(endpoint, that.endpoint) &&
+ Objects.equals(identity, that.identity);
+ }
+
+ @Override public int hashCode() { return Objects.hash(system, zone, endpoint, identity); }
+}
+
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/ControllerFlagsTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/ControllerFlagsTarget.java
new file mode 100644
index 00000000000..a22a9cc63de
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/ControllerFlagsTarget.java
@@ -0,0 +1,38 @@
+// 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.api.systemflags.v1;
+
+/**
+ * @author bjorncs
+ */
+
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget.controllerFile;
+import static com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget.defaultFile;
+import static com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget.systemFile;
+
+class ControllerFlagsTarget implements FlagsTarget {
+ private final SystemName system;
+
+ ControllerFlagsTarget(SystemName system) { this.system = Objects.requireNonNull(system); }
+
+ @Override public List<String> flagDataFilesPrioritized() { return List.of(controllerFile(system), systemFile(system), defaultFile()); }
+ @Override public URI endpoint() { return URI.create("https://localhost:4443/"); } // Note: Cannot use VIPs for controllers due to network configuration on AWS
+ @Override public Optional<AthenzIdentity> athenzHttpsIdentity() { return Optional.empty(); }
+ @Override public String asString() { return String.format("%s.controller", system.value()); }
+
+ @Override public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ControllerFlagsTarget that = (ControllerFlagsTarget) o;
+ return system == that.system;
+ }
+
+ @Override public int hashCode() { return Objects.hash(system); }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/FlagsTarget.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/FlagsTarget.java
new file mode 100644
index 00000000000..1b7f84d1ec7
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/FlagsTarget.java
@@ -0,0 +1,68 @@
+// 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.api.systemflags.v1;
+
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneApi;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
+
+import java.net.URI;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Represents either configservers in a zone or controllers in a system.
+ *
+ * Defines the location and precedence of the flags data files for the given target.
+ *
+ * Naming rules for flags data files:
+ * <ul>
+ * <li>zone specific: {@code <system>.<environment>.<region>.json}</li>
+ * <li>controller specific: {@code <system>.controller.json}</li>
+ * <li>environment specific: {@code <system>.<environment>.json}</li>
+ * <li>system specific: {@code <system>.json}</li>
+ * <li>global default: {@code default.json}</li>
+ * </ul>
+ *
+ * @author bjorncs
+ */
+public interface FlagsTarget {
+
+ List<String> flagDataFilesPrioritized();
+ URI endpoint();
+ Optional<AthenzIdentity> athenzHttpsIdentity();
+ String asString();
+
+ static Set<FlagsTarget> getAllTargetsInSystem(ZoneRegistry registry) {
+ SystemName system = registry.system();
+ Set<FlagsTarget> targets = new HashSet<>();
+ for (ZoneApi zone : registry.zones().reachable().zones()) {
+ targets.add(forConfigServer(registry, zone.getId()));
+ }
+ targets.add(forController(system));
+ return targets;
+ }
+
+ static FlagsTarget forController(SystemName systemName) {
+ return new ControllerFlagsTarget(systemName);
+ }
+
+ static FlagsTarget forConfigServer(ZoneRegistry registry, ZoneId zoneId) {
+ return new ConfigServerFlagsTarget(
+ registry.system(), zoneId, registry.getConfigServerVipUri(zoneId), registry.getConfigServerHttpsIdentity(zoneId));
+ }
+
+ static String defaultFile() { return jsonFile("default"); }
+ static String systemFile(SystemName system) { return jsonFile(system.value()); }
+ static String environmentFile(SystemName system, Environment environment) { return jsonFile(system.value() + "." + environment); }
+ static String zoneFile(SystemName system, ZoneId zone) { return jsonFile(system.value() + "." + zone.environment().value() + "." + zone.region().value()); }
+ static String controllerFile(SystemName system) { return jsonFile(system.value() + ".controller"); }
+
+ private static String jsonFile(String nameWithoutExtension) { return nameWithoutExtension + ".json"; }
+}
+
+
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java
new file mode 100644
index 00000000000..ec14dcb7123
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchive.java
@@ -0,0 +1,138 @@
+// 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.api.systemflags.v1;
+
+import com.yahoo.vespa.flags.FlagId;
+import com.yahoo.vespa.flags.json.FlagData;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import static com.yahoo.yolean.Exceptions.uncheck;
+
+/**
+ * Represents a hierarchy of flag data files. See {@link FlagsTarget} for file naming convention.
+ *
+ * The flag files must reside in a 'flags/' root directory containing a directory for each flag name:
+ * {@code ./flags/<flag-id>/*.json}
+ *
+ * @author bjorncs
+ */
+public class SystemFlagsDataArchive {
+
+ private final Map<FlagId, Map<String, FlagData>> files;
+
+ private SystemFlagsDataArchive(Map<FlagId, Map<String, FlagData>> files) {
+ this.files = files;
+ }
+
+ public static SystemFlagsDataArchive fromZip(InputStream rawIn) {
+ Builder builder = new Builder();
+ try (ZipInputStream zipIn = new ZipInputStream(new BufferedInputStream(rawIn))) {
+ ZipEntry entry;
+ while ((entry = zipIn.getNextEntry()) != null) {
+ String name = entry.getName();
+ if (!entry.isDirectory() && name.startsWith("flags/") && name.endsWith(".json")) {
+ Path filePath = Paths.get(name);
+ String filename = filePath.getFileName().toString();
+ FlagData flagData = FlagData.deserializeUtf8Json(zipIn.readAllBytes());
+ verifyFlagDataMatchesDirectoryName(filePath, flagData);
+ builder.addFile(filename, flagData);
+ }
+ }
+ return builder.build();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public static SystemFlagsDataArchive fromDirectory(Path directory) {
+ Path root = directory.toAbsolutePath();
+ try (Stream<Path> directoryStream = Files.walk(root)) {
+ Builder builder = new Builder();
+ directoryStream.forEach(absolutePath -> {
+ Path relativePath = root.relativize(absolutePath);
+ if (!Files.isDirectory(absolutePath) && relativePath.startsWith("flags")) {
+ String filename = relativePath.getFileName().toString();
+ if (filename.endsWith(".json")) {
+ FlagData flagData = FlagData.deserializeUtf8Json(uncheck(() -> Files.readAllBytes(absolutePath)));
+ verifyFlagDataMatchesDirectoryName(relativePath, flagData);
+ builder.addFile(filename, flagData);
+ }
+ }
+ });
+ return builder.build();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public void toZip(OutputStream out) {
+ ZipOutputStream zipOut = new ZipOutputStream(out);
+ files.forEach((flagId, fileMap) -> {
+ fileMap.forEach((filename, flagData) -> {
+ uncheck(() -> {
+ zipOut.putNextEntry(new ZipEntry("flags/" + flagId.toString() + "/" + filename));
+ zipOut.write(flagData.serializeToUtf8Json());
+ zipOut.closeEntry();
+ });
+ });
+ });
+ uncheck(zipOut::flush);
+ }
+
+ public Set<FlagData> flagData(FlagsTarget target) {
+ List<String> filenames = target.flagDataFilesPrioritized();
+ Set<FlagData> targetData = new HashSet<>();
+ files.forEach((flagId, fileMap) -> {
+ for (String filename : filenames) {
+ FlagData data = fileMap.get(filename);
+ if (data != null) {
+ targetData.add(data);
+ break;
+ }
+ }
+ });
+ return targetData;
+ }
+
+ private static void verifyFlagDataMatchesDirectoryName(Path filePath, FlagData flagData) {
+ String flagDirectoryName = filePath.getName(1).toString();
+ if (!flagDirectoryName.equals(flagData.id().toString())) {
+ throw new IllegalArgumentException(
+ String.format("Flag data file with flag id '%s' in directory for '%s'", flagData.id(), flagDirectoryName));
+ }
+ }
+
+ public static class Builder {
+ private final Map<FlagId, Map<String, FlagData>> files = new TreeMap<>();
+
+ public Builder() {}
+
+ public Builder addFile(String filename, FlagData data) {
+ files.computeIfAbsent(data.id(), k -> new TreeMap<>()).put(filename, data);
+ return this;
+ }
+
+ public SystemFlagsDataArchive build() {
+ Map<FlagId, Map<String, FlagData>> copy = new TreeMap<>();
+ files.forEach((flagId, map) -> copy.put(flagId, new TreeMap<>(map)));
+ return new SystemFlagsDataArchive(copy);
+ }
+
+ }
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/package-info.java
new file mode 100644
index 00000000000..0fe377db08c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/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 bjorncs
+ */
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.systemflags.v1;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/SystemFlagsV1Api.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/SystemFlagsV1Api.java
new file mode 100644
index 00000000000..1107b70c01c
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/SystemFlagsV1Api.java
@@ -0,0 +1,29 @@
+// 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.api.systemflags.v1.wire;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.io.InputStream;
+
+/**
+ * @author bjorncs
+ */
+@Path("/system-flags/v1")
+public interface SystemFlagsV1Api {
+
+ @PUT
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes("application/zip")
+ @Path("/deploy")
+ WireSystemFlagsDeployResult deploy(InputStream inputStream);
+
+ @PUT
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes("application/zip")
+ @Path("/dryrun")
+ WireSystemFlagsDeployResult dryrun(InputStream inputStream);
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/WireErrorResponse.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/WireErrorResponse.java
new file mode 100644
index 00000000000..996f0ac6cdd
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/WireErrorResponse.java
@@ -0,0 +1,18 @@
+// 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.api.systemflags.v1.wire;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class WireErrorResponse {
+ @JsonProperty("message")
+ public String message;
+ @JsonProperty("error-code")
+ public String errorCode;
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/WireSystemFlagsDeployResult.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/WireSystemFlagsDeployResult.java
new file mode 100644
index 00000000000..bd54fd15d15
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/WireSystemFlagsDeployResult.java
@@ -0,0 +1,32 @@
+// 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.api.systemflags.v1.wire;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.vespa.flags.json.wire.WireFlagData;
+
+import java.util.List;
+
+/**
+ *
+ *
+ * @author bjorncs
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class WireSystemFlagsDeployResult {
+ @JsonProperty("changes") public List<WireFlagDataChange> changes;
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class WireFlagDataChange {
+ @JsonProperty("flag-id") public String flagId;
+ @JsonProperty("targets") public List<String> targets;
+ @JsonProperty("operation") public String operation;
+ @JsonProperty("data") public WireFlagData data;
+ @JsonProperty("previous-data") public WireFlagData previousData;
+ }
+}
+
+
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/package-info.java
new file mode 100644
index 00000000000..305cf91014b
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/wire/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 bjorncs
+ */
+@ExportPackage
+package com.yahoo.vespa.hosted.controller.api.systemflags.v1.wire;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java
new file mode 100644
index 00000000000..35fec04e4c5
--- /dev/null
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/systemflags/v1/SystemFlagsDataArchiveTest.java
@@ -0,0 +1,89 @@
+// 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.api.systemflags.v1;
+
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.RawFlag;
+import com.yahoo.vespa.flags.json.FlagData;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author bjorncs
+ */
+public class SystemFlagsDataArchiveTest {
+
+ private static final SystemName SYSTEM = SystemName.main;
+
+ @Rule
+ public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private static FlagsTarget mainControllerTarget = FlagsTarget.forController(SYSTEM);
+ private static FlagsTarget prodUsWestCfgTarget = createConfigserverTarget(Environment.prod, "us-west-1");
+ private static FlagsTarget prodUsEast3CfgTarget = createConfigserverTarget(Environment.prod, "us-east-3");
+ private static FlagsTarget devUsEast1CfgTarget = createConfigserverTarget(Environment.dev, "us-east-1");
+
+ private static FlagsTarget createConfigserverTarget(Environment environment, String region) {
+ return new ConfigServerFlagsTarget(
+ SYSTEM,
+ ZoneId.from(environment, RegionName.from(region)),
+ URI.create("https://cfg-" + region),
+ new AthenzService("vespa.cfg-" + region));
+ }
+
+ @Test
+ public void can_serialize_and_deserialize_archive() throws IOException {
+ File tempFile = temporaryFolder.newFile("serialized-flags-archive");
+ try (OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile))) {
+ var archive = SystemFlagsDataArchive.fromDirectory(Paths.get("src/test/resources/system-flags/"));
+ archive.toZip(out);
+ }
+ try (InputStream in = new BufferedInputStream(new FileInputStream(tempFile))) {
+ SystemFlagsDataArchive archive = SystemFlagsDataArchive.fromZip(in);
+ assertArchiveReturnsCorrectDataForTarget(archive);
+ }
+ }
+
+ @Test
+ public void retrieves_correct_flag_data_for_target() {
+ var archive = SystemFlagsDataArchive.fromDirectory(Paths.get("src/test/resources/system-flags/"));
+ assertArchiveReturnsCorrectDataForTarget(archive);
+ }
+
+ private static void assertArchiveReturnsCorrectDataForTarget(SystemFlagsDataArchive archive) {
+ assertFlagDataHasValue(archive, mainControllerTarget, "main.controller");
+ assertFlagDataHasValue(archive, prodUsWestCfgTarget, "main.prod.us-west-1.json");
+ assertFlagDataHasValue(archive, prodUsEast3CfgTarget, "main.prod");
+ assertFlagDataHasValue(archive, devUsEast1CfgTarget, "main");
+ }
+
+ private static void assertFlagDataHasValue(SystemFlagsDataArchive archive, FlagsTarget target, String value) {
+ Set<FlagData> data = archive.flagData(target);
+ assertThat(data).hasSize(1);
+ FlagData flagData = data.iterator().next();
+ RawFlag rawFlag = flagData.resolve(FetchVector.fromMap(Map.of())).get();
+ assertThat(rawFlag.asJson()).isEqualTo(String.format("\"%s\"", value));
+ }
+
+} \ No newline at end of file
diff --git a/controller-api/src/test/resources/system-flags/flags/my-test-flag/default.json b/controller-api/src/test/resources/system-flags/flags/my-test-flag/default.json
new file mode 100644
index 00000000000..5924eb860c0
--- /dev/null
+++ b/controller-api/src/test/resources/system-flags/flags/my-test-flag/default.json
@@ -0,0 +1,8 @@
+{
+ "id" : "my-test-flag",
+ "rules" : [
+ {
+ "value" : "default"
+ }
+ ]
+} \ No newline at end of file
diff --git a/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.controller.json b/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.controller.json
new file mode 100644
index 00000000000..2860c833533
--- /dev/null
+++ b/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.controller.json
@@ -0,0 +1,8 @@
+{
+ "id" : "my-test-flag",
+ "rules" : [
+ {
+ "value" : "main.controller"
+ }
+ ]
+} \ No newline at end of file
diff --git a/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.json b/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.json
new file mode 100644
index 00000000000..d94390cd2a4
--- /dev/null
+++ b/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.json
@@ -0,0 +1,8 @@
+{
+ "id" : "my-test-flag",
+ "rules" : [
+ {
+ "value" : "main"
+ }
+ ]
+} \ No newline at end of file
diff --git a/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.prod.json b/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.prod.json
new file mode 100644
index 00000000000..28d2f068160
--- /dev/null
+++ b/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.prod.json
@@ -0,0 +1,8 @@
+{
+ "id" : "my-test-flag",
+ "rules" : [
+ {
+ "value" : "main.prod"
+ }
+ ]
+} \ No newline at end of file
diff --git a/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.prod.us-west-1.json b/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.prod.us-west-1.json
new file mode 100644
index 00000000000..87b435cdab1
--- /dev/null
+++ b/controller-api/src/test/resources/system-flags/flags/my-test-flag/main.prod.us-west-1.json
@@ -0,0 +1,8 @@
+{
+ "id" : "my-test-flag",
+ "rules" : [
+ {
+ "value" : "main.prod.us-west-1.json"
+ }
+ ]
+} \ No newline at end of file