summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorJon Marius Venstad <venstad@gmail.com>2020-01-07 12:03:51 +0100
committerJon Marius Venstad <venstad@gmail.com>2020-01-07 15:28:23 +0100
commit52df4364530a1dac305ff7da1c785a0e8988b707 (patch)
treea6dd75fb4eae764a25fa19aa721b5ff6bf099b57 /controller-server
parentb5aaff70bd4ebdd5b659550b055a41e517378c5b (diff)
Move changes to instances
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java13
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java42
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java154
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java155
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java75
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java112
-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.java14
-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/maintenance/ControllerMaintenance.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java82
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java94
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java16
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java17
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java44
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java74
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java16
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java82
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java2
31 files changed, 624 insertions, 462 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index b962b260bf9..e37d6accd89 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -1,6 +1,7 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
@@ -45,7 +46,6 @@ public class Application {
private final ValidationOverrides validationOverrides;
private final Optional<ApplicationVersion> latestVersion;
private final OptionalLong projectId;
- private final Change change;
private final Optional<IssueId> deploymentIssueId;
private final Optional<IssueId> ownershipIssueId;
private final Optional<User> owner;
@@ -56,21 +56,20 @@ public class Application {
/** Creates an empty application. */
public Application(TenantAndApplicationId id, Instant now) {
- this(id, now, DeploymentSpec.empty, ValidationOverrides.empty, Change.empty(),
+ this(id, now, DeploymentSpec.empty, ValidationOverrides.empty,
Optional.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(),
new ApplicationMetrics(0, 0), Set.of(), OptionalLong.empty(), Optional.empty(), List.of());
}
// DO NOT USE! For serialization purposes, only.
public Application(TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec, ValidationOverrides validationOverrides,
- Change change, Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
+ Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Set<PublicKey> deployKeys, OptionalLong projectId,
Optional<ApplicationVersion> latestVersion, Collection<Instance> instances) {
this.id = Objects.requireNonNull(id, "id cannot be null");
this.createdAt = Objects.requireNonNull(createdAt, "instant of creation cannot be null");
this.deploymentSpec = Objects.requireNonNull(deploymentSpec, "deploymentSpec cannot be null");
this.validationOverrides = Objects.requireNonNull(validationOverrides, "validationOverrides cannot be null");
- this.change = Objects.requireNonNull(change, "change cannot be null");
this.deploymentIssueId = Objects.requireNonNull(deploymentIssueId, "deploymentIssueId cannot be null");
this.ownershipIssueId = Objects.requireNonNull(ownershipIssueId, "ownershipIssueId cannot be null");
this.owner = Objects.requireNonNull(owner, "owner cannot be null");
@@ -116,12 +115,6 @@ public class Application {
return get(instance).orElseThrow(() -> new IllegalArgumentException("Unknown instance '" + instance + "' in '" + id + "'"));
}
- /**
- * Returns base change for this application, i.e., the change that is deployed outside block windows.
- * This is empty when no change is currently under deployment.
- */
- public Change change() { return change; }
-
/** Returns ID of any open deployment issue filed for this */
public Optional<IssueId> deploymentIssueId() {
return deploymentIssueId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index ecea7b09782..8339e1b8fdd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -393,7 +393,7 @@ public class ApplicationController {
applicationPackage = getApplicationPackage(instanceId, applicationVersion);
applicationPackage = withTesterCertificate(applicationPackage, instanceId, jobType);
- validateRun(application.get(), instance, zone, platformVersion, applicationVersion);
+ validateRun(application.get().require(instance), zone, platformVersion, applicationVersion);
}
if (controller.zoneRegistry().zones().directlyRouted().ids().contains(zone)) {
@@ -887,14 +887,14 @@ public class ApplicationController {
}
/** Verify that we don't downgrade an existing production deployment. */
- private void validateRun(Application application, InstanceName instance, ZoneId zone, Version platformVersion, ApplicationVersion applicationVersion) {
- Deployment deployment = application.require(instance).deployments().get(zone);
+ private void validateRun(Instance instance, ZoneId zone, Version platformVersion, ApplicationVersion applicationVersion) {
+ Deployment deployment = instance.deployments().get(zone);
if ( zone.environment().isProduction() && deployment != null
- && ( platformVersion.compareTo(deployment.version()) < 0 && ! application.change().isPinned()
+ && ( platformVersion.compareTo(deployment.version()) < 0 && ! instance.change().isPinned()
|| applicationVersion.compareTo(deployment.applicationVersion()) < 0))
throw new IllegalArgumentException(String.format("Rejecting deployment of application %s to %s, as the requested versions (platform: %s, application: %s)" +
" are older than the currently deployed (platform: %s, application: %s).",
- application.id().instance(instance), zone, platformVersion, applicationVersion, deployment.version(), deployment.applicationVersion()));
+ instance.id(), zone, platformVersion, applicationVersion, deployment.version(), deployment.applicationVersion()));
}
/** Returns the rotation repository, used for managing global rotation assignments */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index 09666110f70..3c5bc3b98ad 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -36,7 +36,6 @@ public class LockedApplication {
private final Instant createdAt;
private final DeploymentSpec deploymentSpec;
private final ValidationOverrides validationOverrides;
- private final Change change;
private final Optional<IssueId> deploymentIssueId;
private final Optional<IssueId> ownershipIssueId;
private final Optional<User> owner;
@@ -55,14 +54,14 @@ public class LockedApplication {
*/
LockedApplication(Application application, Lock lock) {
this(Objects.requireNonNull(lock, "lock cannot be null"), application.id(), application.createdAt(),
- application.deploymentSpec(), application.validationOverrides(), application.change(),
+ application.deploymentSpec(), application.validationOverrides(),
application.deploymentIssueId(), application.ownershipIssueId(),
application.owner(), application.majorVersion(), application.metrics(), application.deployKeys(),
application.projectId(), application.latestVersion(), application.instances());
}
private LockedApplication(Lock lock, TenantAndApplicationId id, Instant createdAt, DeploymentSpec deploymentSpec,
- ValidationOverrides validationOverrides, Change change,
+ ValidationOverrides validationOverrides,
Optional<IssueId> deploymentIssueId, Optional<IssueId> ownershipIssueId, Optional<User> owner,
OptionalInt majorVersion, ApplicationMetrics metrics, Set<PublicKey> deployKeys,
OptionalLong projectId, Optional<ApplicationVersion> latestVersion,
@@ -72,7 +71,6 @@ public class LockedApplication {
this.createdAt = createdAt;
this.deploymentSpec = deploymentSpec;
this.validationOverrides = validationOverrides;
- this.change = change;
this.deploymentIssueId = deploymentIssueId;
this.ownershipIssueId = ownershipIssueId;
this.owner = owner;
@@ -86,7 +84,7 @@ public class LockedApplication {
/** Returns a read-only copy of this */
public Application get() {
- return new Application(id, createdAt, deploymentSpec, validationOverrides, change,
+ return new Application(id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances.values());
}
@@ -94,7 +92,7 @@ public class LockedApplication {
public LockedApplication withNewInstance(InstanceName instance) {
var instances = new HashMap<>(this.instances);
instances.put(instance, new Instance(id.instance(instance)));
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
@@ -102,7 +100,7 @@ public class LockedApplication {
public LockedApplication with(InstanceName instance, UnaryOperator<Instance> modification) {
var instances = new HashMap<>(this.instances);
instances.put(instance, modification.apply(instances.get(instance)));
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
@@ -110,69 +108,63 @@ public class LockedApplication {
public LockedApplication without(InstanceName instance) {
var instances = new HashMap<>(this.instances);
instances.remove(instance);
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
public LockedApplication withNewSubmission(ApplicationVersion latestVersion) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, Optional.of(latestVersion), instances);
}
public LockedApplication withProjectId(OptionalLong projectId) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
public LockedApplication withDeploymentIssueId(IssueId issueId) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
Optional.ofNullable(issueId), ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
public LockedApplication with(DeploymentSpec deploymentSpec) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
public LockedApplication with(ValidationOverrides validationOverrides) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
- deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
- projectId, latestVersion, instances);
- }
-
- public LockedApplication withChange(Change change) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
public LockedApplication withOwnershipIssueId(IssueId issueId) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, Optional.of(issueId), owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
public LockedApplication withOwner(User owner) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, Optional.of(owner), majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
/** Set a major version for this, or set to null to remove any major version override */
public LockedApplication withMajorVersion(Integer majorVersion) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner,
majorVersion == null ? OptionalInt.empty() : OptionalInt.of(majorVersion),
metrics, deployKeys, projectId, latestVersion, instances);
}
public LockedApplication with(ApplicationMetrics metrics) {
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, deployKeys,
projectId, latestVersion, instances);
}
@@ -180,7 +172,7 @@ public class LockedApplication {
public LockedApplication withDeployKey(PublicKey pemDeployKey) {
Set<PublicKey> keys = new LinkedHashSet<>(deployKeys);
keys.add(pemDeployKey);
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, keys,
projectId, latestVersion, instances);
}
@@ -188,7 +180,7 @@ public class LockedApplication {
public LockedApplication withoutDeployKey(PublicKey pemDeployKey) {
Set<PublicKey> keys = new LinkedHashSet<>(deployKeys);
keys.remove(pemDeployKey);
- return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides, change,
+ return new LockedApplication(lock, id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics, keys,
projectId, latestVersion, instances);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
index e82029e53bf..10f99395170 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
@@ -49,11 +49,6 @@ public class ApplicationList extends AbstractFilteringList<Application, Applicat
// ----------------------------------- Filters
- /** Returns the subset of applications which are currently deploying a change */
- public ApplicationList deploying() {
- return matching(application -> application.change().hasTargets());
- }
-
/** Returns the subset of applications which have at least one production deployment */
public ApplicationList withProductionDeployment() {
return matching(application -> application.instances().values().stream()
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 af63e8e5da7..8d082222721 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
@@ -8,6 +8,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
+import static java.util.Objects.requireNonNull;
+
/**
* The changes to an application we currently wish to complete deploying.
* A goal of the system is to deploy platform and application versions separately.
@@ -28,17 +30,17 @@ public final class Change {
/** The application version we are changing to, or empty if none */
private final Optional<ApplicationVersion> application;
+ /** Whether this change is a pin to its contained Vespa version, or to the application's current. */
private final boolean pinned;
private Change(Optional<Version> platform, Optional<ApplicationVersion> application, boolean pinned) {
- Objects.requireNonNull(platform, "platform cannot be null");
- Objects.requireNonNull(application, "application cannot be null");
+ this.platform = requireNonNull(platform, "platform cannot be null");
+ this.application = requireNonNull(application, "application cannot be null");
if (application.isPresent() && application.get().isUnknown()) {
throw new IllegalArgumentException("Application version to deploy must be a known version");
}
- this.platform = platform;
- this.application = application;
this.pinned = pinned;
+
}
public Change withoutPlatform() {
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
new file mode 100644
index 00000000000..cfe84658ce5
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
@@ -0,0 +1,154 @@
+package com.yahoo.vespa.hosted.controller.application;
+
+import com.google.common.collect.ImmutableMap;
+import com.yahoo.collections.AbstractFilteringList;
+import com.yahoo.component.Version;
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.InstanceName;
+import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatusList;
+import com.yahoo.vespa.hosted.controller.deployment.RunStatus;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static java.util.Comparator.comparing;
+import static java.util.Comparator.naturalOrder;
+
+public class InstanceList extends AbstractFilteringList<ApplicationId, InstanceList> {
+
+ private final Map<ApplicationId, DeploymentStatus> statuses;
+
+ private InstanceList(Collection<? extends ApplicationId> items, boolean negate, Map<ApplicationId, DeploymentStatus> statuses) {
+ super(items, negate, (i, n) -> new InstanceList(i, n, statuses));
+ this.statuses = statuses;
+ }
+
+ public static InstanceList from(DeploymentStatusList statuses) {
+ ImmutableMap.Builder<ApplicationId, DeploymentStatus> builder = ImmutableMap.builder();
+ for (DeploymentStatus status : statuses.asList())
+ for (InstanceName instance : status.application().deploymentSpec().instanceNames())
+ builder.put(status.application().id().instance(instance), status);
+ Map<ApplicationId, DeploymentStatus> map = builder.build();
+ return new InstanceList(map.keySet(), false, map);
+ }
+
+ /**
+ * Returns the subset of instances that aren't pinned to an an earlier major version than the given one.
+ *
+ * @param targetMajorVersion the target major version which applications returned allows upgrading to
+ * @param defaultMajorVersion the default major version to assume for applications not specifying one
+ */
+ public InstanceList allowMajorVersion(int targetMajorVersion, int defaultMajorVersion) {
+ return matching(id -> targetMajorVersion <= application(id).deploymentSpec().majorVersion()
+ .orElse(application(id).majorVersion()
+ .orElse(defaultMajorVersion)));
+ }
+
+ /** Returns the subset of instances that are allowed to start deploying its outstanding revision at the given time */
+ public InstanceList canChangeRevisionAt(Instant instant) {
+ return matching(id -> {
+ Change change = statuses.get(id).outstandingChange(id.instance());
+ return change.hasTargets() && statuses.get(id).instanceSteps().get(id.instance())
+ .readyAt(change)
+ .map(readyAt -> ! readyAt.isAfter(instant)).orElse(false);
+ });
+ }
+
+ /** Returns the subset of instances that are allowed to upgrade to the given version at the given time */
+ public InstanceList canUpgradeAt(Version version, Instant instant) {
+ return matching(id -> statuses.get(id).instanceSteps().get(id.instance())
+ .readyAt(Change.of(version))
+ .map(readyAt -> ! readyAt.isAfter(instant)).orElse(false));
+ }
+
+ /** Returns the subset of instances which have at least one productiog deployment */
+ public InstanceList withProductionDeployment() {
+ return matching(id -> instance(id).productionDeployments().size() > 0);
+ }
+
+ /** Returns the subset of instances which have at least one deployment on a lower version than the given one */
+ public InstanceList onLowerVersionThan(Version version) {
+ return matching(id -> instance(id).productionDeployments().values().stream()
+ .anyMatch(deployment -> deployment.version().isBefore(version)));
+ }
+
+ /** Returns the subset of instances which have changes left to deploy; blocked, or deploying */
+ public InstanceList withChanges() {
+ return matching(id -> instance(id).change().hasTargets() || statuses.get(id).outstandingChange(id.instance()).hasTargets());
+ }
+
+ /** Returns the subset of instances which are currently deploying a change */
+ public InstanceList deploying() {
+ return matching(id -> instance(id).change().hasTargets());
+ }
+
+ /** Returns the subset of instances which currently have failing jobs on the given version */
+ public InstanceList failingOn(Version version) {
+ return matching(id -> ! statuses.get(id).instanceJobs().get(id).failing().lastCompleted().on(version).isEmpty());
+ }
+
+ /** Returns the subset of instances which are not pinned to a certain Vespa version. */
+ public InstanceList unpinned() {
+ return matching(id -> ! instance(id).change().isPinned());
+ }
+
+ /** Returns the subset of instances which are not currently failing any jobs. */
+ public InstanceList failing() {
+ return matching(id -> ! statuses.get(id).instanceJobs().get(id).failing().not().withStatus(RunStatus.outOfCapacity).isEmpty());
+ }
+
+ /** Returns the subset of instances which are currently failing an upgrade. */
+ public InstanceList failingUpgrade() {
+ return matching(id -> ! statuses.get(id).instanceJobs().get(id).failing().not().failingApplicationChange().isEmpty());
+ }
+
+ /** Returns the subset of instances which are upgrading (to any version), not considering block windows. */
+ public InstanceList upgrading() {
+ return matching(id -> instance(id).change().platform().isPresent());
+ }
+
+ /** Returns the subset of instances which are currently upgrading to the given version */
+ public InstanceList upgradingTo(Version version) {
+ return upgradingTo(List.of(version));
+ }
+
+
+ /** Returns the subset of instances which are currently upgrading to the given version */
+ public InstanceList upgradingTo(Collection<Version> versions) {
+ return matching(id -> versions.stream().anyMatch(version -> instance(id).change().platform().equals(Optional.of(version))));
+ }
+
+ public InstanceList with(DeploymentSpec.UpgradePolicy policy) {
+ return matching(id -> application(id).deploymentSpec().requireInstance(id.instance()).upgradePolicy() == policy);
+ }
+
+ /** Returns the subset of instances which started failing on the given version */
+ public InstanceList startedFailingOn(Version version) {
+ return matching(id -> ! statuses.get(id).instanceJobs().get(id).firstFailing().on(version).isEmpty());
+ }
+
+ /** Returns this list sorted by increasing oldest production deployment version. Applications without any deployments are ordered first. */
+ public InstanceList byIncreasingDeployedVersion() {
+ return sortedBy(comparing(id -> instance(id).productionDeployments().values().stream()
+ .map(Deployment::version)
+ .min(naturalOrder())
+ .orElse(Version.emptyVersion)));
+ }
+
+ private Application application(ApplicationId id) {
+ return statuses.get(id).application();
+ }
+
+ private Instance instance(ApplicationId id) {
+ return application(id).require(id.instance());
+ }
+
+}
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 b2b3720a9fd..0c5766ea54b 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
@@ -12,15 +12,17 @@ import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.Application;
+import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import java.time.Duration;
import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -40,6 +42,7 @@ import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toUnmodifiableList;
+import static java.util.stream.Collectors.toUnmodifiableMap;
/**
* Status of the deployment jobs of an {@link Application}.
@@ -77,14 +80,17 @@ public class DeploymentStatus {
private final JobList allJobs;
private final SystemName system;
private final Version systemVersion;
+ private final Instant now;
private final Map<JobId, StepStatus> jobSteps;
private final List<StepStatus> allSteps;
- public DeploymentStatus(Application application, Map<JobId, JobStatus> allJobs, SystemName system, Version systemVersion) {
+ public DeploymentStatus(Application application, Map<JobId, JobStatus> allJobs, SystemName system,
+ Version systemVersion, Instant now) {
this.application = requireNonNull(application);
this.allJobs = JobList.from(allJobs.values());
- this.system = system;
- this.systemVersion = systemVersion;
+ this.system = requireNonNull(system);
+ this.systemVersion = requireNonNull(systemVersion);
+ this.now = requireNonNull(now);
List<StepStatus> allSteps = new ArrayList<>();
this.jobSteps = jobDependencies(application.deploymentSpec(), allSteps);
this.allSteps = List.copyOf(allSteps);
@@ -127,14 +133,16 @@ public class DeploymentStatus {
* and any test jobs for any oustanding change, which will likely be needed to lated deploy this change.
*/
public Map<JobId, List<Versions>> jobsToRun() {
- Map<JobId, List<Versions>> jobs = jobsToRun(application().change());
- if (outstandingChange().isEmpty())
- return jobs;
+ Map<InstanceName, Change> changes = new LinkedHashMap<>();
+ for (InstanceName instance : application.deploymentSpec().instanceNames())
+ changes.put(instance, application.require(instance).change());
+ Map<JobId, List<Versions>> jobs = jobsToRun(changes);
// Add test jobs for any outstanding change.
- var testJobs = jobsToRun(outstandingChange().onTopOf(application.change()))
- .entrySet().stream()
- .filter(entry -> ! entry.getKey().type().isProduction());
+ for (InstanceName instance : application.deploymentSpec().instanceNames())
+ changes.put(instance, outstandingChange(instance).onTopOf(application.require(instance).change()));
+ var testJobs = jobsToRun(changes).entrySet().stream()
+ .filter(entry -> ! entry.getKey().type().isProduction());
return Stream.concat(jobs.entrySet().stream(), testJobs)
.collect(collectingAndThen(toMap(Map.Entry::getKey,
@@ -145,8 +153,9 @@ public class DeploymentStatus {
}
/** The set of jobs that need to run for the given change to be considered complete. */
- public Map<JobId, List<Versions>> jobsToRun(Change change) {
- Map<JobId, Versions> productionJobs = productionJobs(change);
+ public Map<JobId, List<Versions>> jobsToRun(Map<InstanceName, Change> changes) {
+ Map<JobId, Versions> productionJobs = new LinkedHashMap<>();
+ changes.forEach((instance, change) -> productionJobs.putAll(productionJobs(instance, change)));
Map<JobId, List<Versions>> testJobs = testJobs(productionJobs);
Map<JobId, List<Versions>> jobs = new LinkedHashMap<>(testJobs);
productionJobs.forEach((job, versions) -> jobs.put(job, List.of(versions)));
@@ -156,6 +165,14 @@ public class DeploymentStatus {
/** The step status for all steps in the deployment spec of this, which are jobs, in the same order as in the deployment spec. */
public Map<JobId, StepStatus> jobSteps() { return jobSteps; }
+ public Map<InstanceName, StepStatus> instanceSteps() {
+ ImmutableMap.Builder<InstanceName, StepStatus> instances = ImmutableMap.builder();
+ for (StepStatus status : allSteps)
+ if (status instanceof InstanceStatus)
+ instances.put(status.instance(), status);
+ return instances.build();
+ }
+
/** The step status for all steps in the deployment spec of this, in the same order as in the deployment spec. */
public List<StepStatus> allSteps() { return allSteps; }
@@ -168,10 +185,10 @@ public class DeploymentStatus {
* The change of this application's latest submission, if this upgrades any of its production deployments,
* and has not yet started rolling out, due to some other change or a block window being present at the time of submission.
*/
- public Change outstandingChange() {
+ public Change outstandingChange(InstanceName instance) {
return application.latestVersion().map(Change::of)
- .filter(change -> application.change().application().map(change::upgrades).orElse(true))
- .filter(change -> ! jobsToRun(change).isEmpty())
+ .filter(change -> application.require(instance).change().application().map(change::upgrades).orElse(true))
+ .filter(change -> ! jobsToRun(Map.of(instance, change)).isEmpty())
.orElse(Change.empty());
}
@@ -190,13 +207,15 @@ public class DeploymentStatus {
}
/** The production jobs that need to run to complete roll-out of the given change to production. */
- public Map<JobId, Versions> productionJobs(Change change) {
- Map<JobId, Versions> jobs = new LinkedHashMap<>();
+ public Map<JobId, Versions> productionJobs(InstanceName instance, Change change) {
+ ImmutableMap.Builder<JobId, Versions> jobs = ImmutableMap.builder();
jobSteps.forEach((job, step) -> {
- if (job.type().isProduction() && step.completedAt(change, Optional.of(job)).isEmpty())
+ if ( job.application().instance().equals(instance)
+ && job.type().isProduction()
+ && step.completedAt(change).isEmpty())
jobs.put(job, Versions.from(change, application, deploymentFor(job), systemVersion));
});
- return ImmutableMap.copyOf(jobs);
+ return jobs.build();
}
/** The test jobs that need to run prior to the given production deployment jobs. */
@@ -237,7 +256,7 @@ public class DeploymentStatus {
Map<JobId, StepStatus> dependencies = new LinkedHashMap<>();
List<StepStatus> previous = List.of();
for (DeploymentSpec.Step step : spec.steps())
- previous = fillStep(dependencies, allSteps, step, previous, spec.instanceNames().get(0));
+ previous = fillStep(dependencies, allSteps, step, previous, null);
return ImmutableMap.copyOf(dependencies);
}
@@ -246,8 +265,11 @@ public class DeploymentStatus {
private List<StepStatus> fillStep(Map<JobId, StepStatus> dependencies, List<StepStatus> allSteps,
DeploymentSpec.Step step, List<StepStatus> previous, InstanceName instance) {
if (step.steps().isEmpty()) {
+ if (instance == null)
+ return previous; // Ignore test and staging outside all instances.
+
if ( ! step.delay().isZero()) {
- StepStatus stepStatus = new DelayStatus((DeploymentSpec.Delay) step, previous);
+ StepStatus stepStatus = new DelayStatus((DeploymentSpec.Delay) step, previous, instance);
allSteps.add(stepStatus);
return List.of(stepStatus);
}
@@ -281,17 +303,19 @@ public class DeploymentStatus {
return previous;
}
- // TODO jonmv: Make instance status as well, including block-change and upgrade policy, to keep track of change;
- // set it equal to application's when dependencies are completed.
if (step instanceof DeploymentInstanceSpec) {
- instance = ((DeploymentInstanceSpec) step).name();
+ DeploymentInstanceSpec spec = ((DeploymentInstanceSpec) step);
+ StepStatus instanceStatus = new InstanceStatus(spec, previous, now, application.require(spec.name()));
+ instance = spec.name();
+ allSteps.add(instanceStatus);
+ previous = List.of(instanceStatus);
for (JobType test : List.of(systemTest, stagingTest)) {
JobId job = new JobId(application.id().instance(instance), test);
if ( ! dependencies.containsKey(job)) {
- var stepStatus = JobStepStatus.ofTestDeployment(new DeclaredZone(test.environment()), List.of(),
+ var testStatus = JobStepStatus.ofTestDeployment(new DeclaredZone(test.environment()), List.of(),
this, job.application().instance(), test, false);
- dependencies.put(job, stepStatus);
- allSteps.add(stepStatus);
+ dependencies.put(job, testStatus);
+ allSteps.add(testStatus);
}
}
}
@@ -341,17 +365,13 @@ public class DeploymentStatus {
private final StepType type;
private final DeploymentSpec.Step step;
private final List<StepStatus> dependencies;
- private final Optional<InstanceName> instance;
-
- private StepStatus(StepType type, DeploymentSpec.Step step, List<StepStatus> dependencies) {
- this(type, step, dependencies, null);
- }
+ private final InstanceName instance;
private StepStatus(StepType type, DeploymentSpec.Step step, List<StepStatus> dependencies, InstanceName instance) {
this.type = requireNonNull(type);
this.step = requireNonNull(step);
this.dependencies = List.copyOf(dependencies);
- this.instance = Optional.ofNullable(instance);
+ this.instance = instance;
}
/** The type of step this is. */
@@ -363,18 +383,23 @@ public class DeploymentStatus {
/** The list of steps that need to be complete before this may start. */
public final List<StepStatus> dependencies() { return dependencies; }
- /** The instance of this, if any. */
- public final Optional<InstanceName> instance() { return instance; }
+ /** The instance of this. */
+ public final InstanceName instance() { return instance; }
/** The id of the job this corresponds to, if any. */
public Optional<JobId> job() { return Optional.empty(); }
/** The time at which this is, or was, complete on the given change and / or versions. */
- public abstract Optional<Instant> completedAt(Change change, Optional<JobId> dependent);
+ public Optional<Instant> completedAt(Change change) { return completedAt(change, Optional.empty()); }
+
+ /** The time at which this is, or was, complete on the given change and / or versions. */
+ abstract Optional<Instant> completedAt(Change change, Optional<JobId> dependent);
+
+ /** The time at which this step is ready to run the specified change and / or versions. */
+ public Optional<Instant> readyAt(Change change) { return readyAt(change, Optional.empty()); }
/** The time at which this step is ready to run the specified change and / or versions. */
- // TODO jonmv: Hide the dependent parameter from the public.
- public Optional<Instant> readyAt(Change change, Optional<JobId> dependent) {
+ Optional<Instant> readyAt(Change change, Optional<JobId> dependent) {
return dependenciesCompletedAt(change, dependent)
.map(ready -> Stream.of(blockedUntil(change),
pausedUntil(),
@@ -384,7 +409,7 @@ public class DeploymentStatus {
}
/** The time at which all dependencies completed on the given change and / or versions. */
- public Optional<Instant> dependenciesCompletedAt(Change change, Optional<JobId> dependent) {
+ Optional<Instant> dependenciesCompletedAt(Change change, Optional<JobId> dependent) {
return dependencies.stream().allMatch(step -> step.completedAt(change, dependent).isPresent())
? dependencies.stream().map(step -> step.completedAt(change, dependent).get())
.max(naturalOrder())
@@ -393,7 +418,6 @@ public class DeploymentStatus {
}
/** The time until which this step is blocked by a change blocker. */
- // TODO jonmv: readyAt for instance can be delayed by block window. Upgrade policy / confidence is something different.
public Optional<Instant> blockedUntil(Change change) { return Optional.empty(); }
/** The time until which this step is paused by user intervention. */
@@ -410,8 +434,8 @@ public class DeploymentStatus {
private static class DelayStatus extends StepStatus {
- private DelayStatus(DeploymentSpec.Delay step, List<StepStatus> dependencies) {
- super(StepType.delay, step, dependencies);
+ private DelayStatus(DeploymentSpec.Delay step, List<StepStatus> dependencies, InstanceName instance) {
+ super(StepType.delay, step, dependencies, instance);
}
@Override
@@ -422,6 +446,51 @@ public class DeploymentStatus {
}
+ private static class InstanceStatus extends StepStatus {
+
+ private final DeploymentInstanceSpec spec;
+ private final Instant now;
+ private final Instance instance;
+
+ private InstanceStatus(DeploymentInstanceSpec spec, List<StepStatus> dependencies, Instant now,
+ Instance instance) {
+ super(StepType.instance, spec, dependencies, spec.name());
+ this.spec = spec;
+ this.now = now;
+ this.instance = instance;
+ }
+
+ /** Time of completion of its dependencies, if all parts of the given change are contained in the change for this instance. */
+ @Override
+ public Optional<Instant> completedAt(Change change, Optional<JobId> dependent) {
+ return (change.platform().isEmpty() || change.platform().equals(instance.change().platform()))
+ && (change.application().isEmpty() || change.application().equals(instance.change().application()))
+ ? dependenciesCompletedAt(change, dependent)
+ : Optional.empty();
+ }
+
+ @Override
+ public Optional<Instant> blockedUntil(Change change) {
+ for (Instant current = now; now.plus(Duration.ofDays(7)).isAfter(current); ) {
+ boolean blocked = false;
+ for (DeploymentSpec.ChangeBlocker blocker : spec.changeBlocker()) {
+ while ( blocker.window().includes(current)
+ && now.plus(Duration.ofDays(7)).isAfter(current)
+ && ( change.platform().isPresent() && blocker.blocksVersions()
+ || change.application().isPresent() && blocker.blocksRevisions())) {
+ blocked = true;
+ current = current.plus(Duration.ofHours(1)).truncatedTo(ChronoUnit.HOURS);
+ }
+ }
+ if ( ! blocked)
+ return current == now ? Optional.empty() : Optional.of(current);
+ }
+ return Optional.of(Instant.MAX);
+ }
+
+ }
+
+
private static abstract class JobStepStatus extends StepStatus {
private final JobStatus job;
@@ -485,7 +554,7 @@ public class DeploymentStatus {
&& ! existingDeployment.map(Deployment::version).equals(change.platform()))
return Optional.empty();
- Change fullChange = status.application().change();
+ Change fullChange = status.application().require(instance).change();
if (existingDeployment.map(deployment -> ! (change.upgrades(deployment.version()) || change.upgrades(deployment.applicationVersion()))
&& (fullChange.downgrades(deployment.version()) || fullChange.downgrades(deployment.applicationVersion())))
.orElse(false))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java
index c3f6947ee4a..c14493a0b72 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java
@@ -28,63 +28,10 @@ public class DeploymentStatusList extends AbstractFilteringList<DeploymentStatus
return new DeploymentStatusList(status, false);
}
- public ApplicationList asApplicationList() {
- return ApplicationList.from(mapToList(DeploymentStatus::application));
- }
-
- public DeploymentStatusList forApplications(Collection<TenantAndApplicationId> applications) {
- return matching(status -> applications.contains(status.application().id()));
- }
-
- public DeploymentStatusList withProductionDeployment() {
- return matching(status -> status.application().productionDeployments().values().stream()
- .anyMatch(deployments -> ! deployments.isEmpty()));
- }
-
- public DeploymentStatusList failing() {
- return matching(DeploymentStatus::hasFailures);
- }
-
- public DeploymentStatusList failingUpgrade() {
- return matching(status -> status.instanceJobs().values().stream()
- .anyMatch(jobs -> ! jobs.failing().not().failingApplicationChange().isEmpty()));
- }
-
- /** Returns the subset of applications which are upgrading (to any version), not considering block windows. */
- public DeploymentStatusList upgrading() {
- return matching(status -> status.application().change().platform().isPresent());
- }
-
- /** Returns the subset of applications which are currently upgrading to the given version */
- public DeploymentStatusList upgradingTo(Version version) {
- return upgradingTo(List.of(version));
- }
-
- /** Returns the subset of applications which are currently upgrading to the given version */
- public DeploymentStatusList upgradingTo(Collection<Version> versions) {
- return matching(status -> versions.stream().anyMatch(version -> status.application().change().platform().equals(Optional.of(version))));
- }
-
- /** Returns the subset of applications which are not pinned to a certain Vespa version. */
- public DeploymentStatusList unpinned() {
- return matching(status -> ! status.application().change().isPinned());
- }
-
- /** Returns the subset of applications which has the given upgrade policy */
- // TODO jonmv: Make this instance based when instances are orchestrated, and deployments reported per instance.
- public DeploymentStatusList with(DeploymentSpec.UpgradePolicy policy) {
- return matching(status -> status.application().deploymentSpec().instances().stream()
- .anyMatch(instance -> instance.upgradePolicy() == policy));
- }
-
/** Returns the subset of applications which have changes left to deploy; blocked, or deploying */
public DeploymentStatusList withChanges() {
- return matching(status -> status.application().change().hasTargets() || status.outstandingChange().hasTargets());
- }
-
- /** Returns the subset of applications which are currently deploying a change */
- public DeploymentStatusList deploying() {
- return matching(status -> status.application().change().hasTargets());
+ return matching(status -> status.application().instances().values().stream()
+ .anyMatch(instance -> instance.change().hasTargets() || status.outstandingChange(instance.name()).hasTargets()));
}
/** Returns the subset of applications which have been failing an upgrade to the given version since the given instant */
@@ -99,24 +46,6 @@ public class DeploymentStatusList extends AbstractFilteringList<DeploymentStatus
.anyMatch(jobs -> failingApplicationChangeSince(jobs, threshold)));
}
- /** Returns the subset of applications which currently have failing jobs on the given version */
- public DeploymentStatusList failingOn(Version version) {
- return matching(status -> status.instanceJobs().values().stream()
- .anyMatch(jobs -> failingOn(jobs, version)));
- }
-
- /** Returns the subset of applications which started failing on the given version */
- public DeploymentStatusList startedFailingOn(Version version) {
- return matching(status -> status.instanceJobs().values().stream()
- .anyMatch(jobs -> ! jobs.firstFailing().on(version).isEmpty()));
- }
-
- private static boolean failingOn(JobList jobs, Version version) {
- return ! jobs.failing()
- .lastCompleted().on(version)
- .isEmpty();
- }
-
private static boolean failingUpgradeToVersionSince(JobList jobs, Version version, Instant threshold) {
return ! jobs.not().failingApplicationChange()
.firstFailing().endedBefore(threshold)
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 482df205af1..16fafeca6d0 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
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
@@ -16,7 +17,9 @@ import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.application.InstanceList;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import java.time.Clock;
import java.time.Duration;
@@ -29,6 +32,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Logger;
@@ -39,7 +43,7 @@ import static java.util.stream.Collectors.toList;
/**
* Responsible for scheduling deployment jobs in a build system and keeping
- * {@link Application#change()} in sync with what is scheduled.
+ * {@link Instance#change()} in sync with what is scheduled.
*
* This class is multi-thread safe.
*
@@ -74,16 +78,22 @@ public class DeploymentTrigger {
}
applications().lockApplicationOrThrow(id, application -> {
- if (acceptNewApplicationVersion(application.get())) {
- application = application.withChange(application.get().change().with(version));
- for (Run run : jobs.active(id))
- if ( ! run.id().type().environment().isManuallyDeployed())
- jobs.abort(run.id());
- }
-
application = application.withProjectId(OptionalLong.of(projectId));
application = application.withNewSubmission(version);
- applications().store(application.withChange(remainingChange(application.get())));
+ DeploymentStatus status = jobs.deploymentStatus(application.get());
+ for (ApplicationId instanceId : InstanceList.from(DeploymentStatusList.from(List.of(status)))
+ .canChangeRevisionAt(clock.instant()).asList())
+ if (acceptNewApplicationVersion(status, instanceId.instance())) {
+ application = application.with(instanceId.instance(),
+ instance -> instance.withChange(status.outstandingChange(instance.name()).onTopOf(instance.change())));
+ for (Run run : jobs.active(instanceId))
+ if ( ! run.id().type().environment().isManuallyDeployed())
+ jobs.abort(run.id());
+ }
+
+ for (InstanceName instanceName : application.get().instances().keySet())
+ application = application.with(instanceName, instance -> instance.withChange(remainingChange(instance, status)));
+ applications().store(application);
});
}
@@ -98,7 +108,8 @@ public class DeploymentTrigger {
}
applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application ->
- applications().store(application.withChange(remainingChange(application.get()))));
+ applications().store(application.with(id.instance(),
+ instance -> instance.withChange(remainingChange(instance, jobs.deploymentStatus(application.get()))))));
}
/**
@@ -149,13 +160,13 @@ public class DeploymentTrigger {
Instance instance = application.require(applicationId.instance());
DeploymentStatus status = jobs.deploymentStatus(application);
JobId job = new JobId(instance.id(), jobType);
- Versions versions = Versions.from(application.change(), application, status.deploymentFor(job), controller.systemVersion());
+ Versions versions = Versions.from(instance.change(), application, status.deploymentFor(job), controller.systemVersion());
String reason = "Job triggered manually by " + user;
Map<JobId, List<Versions>> jobs = status.testJobs(Map.of(job, versions));
if (jobs.isEmpty() || ! requireTests)
jobs = Map.of(job, List.of(versions));
jobs.forEach((jobId, versionsList) -> {
- trigger(deploymentJob(instance, versionsList.get(0), application.change(), jobId.type(), status.jobs().get(jobId).get(), reason, clock.instant()));
+ trigger(deploymentJob(instance, versionsList.get(0), jobId.type(), status.jobs().get(jobId).get(), reason, clock.instant()));
});
return List.copyOf(jobs.keySet());
}
@@ -171,33 +182,42 @@ public class DeploymentTrigger {
}
/** Triggers a change of this application, unless it already has a change. */
- public void triggerChange(TenantAndApplicationId applicationId, Change change) {
- applications().lockApplicationOrThrow(applicationId, application -> {
- if ( ! application.get().change().hasTargets())
- forceChange(applicationId, change);
+ public void triggerChange(ApplicationId instanceId, Change change) {
+ applications().lockApplicationOrThrow(TenantAndApplicationId.from(instanceId), application -> {
+ if ( ! application.get().require(instanceId.instance()).change().hasTargets())
+ forceChange(instanceId, change);
});
}
- /** Overrides the given application's platform and application changes with any contained in the given change. */
- public void forceChange(TenantAndApplicationId applicationId, Change change) {
- applications().lockApplicationOrThrow(applicationId, application -> {
- applications().store(application.withChange(change.onTopOf(application.get().change())));
+ public void triggerNewRevision(ApplicationId id) {
+ applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
+ DeploymentStatus status = jobs.deploymentStatus(application.get());
+ triggerChange(id, status.outstandingChange(id.instance()));
+ });
+ }
+
+ /** Overrides the given instance's platform and application changes with any contained in the given change. */
+ public void forceChange(ApplicationId instanceId, Change change) {
+ applications().lockApplicationOrThrow(TenantAndApplicationId.from(instanceId), application -> {
+ applications().store(application.with(instanceId.instance(),
+ instance -> instance.withChange(change.onTopOf(application.get().require(instanceId.instance()).change()))));
});
}
/** Cancels the indicated part of the given application's change. */
- public void cancelChange(TenantAndApplicationId applicationId, ChangesToCancel cancellation) {
- applications().lockApplicationOrThrow(applicationId, application -> {
+ 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().change().withoutPlatform(); break;
- case APPLICATION: change = application.get().change().withoutApplication(); break;
- case PIN: change = application.get().change().withoutPin(); 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 + "'!");
}
- applications().store(application.withChange(change));
+ applications().store(application.with(instanceId.instance(),
+ instance -> instance.withChange(change)));
});
}
@@ -234,19 +254,18 @@ public class DeploymentTrigger {
List<Job> jobs = new ArrayList<>();
status.jobsToRun().forEach((job, versionsList) -> {
for (Versions versions : versionsList)
- status.jobSteps().get(job).readyAt(status.application().change(), Optional.empty())
+ status.jobSteps().get(job).readyAt(status.application().require(job.application().instance()).change())
.filter(readyAt -> ! clock.instant().isBefore(readyAt))
.filter(__ -> ! status.jobs().get(job).get().isRunning())
.filter(__ -> ! (job.type().isProduction() && isSuspendedInAnotherZone(status.application(), job)))
.ifPresent(readyAt -> {
- jobs.add(deploymentJob(status.application().require(job.application().instance()),
- versions,
- status.application().change(),
- job.type(),
- status.instanceJobs(job.application().instance()).get(job.type()),
- "unknown reason",
- readyAt));
- });
+ jobs.add(deploymentJob(status.application().require(job.application().instance()),
+ versions,
+ job.type(),
+ status.instanceJobs(job.application().instance()).get(job.type()),
+ "unknown reason",
+ readyAt));
+ });
});
return Collections.unmodifiableList(jobs);
}
@@ -330,28 +349,25 @@ public class DeploymentTrigger {
// ---------- Change management o_O ----------
- private boolean acceptNewApplicationVersion(Application application) {
- if ( ! application.deploymentSpec().instances().stream()
- .allMatch(instance -> instance.canChangeRevisionAt(clock.instant()))) return false;
- if (application.change().application().isPresent()) return true; // Replacing a previous application change is ok.
- if (jobs.deploymentStatus(application).hasFailures()) return true; // Allow changes to fix upgrade problems.
- return application.change().platform().isEmpty();
+ private boolean acceptNewApplicationVersion(DeploymentStatus status, InstanceName instance) {
+ if (status.application().require(instance).change().application().isPresent()) return true; // Replacing a previous application change is ok.
+ if (status.hasFailures()) return true; // Allow changes to fix upgrade problems.
+ return status.application().require(instance).change().platform().isEmpty();
}
- private Change remainingChange(Application application) {
- Change change = application.change();
- DeploymentStatus status = jobs.deploymentStatus(application);
- if (status.jobsToRun(status.application().change().withoutApplication()).isEmpty())
+ private Change remainingChange(Instance instance, DeploymentStatus status) {
+ Change change = instance.change();
+ if (status.jobsToRun(Map.of(instance.name(), instance.change().withoutApplication())).isEmpty())
change = change.withoutPlatform();
- if (status.jobsToRun(status.application().change().withoutPlatform()).isEmpty())
+ if (status.jobsToRun(Map.of(instance.name(), instance.change().withoutPlatform())).isEmpty())
change = change.withoutApplication();
return change;
}
// ---------- Version and job helpers ----------
- private Job deploymentJob(Instance instance, Versions versions, Change change, JobType jobType, JobStatus jobStatus, String reason, Instant availableSince) {
- return new Job(instance, versions, jobType, availableSince, jobStatus.isOutOfCapacity(), change.application().isPresent());
+ private Job deploymentJob(Instance instance, Versions versions, JobType jobType, JobStatus jobStatus, String reason, Instant availableSince) {
+ return new Job(instance, versions, jobType, availableSince, jobStatus.isOutOfCapacity(), instance.change().application().isPresent());
}
// ---------- Data containers ----------
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 5798a11758d..20c64751335 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
@@ -588,7 +588,7 @@ public class InternalStepRunner implements StepRunner {
private void sendNotification(Run run, DualLogger logger) {
Application application = controller.applications().requireApplication(TenantAndApplicationId.from(run.id().application()));
Notifications notifications = application.deploymentSpec().requireInstance(run.id().application().instance()).notifications();
- boolean newCommit = application.change().application()
+ boolean newCommit = application.require(run.id().application().instance()).change().application()
.map(run.versions().targetApplication()::equals)
.orElse(false);
When when = newCommit ? failingCommit : failing;
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 61b86df6190..f9ad2ebf553 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
@@ -270,7 +270,7 @@ public class JobController {
.collect(toUnmodifiableList());
}
- /** Returns a list of all active runs for the given instance. */
+ /** Returns a list of all active runs for the given application. */
public List<Run> active(TenantAndApplicationId id) {
return copyOf(controller.applications().requireApplication(id).instances().keySet().stream()
.flatMap(name -> Stream.of(JobType.values())
@@ -280,6 +280,15 @@ public class JobController {
.iterator());
}
+ /** Returns a list of all active runs for the given instance. */
+ public List<Run> active(ApplicationId id) {
+ return copyOf(Stream.of(JobType.values())
+ .map(type -> last(id, type))
+ .flatMap(Optional::stream)
+ .filter(run -> ! run.hasEnded())
+ .iterator());
+ }
+
/** Returns the job status of the given job, possibly empty. */
public JobStatus jobStatus(JobId id) {
return new JobStatus(id, runs(id));
@@ -292,7 +301,8 @@ public class JobController {
.collect(toUnmodifiableMap(job -> job,
job -> jobStatus(job))),
controller.system(),
- controller.systemVersion());
+ controller.systemVersion(),
+ controller.clock().instant());
}
/** Adds deployment status to each of the given applications. */
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 87d4f3f0b9a..c4135120e3d 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
@@ -96,11 +96,6 @@ public class Versions {
targetApplication.id());
}
- /** Create versions using change contained in application */
- public static Versions from(Application application, Version defaultPlatformVersion) {
- return from(application.change(), application, Optional.empty(), defaultPlatformVersion);
- }
-
/** Create versions using given change and application */
public static Versions from(Change change, Application application, Optional<Deployment> deployment,
Version defaultPlatformVersion) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
index e43877cb7e5..ec209c5ed98 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
@@ -60,8 +60,8 @@ public class ControllerMaintenance extends AbstractComponent {
deploymentExpirer = new DeploymentExpirer(controller, maintenanceInterval, jobControl);
deploymentIssueReporter = new DeploymentIssueReporter(controller, controller.serviceRegistry().deploymentIssues(), maintenanceInterval, jobControl);
metricsReporter = new MetricsReporter(controller, metric, jobControl);
- outstandingChangeDeployer = new OutstandingChangeDeployer(controller, Duration.ofMinutes(1), jobControl);
- versionStatusUpdater = new VersionStatusUpdater(controller, Duration.ofMinutes(1), jobControl);
+ outstandingChangeDeployer = new OutstandingChangeDeployer(controller, Duration.ofMinutes(3), jobControl);
+ versionStatusUpdater = new VersionStatusUpdater(controller, Duration.ofMinutes(3), jobControl);
upgrader = new Upgrader(controller, maintenanceInterval, jobControl, curator);
readyJobsTrigger = new ReadyJobsTrigger(controller, Duration.ofMinutes(1), jobControl);
clusterInfoMaintainer = new ClusterInfoMaintainer(controller, Duration.ofHours(2), jobControl);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
index d398012a71b..9f3dcfb38a8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
@@ -1,10 +1,18 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
+import com.yahoo.config.application.api.DeploymentInstanceSpec;
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.InstanceName;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Change;
+import com.yahoo.vespa.hosted.controller.application.InstanceList;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatusList;
import java.time.Duration;
@@ -21,14 +29,12 @@ public class OutstandingChangeDeployer extends Maintainer {
@Override
protected void maintain() {
- for (Application application : controller().applications().asList()) {
- Change change = controller().jobController().deploymentStatus(application).outstandingChange();
- if ( change.hasTargets()
- && application.deploymentSpec().instances().stream()
- .allMatch(instance -> instance.canChangeRevisionAt(controller().clock().instant()))) {
- controller().applications().deploymentTrigger().triggerChange(application.id(), change);
- }
- }
+ ApplicationList applications = ApplicationList.from(controller().applications().asList())
+ .withProductionDeployment()
+ .withDeploymentSpec();
+ for (ApplicationId instance : InstanceList.from(controller().jobController().deploymentStatuses(applications))
+ .canChangeRevisionAt(controller().clock().instant()).asList())
+ controller().applications().deploymentTrigger().triggerNewRevision(instance);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index 49a505475c6..da24d758320 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -3,19 +3,18 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec.UpgradePolicy;
+import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.curator.Lock;
-import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Change;
-import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatusList;
+import com.yahoo.vespa.hosted.controller.application.InstanceList;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence;
import java.time.Duration;
import java.util.Collection;
-import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
@@ -26,6 +25,8 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.ChangesToCancel.PLATFORM;
+import static java.util.Comparator.comparing;
+import static java.util.Comparator.naturalOrder;
/**
* Maintenance job which schedules applications for Vespa version upgrade
@@ -57,35 +58,35 @@ public class Upgrader extends Maintainer {
// Cancel upgrades to broken targets (let other ongoing upgrades complete to avoid starvation)
for (VespaVersion version : controller().versionStatus().versions()) {
if (version.confidence() == Confidence.broken)
- cancelUpgradesOf(applications().upgradingTo(version.versionNumber())
- .not().with(UpgradePolicy.canary),
+ cancelUpgradesOf(instances().upgradingTo(version.versionNumber())
+ .not().with(UpgradePolicy.canary),
version.versionNumber() + " is broken");
}
// Canaries should always try the canary target
- cancelUpgradesOf(applications().upgrading()
- .not().upgradingTo(canaryTarget)
- .with(UpgradePolicy.canary),
+ cancelUpgradesOf(instances().upgrading()
+ .not().upgradingTo(canaryTarget)
+ .with(UpgradePolicy.canary),
"Outdated target version for Canaries");
// Cancel *failed* upgrades to earlier versions, as the new version may fix it
String reason = "Failing on outdated version";
- cancelUpgradesOf(applications().upgrading()
- .failing()
- .not().upgradingTo(defaultTargets)
- .with(UpgradePolicy.defaultPolicy),
+ cancelUpgradesOf(instances().upgrading()
+ .failing()
+ .not().upgradingTo(defaultTargets)
+ .with(UpgradePolicy.defaultPolicy),
reason);
- cancelUpgradesOf(applications().upgrading()
- .failing()
- .not().upgradingTo(conservativeTargets)
- .with(UpgradePolicy.conservative),
+ cancelUpgradesOf(instances().upgrading()
+ .failing()
+ .not().upgradingTo(conservativeTargets)
+ .with(UpgradePolicy.conservative),
reason);
// Schedule the right upgrades
- DeploymentStatusList applications = applications();
- upgrade(applications.with(UpgradePolicy.canary), canaryTarget, applications.size());
- defaultTargets.forEach(target -> upgrade(applications.with(UpgradePolicy.defaultPolicy), target, numberOfApplicationsToUpgrade()));
- conservativeTargets.forEach(target -> upgrade(applications.with(UpgradePolicy.conservative), target, numberOfApplicationsToUpgrade()));
+ InstanceList instances = instances();
+ upgrade(instances.with(UpgradePolicy.canary), canaryTarget, instances.size());
+ defaultTargets.forEach(target -> upgrade(instances.with(UpgradePolicy.defaultPolicy), target, numberOfApplicationsToUpgrade()));
+ conservativeTargets.forEach(target -> upgrade(instances.with(UpgradePolicy.conservative), target, numberOfApplicationsToUpgrade()));
}
/** Returns the target versions for given confidence, one per major version in the system */
@@ -97,33 +98,34 @@ public class Upgrader extends Maintainer {
.map(VespaVersion::versionNumber)
.collect(Collectors.toMap(Version::getMajor, // Key on major version
Function.identity(), // Use version as value
- BinaryOperator.<Version>maxBy(Comparator.naturalOrder()))) // Pick highest version when merging versions within this major
+ BinaryOperator.<Version>maxBy(naturalOrder()))) // Pick highest version when merging versions within this major
.values();
}
- /** Returns a list of all applications, except those which are pinned — these should not be manipulated by the Upgrader */
- private DeploymentStatusList applications() {
- return controller().jobController().deploymentStatuses(ApplicationList.from(controller().applications().asList())).unpinned();
+ /** Returns a list of all production application instances, except those which are pinned, which we should not manipulate here. */
+ private InstanceList instances() {
+ return InstanceList.from(controller().jobController().deploymentStatuses(ApplicationList.from(controller().applications().asList())))
+ .withProductionDeployment()
+ .unpinned();
}
- private void upgrade(DeploymentStatusList statuses, Version version, int numberToUpgrade) {
- statuses.withProductionDeployment()
- .not().deploying() // wait with applications deploying an application change or already upgrading
- .not().failingOn(version) // try to upgrade only if it hasn't failed on this version
- .asApplicationList()
- .onLowerVersionThan(version)
- .allowMajorVersion(version.getMajor(), targetMajorVersion().orElse(version.getMajor()))
- .canUpgradeAt(controller().clock().instant()) // wait with applications that are currently blocking upgrades
- .byIncreasingDeployedVersion() // start with lowest versions
- .first(numberToUpgrade)
- .asList().forEach(application -> controller().applications().deploymentTrigger().triggerChange(application.id(), Change.of(version)));
+ private void upgrade(InstanceList instances, Version version, int numberToUpgrade) {
+ instances.not().failingOn(version)
+ .allowMajorVersion(version.getMajor(), targetMajorVersion().orElse(version.getMajor()))
+ .not().deploying()
+ .onLowerVersionThan(version)
+ .canUpgradeAt(version, controller().clock().instant())
+ .byIncreasingDeployedVersion()
+ .first(numberToUpgrade).asList()
+ .forEach(instance -> controller().applications().deploymentTrigger().triggerChange(instance, Change.of(version)));
}
- private void cancelUpgradesOf(DeploymentStatusList applications, String reason) {
- if (applications.isEmpty()) return;
- log.info("Cancelling upgrading of " + applications.asList().size() + " applications: " + reason);
- for (Application application : applications.asApplicationList().asList())
- controller().applications().deploymentTrigger().cancelChange(application.id(), PLATFORM);
+ private void cancelUpgradesOf(InstanceList instances, String reason) {
+ instances = instances.unpinned();
+ if (instances.isEmpty()) return;
+ log.info("Cancelling upgrading of " + instances.asList().size() + " instances: " + reason);
+ for (ApplicationId instance : instances.asList())
+ controller().applications().deploymentTrigger().cancelChange(instance, PLATFORM);
}
/** Returns the number of applications to upgrade in this run */
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 4d6c21fcd1f..acbae53ad2c 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
@@ -173,7 +173,6 @@ public class ApplicationSerializer {
application.projectId().ifPresent(projectId -> root.setLong(projectIdField, projectId));
application.deploymentIssueId().ifPresent(jiraIssueId -> root.setString(deploymentIssueField, jiraIssueId.value()));
application.ownershipIssueId().ifPresent(issueId -> root.setString(ownershipIssueIdField, issueId.value()));
- toSlime(application.change(), root, deployingField);
application.owner().ifPresent(owner -> root.setString(ownerField, owner.username()));
application.majorVersion().ifPresent(majorVersion -> root.setLong(majorVersionField, majorVersion));
root.setDouble(queryQualityField, application.metrics().queryServiceQuality());
@@ -336,7 +335,6 @@ public class ApplicationSerializer {
Instant createdAt = Instant.ofEpochMilli(root.field(createdAtField).asLong());
DeploymentSpec deploymentSpec = DeploymentSpec.fromXml(root.field(deploymentSpecField).asString(), false);
ValidationOverrides validationOverrides = ValidationOverrides.fromXml(root.field(validationOverridesField).asString());
- Change deploying = changeFromSlime(root.field(deployingField));
Optional<IssueId> deploymentIssueId = Serializers.optionalString(root.field(deploymentIssueField)).map(IssueId::from);
Optional<IssueId> ownershipIssueId = Serializers.optionalString(root.field(ownershipIssueIdField)).map(IssueId::from);
Optional<User> owner = Serializers.optionalString(root.field(ownerField)).map(User::from);
@@ -348,7 +346,7 @@ public class ApplicationSerializer {
OptionalLong projectId = Serializers.optionalLong(root.field(projectIdField));
Optional<ApplicationVersion> latestVersion = latestVersionFromSlime(root.field(latestVersionField));
- return new Application(id, createdAt, deploymentSpec, validationOverrides, deploying,
+ return new Application(id, createdAt, deploymentSpec, validationOverrides,
deploymentIssueId, ownershipIssueId, owner, majorVersion, metrics,
deployKeys, projectId, latestVersion, instances);
}
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 ec5392f1224..59bc410797c 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
@@ -214,13 +214,13 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return application(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deployment")) return JobControllerApiHandlerHelper.overviewResponse(controller, TenantAndApplicationId.from(path.get("tenant"), path.get("application")), request.getUri());
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}/deploying")) return deploying(path.get("tenant"), path.get("application"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), request);
+ 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}/metering")) return metering(path.get("tenant"), path.get("application"), 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"), request); // TODO jonmv: remove
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), request); // TODO jonmv: remove
+ 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.jobController().runs(appIdFromPath(path), jobTypeFromPath(path)), request.getUri());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/test-config")) return testConfig(appIdFromPath(path), jobTypeFromPath(path));
@@ -257,16 +257,16 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}")) return createTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/key")) return addDeveloperKey(path.get("tenant"), request);
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"), false, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), true, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application")) return deployApplication(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}/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"), false, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), true, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), 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}/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));
@@ -289,12 +289,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}")) return deleteTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/key")) return removeDeveloperKey(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return deleteApplication(path.get("tenant"), path.get("application"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), "all");
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), path.get("choice"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), "default", "all");
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), "default", path.get("choice"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/key")) return removeDeployKey(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return deleteInstance(path.get("tenant"), path.get("application"), path.get("instance"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), "all");
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), path.get("choice"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying")) return cancelDeploy(path.get("tenant"), path.get("application"), path.get("instance"), "all");
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/{choice}")) return cancelDeploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("choice"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.abortJobResponse(controller.jobController(), appIdFromPath(path), jobTypeFromPath(path));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}")) return deactivate(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), true, request);
@@ -704,13 +704,16 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
application.projectId().ifPresent(id -> object.setLong("projectId", id));
- // Currently deploying change
- if ( ! application.change().isEmpty())
- toSlime(object.setObject("deploying"), application.change());
+ // TODO jonmv: Remove this when users are updated.
+ application.instances().values().stream().findFirst().ifPresent(instance -> {
+ // Currently deploying change
+ if ( ! instance.change().isEmpty())
+ toSlime(object.setObject("deploying"), instance.change());
- // Outstanding change
- if ( ! status.outstandingChange().isEmpty())
- toSlime(object.setObject("outstandingChange"), status.outstandingChange());
+ // Outstanding change
+ if ( ! status.outstandingChange(instance.name()).isEmpty())
+ toSlime(object.setObject("outstandingChange"), status.outstandingChange(instance.name()));
+ });
// Compile version. The version that should be used when building an application
object.setString("compileVersion", compileVersion(application.id()).toFullString());
@@ -849,15 +852,16 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
application.projectId().ifPresent(id -> object.setLong("projectId", id));
- // Currently deploying change
- if ( ! application.change().isEmpty()) {
- toSlime(object.setObject("deploying"), application.change());
- }
+ // TODO jonmv: Remove when users are updated.
+ application.instances().values().stream().findFirst().ifPresent(firstInstance -> {
+ // Currently deploying change
+ if ( ! firstInstance.change().isEmpty())
+ toSlime(object.setObject("deploying"), firstInstance.change());
- // Outstanding change
- if ( ! status.outstandingChange().isEmpty()) {
- toSlime(object.setObject("outstandingChange"), status.outstandingChange());
- }
+ // Outstanding change
+ if ( ! status.outstandingChange(firstInstance.name()).isEmpty())
+ toSlime(object.setObject("outstandingChange"), status.outstandingChange(firstInstance.name()));
+ });
if (application.deploymentSpec().instance(instance.name()).isPresent()) {
// Jobs sorted according to deployment spec
@@ -1251,14 +1255,14 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
- private HttpResponse deploying(String tenant, String application, HttpRequest request) {
- Application app = controller.applications().requireApplication(TenantAndApplicationId.from(tenant, application));
+ private HttpResponse deploying(String tenantName, String applicationName, String instanceName, HttpRequest request) {
+ Instance instance = controller.applications().requireInstance(ApplicationId.from(tenantName, applicationName, instanceName));
Slime slime = new Slime();
Cursor root = slime.setObject();
- if ( ! app.change().isEmpty()) {
- app.change().platform().ifPresent(version -> root.setString("platform", version.toString()));
- app.change().application().ifPresent(applicationVersion -> root.setString("application", applicationVersion.id()));
- root.setBool("pinned", app.change().isPinned());
+ if ( ! instance.change().isEmpty()) {
+ instance.change().platform().ifPresent(version -> root.setString("platform", version.toString()));
+ instance.change().application().ifPresent(applicationVersion -> root.setString("application", applicationVersion.id()));
+ root.setBool("pinned", instance.change().isPinned());
}
return new SlimeJsonResponse(slime);
}
@@ -1357,12 +1361,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
/** Trigger deployment of the given Vespa version if a valid one is given, e.g., "7.8.9". */
- private HttpResponse deployPlatform(String tenantName, String applicationName, boolean pin, HttpRequest request) {
+ private HttpResponse deployPlatform(String tenantName, String applicationName, String instanceName, boolean pin, HttpRequest request) {
request = controller.auditLogger().log(request);
String versionString = readToString(request.getData());
- TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName);
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
StringBuilder response = new StringBuilder();
- controller.applications().lockApplicationOrThrow(id, application -> {
+ controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
Version version = Version.fromString(versionString);
if (version.equals(Version.emptyVersion))
version = controller.systemVersion();
@@ -1385,11 +1389,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
/** Trigger deployment to the last known application package for the given application. */
- private HttpResponse deployApplication(String tenantName, String applicationName, HttpRequest request) {
+ private HttpResponse deployApplication(String tenantName, String applicationName, String instanceName, HttpRequest request) {
controller.auditLogger().log(request);
- TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName);
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
StringBuilder response = new StringBuilder();
- controller.applications().lockApplicationOrThrow(id, application -> {
+ controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
Change change = Change.of(application.get().latestVersion().get());
controller.applications().deploymentTrigger().forceChange(id, change);
response.append("Triggered " + change + " for " + id);
@@ -1398,11 +1402,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
/** Cancel ongoing change for given application, e.g., everything with {"cancel":"all"} */
- private HttpResponse cancelDeploy(String tenantName, String applicationName, String choice) {
- TenantAndApplicationId id = TenantAndApplicationId.from(tenantName, applicationName);
+ private HttpResponse cancelDeploy(String tenantName, String applicationName, String instanceName, String choice) {
+ ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
StringBuilder response = new StringBuilder();
- controller.applications().lockApplicationOrThrow(id, application -> {
- Change change = application.get().change();
+ controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
+ Change change = application.get().require(id.instance()).change();
if (change.isEmpty()) {
response.append("No deployment in progress for " + application + " at this time");
return;
@@ -1411,7 +1415,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
ChangesToCancel cancel = ChangesToCancel.valueOf(choice.toUpperCase());
controller.applications().deploymentTrigger().cancelChange(id, cancel);
response.append("Changed deployment from '" + change + "' to '" +
- controller.applications().requireApplication(id).change() + "' for " + application);
+ controller.applications().requireInstance(id).change() + "' for " + id);
});
return new MessageResponse(response.toString());
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 8c348f2f635..8635fcaf3ab 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
@@ -80,7 +80,7 @@ class JobControllerApiHandlerHelper {
Application application = controller.applications().requireApplication(TenantAndApplicationId.from(id));
DeploymentStatus deploymentStatus = controller.jobController().deploymentStatus(application);
Instance instance = application.require(id.instance());
- Change change = application.change();
+ Change change = instance.change();
Optional<DeploymentInstanceSpec> spec = application.deploymentSpec().instance(id.instance());
Optional<DeploymentSteps> steps = spec.map(s -> new DeploymentSteps(s, controller::system));
List<JobType> jobs = deploymentStatus.jobSteps().keySet().stream()
@@ -210,9 +210,9 @@ class JobControllerApiHandlerHelper {
}
else
lastPlatformObject.setString("pending",
- application.change().isEmpty()
+ instance.change().isEmpty()
? "Waiting for upgrade slot"
- : "Waiting for " + application.change() + " to complete");
+ : "Waiting for " + instance.change() + " to complete");
}
private static void lastApplicationToSlime(Cursor lastApplicationObject, Application application, Instance instance, Map<JobType, JobStatus> status, Change change, List<JobType> productionJobs, Controller controller) {
@@ -320,7 +320,7 @@ class JobControllerApiHandlerHelper {
break;
for (JobType stepType : steps.toJobs(step)) {
if (pendingProduction.containsKey(stepType)) {
- Versions jobVersions = Versions.from(application.change(),
+ Versions jobVersions = Versions.from(instance.change(),
application,
Optional.ofNullable(instance.deployments().get(stepType.zone(controller.system()))),
controller.systemVersion());
@@ -524,17 +524,17 @@ class JobControllerApiHandlerHelper {
responseObject.setString("tenant", id.tenant().value());
responseObject.setString("application", id.application().value());
- Change change = status.application().change();
Map<JobId, List<Versions>> jobsToRun = status.jobsToRun();
Cursor stepsArray = responseObject.setArray("steps");
for (DeploymentStatus.StepStatus stepStatus : status.allSteps()) {
+ Change change = status.application().require(stepStatus.instance()).change();
Cursor stepObject = stepsArray.addObject();
stepObject.setString("type", stepStatus.type().name());
stepStatus.dependencies().stream()
.map(status.allSteps()::indexOf)
.forEach(stepObject.setArray("dependencies")::addLong);
stepObject.setBool("declared", stepStatus.isDeclared());
- stepStatus.instance().ifPresent(instance -> stepObject.setString("instance", instance.value()));
+ stepObject.setString("instance", stepStatus.instance().value());
stepStatus.job().ifPresent(job -> {
stepObject.setString("jobName", job.type().jobName());
@@ -565,8 +565,8 @@ class JobControllerApiHandlerHelper {
Cursor runObject = toRunArray.addObject();
toSlime(runObject.setObject("versions"), versions);
- stepStatus.readyAt(change, Optional.empty()).ifPresent(ready -> runObject.setLong("readyAt", ready.toEpochMilli()));
- stepStatus.readyAt(change, Optional.empty())
+ stepStatus.readyAt(change).ifPresent(ready -> runObject.setLong("readyAt", ready.toEpochMilli()));
+ stepStatus.readyAt(change)
.filter(controller.clock().instant()::isBefore)
.ifPresent(until -> runObject.setLong("delayedUntil", until.toEpochMilli()));
stepStatus.pausedUntil().ifPresent(until -> runObject.setLong("pausedUntil", until.toEpochMilli()));
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 2b523bdc12a..1f1dcbb8001 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
@@ -86,6 +86,10 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
Slime slime = new Slime();
Cursor root = slime.setObject();
Cursor platformArray = root.setArray("versions");
+ Map<ApplicationId, JobList> jobs = controller.jobController().deploymentStatuses(ApplicationList.from(controller.applications().asList()))
+ .asList().stream()
+ .flatMap(status -> status.instanceJobs().entrySet().stream())
+ .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
for (VespaVersion version : controller.versionStatus().versions()) {
Cursor versionObject = platformArray.addObject();
versionObject.setString("version", version.versionNumber().toString());
@@ -101,10 +105,6 @@ public class DeploymentApiHandler extends LoggingRequestHandler {
configServerObject.setString("hostname", hostname.value());
}
- Map<ApplicationId, JobList> jobs = controller.jobController().deploymentStatuses(ApplicationList.from(controller.applications().asList()))
- .asList().stream()
- .flatMap(status -> status.instanceJobs().entrySet().stream())
- .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
Cursor failingArray = versionObject.setArray("failingApplications");
for (ApplicationId id : version.statistics().failing()) {
if (jobs.containsKey(id))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
index 1c4fe391a35..1b2928c57b1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java
@@ -195,7 +195,7 @@ public class VersionStatus {
versionMap.put(infrastructureVersion, DeploymentStatistics.empty(infrastructureVersion));
}
- for (DeploymentStatus status : statuses.withProductionDeployment().asList()) {
+ for (DeploymentStatus status : statuses.asList()) {
for (Instance instance : status.application().instances().values())
for (Deployment deployment : instance.productionDeployments().values())
versionMap.computeIfAbsent(deployment.version(), DeploymentStatistics::empty);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
index bc6fd093e2f..f722eb4f6bb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
+import com.yahoo.vespa.hosted.controller.application.InstanceList;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatusList;
@@ -48,13 +49,13 @@ public class VespaVersion implements Comparable<VespaVersion> {
}
public static Confidence confidenceFrom(DeploymentStatistics statistics, Controller controller) {
- DeploymentStatusList all = controller.jobController().deploymentStatuses(ApplicationList.from(controller.applications().asList()))
- .withProductionDeployment();
+ InstanceList all = InstanceList.from(controller.jobController().deploymentStatuses(ApplicationList.from(controller.applications().asList())))
+ .withProductionDeployment();
// 'production on this': All deployment jobs upgrading to this version have completed without failure
- DeploymentStatusList productionOnThis = all.forApplications(statistics.production().stream().map(TenantAndApplicationId::from).collect(Collectors.toList()))
+ InstanceList productionOnThis = all.matching(statistics.production()::contains)
.not().failingUpgrade()
.not().upgradingTo(statistics.version());
- DeploymentStatusList failingOnThis = all.forApplications(statistics.failing().stream().map(TenantAndApplicationId::from).collect(Collectors.toList()));
+ InstanceList failingOnThis = all.matching(statistics.failing()::contains);
// 'broken' if any Canary fails
if ( ! failingOnThis.with(UpgradePolicy.canary).isEmpty())
@@ -166,10 +167,10 @@ public class VespaVersion implements Comparable<VespaVersion> {
}
private static boolean nonCanaryApplicationsBroken(Version version,
- DeploymentStatusList failingOnThis,
- DeploymentStatusList productionOnThis) {
- DeploymentStatusList failingNonCanaries = failingOnThis.startedFailingOn(version).not().with(UpgradePolicy.canary);
- DeploymentStatusList productionNonCanaries = productionOnThis.not().with(UpgradePolicy.canary);
+ InstanceList failingOnThis,
+ InstanceList productionOnThis) {
+ InstanceList failingNonCanaries = failingOnThis.startedFailingOn(version).not().with(UpgradePolicy.canary);
+ InstanceList productionNonCanaries = productionOnThis.not().with(UpgradePolicy.canary);
if (productionNonCanaries.size() + failingNonCanaries.size() == 0) return false;
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 35e4b018eed..bc98775c00e 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
@@ -80,11 +80,11 @@ public class ControllerTest {
context.submit(applicationPackage);
assertEquals("Application version is known from completion of initial job",
ApplicationVersion.from(DeploymentContext.defaultSourceRevision, 1, "a@b", new Version("6.1"), Instant.ofEpochSecond(1)),
- context.application().change().application().get());
+ context.instance().change().application().get());
context.runJob(systemTest);
context.runJob(stagingTest);
- ApplicationVersion applicationVersion = context.application().change().application().get();
+ ApplicationVersion applicationVersion = context.instance().change().application().get();
assertFalse("Application version has been set during deployment", applicationVersion.isUnknown());
tester.triggerJobs();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
index 309af7e450b..e4b5e77b377 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java
@@ -188,10 +188,6 @@ public class ApplicationPackageBuilder {
xml.append(athenzIdentityAttributes);
}
xml.append(">\n");
- if (explicitSystemTest)
- xml.append(" <test />\n");
- if (explicitStagingTest)
- xml.append(" <staging />\n");
xml.append(" <instance id='").append(instances).append("'>\n");
if (upgradePolicy != null) {
xml.append(" <upgrade policy='");
@@ -200,6 +196,10 @@ public class ApplicationPackageBuilder {
}
xml.append(notifications);
xml.append(blockChange);
+ if (explicitSystemTest)
+ xml.append(" <test />\n");
+ if (explicitStagingTest)
+ xml.append(" <staging />\n");
xml.append(" <");
xml.append(environment.value());
if (globalServiceId != null) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
index fcf2ef97b49..120b9bad468 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java
@@ -92,11 +92,8 @@ public class DeploymentContext {
private final TesterId testerId;
private final JobController jobs;
private final RoutingGeneratorMock routing;
- private final MockTesterCloud cloud;
private final JobRunner runner;
- private final ControllerTester tester;
- private final ReadyJobsTrigger readyJobsTrigger;
- private final NameServiceDispatcher nameServiceDispatcher;;
+ private final DeploymentTester tester;
private ApplicationVersion lastSubmission = null;
private boolean deferDnsUpdates = false;
@@ -108,18 +105,15 @@ public class DeploymentContext {
this.testerId = TesterId.of(instanceId);
this.jobs = tester.controller().jobController();
this.runner = tester.runner();
- this.tester = tester.controllerTester();
- this.routing = this.tester.serviceRegistry().routingGeneratorMock();
- this.cloud = this.tester.serviceRegistry().testerCloud();
- this.readyJobsTrigger = tester.readyJobsTrigger();
- this.nameServiceDispatcher = tester.nameServiceDispatcher();
+ this.tester = tester;
+ this.routing = tester.controllerTester().serviceRegistry().routingGeneratorMock();
createTenantAndApplication();
}
private void createTenantAndApplication() {
try {
- var tenant = tester.createTenant(instanceId.tenant().value());
- tester.createApplication(tenant.value(), instanceId.application().value(), instanceId.instance().value());
+ var tenant = tester.controllerTester().createTenant(instanceId.tenant().value());
+ tester.controllerTester().createApplication(tenant.value(), instanceId.application().value(), instanceId.instance().value());
} catch (IllegalArgumentException ignored) { } // Tenant and or application may already exist with custom setup.
}
@@ -156,30 +150,30 @@ public class DeploymentContext {
/** Completely deploy the latest change */
public DeploymentContext deploy() {
- assertNotNull("Application package submitted", lastSubmission);
+ assertTrue("Application package submitted", application().latestVersion().isPresent());
assertFalse("Submission is not already deployed", application().instances().values().stream()
.anyMatch(instance -> instance.deployments().values().stream()
.anyMatch(deployment -> deployment.applicationVersion().equals(lastSubmission))));
- assertEquals(lastSubmission, application().change().application().get());
+ assertEquals(application().latestVersion(), instance().change().application());
completeRollout();
- assertFalse(application().change().hasTargets());
+ assertFalse(instance().change().hasTargets());
return this;
}
/** Upgrade platform of this to given version */
public DeploymentContext deployPlatform(Version version) {
- assertEquals(application().change().platform().get(), version);
+ assertEquals(instance().change().platform().get(), version);
assertFalse(application().instances().values().stream()
.anyMatch(instance -> instance.deployments().values().stream()
.anyMatch(deployment -> deployment.version().equals(version))));
- assertEquals(version, application().change().platform().get());
- assertFalse(application().change().application().isPresent());
+ assertEquals(version, instance().change().platform().get());
+ assertFalse(instance().change().application().isPresent());
completeRollout();
assertTrue(application().productionDeployments().values().stream()
- .allMatch(deployments -> deployments.stream()
- .allMatch(deployment -> deployment.version().equals(version))));
+ .allMatch(deployments -> deployments.stream()
+ .allMatch(deployment -> deployment.version().equals(version))));
for (var spec : application().deploymentSpec().instances())
for (JobType type : new DeploymentSteps(spec, tester.controller()::system).productionJobs())
@@ -187,7 +181,7 @@ public class DeploymentContext {
.list(type.zone(tester.controller().system()), applicationId.defaultInstance()).stream() // TODO jonmv: support more
.allMatch(node -> node.currentVersion().equals(version)));
- assertFalse(application().change().hasTargets());
+ assertFalse(instance().change().hasTargets());
return this;
}
@@ -200,7 +194,7 @@ public class DeploymentContext {
/** Flush all pending DNS updates */
public DeploymentContext flushDnsUpdates() {
- nameServiceDispatcher.run();
+ tester.nameServiceDispatcher().run();
assertTrue("All name service requests dispatched",
tester.controller().curator().readNameServiceQueue().requests().isEmpty());
return this;
@@ -279,7 +273,7 @@ public class DeploymentContext {
else
throw new AssertionError("Job '" + run.id() + "' was run twice");
- assertFalse("Change should have no targets, but was " + application().change(), application().change().hasTargets());
+ assertFalse("Change should have no targets, but was " + instance().change(), instance().change().hasTargets());
if (!deferDnsUpdates) {
flushDnsUpdates();
}
@@ -370,12 +364,12 @@ public class DeploymentContext {
/** Deploy default application package, start a run for that change and return its ID */
public RunId newRun(JobType type) {
submit();
- readyJobsTrigger.maintain();
+ tester.readyJobsTrigger().maintain();
if (type.isProduction()) {
runJob(JobType.systemTest);
runJob(JobType.stagingTest);
- readyJobsTrigger.maintain();
+ tester.readyJobsTrigger().maintain();
}
Run run = jobs.active().stream()
@@ -523,7 +517,7 @@ public class DeploymentContext {
assertEquals(Step.Status.succeeded, jobs.run(id).get().stepStatuses().get(Step.startTests));
assertEquals(unfinished, jobs.run(id).get().stepStatuses().get(Step.endTests));
- cloud.set(TesterCloud.Status.SUCCESS);
+ tester.cloud().set(TesterCloud.Status.SUCCESS);
runner.advance(currentRun(job));
assertTrue(jobs.run(id).get().hasEnded());
assertFalse(jobs.run(id).get().hasFailed());
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 8cd938dccf6..855c24a38ab 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
@@ -126,7 +126,7 @@ public class DeploymentTriggerTest {
.collect(Collectors.toCollection(() -> EnumSet.noneOf(JobType.class))));
app.deploy();
- assertEquals(Change.empty(), app.application().change());
+ assertEquals(Change.empty(), app.instance().change());
tester.controllerTester().upgradeSystem(new Version("8.9"));
tester.upgrader().maintain();
@@ -264,7 +264,7 @@ public class DeploymentTriggerTest {
tester.configServer().setSuspended(application.deploymentIdIn(ZoneId.from("prod", "us-central-1")), false);
tester.triggerJobs();
application.runJob(productionUsWest1).runJob(productionUsEast3);
- assertEquals(Change.empty(), application.application().change());
+ assertEquals(Change.empty(), application.instance().change());
}
@Test
@@ -292,11 +292,11 @@ public class DeploymentTriggerTest {
assertEquals(0, tester.jobs().active().size());
app.submit(applicationPackage);
- assertTrue(app.deploymentStatus().outstandingChange().hasTargets());
+ assertTrue(app.deploymentStatus().outstandingChange(app.instance().name()).hasTargets());
app.runJob(systemTest).runJob(stagingTest);
tester.outstandingChangeDeployer().run();
- assertTrue(app.deploymentStatus().outstandingChange().hasTargets());
+ assertTrue(app.deploymentStatus().outstandingChange(app.instance().name()).hasTargets());
tester.triggerJobs();
assertEquals(emptyList(), tester.jobs().active());
@@ -304,7 +304,7 @@ public class DeploymentTriggerTest {
tester.clock().advance(Duration.ofHours(2)); // ---------------- Exit block window: 20:30
tester.outstandingChangeDeployer().run();
- assertFalse(app.deploymentStatus().outstandingChange().hasTargets());
+ assertFalse(app.deploymentStatus().outstandingChange(app.instance().name()).hasTargets());
tester.triggerJobs(); // Tests already run for the blocked production job.
app.assertRunning(productionUsWest1);
@@ -337,7 +337,7 @@ public class DeploymentTriggerTest {
tester.outstandingChangeDeployer().run();
app.runJob(productionUsWest1);
assertEquals(1, app.instanceJobs().get(productionUsWest1).lastSuccess().get().versions().targetApplication().buildNumber().getAsLong());
- assertEquals(2, app.deploymentStatus().outstandingChange().application().get().buildNumber().getAsLong());
+ assertEquals(2, app.deploymentStatus().outstandingChange(app.instance().name()).application().get().buildNumber().getAsLong());
tester.triggerJobs();
// Platform upgrade keeps rolling, since it began before block window, and tests for the new revision have also started.
@@ -346,18 +346,18 @@ public class DeploymentTriggerTest {
assertEquals(2, tester.jobs().active().size());
// Upgrade is done, and outstanding change rolls out when block window ends.
- assertEquals(Change.empty(), app.application().change());
- assertTrue(app.deploymentStatus().outstandingChange().hasTargets());
+ assertEquals(Change.empty(), app.instance().change());
+ assertTrue(app.deploymentStatus().outstandingChange(app.instance().name()).hasTargets());
app.runJob(stagingTest).runJob(systemTest);
tester.clock().advance(Duration.ofHours(1));
tester.outstandingChangeDeployer().run();
- assertTrue(app.application().change().hasTargets());
- assertFalse(app.deploymentStatus().outstandingChange().hasTargets());
+ assertTrue(app.instance().change().hasTargets());
+ assertFalse(app.deploymentStatus().outstandingChange(app.instance().name()).hasTargets());
app.runJob(productionUsWest1).runJob(productionUsEast3);
- assertFalse(app.application().change().hasTargets());
+ assertFalse(app.instance().change().hasTargets());
}
@Test
@@ -420,12 +420,12 @@ public class DeploymentTriggerTest {
assertEquals(appVersion1, app.deployment(ZoneId.from("prod.us-central-1")).applicationVersion());
// Verify the application change is not removed when platform change is cancelled.
- tester.deploymentTrigger().cancelChange(app.application().id(), PLATFORM);
- assertEquals(Change.of(appVersion1), app.application().change());
+ tester.deploymentTrigger().cancelChange(app.instanceId(), PLATFORM);
+ assertEquals(Change.of(appVersion1), app.instance().change());
// Now cancel the change as is done through the web API.
- tester.deploymentTrigger().cancelChange(app.application().id(), ALL);
- assertEquals(Change.empty(), app.application().change());
+ tester.deploymentTrigger().cancelChange(app.instanceId(), ALL);
+ assertEquals(Change.empty(), app.instance().change());
// A new version is released, which should now deploy the currently deployed application version to avoid downgrades.
Version version1 = new Version("6.2");
@@ -464,7 +464,7 @@ public class DeploymentTriggerTest {
tester.upgrader().maintain();
// Deploy application2 to keep this version present in the system
app2.deployPlatform(version1);
- tester.deploymentTrigger().cancelChange(app1.application().id(), ALL);
+ tester.deploymentTrigger().cancelChange(app1.instanceId(), ALL);
// version2 is released and application1 starts upgrading
Version version2 = Version.fromString("7.2");
@@ -480,7 +480,7 @@ public class DeploymentTriggerTest {
tester.upgrader().overrideConfidence(version2, VespaVersion.Confidence.broken);
tester.controllerTester().computeVersionStatus();
tester.upgrader().maintain(); // Cancel upgrades to broken version
- assertEquals("Change becomes latest non-broken version", Change.of(version1), app1.application().change());
+ assertEquals("Change becomes latest non-broken version", Change.of(version1), app1.instance().change());
// version1 proceeds 'til the last job, where it fails; us-central-1 is skipped, as current change is strictly dominated by what's deployed there.
app1.failDeployment(productionEuWest1);
@@ -491,7 +491,7 @@ public class DeploymentTriggerTest {
app1.submit(applicationPackage);
ApplicationVersion revision2 = app1.lastSubmission().get();
app1.runJob(systemTest).runJob(stagingTest);
- assertEquals(Change.of(version1).with(revision2), app1.application().change());
+ assertEquals(Change.of(version1).with(revision2), app1.instance().change());
tester.triggerJobs();
app1.assertRunning(productionUsCentral1);
assertEquals(version2, app1.instance().deployments().get(productionUsCentral1.zone(main)).version());
@@ -511,7 +511,7 @@ public class DeploymentTriggerTest {
// Last job has a different deployment target, so tests need to run again.
app1.runJob(systemTest).runJob(stagingTest).runJob(productionEuWest1);
- assertFalse(app1.application().change().hasTargets());
+ assertFalse(app1.instance().change().hasTargets());
assertFalse(app1.instanceJobs().get(productionUsCentral1).isSuccess());
}
@@ -532,7 +532,7 @@ public class DeploymentTriggerTest {
tester.upgrader().maintain();
app.runJob(systemTest).runJob(stagingTest);
app.timeOutConvergence(productionEuWest1);
- tester.deploymentTrigger().cancelChange(app.application().id(), PLATFORM);
+ tester.deploymentTrigger().cancelChange(app.instanceId(), PLATFORM);
assertEquals(v2, app.deployment(productionEuWest1.zone(main)).version());
assertEquals(v1, app.deployment(productionUsEast3.zone(main)).version());
@@ -558,7 +558,7 @@ public class DeploymentTriggerTest {
// Both jobs fail again, and must be re-triggered -- this is ok, as they are both already triggered on their current targets.
app.failDeployment(productionEuWest1).failDeployment(productionUsEast3)
.runJob(productionEuWest1).runJob(productionUsEast3);
- assertFalse(app.application().change().hasTargets());
+ assertFalse(app.instance().change().hasTargets());
assertEquals(2, app.instanceJobs().get(productionEuWest1).lastSuccess().get().versions().targetApplication().buildNumber().getAsLong());
assertEquals(2, app.instanceJobs().get(productionUsEast3).lastSuccess().get().versions().targetApplication().buildNumber().getAsLong());
}
@@ -647,7 +647,7 @@ public class DeploymentTriggerTest {
assertEquals("Application change preserves version, and new region gets oldest version too",
version1, app1.application().oldestDeployedPlatform().get());
assertEquals(version1, tester.configServer().lastPrepareVersion().get());
- assertFalse("Change deployed", app1.application().change().hasTargets());
+ assertFalse("Change deployed", app1.instance().change().hasTargets());
tester.upgrader().maintain();
app1.deployPlatform(version2);
@@ -737,9 +737,9 @@ public class DeploymentTriggerTest {
app3.abortJob(stagingTest);
assertEquals(0, tester.jobs().active().size());
- assertTrue(app1.application().change().application().isPresent());
- assertFalse(app2.application().change().application().isPresent());
- assertFalse(app3.application().change().application().isPresent());
+ assertTrue(app1.instance().change().application().isPresent());
+ assertFalse(app2.instance().change().application().isPresent());
+ assertFalse(app3.instance().change().application().isPresent());
tester.readyJobsTrigger().maintain();
app1.assertRunning(stagingTest);
@@ -801,7 +801,7 @@ public class DeploymentTriggerTest {
app.runJob(testUsEast3)
.runJob(productionUsWest1).runJob(productionUsCentral1)
.runJob(testUsCentral1).runJob(testUsWest1);
- assertEquals(Change.empty(), app.application().change());
+ assertEquals(Change.empty(), app.instance().change());
// Application starts upgrade, but is confidence is broken cancelled after first zone. Tests won't run.
Version version0 = app.application().oldestDeployedPlatform().get();
@@ -820,25 +820,25 @@ public class DeploymentTriggerTest {
tester.upgrader().maintain();
app.failDeployment(testUsEast3);
app.assertNotRunning(testUsEast3);
- assertEquals(Change.empty(), app.application().change());
+ assertEquals(Change.empty(), app.instance().change());
// Application is pinned to previous version, and downgrades to that. Tests are re-run.
- tester.deploymentTrigger().triggerChange(app.application().id(), Change.of(version0).withPin());
+ tester.deploymentTrigger().triggerChange(app.instanceId(), Change.of(version0).withPin());
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.application().change());
+ assertEquals(Change.empty().withPin(), app.instance().change());
}
@Test
public void testDeployComplicatedDeploymentSpec() {
String complicatedDeploymentSpec =
"<deployment version='1.0' athenz-domain='domain' athenz-service='service'>\n" +
- " <staging />\n" +
" <parallel>\n" +
" <instance id='instance' athenz-service='in-service'>\n" +
+ " <staging />\n" +
" <prod>\n" +
" <parallel>\n" +
" <region active='true'>us-west-1</region>\n" +
@@ -875,11 +875,9 @@ public class DeploymentTriggerTest {
" </endpoints>\n" +
" </instance>\n" +
" <instance id='other'>\n" +
- // TODO jonmv: support change per instance — this instance will be upgraded later, but for now this is ignored.
" <upgrade policy='conservative' />\n" +
" <test />\n" +
- // TODO jonmv: test this when change per instance.
- // " <block-change days='sat' hours='10' time-zone='CET' />\n" +
+ " <block-change days='sat' hours='10' time-zone='CET' />\n" +
" <prod>\n" +
" <region active='true'>eu-west-1</region>\n" +
" <test>eu-west-1</test>\n" +
@@ -895,7 +893,7 @@ public class DeploymentTriggerTest {
ApplicationPackage applicationPackage = ApplicationPackageBuilder.fromDeploymentXml(complicatedDeploymentSpec);
var app1 = tester.newDeploymentContext("t", "a", "instance").submit(applicationPackage);
- var app2 = tester.newDeploymentContext("t", "a", "other").submit(applicationPackage);
+ var app2 = tester.newDeploymentContext("t", "a", "other");
tester.triggerJobs();
assertEquals(2, tester.jobs().active().size());
@@ -955,6 +953,10 @@ public class DeploymentTriggerTest {
app1.runJob(productionApSoutheast1);
tester.triggerJobs();
assertEquals(List.of(), tester.jobs().active());
+
+ tester.atMondayMorning().clock().advance(Duration.ofDays(5).plusHours(5)); // Inside block window for second instance.
+ // TODO jonmv: More checks.
+
}
@Test
@@ -968,12 +970,12 @@ public class DeploymentTriggerTest {
app.submit();
tester.triggerJobs();
tester.outstandingChangeDeployer().run();
- assertEquals(Change.of(version), app.application().change());
+ assertEquals(Change.of(version), app.instance().change());
app.runJob(productionUsEast3).runJob(productionUsWest1);
tester.triggerJobs();
tester.outstandingChangeDeployer().run();
- assertEquals(Change.of(app.lastSubmission().get()), app.application().change());
+ assertEquals(Change.of(app.lastSubmission().get()), app.instance().change());
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
index 0afe7377d40..ea946e132a9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
@@ -144,7 +144,7 @@ public class DeploymentIssueReporterTest {
// app2 is changed to be a canary
app2.submit(canaryPackage).deploy();
assertEquals(canary, app2.application().deploymentSpec().requireInstance("default").upgradePolicy());
- assertEquals(Change.empty(), app2.application().change());
+ assertEquals(Change.empty(), app2.instance().change());
// Bump system version to upgrade canary app2.
Version version = Version.fromString("6.3");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
index a949625baf4..99eb4f049b6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
@@ -139,7 +139,7 @@ public class MetricsReporterTest {
// Application change completes
context.deploy();
- assertFalse("Change deployed", context.application().change().hasTargets());
+ assertFalse("Change deployed", context.instance().change().hasTargets());
// New versions is released and upgrade fails in test environments
Version version = Version.fromString("7.1");
@@ -159,7 +159,7 @@ public class MetricsReporterTest {
// Upgrade eventually succeeds
context.runJob(productionUsWest1);
- assertFalse("Upgrade deployed", context.application().change().hasTargets());
+ assertFalse("Upgrade deployed", context.instance().change().hasTargets());
reporter.maintain();
assertEquals(0, getDeploymentsFailingUpgrade(context.instanceId()));
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
index a822e4fca9f..442a2fd1853 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
@@ -40,17 +40,17 @@ public class OutstandingChangeDeployerTest {
var app1 = tester.newDeploymentContext("tenant", "app1", "default").submit(applicationPackage).deploy();
Version version = new Version(6, 2);
- tester.deploymentTrigger().triggerChange(app1.application().id(), Change.of(version));
+ tester.deploymentTrigger().triggerChange(app1.instanceId(), Change.of(version));
tester.deploymentTrigger().triggerReadyJobs();
- assertEquals(Change.of(version), app1.application().change());
- assertFalse(app1.deploymentStatus().outstandingChange().hasTargets());
+ assertEquals(Change.of(version), app1.instance().change());
+ assertFalse(app1.deploymentStatus().outstandingChange(app1.instance().name()).hasTargets());
assertEquals(1, app1.application().latestVersion().get().buildNumber().getAsLong());
app1.submit(applicationPackage, new SourceRevision("repository1", "master", "cafed00d"));
- assertTrue(app1.deploymentStatus().outstandingChange().hasTargets());
- assertEquals("1.0.2-cafed00d", app1.deploymentStatus().outstandingChange().application().get().id());
+ assertTrue(app1.deploymentStatus().outstandingChange(app1.instance().name()).hasTargets());
+ assertEquals("1.0.2-cafed00d", app1.deploymentStatus().outstandingChange(app1.instance().name()).application().get().id());
app1.assertRunning(JobType.systemTest);
app1.assertRunning(JobType.stagingTest);
assertEquals(2, tester.jobs().active().size());
@@ -58,7 +58,7 @@ public class OutstandingChangeDeployerTest {
deployer.maintain();
tester.triggerJobs();
assertEquals("No effect as job is in progress", 2, tester.jobs().active().size());
- assertEquals("1.0.2-cafed00d", app1.deploymentStatus().outstandingChange().application().get().id());
+ assertEquals("1.0.2-cafed00d", app1.deploymentStatus().outstandingChange(app1.instance().name()).application().get().id());
app1.runJob(JobType.systemTest).runJob(JobType.stagingTest).runJob(JobType.productionUsWest1)
.runJob(JobType.stagingTest).runJob(JobType.systemTest);
@@ -66,11 +66,11 @@ public class OutstandingChangeDeployerTest {
deployer.maintain();
tester.triggerJobs();
- assertEquals("1.0.2-cafed00d", app1.application().change().application().get().id());
+ assertEquals("1.0.2-cafed00d", app1.instance().change().application().get().id());
List<Run> runs = tester.jobs().active();
assertEquals(1, runs.size());
app1.assertRunning(JobType.productionUsWest1);
- assertFalse(app1.deploymentStatus().outstandingChange().hasTargets());
+ assertFalse(app1.deploymentStatus().outstandingChange(app1.instance().name()).hasTargets());
}
}
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 c4a3a34a4f6..249c2644170 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
@@ -190,7 +190,7 @@ public class UpgraderTest {
assertEquals("Upgrade of defaults are scheduled", 10, tester.jobs().active().size());
for (var context : List.of(default0, default1, default2, default3, default4))
- assertEquals(version4, context.application().change().platform().get());
+ assertEquals(version4, context.instance().change().platform().get());
default0.deployPlatform(version4);
@@ -207,9 +207,9 @@ public class UpgraderTest {
tester.triggerJobs();
assertEquals("Upgrade of defaults are scheduled", 10, tester.jobs().active().size());
- assertEquals(version5, default0.application().change().platform().get());
+ assertEquals(version5, default0.instance().change().platform().get());
for (var context : List.of(default1, default2, default3, default4))
- assertEquals(version4, context.application().change().platform().get());
+ assertEquals(version4, context.instance().change().platform().get());
default1.deployPlatform(version4);
default2.deployPlatform(version4);
@@ -240,7 +240,7 @@ public class UpgraderTest {
tester.upgrader().maintain();
- assertEquals(version4, default3.application().change().platform().get());
+ assertEquals(version4, default3.instance().change().platform().get());
}
@Test
@@ -361,10 +361,10 @@ public class UpgraderTest {
// We "manually" cancel upgrades to V1 so that we can use the applications to make V2 fail instead
// But we keep one (default4) to avoid V1 being garbage collected
- tester.deploymentTrigger().cancelChange(default0.application().id(), ALL);
- tester.deploymentTrigger().cancelChange(default1.application().id(), ALL);
- tester.deploymentTrigger().cancelChange(default2.application().id(), ALL);
- tester.deploymentTrigger().cancelChange(default3.application().id(), ALL);
+ tester.deploymentTrigger().cancelChange(default0.instanceId(), ALL);
+ tester.deploymentTrigger().cancelChange(default1.instanceId(), ALL);
+ tester.deploymentTrigger().cancelChange(default2.instanceId(), ALL);
+ tester.deploymentTrigger().cancelChange(default3.instanceId(), ALL);
default0.abortJob(systemTest).abortJob(stagingTest);
default1.abortJob(systemTest).abortJob(stagingTest);
default2.abortJob(systemTest).abortJob(stagingTest);
@@ -374,7 +374,7 @@ public class UpgraderTest {
tester.upgrader().maintain();
tester.triggerJobs();
assertEquals("Upgrade scheduled for remaining apps", 10, tester.jobs().active().size());
- assertEquals("default4 is still upgrading to 6.3", v1, default4.application().change().platform().get());
+ assertEquals("default4 is still upgrading to 6.3", v1, default4.instance().change().platform().get());
// 4/5 applications fail (in the last prod zone) and lowers confidence
default0.runJob(systemTest).runJob(stagingTest).runJob(productionUsWest1).failDeployment(productionUsEast3);
@@ -399,7 +399,7 @@ public class UpgraderTest {
assertEquals(v2, default0.deployment(ZoneId.from("prod.us-west-1")).version());
assertEquals("Last zone is upgraded to v1",
v1, default0.deployment(ZoneId.from("prod.us-east-3")).version());
- assertFalse(default0.application().change().hasTargets());
+ assertFalse(default0.instance().change().hasTargets());
}
@Test
@@ -788,8 +788,8 @@ public class UpgraderTest {
// Application change recorded together with ongoing upgrade
assertTrue("Change contains both upgrade and application change",
- app.application().change().platform().get().equals(version) &&
- app.application().change().application().get().id().equals(applicationVersion));
+ app.instance().change().platform().get().equals(version) &&
+ app.instance().change().application().get().id().equals(applicationVersion));
// Deployment completes
app.runJob(systemTest).runJob(stagingTest).runJob(productionUsWest1).runJob(productionUsEast3);
@@ -879,13 +879,13 @@ public class UpgraderTest {
assertEquals(List.of(), tester.jobs().active()); // No jobs left.
tester.outstandingChangeDeployer().run();
- assertFalse(app.application().change().hasTargets());
+ assertFalse(app.instance().change().hasTargets());
tester.clock().advance(Duration.ofHours(2));
tester.outstandingChangeDeployer().run();
- assertTrue(app.application().change().hasTargets());
+ assertTrue(app.instance().change().hasTargets());
app.runJob(productionUsWest1).runJob(productionUsCentral1).runJob(productionUsEast3);
- assertFalse(app.application().change().hasTargets());
+ assertFalse(app.instance().change().hasTargets());
}
@Test
@@ -895,50 +895,50 @@ public class UpgraderTest {
// Create an application with pinned platform version.
var context = tester.newDeploymentContext();
- tester.deploymentTrigger().forceChange(context.application().id(), Change.empty().withPin());
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPin());
context.submit().deploy();
- assertFalse(context.application().change().hasTargets());
- assertTrue(context.application().change().isPinned());
+ assertFalse(context.instance().change().hasTargets());
+ assertTrue(context.instance().change().isPinned());
assertEquals(3, context.instance().deployments().size());
// Application does not upgrade.
Version version1 = Version.fromString("6.3");
tester.controllerTester().upgradeSystem(version1);
tester.upgrader().maintain();
- assertFalse(context.application().change().hasTargets());
- assertTrue(context.application().change().isPinned());
+ assertFalse(context.instance().change().hasTargets());
+ assertTrue(context.instance().change().isPinned());
// New application package is deployed.
context.submit().deploy();
- assertFalse(context.application().change().hasTargets());
- assertTrue(context.application().change().isPinned());
+ assertFalse(context.instance().change().hasTargets());
+ assertTrue(context.instance().change().isPinned());
// Application upgrades to new version when pin is removed.
- tester.deploymentTrigger().cancelChange(context.application().id(), PIN);
+ tester.deploymentTrigger().cancelChange(context.instanceId(), PIN);
tester.upgrader().maintain();
- assertTrue(context.application().change().hasTargets());
- assertFalse(context.application().change().isPinned());
+ assertTrue(context.instance().change().hasTargets());
+ assertFalse(context.instance().change().isPinned());
// Application is pinned to new version, and upgrade is therefore not cancelled, even though confidence is broken.
- tester.deploymentTrigger().forceChange(context.application().id(), Change.empty().withPin());
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPin());
tester.upgrader().maintain();
tester.triggerJobs();
- assertEquals(version1, context.application().change().platform().get());
+ assertEquals(version1, context.instance().change().platform().get());
// Application fails upgrade after one zone is complete, and is pinned again to the old version.
context.runJob(systemTest).runJob(stagingTest).runJob(productionUsCentral1)
.timeOutUpgrade(productionUsWest1);
- tester.deploymentTrigger().cancelChange(context.application().id(), ALL);
- tester.deploymentTrigger().forceChange(context.application().id(), Change.of(version0).withPin());
- assertEquals(version0, context.application().change().platform().get());
+ tester.deploymentTrigger().cancelChange(context.instanceId(), ALL);
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.of(version0).withPin());
+ assertEquals(version0, context.instance().change().platform().get());
// Application downgrades to pinned version.
tester.abortAll();
context.runJob(stagingTest).runJob(productionUsCentral1);
- assertTrue(context.application().change().hasTargets());
+ assertTrue(context.instance().change().hasTargets());
context.runJob(productionUsWest1); // us-east-3 never upgraded, so no downgrade is needed.
- assertFalse(context.application().change().hasTargets());
+ assertFalse(context.instance().change().hasTargets());
}
@Test
@@ -955,7 +955,8 @@ public class UpgraderTest {
// Keep app 1 on current version
tester.controller().applications().lockApplicationIfPresent(app1.application().id(), app ->
- tester.controller().applications().store(app.withChange(app.get().change().withPin())));
+ tester.controller().applications().store(app.with(app1.instance().name(),
+ instance -> instance.withChange(instance.change().withPin()))));
// New version is released
Version version1 = Version.fromString("6.2");
@@ -972,14 +973,15 @@ public class UpgraderTest {
// App 2 is allowed on new major and upgrades
tester.controller().applications().lockApplicationIfPresent(app2.application().id(), app -> tester.applications().store(app.withMajorVersion(7)));
tester.upgrader().maintain();
- assertEquals(version2, app2.application().change().platform().orElseThrow());
+ assertEquals(version2, app2.instance().change().platform().orElseThrow());
// App 1 is unpinned and upgrades to latest 6
tester.controller().applications().lockApplicationIfPresent(app1.application().id(), app ->
- tester.controller().applications().store(app.withChange(app.get().change().withoutPin())));
+ tester.controller().applications().store(app.with(app1.instance().name(),
+ instance -> instance.withChange(instance.change().withoutPin()))));
tester.upgrader().maintain();
assertEquals("Application upgrades to latest allowed major", version1,
- app1.application().change().platform().orElseThrow());
+ app1.instance().change().platform().orElseThrow());
}
@Test
@@ -998,7 +1000,7 @@ public class UpgraderTest {
Version v2 = Version.fromString("6.2");
tester.controllerTester().upgradeSystem(v2);
tester.upgrader().maintain();
- assertEquals(Change.of(v2), application.application().change());
+ assertEquals(Change.of(v2), application.instance().change());
application.runJob(systemTest).runJob(stagingTest).runJob(productionUsCentral1);
tester.triggerJobs();
@@ -1007,13 +1009,13 @@ public class UpgraderTest {
tester.controllerTester().computeVersionStatus();
tester.upgrader().maintain();
application.runJob(productionUsWest1);
- assertTrue(application.application().change().isEmpty());
+ assertTrue(application.instance().change().isEmpty());
// Next version is released
Version v3 = Version.fromString("6.3");
tester.controllerTester().upgradeSystem(v3);
tester.upgrader().maintain();
- assertEquals(Change.of(v3), application.application().change());
+ assertEquals(Change.of(v3), application.instance().change());
application.runJob(systemTest).runJob(stagingTest);
// First deployment starts upgrading
@@ -1038,7 +1040,7 @@ public class UpgraderTest {
// Upgrade completes
application.runJob(productionUsEast3);
- assertTrue("Upgrade complete", application.application().change().isEmpty());
+ assertTrue("Upgrade complete", application.instance().change().isEmpty());
}
@Test
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 14002807b96..e485e60815b 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
@@ -117,13 +117,12 @@ public class ApplicationSerializerTest {
Map.of(),
List.of(),
RotationStatus.EMPTY,
- Change.empty()));
+ Change.of(Version.fromString("6.7")).withPin()));
Application original = new Application(TenantAndApplicationId.from(id1),
Instant.now().truncatedTo(ChronoUnit.MILLIS),
deploymentSpec,
validationOverrides,
- Change.of(Version.fromString("6.7")).withPin(),
Optional.of(IssueId.from("4321")),
Optional.of(IssueId.from("1234")),
Optional.of(User.from("by-username")),
@@ -168,7 +167,6 @@ public class ApplicationSerializerTest {
assertEquals(original.ownershipIssueId(), serialized.ownershipIssueId());
assertEquals(original.owner(), serialized.owner());
assertEquals(original.majorVersion(), serialized.majorVersion());
- assertEquals(original.change(), serialized.change());
assertEquals(original.deployKeys(), serialized.deployKeys());
assertEquals(original.require(id1.instance()).rotations(), serialized.require(id1.instance()).rotations());
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 cbc7a5477c4..16837fa44f3 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
@@ -340,7 +340,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
id2.application());
// Trigger upgrade and then application change
- deploymentTester.applications().deploymentTrigger().triggerChange(TenantAndApplicationId.from(id2), Change.of(Version.fromString("7.0")));
+ deploymentTester.applications().deploymentTrigger().triggerChange(id2, Change.of(Version.fromString("7.0")));
// POST an application package and a test jar, submitting a new application for production deployment.
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2/submit", POST)