diff options
5 files changed, 74 insertions, 34 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ContainerEndpoint.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ContainerEndpoint.java index bac34e73dc5..159e4aa15da 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ContainerEndpoint.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ContainerEndpoint.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.configserver; import java.util.List; import java.util.Objects; +import java.util.OptionalInt; /** * This represents a list of one or more names for a container cluster. @@ -14,11 +15,13 @@ public class ContainerEndpoint { private final String clusterId; private final String scope; private final List<String> names; + private final OptionalInt weight; - public ContainerEndpoint(String clusterId, String scope, List<String> names) { - this.clusterId = nonEmpty(clusterId, "message must be non-empty"); + public ContainerEndpoint(String clusterId, String scope, List<String> names, OptionalInt weight) { + this.clusterId = nonEmpty(clusterId, "clusterId must be non-empty"); this.scope = Objects.requireNonNull(scope, "scope must be non-null"); this.names = List.copyOf(Objects.requireNonNull(names, "names must be non-null")); + this.weight = Objects.requireNonNull(weight, "weight must be non-null"); } /** ID of the cluster to which this points */ @@ -39,22 +42,29 @@ public class ContainerEndpoint { return names; } + /** The relative weight of this endpoint */ + public OptionalInt weight() { + return weight; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ContainerEndpoint that = (ContainerEndpoint) o; - return clusterId.equals(that.clusterId) && scope.equals(that.scope) && names.equals(that.names); + return clusterId.equals(that.clusterId) && scope.equals(that.scope) && names.equals(that.names) && weight.equals(that.weight); } @Override public int hashCode() { - return Objects.hash(clusterId, scope, names); + return Objects.hash(clusterId, scope, names, weight); } @Override public String toString() { - return "container endpoint for " + clusterId + ": " + names + " [scope=" + scope + "]"; + return "container endpoint for cluster " + clusterId + ": " + String.join(", ", names) + + " [scope=" + scope + ",weight=" + + weight.stream().boxed().map(Object::toString).findFirst().orElse("<none>") + "]"; } private static String nonEmpty(String s, String message) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java index 4772dbeaab1..1a9dc90b1b8 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java @@ -53,6 +53,8 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; @@ -286,7 +288,8 @@ public class RoutingController { names.add(assignedRotation.rotationId().asString()); containerEndpoints.add(new ContainerEndpoint(assignedRotation.clusterId().value(), asString(Endpoint.Scope.global), - names)); + names, + OptionalInt.empty())); } // Add endpoints not backed by a rotation (i.e. other routing methods so that the config server always knows // about global names, even when not using rotations) @@ -295,7 +298,8 @@ public class RoutingController { .forEach((clusterId, clusterEndpoints) -> { containerEndpoints.add(new ContainerEndpoint(clusterId.value(), asString(Endpoint.Scope.global), - clusterEndpoints.mapToList(Endpoint::dnsName))); + clusterEndpoints.mapToList(Endpoint::dnsName), + OptionalInt.empty())); }); // Add application endpoints EndpointList applicationEndpoints = endpoints.scope(Endpoint.Scope.application); @@ -313,12 +317,21 @@ public class RoutingController { RecordData.fqdn(vipHostname), Priority.normal); } - applicationEndpoints.groupingBy(Endpoint::cluster) - .forEach((clusterId, clusterEndpoints) -> { - containerEndpoints.add(new ContainerEndpoint(clusterId.value(), - asString(Endpoint.Scope.application), - clusterEndpoints.mapToList(Endpoint::dnsName))); - }); + Map<ClusterSpec.Id, EndpointList> applicationEndpointsByCluster = applicationEndpoints.groupingBy(Endpoint::cluster); + for (var kv : applicationEndpointsByCluster.entrySet()) { + ClusterSpec.Id clusterId = kv.getKey(); + EndpointList clusterEndpoints = kv.getValue(); + for (var endpoint : clusterEndpoints) { + Optional<Endpoint.Target> matchingTarget = endpoint.targets().stream() + .filter(t -> t.routesTo(deployment)) + .findFirst(); + if (matchingTarget.isEmpty()) throw new IllegalStateException("No target found routing to " + deployment + " in " + endpoint); + containerEndpoints.add(new ContainerEndpoint(clusterId.value(), + asString(Endpoint.Scope.application), + List.of(endpoint.dnsName()), + OptionalInt.of(matchingTarget.get().weight()))); + } + } return Collections.unmodifiableSet(containerEndpoints); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java index aee7c1052be..544822e3be3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java @@ -482,6 +482,11 @@ public class Endpoint { return weight; } + /** Returns whether this routes to given deployment */ + public boolean routesTo(DeploymentId deployment) { + return this.deployment.equals(deployment); + } + } public static class EndpointBuilder { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java index f9fd02fbf56..7fe8d554998 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java @@ -53,7 +53,7 @@ public class EndpointList extends AbstractFilteringList<Endpoint, EndpointList> return matching(endpoint -> endpoint.deployments().containsAll(deployments)); } - /** Returns the subset of endpoints which target the given deployments */ + /** Returns the subset of endpoints which target the given deployment */ public EndpointList targets(DeploymentId deployment) { return targets(List.of(deployment)); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java index 30cdd1b8466..2e0bcf78838 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java @@ -13,7 +13,6 @@ import com.yahoo.config.provision.CloudName; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.RoutingMethod; @@ -37,10 +36,10 @@ import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; -import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId; -import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock; import com.yahoo.vespa.hosted.controller.routing.RoutingStatus; import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext; +import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId; +import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock; import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; import org.junit.Test; @@ -50,6 +49,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -617,33 +617,45 @@ public class ControllerTest { @Test public void testDnsUpdatesForApplicationEndpoint() { - var context = tester.newDeploymentContext("tenant1", "app1", "beta"); + ApplicationId beta = ApplicationId.from("tenant1", "app1", "beta"); + ApplicationId main = ApplicationId.from("tenant1", "app1", "main"); + var context = tester.newDeploymentContext(beta); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .instances("beta,main") .region("us-west-1") .region("us-east-3") .applicationEndpoint("a", "default", "us-west-1", - Map.of(InstanceName.from("beta"), 2, - InstanceName.from("main"), 8)) + Map.of(beta.instance(), 2, + main.instance(), 8)) .applicationEndpoint("b", "default", "us-west-1", - Map.of(InstanceName.from("beta"), 1, - InstanceName.from("main"), 1)) + Map.of(beta.instance(), 1, + main.instance(), 1)) .applicationEndpoint("c", "default", "us-east-3", - Map.of(InstanceName.from("beta"), 4, - InstanceName.from("main"), 6)) + Map.of(beta.instance(), 4, + main.instance(), 6)) .build(); context.submit(applicationPackage).deploy(); - // Endpoint names are passed to each deployment - DeploymentId usWest = context.deploymentIdIn(ZoneId.from("prod", "us-west-1")); - DeploymentId usEast = context.deploymentIdIn(ZoneId.from("prod", "us-east-3")); - Map<DeploymentId, List<String>> deploymentEndpoints = Map.of(usWest, List.of("a.app1.tenant1.us-west-1-r.vespa.oath.cloud", "b.app1.tenant1.us-west-1-r.vespa.oath.cloud"), - usEast, List.of("c.app1.tenant1.us-east-3-r.vespa.oath.cloud")); - deploymentEndpoints.forEach((zone, endpointNames) -> { - assertEquals("Endpoint names are passed to config server in " + zone, - Set.of(new ContainerEndpoint("default", "application", - endpointNames)), - tester.configServer().containerEndpoints().get(zone)); + ZoneId usWest = ZoneId.from("prod", "us-west-1"); + ZoneId usEast = ZoneId.from("prod", "us-east-3"); + // Expected container endpoints are passed to each deployment + Map<DeploymentId, Map<String, Integer>> deploymentEndpoints = Map.of( + new DeploymentId(beta, usWest), Map.of("a.app1.tenant1.us-west-1-r.vespa.oath.cloud", 2, + "b.app1.tenant1.us-west-1-r.vespa.oath.cloud", 1), + new DeploymentId(main, usWest), Map.of("a.app1.tenant1.us-west-1-r.vespa.oath.cloud", 8, + "b.app1.tenant1.us-west-1-r.vespa.oath.cloud", 1), + new DeploymentId(beta, usEast), Map.of("c.app1.tenant1.us-east-3-r.vespa.oath.cloud", 4), + new DeploymentId(main, usEast), Map.of("c.app1.tenant1.us-east-3-r.vespa.oath.cloud", 6) + ); + deploymentEndpoints.forEach((deployment, endpoints) -> { + Set<ContainerEndpoint> expected = endpoints.entrySet().stream() + .map(kv -> new ContainerEndpoint("default", "application", + List.of(kv.getKey()), + OptionalInt.of(kv.getValue()))) + .collect(Collectors.toSet()); + assertEquals("Endpoint names for " + deployment + " are passed to config server", + expected, + tester.configServer().containerEndpoints().get(deployment)); }); context.flushDnsUpdates(); |