aboutsummaryrefslogtreecommitdiffstats
path: root/configserver
diff options
context:
space:
mode:
authorØyvind Grønnesby <oyving@verizonmedia.com>2019-06-06 16:22:17 +0200
committerØyvind Grønnesby <oyving@verizonmedia.com>2019-06-06 16:22:17 +0200
commitfeb8f544fc68363c9551e4edced143ad11f8481c (patch)
tree45bda5d5eaf7107dcb10849ae49073738288e0c6 /configserver
parentaf78fa016ffba5917a5d12d91b643fb8849b93b8 (diff)
Re-implement RotationsCache with more semantically correct names and structure
Diffstat (limited to 'configserver')
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpoint.java54
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointSerializer.java76
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointsCache.java59
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationAssignment.java90
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationsCache.java87
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointSerializerTest.java45
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointsCacheTest.java36
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/tenant/RotationsCacheTest.java117
8 files changed, 270 insertions, 294 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpoint.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpoint.java
new file mode 100644
index 00000000000..fb1035b5a03
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpoint.java
@@ -0,0 +1,54 @@
+// 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.tenant;
+
+import com.yahoo.vespa.applicationmodel.ClusterId;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * ContainerEndpoint tracks the service names that a Container Cluster should be
+ * known as. This is used during request routing both for regular requests and
+ * for health checks in traffic distribution.
+ *
+ * @author ogronnesby
+ */
+public class ContainerEndpoint {
+ private final ClusterId clusterId;
+ private final List<String> names;
+
+ ContainerEndpoint(ClusterId clusterId, List<String> names) {
+ this.clusterId = Objects.requireNonNull(clusterId);
+ this.names = List.copyOf(Objects.requireNonNull(names));
+ }
+
+ public ClusterId clusterId() {
+ return clusterId;
+ }
+
+ public List<String> names() {
+ return names;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ContainerEndpoint that = (ContainerEndpoint) o;
+ return Objects.equals(clusterId, that.clusterId) &&
+ Objects.equals(names, that.names);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(clusterId, names);
+ }
+
+ @Override
+ public String toString() {
+ return "ContainerEndpoint{" +
+ "clusterId=" + clusterId +
+ ", names=" + names +
+ '}';
+ }
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointSerializer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointSerializer.java
new file mode 100644
index 00000000000..83d65f5b38b
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointSerializer.java
@@ -0,0 +1,76 @@
+// 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.tenant;
+
+import com.yahoo.slime.ArrayTraverser;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.Inspector;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.applicationmodel.ClusterId;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains all methods for de-/serializing ContainerEndpoints to/from JSON.
+ * Also supports de-/serializing lists of these values.
+ *
+ * @author ogronnesby
+ */
+public class ContainerEndpointSerializer {
+ private static final String clusterIdField = "clusterId";
+ private static final String namesField = "names";
+
+ public static ContainerEndpoint endpointFromSlime(Inspector inspector) {
+ final var clusterId = inspector.field(clusterIdField).asString();
+ final var namesInspector = inspector.field(namesField);
+
+ if (clusterId.isEmpty()) {
+ throw new IllegalStateException("'clusterId' missing on serialized ContainerEndpoint");
+ }
+
+ if (! namesInspector.valid()) {
+ throw new IllegalStateException("'names' missing on serialized ContainerEndpoint");
+ }
+
+ final var names = new ArrayList<String>();
+
+ namesInspector.traverse((ArrayTraverser) (idx, nameInspector) -> {
+ final var containerName = nameInspector.asString();
+ names.add(containerName);
+ });
+
+ return new ContainerEndpoint(new ClusterId(clusterId), names);
+ }
+
+ public static List<ContainerEndpoint> endpointListFromSlime(Slime slime) {
+ final var inspector = slime.get();
+ final var endpoints = new ArrayList<ContainerEndpoint>();
+
+ inspector.traverse((ArrayTraverser) (idx, endpointInspector) -> {
+ final var containerEndpoint = endpointFromSlime(endpointInspector);
+ endpoints.add(containerEndpoint);
+ });
+
+ return endpoints;
+ }
+
+
+ public static void endpointToSlime(Cursor cursor, ContainerEndpoint endpoint) {
+ cursor.setString(clusterIdField, endpoint.clusterId().toString());
+
+ final var namesInspector = cursor.setArray(namesField);
+ endpoint.names().forEach(namesInspector::addString);
+ }
+
+ public static Slime endpointListToSlime(List<ContainerEndpoint> endpoints) {
+ final var slime = new Slime();
+ final var cursor = slime.setArray();
+
+ endpoints.forEach(endpoint -> {
+ final var endpointCursor = cursor.addObject();
+ endpointToSlime(endpointCursor, endpoint);
+ });
+
+ return slime;
+ }
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointsCache.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointsCache.java
new file mode 100644
index 00000000000..eefbce697c6
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointsCache.java
@@ -0,0 +1,59 @@
+// 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.tenant;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.config.SlimeUtils;
+import com.yahoo.vespa.curator.Curator;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.List;
+
+
+/**
+ * Persists assignment of rotations to an application to ZooKeeper.
+ * The entries are RotationAssignments, which keep track of the container
+ * cluster that is the target, the endpoint name, and the rotation used to
+ * give availability to that cluster.
+ *
+ * This is v2 of that storage in a new directory. Previously we only stored
+ * the name of the rotation, since all the other information could be
+ * calculated runtime.
+ *
+ * @author ogronnesby
+ */
+public class ContainerEndpointsCache {
+ private final Path cachePath;
+ private final Curator curator;
+
+ ContainerEndpointsCache(Path tenantPath, Curator curator) {
+ this.cachePath = tenantPath.append("containerEndpointsCache/");
+ this.curator = curator;
+ }
+
+ public List<ContainerEndpoint> read(ApplicationId applicationId) {
+ final var optionalData = curator.getData(applicationPath(applicationId));
+ return optionalData
+ .map(SlimeUtils::jsonToSlime)
+ .map(ContainerEndpointSerializer::endpointListFromSlime)
+ .orElse(List.of());
+ }
+
+ public void write(ApplicationId applicationId, List<ContainerEndpoint> endpoints) {
+ if (endpoints.isEmpty()) return;
+
+ final var slime = ContainerEndpointSerializer.endpointListToSlime(endpoints);
+
+ try {
+ final var bytes = SlimeUtils.toJsonBytes(slime);
+ curator.set(applicationPath(applicationId), bytes);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Error writing endpoints of: " + applicationId, e);
+ }
+ }
+
+ private Path applicationPath(ApplicationId applicationId) {
+ return cachePath.append(applicationId.serializedForm());
+ }
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationAssignment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationAssignment.java
deleted file mode 100644
index 4c9e5b289b7..00000000000
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationAssignment.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.config.server.tenant;
-
-import com.yahoo.config.provision.Rotation;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Inspector;
-import com.yahoo.vespa.applicationmodel.ClusterId;
-
-import java.util.Objects;
-
-/**
- * RotationAssignment is an immutable dataclass that contains the
- * tuple for [endpoint, cluster, rotation]. This information is used
- * to keep track of
- *
- * - Name of an endpoint used externally.
- * - The container cluster that endpoint should point to.
- * - The rotation used to give availability to that cluster endpoint.
- *
- * @author ogronnesby
- */
-public class RotationAssignment {
- private final String endpointId;
- private final Rotation rotation;
- private final ClusterId containerId;
-
- RotationAssignment(String endpointId, Rotation rotationName, ClusterId clusterId) {
- this.endpointId = Objects.requireNonNull(endpointId);
- this.rotation = Objects.requireNonNull(rotationName);
- this.containerId = Objects.requireNonNull(clusterId);
- }
-
- public String getEndpointId() {
- return endpointId;
- }
-
- public Rotation getRotation() {
- return rotation;
- }
-
- public ClusterId getContainerId() {
- return containerId;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- RotationAssignment that = (RotationAssignment) o;
- return Objects.equals(endpointId, that.endpointId) &&
- Objects.equals(rotation, that.rotation) &&
- Objects.equals(containerId, that.containerId);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(endpointId, rotation, containerId);
- }
-
- static RotationAssignment fromSlime(Inspector inspector) {
- final var endpointId = inspector.field("endpointId").asString();
- final var rotationId = inspector.field("rotationId").asString();
- final var containerId = inspector.field("containerId").asString();
-
-
- if (endpointId.equals("")) {
- throw new IllegalStateException("Missing 'endpointId' in RotationsCache");
- }
-
- if (rotationId.equals("")) {
- throw new IllegalStateException("Missing 'rotationId' in RotationsCache");
- }
-
- if (containerId.equals("")) {
- throw new IllegalStateException("Missing 'containerId' in RotationsCache");
- }
-
- return new RotationAssignment(
- endpointId,
- new Rotation(rotationId),
- new ClusterId(containerId)
- );
- }
-
- void toSlime(Cursor cursor) {
- cursor.setString("endpointId", endpointId);
- cursor.setString("rotationId", rotation.getId());
- cursor.setString("containerId", containerId.toString());
- }
-}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationsCache.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationsCache.java
deleted file mode 100644
index d24f6f3bfe1..00000000000
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationsCache.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.config.server.tenant;
-
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.path.Path;
-import com.yahoo.slime.ObjectTraverser;
-import com.yahoo.slime.Slime;
-import com.yahoo.vespa.applicationmodel.ClusterId;
-import com.yahoo.vespa.config.SlimeUtils;
-import com.yahoo.vespa.curator.Curator;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-
-/**
- * Persists assignment of rotations to an application to ZooKeeper.
- * The entries are RotationAssignments, which keep track of the container
- * cluster that is the target, the endpoint name, and the rotation used to
- * give availability to that cluster.
- *
- * This is v2 of that storage in a new directory. Previously we only stored
- * the name of the rotation, since all the other information could be
- * calculated runtime.
- *
- * @author ogronnesby
- */
-public class RotationsCache {
- private final Path cachePath;
- private final Curator curator;
-
- public RotationsCache(Path tenantPath, Curator curator) {
- this.cachePath = tenantPath.append("rotationsCache-v2/");
- this.curator = curator;
- }
-
- public Map<ClusterId, RotationAssignment> read(ApplicationId applicationId) {
- final var optionalData = curator.getData(applicationPath(applicationId));
- return optionalData
- .map(SlimeUtils::jsonToSlime)
- .map(RotationsCache::entryFromSlime)
- .orElse(Collections.emptyMap());
- }
-
- public void write(ApplicationId applicationId, Map<ClusterId, RotationAssignment> assignments) {
- if (assignments.isEmpty()) return;
- try {
- curator.set(
- applicationPath(applicationId),
- SlimeUtils.toJsonBytes(entryToSlime(assignments))
- );
- } catch (IOException e) {
- throw new UncheckedIOException("Error writing rotations of: " + applicationId, e);
- }
- }
-
- static Map<ClusterId, RotationAssignment> entryFromSlime(Slime slime) {
- final var assignmentMap = new HashMap<ClusterId, RotationAssignment>();
-
- slime.get().traverse((ObjectTraverser) (name, inspector) -> {
- final var containerId = new ClusterId(name);
- final var assignment = RotationAssignment.fromSlime(inspector);
- assignmentMap.put(containerId, assignment);
- });
-
- return Map.copyOf(assignmentMap);
- }
-
- static Slime entryToSlime(Map<ClusterId, RotationAssignment> assignments) {
- final var slime = new Slime();
- final var cursor = slime.setObject();
-
- assignments.forEach((clusterId, assignment) -> {
- final var assignmentCursor = cursor.setObject(clusterId.toString());
- assignment.toSlime(assignmentCursor);
- });
-
- return slime;
- }
-
- private Path applicationPath(ApplicationId applicationId) {
- return cachePath.append(applicationId.serializedForm());
- }
-}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointSerializerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointSerializerTest.java
new file mode 100644
index 00000000000..b4d52e6d37c
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointSerializerTest.java
@@ -0,0 +1,45 @@
+package com.yahoo.vespa.config.server.tenant;
+
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.applicationmodel.ClusterId;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class ContainerEndpointSerializerTest {
+ @Test
+ public void readSingleEndpoint() {
+ final var slime = new Slime();
+ final var entry = slime.setObject();
+
+ entry.setString("clusterId", "foobar");
+ final var entryNames = entry.setArray("names");
+ entryNames.addString("a");
+ entryNames.addString("b");
+
+ final var endpoint = ContainerEndpointSerializer.endpointFromSlime(slime.get());
+ assertEquals("foobar", endpoint.clusterId().toString());
+ assertEquals(List.of("a", "b"), endpoint.names());
+ }
+
+ @Test
+ public void writeReadSingleEndpoint() {
+ final var endpoint = new ContainerEndpoint(new ClusterId("foo"), List.of("a", "b"));
+ final var serialized = new Slime();
+ ContainerEndpointSerializer.endpointToSlime(serialized.setObject(), endpoint);
+ final var deserialized = ContainerEndpointSerializer.endpointFromSlime(serialized.get());
+
+ assertEquals(endpoint, deserialized);
+ }
+
+ @Test
+ public void writeReadEndpoints() {
+ final var endpoints = List.of(new ContainerEndpoint(new ClusterId("foo"), List.of("a", "b")));
+ final var serialized = ContainerEndpointSerializer.endpointListToSlime(endpoints);
+ final var deserialized = ContainerEndpointSerializer.endpointListFromSlime(serialized);
+
+ assertEquals(endpoints, deserialized);
+ }
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointsCacheTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointsCacheTest.java
new file mode 100644
index 00000000000..3598b6e63c3
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/ContainerEndpointsCacheTest.java
@@ -0,0 +1,36 @@
+// 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.tenant;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.path.Path;
+import com.yahoo.vespa.applicationmodel.ClusterId;
+import com.yahoo.vespa.curator.mock.MockCurator;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ContainerEndpointsCacheTest {
+ @Test
+ public void readWriteFromCache() {
+ final var cache = new ContainerEndpointsCache(Path.createRoot(), new MockCurator());
+ final var endpoints = List.of(
+ new ContainerEndpoint(new ClusterId("the-cluster-1"), List.of("a", "b", "c"))
+ );
+
+ cache.write(ApplicationId.defaultId(), endpoints);
+
+ final var deserialized = cache.read(ApplicationId.defaultId());
+
+ assertEquals(endpoints, deserialized);
+ }
+
+ @Test
+ public void readingNonExistingEntry() {
+ final var cache = new ContainerEndpointsCache(Path.createRoot(), new MockCurator());
+ final var endpoints = cache.read(ApplicationId.defaultId());
+ assertTrue(endpoints.isEmpty());
+ }
+} \ No newline at end of file
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/RotationsCacheTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/RotationsCacheTest.java
deleted file mode 100644
index e5d361feb9d..00000000000
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/RotationsCacheTest.java
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.config.server.tenant;
-
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Rotation;
-import com.yahoo.path.Path;
-import com.yahoo.slime.Slime;
-import com.yahoo.vespa.applicationmodel.ClusterId;
-import com.yahoo.vespa.curator.mock.MockCurator;
-import org.junit.Test;
-
-import java.util.HashMap;
-import java.util.Set;
-
-import static org.junit.Assert.assertEquals;
-
-public class RotationsCacheTest {
- @Test
- public void assignmentDeserialization() {
- final var slime = new Slime();
- final var cursor = slime.setObject();
-
- cursor.setString("endpointId", "nallefisk");
- cursor.setString("containerId", "froskelosk");
- cursor.setString("rotationId", "rotterott");
-
- final var assignment = RotationAssignment.fromSlime(cursor);
- assertEquals("nallefisk", assignment.getEndpointId());
- assertEquals("froskelosk", assignment.getContainerId().toString());
- assertEquals("rotterott", assignment.getRotation().getId());
- }
-
- @Test
- public void assignmentSerialization() {
- final var assignment = new RotationAssignment(
- "sluttpeker",
- new Rotation("rotasjon"),
- new ClusterId("klynge")
- );
-
- final var serialized = new Slime();
- assignment.toSlime(serialized.setObject());
-
- assertEquals(assignment, RotationAssignment.fromSlime(serialized.get()));
- }
-
- @Test
- public void mapDeserialization() {
- final var slime = new Slime();
- final var cursor = slime.setObject();
-
- final var clusterId = new ClusterId("nalle");
-
- final var entry = cursor.setObject("nalle");
- entry.setString("endpointId", "nallefisk");
- entry.setString("containerId", clusterId.toString());
- entry.setString("rotationId", "rotterott");
-
- final var assignments = RotationsCache.entryFromSlime(slime);
- assertEquals(Set.of(clusterId), assignments.keySet());
-
- // check the entry
- final var assignment = assignments.get(clusterId);
- assertEquals(clusterId, assignment.getContainerId());
- assertEquals("nallefisk", assignment.getEndpointId());
- assertEquals(new Rotation("rotterott"), assignment.getRotation());
- }
-
- @Test
- public void mapSerialization() {
- final var assignments = new HashMap<ClusterId, RotationAssignment>();
-
- assignments.put(
- new ClusterId("the-nallefisk-1"),
- new RotationAssignment(
- "the-endpoint-1",
- new Rotation("the-rotation-1"),
- new ClusterId("the-cluster-1")
- )
- );
-
- assignments.put(
- new ClusterId("the-nallefisk-2"),
- new RotationAssignment(
- "the-endpoint-2",
- new Rotation("the-rotation-2"),
- new ClusterId("the-cluster-2")
- )
- );
-
- final var serialized = RotationsCache.entryToSlime(assignments);
- final var deserialized = RotationsCache.entryFromSlime(serialized);
-
- assertEquals(assignments, deserialized);
- }
-
- @Test
- public void cacheStoreAndLoad() {
- final var rotations = new RotationsCache(Path.createRoot().append("foo"), new MockCurator());
- final var assignments = new HashMap<ClusterId, RotationAssignment>();
-
- assignments.put(
- new ClusterId("the-nallefisk-1"),
- new RotationAssignment(
- "the-endpoint-1",
- new Rotation("the-rotation-1"),
- new ClusterId("the-cluster-1")
- )
- );
-
- rotations.write(ApplicationId.defaultId(), assignments);
- final var fetched = rotations.read(ApplicationId.defaultId());
-
- assertEquals(assignments, fetched);
- }
-
-}