diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-10-22 10:52:45 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-22 10:52:45 +0200 |
commit | b95cd673648e3cddc28debd38eefde9f5661291c (patch) | |
tree | 0cd8f104ac948e26b317fa7c31788dd7f615588d | |
parent | 477354aea222aad8b23bb121ce6c4add8da1c462 (diff) | |
parent | 5e63d00f81639c4d96a0777aeab7a00479c78772 (diff) |
Merge pull request #19695 from vespa-engine/mpolden/named-region-endpoint
Introduce separate scope for internal region endpoint
6 files changed, 187 insertions, 104 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 b441d40c1c3..3a35e3aec7a 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.InstanceName; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.RoutingMethod; @@ -13,13 +14,15 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import java.net.URI; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; /** - * Represents an application's endpoint in hosted Vespa. This encapsulates all logic for building URLs and DNS names for - * application endpoints. + * Represents an application or instance endpoint in hosted Vespa. + * + * This encapsulates the logic for building URLs and DNS names for applications in all hosted Vespa systems. * * @author mpolden */ @@ -50,7 +53,7 @@ public class Endpoint { if (scope == Scope.global) { if (id == null) throw new IllegalArgumentException("Endpoint ID must be set for global endpoints"); } else { - if (id != null) throw new IllegalArgumentException("Endpoint ID cannot be set for " + scope + " endpoints"); + if (scope == Scope.zone && id != null) throw new IllegalArgumentException("Endpoint ID cannot be set for " + scope + " endpoints"); if (zones.size() != 1) throw new IllegalArgumentException("A single zone must be given for " + scope + " endpoints"); } this.id = id; @@ -63,12 +66,14 @@ public class Endpoint { this.tls = port.tls; } - private Endpoint(EndpointId id, ClusterSpec.Id cluster, ApplicationId application, List<ZoneId> zones, Scope scope, SystemName system, - Port port, boolean legacy, RoutingMethod routingMethod) { + private Endpoint(EndpointId id, ClusterSpec.Id cluster, TenantAndApplicationId application, + Optional<InstanceName> instance, List<ZoneId> zones, Scope scope, SystemName system, Port port, + boolean legacy, RoutingMethod routingMethod) { this(id, cluster, createUrl(endpointOrClusterAsString(id, cluster), Objects.requireNonNull(application, "application must be non-null"), + Objects.requireNonNull(instance, "instance must be non-null"), zones, scope, Objects.requireNonNull(system, "system must be non-null"), @@ -164,20 +169,21 @@ public class Endpoint { return id == null ? cluster.value() : id.id(); } - private static URI createUrl(String name, ApplicationId application, List<ZoneId> zones, Scope scope, - SystemName system, Port port, boolean legacy, RoutingMethod routingMethod) { + private static URI createUrl(String name, TenantAndApplicationId application, Optional<InstanceName> instance, + List<ZoneId> zones, Scope scope, SystemName system, Port port, boolean legacy, + RoutingMethod routingMethod) { String scheme = port.tls ? "https" : "http"; String separator = separator(system, routingMethod, port.tls); String portPart = port.isDefault() ? "" : ":" + port.port; return URI.create(scheme + "://" + sanitize(namePart(name, separator)) + systemPart(system, separator, legacy) + - sanitize(instancePart(application, separator)) + + sanitize(instancePart(instance, separator)) + sanitize(application.application().value()) + separator + sanitize(application.tenant().value()) + "." + - scopePart(scope, zones, legacy, system) + + scopePart(scope, zones, system, legacy) + dnsSuffix(system, legacy) + portPart + "/"); @@ -199,26 +205,42 @@ public class Endpoint { return name + separator; } - private static String scopePart(Scope scope, List<ZoneId> zones, boolean legacy, SystemName system) { + private static String scopePart(Scope scope, List<ZoneId> zones, SystemName system, boolean legacy) { + String scopeSymbol = scopeSymbol(scope, system, legacy); + if (scope == Scope.global) return scopeSymbol; + + ZoneId zone = zones.get(0); + String region = zone.region().value(); + boolean skipEnvironment = zone.environment().isProduction() && (system.isPublic() || !legacy); + String environment = skipEnvironment ? "" : "." + zone.environment().value(); if (system.isPublic() && !legacy) { - if (scope == Scope.global) return "g"; - var zone = zones.get(0); - var region = zone.region().value(); - char scopeSymbol = scope == Scope.region ? 'r' : 'z'; - String environment = zone.environment().isProduction() ? "" : "." + zone.environment().value(); return region + environment + "." + scopeSymbol; } - if (scope == Scope.global) return "global"; - var zone = zones.get(0); - var region = zone.region().value(); - if (scope == Scope.region) region += "-w"; - if ((system.isPublic() || !legacy) && zone.environment().isProduction()) return region; - return region + "." + zone.environment().value(); + return region + (scopeSymbol.isEmpty() ? "" : "-" + scopeSymbol) + environment; } - private static String instancePart(ApplicationId application, String separator) { - if (application.instance().isDefault()) return ""; // Skip "default" - return application.instance().value() + separator; + private static String scopeSymbol(Scope scope, SystemName system, boolean legacy) { + if (system.isPublic() && !legacy) { + switch (scope) { + case zone: return "z"; + case regionSplit: return "w"; + case region: return "r"; + case global: return "g"; + } + } + switch (scope) { + case zone: return ""; + case regionSplit: return "w"; + case region: return "r"; + case global: return "global"; + } + throw new IllegalArgumentException("No scope symbol defined for " + scope + " in " + system + " (legacy: " + legacy + ")"); + } + + private static String instancePart(Optional<InstanceName> instance, String separator) { + if (instance.isEmpty()) return ""; + if (instance.get().isDefault()) return ""; // Skip "default" + return instance.get().value() + separator; } private static String systemPart(SystemName system, String separator, boolean legacy) { @@ -246,7 +268,7 @@ public class Endpoint { private static String upstreamIdOf(String name, ApplicationId application, ZoneId zone) { return Stream.of(namePart(name, ""), - instancePart(application, ""), + instancePart(Optional.of(application.instance()), ""), application.application().value(), application.tenant().value(), zone.region().value(), @@ -289,12 +311,21 @@ public class Endpoint { /** Endpoint points to one or more zones. Traffic is routed to the zone closest to the client */ global, - /** Endpoint points to one more zones in the same geographical region. Traffic is routed equally across zones */ + /** + * Endpoint points to one more zones in the same geographical region. Traffic is weighted according to + * configured weights. + */ region, /** Endpoint points to a single zone */ zone, + /** + * Endpoint points to one more zones in the same geographical region. Traffic is routed equally across zones. + * + * This is for internal use only. Endpoints with this scope are not exposed directly to tenants. + */ + regionSplit, } /** Represents an endpoint's HTTP port */ @@ -338,9 +369,14 @@ public class Endpoint { } + /** Build an endpoint for given instance */ + public static EndpointBuilder of(ApplicationId instance) { + return new EndpointBuilder(TenantAndApplicationId.from(instance), Optional.of(instance.instance())); + } + /** Build an endpoint for given application */ - public static EndpointBuilder of(ApplicationId application) { - return new EndpointBuilder(application); + public static EndpointBuilder of(TenantAndApplicationId application) { + return new EndpointBuilder(application, Optional.empty()); } /** Create an endpoint for given system application */ @@ -353,7 +389,8 @@ public class Endpoint { public static class EndpointBuilder { - private final ApplicationId application; + private final TenantAndApplicationId application; + private final Optional<InstanceName> instance; private Scope scope; private List<ZoneId> zones; @@ -363,8 +400,9 @@ public class Endpoint { private RoutingMethod routingMethod = RoutingMethod.shared; private boolean legacy = false; - private EndpointBuilder(ApplicationId application) { - this.application = application; + private EndpointBuilder(TenantAndApplicationId application, Optional<InstanceName> instance) { + this.application = Objects.requireNonNull(application); + this.instance = Objects.requireNonNull(instance); } /** Sets the zone target for this */ @@ -402,9 +440,19 @@ public class Endpoint { } /** Sets the region target for this, deduced from given zone */ - public EndpointBuilder targetRegion(ClusterSpec.Id cluster, ZoneId zone) { + public EndpointBuilder targetRegionSplit(ClusterSpec.Id cluster, ZoneId zone) { checkScope(); this.cluster = cluster; + this.scope = Scope.regionSplit; + this.zones = List.of(effectiveZone(zone)); + return this; + } + + /** Sets the region target for this by endpointId, deduced from given zone */ + public EndpointBuilder targetRegion(EndpointId endpointId, ClusterSpec.Id cluster, ZoneId zone) { + checkScope(); + this.endpointId = endpointId; + this.cluster = cluster; this.scope = Scope.region; this.zones = List.of(effectiveZone(zone)); return this; @@ -436,7 +484,10 @@ public class Endpoint { if (routingMethod.isDirect() && !port.isDefault()) { throw new IllegalArgumentException("Routing method " + routingMethod + " can only use default port"); } - return new Endpoint(endpointId, cluster, application, zones, scope, system, port, legacy, routingMethod); + if (scope == Scope.region && instance.isPresent()) { + throw new IllegalArgumentException("Instance cannot be set for scope " + scope); + } + return new Endpoint(endpointId, cluster, application, instance, zones, scope, system, port, legacy, routingMethod); } private void checkScope() { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java index 3dc95251eb8..bd6376e58aa 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java @@ -102,7 +102,7 @@ public class ApplicationPackageValidator { instant)); } - /** Verify that compactable endpoint parts (instance aname nd endpoint ID) do not clash */ + /** Verify that compactable endpoint parts (instance name and endpoint ID) do not clash */ private void validateCompactedEndpoint(ApplicationPackage applicationPackage) { Map<List<String>, InstanceEndpoint> instanceEndpoints = new HashMap<>(); for (var instanceSpec : applicationPackage.deploymentSpec().instances()) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 59d18fcf17b..51911d62ef4 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -2698,6 +2698,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private static String endpointScopeString(Endpoint.Scope scope) { switch (scope) { case region: return "region"; + case regionSplit: return "regionSplit"; case global: return "global"; case zone: return "zone"; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java index ec9cf4064c9..f93e24f969e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java @@ -111,7 +111,7 @@ public class RoutingPolicy { /** Returns the region endpoint of this */ public Endpoint regionEndpointIn(SystemName system, RoutingMethod routingMethod, boolean legacy) { - Endpoint.EndpointBuilder endpoint = endpoint(routingMethod).targetRegion(id.cluster(), id.zone()); + Endpoint.EndpointBuilder endpoint = endpoint(routingMethod).targetRegionSplit(id.cluster(), id.zone()); if (legacy) { endpoint = endpoint.legacy(); } 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 da641d17a8a..5d985518809 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 @@ -20,8 +20,10 @@ import static org.junit.Assert.assertEquals; */ public class EndpointTest { - private static final ApplicationId app1 = ApplicationId.from("t1", "a1", "default"); - private static final ApplicationId app2 = ApplicationId.from("t2", "a2", "i2"); + private static final ApplicationId instance1 = ApplicationId.from("t1", "a1", "default"); + private static final ApplicationId instance2 = ApplicationId.from("t2", "a2", "i2"); + private static final TenantAndApplicationId app1 = TenantAndApplicationId.from(instance1); + private static final TenantAndApplicationId app2 = TenantAndApplicationId.from(instance2); @Test public void global_endpoints() { @@ -30,62 +32,62 @@ public class EndpointTest { Map<String, Endpoint> tests = Map.of( // Legacy endpoint "http://a1.t1.global.vespa.yahooapis.com:4080/", - Endpoint.of(app1).target(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main), // Legacy endpoint with TLS "https://a1--t1.global.vespa.yahooapis.com:4443/", - Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main), // Main endpoint "https://a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).in(SystemName.main), // Main endpoint in CD "https://cd--a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint in CD "https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/", - Endpoint.of(app2).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance2).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint with direct routing and default TLS port "https://a1.t1.global.vespa.oath.cloud/", - Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint with custom rotation name "https://r1.a1.t1.global.vespa.oath.cloud/", - Endpoint.of(app1).target(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance1).target(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint for custom instance in default rotation "https://i2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(app2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint for custom instance with custom rotation name "https://r2.i2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(app2).target(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance2).target(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint in public system (legacy) "https://a1.t1.global.public.vespa.oath.cloud/", - Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.Public) + Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.Public) ); tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); Map<String, Endpoint> tests2 = Map.of( // Main endpoint in public CD system (legacy) "https://publiccd.a1.t1.global.public-cd.vespa.oath.cloud/", - Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.PublicCd), + Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.PublicCd), // Default endpoint in public system "https://a1.t1.g.vespa-app.cloud/", - Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public), + Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public), // Default endpoint in public CD system "https://a1.t1.g.cd.vespa-app.cloud/", - Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.PublicCd), + Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.PublicCd), // Custom instance in public system "https://i2.a2.t2.g.vespa-app.cloud/", - Endpoint.of(app2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) + Endpoint.of(instance2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) ); tests2.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); } @@ -97,54 +99,54 @@ public class EndpointTest { Map<String, Endpoint> tests = Map.of( // Legacy endpoint "http://a1.t1.global.vespa.yahooapis.com:4080/", - Endpoint.of(app1).target(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(endpointId).on(Port.plain(4080)).legacy().in(SystemName.main), // Legacy endpoint with TLS "https://a1--t1.global.vespa.yahooapis.com:4443/", - Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).legacy().in(SystemName.main), // Main endpoint "https://a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).in(SystemName.main), // Main endpoint in CD "https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/", - Endpoint.of(app2).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance2).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint in CD "https://cd--a1--t1.global.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance1).target(endpointId).on(Port.tls(4443)).in(SystemName.cd), // Main endpoint with direct routing and default TLS port "https://a1.t1.global.vespa.oath.cloud/", - Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint with custom rotation name "https://r1.a1.t1.global.vespa.oath.cloud/", - Endpoint.of(app1).target(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance1).target(EndpointId.of("r1")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint for custom instance in default rotation "https://i2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(app2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance2).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint for custom instance with custom rotation name "https://r2.i2.a2.t2.global.vespa.oath.cloud/", - Endpoint.of(app2).target(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), + Endpoint.of(instance2).target(EndpointId.of("r2")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.main), // Main endpoint in public system (legacy) "https://a1.t1.global.public.vespa.oath.cloud/", - Endpoint.of(app1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.Public) + Endpoint.of(instance1).target(endpointId).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.Public) ); tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); Map<String, Endpoint> tests2 = Map.of( // Custom endpoint and instance in public CD system (legacy) "https://foo.publiccd.i2.a2.t2.global.public-cd.vespa.oath.cloud/", - Endpoint.of(app2).target(EndpointId.of("foo")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.PublicCd), + Endpoint.of(instance2).target(EndpointId.of("foo")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.PublicCd), // Custom endpoint and instance in public system "https://foo.i2.a2.t2.g.vespa-app.cloud/", - Endpoint.of(app2).target(EndpointId.of("foo")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) + Endpoint.of(instance2).target(EndpointId.of("foo")).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public) ); tests2.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); } @@ -158,62 +160,62 @@ public class EndpointTest { Map<String, Endpoint> tests = Map.of( // Legacy endpoint (always contains environment) "http://a1.t1.us-north-1.prod.vespa.yahooapis.com:4080/", - Endpoint.of(app1).target(cluster, prodZone).on(Port.plain(4080)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(cluster, prodZone).on(Port.plain(4080)).legacy().in(SystemName.main), // Secure legacy endpoint "https://a1--t1.us-north-1.prod.vespa.yahooapis.com:4443/", - Endpoint.of(app1).target(cluster, prodZone).on(Port.tls(4443)).legacy().in(SystemName.main), + Endpoint.of(instance1).target(cluster, prodZone).on(Port.tls(4443)).legacy().in(SystemName.main), // Prod endpoint in main "https://a1--t1.us-north-1.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(cluster, prodZone).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance1).target(cluster, prodZone).on(Port.tls(4443)).in(SystemName.main), // Prod endpoint in CD "https://cd--a1--t1.us-north-1.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(cluster, prodZone).on(Port.tls(4443)).in(SystemName.cd), + Endpoint.of(instance1).target(cluster, prodZone).on(Port.tls(4443)).in(SystemName.cd), // Test endpoint in main "https://a1--t1.us-north-2.test.vespa.oath.cloud:4443/", - Endpoint.of(app1).target(cluster, testZone).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance1).target(cluster, testZone).on(Port.tls(4443)).in(SystemName.main), // Non-default cluster in main "https://c1--a1--t1.us-north-1.vespa.oath.cloud/", - Endpoint.of(app1).target(ClusterSpec.Id.from("c1"), prodZone).on(Port.tls()).in(SystemName.main), + Endpoint.of(instance1).target(ClusterSpec.Id.from("c1"), prodZone).on(Port.tls()).in(SystemName.main), // Non-default instance in main "https://i2--a2--t2.us-north-1.vespa.oath.cloud:4443/", - Endpoint.of(app2).target(cluster, prodZone).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance2).target(cluster, prodZone).on(Port.tls(4443)).in(SystemName.main), // Non-default cluster in public (legacy) "https://c1.a1.t1.us-north-1.public.vespa.oath.cloud/", - Endpoint.of(app1).target(ClusterSpec.Id.from("c1"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.Public), + Endpoint.of(instance1).target(ClusterSpec.Id.from("c1"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.Public), // Non-default cluster and instance in public (legacy) "https://c2.i2.a2.t2.us-north-1.public.vespa.oath.cloud/", - Endpoint.of(app2).target(ClusterSpec.Id.from("c2"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.Public), + Endpoint.of(instance2).target(ClusterSpec.Id.from("c2"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.Public), // Endpoint in main using shared layer 4 "https://a1.t1.us-north-1.vespa.oath.cloud/", - Endpoint.of(app1).target(cluster, prodZone).on(Port.tls()).routingMethod(RoutingMethod.sharedLayer4).in(SystemName.main) + Endpoint.of(instance1).target(cluster, prodZone).on(Port.tls()).routingMethod(RoutingMethod.sharedLayer4).in(SystemName.main) ); tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); Map<String, Endpoint> tests2 = Map.of( // Non-default cluster and instance in public CD (legacy) "https://c2.publiccd.i2.a2.t2.us-north-1.public-cd.vespa.oath.cloud/", - Endpoint.of(app2).target(ClusterSpec.Id.from("c2"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.PublicCd), + Endpoint.of(instance2).target(ClusterSpec.Id.from("c2"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).legacy().in(SystemName.PublicCd), // Custom cluster name in public "https://c1.a1.t1.us-north-1.z.vespa-app.cloud/", - Endpoint.of(app1).target(ClusterSpec.Id.from("c1"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public), + Endpoint.of(instance1).target(ClusterSpec.Id.from("c1"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public), // Default cluster name in non-production zone in public "https://a1.t1.us-north-2.test.z.vespa-app.cloud/", - Endpoint.of(app1).target(ClusterSpec.Id.from("default"), testZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public), + Endpoint.of(instance1).target(ClusterSpec.Id.from("default"), testZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.Public), // Default cluster name in public CD "https://a1.t1.us-north-1.z.cd.vespa-app.cloud/", - Endpoint.of(app1).target(ClusterSpec.Id.from("default"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.PublicCd) + Endpoint.of(instance1).target(ClusterSpec.Id.from("default"), prodZone).on(Port.tls()).routingMethod(RoutingMethod.exclusive).in(SystemName.PublicCd) ); tests2.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); } @@ -227,7 +229,7 @@ public class EndpointTest { var tests = Map.of( // Default rotation "https://a1.t1.global.public.vespa.oath.cloud/", - Endpoint.of(app1) + Endpoint.of(instance1) .target(EndpointId.defaultId()) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) @@ -236,7 +238,7 @@ public class EndpointTest { // Wildcard to match other rotations "https://*.a1.t1.global.public.vespa.oath.cloud/", - Endpoint.of(app1) + Endpoint.of(instance1) .wildcard() .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) @@ -245,7 +247,7 @@ public class EndpointTest { // Default cluster in zone "https://a1.t1.us-north-1.public.vespa.oath.cloud/", - Endpoint.of(app1) + Endpoint.of(instance1) .target(defaultCluster, prodZone) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) @@ -254,7 +256,7 @@ public class EndpointTest { // Wildcard to match other clusters in zone "https://*.a1.t1.us-north-1.public.vespa.oath.cloud/", - Endpoint.of(app1) + Endpoint.of(instance1) .wildcard(prodZone) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) @@ -263,7 +265,7 @@ public class EndpointTest { // Default cluster in test zone "https://a1.t1.us-north-2.test.public.vespa.oath.cloud/", - Endpoint.of(app1) + Endpoint.of(instance1) .target(defaultCluster, testZone) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) @@ -272,7 +274,7 @@ public class EndpointTest { // Wildcard to match other clusters in test zone "https://*.a1.t1.us-north-2.test.public.vespa.oath.cloud/", - Endpoint.of(app1) + Endpoint.of(instance1) .wildcard(testZone) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) @@ -281,7 +283,7 @@ public class EndpointTest { // Wildcard to match other clusters in zone "https://*.a1.t1.us-north-1.z.vespa-app.cloud/", - Endpoint.of(app1) + Endpoint.of(instance1) .wildcard(prodZone) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) @@ -296,37 +298,66 @@ public class EndpointTest { var cluster = ClusterSpec.Id.from("default"); var prodZone = ZoneId.from("prod", "us-north-2"); Map<String, Endpoint> tests = Map.of( - "https://a1.t1.us-north-1-w.public.vespa.oath.cloud/", + "https://r0.a1.t1.us-north-2-r.vespa.oath.cloud/", Endpoint.of(app1) - .targetRegion(cluster, ZoneId.from("prod", "us-north-1a")) + .targetRegion(EndpointId.of("r0"), ClusterSpec.Id.from("c1"), prodZone) + .routingMethod(RoutingMethod.sharedLayer4) + .on(Port.tls()) + .in(SystemName.main), + "https://r1.a2.t2.us-north-2.r.vespa-app.cloud/", + Endpoint.of(app2) + .targetRegion(EndpointId.of("r1"), ClusterSpec.Id.from("c1"), prodZone) + .routingMethod(RoutingMethod.exclusive) + .on(Port.tls()) + .in(SystemName.Public) + ); + tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); + Endpoint endpoint = Endpoint.of(instance1) + .targetRegionSplit(cluster, ZoneId.from("prod", "us-north-1a")) + .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 region_split_endpoints() { + var cluster = ClusterSpec.Id.from("default"); + var prodZone = ZoneId.from("prod", "us-north-2"); + Map<String, Endpoint> tests = Map.of( + "https://a1.t1.us-north-1-w.public.vespa.oath.cloud/", + Endpoint.of(instance1) + .targetRegionSplit(cluster, ZoneId.from("prod", "us-north-1a")) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .legacy() .in(SystemName.Public), "https://a1.t1.us-north-2-w.public.vespa.oath.cloud/", - Endpoint.of(app1) - .targetRegion(cluster, prodZone) + Endpoint.of(instance1) + .targetRegionSplit(cluster, prodZone) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .legacy() .in(SystemName.Public), "https://a1.t1.us-north-2-w.test.public.vespa.oath.cloud/", - Endpoint.of(app1) - .targetRegion(cluster, ZoneId.from("test", "us-north-2")) + Endpoint.of(instance1) + .targetRegionSplit(cluster, ZoneId.from("test", "us-north-2")) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .legacy() .in(SystemName.Public), - "https://c1.a1.t1.us-north-2.r.vespa-app.cloud/", - Endpoint.of(app1) - .targetRegion(ClusterSpec.Id.from("c1"), prodZone) + "https://c1.a1.t1.us-north-2.w.vespa-app.cloud/", + Endpoint.of(instance1) + .targetRegionSplit(ClusterSpec.Id.from("c1"), prodZone) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(SystemName.Public) ); tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.url().toString())); - Endpoint endpoint = Endpoint.of(app1) - .targetRegion(cluster, ZoneId.from("prod", "us-north-1a")) + Endpoint endpoint = Endpoint.of(instance1) + .targetRegionSplit(cluster, ZoneId.from("prod", "us-north-1a")) .routingMethod(RoutingMethod.exclusive) .on(Port.tls()) .in(SystemName.main); @@ -341,23 +372,23 @@ public class EndpointTest { var tests1 = Map.of( // With default cluster "a1.t1.us-north-1.prod", - Endpoint.of(app1).target(EndpointId.defaultId()).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance1).target(EndpointId.defaultId()).on(Port.tls(4443)).in(SystemName.main), // With non-default cluster "c1.a1.t1.us-north-1.prod", - Endpoint.of(app1).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) ); var tests2 = Map.of( // With non-default instance and default cluster "i2.a2.t2.us-north-1.prod", - Endpoint.of(app2).target(EndpointId.defaultId(), ClusterSpec.Id.from("default"), List.of(zone)).on(Port.tls(4443)).in(SystemName.main), + Endpoint.of(instance2).target(EndpointId.defaultId(), ClusterSpec.Id.from("default"), List.of(zone)).on(Port.tls(4443)).in(SystemName.main), // With non-default instance and cluster "c2.i2.a2.t2.us-north-1.prod", - Endpoint.of(app2).target(EndpointId.of("ignored2"), ClusterSpec.Id.from("c2"), List.of(zone)).on(Port.tls(4443)).in(SystemName.main) + Endpoint.of(instance2).target(EndpointId.of("ignored2"), ClusterSpec.Id.from("c2"), List.of(zone)).on(Port.tls(4443)).in(SystemName.main) ); - tests1.forEach((expected, endpoint) -> assertEquals(expected, endpoint.upstreamIdOf(new DeploymentId(app1, zone)))); - tests2.forEach((expected, endpoint) -> assertEquals(expected, endpoint.upstreamIdOf(new DeploymentId(app2, zone)))); + tests1.forEach((expected, endpoint) -> assertEquals(expected, endpoint.upstreamIdOf(new DeploymentId(instance1, zone)))); + tests2.forEach((expected, endpoint) -> assertEquals(expected, endpoint.upstreamIdOf(new DeploymentId(instance2, zone)))); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java index 2002b59dc1e..c678a83bf2c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java @@ -364,11 +364,11 @@ public class RoutingPoliciesTest { Map.of(zone1, 1L, zone2, 1L), true); assertEquals("Registers expected DNS names", Set.of("app1.tenant1.aws-eu-west-1-w.public.vespa.oath.cloud", - "app1.tenant1.aws-eu-west-1.r.vespa-app.cloud", + "app1.tenant1.aws-eu-west-1.w.vespa-app.cloud", "app1.tenant1.aws-eu-west-1a.public.vespa.oath.cloud", "app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud", "app1.tenant1.aws-us-east-1-w.public.vespa.oath.cloud", - "app1.tenant1.aws-us-east-1.r.vespa-app.cloud", + "app1.tenant1.aws-us-east-1.w.vespa-app.cloud", "app1.tenant1.aws-us-east-1c.public.vespa.oath.cloud", "app1.tenant1.aws-us-east-1c.z.vespa-app.cloud", "app1.tenant1.g.vespa-app.cloud", @@ -837,7 +837,7 @@ public class RoutingPoliciesTest { DeploymentId deployment = new DeploymentId(application, zone); EndpointList regionEndpoints = tester.controller().routing().endpointsOf(deployment) .cluster(cluster) - .scope(Endpoint.Scope.region); + .scope(Endpoint.Scope.regionSplit); if (!legacy) { regionEndpoints = regionEndpoints.not().legacy(); } |