diff options
Diffstat (limited to 'controller-server')
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()); |