
com.yahoo.vespa.hosted.controller.deployment.Run Maven / Gradle / Ivy
// Copyright Yahoo. 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.vespa.hosted.controller.api.integration.deployment.RunId;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.aborted;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.running;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.success;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.succeeded;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished;
import static java.util.Objects.requireNonNull;
/**
* Immutable class containing status information for a deployment job run by a {@link JobController}.
*
* @author jonmv
*/
public class Run {
private final RunId id;
private final Map steps;
private final Versions versions;
private final boolean isRedeployment;
private final Instant start;
private final Optional end;
private final RunStatus status;
private final long lastTestRecord;
private final Instant lastVespaLogTimestamp;
private final Optional noNodesDownSince;
private final Optional convergenceSummary;
private final Optional testerCertificate;
private final boolean dryRun;
// For deserialisation only -- do not use!
public Run(RunId id, Map steps, Versions versions, boolean isRedeployment, Instant start, Optional end,
RunStatus status, long lastTestRecord, Instant lastVespaLogTimestamp, Optional noNodesDownSince,
Optional convergenceSummary, Optional testerCertificate, boolean dryRun) {
this.id = id;
this.steps = Collections.unmodifiableMap(new EnumMap<>(steps));
this.versions = versions;
this.isRedeployment = isRedeployment;
this.start = start;
this.end = end;
this.status = status;
this.lastTestRecord = lastTestRecord;
this.lastVespaLogTimestamp = lastVespaLogTimestamp;
this.noNodesDownSince = noNodesDownSince;
this.convergenceSummary = convergenceSummary;
this.testerCertificate = testerCertificate;
this.dryRun = dryRun;
}
public static Run initial(RunId id, Versions versions, boolean isRedeployment, Instant now, JobProfile profile) {
EnumMap steps = new EnumMap<>(Step.class);
profile.steps().forEach(step -> steps.put(step, StepInfo.initial(step)));
return new Run(id, steps, requireNonNull(versions), isRedeployment, requireNonNull(now), Optional.empty(), running,
-1, Instant.EPOCH, Optional.empty(), Optional.empty(), Optional.empty(), profile == JobProfile.developmentDryRun);
}
/** Returns a new Run with the status of the given completed step set accordingly. */
public Run with(RunStatus status, LockedStep step) {
requireActive();
StepInfo stepInfo = getRequiredStepInfo(step.get());
if (stepInfo.status() != unfinished)
throw new IllegalStateException("Step '" + step.get() + "' can't be set to '" + status + "'" +
" -- it already completed with status '" + stepInfo.status() + "'!");
EnumMap steps = new EnumMap<>(this.steps);
steps.put(step.get(), stepInfo.with(Step.Status.of(status)));
return new Run(id, steps, versions, isRedeployment, start, end, this.status == running ? status : this.status,
lastTestRecord, lastVespaLogTimestamp, noNodesDownSince, convergenceSummary, testerCertificate, dryRun);
}
/** Returns a new Run with a new start time*/
public Run with(Instant startTime, LockedStep step) {
requireActive();
StepInfo stepInfo = getRequiredStepInfo(step.get());
if (stepInfo.status() != unfinished)
throw new IllegalStateException("Unable to set start timestamp of step " + step.get() +
": it has already completed with status " + stepInfo.status() + "!");
EnumMap steps = new EnumMap<>(this.steps);
steps.put(step.get(), stepInfo.with(startTime));
return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp,
noNodesDownSince, convergenceSummary, testerCertificate, dryRun);
}
public Run finished(Instant now) {
requireActive();
return new Run(id, steps, versions, isRedeployment, start, Optional.of(now), status == running ? success : status,
lastTestRecord, lastVespaLogTimestamp, noNodesDownSince, convergenceSummary, Optional.empty(), dryRun);
}
public Run aborted() {
requireActive();
return new Run(id, steps, versions, isRedeployment, start, end, aborted, lastTestRecord, lastVespaLogTimestamp,
noNodesDownSince, convergenceSummary, testerCertificate, dryRun);
}
public Run with(long lastTestRecord) {
requireActive();
return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp,
noNodesDownSince, convergenceSummary, testerCertificate, dryRun);
}
public Run with(Instant lastVespaLogTimestamp) {
requireActive();
return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp,
noNodesDownSince, convergenceSummary, testerCertificate, dryRun);
}
public Run noNodesDownSince(Instant noNodesDownSince) {
requireActive();
return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp,
Optional.ofNullable(noNodesDownSince), convergenceSummary, testerCertificate, dryRun);
}
public Run withSummary(ConvergenceSummary convergenceSummary) {
requireActive();
return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp,
noNodesDownSince, Optional.ofNullable(convergenceSummary), testerCertificate, dryRun);
}
public Run with(X509Certificate testerCertificate) {
requireActive();
return new Run(id, steps, versions, isRedeployment, start, end, status, lastTestRecord, lastVespaLogTimestamp,
noNodesDownSince, convergenceSummary, Optional.of(testerCertificate), dryRun);
}
/** Returns the id of this run. */
public RunId id() {
return id;
}
/** Whether this run contains this step. */
public boolean hasStep(Step step) {
return steps.containsKey(step);
}
/** Returns info on step, or empty if the given step is not a part of this run. */
public Optional stepInfo(Step step) {
return Optional.ofNullable(steps.get(step));
}
private StepInfo getRequiredStepInfo(Step step) {
return stepInfo(step).orElseThrow(() -> new IllegalArgumentException("There is no such step " + step + " for run " + id));
}
/** Returns status of step, or empty if the given step is not a part of this run. */
public Optional stepStatus(Step step) {
return stepInfo(step).map(StepInfo::status);
}
/** Returns an unmodifiable view of all step information in this run. */
public Map steps() {
return steps;
}
/** Returns an unmodifiable view of the status of all steps in this run. */
public Map stepStatuses() {
return Collections.unmodifiableMap(steps.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().status())));
}
public RunStatus status() {
return status;
}
/** Returns the instant at which this run began. */
public Instant start() {
return start;
}
/** Returns the instant at which this run ended, if it has. */
public Optional end() {
return end;
}
/** Returns whether the run has failed, and should switch to its run-always steps. */
public boolean hasFailed() {
return status != running && status != success;
}
/** Returns whether the run has ended, i.e., has become inactive, and can no longer be updated. */
public boolean hasEnded() {
return end.isPresent();
}
/** Returns the target, and possibly source, versions for this run. */
public Versions versions() {
return versions;
}
/** Returns the sequence id of the last test record received from the tester, for the test logs of this run. */
public long lastTestLogEntry() {
return lastTestRecord;
}
/** Returns the timestamp of the last Vespa log record fetched and stored for this run. */
public Instant lastVespaLogTimestamp() {
return lastVespaLogTimestamp;
}
/** Returns the timestamp of the last time no nodes were allowed to be down. */
public Optional noNodesDownSince() {
return noNodesDownSince;
}
/** Returns a summary of convergence status during an application deployment — staging or upgrade. */
public Optional convergenceSummary() {
return convergenceSummary;
}
/** Returns the tester certificate for this run, or empty. */
public Optional testerCertificate() {
return testerCertificate;
}
/** Whether this is a automatic redeployment. */
public boolean isRedeployment() {
return isRedeployment;
}
/** Whether this is a dry run deployment. */
public boolean isDryRun() { return dryRun; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if ( ! (o instanceof Run)) return false;
Run run = (Run) o;
return id.equals(run.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public String toString() {
return "RunStatus{" +
"id=" + id +
", versions=" + versions +
", start=" + start +
", end=" + end +
", status=" + status +
'}';
}
/** Returns the list of steps to run for this job right now, depending on whether the job has failed. */
public List readySteps() {
return hasFailed() ? forcedSteps() : normalSteps();
}
/** Returns the list of unfinished steps whose prerequisites have all succeeded. */
private List normalSteps() {
return steps.entrySet().stream()
.filter(entry -> entry.getValue().status() == unfinished
&& entry.getKey().prerequisites().stream()
.allMatch(step -> steps.get(step) == null
|| steps.get(step).status() == succeeded))
.map(Map.Entry::getKey)
.collect(Collectors.toUnmodifiableList());
}
/** Returns the list of not-yet-run run-always steps whose run-always prerequisites have all run. */
private List forcedSteps() {
return steps.entrySet().stream()
.filter(entry -> entry.getValue().status() == unfinished
&& entry.getKey().alwaysRun()
&& entry.getKey().prerequisites().stream()
.filter(Step::alwaysRun)
.allMatch(step -> steps.get(step) == null
|| steps.get(step).status() != unfinished))
.map(Map.Entry::getKey)
.collect(Collectors.toUnmodifiableList());
}
private void requireActive() {
if (hasEnded())
throw new IllegalStateException("This run ended at " + end.get() + " -- it can't be further modified!");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy