diff options
author | Martin Polden <mpolden@mpolden.no> | 2020-06-26 10:44:58 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2020-06-29 14:41:52 +0200 |
commit | 97dfae5b40f9f9a1aeb72caac363f82a27c1f381 (patch) | |
tree | 8c74cbb6c9e51566546c69e3ca9c43cbef4d64a9 /controller-server | |
parent | ba3680e98b722cc0c4926e96a7cca2089ef2e7b3 (diff) |
Support building weighted endpoints
Diffstat (limited to 'controller-server')
2 files changed, 74 insertions, 6 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 f8982c96637..62804074337 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 @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.application; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.ZoneId; @@ -36,15 +37,14 @@ public class Endpoint { private final RoutingMethod routingMethod; private final boolean tls; - private Endpoint(String name, URI url, List<ZoneId> zones, Scope scope, Port port, boolean legacy, - RoutingMethod routingMethod) { + private Endpoint(String name, URI url, List<ZoneId> zones, Scope scope, Port port, boolean legacy, RoutingMethod routingMethod) { Objects.requireNonNull(name, "name must be non-null"); Objects.requireNonNull(zones, "zones must be non-null"); Objects.requireNonNull(scope, "scope must be non-null"); Objects.requireNonNull(port, "port must be non-null"); Objects.requireNonNull(routingMethod, "routingMethod must be non-null"); - if (scope == Scope.zone && zones.size() != 1) { - throw new IllegalArgumentException("A single zone must be given for zone-scoped endpoints"); + if ((scope == Scope.zone || scope == Scope.weighted) && zones.size() != 1) { + throw new IllegalArgumentException("A single zone must be given for " + scope + "-scoped endpoints"); } this.name = name; this.url = url; @@ -189,8 +189,10 @@ public class Endpoint { private static String scopePart(Scope scope, List<ZoneId> zones, boolean legacy) { if (scope == Scope.global) return "global"; var zone = zones.get(0); - if (!legacy && zone.environment().isProduction()) return zone.region().value(); // Skip prod environment for non-legacy endpoints - return zone.region().value() + "." + zone.environment().value(); + var region = zone.region().value(); + if (scope == Scope.weighted) region += "-w"; + if (!legacy && zone.environment().isProduction()) return region; // Skip prod environment for non-legacy endpoints + return region + "." + zone.environment().value(); } private static String instancePart(ApplicationId application, String separator) { @@ -241,6 +243,21 @@ public class Endpoint { return part.substring(Math.max(0, part.length() - 63)); } + /** Returns the given region without availability zone */ + private static RegionName effectiveRegion(RegionName region) { + if (region.value().isEmpty()) return region; + String value = region.value(); + char lastChar = value.charAt(value.length() - 1); + if (lastChar >= 'a' && lastChar <= 'z') { // Remove availability zone + value = value.substring(0, value.length() - 1); + } + return RegionName.from(value); + } + + private static ZoneId effectiveZone(ZoneId zone) { + return ZoneId.from(zone.environment(), effectiveRegion(zone.region())); + } + /** An endpoint's scope */ public enum Scope { @@ -250,6 +267,9 @@ public class Endpoint { /** Endpoint points to a single zone */ zone, + /** Endpoint points to a single region */ + weighted, + } /** Represents an endpoint's HTTP port */ @@ -388,6 +408,16 @@ public class Endpoint { return this; } + /** Make this a weighted endpoint */ + public EndpointBuilder weighted() { + if (scope != Scope.zone && scope != Scope.weighted) { + throw new IllegalArgumentException("Endpoint must target zone before making it weighted"); + } + this.scope = Scope.weighted; + this.zones = List.of(effectiveZone(zones.get(0))); + return this; + } + /** Sets the system that owns this */ public Endpoint in(SystemName system) { String name; 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 f968b8c76d7..2e57a5eaaa1 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 @@ -228,6 +228,44 @@ public class EndpointTest { } @Test + public void weighted_endpoints() { + var cluster = ClusterSpec.Id.from("default"); + Map<String, Endpoint> tests = Map.of( + "https://a1.t1.us-north-1-w.public.vespa.oath.cloud/", + Endpoint.of(app1) + .target(cluster, ZoneId.from("prod", "us-north-1a")) + .weighted() + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.Public), + "https://a1.t1.us-north-2-w.public.vespa.oath.cloud/", + Endpoint.of(app1) + .target(cluster, ZoneId.from("prod", "us-north-2")) + .weighted() + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.Public), + "https://a1.t1.us-north-2-w.test.public.vespa.oath.cloud/", + Endpoint.of(app1) + .target(cluster, ZoneId.from("test", "us-north-2")) + .weighted() + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.Public) + ); + tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); + Endpoint endpoint = Endpoint.of(app1) + .target(cluster, ZoneId.from("prod", "us-north-1a")) + .weighted() + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.main); + assertEquals("Availability zone is removed from region", + "us-north-1", + endpoint.zones().get(0).region().value()); + } + + @Test public void upstream_name() { var zone = ZoneId.from("prod", "us-north-1"); var tests1 = Map.of( |