aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Marius Venstad <jonmv@users.noreply.github.com>2023-04-18 12:07:00 +0200
committerGitHub <noreply@github.com>2023-04-18 12:07:00 +0200
commit1b04e62f32af429653203c855f272e894e4d6007 (patch)
treeed9eeb8996ef26663517feacd62a4d335ce2710f
parentf871ed1ecec477f016168293cddb59edf4b2b1a7 (diff)
parentd77d9866204c2272086eb729b9a160c3150d80cc (diff)
Merge pull request #26766 from vespa-engine/jonmv/revision-rollbacks
Jonmv/revision rollbacks
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java34
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java76
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java13
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java56
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java62
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java34
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java42
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json25
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java4
21 files changed, 324 insertions, 145 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
index 04604ae7007..eb2005bf268 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
@@ -1,11 +1,9 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.deployment;
-import ai.vespa.validation.Validation;
import com.yahoo.component.Version;
import java.time.Instant;
-import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
@@ -33,6 +31,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
private final Optional<String> sourceUrl;
private final Optional<String> commit;
private final Optional<String> bundleHash;
+ private final Optional<Instant> obsoleteAt;
private final boolean hasPackage;
private final boolean shouldSkip;
private final Optional<String> description;
@@ -41,7 +40,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
public ApplicationVersion(RevisionId id, Optional<SourceRevision> source, Optional<String> authorEmail,
Optional<Version> compileVersion, Optional<Integer> allowedMajor, Optional<Instant> buildTime,
Optional<String> sourceUrl, Optional<String> commit, Optional<String> bundleHash,
- boolean hasPackage, boolean shouldSkip, Optional<String> description, int risk) {
+ Optional<Instant> obsoleteAt, boolean hasPackage, boolean shouldSkip, Optional<String> description, int risk) {
if (commit.isPresent() && commit.get().length() > 128)
throw new IllegalArgumentException("Commit may not be longer than 128 characters");
@@ -61,6 +60,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
this.sourceUrl = requireNonNull(sourceUrl, "sourceUrl cannot be null");
this.commit = requireNonNull(commit, "commit cannot be null");
this.bundleHash = bundleHash;
+ this.obsoleteAt = obsoleteAt;
this.hasPackage = hasPackage;
this.shouldSkip = shouldSkip;
this.description = description;
@@ -71,19 +71,9 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
return id;
}
- /** Create an application package version from a completed build, without an author email */
- public static ApplicationVersion from(RevisionId id, SourceRevision source) {
- return new ApplicationVersion(id, Optional.of(source), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
- }
-
- /** Creates a version from a completed build, an author email, and build metadata. */
- public static ApplicationVersion from(RevisionId id, SourceRevision source, String authorEmail, Version compileVersion, Instant buildTime) {
- return new ApplicationVersion(id, Optional.of(source), Optional.of(authorEmail), Optional.of(compileVersion), Optional.empty(), Optional.of(buildTime), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
- }
-
/** Creates a minimal version for a development build. */
public static ApplicationVersion forDevelopment(RevisionId id, Optional<Version> compileVersion, Optional<Integer> allowedMajor) {
- return new ApplicationVersion(id, Optional.empty(), Optional.empty(), compileVersion, allowedMajor, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
+ return new ApplicationVersion(id, Optional.empty(), Optional.empty(), compileVersion, allowedMajor, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
}
/** Creates a version from a completed build, an author email, and build metadata. */
@@ -91,7 +81,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
Optional<Version> compileVersion, Optional<Integer> allowedMajor, Optional<Instant> buildTime, Optional<String> sourceUrl,
Optional<String> commit, Optional<String> bundleHash, Optional<String> description, int risk) {
return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime,
- sourceUrl, commit, bundleHash, true, false, description, risk);
+ sourceUrl, commit, bundleHash, Optional.empty(), true, false, description, risk);
}
/** Returns a unique identifier for this version or "unknown" if version is not known */
@@ -150,7 +140,17 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
/** Returns a copy of this without a package stored. */
public ApplicationVersion withoutPackage() {
- return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, false, shouldSkip, description, risk);
+ return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, obsoleteAt, false, shouldSkip, description, risk);
+ }
+
+ /** Returns a copy of this which is obsolete now. */
+ public ApplicationVersion obsoleteAt(Instant now) {
+ return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, Optional.of(now), hasPackage, shouldSkip, description, risk);
+ }
+
+ /** Returns the instant at which this became obsolete, i.e., no longer relevant for automated deployments. */
+ public Optional<Instant> obsoleteAt() {
+ return obsoleteAt;
}
/** Whether we still have the package for this revision. */
@@ -160,7 +160,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
/** Returns a copy of this which will not be rolled out to production. */
public ApplicationVersion skipped() {
- return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, hasPackage, true, description, risk);
+ return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, obsoleteAt, hasPackage, true, description, risk);
}
/** Whether we still have the package for this revision. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
index 64cad599168..5ebb3d53529 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
@@ -2,7 +2,6 @@
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.component.Version;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import java.util.Objects;
@@ -23,7 +22,7 @@ import static java.util.Objects.requireNonNull;
*/
public final class Change {
- private static final Change empty = new Change(Optional.empty(), Optional.empty(), false);
+ private static final Change empty = new Change(Optional.empty(), Optional.empty(), false, false);
/** The platform version we are upgrading to, or empty if none */
private final Optional<Version> platform;
@@ -32,23 +31,27 @@ public final class Change {
private final Optional<RevisionId> revision;
/** Whether this change is a pin to its contained Vespa version, or to the application's current. */
- private final boolean pinned;
+ private final boolean platformPinned;
- private Change(Optional<Version> platform, Optional<RevisionId> revision, boolean pinned) {
+ /** Whether this change is a pin to its contained application revision, or to the application's current. */
+ private final boolean revisionPinned;
+
+ private Change(Optional<Version> platform, Optional<RevisionId> revision, boolean platformPinned, boolean revisionPinned) {
this.platform = requireNonNull(platform, "platform cannot be null");
this.revision = requireNonNull(revision, "revision cannot be null");
if (revision.isPresent() && ( ! revision.get().isProduction())) {
throw new IllegalArgumentException("Application version to deploy must be a known version");
}
- this.pinned = pinned;
+ this.platformPinned = platformPinned;
+ this.revisionPinned = revisionPinned;
}
public Change withoutPlatform() {
- return new Change(Optional.empty(), revision, pinned);
+ return new Change(Optional.empty(), revision, platformPinned, revisionPinned);
}
public Change withoutApplication() {
- return new Change(platform, Optional.empty(), pinned);
+ return new Change(platform, Optional.empty(), platformPinned, revisionPinned);
}
/** Returns whether a change should currently be deployed */
@@ -58,7 +61,7 @@ public final class Change {
/** Returns whether this is the empty change. */
public boolean isEmpty() {
- return ! hasTargets() && ! pinned;
+ return ! hasTargets() && ! platformPinned && ! revisionPinned;
}
/** Returns the platform version carried by this. */
@@ -67,42 +70,55 @@ public final class Change {
/** Returns the application version carried by this. */
public Optional<RevisionId> revision() { return revision; }
- public boolean isPinned() { return pinned; }
+ public boolean isPlatformPinned() { return platformPinned; }
+
+ public boolean isRevisionPinned() { return revisionPinned; }
/** Returns an instance representing no change */
public static Change empty() { return empty; }
/** Returns a version of this change which replaces or adds this platform change */
public Change with(Version platformVersion) {
- if (pinned)
+ if (platformPinned)
throw new IllegalArgumentException("Not allowed to set a platform version when pinned.");
- return new Change(Optional.of(platformVersion), revision, pinned);
+ return new Change(Optional.of(platformVersion), revision, platformPinned, revisionPinned);
}
/** Returns a version of this change which replaces or adds this revision change */
public Change with(RevisionId revision) {
- return new Change(platform, Optional.of(revision), pinned);
+ if (revisionPinned)
+ throw new IllegalArgumentException("Not allowed to set a revision when pinned.");
+
+ return new Change(platform, Optional.of(revision), platformPinned, revisionPinned);
+ }
+
+ /** Returns a change with the versions of this, and with the platform version pinned. */
+ public Change withPlatformPin() {
+ return new Change(platform, revision, true, revisionPinned);
+ }
+
+ /** Returns a change with the versions of this, and with the platform version unpinned. */
+ public Change withoutPlatformPin() {
+ return new Change(platform, revision, false, revisionPinned);
}
/** Returns a change with the versions of this, and with the platform version pinned. */
- public Change withPin() {
- return new Change(platform, revision, true);
+ public Change withRevisionPin() {
+ return new Change(platform, revision, platformPinned, true);
}
/** Returns a change with the versions of this, and with the platform version unpinned. */
- public Change withoutPin() {
- return new Change(platform, revision, false);
+ public Change withoutRevisionPin() {
+ return new Change(platform, revision, platformPinned, false);
}
/** Returns the change obtained when overwriting elements of the given change with any present in this */
public Change onTopOf(Change other) {
- if (platform.isPresent())
- other = other.with(platform.get());
- if (revision.isPresent())
- other = other.with(revision.get());
- if (pinned)
- other = other.withPin();
+ if (platform.isPresent()) other = other.with(platform.get());
+ if (revision.isPresent()) other = other.with(revision.get());
+ if (platformPinned) other = other.withPlatformPin();
+ if (revisionPinned) other = other.withRevisionPin();
return other;
}
@@ -111,34 +127,38 @@ public final class Change {
if (this == o) return true;
if (!(o instanceof Change)) return false;
Change change = (Change) o;
- return pinned == change.pinned &&
+ return platformPinned == change.platformPinned &&
+ revisionPinned == change.revisionPinned &&
Objects.equals(platform, change.platform) &&
Objects.equals(revision, change.revision);
}
@Override
public int hashCode() {
- return Objects.hash(platform, revision, pinned);
+ return Objects.hash(platform, revision, platformPinned, revisionPinned);
}
@Override
public String toString() {
StringJoiner changes = new StringJoiner(" and ");
- if (pinned)
+ if (platformPinned)
changes.add("pin to " + platform.map(Version::toString).orElse("current platform"));
else
platform.ifPresent(version -> changes.add("upgrade to " + version));
- revision.ifPresent(revision -> changes.add("revision change to " + revision));
+ if (revisionPinned)
+ changes.add("pin to " + revision.map(RevisionId::toString).orElse("current revision"));
+ else
+ revision.ifPresent(revision -> changes.add("revision change to " + revision));
changes.setEmptyValue("no change");
return changes.toString();
}
public static Change of(RevisionId revision) {
- return new Change(Optional.empty(), Optional.of(revision), false);
+ return new Change(Optional.empty(), Optional.of(revision), false, false);
}
public static Change of(Version platformChange) {
- return new Change(Optional.of(platformChange), Optional.empty(), false);
+ return new Change(Optional.of(platformChange), Optional.empty(), false, false);
}
/** Returns whether this change carries a revision downgrade relative to the given revision. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
index c1bf083b26c..b94779994e4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
@@ -125,7 +125,7 @@ public class InstanceList extends AbstractFilteringList<ApplicationId, InstanceL
/** Returns the subset of instances which are not pinned to a certain Vespa version. */
public InstanceList unpinned() {
- return matching(id -> ! instance(id).change().isPinned());
+ return matching(id -> ! instance(id).change().isPlatformPinned());
}
/** Returns the subset of instances which are currently failing a job. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
index 00da34fe2e4..0f1bbfeb25e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
@@ -229,7 +229,7 @@ public class DeploymentStatus {
.anyMatch(deployment -> ! compatibleWithCompileVersion.test(deployment.version()))) {
for (Version platform : targetsForPolicy(versionStatus, systemVersion, application.deploymentSpec().requireInstance(instance).upgradePolicy()))
if (compatibleWithCompileVersion.test(platform))
- return change.withoutPin().with(platform);
+ return change.withoutPlatformPin().with(platform);
}
return change;
}
@@ -265,7 +265,7 @@ public class DeploymentStatus {
for (InstanceName instance : application.deploymentSpec().instanceNames()) {
Change outstanding = outstandingChange(instance);
if (outstanding.hasTargets())
- outstandingChanges.put(instance, outstanding.onTopOf(application.require(instance).change()));
+ outstandingChanges.put(instance, outstanding.onTopOf(application.require(instance).change().withoutRevisionPin()));
}
var testJobs = jobsToRun(outstandingChanges, true).entrySet().stream()
.filter(entry -> ! entry.getKey().type().isProduction());
@@ -596,7 +596,8 @@ public class DeploymentStatus {
/** Changes to deploy with the given job, possibly split in two steps. */
private List<Change> changes(JobId job, StepStatus step, Change change) {
- if (change.platform().isEmpty() || change.revision().isEmpty() || change.isPinned())
+ if ( change.platform().isEmpty() || change.revision().isEmpty()
+ || change.isPlatformPinned() || change.isRevisionPinned())
return List.of(change);
if ( step.completedAt(change.withoutApplication(), Optional.of(job)).isPresent()
@@ -1090,14 +1091,14 @@ public class DeploymentStatus {
/** Complete if deployment is on pinned version, and last successful deployment, or if given versions is strictly a downgrade, and this isn't forced by a pin. */
@Override
Optional<Instant> completedAt(Change change, Optional<JobId> dependent) {
- if ( change.isPinned()
+ if ( change.isPlatformPinned()
&& change.platform().isPresent()
&& ! existingDeployment.map(Deployment::version).equals(change.platform()))
return Optional.empty();
if ( change.revision().isPresent()
- && ! existingDeployment.map(Deployment::revision).equals(change.revision())
- && dependent.equals(job())) // Job should (re-)run in this case, but other dependents need not wait.
+ && change.isRevisionPinned()
+ && ! existingDeployment.map(Deployment::revision).equals(change.revision()))
return Optional.empty();
Change fullChange = status.application().require(job.id().application().instance()).change();
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 00a0e22f87d..4e699f2c28f 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
@@ -41,7 +41,6 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import static java.util.Comparator.comparing;
-import static java.util.Comparator.comparingDouble;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;
@@ -331,15 +330,14 @@ public class DeploymentTrigger {
/** Cancels the indicated part of the given application's change. */
public void cancelChange(ApplicationId instanceId, ChangesToCancel cancellation) {
applications().lockApplicationOrThrow(TenantAndApplicationId.from(instanceId), application -> {
- Change change;
- switch (cancellation) {
- case ALL: change = Change.empty(); break;
- case VERSIONS: change = Change.empty().withPin(); break;
- case PLATFORM: change = application.get().require(instanceId.instance()).change().withoutPlatform(); break;
- case APPLICATION: change = application.get().require(instanceId.instance()).change().withoutApplication(); break;
- case PIN: change = application.get().require(instanceId.instance()).change().withoutPin(); break;
- default: throw new IllegalArgumentException("Unknown cancellation choice '" + cancellation + "'!");
- }
+ Change change = switch (cancellation) {
+ case ALL -> Change.empty();
+ case PLATFORM -> application.get().require(instanceId.instance()).change().withoutPlatform();
+ case APPLICATION -> application.get().require(instanceId.instance()).change().withoutApplication();
+ case PIN -> application.get().require(instanceId.instance()).change().withoutPlatformPin();
+ case PLATFORM_PIN -> application.get().require(instanceId.instance()).change().withoutPlatformPin();
+ case APPLICATION_PIN -> application.get().require(instanceId.instance()).change().withoutRevisionPin();
+ };
applications().store(application.with(instanceId.instance(),
instance -> withRemainingChange(instance,
change,
@@ -348,7 +346,7 @@ public class DeploymentTrigger {
});
}
- public enum ChangesToCancel { ALL, PLATFORM, APPLICATION, VERSIONS, PIN }
+ public enum ChangesToCancel { ALL, PLATFORM, APPLICATION, PIN, PLATFORM_PIN, APPLICATION_PIN }
// ---------- Conveniences ----------
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index 7b1a1e879d6..52ddcfd5171 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -437,7 +437,7 @@ public class InternalStepRunner implements StepRunner {
Version targetPlatform = controller.jobController().run(id).versions().targetPlatform();
Version systemVersion = controller.readSystemVersion();
boolean incompatible = controller.applications().versionCompatibility(id.application()).refuse(targetPlatform, systemVersion);
- return incompatible || application(id.application()).change().isPinned() ? targetPlatform : systemVersion;
+ return incompatible || application(id.application()).change().isPlatformPinned() ? targetPlatform : systemVersion;
}
private Optional<RunStatus> installTester(RunId id, DualLogger logger) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 10e4052f067..318a6ffe820 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -111,6 +111,7 @@ import static java.util.logging.Level.WARNING;
public class JobController {
public static final Duration maxHistoryAge = Duration.ofDays(60);
+ public static final Duration obsoletePackageExpiry = Duration.ofDays(7);
private static final Logger log = Logger.getLogger(JobController.class.getName());
@@ -165,8 +166,8 @@ public class JobController {
return Optional.empty();
return active(id).isPresent()
- ? Optional.of(logs.readActive(id.application(), id.type(), after))
- : logs.readFinished(id, after);
+ ? Optional.of(logs.readActive(id.application(), id.type(), after))
+ : logs.readFinished(id, after);
}
}
@@ -284,10 +285,10 @@ public class JobController {
private Optional<InputStream> getVespaLogsFromLogserver(Run run, long fromMillis, boolean tester) {
return deploymentCompletedAt(run, tester).map(at ->
- controller.serviceRegistry().configServer().getLogs(new DeploymentId(tester ? run.id().tester().id() : run.id().application(),
- run.id().type().zone()),
- Map.of("from", Long.toString(Math.max(fromMillis, at.toEpochMilli())),
- "to", Long.toString(run.end().orElse(controller.clock().instant()).toEpochMilli()))));
+ controller.serviceRegistry().configServer().getLogs(new DeploymentId(tester ? run.id().tester().id() : run.id().application(),
+ run.id().type().zone()),
+ Map.of("from", Long.toString(Math.max(fromMillis, at.toEpochMilli())),
+ "to", Long.toString(run.end().orElse(controller.clock().instant()).toEpochMilli()))));
}
/** Fetches any new test log entries, and records the id of the last of these, for continuation. */
@@ -509,14 +510,14 @@ public class JobController {
long successes = runs.values().stream().filter(Run::hasSucceeded).count();
var oldEntries = runs.entrySet().iterator();
for (var old = oldEntries.next();
- old.getKey().number() <= last - historyLength
+ old.getKey().number() <= last - historyLength
|| old.getValue().start().isBefore(controller.clock().instant().minus(maxHistoryAge));
old = oldEntries.next()) {
// Make sure we keep the last success and the first failing
if ( successes == 1
- && old.getValue().hasSucceeded()
- && ! old.getValue().start().isBefore(controller.clock().instant().minus(maxHistoryAge))) {
+ && old.getValue().hasSucceeded()
+ && ! old.getValue().start().isBefore(controller.clock().instant().minus(maxHistoryAge))) {
oldEntries.next();
continue;
}
@@ -624,7 +625,7 @@ public class JobController {
});
}
- private LockedApplication withPrunedPackages(LockedApplication application, RevisionId latest){
+ private LockedApplication withPrunedPackages(LockedApplication application, RevisionId latest) {
TenantAndApplicationId id = application.get().id();
Application wrapped = application.get();
RevisionId oldestDeployed = application.get().oldestDeployedRevision()
@@ -632,11 +633,28 @@ public class JobController {
.flatMap(instance -> instance.change().revision().stream())
.min(naturalOrder()))
.orElse(latest);
- controller.applications().applicationStore().prune(id.tenant(), id.application(), oldestDeployed);
+ RevisionId oldestToKeep = null;
+ Instant now = controller.clock().instant();
+ for (ApplicationVersion version : application.get().revisions().withPackage()) {
+ if (version.id().compareTo(oldestDeployed) < 0) {
+ if (version.obsoleteAt().isEmpty()) {
+ application = application.withRevisions(revisions -> revisions.with(version.obsoleteAt(now)));
+ if (oldestToKeep == null)
+ oldestToKeep = version.id();
+ }
+ else {
+ if (oldestToKeep == null && !version.obsoleteAt().get().isBefore(now.minus(obsoletePackageExpiry)))
+ oldestToKeep = version.id();
+ }
+ }
+ }
- for (ApplicationVersion version : application.get().revisions().withPackage())
- if (version.id().compareTo(oldestDeployed) < 0)
- application = application.withRevisions(revisions -> revisions.with(version.withoutPackage()));
+ if (oldestToKeep != null) {
+ controller.applications().applicationStore().prune(id.tenant(), id.application(), oldestToKeep);
+ for (ApplicationVersion version : application.get().revisions().withPackage())
+ if (version.id().compareTo(oldestToKeep) < 0)
+ application = application.withRevisions(revisions -> revisions.with(version.withoutPackage()));
+ }
return application;
}
@@ -703,8 +721,8 @@ public class JobController {
VersionStatus versionStatus = controller.readVersionStatus();
if ( ! controller.system().isCd()
- && platform.isPresent()
- && versionStatus.deployableVersions().stream().map(VespaVersion::versionNumber).noneMatch(platform.get()::equals))
+ && platform.isPresent()
+ && versionStatus.deployableVersions().stream().map(VespaVersion::versionNumber).noneMatch(platform.get()::equals))
throw new IllegalArgumentException("platform version " + platform.get() + " is not present in this system");
controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
@@ -731,8 +749,8 @@ public class JobController {
controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
Version targetPlatform = platform.orElseGet(() -> findTargetPlatform(applicationPackage, deploymentId, application.get().get(id.instance()), versionStatus));
if ( ! allowOutdatedPlatform
- && ! controller.readVersionStatus().isOnCurrentMajor(targetPlatform)
- && runs(id, type).values().stream().noneMatch(run -> run.versions().targetPlatform().getMajor() == targetPlatform.getMajor()))
+ && ! controller.readVersionStatus().isOnCurrentMajor(targetPlatform)
+ && runs(id, type).values().stream().noneMatch(run -> run.versions().targetPlatform().getMajor() == targetPlatform.getMajor()))
throw new IllegalArgumentException("platform version " + targetPlatform + " is not on a current major version in this system");
controller.applications().applicationStore().putDev(deploymentId, version.id(), applicationPackage.zippedContent(), diff);
@@ -872,7 +890,7 @@ public class JobController {
/** Locks all runs and modifies the list of historic runs for the given application and job type. */
private void locked(ApplicationId id, JobType type, Consumer<SortedMap<RunId, Run>> modifications) {
- try (Mutex __ = curator.lock(id, type)) {
+ try (Mutex __ = curator.lock(id, type)) {
SortedMap<RunId, Run> runs = new TreeMap<>(curator.readHistoricRuns(id, type));
modifications.accept(runs);
curator.writeHistoricRuns(id, type, runs.values());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
index bbab9487ea2..272417ba0ac 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
@@ -93,7 +93,7 @@ public class RevisionHistory {
// Fallback for when an application version isn't known for the given key.
private static ApplicationVersion revisionOf(RevisionId id) {
- return new ApplicationVersion(id, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), false, false, Optional.empty(), 0);
+ return new ApplicationVersion(id, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), false, false, Optional.empty(), 0);
}
/** Returns the production {@link ApplicationVersion} with this revision ID. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
index e7371561636..f752e396c09 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
@@ -126,7 +126,7 @@ public class Versions {
private static Version targetPlatform(Application application, Change change, Optional<Version> existing,
Supplier<Version> defaultVersion) {
- if (change.isPinned() && change.platform().isPresent())
+ if (change.isPlatformPinned() && change.platform().isPresent())
return change.platform().get();
return max(change.platform(), existing)
@@ -135,6 +135,9 @@ public class Versions {
private static RevisionId targetRevision(Application application, Change change,
Optional<RevisionId> existing) {
+ if (change.isRevisionPinned() && change.revision().isPresent())
+ return change.revision().get();
+
return change.revision()
.or(() -> existing)
.orElseGet(() -> defaultRevision(application));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index ee12c9957b1..e5006ab9785 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -82,7 +82,8 @@ public class ApplicationSerializer {
private static final String versionsField = "versions";
private static final String prodVersionsField = "prodVersions";
private static final String devVersionsField = "devVersions";
- private static final String pinnedField = "pinned";
+ private static final String platformPinnedField = "pinned";
+ private static final String revisionPinnedField = "revisionPinned";
private static final String deploymentIssueField = "deploymentIssueId";
private static final String ownershipIssueIdField = "ownershipIssueId";
private static final String ownerField = "confirmedOwner";
@@ -118,6 +119,7 @@ public class ApplicationSerializer {
private static final String riskField = "risk";
private static final String authorEmailField = "authorEmailField";
private static final String deployedDirectlyField = "deployedDirectly";
+ private static final String obsoleteAtField = "obsoleteAt";
private static final String hasPackageField = "hasPackage";
private static final String shouldSkipField = "shouldSkip";
private static final String compileVersionField = "compileVersion";
@@ -265,6 +267,7 @@ public class ApplicationSerializer {
applicationVersion.sourceUrl().ifPresent(url -> object.setString(sourceUrlField, url));
applicationVersion.commit().ifPresent(commit -> object.setString(commitField, commit));
object.setBool(deployedDirectlyField, applicationVersion.isDeployedDirectly());
+ applicationVersion.obsoleteAt().ifPresent(at -> object.setLong(obsoleteAtField, at.toEpochMilli()));
object.setBool(hasPackageField, applicationVersion.hasPackage());
object.setBool(shouldSkipField, applicationVersion.shouldSkip());
applicationVersion.description().ifPresent(description -> object.setString(descriptionField, description));
@@ -295,8 +298,10 @@ public class ApplicationSerializer {
object.setString(versionField, deploying.platform().get().toString());
if (deploying.revision().isPresent())
toSlime(deploying.revision().get(), object);
- if (deploying.isPinned())
- object.setBool(pinnedField, true);
+ if (deploying.isPlatformPinned())
+ object.setBool(platformPinnedField, true);
+ if (deploying.isRevisionPinned())
+ object.setBool(revisionPinnedField, true);
}
private void toSlime(RotationStatus status, Cursor array) {
@@ -487,6 +492,7 @@ public class ApplicationSerializer {
Optional<Instant> buildTime = SlimeUtils.optionalInstant(object.field(buildTimeField));
Optional<String> sourceUrl = SlimeUtils.optionalString(object.field(sourceUrlField));
Optional<String> commit = SlimeUtils.optionalString(object.field(commitField));
+ Optional<Instant> obsoleteAt = SlimeUtils.optionalInstant(object.field(obsoleteAtField));
boolean hasPackage = object.field(hasPackageField).asBool();
boolean shouldSkip = object.field(shouldSkipField).asBool();
Optional<String> description = SlimeUtils.optionalString(object.field(descriptionField));
@@ -494,7 +500,7 @@ public class ApplicationSerializer {
Optional<String> bundleHash = SlimeUtils.optionalString(object.field(bundleHashField));
return new ApplicationVersion(id, sourceRevision, authorEmail, compileVersion, allowedMajor, buildTime,
- sourceUrl, commit, bundleHash, hasPackage, shouldSkip, description, risk);
+ sourceUrl, commit, bundleHash, obsoleteAt, hasPackage, shouldSkip, description, risk);
}
private Optional<SourceRevision> sourceRevisionFromSlime(Inspector object) {
@@ -520,8 +526,10 @@ public class ApplicationSerializer {
change = Change.of(Version.fromString(versionFieldValue.asString()));
if (object.field(applicationBuildNumberField).valid())
change = change.with(revisionFromSlime(object, null));
- if (object.field(pinnedField).asBool())
- change = change.withPin();
+ if (object.field(platformPinnedField).asBool())
+ change = change.withPlatformPin();
+ if (object.field(revisionPinnedField).asBool())
+ change = change.withRevisionPin();
return change;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 81988753621..ded27ee1060 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -260,11 +260,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/package")) return applicationPackage(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/diff/{number}")) return applicationPackageDiff(path.get("tenant"), path.get("application"), path.get("number"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return deploying(path.get("tenant"), path.get("application"), "default", request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), "default", request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance")) return applications(path.get("tenant"), Optional.of(path.get("application")), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return instance(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job")) return JobControllerApiHandlerHelper.jobTypeResponse(controller, appIdFromPath(path), request.getUri());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.runResponse(controller.applications().requireApplication(TenantAndApplicationId.from(path.get("tenant"), path.get("application"))), controller.jobController().runs(appIdFromPath(path), jobTypeFromPath(path)).descendingMap(), Optional.ofNullable(request.getProperty("limit")), request.getUri()); // (((\(✘෴✘)/)))
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/package")) return devApplicationPackage(appIdFromPath(path), jobTypeFromPath(path));
@@ -327,14 +325,18 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return createApplication(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/platform")) return deployPlatform(path.get("tenant"), path.get("application"), "default", false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), "default", true, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/platform-pin")) return deployPlatform(path.get("tenant"), path.get("application"), "default", true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application-pin")) return deployApplication(path.get("tenant"), path.get("application"), "default", true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), "default", false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/key")) return addDeployKey(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/submit")) return submit(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return createInstance(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploy/{jobtype}")) return jobDeploy(appIdFromPath(path), jobTypeFromPath(path), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/platform")) return deployPlatform(path.get("tenant"), path.get("application"), path.get("instance"), false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), path.get("instance"), true, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), path.get("instance"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/platform-pin")) return deployPlatform(path.get("tenant"), path.get("application"), path.get("instance"), true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/application-pin")) return deployApplication(path.get("tenant"), path.get("application"), path.get("instance"), true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), path.get("instance"), false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/submit")) return submit(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return trigger(appIdFromPath(path), jobTypeFromPath(path), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/pause")) return pause(appIdFromPath(path), jobTypeFromPath(path));
@@ -2059,7 +2061,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if ( ! instance.change().isEmpty()) {
instance.change().platform().ifPresent(version -> root.setString("platform", version.toString()));
instance.change().revision().ifPresent(revision -> root.setString("application", revision.toString()));
- root.setBool("pinned", instance.change().isPinned());
+ root.setBool("pinned", instance.change().isPlatformPinned());
+ root.setBool("platform-pinned", instance.change().isPlatformPinned());
+ root.setBool("application-pinned", instance.change().isRevisionPinned());
}
return new SlimeJsonResponse(slime);
}
@@ -2172,7 +2176,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
.collect(joining(", ")));
Change change = Change.of(version);
if (pin)
- change = change.withPin();
+ change = change.withPlatformPin();
controller.applications().deploymentTrigger().forceChange(id, change, isOperator(request));
response.append("Triggered ").append(change).append(" for ").append(id);
@@ -2181,7 +2185,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
/** Trigger deployment to the last known application package for the given application. */
- private HttpResponse deployApplication(String tenantName, String applicationName, String instanceName, HttpRequest request) {
+ private HttpResponse deployApplication(String tenantName, String applicationName, String instanceName, boolean pin, HttpRequest request) {
ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
Inspector buildField = toSlime(request.getData()).get().field("build");
long build = buildField.valid() ? buildField.asLong() : -1;
@@ -2191,6 +2195,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
RevisionId revision = build == -1 ? application.get().revisions().last().get().id()
: getRevision(application.get(), build);
Change change = Change.of(revision);
+ if (pin)
+ change = change.withRevisionPin();
controller.applications().deploymentTrigger().forceChange(id, change, isOperator(request));
response.append("Triggered ").append(change).append(" for ").append(id);
});
@@ -2231,7 +2237,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return;
}
- ChangesToCancel cancel = ChangesToCancel.valueOf(choice.toUpperCase());
+ ChangesToCancel cancel = ChangesToCancel.valueOf(choice.replaceAll("-", "_").toUpperCase());
controller.applications().deploymentTrigger().cancelChange(id, cancel);
response.append("Changed deployment from '").append(change).append("' to '").append(controller.applications().requireInstance(id).change()).append("' for ").append(id);
});
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index 804ae7b7805..9ff8c7df18b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -312,7 +312,9 @@ class JobControllerApiHandlerHelper {
if ( ! change.isEmpty()) {
change.platform().ifPresent(version -> deployingObject.setString("platform", version.toFullString()));
change.revision().ifPresent(revision -> toSlime(deployingObject.setObject("application"), application.revisions().get(revision)));
- if (change.isPinned()) deployingObject.setBool("pinned", true);
+ if (change.isPlatformPinned()) deployingObject.setBool("pinned", true);
+ if (change.isPlatformPinned()) deployingObject.setBool("platformPinned", true);
+ if (change.isRevisionPinned()) deployingObject.setBool("revisionPinned", true);
}
Cursor latestVersionsObject = stepObject.setObject("latestVersions");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
index 069ee58e9c5..6e5635e8c8c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
@@ -171,7 +171,9 @@ public class DeploymentApiHandler extends ThreadedHttpRequestHandler {
instanceObject.setString("application", instance.application().value());
instanceObject.setString("instance", instance.instance().value());
instanceObject.setBool("upgrading", status.application().require(instance.instance()).change().platform().equals(Optional.of(statistics.version())));
- instanceObject.setBool("pinned", status.application().require(instance.instance()).change().isPinned());
+ instanceObject.setBool("pinned", status.application().require(instance.instance()).change().isPlatformPinned());
+ instanceObject.setBool("platformPinned", status.application().require(instance.instance()).change().isPlatformPinned());
+ instanceObject.setBool("revisionPinned", status.application().require(instance.instance()).change().isRevisionPinned());
DeploymentStatus.StepStatus stepStatus = status.instanceSteps().get(instance.instance());
if (stepStatus != null) { // Instance may not have any steps, i.e. an empty deployment spec has been submitted
Readiness platformReadiness = stepStatus.blockedUntil(Change.of(statistics.version()));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index a9a6fe602b6..04c8c46e1ef 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -41,6 +41,7 @@ import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.deployment.Submission;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.notification.Notification;
@@ -106,9 +107,11 @@ public class ControllerTest {
Version version1 = tester.configServer().initialVersion();
var context = tester.newDeploymentContext();
context.submit(applicationPackage);
- assertEquals(ApplicationVersion.from(RevisionId.forProduction(1), DeploymentContext.defaultSourceRevision, "a@b", new Version("6.1"), Instant.ofEpochSecond(1)),
- context.application().revisions().get(context.instance().change().revision().get()),
- "Application version is known from completion of initial job");
+ RevisionId id = RevisionId.forProduction(1);
+ Version compileVersion = new Version("6.1");
+ assertEquals(new ApplicationVersion(id, Optional.of(DeploymentContext.defaultSourceRevision), Optional.of("a@b"), Optional.of(compileVersion), Optional.empty(), Optional.of(Instant.ofEpochSecond(1)), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0),
+ context.application().revisions().get(context.instance().change().revision().get()),
+ "Application version is known from completion of initial job");
context.runJob(systemTest);
context.runJob(stagingTest);
@@ -220,6 +223,59 @@ public class ControllerTest {
}
@Test
+ void testPackagePruning() {
+ DeploymentContext app = tester.newDeploymentContext().submit().deploy();
+ RevisionId revision1 = app.lastSubmission().get();
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+
+ app.submit().deploy();
+ RevisionId revision2 = app.lastSubmission().get();
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision2.number()));
+
+ // Revision 1 is marked as obsolete now
+ app.submit().deploy();
+ RevisionId revision3 = app.lastSubmission().get();
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision2.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision3.number()));
+
+ // Time advances, and revision 2 is marked as obsolete now
+ tester.clock().advance(JobController.obsoletePackageExpiry);
+ app.submit().deploy();
+ RevisionId revision4 = app.lastSubmission().get();
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision2.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision3.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision4.number()));
+
+ // Time advances, and revision is now old enough to be pruned
+ tester.clock().advance(Duration.ofMillis(1));
+ app.submit().deploy();
+ RevisionId revision5 = app.lastSubmission().get();
+ assertFalse(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision2.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision3.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision4.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision5.number()));
+ }
+
+ @Test
void testGlobalRotationStatus() {
var context = tester.newDeploymentContext();
var zone1 = ZoneId.from("prod", "us-west-1");
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 afb92d84f3b..6e5c2458c92 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
@@ -653,15 +653,21 @@ public class DeploymentTriggerTest {
assertEquals(appVersion1, latestDeployed(app.instance()));
// Downgrading application version.
- tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(appVersion0));
- assertEquals(Change.of(appVersion0), app.instance().change());
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(appVersion0).withRevisionPin());
+ assertEquals(Change.of(appVersion0).withRevisionPin(), app.instance().change());
app.runJob(stagingTest)
- .runJob(productionUsCentral1)
- .runJob(productionUsEast3)
- .runJob(productionUsWest1);
- assertEquals(Change.empty(), app.instance().change());
+ .runJob(productionUsCentral1)
+ .runJob(productionUsEast3)
+ .runJob(productionUsWest1);
+ assertEquals(Change.empty().withRevisionPin(), app.instance().change());
assertEquals(appVersion0, app.instance().deployments().get(productionUsEast3.zone()).revision());
assertEquals(appVersion0, latestDeployed(app.instance()));
+
+ tester.outstandingChangeDeployer().run();
+ assertEquals(Change.empty().withRevisionPin(), app.instance().change());
+ tester.deploymentTrigger().cancelChange(app.instanceId(), ALL);
+ tester.outstandingChangeDeployer().run();
+ assertEquals(Change.of(appVersion1), app.instance().change());
}
@Test
@@ -1239,13 +1245,13 @@ public class DeploymentTriggerTest {
assertEquals(Change.empty(), app.instance().change());
// Application is pinned to previous version, and downgrades to that. Tests are re-run.
- tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(version0).withPin());
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(version0).withPlatformPin());
app.runJob(stagingTest).runJob(productionUsEast3);
tester.clock().advance(Duration.ofMinutes(1));
app.failDeployment(testUsEast3);
tester.clock().advance(Duration.ofMinutes(11)); // Job is cooling down after consecutive failures.
app.runJob(testUsEast3);
- assertEquals(Change.empty().withPin(), app.instance().change());
+ assertEquals(Change.empty().withPlatformPin(), app.instance().change());
// A new upgrade is attempted, and production tests wait for redeployment.
tester.controllerTester().upgradeSystem(version2);
@@ -2234,7 +2240,7 @@ public class DeploymentTriggerTest {
.majorVersion(7)
.compileVersion(version1)
.build());
- tester.deploymentTrigger().forceChange(app.instanceId(), app.instance().change().withPin());
+ tester.deploymentTrigger().forceChange(app.instanceId(), app.instance().change().withPlatformPin());
app.deploy();
assertEquals(version1, tester.jobs().last(app.instanceId(), productionUsEast3).get().versions().targetPlatform());
assertEquals(version1, app.application().revisions().get(tester.jobs().last(app.instanceId(), productionUsEast3).get().versions().targetRevision()).compileVersion().get());
@@ -2251,7 +2257,7 @@ public class DeploymentTriggerTest {
// The new app enters a platform block window, and is pinned to the old platform;
// the new submission overrides both those settings, as the new revision should roll out regardless.
tester.atMondayMorning();
- tester.deploymentTrigger().forceChange(newApp.instanceId(), Change.empty().withPin());
+ tester.deploymentTrigger().forceChange(newApp.instanceId(), Change.empty().withPlatformPin());
newApp.submit(new ApplicationPackageBuilder().compileVersion(version2)
.systemTest()
.blockChange(false, true, "mon", "0-23", "UTC")
@@ -2280,11 +2286,11 @@ public class DeploymentTriggerTest {
tester.upgrader().run();
assertEquals(Change.of(newRevision).with(version1), newApp.instance().change());
- tester.deploymentTrigger().forceChange(newApp.instanceId(), newApp.instance().change().withPin());
+ tester.deploymentTrigger().forceChange(newApp.instanceId(), newApp.instance().change().withPlatformPin());
tester.outstandingChangeDeployer().run();
- assertEquals(Change.of(newRevision).with(version1).withPin(), newApp.instance().change());
+ assertEquals(Change.of(newRevision).with(version1).withPlatformPin(), newApp.instance().change());
tester.upgrader().run();
- assertEquals(Change.of(newRevision).with(version1).withPin(), newApp.instance().change());
+ assertEquals(Change.of(newRevision).with(version1).withPlatformPin(), newApp.instance().change());
newApp.deploy();
assertEquals(version1, tester.jobs().last(newApp.instanceId(), productionUsEast3).get().versions().targetPlatform());
@@ -2381,7 +2387,7 @@ public class DeploymentTriggerTest {
.build()))
.getMessage());
- tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(oldVersion).with(app.application().revisions().last().get().id()).withPin());
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(oldVersion).with(app.application().revisions().last().get().id()).withPlatformPin());
app.deploy();
assertEquals(oldVersion, app.deployment(ZoneId.from("prod", "us-east-3")).version());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
index 11110d6edaa..96c1d7c545d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
@@ -5,7 +5,6 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.test.ManualClock;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.application.Change;
@@ -27,7 +26,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.OptionalInt;
import java.util.Set;
import java.util.stream.Collectors;
@@ -856,10 +854,10 @@ public class UpgraderTest {
// Create an application with pinned platform version.
var context = tester.newDeploymentContext().submit().deploy();
- tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPin());
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPlatformPin());
assertFalse(context.instance().change().hasTargets());
- assertTrue(context.instance().change().isPinned());
+ assertTrue(context.instance().change().isPlatformPinned());
assertEquals(3, context.instance().deployments().size());
// Application does not upgrade.
@@ -867,21 +865,21 @@ public class UpgraderTest {
tester.controllerTester().upgradeSystem(version1);
tester.upgrader().maintain();
assertFalse(context.instance().change().hasTargets());
- assertTrue(context.instance().change().isPinned());
+ assertTrue(context.instance().change().isPlatformPinned());
// New application package is deployed.
context.submit().deploy();
assertFalse(context.instance().change().hasTargets());
- assertTrue(context.instance().change().isPinned());
+ assertTrue(context.instance().change().isPlatformPinned());
// Application upgrades to new version when pin is removed.
tester.deploymentTrigger().cancelChange(context.instanceId(), PIN);
tester.upgrader().maintain();
assertTrue(context.instance().change().hasTargets());
- assertFalse(context.instance().change().isPinned());
+ assertFalse(context.instance().change().isPlatformPinned());
// Application is pinned to new version, and upgrade is therefore not cancelled, even though confidence is broken.
- tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPin());
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPlatformPin());
tester.upgrader().maintain();
tester.triggerJobs();
assertEquals(version1, context.instance().change().platform().get());
@@ -890,7 +888,7 @@ public class UpgraderTest {
context.runJob(systemTest).runJob(stagingTest).runJob(productionUsCentral1)
.timeOutUpgrade(productionUsWest1);
tester.deploymentTrigger().cancelChange(context.instanceId(), ALL);
- tester.deploymentTrigger().forceChange(context.instanceId(), Change.of(version0).withPin());
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.of(version0).withPlatformPin());
assertEquals(version0, context.instance().change().platform().get());
// Application downgrades to pinned version.
@@ -913,7 +911,7 @@ public class UpgraderTest {
// Keep app 1 on current version
tester.controller().applications().lockApplicationIfPresent(app1.application().id(), app ->
tester.controller().applications().store(app.with(app1.instance().name(),
- instance -> instance.withChange(instance.change().withPin()))));
+ instance -> instance.withChange(instance.change().withPlatformPin()))));
// New version is released
Version version1 = Version.fromString("6.2");
@@ -935,7 +933,7 @@ public class UpgraderTest {
// App 1 is unpinned and upgrades to latest 6
tester.controller().applications().lockApplicationIfPresent(app1.application().id(), app ->
tester.controller().applications().store(app.with(app1.instance().name(),
- instance -> instance.withChange(instance.change().withoutPin()))));
+ instance -> instance.withChange(instance.change().withoutPlatformPin()))));
tester.upgrader().maintain();
assertEquals(version1,
app1.instance().change().platform().orElseThrow(),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 589fc25700f..b71d3cf838b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -101,16 +101,17 @@ public class ApplicationSerializerTest {
Optional.empty(),
Optional.of("best commit"),
Optional.of("hash1"),
+ Optional.of(Instant.ofEpochMilli(777)),
true,
false,
Optional.of("~(˘▾˘)~"),
3);
assertEquals("https://github/org/repo/tree/commit1", applicationVersion1.sourceUrl().get());
- ApplicationVersion applicationVersion2 = ApplicationVersion.from(RevisionId.forDevelopment(31, new JobId(id1, DeploymentContext.productionUsEast3)),
- new SourceRevision("repo1", "branch1", "commit1"), "a@b",
- Version.fromString("6.3.1"),
- Instant.ofEpochMilli(496));
+ RevisionId id = RevisionId.forDevelopment(31, new JobId(id1, DeploymentContext.productionUsEast3));
+ SourceRevision source = new SourceRevision("repo1", "branch1", "commit1");
+ Version compileVersion = Version.fromString("6.3.1");
+ ApplicationVersion applicationVersion2 = new ApplicationVersion(id, Optional.of(source), Optional.of("a@b"), Optional.of(compileVersion), Optional.empty(), Optional.of(Instant.ofEpochMilli(496)), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
Instant activityAt = Instant.parse("2018-06-01T10:15:30.00Z");
deployments.add(new Deployment(zone1, CloudAccount.empty, applicationVersion1.id(), Version.fromString("1.2.3"), Instant.ofEpochMilli(3),
DeploymentMetrics.none, DeploymentActivity.none, QuotaUsage.none, OptionalDouble.empty()));
@@ -143,7 +144,7 @@ public class ApplicationSerializerTest {
Map.of(),
List.of(),
RotationStatus.EMPTY,
- Change.of(Version.fromString("6.7")).withPin()));
+ Change.of(Version.fromString("6.7")).withPlatformPin().withRevisionPin()));
Application original = new Application(TenantAndApplicationId.from(id1),
Instant.now().truncatedTo(ChronoUnit.MILLIS),
@@ -174,6 +175,7 @@ public class ApplicationSerializerTest {
assertEquals(original.revisions().last().get().sourceUrl(), serialized.revisions().last().get().sourceUrl());
assertEquals(original.revisions().last().get().commit(), serialized.revisions().last().get().commit());
assertEquals(original.revisions().last().get().bundleHash(), serialized.revisions().last().get().bundleHash());
+ assertEquals(original.revisions().last().get().obsoleteAt(), serialized.revisions().last().get().obsoleteAt());
assertEquals(original.revisions().last().get().hasPackage(), serialized.revisions().last().get().hasPackage());
assertEquals(original.revisions().last().get().shouldSkip(), serialized.revisions().last().get().shouldSkip());
assertEquals(original.revisions().last().get().description(), serialized.revisions().last().get().description());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 9a34989aeff..76bcbe078ff 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -541,24 +541,22 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\"message\":\"No deployment in progress for tenant1.application1.instance1 at this time\"}");
// POST pinning to a given version to an application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform-pin", POST)
.userIdentity(USER_ID)
.data("6.1.0"),
"{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
assertTrue(tester.controller().auditLogger().readLog().entries().stream()
- .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin?")),
+ .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform-pin?")),
"Action is logged to audit log");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
- .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", GET)
- .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
+ .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true,\"platform-pinned\":true,\"application-pinned\":false}");
// DELETE only the pin to a given version
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform-pin", DELETE)
.userIdentity(USER_ID),
"{\"message\":\"Changed deployment from 'pin to 6.1' to 'upgrade to 6.1' for tenant1.application1.instance1\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
- .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":false}");
+ .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":false,\"platform-pinned\":false,\"application-pinned\":false}");
// POST pinning again
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", POST)
@@ -566,14 +564,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
.data("6.1"),
"{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
- .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
+ .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true,\"platform-pinned\":true,\"application-pinned\":false}");
// DELETE only the version, but leave the pin
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform", DELETE)
.userIdentity(USER_ID),
"{\"message\":\"Changed deployment from 'pin to 6.1' to 'pin to current platform' for tenant1.application1.instance1\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
- .userIdentity(USER_ID), "{\"pinned\":true}");
+ .userIdentity(USER_ID), "{\"pinned\":true,\"platform-pinned\":true,\"application-pinned\":false}");
// DELETE also the pin to a given version
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", DELETE)
@@ -582,6 +580,32 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{}");
+ // POST pinning to a given revision to an application
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/application-pin", POST)
+ .userIdentity(USER_ID)
+ .data(""),
+ "{\"message\":\"Triggered pin to build 1 for tenant1.application1.instance1\"}");
+ assertTrue(tester.controller().auditLogger().readLog().entries().stream()
+ .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/application-pin?")),
+ "Action is logged to audit log");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
+ .userIdentity(USER_ID), "{\"application\":\"build 1\",\"pinned\":false,\"platform-pinned\":false,\"application-pinned\":true}");
+
+ // DELETE only the pin to a given revision
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/application-pin", DELETE)
+ .userIdentity(USER_ID),
+ "{\"message\":\"Changed deployment from 'pin to build 1' to 'revision change to build 1' for tenant1.application1.instance1\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
+ .userIdentity(USER_ID), "{\"application\":\"build 1\",\"pinned\":false,\"platform-pinned\":false,\"application-pinned\":false}");
+
+ // DELETE deploying to a given revision
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/application", DELETE)
+ .userIdentity(USER_ID),
+ "{\"message\":\"Changed deployment from 'revision change to build 1' to 'no change' for tenant1.application1.instance1\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
+ .userIdentity(USER_ID), "{}");
+
+
// POST a pause to a production job
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-west-1/pause", POST)
.userIdentity(USER_ID),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json
index ec6ccf3ecf2..0b7c64c72a5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json
@@ -48,6 +48,14 @@
"sourceUrl": "repository1/tree/commit1",
"commit": "commit1"
}
+ },
+ {
+ "application": {
+ "build": 1,
+ "compileVersion": "6.1.0",
+ "sourceUrl": "repository1/tree/commit1",
+ "commit": "commit1"
+ }
}
],
"blockers": [ ]
@@ -594,6 +602,14 @@
"sourceUrl": "repository1/tree/commit1",
"commit": "commit1"
}
+ },
+ {
+ "application": {
+ "build": 1,
+ "compileVersion": "6.1.0",
+ "sourceUrl": "repository1/tree/commit1",
+ "commit": "commit1"
+ }
}
],
"blockers": [ ]
@@ -709,6 +725,15 @@
"description": "my best commit yet",
"risk": 9001,
"deployable": false
+ },
+ {
+ "build": 1,
+ "compileVersion": "6.1.0",
+ "sourceUrl": "repository1/tree/commit1",
+ "commit": "commit1",
+ "description": "my best commit yet",
+ "risk": 9001,
+ "deployable": true
}
]
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
index a1f386d51a7..ac43fbf2a80 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
@@ -37,6 +37,8 @@
"instance": "default",
"upgrading": false,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
@@ -78,6 +80,8 @@
"instance": "i2",
"upgrading": false,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
@@ -179,6 +183,8 @@
"instance": "default",
"upgrading": true,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
@@ -249,6 +255,8 @@
"instance": "i1",
"upgrading": false,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
@@ -309,6 +317,8 @@
"instance": "i2",
"upgrading": true,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
index 890d190c24e..b3198a72d1b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
@@ -193,8 +193,8 @@ public class LoadBalancerProvisioner {
Optional<LoadBalancer> loadBalancer = db.readLoadBalancer(id);
LoadBalancer newLoadBalancer;
LoadBalancer.State fromState = loadBalancer.map(LoadBalancer::state).orElse(null);
- boolean recreateLoadBalancer = loadBalancer.isPresent() && (!inAccount(cloudAccount, loadBalancer.get())
- || !hasCorrectVisibility(loadBalancer.get(), zoneEndpoint));
+ boolean recreateLoadBalancer = loadBalancer.isPresent() && ( ! inAccount(cloudAccount, loadBalancer.get())
+ || ! hasCorrectVisibility(loadBalancer.get(), zoneEndpoint));
if (recreateLoadBalancer) {
// We have a load balancer, but with the wrong account or visibility.
// Load balancer must be removed before we can provision a new one with the wanted visibility