aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2023-09-14 11:03:39 +0200
committerMartin Polden <mpolden@mpolden.no>2023-09-14 14:16:38 +0200
commit3e1350d1b4a57105c06dc393f9a3f33293bcf802 (patch)
tree298d9cd222d9c0c0df6c6b2d5104d63730a14756 /controller-server
parentf88d1eb88ba6ee14a5f8721502dcb41405a349ee (diff)
Generate IDs for declared endpoints
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java126
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java19
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java48
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpoints.java32
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java55
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java27
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java22
15 files changed, 241 insertions, 175 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index cc7d1e6070d..bf3a3e1e489 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -56,6 +56,7 @@ import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics;
import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics.Warning;
import com.yahoo.vespa.hosted.controller.application.DeploymentQuotaCalculator;
+import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.QuotaUsage;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
@@ -73,7 +74,6 @@ import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.notification.Notification;
import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
-import com.yahoo.vespa.hosted.controller.routing.GeneratedEndpoints;
import com.yahoo.vespa.hosted.controller.routing.PreparedEndpoints;
import com.yahoo.vespa.hosted.controller.security.AccessControl;
import com.yahoo.vespa.hosted.controller.security.Credentials;
@@ -671,7 +671,7 @@ public class ApplicationController {
DeploymentId deployment = new DeploymentId(application, zone);
// Routing and metadata may have changed, so we need to refresh state after deployment, even if deployment fails.
interface CleanCloseable extends AutoCloseable { void close(); }
- AtomicReference<GeneratedEndpoints> generatedEndpoints = new AtomicReference<>(GeneratedEndpoints.empty);
+ AtomicReference<EndpointList> generatedEndpoints = new AtomicReference<>(EndpointList.EMPTY);
try (CleanCloseable postDeployment = () -> updateRoutingAndMeta(deployment, applicationPackage, generatedEndpoints)) {
Optional<DockerImage> dockerImageRepo = Optional.ofNullable(
dockerImageRepoFlag
@@ -704,7 +704,7 @@ public class ApplicationController {
Supplier<DeploymentEndpoints> endpoints = () -> {
if (preparedEndpoints == null) return DeploymentEndpoints.none;
PreparedEndpoints prepared = preparedEndpoints.get();
- generatedEndpoints.set(prepared.generatedEndpoints());
+ generatedEndpoints.set(prepared.endpoints().generated());
return new DeploymentEndpoints(prepared.containerEndpoints(), prepared.certificate());
};
DeploymentData deploymentData = new DeploymentData(application, zone, applicationPackage::zipStream, platform,
@@ -715,7 +715,7 @@ public class ApplicationController {
}
}
- private void updateRoutingAndMeta(DeploymentId id, ApplicationPackageStream data, AtomicReference<GeneratedEndpoints> generatedEndpoints) {
+ private void updateRoutingAndMeta(DeploymentId id, ApplicationPackageStream data, AtomicReference<EndpointList> generatedEndpoints) {
if (id.applicationId().instance().isTester()) return;
controller.routing().of(id).activate(data.truncatedPackage().deploymentSpec(), generatedEndpoints.get());
if ( ! id.zoneId().environment().isManuallyDeployed()) return;
@@ -932,7 +932,7 @@ public class ApplicationController {
DeploymentId id = new DeploymentId(instanceId, zone);
interface CleanCloseable extends AutoCloseable { void close(); }
try (CleanCloseable postDeactivation = () -> {
- application.ifPresent(app -> controller.routing().of(id).activate(app.get().deploymentSpec(), GeneratedEndpoints.empty));
+ application.ifPresent(app -> controller.routing().of(id).activate(app.get().deploymentSpec(), EndpointList.EMPTY));
if (id.zoneId().environment().isManuallyDeployed())
applicationStore.putMetaTombstone(id, clock.instant());
if ( ! id.zoneId().environment().isTest())
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 b5729cee880..ab85fe01af0 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
@@ -31,7 +31,7 @@ import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.application.pkg.BasicServicesXml;
import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority;
-import com.yahoo.vespa.hosted.controller.routing.GeneratedEndpoints;
+import com.yahoo.vespa.hosted.controller.routing.GeneratedEndpointList;
import com.yahoo.vespa.hosted.controller.routing.PreparedEndpoints;
import com.yahoo.vespa.hosted.controller.routing.RoutingId;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicies;
@@ -55,6 +55,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -63,6 +64,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import static java.util.stream.Collectors.toMap;
@@ -120,44 +122,58 @@ public class RoutingController {
/** Prepares and returns the endpoints relevant for given deployment */
public PreparedEndpoints prepare(DeploymentId deployment, BasicServicesXml services, Optional<EndpointCertificate> certificate, LockedApplication application) {
EndpointList endpoints = EndpointList.EMPTY;
+ DeploymentSpec spec = application.get().deploymentSpec();
// Assign rotations to application
- for (var deploymentInstanceSpec : application.get().deploymentSpec().instances()) {
- if (deploymentInstanceSpec.concerns(Environment.prod)) {
- application = controller.routing().assignRotations(application, deploymentInstanceSpec.name());
+ for (var instanceSpec : spec.instances()) {
+ if (instanceSpec.concerns(Environment.prod)) {
+ application = controller.routing().assignRotations(application, instanceSpec.name());
}
}
// Add zone-scoped endpoints
- final GeneratedEndpoints generatedEndpoints;
+ Map<EndpointId, List<GeneratedEndpoint>> generatedForDeclaredEndpoints = new HashMap<>();
+ Set<ClusterSpec.Id> clustersWithToken = new HashSet<>();
if (randomizedEndpointsEnabled(deployment.applicationId())) { // TODO(mpolden): Remove this guard once config-models < 8.220 are gone
- Map<ClusterSpec.Id, List<GeneratedEndpoint>> generatedEndpointsByCluster = new HashMap<>();
RoutingPolicyList deploymentPolicies = policies().read(deployment);
for (var container : services.containers()) {
ClusterSpec.Id clusterId = ClusterSpec.Id.from(container.id());
boolean tokenSupported = container.authMethods().contains(BasicServicesXml.Container.AuthMethod.token);
+ if (tokenSupported) {
+ clustersWithToken.add(clusterId);
+ }
// Use already existing generated endpoints, if any
- List<GeneratedEndpoint> generatedForCluster = deploymentPolicies.cluster(clusterId)
- .first()
- .map(RoutingPolicy::generatedEndpoints)
- .orElseGet(List::of);
- if (generatedForCluster.isEmpty()) {
- generatedForCluster = certificate.flatMap(EndpointCertificate::randomizedId)
- .map(id -> generateEndpoints(id, deployment.applicationId(), tokenSupported))
- .orElseGet(List::of);
+ Optional<RoutingPolicy> clusterPolicy = deploymentPolicies.cluster(clusterId).first();
+ List<GeneratedEndpoint> generatedForCluster = new ArrayList<>();
+ if (clusterPolicy.isPresent()) {
+ for (var ge : clusterPolicy.get().generatedEndpoints()) {
+ if (ge.declared()) {
+ generatedForDeclaredEndpoints.computeIfAbsent(ge.endpoint().get(), (k) -> new ArrayList<>())
+ .add(ge);
+ } else {
+ generatedForCluster.add(ge);
+ }
+ }
}
- if (!generatedForCluster.isEmpty()) {
- generatedEndpointsByCluster.put(clusterId, generatedForCluster);
+ if (generatedForCluster.isEmpty()) {
+ generatedForCluster = generateEndpoints(deployment, tokenSupported, certificate, Optional.empty());
}
- endpoints = endpoints.and(endpointsOf(deployment, clusterId, generatedForCluster).scope(Scope.zone));
+ endpoints = endpoints.and(endpointsOf(deployment, clusterId, GeneratedEndpointList.copyOf(generatedForCluster)).scope(Scope.zone));
}
- generatedEndpoints = new GeneratedEndpoints(generatedEndpointsByCluster);
- } else {
- generatedEndpoints = GeneratedEndpoints.empty;
+
+ // For all declared endpoints, generate new endpoints if none exist
+ Stream.concat(spec.endpoints().stream(), spec.instances().stream().flatMap(i -> i.endpoints().stream()))
+ .forEach(endpoint -> {
+ EndpointId endpointId = EndpointId.of(endpoint.endpointId());
+ generatedForDeclaredEndpoints.computeIfAbsent(endpointId, (k) -> {
+ boolean tokenSupported = clustersWithToken.contains(ClusterSpec.Id.from(endpoint.containerId()));
+ return generateEndpoints(deployment, tokenSupported, certificate, Optional.of(endpointId));
+ });
+ });
}
// Add global- and application-scoped endpoints
- endpoints = endpoints.and(declaredEndpointsOf(application.get().id(), application.get().deploymentSpec(), generatedEndpoints).targets(deployment));
+ endpoints = endpoints.and(declaredEndpointsOf(application.get().id(), spec, generatedForDeclaredEndpoints).targets(deployment));
PreparedEndpoints prepared = new PreparedEndpoints(deployment,
endpoints,
application.get().require(deployment.applicationId().instance()).rotations(),
@@ -169,11 +185,21 @@ public class RoutingController {
return prepared;
}
+ private List<GeneratedEndpoint> generateEndpoints(DeploymentId deployment, boolean tokenSupported, Optional<EndpointCertificate> certificate, Optional<EndpointId> endpoint) {
+ return certificate.flatMap(EndpointCertificate::randomizedId)
+ .map(id -> generateEndpoints(id,
+ deployment.applicationId(),
+ tokenSupported,
+ endpoint))
+ .orElseGet(List::of);
+ }
+
// -------------- Implicit endpoints (scopes 'zone' and 'weighted') --------------
/** Returns the zone- and region-scoped endpoints of given deployment */
- public EndpointList endpointsOf(DeploymentId deployment, ClusterSpec.Id cluster, List<GeneratedEndpoint> generatedEndpoints) {
- boolean tokenSupported = generatedEndpoints.stream().anyMatch(ge -> ge.authMethod() == AuthMethod.token);
+ public EndpointList endpointsOf(DeploymentId deployment, ClusterSpec.Id cluster, GeneratedEndpointList generatedEndpoints) {
+ requireGeneratedEndpoints(generatedEndpoints, false);
+ boolean tokenSupported = !generatedEndpoints.authMethod(AuthMethod.token).isEmpty();
RoutingMethod routingMethod = controller.zoneRegistry().routingMethod(deployment.zoneId());
boolean isProduction = deployment.zoneId().environment().isProduction();
List<Endpoint> endpoints = new ArrayList<>();
@@ -216,7 +242,7 @@ public class RoutingController {
public EndpointList readEndpointsOf(DeploymentId deployment) {
Set<Endpoint> endpoints = new LinkedHashSet<>();
for (var policy : routingPolicies.read(deployment)) {
- endpoints.addAll(endpointsOf(deployment, policy.id().cluster(), policy.generatedEndpoints()).asList());
+ endpoints.addAll(endpointsOf(deployment, policy.id().cluster(), policy.generatedEndpoints().cluster()).asList());
}
return EndpointList.copyOf(endpoints);
}
@@ -224,7 +250,8 @@ public class RoutingController {
// -------------- Declared endpoints (scopes 'global' and 'application') --------------
/** Returns global endpoints pointing to given deployments */
- public EndpointList declaredEndpointsOf(RoutingId routingId, ClusterSpec.Id cluster, List<DeploymentId> deployments, GeneratedEndpoints generatedEndpoints) {
+ public EndpointList declaredEndpointsOf(RoutingId routingId, ClusterSpec.Id cluster, List<DeploymentId> deployments, GeneratedEndpointList generatedEndpoints) {
+ requireGeneratedEndpoints(generatedEndpoints, true);
var endpoints = new ArrayList<Endpoint>();
var directMethods = 0;
var availableRoutingMethods = routingMethodsOfAll(deployments);
@@ -238,7 +265,7 @@ public class RoutingController {
.on(Port.fromRoutingMethod(method))
.routingMethod(method);
endpoints.add(builder.in(controller.system()));
- for (var ge : generatedEndpoints.cluster(cluster)) {
+ for (var ge : generatedEndpoints) {
endpoints.add(builder.generatedFrom(ge).authMethod(ge.authMethod()).in(controller.system()));
}
}
@@ -247,7 +274,8 @@ public class RoutingController {
/** Returns application endpoints pointing to given deployments */
public EndpointList declaredEndpointsOf(TenantAndApplicationId application, EndpointId endpoint, ClusterSpec.Id cluster,
- Map<DeploymentId, Integer> deployments, GeneratedEndpoints generatedEndpoints) {
+ Map<DeploymentId, Integer> deployments, GeneratedEndpointList generatedEndpoints) {
+ requireGeneratedEndpoints(generatedEndpoints, true);
ZoneId zone = deployments.keySet().iterator().next().zoneId(); // Where multiple zones are possible, they all have the same routing method.
RoutingMethod routingMethod = usesSharedRouting(zone) ? RoutingMethod.sharedLayer4 : RoutingMethod.exclusive;
Endpoint.EndpointBuilder builder = Endpoint.of(application)
@@ -258,7 +286,7 @@ public class RoutingController {
.on(Port.fromRoutingMethod(routingMethod));
List<Endpoint> endpoints = new ArrayList<>();
endpoints.add(builder.in(controller.system()));
- for (var ge : generatedEndpoints.cluster(cluster)) {
+ for (var ge : generatedEndpoints) {
endpoints.add(builder.generatedFrom(ge).authMethod(ge.authMethod()).in(controller.system()));
}
return EndpointList.copyOf(endpoints);
@@ -276,7 +304,7 @@ public class RoutingController {
return readDeclaredEndpointsOf(application).instance(instance.instance());
}
- private EndpointList declaredEndpointsOf(TenantAndApplicationId application, DeploymentSpec deploymentSpec, GeneratedEndpoints generatedEndpoints) {
+ private EndpointList declaredEndpointsOf(TenantAndApplicationId application, DeploymentSpec deploymentSpec, Map<EndpointId, List<GeneratedEndpoint>> generatedEndpoints) {
Set<Endpoint> endpoints = new LinkedHashSet<>();
// Global endpoints
for (var spec : deploymentSpec.instances()) {
@@ -288,7 +316,8 @@ public class RoutingController {
ZoneId.from(Environment.prod, region)))
.toList();
ClusterSpec.Id cluster = ClusterSpec.Id.from(declaredEndpoint.containerId());
- endpoints.addAll(declaredEndpointsOf(routingId, cluster, deployments, generatedEndpoints).asList());
+ GeneratedEndpointList generatedForId = GeneratedEndpointList.copyOf(generatedEndpoints.getOrDefault(routingId.endpointId(), List.of()));
+ endpoints.addAll(declaredEndpointsOf(routingId, cluster, deployments, generatedForId).asList());
}
}
// Application endpoints
@@ -298,8 +327,9 @@ public class RoutingController {
ZoneId.from(Environment.prod, t.region())),
t -> t.weight()));
ClusterSpec.Id cluster = ClusterSpec.Id.from(declaredEndpoint.containerId());
- endpoints.addAll(declaredEndpointsOf(application, EndpointId.of(declaredEndpoint.endpointId()), cluster,
- deployments, generatedEndpoints).asList());
+ EndpointId endpointId = EndpointId.of(declaredEndpoint.endpointId());
+ GeneratedEndpointList generatedForId = GeneratedEndpointList.copyOf(generatedEndpoints.getOrDefault(endpointId, List.of()));
+ endpoints.addAll(declaredEndpointsOf(application, endpointId, cluster, deployments, generatedForId).asList());
}
return EndpointList.copyOf(endpoints);
}
@@ -382,8 +412,11 @@ public class RoutingController {
var deployments = rotation.regions().stream()
.map(region -> new DeploymentId(instance.id(), ZoneId.from(Environment.prod, region)))
.toList();
+ GeneratedEndpointList generatedForId = GeneratedEndpointList.copyOf(readMultiDeploymentGeneratedEndpoints(application.id()).getOrDefault(rotation.endpointId(), List.of()));
endpointsToRemove.addAll(declaredEndpointsOf(RoutingId.of(instance.id(), rotation.endpointId()),
- rotation.clusterId(), deployments, readMultiDeploymentGeneratedEndpoints(application.id())).asList());
+ rotation.clusterId(), deployments,
+ generatedForId)
+ .asList());
}
endpointsToRemove.forEach(endpoint -> controller.nameServiceForwarder()
.removeRecords(Record.Type.CNAME,
@@ -430,10 +463,7 @@ public class RoutingController {
}
/** Generate endpoints for all authentication methods, using given application part */
- private List<GeneratedEndpoint> generateEndpoints(String applicationPart, ApplicationId instance, boolean token) {
- if (!randomizedEndpointsEnabled(instance)) {
- return List.of();
- }
+ private List<GeneratedEndpoint> generateEndpoints(String applicationPart, ApplicationId instance, boolean token, Optional<EndpointId> endpoint) {
return Arrays.stream(AuthMethod.values())
.filter(method -> switch (method) {
case token -> token;
@@ -442,18 +472,21 @@ public class RoutingController {
})
.map(method -> new GeneratedEndpoint(GeneratedEndpoint.createPart(controller.random(true)),
applicationPart,
- method))
+ method,
+ endpoint))
.toList();
}
/** Returns generated endpoint suitable for use in endpoints whose scope is {@link Scope#multiDeployment()} */
- private GeneratedEndpoints readMultiDeploymentGeneratedEndpoints(TenantAndApplicationId application) {
- Map<ClusterSpec.Id, List<GeneratedEndpoint>> endpoints = new HashMap<>();
+ private Map<EndpointId, List<GeneratedEndpoint>> readMultiDeploymentGeneratedEndpoints(TenantAndApplicationId application) {
+ Map<EndpointId, List<GeneratedEndpoint>> endpoints = new HashMap<>();
for (var policy : policies().read(application)) {
- // The cluster part is not used in this context because multi-deployment endpoints have a user-controlled name
- endpoints.putIfAbsent(policy.id().cluster(), policy.generatedEndpoints().stream().toList());
+ Map<EndpointId, List<GeneratedEndpoint>> generatedForDeclared = policy.generatedEndpoints().declared()
+ .asList().stream()
+ .collect(Collectors.groupingBy(ge -> ge.endpoint().get()));
+ generatedForDeclared.forEach(endpoints::putIfAbsent);
}
- return new GeneratedEndpoints(endpoints);
+ return endpoints;
}
/**
@@ -496,6 +529,13 @@ public class RoutingController {
return randomizedEndpoints.with(FetchVector.Dimension.APPLICATION_ID, instance.serializedForm()).value();
}
+ private static void requireGeneratedEndpoints(GeneratedEndpointList generatedEndpoints, boolean declared) {
+ if (generatedEndpoints.asList().stream().anyMatch(ge -> ge.declared() != declared)) {
+ throw new IllegalStateException("All generated endpoints require declared=" + declared +
+ ", got " + generatedEndpoints);
+ }
+ }
+
/** Create a common name based on a hash of given application. This must be less than 64 characters long. */
private static String commonNameHashOf(ApplicationId application, SystemName system) {
@SuppressWarnings("deprecation") // for Hashing.sha1()
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 7d25f58a1a6..b93d634101e 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
@@ -77,8 +77,8 @@ public class Endpoint {
* Returns the name of this endpoint (the first component of the DNS name). This can be one of the following:
*
* - The wildcard character '*' (for wildcard endpoints, with any scope)
- * - The cluster ID (zone scope)
- * - The endpoint ID (global scope)
+ * - The cluster ID ({@link Scope#zone} and {@link Scope#weighted}
+ * - The endpoint ID ({@link Scope#global} and {@link Scope#application})
*/
public String name() {
return endpointOrClusterAsString(id, cluster);
@@ -181,7 +181,7 @@ public class Endpoint {
String portPart = port.isDefault() ? "" : ":" + port.port;
final String subdomain;
if (generated.isPresent()) {
- subdomain = generatedPart(generated.get(), name, scope, separator);
+ subdomain = generatedPart(generated.get(), separator);
} else {
subdomain = sanitize(namePart(name, separator)) +
systemPart(system, separator) +
@@ -199,12 +199,8 @@ public class Endpoint {
"/");
}
- private static String generatedPart(GeneratedEndpoint generated, String name, Scope scope, String separator) {
- return switch (scope) {
- // Endpoints with these scopes have a name part that is explicitly configured through deployment.xml
- case weighted, global, application -> sanitize(namePart(name, separator)) + generated.applicationPart();
- case zone -> generated.clusterPart() + separator + generated.applicationPart();
- };
+ private static String generatedPart(GeneratedEndpoint generated, String separator) {
+ return generated.clusterPart() + separator + generated.applicationPart();
}
private static String sanitize(String part) { // TODO: Reject reserved words
@@ -625,6 +621,9 @@ public class Endpoint {
if ((scope == Scope.weighted) != (authMethod == AuthMethod.none)) {
throw illegal(url, "Attempted to set unsupported authentication method " + authMethod + " on " + scope + " endpoint");
}
+ if (scope.multiDeployment() && generated.isPresent() && (generated.get().endpoint().isEmpty() || !generated.get().endpoint().get().equals(endpointId))) {
+ throw illegal(url, "Generated endpoint must contain a matching endpoint ID, but got " + generated.get().endpoint());
+ }
return new Endpoint(application,
instance,
endpointId,
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
index a9d6dcb08f9..8db4492356a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
@@ -3,6 +3,8 @@ package com.yahoo.vespa.hosted.controller.application;
import ai.vespa.validation.Validation;
import com.yahoo.config.provision.zone.AuthMethod;
+import java.util.Objects;
+import java.util.Optional;
import java.util.random.RandomGenerator;
import java.util.regex.Pattern;
@@ -12,15 +14,30 @@ import java.util.regex.Pattern;
*
* @author mpolden
*/
-public record GeneratedEndpoint(String clusterPart, String applicationPart, AuthMethod authMethod) {
+public record GeneratedEndpoint(String clusterPart, String applicationPart, AuthMethod authMethod, Optional<EndpointId> endpoint) {
private static final Pattern PART_PATTERN = Pattern.compile("^[a-f][a-f0-9]{7}$");
public GeneratedEndpoint {
+ Objects.requireNonNull(clusterPart);
+ Objects.requireNonNull(applicationPart);
+ Objects.requireNonNull(authMethod);
+ Objects.requireNonNull(endpoint);
+
Validation.requireMatch(clusterPart, "Cluster part", PART_PATTERN);
Validation.requireMatch(applicationPart, "Application part", PART_PATTERN);
}
+ /** Returns whether this was generated for an endpoint declared in {@link com.yahoo.config.application.api.DeploymentSpec} */
+ public boolean declared() {
+ return endpoint.isPresent();
+ }
+
+ /** Returns whether this was generated for a cluster declared in {@link com.yahoo.vespa.hosted.controller.application.pkg.BasicServicesXml} */
+ public boolean cluster() {
+ return !declared();
+ }
+
/** Create a new endpoint part, using random as a source of randomness */
public static String createPart(RandomGenerator random) {
String alphabet = "abcdef0123456789";
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
index b490995b9ec..08d603204b0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
@@ -13,6 +13,7 @@ import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.GeneratedEndpoint;
+import com.yahoo.vespa.hosted.controller.routing.GeneratedEndpointList;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
@@ -56,6 +57,7 @@ public class RoutingPolicySerializer {
private static final String clusterPartField = "clusterPart";
private static final String applicationPartField = "applicationPart";
private static final String authMethodField = "authMethod";
+ private static final String endpointIdField = "endpointId";
public Slime toSlime(List<RoutingPolicy> routingPolicies) {
var slime = new Slime();
@@ -80,6 +82,7 @@ public class RoutingPolicySerializer {
generatedEndpointObject.setString(clusterPartField, generatedEndpoint.clusterPart());
generatedEndpointObject.setString(applicationPartField, generatedEndpoint.applicationPart());
generatedEndpointObject.setString(authMethodField, authMethod(generatedEndpoint.authMethod()));
+ generatedEndpoint.endpoint().ifPresent(endpointId -> generatedEndpointObject.setString(endpointIdField, endpointId.id()));
});
});
return slime;
@@ -104,7 +107,9 @@ public class RoutingPolicySerializer {
generatedEndpointsArray.traverse((ArrayTraverser) (idx, generatedEndpointObject) ->
generatedEndpoints.add(new GeneratedEndpoint(generatedEndpointObject.field(clusterPartField).asString(),
generatedEndpointObject.field(applicationPartField).asString(),
- authMethodFromSlime(generatedEndpointObject.field(authMethodField)))));
+ authMethodFromSlime(generatedEndpointObject.field(authMethodField)),
+ SlimeUtils.optionalString(generatedEndpointObject.field(endpointIdField))
+ .map(EndpointId::of))));
}
policies.add(new RoutingPolicy(id,
SlimeUtils.optionalString(inspect.field(canonicalNameField)).map(DomainName::of),
@@ -114,7 +119,7 @@ public class RoutingPolicySerializer {
applicationEndpoints,
routingStatusFromSlime(inspect.field(globalRoutingField)),
isPublic,
- generatedEndpoints));
+ GeneratedEndpointList.copyOf(generatedEndpoints)));
});
return Collections.unmodifiableList(policies);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java
new file mode 100644
index 00000000000..13d6f465c30
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java
@@ -0,0 +1,48 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
+
+import com.yahoo.collections.AbstractFilteringList;
+import com.yahoo.config.provision.zone.AuthMethod;
+import com.yahoo.vespa.hosted.controller.application.GeneratedEndpoint;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An immutable, filterable list of {@link GeneratedEndpoint}.
+ *
+ * @author mpolden
+ */
+public class GeneratedEndpointList extends AbstractFilteringList<GeneratedEndpoint, GeneratedEndpointList> {
+
+ public static final GeneratedEndpointList EMPTY = new GeneratedEndpointList(List.of(), false);
+
+ private GeneratedEndpointList(Collection<? extends GeneratedEndpoint> items, boolean negate) {
+ super(items, negate, GeneratedEndpointList::new);
+ }
+
+ /** Returns the subset of endpoints which are generated for endpoints declared in {@link com.yahoo.config.application.api.DeploymentSpec} */
+ public GeneratedEndpointList declared() {
+ return matching(GeneratedEndpoint::declared);
+ }
+
+ /** Returns the subset endpoints which are generated for clusters declared in {@link com.yahoo.vespa.hosted.controller.application.pkg.BasicServicesXml} */
+ public GeneratedEndpointList cluster() {
+ return not().declared();
+ }
+
+ /** Returns the subset of endpoints matching given auth method */
+ public GeneratedEndpointList authMethod(AuthMethod authMethod) {
+ return matching(ge -> ge.authMethod() == authMethod);
+ }
+
+ public static GeneratedEndpointList copyOf(Collection<GeneratedEndpoint> generatedEndpoints) {
+ return generatedEndpoints.isEmpty() ? EMPTY : new GeneratedEndpointList(generatedEndpoints, false);
+ }
+
+ @Override
+ public String toString() {
+ return asList().toString();
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpoints.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpoints.java
deleted file mode 100644
index 3adbb43a7b5..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpoints.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.yahoo.vespa.hosted.controller.routing;
-
-import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.vespa.hosted.controller.application.GeneratedEndpoint;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * This represents endpoints generated by the controller for a deployment, grouped by their assigned cluster.
- *
- * @author mpolden
- */
-public record GeneratedEndpoints(Map<ClusterSpec.Id, List<GeneratedEndpoint>> endpoints) {
-
- public static final GeneratedEndpoints empty = new GeneratedEndpoints(Map.of());
-
- public GeneratedEndpoints(Map<ClusterSpec.Id, List<GeneratedEndpoint>> endpoints) {
- this.endpoints = Map.copyOf(Objects.requireNonNull(endpoints));
- endpoints.forEach((cluster, generatedEndpoints) -> {
- if (generatedEndpoints.stream().distinct().count() != generatedEndpoints.size()) {
- throw new IllegalStateException("Endpoints for " + cluster + " must be distinct, got " + generatedEndpoints);
- }
- });
- }
-
- public List<GeneratedEndpoint> cluster(ClusterSpec.Id cluster) {
- return endpoints.getOrDefault(cluster, List.of());
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java
index 133a0c4c4ca..62dc8eab1c7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java
@@ -1,6 +1,5 @@
package com.yahoo.vespa.hosted.controller.routing;
-import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
@@ -8,10 +7,8 @@ import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
-import com.yahoo.vespa.hosted.controller.application.GeneratedEndpoint;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -39,18 +36,6 @@ public record PreparedEndpoints(DeploymentId deployment,
this.certificate = Objects.requireNonNull(certificate);
}
- /** Returns the endpoints generated by this prepare */
- public GeneratedEndpoints generatedEndpoints() {
- Map<ClusterSpec.Id, List<GeneratedEndpoint>> generated = new HashMap<>();
- for (var endpoint : endpoints.generated()) {
- List<GeneratedEndpoint> clusterGenerated = generated.computeIfAbsent(endpoint.cluster(), (k) -> new ArrayList<>());
- if (!clusterGenerated.contains(endpoint.generated().get())) {
- clusterGenerated.add(endpoint.generated().get());
- }
- }
- return new GeneratedEndpoints(generated);
- }
-
/** Returns the endpoints contained in this as {@link com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint} */
public Set<ContainerEndpoint> containerEndpoints() {
Map<EndpointId, AssignedRotation> rotationsByEndpointId = rotations.stream()
@@ -75,7 +60,7 @@ public record PreparedEndpoints(DeploymentId deployment,
EndpointId endpointId = EndpointId.of(endpoint.name());
AssignedRotation rotation = rotationsByEndpointId.get(endpointId);
if (rotation == null) {
- throw new IllegalArgumentException(endpoint + " requires a rotation, but no rotation has been assigned to " + endpointId);
+ throw new IllegalStateException(endpoint + " requires a rotation, but no rotation has been assigned to " + endpointId);
}
// Include the rotation ID as a valid name of this container endpoint
// (required by global routing health checks)
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
index 7ac9b034900..c702d76218d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
@@ -109,7 +109,10 @@ public class RoutingPolicies {
* Refresh routing policies for instance in given zone. This is idempotent and changes will only be performed if
* routing configuration affecting given deployment has changed.
*/
- public void refresh(DeploymentId deployment, DeploymentSpec deploymentSpec, GeneratedEndpoints generatedEndpoints) {
+ public void refresh(DeploymentId deployment, DeploymentSpec deploymentSpec, EndpointList generatedEndpoints) {
+ if (!generatedEndpoints.not().generated().isEmpty()) {
+ throw new IllegalStateException("Generated endpoints contains non-generated, got " + generatedEndpoints);
+ }
ApplicationId instance = deployment.applicationId();
List<LoadBalancer> loadBalancers = controller.serviceRegistry().configServer()
.getLoadBalancers(instance, deployment.zoneId());
@@ -182,7 +185,7 @@ public class RoutingPolicies {
/** Update global DNS records for given global endpoint */
private void updateGlobalDnsOf(Endpoint endpoint, List<RoutingPolicy> policies,
Optional<DeploymentId> deployment, Optional<TenantAndApplicationId> owner) {
- if (endpoint.scope() != Endpoint.Scope.global) throw new IllegalArgumentException("Endpoint " + endpoint + " is not global");
+ if (endpoint.scope() != Endpoint.Scope.global) throw new IllegalStateException("Endpoint " + endpoint + " is not global");
if (deployment.isPresent() && !endpoint.deployments().contains(deployment.get())) return;
Collection<RegionEndpoint> regionEndpoints = computeRegionEndpoints(endpoint, policies);
@@ -239,7 +242,7 @@ public class RoutingPolicies {
/** Compute region endpoints and their targets from given policies */
private Collection<RegionEndpoint> computeRegionEndpoints(Endpoint parent, List<RoutingPolicy> policies) {
if (!parent.scope().multiDeployment()) {
- throw new IllegalArgumentException(parent + " has unexpected scope");
+ throw new IllegalStateException(parent + " has unexpected scope, got " + parent.scope());
}
Map<Endpoint, RegionEndpoint> endpoints = new LinkedHashMap<>();
for (var policy : policies) {
@@ -253,7 +256,7 @@ public class RoutingPolicies {
EndpointList weightedEndpoints = controller.routing()
.endpointsOf(policy.id().deployment(),
policy.id().cluster(),
- parent.generated().stream().toList())
+ policy.generatedEndpoints().cluster())
.scope(Endpoint.Scope.weighted);
if (generated) {
weightedEndpoints = weightedEndpoints.generated();
@@ -368,29 +371,24 @@ public class RoutingPolicies {
*
* @return the updated policies
*/
- private RoutingPolicyList storePoliciesOf(LoadBalancerAllocation allocation, RoutingPolicyList applicationPolicies, GeneratedEndpoints generatedEndpoints, @SuppressWarnings("unused") Mutex lock) {
+ private RoutingPolicyList storePoliciesOf(LoadBalancerAllocation allocation, RoutingPolicyList applicationPolicies, EndpointList generatedEndpoints, @SuppressWarnings("unused") Mutex lock) {
Map<RoutingPolicyId, RoutingPolicy> policies = new LinkedHashMap<>(applicationPolicies.instance(allocation.deployment.applicationId()).asMap());
for (LoadBalancer loadBalancer : allocation.loadBalancers) {
if (loadBalancer.hostname().isEmpty() && loadBalancer.ipAddress().isEmpty()) continue;
- var policyId = new RoutingPolicyId(loadBalancer.application(), loadBalancer.cluster(), allocation.deployment.zoneId());
- var existingPolicy = policies.get(policyId);
- var dnsZone = loadBalancer.ipAddress().isPresent() ? Optional.of("ignored") : loadBalancer.dnsZone();
- var clusterGeneratedEndpoints = generatedEndpoints.cluster(loadBalancer.cluster());
+ RoutingPolicyId policyId = new RoutingPolicyId(loadBalancer.application(), loadBalancer.cluster(), allocation.deployment.zoneId());
+ RoutingPolicy existingPolicy = policies.get(policyId);
+ Optional<String> dnsZone = loadBalancer.ipAddress().isPresent() ? Optional.of("ignored") : loadBalancer.dnsZone();
+ List<GeneratedEndpoint> clusterGeneratedEndpoints = generatedEndpoints.cluster(loadBalancer.cluster())
+ .mapToList(e -> e.generated().get());
+ clusterGeneratedEndpoints.forEach(ge -> requireNonClashing(ge, applicationPolicies.without(existingPolicy)));
var newPolicy = new RoutingPolicy(policyId, loadBalancer.hostname(), loadBalancer.ipAddress(), dnsZone,
allocation.instanceEndpointsOf(loadBalancer),
allocation.applicationEndpointsOf(loadBalancer),
RoutingStatus.DEFAULT,
loadBalancer.isPublic(),
- clusterGeneratedEndpoints);
- boolean addingGeneratedEndpoints = !clusterGeneratedEndpoints.isEmpty() && (existingPolicy == null || existingPolicy.generatedEndpoints().isEmpty());
- if (addingGeneratedEndpoints) {
- clusterGeneratedEndpoints.forEach(ge -> requireNonClashing(ge, applicationPolicies));
- }
+ GeneratedEndpointList.copyOf(clusterGeneratedEndpoints));
if (existingPolicy != null) {
- newPolicy = newPolicy.with(existingPolicy.routingStatus()); // Always preserve routing status
- if (!addingGeneratedEndpoints) {
- newPolicy = newPolicy.with(existingPolicy.generatedEndpoints()); // Endpoints are generated once
- }
+ newPolicy = newPolicy.with(existingPolicy.routingStatus()); // Always preserve routing status
}
updateZoneDnsOf(newPolicy, loadBalancer, allocation.deployment);
policies.put(newPolicy.id(), newPolicy);
@@ -404,7 +402,7 @@ public class RoutingPolicies {
private void updateZoneDnsOf(RoutingPolicy policy, LoadBalancer loadBalancer, DeploymentId deploymentId) {
EndpointList zoneEndpoints = controller.routing().endpointsOf(deploymentId,
policy.id().cluster(),
- policy.generatedEndpoints())
+ policy.generatedEndpoints().cluster())
.scope(Endpoint.Scope.zone);
for (var endpoint : zoneEndpoints) {
RecordName name = RecordName.from(endpoint.dnsName());
@@ -488,7 +486,7 @@ public class RoutingPolicies {
for (var policy : removable) {
EndpointList zoneEndpoints = controller.routing().endpointsOf(allocation.deployment,
policy.id().cluster(),
- policy.generatedEndpoints())
+ policy.generatedEndpoints().cluster())
.scope(Endpoint.Scope.zone);
for (var endpoint : zoneEndpoints) {
Record.Type type = policy.canonicalName().isPresent() ? Record.Type.CNAME : Record.Type.A;
@@ -516,8 +514,8 @@ public class RoutingPolicies {
Set<Endpoint> endpoints = new LinkedHashSet<>();
policyByCluster.forEach((cluster, clusterPolicies) -> {
List<DeploymentId> deployments = clusterPolicies.stream().map(p -> p.id().deployment()).toList();
- List<GeneratedEndpoint> generated = clusterPolicies.stream().flatMap(p -> p.generatedEndpoints().stream()).distinct().toList();
- endpoints.addAll(controller.routing().declaredEndpointsOf(id, cluster, deployments, new GeneratedEndpoints(Map.of(cluster, generated)))
+ GeneratedEndpointList generated = declaredGeneratedEndpoints(clusterPolicies);
+ endpoints.addAll(controller.routing().declaredEndpointsOf(id, cluster, deployments, generated)
.not().requiresRotation()
.named(id.endpointId(), Endpoint.Scope.global).asList());
});
@@ -554,9 +552,9 @@ public class RoutingPolicies {
Map<DeploymentId, Integer> deployments = clusterPolicies.stream()
.map(p -> p.id().deployment())
.collect(Collectors.toMap(Function.identity(), (ignored) -> 1));
- List<GeneratedEndpoint> generated = clusterPolicies.stream().flatMap(p -> p.generatedEndpoints().stream()).distinct().toList();
+ GeneratedEndpointList generated = declaredGeneratedEndpoints(clusterPolicies);
endpoints.addAll(controller.routing().declaredEndpointsOf(application, id.endpointId(), cluster,
- deployments, new GeneratedEndpoints(Map.of(cluster, generated))).asList());
+ deployments, generated).asList());
});
for (var policy : policies) {
if (!policy.appliesTo(allocation.deployment)) continue;
@@ -588,6 +586,13 @@ public class RoutingPolicies {
return routingIdsFrom(allocation, true);
}
+ private static GeneratedEndpointList declaredGeneratedEndpoints(List<RoutingPolicy> clusterPolicies) {
+ return GeneratedEndpointList.copyOf(clusterPolicies.stream()
+ .flatMap(p -> p.generatedEndpoints().declared().asList().stream())
+ .distinct()
+ .toList());
+ }
+
/** Compute routing IDs from given load balancers */
private static Set<RoutingId> routingIdsFrom(LoadBalancerAllocation allocation, boolean applicationLevel) {
Set<RoutingId> routingIds = new LinkedHashSet<>();
@@ -760,7 +765,7 @@ public class RoutingPolicies {
for (var policy : applicationPolicies) {
for (var ge : policy.generatedEndpoints()) {
if (ge.clusterPart().equals(generatedEndpoint.clusterPart())) {
- throw new IllegalArgumentException(generatedEndpoint + " clashes with " + ge + " in " + policy.id());
+ throw new IllegalStateException(generatedEndpoint + " clashes with " + ge + " in " + policy.id());
}
}
}
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 2363524e306..39b25f76cce 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
@@ -5,9 +5,7 @@ import ai.vespa.http.DomainName;
import com.google.common.collect.ImmutableSortedSet;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
-import com.yahoo.vespa.hosted.controller.application.GeneratedEndpoint;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -26,12 +24,12 @@ public record RoutingPolicy(RoutingPolicyId id,
Set<EndpointId> applicationEndpoints,
RoutingStatus routingStatus,
boolean isPublic,
- List<GeneratedEndpoint> generatedEndpoints) {
+ GeneratedEndpointList generatedEndpoints) {
/** DO NOT USE. Public for serialization purposes */
public RoutingPolicy(RoutingPolicyId id, Optional<DomainName> canonicalName, Optional<String> ipAddress, Optional<String> dnsZone,
Set<EndpointId> instanceEndpoints, Set<EndpointId> applicationEndpoints, RoutingStatus routingStatus, boolean isPublic,
- List<GeneratedEndpoint> generatedEndpoints) {
+ GeneratedEndpointList generatedEndpoints) {
this.id = Objects.requireNonNull(id, "id must be non-null");
this.canonicalName = Objects.requireNonNull(canonicalName, "canonicalName must be non-null");
this.ipAddress = Objects.requireNonNull(ipAddress, "ipAddress must be non-null");
@@ -40,7 +38,7 @@ public record RoutingPolicy(RoutingPolicyId id,
this.applicationEndpoints = ImmutableSortedSet.copyOf(Objects.requireNonNull(applicationEndpoints, "applicationEndpoints must be non-null"));
this.routingStatus = Objects.requireNonNull(routingStatus, "status must be non-null");
this.isPublic = isPublic;
- this.generatedEndpoints = List.copyOf(Objects.requireNonNull(generatedEndpoints, "generatedEndpoints must be non-null"));
+ this.generatedEndpoints = Objects.requireNonNull(generatedEndpoints, "generatedEndpoints must be non-null");
if (canonicalName.isEmpty() == ipAddress.isEmpty())
throw new IllegalArgumentException("Exactly 1 of canonicalName=%s and ipAddress=%s must be set".formatted(
@@ -81,8 +79,8 @@ public record RoutingPolicy(RoutingPolicyId id,
return applicationEndpoints;
}
- /** The endpoints to generate for this policy, if any */
- public List<GeneratedEndpoint> generatedEndpoints() {
+ /** The endpoints generated for this policy, if any */
+ public GeneratedEndpointList generatedEndpoints() {
return generatedEndpoints;
}
@@ -107,10 +105,6 @@ public record RoutingPolicy(RoutingPolicyId id,
return new RoutingPolicy(id, canonicalName, ipAddress, dnsZone, instanceEndpoints, applicationEndpoints, routingStatus, isPublic, generatedEndpoints);
}
- public RoutingPolicy with(List<GeneratedEndpoint> generatedEndpoints) {
- return new RoutingPolicy(id, canonicalName, ipAddress, dnsZone, instanceEndpoints, applicationEndpoints, routingStatus, isPublic, generatedEndpoints);
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java
index 366c28a6be0..68ccd9143df 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java
@@ -75,6 +75,13 @@ public class RoutingPolicyList extends AbstractFilteringList<RoutingPolicy, Rout
return copyOf(copy);
}
+ /** Returns a copy of this excluding the given policy */
+ public RoutingPolicyList without(RoutingPolicy policy) {
+ List<RoutingPolicy> copy = new ArrayList<>(asList());
+ copy.remove(policy);
+ return copyOf(copy);
+ }
+
/** Create a routing table for instance-level endpoints backed by routing policies in this */
Map<RoutingId, List<RoutingPolicy>> asInstanceRoutingTable() {
return asRoutingTable(false);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
index 64a969a9c9d..fe1e3d3d880 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
@@ -11,8 +11,8 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
+import com.yahoo.vespa.hosted.controller.application.EndpointList;
import com.yahoo.vespa.hosted.controller.application.pkg.BasicServicesXml;
-import com.yahoo.vespa.hosted.controller.routing.GeneratedEndpoints;
import com.yahoo.vespa.hosted.controller.routing.PreparedEndpoints;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
@@ -50,7 +50,7 @@ public abstract class DeploymentRoutingContext implements RoutingContext {
}
/** Finalize routing configuration for the deployment in this context, using given deployment spec */
- public final void activate(DeploymentSpec deploymentSpec, GeneratedEndpoints generatedEndpoints) {
+ public final void activate(DeploymentSpec deploymentSpec, EndpointList generatedEndpoints) {
routing.policies().refresh(deployment, deploymentSpec, generatedEndpoints);
}
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 cc7a001b0b4..79e40e61387 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
@@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -266,11 +267,11 @@ public class EndpointTest {
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.Public),
- "https://c1.cafed00d.us-north-2.w.vespa-app.cloud/",
+ "https://deadbeef.cafed00d.us-north-2.w.vespa-app.cloud/",
Endpoint.of(instance1)
.targetRegion(ClusterSpec.Id.from("c1"), prodZone)
.routingMethod(RoutingMethod.exclusive)
- .generatedFrom(new GeneratedEndpoint("deadbeef", "cafed00d", AuthMethod.mtls))
+ .generatedFrom(new GeneratedEndpoint("deadbeef", "cafed00d", AuthMethod.mtls, Optional.empty()))
.on(Port.tls())
.in(SystemName.Public)
);
@@ -354,31 +355,27 @@ public class EndpointTest {
@Test
public void generated_id() {
- GeneratedEndpoint ge = new GeneratedEndpoint("cafed00d", "deadbeef", AuthMethod.mtls);
+ GeneratedEndpoint ge1 = new GeneratedEndpoint("cafed00d", "deadbeef", AuthMethod.mtls, Optional.empty());
+ GeneratedEndpoint ge2 = new GeneratedEndpoint("dead2bad", "deadbeef", AuthMethod.mtls, Optional.of(EndpointId.of("foo")));
var deployment = new DeploymentId(instance1, ZoneId.from("prod", "us-north-1"));
var tests = Map.of(
// Zone endpoint in main, unlike named endpoints, this includes the scope symbol 'z'
"cafed00d.deadbeef.z.vespa.oath.cloud",
- Endpoint.of(instance1).target(ClusterSpec.Id.from("c1"), deployment).generatedFrom(ge)
+ Endpoint.of(instance1).target(ClusterSpec.Id.from("c1"), deployment).generatedFrom(ge1)
.routingMethod(RoutingMethod.sharedLayer4).on(Port.tls()).in(SystemName.main),
// Zone endpoint in public
"cafed00d.deadbeef.z.vespa-app.cloud",
- Endpoint.of(instance1).target(ClusterSpec.Id.from("c1"), deployment).generatedFrom(ge)
+ Endpoint.of(instance1).target(ClusterSpec.Id.from("c1"), deployment).generatedFrom(ge1)
.routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public),
// Global endpoint in public
- "foo.deadbeef.g.vespa-app.cloud",
+ "dead2bad.deadbeef.g.vespa-app.cloud",
Endpoint.of(instance1).target(EndpointId.of("foo"), ClusterSpec.Id.from("c1"), List.of(deployment))
- .generatedFrom(ge)
- .routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public),
- // Global endpoint in public, with default ID
- "deadbeef.g.vespa-app.cloud",
- Endpoint.of(instance1).target(EndpointId.defaultId(), ClusterSpec.Id.from("c1"), List.of(deployment))
- .generatedFrom(ge)
+ .generatedFrom(ge2)
.routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public),
// Application endpoint in public
- "bar.deadbeef.a.vespa-app.cloud",
- Endpoint.of(TenantAndApplicationId.from(instance1)).targetApplication(EndpointId.of("bar"), deployment)
- .generatedFrom(ge)
+ "dead2bad.deadbeef.a.vespa-app.cloud",
+ Endpoint.of(TenantAndApplicationId.from(instance1)).targetApplication(EndpointId.of("foo"), deployment)
+ .generatedFrom(ge2)
.routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public)
);
tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.dnsName()));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
index d9007910541..007e57cc615 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.zone.AuthMethod;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.GeneratedEndpoint;
+import com.yahoo.vespa.hosted.controller.routing.GeneratedEndpointList;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicy;
import com.yahoo.vespa.hosted.controller.routing.RoutingPolicyId;
import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
@@ -47,7 +48,7 @@ public class RoutingPolicySerializerTest {
Set.of(),
RoutingStatus.DEFAULT,
false,
- List.of(new GeneratedEndpoint("deadbeef", "cafed00d", AuthMethod.mtls))),
+ GeneratedEndpointList.copyOf(List.of(new GeneratedEndpoint("deadbeef", "cafed00d", AuthMethod.mtls, Optional.of(EndpointId.of("foo")))))),
new RoutingPolicy(id2,
Optional.of(HostName.of("long-and-ugly-name-2")),
Optional.empty(),
@@ -58,7 +59,7 @@ public class RoutingPolicySerializerTest {
RoutingStatus.Agent.tenant,
Instant.ofEpochSecond(123)),
true,
- List.of(new GeneratedEndpoint("cafed00d", "deadbeef", AuthMethod.token))),
+ GeneratedEndpointList.copyOf(List.of(new GeneratedEndpoint("cafed00d", "deadbeef", AuthMethod.token, Optional.empty())))),
new RoutingPolicy(id1,
Optional.empty(),
Optional.of("127.0.0.1"),
@@ -67,7 +68,7 @@ public class RoutingPolicySerializerTest {
applicationEndpoints,
RoutingStatus.DEFAULT,
true,
- List.of()));
+ GeneratedEndpointList.EMPTY));
var serialized = serializer.fromSlime(owner, serializer.toSlime(policies));
assertEquals(policies.size(), serialized.size());
for (Iterator<RoutingPolicy> it1 = policies.iterator(), it2 = serialized.iterator(); it1.hasNext(); ) {
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 bfc6d15eedb..45ee1cca4cc 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
@@ -1030,24 +1030,24 @@ public class RoutingPoliciesTest {
// Deployment creates generated zone names
List<String> expectedRecords = List.of(
// save me, jebus!
- "b22ab332.cafed00d.z.vespa-app.cloud",
- "b7e79800.cafed00d.z.vespa-app.cloud",
- "b8ee0967.cafed00d.z.vespa-app.cloud",
+ "b36bf591.cafed00d.aws-us-east-1.w.vespa-app.cloud",
+ "b36bf591.cafed00d.z.vespa-app.cloud",
"bar.app1.tenant1.a.vespa-app.cloud",
- "bar.cafed00d.a.vespa-app.cloud",
+ "bc50b636.cafed00d.z.vespa-app.cloud",
"c0.app1.tenant1.aws-eu-west-1.w.vespa-app.cloud",
"c0.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1.w.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
- "c0.cafed00d.aws-eu-west-1.w.vespa-app.cloud",
- "c0.cafed00d.aws-us-east-1.w.vespa-app.cloud",
"c1.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
"c1.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
- "c60d3149.cafed00d.z.vespa-app.cloud",
- "cbff1506.cafed00d.z.vespa-app.cloud",
- "d151139b.cafed00d.z.vespa-app.cloud",
- "foo.app1.tenant1.g.vespa-app.cloud",
- "foo.cafed00d.g.vespa-app.cloud"
+ "c33db5ed.cafed00d.z.vespa-app.cloud",
+ "d71005bf.cafed00d.z.vespa-app.cloud",
+ "dd0971b4.cafed00d.aws-eu-west-1.w.vespa-app.cloud",
+ "dd0971b4.cafed00d.z.vespa-app.cloud",
+ "eb48ad53.cafed00d.z.vespa-app.cloud",
+ "f2fa41ec.cafed00d.g.vespa-app.cloud",
+ "f4a4d111.cafed00d.a.vespa-app.cloud",
+ "foo.app1.tenant1.g.vespa-app.cloud"
);
assertEquals(expectedRecords, tester.recordNames());
assertEquals(4, tester.policiesOf(context.instanceId()).size());