summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2017-11-02 12:25:17 +0100
committerJon Bratseth <bratseth@yahoo-inc.com>2017-11-02 12:25:17 +0100
commitc45c9e7f266d27b2abd2d693681e646cc1e2055f (patch)
tree6ff934835682748f41ecc0a00218aea53ef639fa /controller-server
parent5ae314b3ea171fce5b7f8c0d5dd8ad625e633a27 (diff)
Newer downgrade a production deployment
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java60
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java5
4 files changed, 72 insertions, 19 deletions
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 3339e59d581..f974fbf0faf 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
@@ -319,11 +319,16 @@ public class ApplicationController {
store(application, lock); // store missing information even if we fail deployment below
}
- // Ensure that the deploying change is tested
- if (! canDeployDirectlyTo(zone, options) &&
- ! application.deploymentJobs().isDeployableTo(zone.environment(), application.deploying()))
- throw new IllegalArgumentException("Rejecting deployment of " + application + " to " + zone +
- " as " + application.deploying().get() + " is not tested");
+ if ( ! canDeployDirectlyTo(zone, options)) { // validate automated deployment
+ if (!application.deploymentJobs().isDeployableTo(zone.environment(), application.deploying()))
+ throw new IllegalArgumentException("Rejecting deployment of " + application + " to " + zone +
+ " as " + application.deploying().get() + " is not tested");
+ Deployment existingDeployment = application.deployments().get(zone);
+ if (existingDeployment != null && existingDeployment.version().isAfter(version))
+ throw new IllegalArgumentException("Rejecting deployment of " + application + " to " + zone +
+ " as the requested version " + version + " is older than" +
+ " the current version " + existingDeployment.version());
+ }
// Carry out deployment
DeploymentId deploymentId = new DeploymentId(applicationId, zone);
@@ -338,7 +343,8 @@ public class ApplicationController {
// Use info from previous deployments is available
Deployment previousDeployment = application.deployments().getOrDefault(zone, new Deployment(zone, revision, version, clock.instant()));
Deployment newDeployment = new Deployment(zone, revision, version, clock.instant(),
- previousDeployment.clusterUtils(), previousDeployment.clusterInfo(), previousDeployment.metrics());
+ previousDeployment.clusterUtils(),
+ previousDeployment.clusterInfo(), previousDeployment.metrics());
application = application.with(newDeployment);
store(application, lock);
@@ -614,7 +620,7 @@ public class ApplicationController {
/** Returns whether a direct deployment to given zone is allowed */
private static boolean canDeployDirectlyTo(Zone zone, DeployOptions options) {
- return !options.screwdriverBuildJob.isPresent() ||
+ return ! options.screwdriverBuildJob.isPresent() ||
options.screwdriverBuildJob.get().screwdriverId == null ||
zone.environment().isManuallyDeployed();
}
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 11074e1a68e..9de35f77943 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
@@ -1,6 +1,7 @@
// 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.deployment;
+import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Environment;
@@ -12,6 +13,7 @@ import com.yahoo.vespa.hosted.controller.ApplicationController;
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.Deployment;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport;
@@ -155,22 +157,28 @@ public class DeploymentTrigger {
* newer (different) than the one last completed successfully in next
*/
private boolean changesAvailable(Application application, JobStatus previous, JobStatus next) {
- if ( ! previous.lastSuccess().isPresent()) return false;
-
if ( ! application.deploying().isPresent()) return false;
Change change = application.deploying().get();
- if (change instanceof Change.VersionChange && // the last completed is out of date - don't continue with it
- ! ((Change.VersionChange)change).version().equals(previous.lastSuccess().get().version()))
- return false;
+
+ if ( ! previous.lastSuccess().isPresent() &&
+ ! isProductionJobPreviouslySucceedingOnCurrentlyDeployingVersion(previous, change)) return false;
+
+ if (change instanceof Change.VersionChange) {
+ Version targetVersion = ((Change.VersionChange)change).version();
+ if ( ! (targetVersion.equals(previous.lastSuccess().get().version())) )
+ return false; // version is outdated
+ if (isOnNewerVersionInProductionThan(targetVersion, application, next.type()))
+ return false; // Don't downgrade
+ }
if (next == null) return true;
if ( ! next.lastSuccess().isPresent()) return true;
-
+
JobStatus.JobRun previousSuccess = previous.lastSuccess().get();
JobStatus.JobRun nextSuccess = next.lastSuccess().get();
if (previousSuccess.revision().isPresent() && ! previousSuccess.revision().get().equals(nextSuccess.revision().get()))
return true;
- if (! previousSuccess.version().equals(nextSuccess.version()))
+ if ( ! previousSuccess.version().equals(nextSuccess.version()))
return true;
return false;
}
@@ -396,6 +404,10 @@ public class DeploymentTrigger {
if ( ! deploysTo(application, jobType)) return false;
// Ignore applications that are not associated with a project
if ( ! application.deploymentJobs().projectId().isPresent()) return false;
+ if (application.deploying().isPresent() && application.deploying().get() instanceof Change.VersionChange) {
+ Version targetVersion = ((Change.VersionChange)application.deploying().get()).version();
+ if (isOnNewerVersionInProductionThan(targetVersion, application, jobType)) return false; // Don't downgrade
+ }
return true;
}
@@ -404,6 +416,40 @@ public class DeploymentTrigger {
return application.deploymentJobs().jobStatus().entrySet().stream()
.anyMatch(entry -> entry.getKey().isProduction() && entry.getValue().isRunning(jobTimeoutLimit()));
}
+
+ /**
+ * When upgrading it is ok to trigger the next job even if the previous failed if the previous has earlier succeeded
+ * on the version we are currently upgrading to
+ */
+ private boolean isProductionJobPreviouslySucceedingOnCurrentlyDeployingVersion(JobStatus jobStatus, Change change) {
+ if ( ! (change instanceof Change.VersionChange) ) return false;
+ if ( ! isProduction(jobStatus.type())) return false;
+ Optional<JobStatus.JobRun> lastSuccess = jobStatus.lastSuccess();
+ if ( ! lastSuccess.isPresent()) return false;
+ return lastSuccess.get().version().equals(((Change.VersionChange)change).version());
+ }
+
+ /**
+ * Returns whether the current deployed version in the zone given by the job
+ * is never than the given version. This may be the case even if the production job
+ * in question failed, if the failure happens after deployment.
+ * In that case we should never deploy an earlier version as that may potentially
+ * downgrade production nodes which we are not guaranteed to support.
+ */
+ private boolean isOnNewerVersionInProductionThan(Version version, Application application, JobType job) {
+ if ( ! isProduction(job)) return false;
+ Optional<Zone> zone = job.zone(controller.system());
+ if ( ! zone.isPresent()) return false;
+ Deployment existingDeployment = application.deployments().get(zone.get());
+ if (existingDeployment == null) return false;
+ return existingDeployment.version().isAfter(version);
+ }
+
+ private boolean isProduction(JobType job) {
+ Optional<Zone> zone = job.zone(controller.system());
+ if ( ! zone.isPresent()) return false; // arbitrary
+ return zone.get().environment() == Environment.prod;
+ }
private boolean acceptNewRevisionNow(Application application) {
if ( ! application.deploying().isPresent()) return true;
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 78b4f7f895f..4886eba40b6 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
@@ -24,13 +24,13 @@ public class OutstandingChangeDeployerTest {
@Test
public void testChangeDeployer() {
DeploymentTester tester = new DeploymentTester();
+ tester.configServer().setDefaultVersion(new Version(6, 1));
OutstandingChangeDeployer deployer = new OutstandingChangeDeployer(tester.controller(), Duration.ofMinutes(10),
new JobControl(new MockCuratorDb()));
-
tester.createAndDeploy("app1", 11, "default");
tester.createAndDeploy("app2", 22, "default");
- Version version = new Version(5, 2);
+ Version version = new Version(6, 2);
tester.deploymentTrigger().triggerChange(tester.application("app1").id(), new Change.VersionChange(version));
assertEquals(new Change.VersionChange(version), tester.application("app1").deploying().get());
@@ -52,5 +52,5 @@ public class OutstandingChangeDeployerTest {
assertEquals(DeploymentJobs.JobType.systemTest.id(), jobs.get(0).jobName());
assertFalse(tester.application("app1").hasOutstandingChange());
}
-
+
}
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 2f24d1deca5..0414cda3f55 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
@@ -147,6 +147,7 @@ public class UpgraderTest {
tester.upgrader().maintain();
assertEquals("Applications are on 5.3 - nothing to do", 0, tester.buildSystem().jobs().size());
+ // --- Starting upgrading to a new version which breaks, causing upgrades to commence on the previous version
version = Version.fromString("5.4");
Application default3 = tester.createAndDeploy("default3", 5, "default"); // need 4 to break a version
Application default4 = tester.createAndDeploy("default4", 5, "default");
@@ -183,7 +184,7 @@ public class UpgraderTest {
tester.completeUpgradeWithError(default0, version, "default", DeploymentJobs.JobType.stagingTest);
tester.completeUpgradeWithError(default1, version, "default", DeploymentJobs.JobType.stagingTest);
tester.completeUpgradeWithError(default2, version, "default", DeploymentJobs.JobType.stagingTest);
- tester.completeUpgradeWithError(default3, version, "default", DeploymentJobs.JobType.stagingTest);
+ tester.completeUpgradeWithError(default3, version, "default", DeploymentJobs.JobType.productionUsWest1);
tester.completeUpgrade(default4, version, "default");
tester.updateVersionStatus(version);
assertEquals(VespaVersion.Confidence.broken, tester.controller().versionStatus().systemVersion().get().confidence());
@@ -192,7 +193,7 @@ public class UpgraderTest {
3, tester.buildSystem().jobs().size());
assertEquals("5.4", ((Change.VersionChange)tester.application(default1.id()).deploying().get()).version().toString());
assertEquals("5.4", ((Change.VersionChange)tester.application(default2.id()).deploying().get()).version().toString());
- assertEquals("5.4", ((Change.VersionChange)tester.application(default2.id()).deploying().get()).version().toString());
+ assertEquals("5.4", ((Change.VersionChange)tester.application(default3.id()).deploying().get()).version().toString());
}
@Test