aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Marius Venstad <jonmv@users.noreply.github.com>2019-12-02 09:30:22 +0100
committerGitHub <noreply@github.com>2019-12-02 09:30:22 +0100
commite5bb149b5a59fb3979ddfc9a4a11397147798626 (patch)
tree086de6d2c5c86f37501655abf18d89bbd6ccfd9a
parente346de03f3ff1b37e9b7ce94399492fdebcd5ac6 (diff)
parent7adc4051326cb29a2dfcfa4e034451aae8ee8118 (diff)
Merge pull request #11461 from vespa-engine/jvenstad/test-steps-in-deployment-spec
Jvenstad/test steps in deployment spec
-rw-r--r--config-model-api/abi-spec.json69
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java115
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java171
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java23
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java223
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java207
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java2
-rw-r--r--config-model/src/main/resources/schema/deployment.rnc23
-rw-r--r--config-model/src/test/cfg/application/app_complicated_deployment_spec/deployment.xml48
-rw-r--r--config-model/src/test/cfg/application/app_complicated_deployment_spec/hosts.xml21
-rw-r--r--config-model/src/test/cfg/application/app_complicated_deployment_spec/searchdefinitions/music.sd37
-rw-r--r--config-model/src/test/cfg/application/app_complicated_deployment_spec/services.xml59
-rw-r--r--config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml4
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java2
18 files changed, 813 insertions, 219 deletions
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json
index edf83fe4475..d8482499a93 100644
--- a/config-model-api/abi-spec.json
+++ b/config-model-api/abi-spec.json
@@ -188,7 +188,7 @@
"fields": []
},
"com.yahoo.config.application.api.DeploymentInstanceSpec": {
- "superClass": "com.yahoo.config.application.api.DeploymentSpec$Step",
+ "superClass": "com.yahoo.config.application.api.DeploymentSpec$Steps",
"interfaces": [],
"attributes": [
"public"
@@ -196,20 +196,16 @@
"methods": [
"public void <init>(com.yahoo.config.provision.InstanceName, java.util.List, com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy, java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, com.yahoo.config.application.api.Notifications, java.util.List)",
"public com.yahoo.config.provision.InstanceName name()",
- "public java.time.Duration delay()",
- "public java.util.List steps()",
"public com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy upgradePolicy()",
"public java.util.List changeBlocker()",
"public java.util.Optional globalServiceId()",
"public boolean canUpgradeAt(java.time.Instant)",
"public boolean canChangeRevisionAt(java.time.Instant)",
- "public java.util.List zones()",
- "public boolean deploysTo(com.yahoo.config.provision.Environment, java.util.Optional)",
"public java.util.Optional athenzDomain()",
"public java.util.Optional athenzService(com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
"public com.yahoo.config.application.api.Notifications notifications()",
"public java.util.List endpoints()",
- "public boolean includes(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public boolean deploysTo(com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
"public boolean equals(java.lang.Object)",
"public int hashCode()",
"public java.lang.String toString()"
@@ -231,6 +227,23 @@
],
"fields": []
},
+ "com.yahoo.config.application.api.DeploymentSpec$DeclaredTest": {
+ "superClass": "com.yahoo.config.application.api.DeploymentSpec$Step",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(com.yahoo.config.provision.RegionName)",
+ "public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public boolean isTest()",
+ "public com.yahoo.config.provision.RegionName region()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
+ ],
+ "fields": []
+ },
"com.yahoo.config.application.api.DeploymentSpec$DeclaredZone": {
"superClass": "com.yahoo.config.application.api.DeploymentSpec$Step",
"interfaces": [],
@@ -246,7 +259,8 @@
"public java.util.Optional testerFlavor()",
"public java.util.Optional athenzService()",
"public java.util.List zones()",
- "public boolean deploysTo(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public boolean isTest()",
"public int hashCode()",
"public boolean equals(java.lang.Object)",
"public java.lang.String toString()"
@@ -262,22 +276,21 @@
"methods": [
"public void <init>(java.time.Duration)",
"public java.time.Duration delay()",
- "public boolean deploysTo(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
"public java.lang.String toString()"
],
"fields": []
},
- "com.yahoo.config.application.api.DeploymentSpec$ParallelZones": {
- "superClass": "com.yahoo.config.application.api.DeploymentSpec$Step",
+ "com.yahoo.config.application.api.DeploymentSpec$ParallelSteps": {
+ "superClass": "com.yahoo.config.application.api.DeploymentSpec$Steps",
"interfaces": [],
"attributes": [
"public"
],
"methods": [
"public void <init>(java.util.List)",
- "public java.util.List zones()",
- "public java.util.List steps()",
- "public boolean deploysTo(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public java.time.Duration delay()",
+ "public boolean isOrdered()",
"public boolean equals(java.lang.Object)",
"public int hashCode()",
"public java.lang.String toString()"
@@ -293,11 +306,32 @@
],
"methods": [
"public void <init>()",
- "public final boolean deploysTo(com.yahoo.config.provision.Environment)",
- "public abstract boolean deploysTo(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public final boolean concerns(com.yahoo.config.provision.Environment)",
+ "public boolean deploysTo(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public abstract boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
"public java.util.List zones()",
"public java.time.Duration delay()",
- "public java.util.List steps()"
+ "public java.util.List steps()",
+ "public boolean isTest()",
+ "public boolean isOrdered()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.config.application.api.DeploymentSpec$Steps": {
+ "superClass": "com.yahoo.config.application.api.DeploymentSpec$Step",
+ "interfaces": [],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>(java.util.List)",
+ "public java.util.List zones()",
+ "public java.util.List steps()",
+ "public boolean concerns(com.yahoo.config.provision.Environment, java.util.Optional)",
+ "public java.time.Duration delay()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
+ "public java.lang.String toString()"
],
"fields": []
},
@@ -331,7 +365,6 @@
"public java.util.List steps()",
"public java.util.Optional athenzDomain()",
"public java.util.Optional athenzService()",
- "public java.util.Optional athenzService(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)",
"public java.lang.String xmlForm()",
"public java.util.Optional instance(com.yahoo.config.provision.InstanceName)",
"public com.yahoo.config.application.api.DeploymentInstanceSpec requireInstance(java.lang.String)",
@@ -572,4 +605,4 @@
"public static final com.yahoo.config.application.api.ValidationOverrides all"
]
}
-}
+} \ No newline at end of file
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
index f96400fc9a9..2c397c95d5b 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java
@@ -7,7 +7,6 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
-import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
@@ -22,12 +21,11 @@ import java.util.stream.Collectors;
*
* @author bratseth
*/
-public class DeploymentInstanceSpec extends DeploymentSpec.Step {
+public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
/** The name of the instance this step deploys */
private final InstanceName name;
- private final List<DeploymentSpec.Step> steps;
private final DeploymentSpec.UpgradePolicy upgradePolicy;
private final List<DeploymentSpec.ChangeBlocker> changeBlockers;
private final Optional<String> globalServiceId;
@@ -45,34 +43,54 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Step {
Optional<AthenzService> athenzService,
Notifications notifications,
List<Endpoint> endpoints) {
+ super(steps);
this.name = name;
- this.steps = steps;
this.upgradePolicy = upgradePolicy;
this.changeBlockers = changeBlockers;
this.globalServiceId = globalServiceId;
this.athenzDomain = athenzDomain;
this.athenzService = athenzService;
this.notifications = notifications;
- this.endpoints = List.copyOf(validateEndpoints(endpoints, this.steps));
- validateZones(this.steps);
- validateEndpoints(this.steps, globalServiceId, this.endpoints);
+ this.endpoints = List.copyOf(validateEndpoints(endpoints, steps()));
+ validateZones(new HashSet<>(), this);
+ validateEndpoints(steps(), globalServiceId, this.endpoints);
validateAthenz();
}
public InstanceName name() { return name; }
- /** Throw an IllegalArgumentException if any production zone is declared multiple times */
- private void validateZones(List<DeploymentSpec.Step> steps) {
- Set<DeploymentSpec.DeclaredZone> zones = new HashSet<>();
-
- for (DeploymentSpec.Step step : steps)
- for (DeploymentSpec.DeclaredZone zone : step.zones())
- ensureUnique(zone, zones);
- }
+ /**
+ * Throws an IllegalArgumentException if any production deployment or test is declared multiple times,
+ * or if any production test is declared not after its corresponding deployment.
+ *
+ * @param deployments previously seen deployments
+ * @param step step whose members to validate
+ * @return all contained tests
+ */
+ private static Set<RegionName> validateZones(Set<RegionName> deployments, DeploymentSpec.Step step) {
+ if ( ! step.steps().isEmpty()) {
+ Set<RegionName> oldDeployments = Set.copyOf(deployments);
+ Set<RegionName> tests = new HashSet<>();
+ for (DeploymentSpec.Step nested : step.steps()) {
+ for (RegionName test : validateZones(deployments, nested)) {
+ if ( ! (step.isOrdered() ? deployments : oldDeployments).contains(test))
+ throw new IllegalArgumentException("tests for prod." + test + " must be after the corresponding deployment in deployment.xml");
+
+ if ( ! tests.add(test))
+ throw new IllegalArgumentException("tests for prod." + test + " arelisted twice in deployment.xml");
+ }
+ }
+ return tests;
+ }
+ if (step.concerns(Environment.prod)) {
+ if (step.isTest())
+ return Set.of(((DeploymentSpec.DeclaredTest) step).region());
- private void ensureUnique(DeploymentSpec.DeclaredZone zone, Set<DeploymentSpec.DeclaredZone> zones) {
- if ( ! zones.add(zone))
- throw new IllegalArgumentException(zone + " is listed twice in deployment.xml");
+ RegionName region = ((DeploymentSpec.DeclaredZone) step).region().get();
+ if ( ! deployments.add(region))
+ throw new IllegalArgumentException("prod." + region + " is listed twice in deployment.xml");
+ }
+ return Set.of();
}
/** Validates the endpoints and makes sure default values are respected */
@@ -80,7 +98,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Step {
Objects.requireNonNull(endpoints, "Missing endpoints parameter");
var productionRegions = steps.stream()
- .filter(step -> step.deploysTo(Environment.prod))
+ .filter(step -> step.concerns(Environment.prod))
.flatMap(step -> step.zones().stream())
.flatMap(zone -> zone.region().stream())
.map(RegionName::value)
@@ -143,15 +161,6 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Step {
}
}
- @Override
- public Duration delay() {
- return Duration.ofSeconds(steps.stream().mapToLong(step -> (step.delay().getSeconds())).sum());
- }
-
- /** Returns the deployment steps inside this in the order they will be performed */
- @Override
- public List<DeploymentSpec.Step> steps() { return steps; }
-
/** Returns the upgrade policy of this, which is defaultPolicy if none is specified */
public DeploymentSpec.UpgradePolicy upgradePolicy() { return upgradePolicy; }
@@ -173,32 +182,16 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Step {
.noneMatch(block -> block.window().includes(instant));
}
- /** Returns all the deployment steps which are zones in the order they are declared */
- public List<DeploymentSpec.DeclaredZone> zones() {
- return steps.stream()
- .flatMap(step -> step.zones().stream())
- .collect(Collectors.toList());
- }
-
- /** Returns whether this deployment spec specifies the given zone, either implicitly or explicitly */
- @Override
- public boolean deploysTo(Environment environment, Optional<RegionName> region) {
- for (DeploymentSpec.Step step : steps)
- if (step.deploysTo(environment, region)) return true;
- return false;
- }
-
/** Returns the athenz domain if configured */
public Optional<AthenzDomain> athenzDomain() { return athenzDomain; }
/** Returns the athenz service for environment/region if configured */
public Optional<AthenzService> athenzService(Environment environment, RegionName region) {
- AthenzService athenzService = zones().stream()
- .filter(zone -> zone.deploysTo(environment, Optional.of(region)))
- .findFirst()
- .flatMap(DeploymentSpec.DeclaredZone::athenzService)
- .orElse(this.athenzService.orElse(null));
- return Optional.ofNullable(athenzService);
+ return zones().stream()
+ .filter(zone -> zone.concerns(environment, Optional.of(region)))
+ .findFirst()
+ .flatMap(DeploymentSpec.DeclaredZone::athenzService)
+ .or(() -> this.athenzService);
}
/** Returns the notification configuration of these instances */
@@ -207,23 +200,9 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Step {
/** Returns the rotations configuration of these instances */
public List<Endpoint> endpoints() { return endpoints; }
- /** Returns whether this instances deployment specifies the given zone, either implicitly or explicitly */
- public boolean includes(Environment environment, Optional<RegionName> region) {
- for (DeploymentSpec.Step step : steps)
- if (step.deploysTo(environment, region)) return true;
- return false;
- }
-
- DeploymentInstanceSpec withSteps(List<DeploymentSpec.Step> steps) {
- return new DeploymentInstanceSpec(name,
- steps,
- upgradePolicy,
- changeBlockers,
- globalServiceId,
- athenzDomain,
- athenzService,
- notifications,
- endpoints);
+ /** Returns whether this instance deploys to the given zone, either implicitly or explicitly */
+ public boolean deploysTo(Environment environment, RegionName region) {
+ return zones().stream().anyMatch(zone -> zone.concerns(environment, Optional.of(region)));
}
@Override
@@ -234,7 +213,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Step {
return globalServiceId.equals(other.globalServiceId) &&
upgradePolicy == other.upgradePolicy &&
changeBlockers.equals(other.changeBlockers) &&
- steps.equals(other.steps) &&
+ steps().equals(other.steps()) &&
athenzDomain.equals(other.athenzDomain) &&
athenzService.equals(other.athenzService) &&
notifications.equals(other.notifications) &&
@@ -243,7 +222,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Step {
@Override
public int hashCode() {
- return Objects.hash(globalServiceId, upgradePolicy, changeBlockers, steps, athenzDomain, athenzService, notifications, endpoints);
+ return Objects.hash(globalServiceId, upgradePolicy, changeBlockers, steps(), athenzDomain, athenzService, notifications, endpoints);
}
@Override
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 ea7df677e0f..8c05b47e8e8 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
@@ -13,10 +13,12 @@ import java.io.Reader;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Specifies the environments and regions to which an application should be deployed.
@@ -75,14 +77,14 @@ public class DeploymentSpec {
List<Step> steps = new ArrayList<>(inputSteps);
// Add staging if required and missing
- if (steps.stream().anyMatch(step -> step.deploysTo(Environment.prod)) &&
- steps.stream().noneMatch(step -> step.deploysTo(Environment.staging))) {
+ if (steps.stream().anyMatch(step -> step.concerns(Environment.prod)) &&
+ steps.stream().noneMatch(step -> step.concerns(Environment.staging))) {
steps.add(new DeploymentSpec.DeclaredZone(Environment.staging));
}
// Add test if required and missing
- if (steps.stream().anyMatch(step -> step.deploysTo(Environment.staging)) &&
- steps.stream().noneMatch(step -> step.deploysTo(Environment.test))) {
+ if (steps.stream().anyMatch(step -> step.concerns(Environment.staging)) &&
+ steps.stream().noneMatch(step -> step.concerns(Environment.test))) {
steps.add(new DeploymentSpec.DeclaredZone(Environment.test));
}
@@ -162,13 +164,6 @@ public class DeploymentSpec {
// to have environment, instance or region variants on those.
public Optional<AthenzService> athenzService() { return this.athenzService; }
- // TODO remove when 7.135 is the oldest version
- public Optional<AthenzService> athenzService(InstanceName instanceName, Environment environment, RegionName region) {
- Optional<DeploymentInstanceSpec> instance = instance(instanceName);
- if (instance.isEmpty()) return this.athenzService;
- return instance.get().athenzService(environment, region).or(() -> this.athenzService);
- }
-
/** Returns the XML form of this spec, or null if it was not created by fromXml, nor is empty */
public String xmlForm() { return xmlForm; }
@@ -205,11 +200,15 @@ public class DeploymentSpec {
private static List<DeploymentInstanceSpec> instances(List<DeploymentSpec.Step> steps) {
return steps.stream()
- .flatMap(step -> step instanceof ParallelZones ? ((ParallelZones) step).steps.stream() : List.of(step).stream())
- .filter(step -> step instanceof DeploymentInstanceSpec).map(DeploymentInstanceSpec.class::cast)
+ .flatMap(DeploymentSpec::flatten)
.collect(Collectors.toList());
}
+ private static Stream<DeploymentInstanceSpec> flatten(Step step) {
+ if (step instanceof DeploymentInstanceSpec) return Stream.of((DeploymentInstanceSpec) step);
+ return step.steps().stream().flatMap(DeploymentSpec::flatten);
+ }
+
/**
* Creates a deployment spec from XML.
*
@@ -272,23 +271,37 @@ public class DeploymentSpec {
/** A deployment step */
public abstract static class Step {
- /** Returns whether this step deploys to the given region */
- public final boolean deploysTo(Environment environment) {
- return deploysTo(environment, Optional.empty());
+ /** Returns whether this step specifies the given environment. */
+ public final boolean concerns(Environment environment) {
+ return concerns(environment, Optional.empty());
}
- /** Returns whether this step deploys to the given environment, and (if specified) region */
- public abstract boolean deploysTo(Environment environment, Optional<RegionName> region);
+ /** Returns whether this step specifies the given environment, and, optionally, region. */
+ // TODO jonmv: Remove when 7.147 is the oldest version.
+ public boolean deploysTo(Environment environment, Optional<RegionName> region) {
+ return concerns(environment, region);
+ }
+
+ /** Returns whether this step specifies the given environment, and, optionally, region. */
+ public abstract boolean concerns(Environment environment, Optional<RegionName> region);
- /** Returns the zones deployed to in this step */
+ /** Returns the zones deployed to in this step. */
public List<DeclaredZone> zones() { return Collections.emptyList(); }
- /** The delay introduced by this step (beyond the time it takes to execute the step). Default is zero. */
+ /** The delay introduced by this step (beyond the time it takes to execute the step). */
public Duration delay() { return Duration.ZERO; }
- /** Returns all the steps nested in this. This default implementatiino returns an empty list. */
+ /** Returns any steps nested in this. */
public List<Step> steps() { return List.of(); }
+ /** Returns whether this step is a test step. */
+ public boolean isTest() { return false; }
+
+ /** Returns whether the nested steps in this, if any, should be performed in declaration order. */
+ public boolean isOrdered() {
+ return true;
+ }
+
}
/** A deployment step which is to wait for some time before progressing to the next step */
@@ -304,7 +317,7 @@ public class DeploymentSpec {
public Duration delay() { return duration; }
@Override
- public boolean deploysTo(Environment environment, Optional<RegionName> region) { return false; }
+ public boolean concerns(Environment environment, Optional<RegionName> region) { return false; }
@Override
public String toString() {
@@ -355,13 +368,16 @@ public class DeploymentSpec {
public List<DeclaredZone> zones() { return Collections.singletonList(this); }
@Override
- public boolean deploysTo(Environment environment, Optional<RegionName> region) {
+ public boolean concerns(Environment environment, Optional<RegionName> region) {
if (environment != this.environment) return false;
if (region.isPresent() && ! region.equals(this.region)) return false;
return true;
}
@Override
+ public boolean isTest() { return environment.isTest(); }
+
+ @Override
public int hashCode() {
return Objects.hash(environment, region);
}
@@ -383,39 +399,82 @@ public class DeploymentSpec {
}
- /** A deployment step which is to run multiple steps (zones or instances) in parallel */
- public static class ParallelZones extends Step {
+ /** A declared production test */
+ public static class DeclaredTest extends Step {
+
+ private final RegionName region;
+
+ public DeclaredTest(RegionName region) {
+ this.region = Objects.requireNonNull(region);
+ }
+
+ @Override
+ public boolean concerns(Environment environment, Optional<RegionName> region) {
+ return region.map(this.region::equals).orElse(true) && environment == Environment.prod;
+ }
+
+ @Override
+ public boolean isTest() { return true; }
+
+ /** Returns the region this test is for. */
+ public RegionName region() {
+ return region;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DeclaredTest that = (DeclaredTest) o;
+ return region.equals(that.region);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(region);
+ }
+
+ @Override
+ public String toString() {
+ return "tests for prod." + region;
+ }
+
+ }
+
+ /** A container for several steps, by default in serial order */
+ public static class Steps extends Step {
private final List<Step> steps;
- public ParallelZones(List<Step> steps) {
+ public Steps(List<Step> steps) {
this.steps = List.copyOf(steps);
}
- /** Returns the steps inside this which are zones */
@Override
public List<DeclaredZone> zones() {
- return this.steps.stream()
- .filter(step -> step instanceof DeclaredZone)
- .map(DeclaredZone.class::cast)
- .collect(Collectors.toList());
+ return steps.stream()
+ .flatMap(step -> step.zones().stream())
+ .collect(Collectors.toUnmodifiableList());
}
- /** Returns all the steps nested in this */
@Override
public List<Step> steps() { return steps; }
@Override
- public boolean deploysTo(Environment environment, Optional<RegionName> region) {
- return steps().stream().anyMatch(zone -> zone.deploysTo(environment, region));
+ public boolean concerns(Environment environment, Optional<RegionName> region) {
+ return steps.stream().anyMatch(step -> step.concerns(environment, region));
+ }
+
+ @Override
+ public Duration delay() {
+ return steps.stream().map(Step::delay).reduce(Duration.ZERO, Duration::plus);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof ParallelZones)) return false;
- ParallelZones that = (ParallelZones) o;
- return Objects.equals(steps, that.steps);
+ if (o == null || getClass() != o.getClass()) return false;
+ return steps.equals(((Steps) o).steps);
}
@Override
@@ -425,7 +484,43 @@ public class DeploymentSpec {
@Override
public String toString() {
- return steps.size() + " parallel steps";
+ return steps.size() + " steps";
+ }
+
+ }
+
+ /** A container for multiple other steps, which are executed in parallel */
+ public static class ParallelSteps extends Steps {
+
+ public ParallelSteps(List<Step> steps) {
+ super(steps);
+ }
+
+ @Override
+ public Duration delay() {
+ return steps().stream().map(Step::delay).max(Comparator.naturalOrder()).orElse(Duration.ZERO);
+ }
+
+ @Override
+ public boolean isOrdered() {
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if ( ! (o instanceof ParallelSteps)) return false;
+ return Objects.equals(steps(), ((ParallelSteps) o).steps());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(steps());
+ }
+
+ @Override
+ public String toString() {
+ return steps().size() + " parallel steps";
}
}
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 bc17ee0cb2b..c14e6ce5966 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
@@ -3,10 +3,12 @@ package com.yahoo.config.application.api.xml;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.application.api.DeploymentSpec.DeclaredTest;
import com.yahoo.config.application.api.DeploymentSpec.DeclaredZone;
import com.yahoo.config.application.api.DeploymentSpec.Delay;
-import com.yahoo.config.application.api.DeploymentSpec.ParallelZones;
+import com.yahoo.config.application.api.DeploymentSpec.ParallelSteps;
import com.yahoo.config.application.api.DeploymentSpec.Step;
+import com.yahoo.config.application.api.DeploymentSpec.Steps;
import com.yahoo.config.application.api.Endpoint;
import com.yahoo.config.application.api.Notifications;
import com.yahoo.config.application.api.Notifications.Role;
@@ -20,6 +22,7 @@ import com.yahoo.config.provision.RegionName;
import com.yahoo.io.IOUtils;
import com.yahoo.text.XML;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import java.io.IOException;
import java.io.Reader;
@@ -32,8 +35,10 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* @author bratseth
@@ -50,6 +55,7 @@ public class DeploymentSpecXmlReader {
private static final String regionTag = "region";
private static final String delayTag = "delay";
private static final String parallelTag = "parallel";
+ private static final String stepsTag = "steps";
private static final String endpointsTag = "endpoints";
private static final String endpointTag = "endpoint";
private static final String notificationsTag = "notifications";
@@ -118,7 +124,7 @@ public class DeploymentSpecXmlReader {
*
* @param instanceNameString a comma-separated list of the names of the instances this is for
* @param instanceTag the element having the content of this instance
- * @param parentTag the parent of instanceTag (or the same, if this instances is implicitly defined which means instanceTag is the root)
+ * @param parentTag the parent of instanceTag (or the same, if this instance is implicitly defined, which means instanceTag is the root)
* @return the instances specified, one for each instance name element
*/
private List<DeploymentInstanceSpec> readInstanceContent(String instanceNameString,
@@ -169,6 +175,7 @@ public class DeploymentSpecXmlReader {
}
// Consume the given tag as 0-N steps. 0 if it is not a step, >1 if it contains multiple nested steps that should be flattened
+ @SuppressWarnings("fallthrough")
private List<Step> readNonInstanceSteps(Element stepTag, MutableOptional<String> globalServiceId, Element parentTag) {
Optional<AthenzService> athenzService = stringAttribute(athenzServiceAttribute, stepTag)
.or(() -> stringAttribute(athenzServiceAttribute, parentTag))
@@ -182,7 +189,11 @@ public class DeploymentSpecXmlReader {
throw new IllegalArgumentException("Attribute 'global-service-id' is only valid on 'prod' tag.");
switch (stepTag.getTagName()) {
- case testTag: case stagingTag:
+ case testTag:
+ if (Stream.iterate(stepTag, Objects::nonNull, Node::getParentNode)
+ .anyMatch(node -> prodTag.equals(node.getNodeName())))
+ return List.of(new DeclaredTest(RegionName.from(XML.getValue(stepTag).trim())));
+ case stagingTag:
return List.of(new DeclaredZone(Environment.from(stepTag.getTagName()), Optional.empty(), false, athenzService, testerFlavor));
case prodTag: // regions, delay and parallel may be nested within, but we can flatten them
return XML.getChildren(stepTag).stream()
@@ -193,9 +204,13 @@ public class DeploymentSpecXmlReader {
longAttribute("minutes", stepTag) * 60 +
longAttribute("seconds", stepTag))));
case parallelTag: // regions and instances may be nested within
- return List.of(new ParallelZones(XML.getChildren(stepTag).stream()
+ return List.of(new ParallelSteps(XML.getChildren(stepTag).stream()
.flatMap(child -> readSteps(child, globalServiceId, parentTag).stream())
.collect(Collectors.toList())));
+ case stepsTag: // regions and instances may be nested within
+ return List.of(new Steps(XML.getChildren(stepTag).stream()
+ .flatMap(child -> readSteps(child, globalServiceId, parentTag).stream())
+ .collect(Collectors.toList())));
case regionTag:
return List.of(readDeclaredZone(Environment.prod, athenzService, testerFlavor, stepTag));
default:
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 987e13c0e86..584664b6ef1 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
@@ -3,11 +3,11 @@ package com.yahoo.config.application.api;
import com.google.common.collect.ImmutableSet;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import org.junit.Test;
import java.io.StringReader;
+import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Collections;
@@ -42,11 +42,11 @@ public class DeploymentSpecTest {
assertEquals(specXml, spec.xmlForm());
assertEquals(1, spec.requireInstance("default").steps().size());
assertFalse(spec.majorVersion().isPresent());
- assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.test));
- assertTrue(spec.requireInstance("default").includes(Environment.test, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
- assertFalse(spec.requireInstance("default").includes(Environment.staging, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.empty()));
+ assertTrue(spec.requireInstance("default").steps().get(0).concerns(Environment.test));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.test, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.staging, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.prod, Optional.empty()));
assertFalse(spec.requireInstance("default").globalServiceId().isPresent());
}
@@ -79,11 +79,11 @@ public class DeploymentSpecTest {
DeploymentSpec spec = DeploymentSpec.fromXml(r);
assertEquals(2, spec.steps().size());
assertEquals(1, spec.requireInstance("default").steps().size());
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
- assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.staging));
- assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.empty()));
- assertTrue(spec.requireInstance("default").includes(Environment.staging, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.empty()));
+ assertTrue(spec.steps().get(0).concerns(Environment.test));
+ assertTrue(spec.requireInstance("default").steps().get(0).concerns(Environment.staging));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.test, Optional.empty()));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.staging, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.prod, Optional.empty()));
assertFalse(spec.requireInstance("default").globalServiceId().isPresent());
}
@@ -104,21 +104,21 @@ public class DeploymentSpecTest {
assertEquals(3, spec.steps().size());
assertEquals(2, spec.requireInstance("default").steps().size());
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.steps().get(0).concerns(Environment.test));
- assertTrue(spec.steps().get(1).deploysTo(Environment.staging));
+ assertTrue(spec.steps().get(1).concerns(Environment.staging));
- assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.requireInstance("default").steps().get(0).concerns(Environment.prod, Optional.of(RegionName.from("us-east1"))));
assertFalse(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(0)).active());
- assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(spec.requireInstance("default").steps().get(1).concerns(Environment.prod, Optional.of(RegionName.from("us-west1"))));
assertTrue(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(1)).active());
- assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.staging, Optional.empty()));
- assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
- assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
- assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.test, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.staging, Optional.empty()));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
assertFalse(spec.requireInstance("default").globalServiceId().isPresent());
assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, spec.requireInstance("default").upgradePolicy());
@@ -145,6 +145,84 @@ public class DeploymentSpecTest {
}
@Test
+ public void productionTests() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='default'>" +
+ " <test/>" +
+ " <staging/>" +
+ " <prod>" +
+ " <region active='false'>us-east-1</region>" +
+ " <region active='true'>us-west-1</region>" +
+ " <delay hours='1' />" +
+ " <test>us-west-1</test>" +
+ " <test>us-east-1</test>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ List<DeploymentSpec.Step> instanceSteps = spec.steps().get(0).steps();
+ assertEquals(7, instanceSteps.size());
+ assertEquals("test", instanceSteps.get(0).toString());
+ assertEquals("staging", instanceSteps.get(1).toString());
+ assertEquals("prod.us-east-1", instanceSteps.get(2).toString());
+ assertEquals("prod.us-west-1", instanceSteps.get(3).toString());
+ assertEquals("delay PT1H", instanceSteps.get(4).toString());
+ assertEquals("tests for prod.us-west-1", instanceSteps.get(5).toString());
+ assertEquals("tests for prod.us-east-1", instanceSteps.get(6).toString());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void duplicateProductionTest() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <region active='true'>us-east1</region>" +
+ " <test>us-east1</test>" +
+ " <test>us-east1</test>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void productionTestBeforeDeployment() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <test>us-east1</test>" +
+ " <region active='true'>us-east1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void productionTestInParallelWithDeployment() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <parallel>" +
+ " <region active='true'>us-east1</region>" +
+ " <test>us-east1</test>" +
+ " </parallel>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test
public void maximalProductionSpecMultipleInstances() {
StringReader r = new StringReader(
"<deployment version='1.0'>" +
@@ -173,7 +251,7 @@ public class DeploymentSpecTest {
assertEquals(1, instance2.steps().size());
assertEquals(1, instance2.zones().size());
- assertTrue(instance2.steps().get(0).deploysTo(Environment.prod, Optional.of(RegionName.from("us-central1"))));
+ assertTrue(instance2.steps().get(0).concerns(Environment.prod, Optional.of(RegionName.from("us-central1"))));
}
@Test
@@ -202,25 +280,25 @@ public class DeploymentSpecTest {
assertEquals(5, instance.steps().size());
assertEquals(4, instance.zones().size());
- assertTrue(instance.steps().get(0).deploysTo(Environment.test));
+ assertTrue(instance.steps().get(0).concerns(Environment.test));
- assertTrue(instance.steps().get(1).deploysTo(Environment.staging));
+ assertTrue(instance.steps().get(1).concerns(Environment.staging));
- assertTrue(instance.steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(instance.steps().get(2).concerns(Environment.prod, Optional.of(RegionName.from("us-east1"))));
assertFalse(((DeploymentSpec.DeclaredZone)instance.steps().get(2)).active());
assertTrue(instance.steps().get(3) instanceof DeploymentSpec.Delay);
assertEquals(3 * 60 * 60 + 30 * 60, instance.steps().get(3).delay().getSeconds());
- assertTrue(instance.steps().get(4).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(instance.steps().get(4).concerns(Environment.prod, Optional.of(RegionName.from("us-west1"))));
assertTrue(((DeploymentSpec.DeclaredZone)instance.steps().get(4)).active());
- assertTrue(instance.includes(Environment.test, Optional.empty()));
- assertFalse(instance.includes(Environment.test, Optional.of(RegionName.from("region1"))));
- assertTrue(instance.includes(Environment.staging, Optional.empty()));
- assertTrue(instance.includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
- assertTrue(instance.includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
- assertFalse(instance.includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertTrue(instance.deploysTo(Environment.test, Optional.empty()));
+ assertFalse(instance.deploysTo(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(instance.deploysTo(Environment.staging, Optional.empty()));
+ assertTrue(instance.deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(instance.deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(instance.deploysTo(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
assertFalse(instance.globalServiceId().isPresent());
}
@@ -372,10 +450,10 @@ public class DeploymentSpecTest {
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.requireInstance("default").steps().get(1));
- 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());
+ DeploymentSpec.ParallelSteps parallelSteps = ((DeploymentSpec.ParallelSteps) spec.requireInstance("default").steps().get(1));
+ assertEquals(2, parallelSteps.zones().size());
+ assertEquals(RegionName.from("us-central-1"), parallelSteps.zones().get(0).region().get());
+ assertEquals(RegionName.from("us-east-3"), parallelSteps.zones().get(1).region().get());
}
@Test
@@ -419,6 +497,77 @@ public class DeploymentSpecTest {
}
@Test
+ public void testNestedParallelAndSteps() {
+ StringReader r = new StringReader(
+ "<deployment athenz-domain='domain'>" +
+ " <instance id='instance' athenz-service='in-service'>" +
+ " <prod>" +
+ " <parallel>" +
+ " <region active='true'>us-west-1</region>" +
+ " <steps>" +
+ " <region active='true'>us-east-3</region>" +
+ " <delay hours='2' />" +
+ " <region active='true'>eu-west-1</region>" +
+ " <delay hours='2' />" +
+ " </steps>" +
+ " <steps>" +
+ " <delay hours='3' />" +
+ " <region active='true'>aws-us-east-1a</region>" +
+ " <parallel>" +
+ " <region active='true' athenz-service='no-service'>ap-northeast-1</region>" +
+ " <region active='true'>ap-southeast-2</region>" +
+ " </parallel>" +
+ " </steps>" +
+ " <delay hours='3' minutes='30' />" +
+ " </parallel>" +
+ " <region active='true'>us-north-7</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ List<DeploymentSpec.Step> steps = spec.steps();
+ assertEquals(3, steps.size());
+ assertEquals("test", steps.get(0).toString());
+ assertEquals("staging", steps.get(1).toString());
+ assertEquals("instance 'instance'", steps.get(2).toString());
+ assertEquals(Duration.ofHours(4), steps.get(2).delay());
+
+ List<DeploymentSpec.Step> instanceSteps = steps.get(2).steps();
+ assertEquals(2, instanceSteps.size());
+ assertEquals("4 parallel steps", instanceSteps.get(0).toString());
+ assertEquals("prod.us-north-7", instanceSteps.get(1).toString());
+
+ List<DeploymentSpec.Step> parallelSteps = instanceSteps.get(0).steps();
+ assertEquals(4, parallelSteps.size());
+ assertEquals("prod.us-west-1", parallelSteps.get(0).toString());
+ assertEquals("4 steps", parallelSteps.get(1).toString());
+ assertEquals("3 steps", parallelSteps.get(2).toString());
+ assertEquals("delay PT3H30M", parallelSteps.get(3).toString());
+
+ List<DeploymentSpec.Step> firstSerialSteps = parallelSteps.get(1).steps();
+ assertEquals(4, firstSerialSteps.size());
+ assertEquals("prod.us-east-3", firstSerialSteps.get(0).toString());
+ assertEquals("delay PT2H", firstSerialSteps.get(1).toString());
+ assertEquals("prod.eu-west-1", firstSerialSteps.get(2).toString());
+ assertEquals("delay PT2H", firstSerialSteps.get(3).toString());
+
+ List<DeploymentSpec.Step> secondSerialSteps = parallelSteps.get(2).steps();
+ assertEquals(3, secondSerialSteps.size());
+ assertEquals("delay PT3H", secondSerialSteps.get(0).toString());
+ assertEquals("prod.aws-us-east-1a", secondSerialSteps.get(1).toString());
+ assertEquals("2 parallel steps", secondSerialSteps.get(2).toString());
+
+ List<DeploymentSpec.Step> innerParallelSteps = secondSerialSteps.get(2).steps();
+ assertEquals(2, innerParallelSteps.size());
+ assertEquals("prod.ap-northeast-1", innerParallelSteps.get(0).toString());
+ assertEquals("no-service", spec.requireInstance("instance").athenzService(Environment.prod, RegionName.from("ap-northeast-1")).get().value());
+ assertEquals("prod.ap-southeast-2", innerParallelSteps.get(1).toString());
+ assertEquals("in-service", spec.requireInstance("instance").athenzService(Environment.prod, RegionName.from("ap-southeast-2")).get().value());
+ }
+
+ @Test
public void testParallelInstances() {
StringReader r = new StringReader(
"<deployment>" +
@@ -486,8 +635,8 @@ public class DeploymentSpecTest {
" <region active='true'>us-west-1</region>" +
" <parallel>" +
" <region active='true'>us-west-1</region>" +
- " <region active='true'>us-central-1</region>" +
- " <region active='true'>us-east-3</region>" +
+ " <region active='true'>us-central-1</region>" +
+ " <region active='true'>us-east-3</region>" +
" </parallel>" +
" </prod>" +
" </instance>" +
diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java
index 31a30e5bd83..13ec7bf1a8b 100644
--- a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java
+++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java
@@ -3,11 +3,11 @@ package com.yahoo.config.application.api;
import com.google.common.collect.ImmutableSet;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
import org.junit.Test;
import java.io.StringReader;
+import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Collections;
@@ -40,11 +40,11 @@ public class DeploymentSpecWithoutInstanceTest {
assertEquals(specXml, spec.xmlForm());
assertEquals(1, spec.steps().size());
assertFalse(spec.majorVersion().isPresent());
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
- assertTrue(spec.requireInstance("default").includes(Environment.test, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
- assertFalse(spec.requireInstance("default").includes(Environment.staging, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.empty()));
+ assertTrue(spec.steps().get(0).concerns(Environment.test));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.test, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.staging, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.prod, Optional.empty()));
assertFalse(spec.requireInstance("default").globalServiceId().isPresent());
}
@@ -73,11 +73,11 @@ public class DeploymentSpecWithoutInstanceTest {
DeploymentSpec spec = DeploymentSpec.fromXml(r);
assertEquals(2, spec.steps().size());
assertEquals(1, spec.requireInstance("default").steps().size());
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
- assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.staging));
- assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.empty()));
- assertTrue(spec.requireInstance("default").includes(Environment.staging, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.empty()));
+ assertTrue(spec.steps().get(0).concerns(Environment.test));
+ assertTrue(spec.requireInstance("default").steps().get(0).concerns(Environment.staging));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.test, Optional.empty()));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.staging, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.prod, Optional.empty()));
assertFalse(spec.requireInstance("default").globalServiceId().isPresent());
}
@@ -96,21 +96,21 @@ public class DeploymentSpecWithoutInstanceTest {
assertEquals(3, spec.steps().size());
assertEquals(2, spec.requireInstance("default").steps().size());
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.steps().get(0).concerns(Environment.test));
- assertTrue(spec.steps().get(1).deploysTo(Environment.staging));
+ assertTrue(spec.steps().get(1).concerns(Environment.staging));
- assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.requireInstance("default").steps().get(0).concerns(Environment.prod, Optional.of(RegionName.from("us-east1"))));
assertFalse(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(0)).active());
- assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(spec.requireInstance("default").steps().get(1).concerns(Environment.prod, Optional.of(RegionName.from("us-west1"))));
assertTrue(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(1)).active());
- assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.staging, Optional.empty()));
- assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
- assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
- assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.test, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.staging, Optional.empty()));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
assertFalse(spec.requireInstance("default").globalServiceId().isPresent());
assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, spec.requireInstance("default").upgradePolicy());
@@ -134,29 +134,99 @@ public class DeploymentSpecWithoutInstanceTest {
assertEquals(5, spec.requireInstance("default").steps().size());
assertEquals(4, spec.requireInstance("default").zones().size());
- assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.requireInstance("default").steps().get(0).concerns(Environment.test));
- assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.staging));
+ assertTrue(spec.requireInstance("default").steps().get(1).concerns(Environment.staging));
- assertTrue(spec.requireInstance("default").steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.requireInstance("default").steps().get(2).concerns(Environment.prod, Optional.of(RegionName.from("us-east1"))));
assertFalse(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(2)).active());
assertTrue(spec.requireInstance("default").steps().get(3) instanceof DeploymentSpec.Delay);
assertEquals(3 * 60 * 60 + 30 * 60, spec.requireInstance("default").steps().get(3).delay().getSeconds());
- assertTrue(spec.requireInstance("default").steps().get(4).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(spec.requireInstance("default").steps().get(4).concerns(Environment.prod, Optional.of(RegionName.from("us-west1"))));
assertTrue(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(4)).active());
- assertTrue(spec.requireInstance("default").includes(Environment.test, Optional.empty()));
- assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
- assertTrue(spec.requireInstance("default").includes(Environment.staging, Optional.empty()));
- assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
- assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
- assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.test, Optional.empty()));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.staging, Optional.empty()));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(spec.requireInstance("default").deploysTo(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
assertFalse(spec.requireInstance("default").globalServiceId().isPresent());
}
@Test
+ public void productionTests() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <test/>" +
+ " <staging/>" +
+ " <prod>" +
+ " <region active='false'>us-east-1</region>" +
+ " <region active='true'>us-west-1</region>" +
+ " <delay hours='1' />" +
+ " <test>us-west-1</test>" +
+ " <test>us-east-1</test>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ List<DeploymentSpec.Step> instanceSteps = spec.steps().get(0).steps();
+ assertEquals(7, instanceSteps.size());
+ assertEquals("test", instanceSteps.get(0).toString());
+ assertEquals("staging", instanceSteps.get(1).toString());
+ assertEquals("prod.us-east-1", instanceSteps.get(2).toString());
+ assertEquals("prod.us-west-1", instanceSteps.get(3).toString());
+ assertEquals("delay PT1H", instanceSteps.get(4).toString());
+ assertEquals("tests for prod.us-west-1", instanceSteps.get(5).toString());
+ assertEquals("tests for prod.us-east-1", instanceSteps.get(6).toString());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void duplicateProductionTests() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <prod>" +
+ " <region active='true'>us-east1</region>" +
+ " <test>us-east1</test>" +
+ " <test>us-east1</test>" +
+ " </prod>" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void productionTestBeforeDeployment() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <prod>" +
+ " <test>us-east1</test>" +
+ " <region active='true'>us-east1</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void productionTestInParallelWithDeployment() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <prod>" +
+ " <parallel>" +
+ " <region active='true'>us-east1</region>" +
+ " <test>us-east1</test>" +
+ " </parallel>" +
+ " </prod>" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test
public void productionSpecWithGlobalServiceId() {
StringReader r = new StringReader(
"<deployment version='1.0'>" +
@@ -272,10 +342,79 @@ public class DeploymentSpecWithoutInstanceTest {
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.requireInstance("default").steps().get(1));
- 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());
+ DeploymentSpec.ParallelSteps parallelSteps = ((DeploymentSpec.ParallelSteps) spec.requireInstance("default").steps().get(1));
+ assertEquals(2, parallelSteps.zones().size());
+ assertEquals(RegionName.from("us-central-1"), parallelSteps.zones().get(0).region().get());
+ assertEquals(RegionName.from("us-east-3"), parallelSteps.zones().get(1).region().get());
+ }
+
+ @Test
+ public void testNestedParallelAndSteps() {
+ StringReader r = new StringReader(
+ "<deployment athenz-domain='domain' athenz-service='service'>" +
+ " <prod>" +
+ " <parallel>" +
+ " <region active='true'>us-west-1</region>" +
+ " <steps>" +
+ " <region active='true'>us-east-3</region>" +
+ " <delay hours='2' />" +
+ " <region active='true'>eu-west-1</region>" +
+ " <delay hours='2' />" +
+ " </steps>" +
+ " <steps>" +
+ " <delay hours='3' />" +
+ " <region active='true'>aws-us-east-1a</region>" +
+ " <parallel>" +
+ " <region active='true' athenz-service='no-service'>ap-northeast-1</region>" +
+ " <region active='true'>ap-southeast-2</region>" +
+ " </parallel>" +
+ " </steps>" +
+ " <delay hours='3' minutes='30' />" +
+ " </parallel>" +
+ " <region active='true'>us-north-7</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ List<DeploymentSpec.Step> steps = spec.steps();
+ assertEquals(3, steps.size());
+ assertEquals("test", steps.get(0).toString());
+ assertEquals("staging", steps.get(1).toString());
+ assertEquals("instance 'default'", steps.get(2).toString());
+ assertEquals(Duration.ofHours(4), steps.get(2).delay());
+
+ List<DeploymentSpec.Step> instanceSteps = steps.get(2).steps();
+ assertEquals(2, instanceSteps.size());
+ assertEquals("4 parallel steps", instanceSteps.get(0).toString());
+ assertEquals("prod.us-north-7", instanceSteps.get(1).toString());
+
+ List<DeploymentSpec.Step> parallelSteps = instanceSteps.get(0).steps();
+ assertEquals(4, parallelSteps.size());
+ assertEquals("prod.us-west-1", parallelSteps.get(0).toString());
+ assertEquals("4 steps", parallelSteps.get(1).toString());
+ assertEquals("3 steps", parallelSteps.get(2).toString());
+ assertEquals("delay PT3H30M", parallelSteps.get(3).toString());
+
+ List<DeploymentSpec.Step> firstSerialSteps = parallelSteps.get(1).steps();
+ assertEquals(4, firstSerialSteps.size());
+ assertEquals("prod.us-east-3", firstSerialSteps.get(0).toString());
+ assertEquals("delay PT2H", firstSerialSteps.get(1).toString());
+ assertEquals("prod.eu-west-1", firstSerialSteps.get(2).toString());
+ assertEquals("delay PT2H", firstSerialSteps.get(3).toString());
+
+ List<DeploymentSpec.Step> secondSerialSteps = parallelSteps.get(2).steps();
+ assertEquals(3, secondSerialSteps.size());
+ assertEquals("delay PT3H", secondSerialSteps.get(0).toString());
+ assertEquals("prod.aws-us-east-1a", secondSerialSteps.get(1).toString());
+ assertEquals("2 parallel steps", secondSerialSteps.get(2).toString());
+
+ List<DeploymentSpec.Step> innerParallelSteps = secondSerialSteps.get(2).steps();
+ assertEquals(2, innerParallelSteps.size());
+ assertEquals("prod.ap-northeast-1", innerParallelSteps.get(0).toString());
+ assertEquals("no-service", spec.requireInstance("default").athenzService(Environment.prod, RegionName.from("ap-northeast-1")).get().value());
+ assertEquals("prod.ap-southeast-2", innerParallelSteps.get(1).toString());
+ assertEquals("service", spec.requireInstance("default").athenzService(Environment.prod, RegionName.from("ap-southeast-2")).get().value());
}
@Test
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
index e5e22c3a260..188a9b4765c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
@@ -236,7 +236,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
Optional<DeploymentInstanceSpec> instance = spec.instance(app.getApplicationId().instance());
if (instance.isEmpty()) return false;
return instance.get().zones().stream()
- .anyMatch(declaredZone -> declaredZone.deploysTo(zone.environment(), Optional.of(zone.region())) &&
+ .anyMatch(declaredZone -> declaredZone.concerns(zone.environment(), Optional.of(zone.region())) &&
declaredZone.active());
}
diff --git a/config-model/src/main/resources/schema/deployment.rnc b/config-model/src/main/resources/schema/deployment.rnc
index 1e1d9ad3aa9..6a8bc8f77b9 100644
--- a/config-model/src/main/resources/schema/deployment.rnc
+++ b/config-model/src/main/resources/schema/deployment.rnc
@@ -25,6 +25,11 @@ StepExceptInstance =
Staging? &
Prod*
+PrimitiveStep =
+ Delay* &
+ Region* &
+ ProdTest*
+
Instance = element instance {
attribute id { xsd:string } &
attribute athenz-domain { xsd:string }? &
@@ -32,8 +37,14 @@ Instance = element instance {
StepExceptInstance
}
-ParallelRegions = element parallel {
- Region*
+ParallelSteps = element parallel {
+ SerialSteps* &
+ PrimitiveStep*
+}
+
+SerialSteps = element steps {
+ ParallelSteps* &
+ PrimitiveStep*
}
ParallelInstances = element parallel {
@@ -81,11 +92,17 @@ Prod = element prod {
attribute tester-flavor { xsd:string }? &
Region* &
Delay* &
- ParallelRegions*
+ ProdTest* &
+ ParallelSteps*
+}
+
+ProdTest = element test {
+ text
}
Region = element region {
attribute active { xsd:boolean } &
+ attribute athenz-service { xsd:string }? &
text
}
diff --git a/config-model/src/test/cfg/application/app_complicated_deployment_spec/deployment.xml b/config-model/src/test/cfg/application/app_complicated_deployment_spec/deployment.xml
new file mode 100644
index 00000000000..36c1d449021
--- /dev/null
+++ b/config-model/src/test/cfg/application/app_complicated_deployment_spec/deployment.xml
@@ -0,0 +1,48 @@
+<deployment version='1.0' athenz-domain='domain'>
+ <staging />
+ <parallel>
+ <instance id='instance' athenz-service='in-service'>
+ <prod>
+ <parallel>
+ <region active='true'>us-west-1</region>
+ <steps>
+ <region active='true'>us-east-3</region>
+ <delay hours='2' />
+ <region active='true'>eu-west-1</region>
+ <delay hours='2' />
+ </steps>
+ <steps>
+ <delay hours='3' />
+ <region active='true'>aws-us-east-1a</region>
+ <parallel>
+ <region active='true' athenz-service='no-service'>ap-northeast-1</region>
+ <region active='true'>ap-southeast-2</region>
+ </parallel>
+ </steps>
+ <delay hours='3' minutes='30' />
+ </parallel>
+ <region active='true'>us-north-7</region>
+ </prod>
+ </instance>
+ <instance id='other'>
+ <upgrade policy='conservative' />
+ <block-change days='sat' hours='10' time-zone='CET' />
+ <test />
+ <prod>
+ <region active='true'>eu-central-2</region>
+ </prod>
+ <notifications when='failing'>
+ <email role='author' />
+ <email address='john@dev' when='failing-commit' />
+ <email address='jane@dev' />
+ </notifications>
+ <endpoints>
+ <endpoint id='foo' container-id='bar'>
+ <region>us-east</region>
+ </endpoint>
+ <endpoint id='nalle' container-id='frosk' />
+ <endpoint container-id='quux' />
+ </endpoints>
+ </instance>
+ </parallel>
+</deployment>
diff --git a/config-model/src/test/cfg/application/app_complicated_deployment_spec/hosts.xml b/config-model/src/test/cfg/application/app_complicated_deployment_spec/hosts.xml
new file mode 100644
index 00000000000..a7fc725d377
--- /dev/null
+++ b/config-model/src/test/cfg/application/app_complicated_deployment_spec/hosts.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<hosts>
+ <host name="bogusname1">
+ <alias>node1</alias>
+ </host>
+ <host name="bogusname2">
+ <alias>node2</alias>
+ </host>
+ <host name="bogusname3">
+ <alias>node3</alias>
+ </host>
+ <host name="bogusname4">
+ <alias>node4</alias>
+ </host>
+ <host name="bogusname5">
+ <alias>node5</alias>
+ </host>
+
+</hosts>
+
diff --git a/config-model/src/test/cfg/application/app_complicated_deployment_spec/searchdefinitions/music.sd b/config-model/src/test/cfg/application/app_complicated_deployment_spec/searchdefinitions/music.sd
new file mode 100644
index 00000000000..693afbd308d
--- /dev/null
+++ b/config-model/src/test/cfg/application/app_complicated_deployment_spec/searchdefinitions/music.sd
@@ -0,0 +1,37 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# A basic search definition - called music, should be saved to music.sd
+search music {
+
+ # It contains one document type only - called music as well
+ document music {
+
+ field title type string {
+ indexing: summary | index # How this field should be indexed
+ # index-to: title, default # Create two indexes
+ rank-type: about # Type of ranking settings to apply
+ }
+
+ field artist type string {
+ indexing: summary | attribute | index
+ # index-to: artist, default
+ rank-type:about
+ }
+
+ field year type int {
+ indexing: summary | attribute
+ }
+
+ # Increase rank score of popular documents regardless of query
+ field popularity type int {
+ indexing: summary | attribute
+ }
+
+ field url type uri {
+ indexing: summary | index
+ }
+
+ field cover type raw {}
+
+ }
+
+}
diff --git a/config-model/src/test/cfg/application/app_complicated_deployment_spec/services.xml b/config-model/src/test/cfg/application/app_complicated_deployment_spec/services.xml
new file mode 100644
index 00000000000..9ae7f122e81
--- /dev/null
+++ b/config-model/src/test/cfg/application/app_complicated_deployment_spec/services.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<services version="1.0">
+
+ <service version="1.0" name="myservice" command="mycmd1.sh">
+ <config name="a.myconfig">
+ <mysetting>bar</mysetting>
+ </config>
+ <node hostalias="node1">
+ <config name="a.myconfig">
+ <mysetting>baz</mysetting>
+ </config>
+ </node>
+ <node hostalias="node2"/>
+ <node hostalias="node3"/>
+ <node hostalias="node3"/>
+ </service>
+
+ <service version="1.0" name="myotherservice" command="/home/vespa/bin/mycmd2.sh --ytest $FOO_BAR">
+ <config name="a.myconfig">
+ <mysetting>bar2</mysetting>
+ </config>
+ <node hostalias="node3">
+ <config name="a.myconfig">
+ <mysetting>baz2</mysetting>
+ </config>
+ </node>
+ <node hostalias="node4"/>
+ <node hostalias="node5"/>
+ </service>
+
+ <admin version="2.0">
+ <adminserver hostalias="node1"/>
+ <slobroks>
+ <slobrok hostalias="node1"/>
+ <slobrok hostalias="node2"/>
+ </slobroks>
+ </admin>
+
+ <container version="1.0">
+ <nodes>
+ <node hostalias="node1" />
+ </nodes>
+
+ <search/>
+ <document-api/>
+ </container>
+
+ <content id="music" version="1.0">
+ <redundancy>1</redundancy>
+ <documents>
+ <document type="music" mode="index" />
+ </documents>
+ <nodes>
+ <node hostalias="node1" distribution-key="0" />
+ </nodes>
+ </content>
+
+</services>
diff --git a/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml b/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml
index 2f6e9b7a1a6..8e13f0abb53 100644
--- a/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml
+++ b/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml
@@ -4,9 +4,7 @@
<staging/>
<prod global-service-id="query">
<parallel>
- <region active="true">us-east-3</region>
- <delay hours="1"/>
- <region active="true">us-west-1</region>
+ <instance id="hello" />
</parallel>
</prod>
</deployment>
diff --git a/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java b/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
index 8a8b23bb0c8..cb1577417b4 100644
--- a/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
+++ b/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
@@ -212,7 +212,14 @@ public class ApplicationDeployTest {
}
@Test
- public void testThatAppWithIllegalEmptyProdRegion() throws IOException {
+ public void testComplicatedDeploymentSpec() throws IOException {
+ File tmpDir = tmpFolder.getRoot();
+ IOUtils.copyDirectory(new File(TESTDIR, "app_complicated_deployment_spec"), tmpDir);
+ ApplicationPackageTester.create(tmpDir.getAbsolutePath());
+ }
+
+ @Test
+ public void testAppWithEmptyProdRegion() throws IOException {
File tmpDir = tmpFolder.getRoot();
IOUtils.copyDirectory(new File(TESTDIR, "empty_prod_region_in_deployment_xml"), tmpDir);
ApplicationPackageTester.create(tmpDir.getAbsolutePath());
@@ -226,7 +233,7 @@ public class ApplicationDeployTest {
ApplicationPackageTester.create(tmpDir.getAbsolutePath());
fail("Expected exception");
} catch (IllegalArgumentException e) {
- assertEquals("XML error in deployment.xml: element \"delay\" not allowed here; expected the element end-tag or element \"region\" [8:25], input:\n", e.getMessage());
+ assertEquals("XML error in deployment.xml: element \"instance\" not allowed here; expected the element end-tag or element \"delay\", \"region\", \"steps\" or \"test\" [7:30], input:\n", e.getMessage());
}
}
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 3f985e95a4f..72e2f073453 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
@@ -19,9 +19,6 @@ import com.yahoo.vespa.athenz.api.AthenzPrincipal;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.curator.Lock;
-import com.yahoo.vespa.flags.BooleanFlag;
-import com.yahoo.vespa.flags.FetchVector;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.controller.api.ActivateResult;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeployOptions;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.EndpointStatus;
@@ -456,7 +453,7 @@ public class ApplicationController {
}
for (InstanceName instance : declaredInstances)
- if (applicationPackage.deploymentSpec().requireInstance(instance).deploysTo(Environment.prod))
+ if (applicationPackage.deploymentSpec().requireInstance(instance).concerns(Environment.prod))
application = withRotation(applicationPackage.deploymentSpec(), application, instance);
store(application);
@@ -616,8 +613,8 @@ public class ApplicationController {
List<ZoneId> deploymentsToRemove = application.get().require(instance).productionDeployments().values().stream()
.map(Deployment::zone)
.filter(zone -> deploymentSpec.instance(instance).isEmpty()
- || ! deploymentSpec.requireInstance(instance).includes(zone.environment(),
- Optional.of(zone.region())))
+ || ! deploymentSpec.requireInstance(instance).deploysTo(zone.environment(),
+ zone.region()))
.collect(Collectors.toList());
if (deploymentsToRemove.isEmpty())
@@ -647,7 +644,8 @@ public class ApplicationController {
for (JobType job : JobList.from(instance).production().mapToList(JobStatus::type)) {
ZoneId zone = job.zone(controller.system());
if (deploymentSpec.instance(instance.name())
- .map(spec -> spec.includes(zone.environment(), Optional.of(zone.region())))
+ // TODO jonmv: Properly convert to job here.
+ .map(spec -> spec.deploysTo(zone.environment(), zone.region()))
.orElse(false))
continue;
instance = instance.withoutDeploymentJob(job);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java
index 1b722c80ec1..ddfcf17b0be 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.controller.deployment;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
@@ -91,7 +90,7 @@ public class DeploymentSteps {
}
private boolean isTest(DeploymentSpec.Step step) {
- return step.deploysTo(Environment.test) || step.deploysTo(Environment.staging);
+ return step.concerns(Environment.test) || step.concerns(Environment.staging);
}
/** Resolve job from deployment zone */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index 972e96e94f2..a5a7f6940a3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -700,7 +700,7 @@ public class InternalStepRunner implements StepRunner {
private static Optional<String> testerFlavorFor(RunId id, DeploymentSpec spec) {
for (DeploymentSpec.Step step : spec.steps())
- if (step.deploysTo(id.type().environment()))
+ if (step.concerns(id.type().environment()))
return step.zones().get(0).testerFlavor();
throw new IllegalStateException("No step deploys to the zone this run is for!");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
index 6ea7417dffd..8ca8d31b32b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepository.java
@@ -83,7 +83,7 @@ public class RotationRepository {
instance.name() + "'");
}
long productionZones = deploymentSpec.requireInstance(instance.name()).zones().stream()
- .filter(zone -> zone.deploysTo(Environment.prod))
+ .filter(zone -> zone.concerns(Environment.prod))
.count();
if (productionZones < 2) {
throw new IllegalArgumentException("global-service-id is set but less than 2 prod zones are defined " +