diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-11-02 15:50:24 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2021-11-02 15:50:24 +0100 |
commit | 7f6989ae1f410f3932fb661beb28922f02d2d01f (patch) | |
tree | 0871912e951739ec33f130f89def4f5f9d0b6c45 /controller-server | |
parent | 6bd3bb5ee34ea82c129dd03d30eeb414eb15196e (diff) |
Support building application-scoped endpoint names
Diffstat (limited to 'controller-server')
2 files changed, 78 insertions, 9 deletions
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 cd70b804dc3..5e458a051b6 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 @@ -75,7 +75,7 @@ public class Endpoint { zones, scope, Objects.requireNonNull(system, "system must be non-null"), - port, + Objects.requireNonNull(port, "port must be non-null"), legacy, routingMethod), zones, scope, port, legacy, routingMethod); @@ -140,7 +140,7 @@ public class Endpoint { /** Returns the upstream ID of given deployment. This *must* match what the routing layer generates */ public String upstreamIdOf(DeploymentId deployment) { - if (scope != Scope.global) throw new IllegalArgumentException("Scope " + scope + " does not have upstream name"); + if (!scope.multiRegion()) throw new IllegalArgumentException("Scope " + scope + " does not have upstream name"); if (!routingMethod.isShared()) throw new IllegalArgumentException("Routing method " + routingMethod + " does not have upstream name"); return upstreamIdOf(cluster.value(), deployment.applicationId(), deployment.zoneId()); } @@ -175,7 +175,7 @@ public class Endpoint { String portPart = port.isDefault() ? "" : ":" + port.port; return URI.create(scheme + "://" + sanitize(namePart(name, separator)) + - systemPart(system, separator, legacy) + + systemPart(system, separator) + sanitize(instancePart(instance, separator)) + sanitize(application.application().value()) + separator + @@ -205,7 +205,7 @@ public class Endpoint { private static String scopePart(Scope scope, List<ZoneId> zones, SystemName system, boolean legacy) { String scopeSymbol = scopeSymbol(scope, system); - if (scope == Scope.global) return scopeSymbol; + if (scope.multiRegion()) return scopeSymbol; ZoneId zone = zones.get(0); String region = zone.region().value(); @@ -223,12 +223,14 @@ public class Endpoint { case zone: return "z"; case region: return "w"; case global: return "g"; + case application: return "a"; } } switch (scope) { case zone: return ""; case region: return "w"; case global: return "global"; + case application: return "a"; } throw new IllegalArgumentException("No scope symbol defined for " + scope + " in " + system); } @@ -239,9 +241,9 @@ public class Endpoint { return instance.get().value() + separator; } - private static String systemPart(SystemName system, String separator, boolean legacy) { + private static String systemPart(SystemName system, String separator) { if (!system.isCd()) return ""; - if (system.isPublic() && !legacy) return ""; + if (system.isPublic()) return ""; return system.value() + separator; } @@ -315,6 +317,13 @@ public class Endpoint { /** An endpoint's scope */ public enum Scope { + /** + * Endpoint points to a multiple instances of an application. + * + * Traffic is routed across instances according to weights specified in deployment.xml + */ + application, + /** Endpoint points to one or more zones. Traffic is routed to the zone closest to the client */ global, @@ -326,12 +335,23 @@ public class Endpoint { region, /** Endpoint points to a single zone */ - zone, + zone; + + /** Returns whether this scope may span multiple regions */ + public boolean multiRegion() { + // application scope doesn't technically support multiple regions in practice, but we assume it does for the + // purposes of building an endpoint name. This allows us to support multiple regions in the future without + // needing to change endpoint names. + return this == application || this == global; + } + } /** Represents an endpoint's HTTP port */ public static class Port { + private static final Port TLS_DEFAULT = new Port(443, true); + private final int port; private final boolean tls; @@ -349,7 +369,7 @@ public class Endpoint { /** Returns the default HTTPS port */ public static Port tls() { - return new Port(443, true); + return TLS_DEFAULT; } /** Returns default port for the given routing method */ @@ -440,6 +460,13 @@ public class Endpoint { return target(ClusterSpec.Id.from("*"), zone); } + /** Sets the application target with given ID, zones and cluster (as defined in deployments.xml) */ + public EndpointBuilder targetApplication(EndpointId endpointId, ClusterSpec.Id cluster, ZoneId zone) { + target(endpointId, cluster, List.of(zone)); + this.scope = Scope.application; + return this; + } + /** Sets the region target for this, deduced from given zone */ public EndpointBuilder targetRegion(ClusterSpec.Id cluster, ZoneId zone) { checkScope(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java index 09298113827..e2fb70e5338 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java @@ -311,6 +311,41 @@ public class EndpointTest { } @Test + public void application_endpoints() { + Map<String, Endpoint> tests = Map.of( + "https://weighted.a1.t1.a.vespa-app.cloud/", + Endpoint.of(app1) + .targetApplication(EndpointId.of("weighted"), ClusterSpec.Id.from("qrs"), + ZoneId.from("prod", "us-west-1")) + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.Public), + "https://weighted.a1.t1.a.cd.vespa-app.cloud/", + Endpoint.of(app1) + .targetApplication(EndpointId.of("weighted"), ClusterSpec.Id.from("qrs"), + ZoneId.from("prod", "us-west-1")) + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.PublicCd), + "https://a2.t2.a.vespa.oath.cloud/", + Endpoint.of(app2) + .targetApplication(EndpointId.defaultId(), ClusterSpec.Id.from("qrs"), + ZoneId.from("prod", "us-east-3")) + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.main), + "https://cd.a2.t2.a.vespa.oath.cloud/", + Endpoint.of(app2) + .targetApplication(EndpointId.defaultId(), ClusterSpec.Id.from("qrs"), + ZoneId.from("prod", "us-east-3")) + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.cd) + ); + tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); + } + + @Test public void upstream_name() { var zone = ZoneId.from("prod", "us-north-1"); var tests1 = Map.of( @@ -320,7 +355,14 @@ public class EndpointTest { // With non-default cluster "c1.a1.t1.us-north-1.prod", - Endpoint.of(instance1).target(EndpointId.of("ignored1"), ClusterSpec.Id.from("c1"), List.of(zone)).on(Port.tls(4443)).in(SystemName.main) + Endpoint.of(instance1).target(EndpointId.of("ignored1"), ClusterSpec.Id.from("c1"), List.of(zone)).on(Port.tls(4443)).in(SystemName.main), + + // With application endpoint + "c2.a1.t1.us-north-1.prod", + Endpoint.of(app1).targetApplication(EndpointId.defaultId(), ClusterSpec.Id.from("c2"), zone) + .routingMethod(RoutingMethod.sharedLayer4) + .on(Port.tls()) + .in(SystemName.main) ); var tests2 = Map.of( // With non-default instance and default cluster |