diff options
author | Martin Polden <mpolden@mpolden.no> | 2023-10-12 09:46:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-12 09:46:05 +0200 |
commit | 0f14059f373b486987f82b1d161ff89b01f03204 (patch) | |
tree | ad68405fffdc92e9af68739ebef593f5fd363f95 /controller-server/src/test/java | |
parent | e9562b0af56cf1163e41c99f30c2c836aa1720f1 (diff) | |
parent | 2f6bcf34688f229529e25e2f09d0552c3214318d (diff) |
Merge pull request #28842 from vespa-engine/mpolden/cert-assignment
Refactor certificate assignment and migration
Diffstat (limited to 'controller-server/src/test/java')
7 files changed, 314 insertions, 272 deletions
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java index f456b6e12dc..eb86f23fbfb 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java @@ -73,7 +73,6 @@ import java.util.Set; import java.util.TreeSet; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import static com.yahoo.config.provision.SystemName.main; import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.devAwsUsEast2a; @@ -952,8 +951,6 @@ public class ControllerTest { // Create app1 var context1 = tester.newDeploymentContext("tenant1", "app1", "default"); var prodZone = ZoneId.from("prod", "us-west-1"); - var stagingZone = ZoneId.from("staging", "us-east-3"); - var testZone = ZoneId.from("test", "us-east-1"); tester.controllerTester().zoneRegistry().exclusiveRoutingIn(ZoneApiMock.from(prodZone)); var applicationPackage = new ApplicationPackageBuilder().athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service")) .region(prodZone.region()) @@ -962,16 +959,23 @@ public class ControllerTest { context1.submit(applicationPackage).deploy(); var cert = certificate.apply(context1.instance()); assertTrue(cert.isPresent(), "Provisions certificate in " + Environment.prod); - assertEquals(Stream.concat(Stream.of("vznqtz7a5ygwjkbhhj7ymxvlrekgt4l6g.vespa.oath.cloud", - "app1.tenant1.global.vespa.oath.cloud", - "*.app1.tenant1.global.vespa.oath.cloud"), - Stream.of(prodZone, testZone, stagingZone) - .flatMap(zone -> Stream.of("", "*.") - .map(prefix -> prefix + "app1.tenant1." + zone.region().value() + - (zone.environment() == Environment.prod ? "" : "." + zone.environment().value()) + - ".vespa.oath.cloud"))) - .collect(Collectors.toUnmodifiableSet()), - Set.copyOf(tester.controllerTester().serviceRegistry().endpointCertificateMock().dnsNamesOf(cert.get().rootRequestId()))); + assertEquals(List.of("*.app1.tenant1.global.vespa.oath.cloud", + "*.app1.tenant1.us-east-1.test.vespa.oath.cloud", + "*.app1.tenant1.us-east-3.staging.vespa.oath.cloud", + "*.app1.tenant1.us-west-1.vespa.oath.cloud", + "*.f5549014.a.vespa.oath.cloud", + "*.f5549014.g.vespa.oath.cloud", + "*.f5549014.z.vespa.oath.cloud", + "app1.tenant1.global.vespa.oath.cloud", + "app1.tenant1.us-east-1.test.vespa.oath.cloud", + "app1.tenant1.us-east-3.staging.vespa.oath.cloud", + "app1.tenant1.us-west-1.vespa.oath.cloud", + "vznqtz7a5ygwjkbhhj7ymxvlrekgt4l6g.vespa.oath.cloud"), + tester.controllerTester().serviceRegistry().endpointCertificateMock() + .dnsNamesOf(cert.get().rootRequestId()) + .stream() + .sorted() + .toList()); // Next deployment reuses certificate context1.submit(applicationPackage).deploy(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java index 40162186066..7bdecca11c0 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java @@ -3,9 +3,7 @@ package com.yahoo.vespa.hosted.controller; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostName; -import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.RoutingMethod; @@ -20,6 +18,7 @@ import com.yahoo.vespa.athenz.api.OAuthCredentials; import com.yahoo.vespa.flags.FlagSource; import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.flags.PermanentFlags; +import com.yahoo.vespa.hosted.controller.api.identifiers.ControllerVersion; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock; @@ -54,7 +53,6 @@ import com.yahoo.vespa.hosted.controller.security.Credentials; import com.yahoo.vespa.hosted.controller.security.TenantSpec; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; -import com.yahoo.vespa.hosted.controller.api.identifiers.ControllerVersion; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; import com.yahoo.yolean.concurrent.Sleeper; @@ -289,14 +287,6 @@ public final class ControllerTester { return controller().clock().instant().atOffset(ZoneOffset.UTC).getHour(); } - public ZoneId toZone(Environment environment) { - return switch (environment) { - case dev, test -> ZoneId.from(environment, RegionName.from("us-east-1")); - case staging -> ZoneId.from(environment, RegionName.from("us-east-3")); - default -> ZoneId.from(environment, RegionName.from("us-west-1")); - }; - } - public AthenzDomain createDomainWithAdmin(String domainName, AthenzUser user) { AthenzDomain domain = new AthenzDomain(domainName); athenzDb.getOrCreateDomain(domain).admin(user); @@ -405,7 +395,7 @@ public final class ControllerTester { RotationsConfig.Builder builder = new RotationsConfig.Builder(); for (int i = 1; i <= availableRotations; i++) { String id = Text.format("%02d", i); - builder = builder.rotations("rotation-id-" + id, "rotation-fqdn-" + id); + builder.rotations("rotation-id-" + id, "rotation-fqdn-" + id); } return new RotationsConfig(builder); } 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 23481cb324e..cec48dd1598 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 @@ -370,6 +370,24 @@ public class EndpointTest { "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), + // Wildcard endpoint for zone + "*.deadbeef.z.vespa-app.cloud", + Endpoint.of(instance1) + .wildcardGenerated(ge1.applicationPart(), Endpoint.Scope.zone) + .certificateName() + .routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public), + // Wildcard endpoint for global + "*.deadbeef.g.vespa-app.cloud", + Endpoint.of(instance1) + .wildcardGenerated(ge1.applicationPart(), Endpoint.Scope.global) + .certificateName() + .routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public), + // Wildcard endpoint for application + "*.deadbeef.a.vespa-app.cloud", + Endpoint.of(instance1) + .wildcardGenerated(ge1.applicationPart(), Endpoint.Scope.application) + .certificateName() .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/certificate/EndpointCertificatesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java index 78748cd2cd8..7faaee95abb 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 @@ -17,18 +17,21 @@ import com.yahoo.security.SignatureAlgorithm; import com.yahoo.security.X509CertificateBuilder; import com.yahoo.security.X509CertificateUtils; import com.yahoo.test.ManualClock; +import com.yahoo.transaction.Mutex; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.hosted.controller.ControllerTester; -import com.yahoo.vespa.hosted.controller.Instance; +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.certificates.EndpointCertificateProviderMock; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidatorImpl; +import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidatorMock; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; 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.persistence.CuratorDb; +import com.yahoo.vespa.hosted.controller.routing.EndpointConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -58,12 +61,13 @@ public class EndpointCertificatesTest { private final ControllerTester tester = new ControllerTester(); private final SecretStoreMock secretStore = new SecretStoreMock(); - private final CuratorDb mockCuratorDb = tester.curator(); + private final CuratorDb curator = tester.curator(); private final ManualClock clock = tester.clock(); private final EndpointCertificateProviderMock endpointCertificateProviderMock = new EndpointCertificateProviderMock(); private final EndpointCertificateValidatorImpl endpointCertificateValidator = new EndpointCertificateValidatorImpl(secretStore, clock); private final EndpointCertificates endpointCertificates = new EndpointCertificates(tester.controller(), endpointCertificateProviderMock, endpointCertificateValidator); private final KeyPair testKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 192); + private final Mutex lock = () -> {}; private X509Certificate testCertificate; private X509Certificate testCertificate2; @@ -74,6 +78,9 @@ public class EndpointCertificatesTest { "*.default.default.global.vespa.oath.cloud", "default.default.aws-us-east-1a.vespa.oath.cloud", "*.default.default.aws-us-east-1a.vespa.oath.cloud", + "*.f5549014.z.vespa.oath.cloud", + "*.f5549014.g.vespa.oath.cloud", + "*.f5549014.a.vespa.oath.cloud", "default.default.us-east-1.test.vespa.oath.cloud", "*.default.default.us-east-1.test.vespa.oath.cloud", "default.default.us-east-3.staging.vespa.oath.cloud", @@ -93,7 +100,10 @@ public class EndpointCertificatesTest { private static final List<String> expectedDevSans = List.of( "vt2ktgkqme5zlnp4tj4ttyor7fj3v7q5o.vespa.oath.cloud", "default.default.us-east-1.dev.vespa.oath.cloud", - "*.default.default.us-east-1.dev.vespa.oath.cloud" + "*.default.default.us-east-1.dev.vespa.oath.cloud", + "*.f5549014.z.vespa.oath.cloud", + "*.f5549014.g.vespa.oath.cloud", + "*.f5549014.a.vespa.oath.cloud" ); private X509Certificate makeTestCert(List<String> sans) { @@ -108,7 +118,7 @@ public class EndpointCertificatesTest { return x509CertificateBuilder.build(); } - private final Instance instance = new Instance(ApplicationId.defaultId()); + private final ApplicationId instance = ApplicationId.defaultId(); private final String testKeyName = "testKeyName"; private final String testCertName = "testCertName"; private ZoneId prodZone; @@ -125,22 +135,20 @@ public class EndpointCertificatesTest { @Test void provisions_new_certificate_in_dev() { ZoneId testZone = tester.zoneRegistry().zones().all().routingMethod(RoutingMethod.exclusive).in(Environment.dev).zones().stream().findFirst().orElseThrow().getId(); - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, testZone, DeploymentSpec.empty); - assertTrue(cert.isPresent()); - assertTrue(cert.get().keyName().matches("vespa.tls.default.default.*-key")); - assertTrue(cert.get().certName().matches("vespa.tls.default.default.*-cert")); - assertEquals(0, cert.get().version()); - assertEquals(expectedDevSans, cert.get().requestedDnsSans()); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, testZone), DeploymentSpec.empty, lock); + assertTrue(cert.keyName().matches("vespa.tls.default.default.*-key")); + assertTrue(cert.certName().matches("vespa.tls.default.default.*-cert")); + assertEquals(0, cert.version()); + assertEquals(expectedDevSans, cert.requestedDnsSans()); } @Test void provisions_new_certificate_in_prod() { - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertTrue(cert.isPresent()); - assertTrue(cert.get().keyName().matches("vespa.tls.default.default.*-key")); - assertTrue(cert.get().certName().matches("vespa.tls.default.default.*-cert")); - assertEquals(0, cert.get().version()); - assertEquals(expectedSans, cert.get().requestedDnsSans()); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock); + assertTrue(cert.keyName().matches("vespa.tls.default.default.*-key")); + assertTrue(cert.certName().matches("vespa.tls.default.default.*-cert")); + assertEquals(0, cert.version()); + assertEquals(expectedSans, cert.requestedDnsSans()); } private ControllerTester publicTester() { @@ -160,66 +168,68 @@ public class EndpointCertificatesTest { "*.default.default.g.vespa-app.cloud", "default.default.aws-us-east-1a.z.vespa-app.cloud", "*.default.default.aws-us-east-1a.z.vespa-app.cloud", + "*.f5549014.z.vespa-app.cloud", + "*.f5549014.g.vespa-app.cloud", + "*.f5549014.a.vespa-app.cloud", "default.default.us-east-1.test.z.vespa-app.cloud", "*.default.default.us-east-1.test.z.vespa-app.cloud", "default.default.us-east-3.staging.z.vespa-app.cloud", "*.default.default.us-east-3.staging.z.vespa-app.cloud" ); - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertTrue(cert.isPresent()); - assertTrue(cert.get().keyName().matches("vespa.tls.default.default.*-key")); - assertTrue(cert.get().certName().matches("vespa.tls.default.default.*-cert")); - assertEquals(0, cert.get().version()); - assertEquals(expectedSans, cert.get().requestedDnsSans()); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock); + assertTrue(cert.keyName().matches("vespa.tls.default.default.*-key")); + assertTrue(cert.certName().matches("vespa.tls.default.default.*-cert")); + assertEquals(0, cert.version()); + assertEquals(expectedSans, cert.requestedDnsSans()); } @Test void reuses_stored_certificate() { - mockCuratorDb.writeAssignedCertificate(assignedCertificate(instance.id(), new EndpointCertificate(testKeyName, testCertName, 7, 0, "request_id", Optional.of("leaf-request-uuid"), - List.of("vt2ktgkqme5zlnp4tj4ttyor7fj3v7q5o.vespa.oath.cloud", + curator.writeAssignedCertificate(assignedCertificate(instance, new EndpointCertificate(testKeyName, testCertName, 7, 0, "request_id", Optional.of("leaf-request-uuid"), + List.of("vt2ktgkqme5zlnp4tj4ttyor7fj3v7q5o.vespa.oath.cloud", "default.default.global.vespa.oath.cloud", "*.default.default.global.vespa.oath.cloud", "default.default.aws-us-east-1a.vespa.oath.cloud", - "*.default.default.aws-us-east-1a.vespa.oath.cloud"), - "", Optional.empty(), Optional.empty(), Optional.empty()))); + "*.default.default.aws-us-east-1a.vespa.oath.cloud", + "*.f5549014.z.vespa.oath.cloud", + "*.f5549014.g.vespa.oath.cloud", + "*.f5549014.a.vespa.oath.cloud"), + "", Optional.empty(), Optional.empty(), Optional.empty()))); secretStore.setSecret(testKeyName, KeyUtils.toPem(testKeyPair.getPrivate()), 7); secretStore.setSecret(testCertName, X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), 7); - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertTrue(cert.isPresent()); - assertEquals(testKeyName, cert.get().keyName()); - assertEquals(testCertName, cert.get().certName()); - assertEquals(7, cert.get().version()); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock); + assertEquals(testKeyName, cert.keyName()); + assertEquals(testCertName, cert.certName()); + assertEquals(7, cert.version()); } @Test void reprovisions_certificate_when_necessary() { - mockCuratorDb.writeAssignedCertificate(assignedCertificate(instance.id(), new EndpointCertificate(testKeyName, testCertName, -1, 0, "root-request-uuid", Optional.of("leaf-request-uuid"), List.of(), "issuer", Optional.empty(), Optional.empty(), Optional.empty()))); + curator.writeAssignedCertificate(assignedCertificate(instance, new EndpointCertificate(testKeyName, testCertName, -1, 0, "root-request-uuid", Optional.of("leaf-request-uuid"), List.of(), "issuer", Optional.empty(), Optional.empty(), Optional.empty()))); secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), 0); secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), 0); - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertTrue(cert.isPresent()); - assertEquals(0, cert.get().version()); - assertEquals(cert, mockCuratorDb.readAssignedCertificate(instance.id()).map(AssignedCertificate::certificate)); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock); + assertEquals(0, cert.version()); + assertEquals(cert, curator.readAssignedCertificate(instance).map(AssignedCertificate::certificate).get()); } @Test void reprovisions_certificate_with_added_sans_when_deploying_to_new_zone() { ZoneId testZone = ZoneId.from("prod.ap-northeast-1"); - mockCuratorDb.writeAssignedCertificate(assignedCertificate(instance.id(), new EndpointCertificate(testKeyName, testCertName, -1, 0, "original-request-uuid", Optional.of("leaf-request-uuid"), expectedSans, "mockCa", Optional.empty(), Optional.empty(), Optional.empty()))); + curator.writeAssignedCertificate(assignedCertificate(instance, new EndpointCertificate(testKeyName, testCertName, -1, 0, "original-request-uuid", Optional.of("leaf-request-uuid"), expectedSans, "mockCa", Optional.empty(), Optional.empty(), Optional.empty()))); secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), -1); secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), -1); secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), 0); secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate2) + X509CertificateUtils.toPem(testCertificate2), 0); - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, testZone, DeploymentSpec.empty); - assertTrue(cert.isPresent()); - assertEquals(0, cert.get().version()); - assertEquals(cert, mockCuratorDb.readAssignedCertificate(instance.id()).map(AssignedCertificate::certificate)); - assertEquals("original-request-uuid", cert.get().rootRequestId()); - assertNotEquals(Optional.of("leaf-request-uuid"), cert.get().leafRequestId()); - assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(cert.get().requestedDnsSans())); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, testZone), DeploymentSpec.empty, lock); + assertEquals(0, cert.version()); + assertEquals(cert, curator.readAssignedCertificate(instance).map(AssignedCertificate::certificate).get()); + assertEquals("original-request-uuid", cert.rootRequestId()); + assertNotEquals(Optional.of("leaf-request-uuid"), cert.leafRequestId()); + assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(cert.requestedDnsSans())); } @Test @@ -238,17 +248,16 @@ public class EndpointCertificatesTest { ); ZoneId testZone = tester.zoneRegistry().zones().all().in(Environment.staging).zones().stream().findFirst().orElseThrow().getId(); - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, testZone, deploymentSpec); - assertTrue(cert.isPresent()); - assertTrue(cert.get().keyName().matches("vespa.tls.default.default.*-key")); - assertTrue(cert.get().certName().matches("vespa.tls.default.default.*-cert")); - assertEquals(0, cert.get().version()); - assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(cert.get().requestedDnsSans())); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, testZone), deploymentSpec, lock); + assertTrue(cert.keyName().matches("vespa.tls.default.default.*-key")); + assertTrue(cert.certName().matches("vespa.tls.default.default.*-cert")); + assertEquals(0, cert.version()); + assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(cert.requestedDnsSans())); } @Test void includes_application_endpoint_when_declared() { - Instance instance = new Instance(ApplicationId.from("t1", "a1", "default")); + ApplicationId instance = ApplicationId.from("t1", "a1", "default"); ZoneId zone1 = ZoneId.from(Environment.prod, RegionName.from("aws-us-east-1c")); ZoneId zone2 = ZoneId.from(Environment.prod, RegionName.from("aws-us-west-2a")); ControllerTester tester = publicTester(); @@ -280,28 +289,25 @@ public class EndpointCertificatesTest { "a1.t1.us-east-1.test.z.vespa-app.cloud", "*.a1.t1.us-east-1.test.z.vespa-app.cloud", "a1.t1.us-east-3.staging.z.vespa-app.cloud", - "*.a1.t1.us-east-3.staging.z.vespa-app.cloud" + "*.a1.t1.us-east-3.staging.z.vespa-app.cloud", + "*.f5549014.z.vespa-app.cloud", + "*.f5549014.g.vespa-app.cloud", + "*.f5549014.a.vespa-app.cloud" ).sorted().toList(); - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, zone1, applicationPackage.deploymentSpec()); - assertTrue(cert.isPresent()); - assertTrue(cert.get().keyName().matches("vespa.tls.t1.a1.*-key")); - assertTrue(cert.get().certName().matches("vespa.tls.t1.a1.*-cert")); - assertEquals(0, cert.get().version()); - assertEquals(expectedSans, cert.get().requestedDnsSans().stream().sorted().toList()); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, zone1), applicationPackage.deploymentSpec(), lock); + assertTrue(cert.keyName().matches("vespa.tls.t1.a1.*-key")); + assertTrue(cert.certName().matches("vespa.tls.t1.a1.*-cert")); + assertEquals(0, cert.version()); + assertEquals(expectedSans, cert.requestedDnsSans().stream().sorted().toList()); } @Test public void assign_certificate_from_pool() { - // Initial certificate is requested directly from provider - Optional<EndpointCertificate> certFromProvider = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertTrue(certFromProvider.isPresent()); - assertFalse(certFromProvider.get().generatedId().isPresent()); - - // Pooled certificates become available tester.flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true); + tester.flagSource().withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), false); try { - addCertificateToPool("pool-cert-1", UnassignedCertificate.State.requested); - endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); + addCertificateToPool("bad0f00d", UnassignedCertificate.State.requested, tester); + endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock); fail("Expected exception as certificate is not ready"); } catch (IllegalArgumentException ignored) {} @@ -311,76 +317,169 @@ public class EndpointCertificatesTest { // Certificate is assigned from pool instead. The previously assigned certificate will eventually be cleaned up // by EndpointCertificateMaintainer { // prod - String certId = "pool-cert-1"; - addCertificateToPool(certId, UnassignedCertificate.State.ready); - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - 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"); + String certId = "bad0f00d"; + addCertificateToPool(certId, UnassignedCertificate.State.ready, tester); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock); + assertEquals(certId, cert.generatedId().get()); + assertEquals(certId, tester.curator().readAssignedCertificate(TenantAndApplicationId.from(instance), 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()); + assertEquals(clock.instant().getEpochSecond(), cert.lastRequested()); } { // dev - String certId = "pool-cert-2"; - addCertificateToPool(certId, UnassignedCertificate.State.ready); + String certId = "f00d0bad"; + addCertificateToPool(certId, UnassignedCertificate.State.ready, tester); 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().generatedId().get()); - assertEquals(certId, tester.curator().readAssignedCertificate(instance.id()).get().certificate().generatedId().get(), "Certificate is assigned at instance-level"); + EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, devZone), DeploymentSpec.empty, lock); + assertEquals(certId, cert.generatedId().get()); + assertEquals(certId, tester.curator().readAssignedCertificate(instance).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()); + assertEquals(clock.instant().getEpochSecond(), cert.lastRequested()); } } @Test - void reuse_per_instance_certificate_if_assigned_random_id() { - // Initial certificate is requested directly from provider - Optional<EndpointCertificate> certFromProvider = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertTrue(certFromProvider.isPresent()); - assertFalse(certFromProvider.get().generatedId().isPresent()); - - // Simulate endpoint certificate maintainer to assign random id - TenantAndApplicationId tenantAndApplicationId = TenantAndApplicationId.from(instance.id()); - Optional<InstanceName> instanceName = Optional.of(instance.name()); - Optional<AssignedCertificate> assignedCertificate = tester.controller().curator().readAssignedCertificate(tenantAndApplicationId, instanceName); - assertTrue(assignedCertificate.isPresent()); - String assignedRandomId = "randomid"; - AssignedCertificate updated = assignedCertificate.get().with(assignedCertificate.get().certificate().withGeneratedId(assignedRandomId)); - tester.controller().curator().writeAssignedCertificate(updated); - - // Pooled certificates become available + public void certificate_migration() { + // An application is initially deployed with legacy config + ZoneId zone1 = ZoneId.from(Environment.prod, RegionName.from("aws-us-east-1c")); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder().region(zone1.region()) + .build(); + ControllerTester tester = publicTester(); + EndpointCertificates endpointCertificates = new EndpointCertificates(tester.controller(), endpointCertificateProviderMock, new EndpointCertificateValidatorMock()); + ApplicationId instance = ApplicationId.from("t1", "a1", "default"); + DeploymentId deployment0 = new DeploymentId(instance, zone1); + final EndpointCertificate certificate = endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock); + final String generatedId = certificate.generatedId().get(); + assertEquals(List.of("vlfms2wpoa4nyrka2s5lktucypjtxkqhv.internal.vespa-app.cloud", + "a1.t1.g.vespa-app.cloud", + "*.a1.t1.g.vespa-app.cloud", + "a1.t1.aws-us-east-1c.z.vespa-app.cloud", + "*.a1.t1.aws-us-east-1c.z.vespa-app.cloud", + "*.f5549014.z.vespa-app.cloud", + "*.f5549014.g.vespa-app.cloud", + "*.f5549014.a.vespa-app.cloud", + "a1.t1.us-east-1.test.z.vespa-app.cloud", + "*.a1.t1.us-east-1.test.z.vespa-app.cloud", + "a1.t1.us-east-3.staging.z.vespa-app.cloud", + "*.a1.t1.us-east-3.staging.z.vespa-app.cloud"), + certificate.requestedDnsSans()); + Optional<AssignedCertificate> assignedCertificate = tester.curator().readAssignedCertificate(deployment0.applicationId()); + assertTrue(assignedCertificate.isPresent(), "Certificate is assigned at instance level"); + assertTrue(assignedCertificate.get().certificate().generatedId().isPresent(), "Certificate contains generated ID"); + + // Re-requesting certificate does not make any changes, except last requested time + tester.clock().advance(Duration.ofHours(1)); + assertEquals(certificate.withLastRequested(tester.clock().instant().getEpochSecond()), + endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock), + "Next request returns same certificate and updates last requested time"); + + // An additional instance is added to deployment spec + applicationPackage = new ApplicationPackageBuilder().instances("default,beta") + .region(zone1.region()) + .build(); + DeploymentId deployment1 = new DeploymentId(ApplicationId.from("t1", "a1", "beta"), zone1); + EndpointCertificate betaCert = endpointCertificates.get(deployment1, applicationPackage.deploymentSpec(), lock); + assertEquals(List.of("v43ctkgqim52zsbwefrg6ixkuwidvsumy.internal.vespa-app.cloud", + "beta.a1.t1.g.vespa-app.cloud", + "*.beta.a1.t1.g.vespa-app.cloud", + "beta.a1.t1.aws-us-east-1c.z.vespa-app.cloud", + "*.beta.a1.t1.aws-us-east-1c.z.vespa-app.cloud", + "*.f5549014.z.vespa-app.cloud", + "*.f5549014.g.vespa-app.cloud", + "*.f5549014.a.vespa-app.cloud", + "beta.a1.t1.us-east-1.test.z.vespa-app.cloud", + "*.beta.a1.t1.us-east-1.test.z.vespa-app.cloud", + "beta.a1.t1.us-east-3.staging.z.vespa-app.cloud", + "*.beta.a1.t1.us-east-3.staging.z.vespa-app.cloud"), + betaCert.requestedDnsSans()); + assertEquals(generatedId, betaCert.generatedId().get(), "Certificate inherits generated ID of existing instance"); + + // A dev instance is deployed + DeploymentId devDeployment0 = new DeploymentId(ApplicationId.from("t1", "a1", "dev"), + ZoneId.from("dev", "us-east-1")); + EndpointCertificate devCert0 = endpointCertificates.get(devDeployment0, applicationPackage.deploymentSpec(), lock); + assertNotEquals(generatedId, devCert0.generatedId().get(), "Dev deployments gets a new generated ID"); + assertEquals(List.of("vld3y4mggzpd5wmm5jmldzcbyetjoqtzq.internal.vespa-app.cloud", + "dev.a1.t1.us-east-1.dev.z.vespa-app.cloud", + "*.dev.a1.t1.us-east-1.dev.z.vespa-app.cloud", + "*.a89ff7c6.z.vespa-app.cloud", + "*.a89ff7c6.g.vespa-app.cloud", + "*.a89ff7c6.a.vespa-app.cloud"), + devCert0.requestedDnsSans()); + + // Application switches to combined config tester.flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true); - - // Create 1 cert in pool - String certId = "pool-cert-1"; - addCertificateToPool(certId, UnassignedCertificate.State.ready); - - // Request cert for app - Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty); - assertEquals(assignedRandomId, cert.get().generatedId().get()); - - // Pooled cert remains unassigned - List<String> unassignedCertificateIds = tester.curator().readUnassignedCertificates().stream() - .map(UnassignedCertificate::certificate) - .map(EndpointCertificate::generatedId) - .map(Optional::get) - .toList(); - assertEquals(List.of(certId), unassignedCertificateIds); + tester.flagSource().withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), true); + tester.clock().advance(Duration.ofHours(1)); + assertEquals(certificate.withLastRequested(tester.clock().instant().getEpochSecond()), + endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock), + "No change to certificate: Existing certificate is compatible with " + + EndpointConfig.combined + " config"); + assertTrue(tester.curator().readAssignedCertificate(deployment0.applicationId()).isPresent(), "Certificate is assigned at instance level"); + assertFalse(tester.curator().readAssignedCertificate(TenantAndApplicationId.from(deployment0.applicationId()), Optional.empty()).isPresent(), + "Certificate is not assigned at application level"); + + // Application switches to generated config + tester.flagSource().withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), false); + tester.clock().advance(Duration.ofHours(1)); + assertEquals(certificate.withLastRequested(tester.clock().instant().getEpochSecond()), + endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock), + "No change to certificate: Existing certificate is compatible with " + + EndpointConfig.generated + " config"); + assertFalse(tester.curator().readAssignedCertificate(deployment0.applicationId()).isPresent(), "Certificate is no longer assigned at instance level"); + assertTrue(tester.curator().readAssignedCertificate(TenantAndApplicationId.from(deployment0.applicationId()), Optional.empty()).isPresent(), + "Certificate is assigned at application level"); + + // Both instances still use the same certificate + assertEquals(endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock), + endpointCertificates.get(deployment1, applicationPackage.deploymentSpec(), lock)); + + // Another dev instance is deployed, and is assigned certificate from pool + String poolCertId0 = "badf00d0"; + addCertificateToPool(poolCertId0, UnassignedCertificate.State.ready, tester); + EndpointCertificate devCert1 = endpointCertificates.get(new DeploymentId(ApplicationId.from("t1", "a1", "dev2"), + ZoneId.from("dev", "us-east-1")), + applicationPackage.deploymentSpec(), lock); + assertEquals(poolCertId0, devCert1.generatedId().get()); + + // Another application is deployed, and is assigned certificate from pool + String poolCertId1 = "badf00d1"; + addCertificateToPool(poolCertId1, UnassignedCertificate.State.ready, tester); + EndpointCertificate prodCertificate = endpointCertificates.get(new DeploymentId(ApplicationId.from("t1", "a2", "default"), + ZoneId.from("prod", "us-east-1")), + applicationPackage.deploymentSpec(), lock); + assertEquals(poolCertId1, prodCertificate.generatedId().get()); + + // Application switches back to legacy config + tester.flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), false); + tester.flagSource().withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), true); + EndpointCertificate reissuedCertificate = endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock); + assertEquals(certificate.requestedDnsSans(), reissuedCertificate.requestedDnsSans()); + assertTrue(tester.curator().readAssignedCertificate(deployment0.applicationId()).isPresent(), "Certificate is assigned at instance level again"); + assertTrue(tester.curator().readAssignedCertificate(TenantAndApplicationId.from(deployment0.applicationId()), Optional.empty()).isPresent(), + "Certificate is still assigned at application level"); // Not removed because the assumption is that the application will eventually migrate back } - private void addCertificateToPool(String id, UnassignedCertificate.State state) { - EndpointCertificate cert = new EndpointCertificate(testKeyName, testCertName, 1, 0, + private void addCertificateToPool(String id, UnassignedCertificate.State state, ControllerTester tester) { + EndpointCertificate cert = new EndpointCertificate(testKeyName, + testCertName, + 1, + 0, "request-id", Optional.of("leaf-request-uuid"), - List.of("name1", "name2"), - "", Optional.empty(), - Optional.empty(), Optional.of(id)); + List.of("*." + id + ".z.vespa.oath.cloud", + "*." + id + ".g.vespa.oath.cloud", + "*." + id + ".a.vespa.oath.cloud"), + "", + Optional.empty(), + Optional.empty(), + Optional.of(id)); UnassignedCertificate pooledCert = new UnassignedCertificate(cert, state); tester.controller().curator().writeUnassignedCertificate(pooledCert); } private static AssignedCertificate assignedCertificate(ApplicationId instance, EndpointCertificate certificate) { - return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate); + return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate, false); } } 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 bb36aa01b0f..f551a99829e 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 @@ -45,7 +45,6 @@ import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.pro import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.stagingTest; import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.systemTest; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -120,6 +119,7 @@ public class EndpointCertificateMaintainerTest { var applicationPackage = new ApplicationPackageBuilder() .region("us-west-1") + .container("default") .build(); DeploymentContext deploymentContext = deploymentTester.newDeploymentContext("tenant", "application", "default"); @@ -191,68 +191,6 @@ public class EndpointCertificateMaintainerTest { } @Test - void production_deployment_certificates_are_assigned_random_id() { - var app = ApplicationId.from("tenant", "app", "default"); - DeploymentTester deploymentTester = new DeploymentTester(tester); - deployToAssignCert(deploymentTester, app, List.of(systemTest, stagingTest, productionUsWest1), Optional.empty()); - assertEquals(1, tester.curator().readAssignedCertificates().size()); - - maintainer.maintain(); - assertEquals(2, tester.curator().readAssignedCertificates().size()); - - // Verify random id is same for application and instance certificates - Optional<AssignedCertificate> applicationCertificate = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(app), Optional.empty()); - assertTrue(applicationCertificate.isPresent()); - Optional<AssignedCertificate> instanceCertificate = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(app), Optional.of(app.instance())); - assertTrue(instanceCertificate.isPresent()); - 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(); - assertEquals(3, appWildcardSans.size()); - List<String> instanceSans = instanceCertificate.get().certificate().requestedDnsSans(); - List<String> wildcards = instanceSans.stream().filter(appWildcardSans::contains).toList(); - assertEquals(appWildcardSans, wildcards); - } - - @Test - void existing_application_randomid_is_copied_to_new_instance_deployments() { - var instance1 = ApplicationId.from("tenant", "prod", "instance1"); - var instance2 = ApplicationId.from("tenant", "prod", "instance2"); - - DeploymentTester deploymentTester = new DeploymentTester(tester); - deployToAssignCert(deploymentTester, instance1, List.of(systemTest, stagingTest,productionUsWest1),Optional.of("instance1")); - assertEquals(1, tester.curator().readAssignedCertificates().size()); - maintainer.maintain(); - - 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().generatedId().get()); - } - - @Test - void dev_certificates_are_not_assigned_application_level_certificate() { - var devApp = ApplicationId.from("tenant", "devonly", "foo"); - DeploymentTester deploymentTester = new DeploymentTester(tester); - deployToAssignCert(deploymentTester, devApp, List.of(devUsEast1), Optional.empty()); - assertEquals(1, tester.curator().readAssignedCertificates().size()); - List<String> originalRequestedSans = tester.curator().readAssignedCertificate(devApp).get().certificate().requestedDnsSans(); - maintainer.maintain(); - assertEquals(1, tester.curator().readAssignedCertificates().size()); - - // Verify certificate is assigned random id and 3 new names - Optional<AssignedCertificate> assignedCertificate = tester.curator().readAssignedCertificate(devApp); - 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()); - } - - @Test void deploy_to_other_manual_zone_refreshes_cert() { String devSan = "*.foo.manual.tenant.us-east-1.dev.vespa.oath.cloud"; String perfSan = "*.foo.manual.tenant.us-east-3.perf.vespa.oath.cloud"; @@ -301,10 +239,6 @@ public class EndpointCertificateMaintainerTest { Assertions.assertThat(usCentralWestSans).contains(centralSan); } - private void deploy() { - - } - private void deployToAssignCert(DeploymentTester tester, ApplicationId applicationId, List<JobType> jobTypes, Optional<String> instances) { var applicationPackageBuilder = new ApplicationPackageBuilder(); @@ -322,19 +256,15 @@ public class EndpointCertificateMaintainerTest { jobs.forEach(deploymentContext::runJob); } - EndpointCertificate certificate(List<String> sans) { - return new EndpointCertificate("keyName", "certName", 0, 0, "root-request-uuid", Optional.of("leaf-request-uuid"), List.of(), "issuer", Optional.empty(), Optional.empty(), Optional.empty()); - } - - private static AssignedCertificate assignedCertificate(ApplicationId instance, EndpointCertificate certificate) { - return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate); + return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate, false); } private void prepareCertificatePool(int numCertificates) { ((InMemoryFlagSource)tester.controller().flagSource()).withIntFlag(PermanentFlags.CERT_POOL_SIZE.id(), numCertificates); ((InMemoryFlagSource)tester.controller().flagSource()).withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true); + ((InMemoryFlagSource)tester.controller().flagSource()).withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), false); // Provision certificates for (int i = 0; i < numCertificates; i++) { @@ -351,4 +281,5 @@ public class EndpointCertificateMaintainerTest { }); certificatePoolMaintainer.maintain(); } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java index 6882f43f1a7..c8853c008f7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java @@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; import com.yahoo.vespa.hosted.controller.application.Change; @@ -26,6 +25,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Random; import java.util.Set; import java.util.stream.Collectors; @@ -40,7 +40,6 @@ import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.Cha import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.ChangesToCancel.PLATFORM; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -1100,8 +1099,9 @@ public class UpgraderTest { default2.instanceId(), default2); // Throttle upgrades per run - ((ManualClock) tester.controller().clock()).setInstant(Instant.ofEpochMilli(1589787107000L)); // Fixed random seed - Upgrader upgrader = new Upgrader(tester.controller(), Duration.ofMinutes(10)); + Upgrader upgrader = new Upgrader(tester.controller(), + Duration.ofMinutes(10), + new Random(1589787107000L)); // Fixed random seed upgrader.setUpgradesPerMinute(0.1); // Trigger some upgrades 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 2d9c2f40a2a..a10bfd46b0c 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 @@ -1068,11 +1068,9 @@ public class RoutingPoliciesTest { } @Test - public void generated_endpoints() { - var tester = new RoutingPoliciesTester(SystemName.Public); + public void combined_endpoint_config() { + var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.combined); var context = tester.newDeploymentContext("tenant1", "app1", "default"); - tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true); - addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester); // Deploy application int clustersPerZone = 2; @@ -1093,10 +1091,10 @@ public class RoutingPoliciesTest { // Deployment creates generated zone names List<String> expectedRecords = List.of( // save me, jebus! - "a6414896.cafed00d.aws-eu-west-1.w.vespa-app.cloud", - "b36bf591.cafed00d.z.vespa-app.cloud", + "a6414896.f5549014.aws-eu-west-1.w.vespa-app.cloud", + "aa7591aa.f5549014.z.vespa-app.cloud", "bar.app1.tenant1.a.vespa-app.cloud", - "bc50b636.cafed00d.z.vespa-app.cloud", + "bc50b636.f5549014.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", @@ -1105,16 +1103,16 @@ public class RoutingPoliciesTest { "c1.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud", "c1.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud", "c1.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud", - "c33db5ed.cafed00d.z.vespa-app.cloud", - "d467800f.cafed00d.z.vespa-app.cloud", - "d71005bf.cafed00d.z.vespa-app.cloud", - "dd0971b4.cafed00d.z.vespa-app.cloud", - "eb48ad53.cafed00d.z.vespa-app.cloud", - "ec1e1288.cafed00d.z.vespa-app.cloud", - "f2fa41ec.cafed00d.g.vespa-app.cloud", - "f411d177.cafed00d.z.vespa-app.cloud", - "f4a4d111.cafed00d.a.vespa-app.cloud", - "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud", + "c33db5ed.f5549014.z.vespa-app.cloud", + "d467800f.f5549014.z.vespa-app.cloud", + "d71005bf.f5549014.z.vespa-app.cloud", + "dd0971b4.f5549014.g.vespa-app.cloud", + "eb48ad53.f5549014.z.vespa-app.cloud", + "ec1e1288.f5549014.z.vespa-app.cloud", + "f2fa41ec.f5549014.a.vespa-app.cloud", + "f411d177.f5549014.z.vespa-app.cloud", + "f4a4d111.f5549014.z.vespa-app.cloud", + "fcf1bd63.f5549014.aws-us-east-1.w.vespa-app.cloud", "foo.app1.tenant1.g.vespa-app.cloud" ); assertEquals(expectedRecords, tester.recordNames()); @@ -1178,23 +1176,23 @@ public class RoutingPoliciesTest { .build(); context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy(); assertEquals(List.of( - "b36bf591.cafed00d.z.vespa-app.cloud", + "aa7591aa.f5549014.z.vespa-app.cloud", "bar.app1.tenant1.a.vespa-app.cloud", - "bc50b636.cafed00d.z.vespa-app.cloud", + "bc50b636.f5549014.z.vespa-app.cloud", "c0.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud", "c0.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud", "c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud", "c1.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud", "c1.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud", "c1.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud", - "c33db5ed.cafed00d.z.vespa-app.cloud", - "d467800f.cafed00d.z.vespa-app.cloud", - "d71005bf.cafed00d.z.vespa-app.cloud", - "dd0971b4.cafed00d.z.vespa-app.cloud", - "eb48ad53.cafed00d.z.vespa-app.cloud", - "ec1e1288.cafed00d.z.vespa-app.cloud", - "f411d177.cafed00d.z.vespa-app.cloud", - "f4a4d111.cafed00d.a.vespa-app.cloud" + "c33db5ed.f5549014.z.vespa-app.cloud", + "d467800f.f5549014.z.vespa-app.cloud", + "d71005bf.f5549014.z.vespa-app.cloud", + "eb48ad53.f5549014.z.vespa-app.cloud", + "ec1e1288.f5549014.z.vespa-app.cloud", + "f2fa41ec.f5549014.a.vespa-app.cloud", + "f411d177.f5549014.z.vespa-app.cloud", + "f4a4d111.f5549014.z.vespa-app.cloud" ), tester.recordNames()); // Removing application removes all records @@ -1206,11 +1204,9 @@ public class RoutingPoliciesTest { } @Test - public void generated_endpoints_enable_token() { - var tester = new RoutingPoliciesTester(SystemName.Public); + public void generated_endpoint_config_with_token() { + var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.generated); 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 @@ -1270,12 +1266,9 @@ public class RoutingPoliciesTest { } @Test - public void generated_endpoints_only() { - var tester = new RoutingPoliciesTester(SystemName.Public); + public void generated_endpoint_config() { + var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.generated); var context = tester.newDeploymentContext("tenant1", "app1", "default"); - tester.controllerTester().flagSource() - .withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true) - .withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), false); addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester); // Deploy application @@ -1317,12 +1310,10 @@ public class RoutingPoliciesTest { } @Test - public void generated_endpoints_multi_instance() { - var tester = new RoutingPoliciesTester(SystemName.Public); + public void combined_endpoint_config_with_multiple_instances() { + var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.combined); var context0 = tester.newDeploymentContext("tenant1", "app1", "default"); var context1 = tester.newDeploymentContext("tenant1", "app1", "beta"); - tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true); - addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester); // Deploy application int clustersPerZone = 1; @@ -1338,11 +1329,11 @@ public class RoutingPoliciesTest { tester.provisionLoadBalancers(clustersPerZone, context1.instanceId(), zone1); context0.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy(); assertEquals(List.of("a0.app1.tenant1.a.vespa-app.cloud", - "a9c8c045.cafed00d.z.vespa-app.cloud", "c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud", "c0.beta.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud", - "e144a11b.cafed00d.z.vespa-app.cloud", - "ee82b867.cafed00d.a.vespa-app.cloud"), + "cbff1506.f5549014.z.vespa-app.cloud", + "e144a11b.f5549014.a.vespa-app.cloud", + "ee82b867.f5549014.z.vespa-app.cloud"), tester.recordNames()); tester.assertTargets(context0.application().id(), EndpointId.of("a0"), ClusterSpec.Id.from("c0"), 0, Map.of(context0.deploymentIdIn(zone1), 1, context1.deploymentIdIn(zone1), 1)); @@ -1356,11 +1347,11 @@ public class RoutingPoliciesTest { .build(); context0.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy(); assertEquals(List.of("a0.app1.tenant1.a.vespa-app.cloud", - "a9c8c045.cafed00d.z.vespa-app.cloud", "c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud", "c0.beta.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud", - "e144a11b.cafed00d.z.vespa-app.cloud", - "ee82b867.cafed00d.a.vespa-app.cloud"), + "cbff1506.f5549014.z.vespa-app.cloud", + "e144a11b.f5549014.a.vespa-app.cloud", + "ee82b867.f5549014.z.vespa-app.cloud"), tester.recordNames()); tester.assertTargets(context0.application().id(), EndpointId.of("a0"), ClusterSpec.Id.from("c0"), 0, Map.of(context1.deploymentIdIn(zone1), 1)); @@ -1374,10 +1365,9 @@ public class RoutingPoliciesTest { } @Test - public void generated_endpoint_migration_with_global_endpoint() { - var tester = new RoutingPoliciesTester(SystemName.Public); + public void migrate_legacy_to_combined_endpoint_config_with_global_endpoint() { + var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.legacy); var context = tester.newDeploymentContext("tenant1", "app1", "default"); - addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester); // Deploy application int clustersPerZone = 2; @@ -1392,8 +1382,8 @@ public class RoutingPoliciesTest { context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy(); tester.assertTargets(context.instanceId(), EndpointId.of("foo"), 0, zone1, zone2); - // Switch to generated - tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true); + // Switch to combined + tester.setEndpointConfig(EndpointConfig.combined); context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy(); tester.assertTargets(context.instance().id(), EndpointId.of("foo"), ClusterSpec.Id.from("c0"), 0, Map.of(zone1, 1L, zone2, 1L), true); @@ -1403,9 +1393,13 @@ public class RoutingPoliciesTest { EndpointCertificate cert = new EndpointCertificate("testKey", "testCert", 1, 0, "request-id", Optional.of("leaf-request-uuid"), - List.of("name1", "name2"), - "", Optional.empty(), - Optional.empty(), Optional.of(id)); + List.of("*." + id + ".z.vespa-app.cloud", + "*." + id + ".g.vespa-app.cloud", + "*." + id + ".a.vespa-app.cloud"), + "", + Optional.empty(), + Optional.empty(), + Optional.of(id)); UnassignedCertificate pooledCert = new UnassignedCertificate(cert, state); tester.controllerTester().controller().curator().writeUnassignedCertificate(pooledCert); } @@ -1521,6 +1515,12 @@ public class RoutingPoliciesTest { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + public RoutingPoliciesTester setEndpointConfig(EndpointConfig config) { + tester.controllerTester().flagSource().withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), config.supportsLegacy()); + tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), config.supportsGenerated()); + return this; + } + public RoutingPolicies routingPolicies() { return tester.controllerTester().controller().routing().policies(); } |