diff options
Diffstat (limited to 'configserver')
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); - } - -} |