aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorJon Marius Venstad <venstad@gmail.com>2022-01-20 21:25:44 +0100
committerJon Marius Venstad <venstad@gmail.com>2022-01-20 21:25:44 +0100
commit8b2ac541bf5e21e5d31cb77550e44cc95e42a0e0 (patch)
tree12f40c9402335888e8cc39588a9066facd759b88 /controller-server
parent8eca103ad60e043fd90c9c79c2eaeca83a9023ed (diff)
Compute outstanding change based on previous instances, when any
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java109
3 files changed, 130 insertions, 4 deletions
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 1cef9d7d855..ad237a6aa6e 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
@@ -13,6 +13,7 @@ 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.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.Change;
@@ -26,7 +27,6 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -216,12 +216,29 @@ public class DeploymentStatus {
* 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(InstanceName instance) {
- return application.latestVersion().map(Change::of)
+ return nextVersion(instance).map(Change::of)
.filter(change -> application.require(instance).change().application().map(change::upgrades).orElse(true))
.filter(change -> ! jobsToRun(Map.of(instance, change)).isEmpty())
.orElse(Change.empty());
}
+ /** The next application version to roll out to instance. */
+ private Optional<ApplicationVersion> nextVersion(InstanceName instance) {
+ return Optional.ofNullable(instanceSteps().get(instance)).stream()
+ .flatMap(this::allDependencies)
+ .flatMap(step -> step.instance.latestDeployed().stream())
+ .min(naturalOrder())
+ .or(application::latestVersion);
+ }
+
+ private Stream<InstanceStatus> allDependencies(StepStatus step) {
+ return step.dependencies.stream()
+ .flatMap(dep -> Stream.concat(Stream.of(dep), allDependencies(dep)))
+ .filter(InstanceStatus.class::isInstance)
+ .map(InstanceStatus.class::cast)
+ .distinct();
+ }
+
/**
* True if the job has already been triggered on the given versions, or if all test types (systemTest, stagingTest),
* restricted to the job's instance if declared in that instance, have successful runs on the given versions.
@@ -310,7 +327,7 @@ public class DeploymentStatus {
/** Adds the primitive steps contained in the given step, which depend on the given previous primitives, to the dependency graph. */
private List<StepStatus> fillStep(Map<JobId, StepStatus> dependencies, List<StepStatus> allSteps,
DeploymentSpec.Step step, List<StepStatus> previous, InstanceName instance) {
- if (step.steps().isEmpty()) {
+ if (step.steps().isEmpty() && ! (step instanceof DeploymentInstanceSpec)) {
if (instance == null)
return previous; // Ignore test and staging outside all instances.
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 c6e4d02acfa..22df5ca559e 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
@@ -24,7 +24,7 @@ public class DeploymentStatusList extends AbstractFilteringList<DeploymentStatus
/** Returns the subset of applications which have changes left to deploy; blocked, or deploying */
public DeploymentStatusList withChanges() {
- return matching(status -> status.application().instances().values().stream()
+ return matching(status -> status.application().productionInstances().values().stream()
.anyMatch(instance -> instance.change().hasTargets() || status.outstandingChange(instance.name()).hasTargets()));
}
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 a1b427fbeaa..52521775ddd 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
@@ -819,6 +819,115 @@ public class DeploymentTriggerTest {
}
@Test
+ public void testMultipleInstancesWithDifferentChanges() {
+ DeploymentContext i1 = tester.newDeploymentContext("t", "a", "i1");
+ DeploymentContext i2 = tester.newDeploymentContext("t", "a", "i2");
+ DeploymentContext i3 = tester.newDeploymentContext("t", "a", "i3");
+ DeploymentContext i4 = tester.newDeploymentContext("t", "a", "i4");
+ ApplicationPackage applicationPackage = ApplicationPackageBuilder
+ .fromDeploymentXml("<deployment version='1'>\n" +
+ " <parallel>\n" +
+ " <instance id='i1'>\n" +
+ " <prod>\n" +
+ " <region>us-east-3</region>\n" +
+ " <delay hours='6' />\n" +
+ " </prod>\n" +
+ " </instance>\n" +
+ " <instance id='i2'>\n" +
+ " <prod>\n" +
+ " <region>us-east-3</region>\n" +
+ " </prod>\n" +
+ " </instance>\n" +
+ " </parallel>\n" +
+ " <instance id='i3'>\n" +
+ " <prod>\n" +
+ " <region>us-east-3</region>\n" +
+ " <delay hours='18' />\n" +
+ " <test>us-east-3</test>\n" +
+ " </prod>\n" +
+ " </instance>\n" +
+ " <instance id='i4'>\n" +
+ " <test />\n" +
+ " <staging />\n" +
+ " <prod>\n" +
+ " <region>us-east-3</region>\n" +
+ " </prod>\n" +
+ " </instance>\n" +
+ "</deployment>\n");
+
+ // Package is submitted, and change propagated to the two first instances.
+ i1.submit(applicationPackage);
+ Optional<ApplicationVersion> v0 = i1.lastSubmission();
+ tester.outstandingChangeDeployer().run();
+ assertEquals(v0, i1.instance().change().application());
+ assertEquals(v0, i2.instance().change().application());
+ assertEquals(Optional.empty(), i3.instance().change().application());
+ assertEquals(Optional.empty(), i4.instance().change().application());
+
+ // Tests run in i4, as they're declared there, and i1 and i2 get to work
+ i4.runJob(systemTest).runJob(stagingTest);
+ i1.runJob(productionUsEast3);
+ i2.runJob(productionUsEast3);
+
+ // Since the post-deployment delay of i1 is incomplete, i3 doesn't yet get the change.
+ tester.outstandingChangeDeployer().run();
+ assertEquals(v0, i1.instance().latestDeployed());
+ assertEquals(v0, i2.instance().latestDeployed());
+ assertEquals(Optional.empty(), i1.instance().change().application());
+ assertEquals(Optional.empty(), i2.instance().change().application());
+ assertEquals(Optional.empty(), i3.instance().change().application());
+ assertEquals(Optional.empty(), i4.instance().change().application());
+
+ // When the delay is done, i3 gets the change.
+ tester.clock().advance(Duration.ofHours(6));
+ tester.outstandingChangeDeployer().run();
+ assertEquals(Optional.empty(), i1.instance().change().application());
+ assertEquals(Optional.empty(), i2.instance().change().application());
+ assertEquals(v0, i3.instance().change().application());
+ assertEquals(Optional.empty(), i4.instance().change().application());
+
+ // v0 begins roll-out in i3, and v1 is submitted and rolls out in i1 and i2 some time later
+ i3.runJob(productionUsEast3); // v0
+ tester.clock().advance(Duration.ofHours(12));
+ i1.submit(applicationPackage);
+ Optional<ApplicationVersion> v1 = i1.lastSubmission();
+ i4.runJob(systemTest).runJob(stagingTest);
+ i1.runJob(productionUsEast3); // v1
+ i2.runJob(productionUsEast3); // v1
+ assertEquals(v1, i1.instance().latestDeployed());
+ assertEquals(v1, i2.instance().latestDeployed());
+ assertEquals(Optional.empty(), i1.instance().change().application());
+ assertEquals(Optional.empty(), i2.instance().change().application());
+ assertEquals(v0, i3.instance().change().application());
+ assertEquals(Optional.empty(), i4.instance().change().application());
+
+ // After some time, v2 also starts rolling out to i1 and i2, but does not complete in i2
+ tester.clock().advance(Duration.ofHours(3));
+ i1.submit(applicationPackage);
+ Optional<ApplicationVersion> v2 = i1.lastSubmission();
+ i4.runJob(systemTest).runJob(stagingTest);
+ i1.runJob(productionUsEast3); // v2
+ tester.clock().advance(Duration.ofHours(3));
+
+ // v1 is all done in i1 and i2, but does not yet roll out in i3; v2 is not completely rolled out there yet.
+ // TODO jonmv: thie belowh new revision policy, but must be faked for now, as v1 would not wait for v0 to complete.
+ //tester.outstandingChangeDeployer().run();
+ assertEquals(v0, i3.instance().change().application());
+
+ // i3 completes v0, which rolls out to i4; v1 is ready for i3, but v2 is not.
+ i3.runJob(testUsEast3);
+ assertEquals(Optional.empty(), i3.instance().change().application());
+ tester.outstandingChangeDeployer().run();
+ assertEquals(v2, i1.instance().latestDeployed());
+ assertEquals(v1, i2.instance().latestDeployed());
+ assertEquals(v0, i3.instance().latestDeployed());
+ assertEquals(Optional.empty(), i1.instance().change().application());
+ assertEquals(v2, i2.instance().change().application());
+ assertEquals(v1, i3.instance().change().application());
+ assertEquals(v0, i4.instance().change().application());
+ }
+
+ @Test
public void testMultipleInstances() {
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.instances("instance1,instance2")