diff options
Diffstat (limited to 'controller-server/src')
11 files changed, 160 insertions, 65 deletions
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 90c4a506f10..b763af1af9d 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 @@ -51,7 +51,6 @@ import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -135,7 +134,7 @@ public class RoutingController { } // Add zone-scoped endpoints - Map<EndpointId, GeneratedEndpointList> generatedForDeclaredEndpoints = new HashMap<>(); + Map<EndpointId, List<GeneratedEndpoint>> generatedForDeclaredEndpoints = new HashMap<>(); Set<ClusterSpec.Id> clustersWithToken = new HashSet<>(); boolean generatedEndpointsEnabled = generatedEndpointsEnabled(deployment.applicationId()); RoutingPolicyList applicationPolicies = policies().read(TenantAndApplicationId.from(deployment.applicationId())); @@ -149,9 +148,10 @@ public class RoutingController { Optional<RoutingPolicy> clusterPolicy = deploymentPolicies.cluster(clusterId).first(); List<GeneratedEndpoint> generatedForCluster = clusterPolicy.map(policy -> policy.generatedEndpoints().cluster().asList()) .orElseGet(List::of); - // Generate endpoints if cluster does not have any - if (generatedForCluster.isEmpty()) { - generatedForCluster = generateEndpoints(tokenSupported, certificate, Optional.empty()); + // Generate endpoint for each auth method, if not present + generatedForCluster = generateEndpoints(AuthMethod.mtls, certificate, Optional.empty(), generatedForCluster); + if (tokenSupported) { + generatedForCluster = generateEndpoints(AuthMethod.token, certificate, Optional.empty(), generatedForCluster); } GeneratedEndpointList generatedEndpoints = generatedEndpointsEnabled ? GeneratedEndpointList.copyOf(generatedForCluster) : GeneratedEndpointList.EMPTY; endpoints = endpoints.and(endpointsOf(deployment, clusterId, generatedEndpoints).scope(Scope.zone)); @@ -162,18 +162,34 @@ public class RoutingController { ClusterSpec.Id clusterId = ClusterSpec.Id.from(container.id()); applicationPolicies.cluster(clusterId).asList().stream() .flatMap(policy -> policy.generatedEndpoints().declared().asList().stream()) - .forEach(ge -> generatedForDeclaredEndpoints.computeIfAbsent(ge.endpoint().get(), (k) -> GeneratedEndpointList.of(ge))); + .forEach(ge -> { + List<GeneratedEndpoint> generated = generatedForDeclaredEndpoints.computeIfAbsent(ge.endpoint().get(), (k) -> new ArrayList<>()); + if (!generated.contains(ge)) { + generated.add(ge); + } + }); } // Generate endpoints if declared endpoint does not have any 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) -> { + generatedForDeclaredEndpoints.compute(endpointId, (k, old) -> { + if (old == null) { + old = List.of(); + } + List<GeneratedEndpoint> generatedEndpoints = generateEndpoints(AuthMethod.mtls, certificate, Optional.of(endpointId), old); boolean tokenSupported = clustersWithToken.contains(ClusterSpec.Id.from(endpoint.containerId())); - return generatedEndpointsEnabled ? GeneratedEndpointList.copyOf(generateEndpoints(tokenSupported, certificate, Optional.of(endpointId))) : null; + if (tokenSupported){ + generatedEndpoints = generateEndpoints(AuthMethod.token, certificate, Optional.of(endpointId), generatedEndpoints); + } + return generatedEndpoints; }); }); - Map<EndpointId, GeneratedEndpointList> generatedEndpoints = generatedEndpointsEnabled ? generatedForDeclaredEndpoints : Map.of(); + Map<EndpointId, GeneratedEndpointList> generatedEndpoints = generatedEndpointsEnabled + ? generatedForDeclaredEndpoints.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, kv -> GeneratedEndpointList.copyOf(kv.getValue()))) + : Map.of(); endpoints = endpoints.and(declaredEndpointsOf(application.get().id(), spec, generatedEndpoints).targets(deployment)); PreparedEndpoints prepared = new PreparedEndpoints(deployment, endpoints, @@ -186,12 +202,6 @@ public class RoutingController { return prepared; } - private List<GeneratedEndpoint> generateEndpoints(boolean tokenSupported, Optional<EndpointCertificate> certificate, Optional<EndpointId> endpoint) { - return certificate.flatMap(EndpointCertificate::randomizedId) - .map(id -> generateEndpoints(id, tokenSupported, endpoint)) - .orElseGet(List::of); - } - // -------------- Implicit endpoints (scopes 'zone' and 'weighted') -------------- /** Returns the zone- and region-scoped endpoints of given deployment */ @@ -480,19 +490,22 @@ public class RoutingController { } } - /** Generate endpoints for all authentication methods, using given application part */ - private List<GeneratedEndpoint> generateEndpoints(String applicationPart, boolean token, Optional<EndpointId> endpoint) { - return Arrays.stream(AuthMethod.values()) - .filter(method -> switch (method) { - case token -> token; - case mtls -> true; - case none -> false; - }) - .map(method -> new GeneratedEndpoint(GeneratedEndpoint.createPart(controller.random(true)), - applicationPart, - method, - endpoint)) - .toList(); + /** Returns generated endpoints. A new endpoint is generated if no matching endpoint already exists */ + private List<GeneratedEndpoint> generateEndpoints(AuthMethod authMethod, Optional<EndpointCertificate> certificate, + Optional<EndpointId> declaredEndpoint, + List<GeneratedEndpoint> current) { + if (current.stream().anyMatch(e -> e.authMethod() == authMethod && e.endpoint().equals(declaredEndpoint))) { + return current; + } + Optional<String> applicationPart = certificate.flatMap(EndpointCertificate::generatedId); + if (applicationPart.isPresent()) { + current = new ArrayList<>(current); + current.add(new GeneratedEndpoint(GeneratedEndpoint.createPart(controller.random(true)), + applicationPart.get(), + authMethod, + declaredEndpoint)); + } + return current; } /** Generate the cluster part of a {@link GeneratedEndpoint} for use in a {@link Endpoint.Scope#weighted} endpoint */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java index 8f3a02528fe..6a634cb8f53 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java @@ -9,7 +9,6 @@ import org.w3c.dom.Element; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.TreeSet; /** * A partially parsed variant of services.xml, for use by the {@link com.yahoo.vespa.hosted.controller.Controller}. @@ -40,7 +39,9 @@ public record BasicServicesXml(List<Container> containers) { for (var childNode : XML.getChildren(root)) { if (childNode.getTagName().equals(CONTAINER_TAG)) { String id = childNode.getAttribute("id"); - if (id.isEmpty()) throw new IllegalArgumentException(CONTAINER_TAG + " tag requires 'id' attribute"); + if (id.isEmpty()) { + id = CONTAINER_TAG; // ID defaults to tag name when unset. See ConfigModelBuilder::getIdString + } List<Container.AuthMethod> methods = new ArrayList<>(); List<TokenId> tokens = new ArrayList<>(); parseAuthMethods(childNode, methods, tokens); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java index b5e012253c7..ec2ef4b7ff8 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java @@ -119,11 +119,11 @@ public class EndpointCertificates { TenantAndApplicationId application = TenantAndApplicationId.from(instance.id()); Optional<AssignedCertificate> perInstanceAssignedCertificate = curator.readAssignedCertificate(application, Optional.of(instance.name())); - if (perInstanceAssignedCertificate.isPresent() && perInstanceAssignedCertificate.get().certificate().randomizedId().isPresent()) { + if (perInstanceAssignedCertificate.isPresent() && perInstanceAssignedCertificate.get().certificate().generatedId().isPresent()) { return updateLastRequested(perInstanceAssignedCertificate.get()).certificate(); } else if (! zone.environment().isManuallyDeployed()) { Optional<AssignedCertificate> perApplicationAssignedCertificate = curator.readAssignedCertificate(application, Optional.empty()); - if (perApplicationAssignedCertificate.isPresent() && perApplicationAssignedCertificate.get().certificate().randomizedId().isPresent()) { + if (perApplicationAssignedCertificate.isPresent() && perApplicationAssignedCertificate.get().certificate().generatedId().isPresent()) { return updateLastRequested(perApplicationAssignedCertificate.get()).certificate(); } } @@ -181,7 +181,7 @@ public class EndpointCertificates { boolean legacyNames = assignLegacyNames.with(FetchVector.Dimension.INSTANCE_ID, instance.id().serializedForm()) .with(FetchVector.Dimension.APPLICATION_ID, instance.id().toSerializedFormWithoutInstance()).value(); - var requiredSansForZone = legacyNames || currentCertificate.get().randomizedId().isEmpty() ? + var requiredSansForZone = legacyNames || currentCertificate.get().generatedId().isEmpty() ? controller.routing().certificateDnsNames(deployment, deploymentSpec) : List.<String>of(); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java index 3a8580b7eb5..1566949664b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java @@ -14,13 +14,13 @@ import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCe public record UnassignedCertificate(EndpointCertificate certificate, UnassignedCertificate.State state) { public UnassignedCertificate { - if (certificate.randomizedId().isEmpty()) { - throw new IllegalArgumentException("randomizedId must be set for a pooled certificate"); + if (certificate.generatedId().isEmpty()) { + throw new IllegalArgumentException("generatedId must be set for a pooled certificate"); } } public String id() { - return certificate.randomizedId().get(); + return certificate.generatedId().get(); } public UnassignedCertificate withState(State state) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java index ed383175cc3..a07b6e14625 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java @@ -106,7 +106,7 @@ public class CertificatePoolMaintainer extends ControllerMaintainer { curator.readAssignedCertificates().stream() .map(AssignedCertificate::certificate) - .map(EndpointCertificate::randomizedId) + .map(EndpointCertificate::generatedId) .forEach(id -> id.ifPresent(existingNames::add)); String id = generateRandomId(); @@ -122,7 +122,7 @@ public class CertificatePoolMaintainer extends ControllerMaintainer { Optional.empty(), endpointCertificateAlgo.value(), useAlternateCertProvider.value()) - .withRandomizedId(id); + .withGeneratedId(id); UnassignedCertificate certificate = new UnassignedCertificate(f, UnassignedCertificate.State.requested); curator.writeUnassignedCertificate(certificate); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java index 805bf3d7ada..f4936dcfa8b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java @@ -11,7 +11,6 @@ import com.yahoo.container.jdisc.secretstore.SecretStore; import com.yahoo.transaction.Mutex; import com.yahoo.transaction.NestedTransaction; import com.yahoo.vespa.flags.BooleanFlag; -import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.IntFlag; import com.yahoo.vespa.flags.PermanentFlags; @@ -280,7 +279,7 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer { */ assignedCertificates.stream() .filter(c -> c.instance().isPresent()) - .filter(c -> c.certificate().randomizedId().isEmpty()) + .filter(c -> c.certificate().generatedId().isEmpty()) .filter(c -> controller().applications().getApplication(c.application()).isPresent()) // In case application has been deleted, but certificate is pending deletion .limit(assignRandomizedIdRate.value()) .forEach(c -> assignRandomizedId(c.application(), c.instance().get())); @@ -300,7 +299,7 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer { log.log(Level.INFO, "Assigned certificate missing for " + tenantAndApplicationId.instance(instanceName).toFullString() + " when assigning randomized id"); } // Verify that the assigned certificate still does not have randomized id assigned - if (assignedCertificate.get().certificate().randomizedId().isPresent()) return; + if (assignedCertificate.get().certificate().generatedId().isPresent()) return; controller().applications().lockApplicationOrThrow(tenantAndApplicationId, application -> { DeploymentSpec deploymentSpec = application.get().deploymentSpec(); @@ -320,7 +319,7 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer { EndpointCertificate withRandomNames = requestRandomNames( tenantAndApplicationId, instanceLevelAssignedCertificate.instance(), - applicationLevelAssignedCertificate.get().certificate().randomizedId() + applicationLevelAssignedCertificate.get().certificate().generatedId() .orElseThrow(() -> new IllegalArgumentException("Application certificate already assigned to " + tenantAndApplicationId.toString() + ", but random id is missing")), Optional.of(instanceLevelAssignedCertificate.certificate())); AssignedCertificate assignedCertWithRandomNames = instanceLevelAssignedCertificate.with(withRandomNames); @@ -365,12 +364,12 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer { previousRequest, endpointCertificateAlgo.value(), useAlternateCertProvider.value()) - .withRandomizedId(randomId); + .withGeneratedId(randomId); } private String generateRandomId() { List<String> unassignedIds = curator.readUnassignedCertificates().stream().map(UnassignedCertificate::id).toList(); - List<String> assignedIds = curator.readAssignedCertificates().stream().map(AssignedCertificate::certificate).map(EndpointCertificate::randomizedId).filter(Optional::isPresent).map(Optional::get).toList(); + List<String> assignedIds = curator.readAssignedCertificates().stream().map(AssignedCertificate::certificate).map(EndpointCertificate::generatedId).filter(Optional::isPresent).map(Optional::get).toList(); Set<String> allIds = Stream.concat(unassignedIds.stream(), assignedIds.stream()).collect(Collectors.toSet()); String randomId; do { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java index fae9ea1e0e3..2ff4f1fb194 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java @@ -35,7 +35,7 @@ public class EndpointCertificateSerializer { private final static String issuerField = "issuer"; private final static String expiryField = "expiry"; private final static String lastRefreshedField = "lastRefreshed"; - private final static String randomizedIdField = "randomizedId"; + private final static String generatedIdField = "randomizedId"; public static Slime toSlime(EndpointCertificate cert) { Slime slime = new Slime(); @@ -56,7 +56,7 @@ public class EndpointCertificateSerializer { object.setString(issuerField, cert.issuer()); cert.expiry().ifPresent(expiry -> object.setLong(expiryField, expiry)); cert.lastRefreshed().ifPresent(refreshTime -> object.setLong(lastRefreshedField, refreshTime)); - cert.randomizedId().ifPresent(randomizedId -> object.setString(randomizedIdField, randomizedId)); + cert.generatedId().ifPresent(id -> object.setString(generatedIdField, id)); } public static EndpointCertificate fromSlime(Inspector inspector) { @@ -79,8 +79,8 @@ public class EndpointCertificateSerializer { inspector.field(lastRefreshedField).valid() ? Optional.of(inspector.field(lastRefreshedField).asLong()) : Optional.empty(), - inspector.field(randomizedIdField).valid() ? - Optional.of(inspector.field(randomizedIdField).asString()) : + inspector.field(generatedIdField).valid() ? + Optional.of(inspector.field(generatedIdField).asString()) : Optional.empty()); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java index c28185466b0..f7bd1e06e68 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java @@ -18,11 +18,13 @@ class BasicServicesXmlTest { public void parse() { assertServices(new BasicServicesXml(List.of()), "<services/>"); assertServices(new BasicServicesXml(List.of(new Container("foo", List.of(Container.AuthMethod.mtls), List.of()), - new Container("bar", List.of(Container.AuthMethod.mtls), List.of()))), + new Container("bar", List.of(Container.AuthMethod.mtls), List.of()), + new Container("container", List.of(Container.AuthMethod.mtls), List.of()))), """ <services> <container id="foo"/> <container id="bar"/> + <container/> </services> """); assertServices(new BasicServicesXml(List.of( diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java index a6d3b435dcb..2bc11adddf7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java @@ -28,7 +28,6 @@ import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.integration.SecretStoreMock; import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock; -import com.yahoo.vespa.hosted.controller.maintenance.EndpointCertificateMaintainer; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -296,7 +295,7 @@ public class EndpointCertificatesTest { // Initial certificate is requested directly from provider Optional<EndpointCertificate> certFromProvider = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); assertTrue(certFromProvider.isPresent()); - assertFalse(certFromProvider.get().randomizedId().isPresent()); + assertFalse(certFromProvider.get().generatedId().isPresent()); // Pooled certificates become available tester.flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true); @@ -315,8 +314,8 @@ public class EndpointCertificatesTest { String certId = "pool-cert-1"; addCertificateToPool(certId, UnassignedCertificate.State.ready); Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertEquals(certId, cert.get().randomizedId().get()); - assertEquals(certId, tester.curator().readAssignedCertificate(TenantAndApplicationId.from(instance.id()), Optional.empty()).get().certificate().randomizedId().get(), "Certificate is assigned at application-level"); + assertEquals(certId, cert.get().generatedId().get()); + assertEquals(certId, tester.curator().readAssignedCertificate(TenantAndApplicationId.from(instance.id()), Optional.empty()).get().certificate().generatedId().get(), "Certificate is assigned at application-level"); assertTrue(tester.controller().curator().readUnassignedCertificate(certId).isEmpty(), "Certificate is removed from pool"); assertEquals(clock.instant().getEpochSecond(), cert.get().lastRequested()); } @@ -326,8 +325,8 @@ public class EndpointCertificatesTest { addCertificateToPool(certId, UnassignedCertificate.State.ready); ZoneId devZone = tester.zoneRegistry().zones().all().routingMethod(RoutingMethod.exclusive).in(Environment.dev).zones().stream().findFirst().orElseThrow().getId(); Optional<EndpointCertificate> cert = endpointCertificates.get(instance, devZone, DeploymentSpec.empty); - assertEquals(certId, cert.get().randomizedId().get()); - assertEquals(certId, tester.curator().readAssignedCertificate(instance.id()).get().certificate().randomizedId().get(), "Certificate is assigned at instance-level"); + assertEquals(certId, cert.get().generatedId().get()); + assertEquals(certId, tester.curator().readAssignedCertificate(instance.id()).get().certificate().generatedId().get(), "Certificate is assigned at instance-level"); assertTrue(tester.controller().curator().readUnassignedCertificate(certId).isEmpty(), "Certificate is removed from pool"); assertEquals(clock.instant().getEpochSecond(), cert.get().lastRequested()); } @@ -338,7 +337,7 @@ public class EndpointCertificatesTest { // Initial certificate is requested directly from provider Optional<EndpointCertificate> certFromProvider = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); assertTrue(certFromProvider.isPresent()); - assertFalse(certFromProvider.get().randomizedId().isPresent()); + assertFalse(certFromProvider.get().generatedId().isPresent()); // Simulate endpoint certificate maintainer to assign random id TenantAndApplicationId tenantAndApplicationId = TenantAndApplicationId.from(instance.id()); @@ -346,7 +345,7 @@ public class EndpointCertificatesTest { Optional<AssignedCertificate> assignedCertificate = tester.controller().curator().readAssignedCertificate(tenantAndApplicationId, instanceName); assertTrue(assignedCertificate.isPresent()); String assignedRandomId = "randomid"; - AssignedCertificate updated = assignedCertificate.get().with(assignedCertificate.get().certificate().withRandomizedId(assignedRandomId)); + AssignedCertificate updated = assignedCertificate.get().with(assignedCertificate.get().certificate().withGeneratedId(assignedRandomId)); tester.controller().curator().writeAssignedCertificate(updated); // Pooled certificates become available @@ -358,12 +357,12 @@ public class EndpointCertificatesTest { // Request cert for app Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertEquals(assignedRandomId, cert.get().randomizedId().get()); + assertEquals(assignedRandomId, cert.get().generatedId().get()); // Pooled cert remains unassigned List<String> unassignedCertificateIds = tester.curator().readUnassignedCertificates().stream() .map(UnassignedCertificate::certificate) - .map(EndpointCertificate::randomizedId) + .map(EndpointCertificate::generatedId) .map(Optional::get) .toList(); assertEquals(List.of(certId), unassignedCertificateIds); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java index 647c809231e..2f996bac897 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java @@ -41,7 +41,6 @@ import java.util.stream.Stream; import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.devUsEast1; import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.perfUsEast3; import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsCentral1; -import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsEast3; import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsWest1; import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.stagingTest; import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.systemTest; @@ -138,7 +137,7 @@ public class EndpointCertificateMaintainerTest { tester.clock().advance(Duration.ofDays(3)); secretStore.setSecret(assignedCertificate.certificate().keyName(), "foo", 1); secretStore.setSecret(assignedCertificate.certificate().certName(), "bar", 1); - tester.controller().serviceRegistry().endpointCertificateProvider().requestCaSignedCertificate("preprovisioned." + assignedCertificate.certificate().randomizedId().get(), assignedCertificate.certificate().requestedDnsSans(), Optional.of(assignedCertificate.certificate()), "rsa_2048", false); + tester.controller().serviceRegistry().endpointCertificateProvider().requestCaSignedCertificate("preprovisioned." + assignedCertificate.certificate().generatedId().get(), assignedCertificate.certificate().requestedDnsSans(), Optional.of(assignedCertificate.certificate()), "rsa_2048", false); // We should now pick up the new key and cert version + uuid, but not force trigger deployment yet assertEquals(0.0, maintainer.maintain(), 0.0000001); @@ -206,7 +205,7 @@ public class EndpointCertificateMaintainerTest { assertTrue(applicationCertificate.isPresent()); Optional<AssignedCertificate> instanceCertificate = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(app), Optional.of(app.instance())); assertTrue(instanceCertificate.isPresent()); - assertEquals(instanceCertificate.get().certificate().randomizedId(), applicationCertificate.get().certificate().randomizedId()); + assertEquals(instanceCertificate.get().certificate().generatedId(), applicationCertificate.get().certificate().generatedId()); // Verify the 3 wildcard random names are same in all certs List<String> appWildcardSans = applicationCertificate.get().certificate().requestedDnsSans(); @@ -226,13 +225,13 @@ public class EndpointCertificateMaintainerTest { assertEquals(1, tester.curator().readAssignedCertificates().size()); maintainer.maintain(); - String randomId = tester.curator().readAssignedCertificate(instance1).get().certificate().randomizedId().get(); + String randomId = tester.curator().readAssignedCertificate(instance1).get().certificate().generatedId().get(); deployToAssignCert(deploymentTester, instance2, List.of(productionUsWest1), Optional.of("instance1,instance2")); maintainer.maintain(); assertEquals(3, tester.curator().readAssignedCertificates().size()); - assertEquals(randomId, tester.curator().readAssignedCertificate(instance1).get().certificate().randomizedId().get()); + assertEquals(randomId, tester.curator().readAssignedCertificate(instance1).get().certificate().generatedId().get()); } @Test @@ -247,7 +246,7 @@ public class EndpointCertificateMaintainerTest { // Verify certificate is assigned random id and 3 new names Optional<AssignedCertificate> assignedCertificate = tester.curator().readAssignedCertificate(devApp); - assertTrue(assignedCertificate.get().certificate().randomizedId().isPresent()); + assertTrue(assignedCertificate.get().certificate().generatedId().isPresent()); List<String> newRequestedSans = assignedCertificate.get().certificate().requestedDnsSans(); List<String> randomizedNames = newRequestedSans.stream().filter(san -> !originalRequestedSans.contains(san)).toList(); assertEquals(3, randomizedNames.size()); 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 22523103208..3405009714d 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 @@ -1206,6 +1206,70 @@ public class RoutingPoliciesTest { } @Test + public void generated_endpoints_enable_token() { + var tester = new RoutingPoliciesTester(SystemName.Public); + var context = tester.newDeploymentContext("tenant1", "app1", "default"); + tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true); + tester.controllerTester().flagSource().withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), false); + addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester); + + // Deploy application without token + var zone1 = ZoneId.from("prod", "aws-us-east-1c"); + ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region()) + .container("c0", AuthMethod.mtls) + .endpoint("foo", "c0") + .build(); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("test", "aws-us-east-2c")); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("staging", "aws-us-east-3c")); + tester.provisionLoadBalancers(1, context.instanceId(), zone1); + context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy(); + assertEquals(List.of("a9c8c045.cafed00d.g.vespa-app.cloud", + "ebd395b6.cafed00d.z.vespa-app.cloud", + "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"), + tester.recordNames()); + + // Re-deploy with token enabled + applicationPackage = applicationPackageBuilder().region(zone1.region()) + .container("c0", AuthMethod.mtls, AuthMethod.token) + .endpoint("foo", "c0") + .build(); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("test", "aws-us-east-2c")); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("staging", "aws-us-east-3c")); + context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy(); + // Additional zone- and global-scoped endpoints are added (token) + assertEquals(List.of("a9c8c045.cafed00d.g.vespa-app.cloud", + "b7e79800.cafed00d.z.vespa-app.cloud", + "c60d3149.cafed00d.g.vespa-app.cloud", + "ebd395b6.cafed00d.z.vespa-app.cloud", + "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"), + tester.recordNames()); + + // Add new endpoint is generated for an additional global endpoint + applicationPackage = applicationPackageBuilder().region(zone1.region()) + .container("c0", AuthMethod.mtls, AuthMethod.token) + .endpoint("foo", "c0") + .endpoint("bar", "c0") + .build(); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("test", "aws-us-east-2c")); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("staging", "aws-us-east-3c")); + context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy(); + List<String> expectedRecords = List.of("a9c8c045.cafed00d.g.vespa-app.cloud", + "aa7591aa.cafed00d.g.vespa-app.cloud", + "b7e79800.cafed00d.z.vespa-app.cloud", + "c60d3149.cafed00d.g.vespa-app.cloud", + "d467800f.cafed00d.g.vespa-app.cloud", + "ebd395b6.cafed00d.z.vespa-app.cloud", + "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"); + assertEquals(expectedRecords, tester.recordNames()); + + // No change on redeployment + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("test", "aws-us-east-2c")); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("staging", "aws-us-east-3c")); + context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy(); + assertEquals(expectedRecords, tester.recordNames()); + } + + @Test public void generated_endpoints_only() { var tester = new RoutingPoliciesTester(SystemName.Public); var context = tester.newDeploymentContext("tenant1", "app1", "default"); @@ -1216,6 +1280,7 @@ public class RoutingPoliciesTest { // Deploy application var zone1 = ZoneId.from("prod", "aws-us-east-1c"); + var zone2 = ZoneId.from("prod", "aws-eu-west-1a"); ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region()) .container("c0", AuthMethod.mtls) .endpoint("foo", "c0") @@ -1232,6 +1297,23 @@ public class RoutingPoliciesTest { "ebd395b6.cafed00d.z.vespa-app.cloud", "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"), tester.recordNames()); + + // Another zone is added to global endpoint + applicationPackage = applicationPackageBuilder().region(zone1.region()) + .region(zone2.region()) + .container("c0", AuthMethod.mtls) + .endpoint("foo", "c0") + .build(); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("test", "aws-us-east-2c")); + tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("staging", "aws-us-east-3c")); + tester.provisionLoadBalancers(1, context.instanceId(), zone2); + context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy(); + assertEquals(List.of("a6414896.cafed00d.aws-eu-west-1.w.vespa-app.cloud", + "a9c8c045.cafed00d.g.vespa-app.cloud", + "cbff1506.cafed00d.z.vespa-app.cloud", + "ebd395b6.cafed00d.z.vespa-app.cloud", + "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"), + tester.recordNames()); } @Test |