diff options
author | Martin Polden <mpolden@mpolden.no> | 2017-08-30 14:23:38 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2017-08-30 14:25:48 +0200 |
commit | 8748f2455e29085f7a78409ae8326c78e990ff1a (patch) | |
tree | c042bb6510a910234cea24177f3fde5441c4598c /config-model-api/src | |
parent | c1a93692a89586ea6ed51e682cf18f9eafe7d89b (diff) |
Read parallel tag from deployment.xml
Diffstat (limited to 'config-model-api/src')
-rw-r--r-- | config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java | 92 | ||||
-rw-r--r-- | config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java | 56 |
2 files changed, 133 insertions, 15 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 68c1a897dc3..ba68404b7ca 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 @@ -15,10 +15,12 @@ import java.io.Reader; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** @@ -57,6 +59,7 @@ public class DeploymentSpec { this.upgradePolicy = upgradePolicy; this.steps = ImmutableList.copyOf(completeSteps(new ArrayList<>(steps))); this.xmlForm = xmlForm; + validateZones(this.steps); } /** Throw an IllegalArgumentException if the total delay exceeds 24 hours */ @@ -68,6 +71,33 @@ public class DeploymentSpec { throw new IllegalArgumentException("The total delay specified is " + Duration.ofSeconds(totalDelaySeconds) + " but max 24 hours is allowed"); } + + /** Throw an IllegalArgumentException if any production zone is declared multiple times */ + private static void validateZones(List<Step> steps) { + // Collect both non-parallel and parallel zones + List<DeclaredZone> zones = new ArrayList<>(); + steps.stream() + .filter(step -> step instanceof DeclaredZone) + .map(DeclaredZone.class::cast) + .forEach(zones::add); + steps.stream() + .filter(step -> step instanceof ParallelZones) + .map(ParallelZones.class::cast) + .flatMap(parallelZones -> parallelZones.zones().stream()) + .forEach(zones::add); + + + // Detect duplicates + Set<DeclaredZone> unique = new HashSet<>(); + List<RegionName> duplicates = zones.stream() + .filter(z -> z.environment() == Environment.prod && !unique.add(z)) + .map(z -> z.region().get()) + .collect(Collectors.toList()); + if (!duplicates.isEmpty()) { + throw new IllegalArgumentException("All declared regions must be unique, but found these " + + "duplicated regions: " + duplicates); + } + } /** Adds missing required steps and reorders steps to a permissible order */ private static List<Step> completeSteps(List<Step> steps) { @@ -123,7 +153,7 @@ public class DeploymentSpec { public List<Step> steps() { return steps; } /** Returns only the DeclaredZone deployment steps of this in the order they will be performed */ - public List<DeclaredZone> zones() { + public List<DeclaredZone> zones() { return steps.stream().filter(step -> step instanceof DeclaredZone).map(DeclaredZone.class::cast) .collect(Collectors.toList()); } @@ -168,17 +198,21 @@ public class DeploymentSpec { if (environment == Environment.prod) { for (Element stepTag : XML.getChildren(environmentTag)) { - if (stepTag.getTagName().equals("delay")) - steps.add(new Delay(Duration.ofSeconds(longAttribute("hours", stepTag) * 60 * 60 + - longAttribute("minutes", stepTag) * 60 + - longAttribute("seconds", stepTag)))); - else // a region: deploy step - steps.add(new DeclaredZone(environment, - Optional.of(RegionName.from(XML.getValue(stepTag).trim())), - readActive(stepTag))); + if (stepTag.getTagName().equals("delay")) { + steps.add(new Delay(Duration.ofSeconds(longAttribute("hours", stepTag) * 60 * 60 + + longAttribute("minutes", stepTag) * 60 + + longAttribute("seconds", stepTag)))); + } else if (stepTag.getTagName().equals("parallel")) { + List<DeclaredZone> zones = new ArrayList<>(); + for (Element regionTag : XML.getChildren(stepTag)) { + zones.add(readDeclaredZone(environment, regionTag)); + } + steps.add(new ParallelZones(zones)); + } else { // a region: deploy step + steps.add(readDeclaredZone(environment, stepTag)); + } } - } - else { + } else { steps.add(new DeclaredZone(environment)); } @@ -207,6 +241,11 @@ public class DeploymentSpec { return tagName.equals("test") || tagName.equals("staging") || tagName.equals("prod"); } + private static DeclaredZone readDeclaredZone(Environment environment, Element regionTag) { + return new DeclaredZone(environment, Optional.of(RegionName.from(XML.getValue(regionTag).trim())), + readActive(regionTag)); + } + private static Optional<String> readGlobalServiceId(Element environmentTag) { String globalServiceId = environmentTag.getAttribute("global-service-id"); if (globalServiceId == null || globalServiceId.isEmpty()) { @@ -363,6 +402,37 @@ public class DeploymentSpec { } + /** A deployment step which is to run deployment to multiple zones in parallel */ + public static class ParallelZones extends Step { + + private final List<DeclaredZone> zones; + + public ParallelZones(List<DeclaredZone> zones) { + this.zones = ImmutableList.copyOf(zones); + } + + /** The list of zones to deploy in */ + public List<DeclaredZone> zones() { return this.zones; } + + @Override + public boolean deploysTo(Environment environment, Optional<RegionName> region) { + return zones.stream().anyMatch(zone -> zone.deploysTo(environment, region)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ParallelZones)) return false; + ParallelZones that = (ParallelZones) o; + return Objects.equals(zones, that.zones); + } + + @Override + public int hashCode() { + return Objects.hash(zones); + } + } + /** Controls when this application will be upgraded to new Vespa versions */ public enum UpgradePolicy { /** Canary: Applications with this policy will upgrade before any other */ 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 0458e9d4c15..2b503125e33 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 @@ -4,14 +4,15 @@ package com.yahoo.config.application.api; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.fail; import java.io.StringReader; import java.util.Optional; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * @author bratseth */ @@ -233,4 +234,51 @@ public class DeploymentSpecTest { assertEquals("<deployment version='1.0'/>", DeploymentSpec.empty.xmlForm()); } + @Test + public void productionSpecWithParallelDeployments() { + StringReader r = new StringReader( + "<deployment>\n" + + " <prod> \n" + + " <region active='true'>us-west-1</region>\n" + + " <parallel>\n" + + " <region active='true'>us-central-1</region>\n" + + " <region active='true'>us-east-3</region>\n" + + " </parallel>\n" + + " </prod>\n" + + "</deployment>" + ); + DeploymentSpec spec = DeploymentSpec.fromXml(r); + DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.steps().get(3)); + assertEquals(2, parallelZones.zones().size()); + assertEquals(RegionName.from("us-central-1"), parallelZones.zones().get(0).region().get()); + assertEquals(RegionName.from("us-east-3"), parallelZones.zones().get(1).region().get()); + } + + @Test + public void productionSpecWithDuplicateRegions() { + StringReader r = new StringReader( + "<deployment>\n" + + " <prod>\n" + + " <region active='true'>us-west-1</region>\n" + + " <parallel>\n" + + " <region active='true'>us-west-1</region>\n" + + " <region active='true'>us-central-1</region>\n" + + " <region active='true'>us-east-3</region>\n" + + " </parallel>\n" + + " <parallel>\n" + + " <region active='true'>eu-west-1</region>\n" + + " <region active='true'>us-central-1</region>\n" + + " </parallel>\n" + + " </prod>\n" + + "</deployment>" + ); + try { + DeploymentSpec.fromXml(r); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + assertEquals("All declared regions must be unique, but found these duplicated regions: " + + "[us-west-1, us-central-1]", e.getMessage()); + } + } + } |