summaryrefslogtreecommitdiffstats
path: root/config-model-api
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2017-08-30 14:23:38 +0200
committerMartin Polden <mpolden@mpolden.no>2017-08-30 14:25:48 +0200
commit8748f2455e29085f7a78409ae8326c78e990ff1a (patch)
treec042bb6510a910234cea24177f3fde5441c4598c /config-model-api
parentc1a93692a89586ea6ed51e682cf18f9eafe7d89b (diff)
Read parallel tag from deployment.xml
Diffstat (limited to 'config-model-api')
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java92
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java56
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());
+ }
+ }
+
}