diff options
author | Martin Polden <mpolden@mpolden.no> | 2017-08-30 14:49:48 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2017-08-31 08:14:21 +0200 |
commit | 6a0806c27663aeb60ac7c496f0d9322bf86b03c2 (patch) | |
tree | d5493ce993a9e762e3952e5bb5eb3028f94f90f5 /controller-server | |
parent | 8ad5f7d60172ed7b981553bebb0354fc3aad57d7 (diff) |
Extract DeploymentOrder class
Diffstat (limited to 'controller-server')
2 files changed, 111 insertions, 70 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java new file mode 100644 index 00000000000..113fe4ce1f2 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentOrder.java @@ -0,0 +1,98 @@ +package com.yahoo.vespa.hosted.controller.deployment; + +import com.yahoo.config.application.api.DeploymentSpec; +import com.yahoo.vespa.hosted.controller.Application; +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.application.DeploymentJobs; +import com.yahoo.vespa.hosted.controller.application.JobStatus; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * This class determines order of deployments according to an application's deployment spec + * + * @author mpolden + */ +public class DeploymentOrder { + + private static final Logger log = Logger.getLogger(DeploymentOrder.class.getName()); + + private final Controller controller; + private final Clock clock; + + public DeploymentOrder(Controller controller) { + this.controller = controller; + this.clock = controller.clock(); + } + + /** Returns the next job(s) to trigger after the given job, or empty if none should be triggered */ + public List<DeploymentJobs.JobType> nextAfter(DeploymentJobs.JobType jobType, Application application) { + // Always trigger system test after component as deployment spec might not be available yet (e.g. if this is a + // new application with no previous deployments) + if (jobType == DeploymentJobs.JobType.component) { + return Collections.singletonList(DeploymentJobs.JobType.systemTest); + } + + // At this point we've at least deployed to system test, so deployment spec should be available + List<DeploymentSpec.DeclaredZone> zones = application.deploymentSpec().zones(); + Optional<DeploymentSpec.DeclaredZone> zoneForJob = zoneForJob(application, jobType); + if (!zoneForJob.isPresent()) { + return Collections.emptyList(); + } + int zoneIndex = application.deploymentSpec().zones().indexOf(zoneForJob.get()); + + // This is last zone + if (zoneIndex == zones.size() - 1) { + return Collections.emptyList(); + } + + // Skip next job if delay has not passed yet + Duration delay = delayAfter(application, zoneForJob.get()); + Optional<Instant> lastSuccess = Optional.ofNullable(application.deploymentJobs().jobStatus().get(jobType)) + .flatMap(JobStatus::lastSuccess) + .map(JobStatus.JobRun::at); + if (lastSuccess.isPresent() && lastSuccess.get().plus(delay).isAfter(clock.instant())) { + log.info(String.format("Delaying next job after %s of %s by %s", jobType, application, delay)); + return Collections.emptyList(); + } + + DeploymentSpec.DeclaredZone nextZone = application.deploymentSpec().zones().get(zoneIndex + 1); + return Collections.singletonList( + DeploymentJobs.JobType.from(controller.system(), nextZone.environment(), nextZone.region().orElse(null)) + ); + } + + private Duration delayAfter(Application application, DeploymentSpec.DeclaredZone zone) { + int stepIndex = application.deploymentSpec().steps().indexOf(zone); + if (stepIndex == -1 || stepIndex == application.deploymentSpec().steps().size() - 1) { + return Duration.ZERO; + } + Duration totalDelay = Duration.ZERO; + List<DeploymentSpec.Step> remainingSteps = application.deploymentSpec().steps() + .subList(stepIndex + 1, application.deploymentSpec().steps().size()); + for (DeploymentSpec.Step step : remainingSteps) { + if (!(step instanceof DeploymentSpec.Delay)) { + break; + } + totalDelay = totalDelay.plus(((DeploymentSpec.Delay) step).duration()); + } + return totalDelay; + } + + private Optional<DeploymentSpec.DeclaredZone> zoneForJob(Application application, DeploymentJobs.JobType jobType) { + return application.deploymentSpec() + .zones() + .stream() + .filter(zone -> zone.deploysTo( + jobType.environment(), + jobType.isProduction() ? jobType.region(controller.system()) : Optional.empty())) + .findFirst(); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java index ce1fee4dd14..1ccbd70d4d2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java @@ -39,6 +39,7 @@ public class DeploymentTrigger { private final Controller controller; private final Clock clock; private final BuildSystem buildSystem; + private final DeploymentOrder order; public DeploymentTrigger(Controller controller, CuratorDb curator, Clock clock) { Objects.requireNonNull(controller,"controller cannot be null"); @@ -46,6 +47,7 @@ public class DeploymentTrigger { this.controller = controller; this.clock = clock; this.buildSystem = new PolledBuildSystem(controller, curator); + this.order = new DeploymentOrder(controller); } //--- Start of methods which triggers deployment jobs ------------------------- @@ -76,11 +78,11 @@ public class DeploymentTrigger { // Trigger next if (report.success()) - application = trigger(nextAfter(report.jobType(), application), application, report.jobType() + " completed successfully", lock); + application = trigger(order.nextAfter(report.jobType(), application), application, report.jobType() + " completed successfully", lock); else if (isCapacityConstrained(report.jobType()) && shouldRetryOnOutOfCapacity(application, report.jobType())) application = trigger(report.jobType(), application, true, "Retrying due to out of capacity", lock); else if (shouldRetryNow(application)) - application = trigger(report.jobType(), application, "Retrying as job just started failing", lock); + application = trigger(report.jobType(), application, false, "Retrying as job just started failing", lock); applications().store(application, lock); } @@ -95,7 +97,7 @@ public class DeploymentTrigger { if (shouldRetryFromBeginning(application)) { // failed for a long time: Discard existing change and restart from the component job application = application.withDeploying(Optional.empty()); - application = trigger(JobType.component, application, "Retrying failing deployment from beginning: " + cause, lock); + application = trigger(JobType.component, application, false, "Retrying failing deployment from beginning: " + cause, lock); applications().store(application, lock); } else { // retry the failed job (with backoff) @@ -103,7 +105,7 @@ public class DeploymentTrigger { JobStatus jobStatus = application.deploymentJobs().jobStatus().get(jobType); if (isFailing(jobStatus)) { if (shouldRetryNow(jobStatus)) { - application = trigger(jobType, application, "Retrying failing job: " + cause, lock); + application = trigger(jobType, application, false, "Retrying failing job: " + cause, lock); applications().store(application, lock); } break; @@ -133,7 +135,7 @@ public class DeploymentTrigger { // Trigger next try (Lock lock = applications().lock(application.id())) { application = applications().require(application.id()); - application = trigger(nextAfter(lastSuccessfulJob.get().type(), application), application, + application = trigger(order.nextAfter(lastSuccessfulJob.get().type(), application), application, "Resuming delayed deployment", lock); applications().store(application, lock); } @@ -154,7 +156,7 @@ public class DeploymentTrigger { application = application.withDeploying(Optional.of(change)); if (change instanceof Change.ApplicationChange) application = application.withOutstandingChange(false); - application = trigger(JobType.systemTest, application, "Deploying change", lock); + application = trigger(JobType.systemTest, application, false, "Deploying change", lock); applications().store(application, lock); } } @@ -176,68 +178,6 @@ public class DeploymentTrigger { //--- End of methods which triggers deployment jobs ---------------------------- private ApplicationController applications() { return controller.applications(); } - - /** Returns the next job to trigger after this job, or null if none should be triggered */ - private JobType nextAfter(JobType jobType, Application application) { - // Always trigger system test after component as deployment spec might not be available yet (e.g. if this is a - // new application with no previous deployments) - if (jobType == JobType.component) { - return JobType.systemTest; - } - - // At this point we've at least deployed to system test, so deployment spec should be available - List<DeploymentSpec.DeclaredZone> zones = application.deploymentSpec().zones(); - Optional<DeploymentSpec.DeclaredZone> zoneForJob = zoneForJob(application, jobType); - if (!zoneForJob.isPresent()) { - return null; - } - int zoneIndex = application.deploymentSpec().zones().indexOf(zoneForJob.get()); - - // This is last zone - if (zoneIndex == zones.size() - 1) { - return null; - } - - // Skip next job if delay has not passed yet - Duration delay = delayAfter(application, zoneForJob.get()); - Optional<Instant> lastSuccess = Optional.ofNullable(application.deploymentJobs().jobStatus().get(jobType)) - .flatMap(JobStatus::lastSuccess) - .map(JobStatus.JobRun::at); - if (lastSuccess.isPresent() && lastSuccess.get().plus(delay).isAfter(clock.instant())) { - log.info(String.format("Delaying next job after %s of %s by %s", jobType, application, delay)); - return null; - } - - DeploymentSpec.DeclaredZone nextZone = application.deploymentSpec().zones().get(zoneIndex + 1); - return JobType.from(controller.system(), nextZone.environment(), nextZone.region().orElse(null)); - } - - private Duration delayAfter(Application application, DeploymentSpec.DeclaredZone zone) { - int stepIndex = application.deploymentSpec().steps().indexOf(zone); - if (stepIndex == -1 || stepIndex == application.deploymentSpec().steps().size() - 1) { - return Duration.ZERO; - } - Duration totalDelay = Duration.ZERO; - List<DeploymentSpec.Step> remainingSteps = application.deploymentSpec().steps() - .subList(stepIndex + 1, application.deploymentSpec().steps().size()); - for (DeploymentSpec.Step step : remainingSteps) { - if (!(step instanceof DeploymentSpec.Delay)) { - break; - } - totalDelay = totalDelay.plus(((DeploymentSpec.Delay) step).duration()); - } - return totalDelay; - } - - private Optional<DeploymentSpec.DeclaredZone> zoneForJob(Application application, JobType jobType) { - return application.deploymentSpec() - .zones() - .stream() - .filter(zone -> zone.deploysTo( - jobType.environment(), - jobType.isProduction() ? jobType.region(controller.system()) : Optional.empty())) - .findFirst(); - } private boolean isFirstJob(JobType jobType) { return jobType == JobType.component; @@ -356,8 +296,11 @@ public class DeploymentTrigger { return application.withJobTriggering(jobType, clock.instant(), controller); } - private Application trigger(JobType jobType, Application application, String cause, Lock lock) { - return trigger(jobType, application, false, cause, lock); + private Application trigger(List<JobType> jobs, Application application, String cause, Lock lock) { + for (JobType job : jobs) { + application = trigger(job, application, false, cause, lock); + } + return application; } public BuildSystem buildSystem() { return buildSystem; } |