diff options
7 files changed, 100 insertions, 3 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java index 64e986abf9b..6fc6d2257e2 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java @@ -39,6 +39,7 @@ public class DeploymentSpec { /** The empty deployment spec, specifying no zones or rotation, and defaults for all settings */ public static final DeploymentSpec empty = new DeploymentSpec(Optional.empty(), UpgradePolicy.defaultPolicy, + Optional.empty(), Collections.emptyList(), Collections.emptyList(), "<deployment version='1.0'/>", @@ -47,6 +48,7 @@ public class DeploymentSpec { private final Optional<String> globalServiceId; private final UpgradePolicy upgradePolicy; + private final Optional<Integer> majorVersion; private final List<ChangeBlocker> changeBlockers; private final List<Step> steps; private final String xmlForm; @@ -55,15 +57,21 @@ public class DeploymentSpec { public DeploymentSpec(Optional<String> globalServiceId, UpgradePolicy upgradePolicy, List<ChangeBlocker> changeBlockers, List<Step> steps) { - this(globalServiceId, upgradePolicy, changeBlockers, steps, null, Optional.empty(), Optional.empty()); + this(globalServiceId, upgradePolicy, Optional.empty(), changeBlockers, steps, null, Optional.empty(), Optional.empty()); } - public DeploymentSpec(Optional<String> globalServiceId, UpgradePolicy upgradePolicy, + public DeploymentSpec(Optional<String> globalServiceId, UpgradePolicy upgradePolicy, Optional<Integer> majorVersion, + List<ChangeBlocker> changeBlockers, List<Step> steps) { + this(globalServiceId, upgradePolicy, majorVersion, changeBlockers, steps, null, Optional.empty(), Optional.empty()); + } + + public DeploymentSpec(Optional<String> globalServiceId, UpgradePolicy upgradePolicy, Optional<Integer> majorVersion, List<ChangeBlocker> changeBlockers, List<Step> steps, String xmlForm, Optional<AthenzDomain> athenzDomain, Optional<AthenzService> athenzService) { validateTotalDelay(steps); this.globalServiceId = globalServiceId; this.upgradePolicy = upgradePolicy; + this.majorVersion = majorVersion; this.changeBlockers = changeBlockers; this.steps = ImmutableList.copyOf(completeSteps(new ArrayList<>(steps))); this.xmlForm = xmlForm; @@ -166,6 +174,9 @@ public class DeploymentSpec { /** Returns the upgrade policy of this, which is defaultPolicy if none is specified */ public UpgradePolicy upgradePolicy() { return upgradePolicy; } + /** Returns the major version this application is pinned to, or empty (default) to allow all major versions */ + public Optional<Integer> majorVersion() { return majorVersion; } + /** Returns whether upgrade can occur at the given instant */ public boolean canUpgradeAt(Instant instant) { return changeBlockers.stream().filter(block -> block.blocksVersions()) diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java index 8bc4e0026a6..1900e2139cd 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java @@ -30,6 +30,7 @@ import java.util.stream.Collectors; */ public class DeploymentSpecXmlReader { + private static final String majorVersionTag = "major-version"; private static final String testTag = "test"; private static final String stagingTag = "staging"; private static final String blockChangeTag = "block-change"; @@ -101,7 +102,14 @@ public class DeploymentSpecXmlReader { } Optional<AthenzDomain> athenzDomain = stringAttribute("athenz-domain", root).map(AthenzDomain::from); Optional<AthenzService> athenzService = stringAttribute("athenz-service", root).map(AthenzService::from); - return new DeploymentSpec(globalServiceId, readUpgradePolicy(root), readChangeBlockers(root), steps, xmlForm, athenzDomain, athenzService); + return new DeploymentSpec(globalServiceId, + readUpgradePolicy(root), + optionalIntegerAttribute(majorVersionTag, root), + readChangeBlockers(root), + steps, + xmlForm, + athenzDomain, + athenzService); } /** Imposes some constraints on tag order which are not expressible in the schema */ @@ -138,6 +146,19 @@ public class DeploymentSpecXmlReader { } } + /** Returns the given attribute as an integer, or 0 if it is not present */ + private Optional<Integer> optionalIntegerAttribute(String attributeName, Element tag) { + String value = tag.getAttribute(attributeName); + if (value == null || value.isEmpty()) return Optional.empty(); + try { + return Optional.of(Integer.parseInt(value)); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("Expected an integer for attribute '" + attributeName + + "' but got '" + value + "'"); + } + } + /** Returns the given attribute as a string, or Optional.empty if it is not present or empty */ private Optional<String> stringAttribute(String attributeName, Element tag) { String value = tag.getAttribute(attributeName); diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java index 103724744de..a0423293d1d 100644 --- a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java +++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java @@ -30,6 +30,7 @@ public class DeploymentSpecTest { DeploymentSpec spec = DeploymentSpec.fromXml(r); assertEquals(specXml, spec.xmlForm()); assertEquals(1, spec.steps().size()); + assertFalse(spec.majorVersion().isPresent()); assertTrue(spec.steps().get(0).deploysTo(Environment.test)); assertTrue(spec.includes(Environment.test, Optional.empty())); assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1")))); @@ -39,6 +40,20 @@ public class DeploymentSpecTest { } @Test + public void testSpecPinningMajorVersion() { + String specXml = "<deployment version='1.0' major-version='6'>" + + " <test/>" + + "</deployment>"; + + StringReader r = new StringReader(specXml); + DeploymentSpec spec = DeploymentSpec.fromXml(r); + assertEquals(specXml, spec.xmlForm()); + assertEquals(1, spec.steps().size()); + assertTrue(spec.majorVersion().isPresent()); + assertEquals(6, (int)spec.majorVersion().get()); + } + + @Test public void stagingSpec() { StringReader r = new StringReader( "<deployment version='1.0'>" + 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 ca642d0fb2c..e46df3c5a4a 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 @@ -162,6 +162,12 @@ public class ApplicationList { return listOf(list.stream().filter(a -> a.deploymentSpec().canUpgradeAt(instant))); } + /** Returns the subset of applications that hasn't pinned to another major version than the given one */ + public ApplicationList allowMajorVersion(int majorVersion) { + return listOf(list.stream().filter(a -> ! a.deploymentSpec().majorVersion().isPresent() || + a.deploymentSpec().majorVersion().get().equals(majorVersion))); + } + /** Returns the first n application in this (or all, if there are less than n). */ public ApplicationList first(int n) { if (list.size() < n) return this; 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 506b9b4642b..676fe808bfe 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 @@ -66,6 +66,7 @@ public class Upgrader extends Maintainer { cancelUpgradesOf(applications().with(UpgradePolicy.conservative).upgrading().failing().notUpgradingTo(conservativeTarget), reason); // Schedule the right upgrades + canaryTarget.ifPresent(target -> upgrade(applications().with(UpgradePolicy.canary), target)); defaultTarget.ifPresent(target -> upgrade(applications().with(UpgradePolicy.defaultPolicy), target)); conservativeTarget.ifPresent(target -> upgrade(applications().with(UpgradePolicy.conservative), target)); @@ -94,6 +95,7 @@ public class Upgrader extends Maintainer { applications = applications.notPullRequest(); // Pull requests are deployed as separate applications to test then deleted; No need to upgrade applications = applications.hasProductionDeployment(); applications = applications.onLowerVersionThan(version); + applications = applications.allowMajorVersion(version.getMajor()); applications = applications.notDeployingAt(controller().clock().instant()); // wait with applications deploying an application change or already upgrading applications = applications.notFailingOn(version); // try to upgrade only if it hasn't failed on this version applications = applications.canUpgradeAt(controller().clock().instant()); // wait with applications that are currently blocking upgrades 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 2f9703b91e1..b7539166c33 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 @@ -16,6 +16,7 @@ import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Date; +import java.util.Optional; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -30,12 +31,18 @@ public class ApplicationPackageBuilder { private final StringBuilder validationOverridesBody = new StringBuilder(); private final StringBuilder blockChange = new StringBuilder(); + private Optional<Integer> majorVersion = Optional.empty(); private String upgradePolicy = null; private Environment environment = Environment.prod; private String globalServiceId = null; private String athenzIdentityAttributes = null; private String searchDefinition = "search test { }"; + public ApplicationPackageBuilder majorVersion(int majorVersion) { + this.majorVersion = Optional.of(majorVersion); + return this; + } + public ApplicationPackageBuilder upgradePolicy(String upgradePolicy) { this.upgradePolicy = upgradePolicy; return this; @@ -108,6 +115,7 @@ public class ApplicationPackageBuilder { private byte[] deploymentSpec() { StringBuilder xml = new StringBuilder(); xml.append("<deployment version='1.0' "); + majorVersion.ifPresent(v -> xml.append("major-version='").append(v).append("' ")); if(athenzIdentityAttributes != null) { xml.append(athenzIdentityAttributes); } 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 a6e31a4a34b..04c0e7e9a6f 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 @@ -890,6 +890,40 @@ public class UpgraderTest { } @Test + public void testPinningMajorVersion() { + Version version = Version.fromString("6.2"); + tester.upgradeSystem(version); + + ApplicationPackage version6ApplicationPackage = new ApplicationPackageBuilder() + .majorVersion(6) + .upgradePolicy("default") + .environment(Environment.prod) + .region("us-west-1") + .build(); + + // Setup applications + Application canary0 = tester.createAndDeploy("canary0", 1, "canary"); + Application default0 = tester.createAndDeploy("default0", 2, version6ApplicationPackage); + + // New major version is released + version = Version.fromString("7.0"); + tester.upgradeSystem(version); + assertEquals(version, tester.controller().versionStatus().systemVersion().get().versionNumber()); + tester.triggerUntilQuiescence(); + + // ... canary upgrade to it + assertEquals(2, tester.buildService().jobs().size()); + tester.completeUpgrade(canary0, version, "canary"); + assertEquals(0, tester.buildService().jobs().size()); + tester.computeVersionStatus(); + + // The other application does not because it has pinned to major version 6 + tester.upgrader().maintain(); + tester.triggerUntilQuiescence(); + assertEquals(0, tester.buildService().jobs().size()); + } + + @Test public void testAllowApplicationChangeDuringFailingUpgrade() { Version version = Version.fromString("6.2"); tester.upgradeSystem(version); |