diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-10-25 14:55:09 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2019-10-28 11:22:56 +0100 |
commit | 95a8da1ac91257a6c63dac41ceea97613e787c56 (patch) | |
tree | 8bbb5d04e16961faa00071042ab344caa1bfd6ea /controller-server/src/test/java/com | |
parent | f8d607f4b6ec8aca16731141da3317def4ebdc32 (diff) |
Extract DeploymentContext from InternalDeploymentTester
Diffstat (limited to 'controller-server/src/test/java/com')
10 files changed, 643 insertions, 476 deletions
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 ce357a68ae2..6e204a418ad 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 @@ -49,6 +49,8 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.OptionalLong; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Handler; @@ -73,6 +75,9 @@ public final class ControllerTester { private final ServiceRegistryMock serviceRegistry; private final CuratorDb curator; private final RotationsConfig rotationsConfig; + private final AtomicLong nextPropertyId = new AtomicLong(1000); + private final AtomicInteger nextProjectId = new AtomicInteger(1000); + private final AtomicInteger nextDomainId = new AtomicInteger(1000); private Controller controller; @@ -262,6 +267,11 @@ public final class ControllerTester { return name; } + public TenantName createTenant(String tenantName) { + return createTenant(tenantName, "domain" + nextDomainId.getAndIncrement(), + nextPropertyId.getAndIncrement()); + } + public TenantName createTenant(String tenantName, String domainName, Long propertyId) { return createTenant(tenantName, domainName, propertyId, Optional.empty()); } @@ -272,6 +282,10 @@ public final class ControllerTester { new OktaAccessToken("okta-token"))); } + public Application createApplication(TenantName tenant, String applicationName, String instanceName) { + return createApplication(tenant, applicationName, instanceName, nextProjectId.getAndIncrement()); + } + public Application createApplication(TenantName tenant, String applicationName, String instanceName, long projectId) { TenantAndApplicationId applicationId = TenantAndApplicationId.from(tenant.value(), applicationName); controller().applications().createApplication(applicationId, credentialsFor(applicationId)); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java new file mode 100644 index 00000000000..ca60182d485 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java @@ -0,0 +1,490 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.deployment; + +import com.yahoo.component.Version; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.AthenzDomain; +import com.yahoo.config.provision.AthenzService; +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.security.KeyAlgorithm; +import com.yahoo.security.KeyUtils; +import com.yahoo.security.SignatureAlgorithm; +import com.yahoo.security.X509CertificateBuilder; +import com.yahoo.vespa.hosted.controller.Application; +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.deployment.ApplicationVersion; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId; +import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint; +import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGeneratorMock; +import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud; +import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; +import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock; +import com.yahoo.vespa.hosted.controller.maintenance.JobRunner; +import com.yahoo.vespa.hosted.controller.maintenance.NameServiceDispatcher; +import com.yahoo.vespa.hosted.controller.maintenance.ReadyJobsTrigger; + +import javax.security.auth.x500.X500Principal; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * A deployment context for an application. This allows fine-grained control of the deployment of an application's + * instances. + * + * References to this should be acquired through {@link InternalDeploymentTester#deploymentContext}. + * + * Tester code that is not specific to deployments should be added to either {@link ControllerTester} or + * {@link InternalDeploymentTester} instead of this class. + * + * @author mpolden + */ +public class DeploymentContext { + + public static final ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service")) + .upgradePolicy("default") + .region("us-central-1") + .parallel("us-west-1", "us-east-3") + .emailRole("author") + .emailAddress("b@a") + .build(); + + public static final ApplicationPackage publicCdApplicationPackage = new ApplicationPackageBuilder() + .athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service")) + .upgradePolicy("default") + .region("aws-us-east-1c") + .emailRole("author") + .emailAddress("b@a") + .trust(generateCertificate()) + .build(); + + private final TenantAndApplicationId applicationId; + private final ApplicationId instanceId; + private final TesterId testerId; + private final JobController jobs; + private final RoutingGeneratorMock routing; + private final MockTesterCloud cloud; + private final JobRunner runner; + private final ControllerTester tester; + private final ReadyJobsTrigger readyJobsTrigger; + private final NameServiceDispatcher nameServiceDispatcher;; + + private ApplicationVersion lastSubmission = null; + private boolean deferDnsUpdates = false; + + public DeploymentContext(ApplicationId instanceId, InternalDeploymentTester tester) { + + this.applicationId = TenantAndApplicationId.from(instanceId); + this.instanceId = instanceId; + this.testerId = TesterId.of(instanceId); + this.jobs = tester.controller().jobController(); + this.runner = tester.runner(); + this.tester = tester.controllerTester(); + this.routing = this.tester.serviceRegistry().routingGeneratorMock(); + this.cloud = this.tester.serviceRegistry().testerCloud(); + this.readyJobsTrigger = tester.readyJobsTrigger(); + this.nameServiceDispatcher = tester.nameServiceDispatcher(); + createTenantAndApplication(); + } + + private void createTenantAndApplication() { + try { + var tenant = tester.createTenant(instanceId.tenant().value()); + tester.createApplication(tenant, instanceId.application().value(), instanceId.instance().value()); + } catch (IllegalArgumentException ignored) { + // TODO(mpolden): Application already exists. Remove this once InternalDeploymentTester stops implicitly creating applications + } + } + + public Application application() { + return tester.controller().applications().requireApplication(applicationId); + } + + public Instance instance() { + return tester.controller().applications().requireInstance(instanceId); + } + + public ApplicationId instanceId() { + return instanceId; + } + + public DeploymentId deploymentIdIn(ZoneId zone) { + return new DeploymentId(instanceId, zone); + } + + + /** Completely deploy the latest change */ + public DeploymentContext deploy() { + assertNotNull("Application package submitted", lastSubmission); + assertFalse("Submission is not already deployed", application().instances().values().stream() + .anyMatch(instance -> instance.deployments().values().stream() + .anyMatch(deployment -> deployment.applicationVersion().equals(lastSubmission)))); + assertEquals(lastSubmission, application().change().application().get()); + assertFalse(application().change().platform().isPresent()); + completeRollout(); + assertFalse(application().change().hasTargets()); + return this; + } + + /** Upgrade platform of this to given version */ + public DeploymentContext deployPlatform(Version version) { + assertEquals(tester.controller().systemVersion(), version); + assertFalse(application().instances().values().stream() + .anyMatch(instance -> instance.deployments().values().stream() + .anyMatch(deployment -> deployment.version().equals(version)))); + assertEquals(version, application().change().platform().get()); + assertFalse(application().change().application().isPresent()); + + completeRollout(); + + assertTrue(application().productionDeployments().values().stream() + .allMatch(deployments -> deployments.stream() + .allMatch(deployment -> deployment.version().equals(version)))); + + for (JobType type : new DeploymentSteps(application().deploymentSpec(), tester.controller()::system).productionJobs()) + assertTrue(tester.configServer().nodeRepository() + .list(type.zone(tester.controller().system()), applicationId.defaultInstance()).stream() // TODO jonmv: support more + .allMatch(node -> node.currentVersion().equals(version))); + + assertFalse(application().change().hasTargets()); + return this; + } + + + /** Defer DNS updates */ + public DeploymentContext deferDnsUpdates() { + deferDnsUpdates = true; + return this; + } + + /** Flush all pending DNS updates */ + public DeploymentContext flushDnsUpdates() { + nameServiceDispatcher.run(); + assertTrue("All name service requests dispatched", + tester.controller().curator().readNameServiceQueue().requests().isEmpty()); + return this; + } + + /** Submit given application package for deployment */ + public DeploymentContext submit(ApplicationPackage applicationPackage) { + return submit(applicationPackage, BuildJob.defaultSourceRevision); + } + + /** Submit given application package for deployment */ + public DeploymentContext submit(ApplicationPackage applicationPackage, SourceRevision sourceRevision) { + var projectId = tester.controller().applications() + .requireApplication(applicationId) + .projectId() + .orElseThrow(() -> new IllegalArgumentException("No project ID set for " + applicationId)); + lastSubmission = jobs.submit(applicationId, sourceRevision, "a@b", projectId, applicationPackage, new byte[0]); + return this; + } + + + /** Submit the default application package for deployment */ + public DeploymentContext submit() { + return submit(tester.controller().system().isPublic() ? publicCdApplicationPackage : applicationPackage); + } + + /** Trigger all outstanding jobs, if any */ + public DeploymentContext triggerJobs() { + while (tester.controller().applications().deploymentTrigger().triggerReadyJobs() > 0); + return this; + } + + /** Fail current deployment in given job */ + public DeploymentContext failDeployment(JobType type) { + triggerJobs(); + var job = jobId(type); + RunId id = currentRun(job).id(); + configServer().throwOnNextPrepare(new IllegalArgumentException("Exception")); + runner.advance(currentRun(job)); + assertTrue(jobs.run(id).get().hasFailed()); + assertTrue(jobs.run(id).get().hasEnded()); + doTeardown(job); + return this; + } + + /** Returns the last submitted application version */ + public Optional<ApplicationVersion> lastSubmission() { + return Optional.ofNullable(lastSubmission); + } + + /** Runs and returns all remaining jobs for the application, at most once, and asserts the current change is rolled out. */ + public DeploymentContext completeRollout() { + readyJobsTrigger.run(); + Set<JobType> jobs = new HashSet<>(); + List<Run> activeRuns; + while ( ! (activeRuns = this.jobs.active(applicationId)).isEmpty()) + for (Run run : activeRuns) + if (jobs.add(run.id().type())) { + runJob(run.id().type()); + readyJobsTrigger.run(); + } + else + throw new AssertionError("Job '" + run.id().type() + "' was run twice for '" + instanceId + "'"); + + assertFalse("Change should have no targets, but was " + application().change(), application().change().hasTargets()); + if (!deferDnsUpdates) { + flushDnsUpdates(); + } + return this; + } + + /** Pulls the ready job trigger, and then runs the whole of the given job, successfully. */ + public DeploymentContext runJob(JobType type) { + var job = jobId(type); + triggerJobs(); + doDeploy(job); + doUpgrade(job); + doConverge(job); + if (job.type().environment().isManuallyDeployed()) + return this; + doInstallTester(job); + doTests(job); + doTeardown(job); + return this; + } + + /** Simulate upgrade time out in given job */ + public DeploymentContext timeOutUpgrade(JobType type) { + var job = jobId(type); + triggerJobs(); + RunId id = currentRun(job).id(); + doDeploy(job); + tester.clock().advance(InternalStepRunner.installationTimeout.plusSeconds(1)); + runner.advance(currentRun(job)); + assertTrue(jobs.run(id).get().hasFailed()); + assertTrue(jobs.run(id).get().hasEnded()); + doTeardown(job); + return this; + } + + /** Simulate convergence time out in given job */ + public void timeOutConvergence(JobType type) { + var job = jobId(type); + triggerJobs(); + RunId id = currentRun(job).id(); + doDeploy(job); + doUpgrade(job); + tester.clock().advance(InternalStepRunner.installationTimeout.plusSeconds(1)); + runner.advance(currentRun(job)); + assertTrue(jobs.run(id).get().hasFailed()); + assertTrue(jobs.run(id).get().hasEnded()); + doTeardown(job); + } + + /** Sets a single endpoint in the routing layer for the instance in this */ + public DeploymentContext setEndpoints(ZoneId zone) { + return setEndpoints(zone, false); + } + + /** Deploy default application package, start a run for that change and return its ID */ + public RunId newRun(JobType type) { + assertFalse(application().internal()); // Use this only once per test. + submit(); + readyJobsTrigger.maintain(); + + if (type.isProduction()) { + runJob(JobType.systemTest); + runJob(JobType.stagingTest); + readyJobsTrigger.maintain(); + } + + Run run = jobs.active().stream() + .filter(r -> r.id().type() == type) + .findAny() + .orElseThrow(() -> new AssertionError(type + " is not among the active: " + jobs.active())); + return run.id(); + } + + /** Start tests in system test stage */ + public RunId startSystemTestTests() { + RunId id = newRun(JobType.systemTest); + runner.run(); + configServer().convergeServices(instanceId, JobType.systemTest.zone(tester.controller().system())); + configServer().convergeServices(testerId.id(), JobType.systemTest.zone(tester.controller().system())); + setEndpoints(JobType.systemTest.zone(tester.controller().system())); + setTesterEndpoints(JobType.systemTest.zone(tester.controller().system())); + runner.run(); + assertEquals(unfinished, jobs.run(id).get().steps().get(Step.endTests)); + return id; + } + + /** Deploys tester and real app, and completes initial staging installation first if needed. */ + private void doDeploy(JobId job) { + RunId id = currentRun(job).id(); + ZoneId zone = zone(job); + DeploymentId deployment = new DeploymentId(job.application(), zone); + + // First steps are always deployments. + runner.advance(currentRun(job)); + + if (job.type() == JobType.stagingTest) { // Do the initial deployment and installation of the real application. + assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installInitialReal)); + Versions versions = currentRun(job).versions(); + tester.configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), versions.sourcePlatform().orElse(versions.targetPlatform())); + configServer().convergeServices(id.application(), zone); + setEndpoints(zone); + runner.advance(currentRun(job)); + assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installInitialReal)); + } + } + + /** Upgrades nodes to target version. */ + private void doUpgrade(JobId job) { + RunId id = currentRun(job).id(); + ZoneId zone = zone(job); + DeploymentId deployment = new DeploymentId(job.application(), zone); + + assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installReal)); + configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), currentRun(job).versions().targetPlatform()); + runner.advance(currentRun(job)); + } + + /** Returns the current run for the given job type, and verifies it is still running normally. */ + private Run currentRun(JobId job) { + Run run = jobs.last(job) + .filter(r -> r.id().type() == job.type()) + .orElseThrow(() -> new AssertionError(job.type() + " is not among the active: " + jobs.active())); + assertFalse(run.hasFailed()); + assertFalse(run.hasEnded()); + return run; + } + + /** Sets a single endpoint in the routing layer for the tester instance in this */ + private DeploymentContext setTesterEndpoints(ZoneId zone) { + return setEndpoints(zone, true); + } + + /** Sets a single endpoint in the routing layer; this matches that required for the tester */ + private DeploymentContext setEndpoints(ZoneId zone, boolean tester) { + var id = instanceId; + if (tester) { + id = testerId.id(); + } + routing.putEndpoints(new DeploymentId(id, zone), + Collections.singletonList(new RoutingEndpoint(String.format("https://%s--%s--%s.%s.%s.vespa:43", + id.instance().value(), + id.application().value(), + id.tenant().value(), + zone.region().value(), + zone.environment().value()), + "host1", + false, + String.format("cluster1.%s.%s.%s.%s", + id.application().value(), + id.tenant().value(), + zone.region().value(), + zone.environment().value())))); + return this; + } + + /** Lets nodes converge on new application version. */ + private void doConverge(JobId job) { + RunId id = currentRun(job).id(); + ZoneId zone = zone(job); + + assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installReal)); + configServer().convergeServices(id.application(), zone); + setEndpoints(zone); + runner.advance(currentRun(job)); + if (job.type().environment().isManuallyDeployed()) { + assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installReal)); + assertTrue(jobs.run(id).get().hasEnded()); + return; + } + assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installReal)); + } + + /** Installs tester and starts tests. */ + private void doInstallTester(JobId job) { + RunId id = currentRun(job).id(); + ZoneId zone = zone(job); + + assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installTester)); + configServer().nodeRepository().doUpgrade(new DeploymentId(TesterId.of(job.application()).id(), zone), Optional.empty(), currentRun(job).versions().targetPlatform()); + runner.advance(currentRun(job)); + assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installTester)); + configServer().convergeServices(TesterId.of(id.application()).id(), zone); + runner.advance(currentRun(job)); + assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installTester)); + setTesterEndpoints(zone); + runner.advance(currentRun(job)); + } + + /** Completes tests with success. */ + private void doTests(JobId job) { + RunId id = currentRun(job).id(); + ZoneId zone = zone(job); + + // All installation is complete and endpoints are ready, so tests may begin. + assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installReal)); + assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installTester)); + assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.startTests)); + + assertEquals(unfinished, jobs.run(id).get().steps().get(Step.endTests)); + cloud.set(TesterCloud.Status.SUCCESS); + runner.advance(currentRun(job)); + assertTrue(jobs.run(id).get().hasEnded()); + assertFalse(jobs.run(id).get().hasFailed()); + assertEquals(job.type().isProduction(), instance().deployments().containsKey(zone)); + assertTrue(configServer().nodeRepository().list(zone, TesterId.of(id.application()).id()).isEmpty()); + } + + /** Removes endpoints from routing layer — always call this. */ + private void doTeardown(JobId job) { + ZoneId zone = zone(job); + DeploymentId deployment = new DeploymentId(job.application(), zone); + + if ( ! instance().deployments().containsKey(zone)) + routing.removeEndpoints(deployment); + routing.removeEndpoints(new DeploymentId(TesterId.of(job.application()).id(), zone)); + } + + private JobId jobId(JobType type) { + return new JobId(instanceId, type); + } + + private ZoneId zone(JobId job) { + return job.type().zone(tester.controller().system()); + } + + private ConfigServerMock configServer() { + return tester.configServer(); + } + + private static X509Certificate generateCertificate() { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + X500Principal subject = new X500Principal("CN=subject"); + return X509CertificateBuilder.fromKeypair(keyPair, + subject, + Instant.now(), + Instant.now().plusSeconds(1), + SignatureAlgorithm.SHA512_WITH_ECDSA, + BigInteger.valueOf(1)) + .build(); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java index 7018e063efc..fb5f0bd94c0 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java @@ -161,7 +161,7 @@ public class DeploymentTriggerTest { public void abortsInternalJobsOnNewApplicationChange() { Instance instance = iTester.instance(); Application application = iTester.application(); - ApplicationPackage applicationPackage = InternalDeploymentTester.applicationPackage; + ApplicationPackage applicationPackage = DeploymentContext.applicationPackage; tester.jobCompletion(component).application(application).uploadArtifact(applicationPackage).submit(); tester.deployAndNotify(instance.id(), Optional.empty(), true, systemTest); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java index 206db46ac27..fb9367cc020 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalDeploymentTester.java @@ -3,14 +3,8 @@ package com.yahoo.vespa.hosted.controller.deployment; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.AthenzDomain; -import com.yahoo.config.provision.AthenzService; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.log.LogLevel; -import com.yahoo.security.KeyAlgorithm; -import com.yahoo.security.KeyUtils; -import com.yahoo.security.SignatureAlgorithm; -import com.yahoo.security.X509CertificateBuilder; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ApplicationController; @@ -20,13 +14,10 @@ import com.yahoo.vespa.hosted.controller.Instance; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud; import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterId; -import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingEndpoint; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGeneratorMock; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; @@ -36,67 +27,43 @@ import com.yahoo.vespa.hosted.controller.maintenance.JobControl; import com.yahoo.vespa.hosted.controller.maintenance.JobRunner; import com.yahoo.vespa.hosted.controller.maintenance.JobRunnerTest; import com.yahoo.vespa.hosted.controller.maintenance.NameServiceDispatcher; +import com.yahoo.vespa.hosted.controller.maintenance.ReadyJobsTrigger; import com.yahoo.vespa.hosted.controller.maintenance.Upgrader; -import javax.security.auth.x500.X500Principal; -import java.math.BigInteger; -import java.security.KeyPair; -import java.security.cert.X509Certificate; import java.time.Duration; -import java.time.Instant; import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; -import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.aborted; -import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +/** + * @author jonmv + */ public class InternalDeploymentTester { + // Set a long interval so that maintainers never do scheduled runs during tests + private static final Duration maintenanceInterval = Duration.ofDays(1); + private static final String ATHENZ_DOMAIN = "domain"; private static final String ATHENZ_SERVICE = "service"; - public static final ApplicationPackage applicationPackage = new ApplicationPackageBuilder() - .athenzIdentity(AthenzDomain.from(ATHENZ_DOMAIN), AthenzService.from(ATHENZ_SERVICE)) - .upgradePolicy("default") - .region("us-central-1") - .parallel("us-west-1", "us-east-3") - .emailRole("author") - .emailAddress("b@a") - .build(); - public static final ApplicationPackage publicCdApplicationPackage = new ApplicationPackageBuilder() - .athenzIdentity(AthenzDomain.from(ATHENZ_DOMAIN), AthenzService.from(ATHENZ_SERVICE)) - .upgradePolicy("default") - .region("aws-us-east-1c") - .emailRole("author") - .emailAddress("b@a") - .trust(generateCertificate()) - .build(); public static final TenantAndApplicationId appId = TenantAndApplicationId.from("tenant", "application"); public static final ApplicationId instanceId = appId.defaultInstance(); public static final TesterId testerId = TesterId.of(instanceId); public static final String athenzDomain = "domain"; + private final DeploymentContext defaultContext; private final DeploymentTester tester; private final JobController jobs; private final RoutingGeneratorMock routing; private final MockTesterCloud cloud; private final JobRunner runner; + private final ReadyJobsTrigger readyJobsTrigger; private final NameServiceDispatcher nameServiceDispatcher; - private final AtomicLong nextPropertyId = new AtomicLong(1); - private final AtomicInteger nextProjectId = new AtomicInteger(1); - private final AtomicInteger nextDomainId = new AtomicInteger(100); - public DeploymentTester tester() { return tester; } public JobController jobs() { return jobs; } public RoutingGeneratorMock routing() { return routing; } @@ -116,15 +83,16 @@ public class InternalDeploymentTester { public InternalDeploymentTester() { tester = new DeploymentTester(); - createApplication(instanceId.tenant().value(), instanceId.application().value(), instanceId.instance().value()); jobs = tester.controller().jobController(); routing = tester.controllerTester().serviceRegistry().routingGeneratorMock(); cloud = (MockTesterCloud) tester.controller().jobController().cloud(); - runner = new JobRunner(tester.controller(), Duration.ofDays(1), new JobControl(tester.controller().curator()), + var jobControl = new JobControl(tester.controller().curator()); + runner = new JobRunner(tester.controller(), Duration.ofDays(1), jobControl, JobRunnerTest.inThreadExecutor(), new InternalStepRunner(tester.controller())); - this.nameServiceDispatcher = new NameServiceDispatcher(tester.controller(), Duration.ofHours(12), - new JobControl(tester.controller().curator()), - Integer.MAX_VALUE); + readyJobsTrigger = new ReadyJobsTrigger(tester.controller(), maintenanceInterval, jobControl); + nameServiceDispatcher = new NameServiceDispatcher(tester.controller(), maintenanceInterval, jobControl, + Integer.MAX_VALUE); + defaultContext = newDeploymentContext(instanceId); routing.putEndpoints(new DeploymentId(null, null), Collections.emptyList()); // Turn off default behaviour for the mock. // Get deployment job logs to stderr. @@ -137,26 +105,41 @@ public class InternalDeploymentTester { domain.services.put(ATHENZ_SERVICE, new AthenzDbMock.Service(true)); } + public ReadyJobsTrigger readyJobsTrigger() { + return readyJobsTrigger; + } + + public NameServiceDispatcher nameServiceDispatcher() { + return nameServiceDispatcher; + } + + /** Returns the default deployment context owned by this */ + public DeploymentContext deploymentContext() { + return defaultContext; + } + + /** Create a new deployment context for given application */ + public DeploymentContext newDeploymentContext(String tenantName, String applicationName, String instanceName) { + return newDeploymentContext(ApplicationId.from(tenantName, applicationName, instanceName)); + } + + /** Create a new deployment context for given application */ + public DeploymentContext newDeploymentContext(ApplicationId instance) { + return new DeploymentContext(instance, this); + } + /** Create a new application with given tenant and application name */ public Application createApplication(String tenantName, String applicationName, String instanceName) { - return tester.controllerTester().createApplication(tester.controllerTester().createTenant(tenantName, - athenzDomain + nextDomainId.getAndIncrement(), - nextPropertyId.getAndIncrement()), - applicationName, - instanceName, - nextProjectId.getAndIncrement()); + return newDeploymentContext(tenantName, applicationName, instanceName).application(); } /** Submits a new application, and returns the version of the new submission. */ - public ApplicationVersion newSubmission(TenantAndApplicationId id, ApplicationPackage applicationPackage, - SourceRevision revision, String authorEmail, long projectId) { - return jobs.submit(id, revision, authorEmail, projectId, applicationPackage, new byte[0]); + public ApplicationVersion newSubmission(TenantAndApplicationId id, ApplicationPackage applicationPackage, SourceRevision sourceRevision) { + return newDeploymentContext(id.defaultInstance()).submit(applicationPackage, sourceRevision).lastSubmission().get(); } - /** Submits a new application, and returns the version of the new submission. */ public ApplicationVersion newSubmission(TenantAndApplicationId id, ApplicationPackage applicationPackage) { - var projectId = tester.application(id).projectId().orElseThrow(() -> new IllegalArgumentException("No project ID set for " + id)); - return newSubmission(id, applicationPackage, BuildJob.defaultSourceRevision, "a@b", projectId); + return newSubmission(id, applicationPackage, BuildJob.defaultSourceRevision); } /** @@ -170,45 +153,14 @@ public class InternalDeploymentTester { * Submits a new application, and returns the version of the new submission. */ public ApplicationVersion newSubmission() { - return newSubmission(appId, tester.controller().system().isPublic() ? publicCdApplicationPackage : applicationPackage); + return defaultContext.submit().lastSubmission().get(); } /** * Sets a single endpoint in the routing mock; this matches that required for the tester. */ public void setEndpoints(ApplicationId id, ZoneId zone) { - routing.putEndpoints(new DeploymentId(id, zone), - Collections.singletonList(new RoutingEndpoint(String.format("https://%s--%s--%s.%s.%s.vespa:43", - id.instance().value(), - id.application().value(), - id.tenant().value(), - zone.region().value(), - zone.environment().value()), - "host1", - false, - String.format("cluster1.%s.%s.%s.%s", - id.application().value(), - id.tenant().value(), - zone.region().value(), - zone.environment().value())))); - } - - /** Runs and returns all remaining jobs for the application, at most once, and asserts the current change is rolled out. */ - public List<JobType> completeRollout(TenantAndApplicationId id) { - tester.readyJobTrigger().run(); - Set<JobType> jobs = new HashSet<>(); - List<Run> activeRuns; - while ( ! (activeRuns = jobs().active(id)).isEmpty()) - for (Run run : activeRuns) - if (jobs.add(run.id().type())) { - runJob(run.id().job()); - tester.readyJobTrigger().run(); - } - else - throw new AssertionError("Job '" + run.id().type() + "' was run twice for '" + instanceId + "'"); - - assertFalse("Change should have no targets, but was " + application().change(), application().change().hasTargets()); - return List.copyOf(jobs); + newDeploymentContext(id).setEndpoints(zone); } /** Completely deploys the given application version, assuming it is the last to be submitted. */ @@ -218,13 +170,15 @@ public class InternalDeploymentTester { /** Completely deploys the given application version, assuming it is the last to be submitted. */ public void deployNewSubmission(TenantAndApplicationId id, ApplicationVersion version) { - assertFalse(tester.application(id).instances().values().stream() - .anyMatch(instance -> instance.deployments().values().stream() - .anyMatch(deployment -> deployment.applicationVersion().equals(version)))); - assertEquals(version, tester.application(id).change().application().get()); - assertFalse(tester.application(id).change().platform().isPresent()); - completeRollout(id); - assertFalse(tester.application(id).change().hasTargets()); + var context = newDeploymentContext(id.defaultInstance()); + var application = context.application(); + assertFalse(application.instances().values().stream() + .anyMatch(instance -> instance.deployments().values().stream() + .anyMatch(deployment -> deployment.applicationVersion().equals(version)))); + assertEquals(version, application.change().application().get()); + assertFalse(application.change().platform().isPresent()); + context.completeRollout(); + assertFalse(context.application().change().hasTargets()); } /** Completely deploys the given, new platform. */ @@ -234,340 +188,62 @@ public class InternalDeploymentTester { /** Completely deploys the given, new platform. */ public void deployNewPlatform(TenantAndApplicationId id, Version version) { - assertEquals(tester.controller().systemVersion(), version); - assertFalse(tester.application(id).instances().values().stream() - .anyMatch(instance -> instance.deployments().values().stream() - .anyMatch(deployment -> deployment.version().equals(version)))); - assertEquals(version, tester.application(id).change().platform().get()); - assertFalse(tester.application(id).change().application().isPresent()); - - completeRollout(id); - - assertTrue(tester.application(id).productionDeployments().values().stream() - .allMatch(deployments -> deployments.stream() - .allMatch(deployment -> deployment.version().equals(version)))); - - for (JobType type : new DeploymentSteps(application().deploymentSpec(), tester.controller()::system).productionJobs()) - assertTrue(tester.configServer().nodeRepository() - .list(type.zone(tester.controller().system()), id.defaultInstance()).stream() // TODO jonmv: support more - .allMatch(node -> node.currentVersion().equals(version))); - - assertFalse(tester.application(id).change().hasTargets()); + newDeploymentContext(id.defaultInstance()).deployPlatform(version); } public void triggerJobs() { tester.triggerUntilQuiescence(); } - /** Returns the current run for the given job type, and verifies it is still running normally. */ - public Run currentRun(JobId job) { - Run run = jobs.last(job) - .filter(r -> r.id().type() == job.type()) - .orElseThrow(() -> new AssertionError(job.type() + " is not among the active: " + jobs.active())); - assertFalse(run.hasFailed()); - assertFalse(run.hasEnded()); - return run; - } - - /** Deploys tester and real app, and completes initial staging installation first if needed. */ - public void doDeploy(JobType type) { - doDeploy(new JobId(instanceId, type)); - } - - /** Deploys tester and real app, and completes initial staging installation first if needed. */ - public void doDeploy(ApplicationId instanceId, JobType type) { - doDeploy(new JobId(instanceId, type)); - } - - /** Deploys tester and real app, and completes initial staging installation first if needed. */ - public void doDeploy(JobId job) { - RunId id = currentRun(job).id(); - ZoneId zone = job.type().zone(tester.controller().system()); - DeploymentId deployment = new DeploymentId(job.application(), zone); - - // First steps are always deployments. - runner.advance(currentRun(job)); - - if (job.type() == JobType.stagingTest) { // Do the initial deployment and installation of the real application. - assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installInitialReal)); - Versions versions = currentRun(job).versions(); - tester.configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), versions.sourcePlatform().orElse(versions.targetPlatform())); - tester.configServer().convergeServices(id.application(), zone); - setEndpoints(id.application(), zone); - runner.advance(currentRun(job)); - assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installInitialReal)); - } - } - - /** Upgrades nodes to target version. */ - public void doUpgrade(JobType type) { - doUpgrade(new JobId(instanceId, type)); - } - - /** Upgrades nodes to target version. */ - public void doUpgrade(ApplicationId instanceId, JobType type) { - doUpgrade(new JobId(instanceId, type)); - } - - /** Upgrades nodes to target version. */ - public void doUpgrade(JobId job) { - RunId id = currentRun(job).id(); - ZoneId zone = job.type().zone(tester.controller().system()); - DeploymentId deployment = new DeploymentId(job.application(), zone); - - assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installReal)); - tester.configServer().nodeRepository().doUpgrade(deployment, Optional.empty(), currentRun(job).versions().targetPlatform()); - runner.advance(currentRun(job)); - } - - /** Lets nodes converge on new application version. */ - public void doConverge(JobType type) { - doConverge(new JobId(instanceId, type)); - } - - /** Lets nodes converge on new application version. */ - public void doConverge(ApplicationId instanceId, JobType type) { - doConverge(new JobId(instanceId, type)); - } - - /** Lets nodes converge on new application version. */ - public void doConverge(JobId job) { - RunId id = currentRun(job).id(); - ZoneId zone = job.type().zone(tester.controller().system()); - - assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installReal)); - tester.configServer().convergeServices(id.application(), zone); - setEndpoints(id.application(), zone); - runner.advance(currentRun(job)); - if (job.type().environment().isManuallyDeployed()) { - assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installReal)); - assertTrue(jobs.run(id).get().hasEnded()); - return; - } - assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installReal)); - } - - /** Installs tester and starts tests. */ - public void doInstallTester(JobType type) { - doInstallTester(new JobId(instanceId, type)); - } - - /** Installs tester and starts tests. */ - public void doInstallTester(ApplicationId instanceId, JobType type) { - doInstallTester(new JobId(instanceId, type)); - } - - /** Installs tester and starts tests. */ - public void doInstallTester(JobId job) { - RunId id = currentRun(job).id(); - ZoneId zone = job.type().zone(tester.controller().system()); - - assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installTester)); - tester.configServer().nodeRepository().doUpgrade(new DeploymentId(TesterId.of(job.application()).id(), zone), Optional.empty(), currentRun(job).versions().targetPlatform()); - runner.advance(currentRun(job)); - assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installTester)); - tester.configServer().convergeServices(TesterId.of(id.application()).id(), zone); - runner.advance(currentRun(job)); - assertEquals(unfinished, jobs.run(id).get().steps().get(Step.installTester)); - setEndpoints(TesterId.of(id.application()).id(), zone); - runner.advance(currentRun(job)); - } - - /** Completes tests with success. */ - public void doTests(JobType type) { - doTests(new JobId(instanceId, type)); - } - - /** Completes tests with success. */ - public void doTests(ApplicationId instanceId, JobType type) { - doTests(new JobId(instanceId, type)); - } - - /** Completes tests with success. */ - public void doTests(JobId job) { - RunId id = currentRun(job).id(); - ZoneId zone = job.type().zone(tester.controller().system()); - - // All installation is complete and endpoints are ready, so tests may begin. - assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installReal)); - assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.installTester)); - assertEquals(Step.Status.succeeded, jobs.run(id).get().steps().get(Step.startTests)); - - assertEquals(unfinished, jobs.run(id).get().steps().get(Step.endTests)); - cloud.set(TesterCloud.Status.SUCCESS); - runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasEnded()); - assertFalse(jobs.run(id).get().hasFailed()); - assertEquals(job.type().isProduction(), tester.instance(job.application()).deployments().containsKey(zone)); - assertTrue(tester.configServer().nodeRepository().list(zone, TesterId.of(id.application()).id()).isEmpty()); - } - - /** Removes endpoints from routing layer — always call this. */ - public void doTeardown(JobType type) { - doTeardown(new JobId(instanceId, type)); - } - - /** Removes endpoints from routing layer — always call this. */ - public void doTeardown(ApplicationId instanceId, JobType type) { - doTeardown(new JobId(instanceId, type)); - } - - /** Removes endpoints from routing layer — always call this. */ - public void doTeardown(JobId job) { - ZoneId zone = job.type().zone(tester.controller().system()); - DeploymentId deployment = new DeploymentId(job.application(), zone); - - if ( ! instance().deployments().containsKey(zone)) - routing.removeEndpoints(deployment); - routing.removeEndpoints(new DeploymentId(TesterId.of(job.application()).id(), zone)); - } - /** Starts a manual deployment of the given package, and then runs the whole of the given job, successfully. */ public void runJob(ApplicationId instanceId, JobType type, ApplicationPackage applicationPackage) { jobs.deploy(instanceId, type, Optional.empty(), applicationPackage); - runJob(new JobId(instanceId, type)); + newDeploymentContext(instanceId).runJob(type); } /** Pulls the ready job trigger, and then runs the whole of the given job, successfully. */ public void runJob(JobType type) { - runJob(instanceId, type); + defaultContext.runJob(type); } /** Pulls the ready job trigger, and then runs the whole of the given job, successfully. */ public void runJob(ApplicationId instanceId, JobType type) { if (type.environment().isManuallyDeployed()) throw new IllegalArgumentException("Use overload with application package for dev/perf jobs"); - runJob(new JobId(instanceId, type)); - } - - /** Pulls the ready job trigger, and then runs the whole of the given job, successfully. */ - private void runJob(JobId job) { - triggerJobs(); - doDeploy(job); - doUpgrade(job); - doConverge(job); - if (job.type().environment().isManuallyDeployed()) - return; - - doInstallTester(job); - doTests(job); - doTeardown(job); - } - - public void jobAborted(JobType type) { - jobAborted(new JobId(instanceId, type)); - } - - public void jobAborted(ApplicationId instanceId, JobType type) { - jobAborted(new JobId(instanceId, type)); - } - - public void jobAborted(JobId job) { - triggerJobs(); - RunId id = currentRun(job).id(); - runner.advance(currentRun(job)); - assertEquals(aborted, jobs.run(id).get().status()); - assertTrue(jobs.run(id).get().hasEnded()); + newDeploymentContext(instanceId).runJob(type); } public void failDeployment(JobType type) { - failDeployment(new JobId(instanceId, type)); + defaultContext.failDeployment(type); } public void failDeployment(ApplicationId instanceId, JobType type) { - failDeployment(new JobId(instanceId, type)); - } - - public void failDeployment(JobId job) { - triggerJobs(); - RunId id = currentRun(job).id(); - tester.configServer().throwOnNextPrepare(new IllegalArgumentException("Exception")); - runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasFailed()); - assertTrue(jobs.run(id).get().hasEnded()); - doTeardown(job); + newDeploymentContext(instanceId).failDeployment(type); } public void timeOutUpgrade(JobType type) { - timeOutUpgrade(new JobId(instanceId, type)); + defaultContext.timeOutUpgrade(type); } public void timeOutUpgrade(ApplicationId instanceId, JobType type) { - timeOutUpgrade(new JobId(instanceId, type)); - } - - public void timeOutUpgrade(JobId job) { - triggerJobs(); - RunId id = currentRun(job).id(); - doDeploy(job); - clock().advance(InternalStepRunner.installationTimeout.plusSeconds(1)); - runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasFailed()); - assertTrue(jobs.run(id).get().hasEnded()); - doTeardown(job); + newDeploymentContext(instanceId).timeOutConvergence(type); } public void timeOutConvergence(JobType type) { - timeOutConvergence(new JobId(instanceId, type)); + defaultContext.timeOutConvergence(type); } public void timeOutConvergence(ApplicationId instanceId, JobType type) { - timeOutConvergence(new JobId(instanceId, type)); - } - - public void timeOutConvergence(JobId job) { - triggerJobs(); - RunId id = currentRun(job).id(); - doDeploy(job); - doUpgrade(job); - clock().advance(InternalStepRunner.installationTimeout.plusSeconds(1)); - runner.advance(currentRun(job)); - assertTrue(jobs.run(id).get().hasFailed()); - assertTrue(jobs.run(id).get().hasEnded()); - doTeardown(job); + newDeploymentContext(instanceId).timeOutConvergence(type); } public RunId startSystemTestTests() { - RunId id = newRun(JobType.systemTest); - runner.run(); - tester.configServer().convergeServices(instanceId, JobType.systemTest.zone(tester.controller().system())); - tester.configServer().convergeServices(testerId.id(), JobType.systemTest.zone(tester.controller().system())); - setEndpoints(instanceId, JobType.systemTest.zone(tester.controller().system())); - setEndpoints(testerId.id(), JobType.systemTest.zone(tester.controller().system())); - runner.run(); - assertEquals(unfinished, jobs.run(id).get().steps().get(Step.endTests)); - return id; + return defaultContext.startSystemTestTests(); } /** Creates and submits a new application, and then starts the job of the given type. Use only once per test. */ public RunId newRun(JobType type) { - assertFalse(application().internal()); // Use this only once per test. - newSubmission(); - tester.readyJobTrigger().maintain(); - - if (type.isProduction()) { - runJob(new JobId(instanceId, JobType.systemTest)); - runJob(new JobId(instanceId, JobType.stagingTest)); - tester.readyJobTrigger().maintain(); - } - - Run run = jobs.active().stream() - .filter(r -> r.id().type() == type) - .findAny() - .orElseThrow(() -> new AssertionError(type + " is not among the active: " + jobs.active())); - return run.id(); - } - - static X509Certificate generateCertificate() { - KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - X500Principal subject = new X500Principal("CN=subject"); - return X509CertificateBuilder.fromKeypair(keyPair, - subject, - Instant.now(), - Instant.now().plusSeconds(1), - SignatureAlgorithm.SHA512_WITH_ECDSA, - BigInteger.valueOf(1)) - .build(); + return defaultContext.newRun(type); } public void assertRunning(JobType type) { @@ -578,11 +254,4 @@ public class InternalDeploymentTester { assertTrue(jobs.active().stream().anyMatch(run -> run.id().application().equals(id) && run.id().type() == type)); } - /** Flush all pending name services requests */ - public void flushDnsRequests() { - nameServiceDispatcher.run(); - assertTrue("All name service requests dispatched", - controller().curator().readNameServiceQueue().requests().isEmpty()); - } - } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java index 0188bb0e5ae..fc73af6acf7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java @@ -52,8 +52,8 @@ import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.in import static com.yahoo.vespa.hosted.controller.api.integration.LogEntry.Type.warning; import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.appId; import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.instanceId; -import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.applicationPackage; -import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.publicCdApplicationPackage; +import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.applicationPackage; +import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.publicCdApplicationPackage; import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.testerId; import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.failed; import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.succeeded; @@ -93,7 +93,7 @@ public class InternalStepRunnerTest { @Test public void canSwitchFromScrewdriverAndBackAgain() { // Deploys a default application package with default build number. - tester.tester().deployCompletely(tester.application(), InternalDeploymentTester.applicationPackage); + tester.tester().deployCompletely(tester.application(), DeploymentContext.applicationPackage); tester.setEndpoints(instanceId, JobType.productionUsCentral1.zone(system())); tester.setEndpoints(instanceId, JobType.productionUsWest1.zone(system())); tester.setEndpoints(instanceId, JobType.productionUsEast3.zone(system())); @@ -111,11 +111,11 @@ public class InternalStepRunnerTest { tester.jobs().unregister(appId); try { - tester.tester().deployCompletely(tester.application(), InternalDeploymentTester.applicationPackage, BuildJob.defaultBuildNumber + 1); + tester.tester().deployCompletely(tester.application(), DeploymentContext.applicationPackage, BuildJob.defaultBuildNumber + 1); throw new IllegalStateException("Component job should get even again with build numbers to produce a change."); } catch (AssertionError expected) { } - tester.tester().deployCompletely(tester.application(), InternalDeploymentTester.applicationPackage, BuildJob.defaultBuildNumber + 2); + tester.tester().deployCompletely(tester.application(), DeploymentContext.applicationPackage, BuildJob.defaultBuildNumber + 2); } @Test diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java index 416f2ee89ad..672017bec3e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java @@ -12,21 +12,21 @@ import com.yahoo.vespa.hosted.controller.api.integration.certificates.Applicatio import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationStore; import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud; import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService; import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; import com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService; import com.yahoo.vespa.hosted.controller.api.integration.organization.Billing; import com.yahoo.vespa.hosted.controller.api.integration.organization.ContactRetriever; -import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueHandler; import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues; +import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueHandler; import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockBilling; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueHandler; import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumer; +import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock; import com.yahoo.vespa.hosted.controller.api.integration.resource.MeteringClient; import com.yahoo.vespa.hosted.controller.api.integration.resource.MockTenantCost; import com.yahoo.vespa.hosted.controller.api.integration.resource.TenantCost; @@ -41,7 +41,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMeteringClient; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockRunDataStore; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud; -import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock; /** * A mock implementation of a {@link ServiceRegistry} for testing purposes. @@ -149,7 +148,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg } @Override - public TesterCloud testerCloud() { + public MockTesterCloud testerCloud() { return mockTesterCloud; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java index 68c95739ec6..18acd91969b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java @@ -1,7 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.maintenance; -import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.InstanceName; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.LockedTenant; @@ -11,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; import com.yahoo.vespa.hosted.controller.api.integration.organization.User; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; +import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext; import com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; import com.yahoo.vespa.hosted.controller.tenant.UserTenant; @@ -56,7 +56,7 @@ public class ApplicationOwnershipConfirmerTest { tester.createApplication(user.name().value(), "application", "default"); TenantAndApplicationId userAppId = TenantAndApplicationId.from("by-user", "application"); Supplier<Application> userApp = () -> tester.controller().applications().requireApplication(userAppId); - tester.deployNewSubmission(userAppId, tester.newSubmission(userAppId, InternalDeploymentTester.applicationPackage)); + tester.deployNewSubmission(userAppId, tester.newSubmission(userAppId, DeploymentContext.applicationPackage)); assertFalse("No issue is initially stored for a new application.", propertyApp.get().ownershipIssueId().isPresent()); assertFalse("No issue is initially stored for a new application.", userApp.get().ownershipIssueId().isPresent()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java index 24420d4590a..af6017c1188 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java @@ -7,11 +7,8 @@ import com.yahoo.config.provision.CloudName; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.zone.UpgradePolicy; import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.ControllerTester; -import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.SystemApplication; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; @@ -50,23 +47,22 @@ public class MetricsReporterTest { assertEquals(0.0, metrics.getMetric(MetricsReporter.DEPLOYMENT_FAIL_METRIC)); // Deploy all apps successfully - Application app1 = tester.createApplication("app1", "tenant1", "default"); - Application app2 = tester.createApplication("app2", "tenant1", "default"); - Application app3 = tester.createApplication("app3", "tenant1", "default"); - Application app4 = tester.createApplication("app4", "tenant1", "default"); - var version1 = tester.newSubmission(app1.id(), applicationPackage); - tester.deployNewSubmission(app1.id(), version1); - tester.deployNewSubmission(app2.id(), tester.newSubmission(app2.id(), applicationPackage)); - tester.deployNewSubmission(app3.id(), tester.newSubmission(app3.id(), applicationPackage)); - tester.deployNewSubmission(app4.id(), tester.newSubmission(app4.id(), applicationPackage)); + var context1 = tester.newDeploymentContext("app1", "tenant1", "default"); + var context2 = tester.newDeploymentContext("app2", "tenant1", "default"); + var context3 = tester.newDeploymentContext("app3", "tenant1", "default"); + var context4 = tester.newDeploymentContext("app4", "tenant1", "default"); + context1.submit(applicationPackage).deploy(); + context2.submit(applicationPackage).deploy(); + context3.submit(applicationPackage).deploy(); + context4.submit(applicationPackage).deploy(); metricsReporter.maintain(); assertEquals(0.0, metrics.getMetric(MetricsReporter.DEPLOYMENT_FAIL_METRIC)); // 1 app fails system-test - tester.newSubmission(app4.id(), applicationPackage); - tester.triggerJobs(); - tester.failDeployment(app4.id().defaultInstance(), systemTest); + context1.submit(applicationPackage) + .triggerJobs() + .failDeployment(systemTest); metricsReporter.maintain(); assertEquals(25.0, metrics.getMetric(MetricsReporter.DEPLOYMENT_FAIL_METRIC)); @@ -82,30 +78,31 @@ public class MetricsReporterTest { MetricsReporter reporter = createReporter(tester.controller()); - Application app = tester.createApplication("app1", "tenant1", "default"); - tester.deployNewSubmission(app.id(), tester.newSubmission(app.id(), applicationPackage)); + var context = tester.deploymentContext() + .submit(applicationPackage) + .deploy(); reporter.maintain(); - assertEquals(Duration.ZERO, getAverageDeploymentDuration(app.id().defaultInstance())); // An exceptionally fast deployment :-) + assertEquals(Duration.ZERO, getAverageDeploymentDuration(context.instanceId())); // An exceptionally fast deployment :-) // App spends 3 hours deploying - tester.newSubmission(app.id(), applicationPackage); + context.submit(applicationPackage); tester.clock().advance(Duration.ofHours(1)); - tester.runJob(app.id().defaultInstance(), systemTest); + context.runJob(systemTest); tester.clock().advance(Duration.ofMinutes(30)); - tester.runJob(app.id().defaultInstance(), stagingTest); + context.runJob(stagingTest); tester.triggerJobs(); tester.clock().advance(Duration.ofMinutes(90)); - tester.runJob(app.id().defaultInstance(), productionUsWest1); + context.runJob(productionUsWest1); reporter.maintain(); // Average time is 1 hour (system-test) + 90 minutes (staging-test runs in parallel with system-test) + 90 minutes (production) / 3 jobs - assertEquals(Duration.ofMinutes(80), getAverageDeploymentDuration(app.id().defaultInstance())); + assertEquals(Duration.ofMinutes(80), getAverageDeploymentDuration(context.instanceId())); // Another deployment starts and stalls for 12 hours - tester.newSubmission(app.id(), applicationPackage); - tester.triggerJobs(); + context.submit(applicationPackage) + .triggerJobs(); tester.clock().advance(Duration.ofHours(12)); reporter.maintain(); @@ -113,7 +110,7 @@ public class MetricsReporterTest { .plus(Duration.ofHours(12)) // hanging staging-test .plus(Duration.ofMinutes(90)) // previous production job .dividedBy(3), // Total number of orchestrated jobs - getAverageDeploymentDuration(app.id().defaultInstance())); + getAverageDeploymentDuration(context.instanceId())); } @Test @@ -125,47 +122,47 @@ public class MetricsReporterTest { .build(); MetricsReporter reporter = createReporter(tester.controller()); - Application app = tester.createApplication("app1", "tenant1", "default"); + var context = tester.deploymentContext(); // Initial deployment without failures - tester.deployNewSubmission(app.id(), tester.newSubmission(app.id(), applicationPackage)); + context.submit(applicationPackage).deploy(); reporter.maintain(); - assertEquals(0, getDeploymentsFailingUpgrade(app.id().defaultInstance())); + assertEquals(0, getDeploymentsFailingUpgrade(context.instanceId())); // Failing application change is not counted - var submission = tester.newSubmission(app.id(), applicationPackage); - tester.triggerJobs(); - tester.failDeployment(app.id().defaultInstance(), systemTest); + context.submit(applicationPackage) + .triggerJobs() + .failDeployment(systemTest); reporter.maintain(); - assertEquals(0, getDeploymentsFailingUpgrade(app.id().defaultInstance())); + assertEquals(0, getDeploymentsFailingUpgrade(context.instanceId())); // Application change completes - tester.deployNewSubmission(app.id(), submission); - assertFalse("Change deployed", tester.controller().applications().requireApplication(app.id()).change().hasTargets()); + context.deploy(); + assertFalse("Change deployed", context.application().change().hasTargets()); // New versions is released and upgrade fails in test environments Version version = Version.fromString("7.1"); tester.controllerTester().upgradeSystem(version); tester.upgrader().maintain(); - tester.triggerJobs(); - tester.failDeployment(app.id().defaultInstance(), systemTest); - tester.failDeployment(app.id().defaultInstance(), stagingTest); + context.triggerJobs() + .failDeployment(systemTest) + .failDeployment(stagingTest); reporter.maintain(); - assertEquals(2, getDeploymentsFailingUpgrade(app.id().defaultInstance())); + assertEquals(2, getDeploymentsFailingUpgrade(context.instanceId())); // Test and staging pass and upgrade fails in production - tester.runJob(app.id().defaultInstance(), systemTest); - tester.runJob(app.id().defaultInstance(), stagingTest); - tester.triggerJobs(); - tester.failDeployment(app.id().defaultInstance(), productionUsWest1); + context.runJob(systemTest) + .runJob(stagingTest) + .triggerJobs() + .failDeployment(productionUsWest1); reporter.maintain(); - assertEquals(1, getDeploymentsFailingUpgrade(app.id().defaultInstance())); + assertEquals(1, getDeploymentsFailingUpgrade(context.instanceId())); // Upgrade eventually succeeds - tester.runJob(app.id().defaultInstance(), productionUsWest1); - assertFalse("Upgrade deployed", tester.controller().applications().requireApplication(app.id()).change().hasTargets()); + context.runJob(productionUsWest1); + assertFalse("Upgrade deployed", context.application().change().hasTargets()); reporter.maintain(); - assertEquals(0, getDeploymentsFailingUpgrade(app.id().defaultInstance())); + assertEquals(0, getDeploymentsFailingUpgrade(context.instanceId())); } @Test @@ -177,25 +174,27 @@ public class MetricsReporterTest { .region("us-east-3") .build(); MetricsReporter reporter = createReporter(tester.controller()); - Application application = tester.createApplication("app1", "tenant1", "default"); - tester.configServer().generateWarnings(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-west-1")), 3); - tester.configServer().generateWarnings(new DeploymentId(application.id().defaultInstance(), ZoneId.from("prod", "us-east-3")), 4); - tester.deployNewSubmission(application.id(), tester.newSubmission(application.id(), applicationPackage)); + var context = tester.deploymentContext(); + tester.configServer().generateWarnings(context.deploymentIdIn(ZoneId.from("prod", "us-west-1")), 3); + tester.configServer().generateWarnings(context.deploymentIdIn(ZoneId.from("prod", "us-west-1")), 4); + context.submit(applicationPackage).deploy(); reporter.maintain(); - assertEquals(4, getDeploymentWarnings(application.id().defaultInstance())); + assertEquals(4, getDeploymentWarnings(context.instanceId())); } @Test public void build_time_reporting() { var tester = new InternalDeploymentTester(); - ApplicationVersion version = tester.newSubmission(); - tester.deployNewSubmission(version); - assertEquals(1000, version.buildTime().get().toEpochMilli()); + var applicationPackage = new ApplicationPackageBuilder().region("us-west-1").build(); + var context = tester.deploymentContext() + .submit(applicationPackage) + .deploy(); + assertEquals(1000, context.lastSubmission().get().buildTime().get().toEpochMilli()); MetricsReporter reporter = createReporter(tester.tester().controller()); reporter.maintain(); assertEquals(tester.clock().instant().getEpochSecond() - 1, - getMetric(MetricsReporter.DEPLOYMENT_BUILD_AGE_SECONDS, tester.instance().id())); + getMetric(MetricsReporter.DEPLOYMENT_BUILD_AGE_SECONDS, context.instanceId())); } @Test @@ -208,15 +207,16 @@ public class MetricsReporterTest { .region("us-east-3") .build(); MetricsReporter reporter = createReporter(tester.controller()); - Application application = tester.createApplication("app1", "tenant1", "default"); + var context = tester.deploymentContext() + .deferDnsUpdates(); reporter.maintain(); assertEquals("Queue is empty initially", 0, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue()); - tester.deployNewSubmission(application.id(), tester.newSubmission(application.id(), applicationPackage)); + context.submit(applicationPackage).deploy(); reporter.maintain(); assertEquals("Deployment queues name services requests", 6, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue()); - tester.flushDnsRequests(); + context.flushDnsUpdates(); reporter.maintain(); assertEquals("Queue consumed", 0, metrics.getMetric(MetricsReporter.NAME_SERVICE_REQUESTS_QUEUED).intValue()); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java index 92ee2a2ebdb..0a6d5d7698d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java @@ -5,14 +5,11 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.vespa.hosted.controller.Application; -import com.yahoo.vespa.hosted.controller.Instance; -import com.yahoo.vespa.hosted.controller.api.integration.BuildService; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.Change; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.SourceRevision; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; -import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester; import com.yahoo.vespa.hosted.controller.deployment.Run; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; @@ -20,7 +17,6 @@ import org.junit.Test; import java.time.Duration; import java.util.List; -import java.util.Optional; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -52,8 +48,7 @@ public class OutstandingChangeDeployerTest { assertFalse(tester.application(app1.id()).outstandingChange().hasTargets()); assertEquals(1, tester.application(app1.id()).latestVersion().get().buildNumber().getAsLong()); - tester.newSubmission(app1.id(), applicationPackage, new SourceRevision("repository1","master", "cafed00d"), - "author@domain", app1.projectId().getAsLong()); + tester.newSubmission(app1.id(), applicationPackage, new SourceRevision("repository1", "master", "cafed00d")); ApplicationId instanceId = app1.id().defaultInstance(); assertTrue(tester.application(app1.id()).outstandingChange().hasTargets()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java index 184f75bfd18..1105a478e14 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java @@ -34,7 +34,7 @@ import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobTy import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest; import static com.yahoo.vespa.hosted.controller.api.integration.deployment.TesterCloud.Status.FAILURE; import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.instanceId; -import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.applicationPackage; +import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.applicationPackage; import static com.yahoo.vespa.hosted.controller.deployment.InternalDeploymentTester.testerId; import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.deploymentFailed; import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.installationFailed; @@ -56,7 +56,7 @@ public class JobControllerApiHandlerHelperTest { // Revision 1 gets deployed everywhere. ApplicationVersion revision1 = tester.newSubmission(); tester.deployNewSubmission(revision1); - assertEquals(1, tester.application().projectId().getAsLong()); + assertEquals(1000, tester.application().projectId().getAsLong()); tester.clock().advance(Duration.ofMillis(1000)); // Revision 2 gets deployed everywhere except in us-east-3. |