summaryrefslogtreecommitdiffstats
path: root/config-model-api/src/test/java/com/yahoo/config/application
diff options
context:
space:
mode:
Diffstat (limited to 'config-model-api/src/test/java/com/yahoo/config/application')
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecDeprecatedAPITest.java572
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java811
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java526
3 files changed, 1671 insertions, 238 deletions
diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecDeprecatedAPITest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecDeprecatedAPITest.java
new file mode 100644
index 00000000000..dabdd0c4a69
--- /dev/null
+++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecDeprecatedAPITest.java
@@ -0,0 +1,572 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.application.api;
+
+import com.google.common.collect.ImmutableSet;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import org.junit.Test;
+
+import java.io.StringReader;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.yahoo.config.application.api.Notifications.Role.author;
+import static com.yahoo.config.application.api.Notifications.When.failing;
+import static com.yahoo.config.application.api.Notifications.When.failingCommit;
+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
+ */
+// TODO: Remove after October 2019
+public class DeploymentSpecDeprecatedAPITest {
+
+ @Test
+ public void testSpec() {
+ String specXml = "<deployment version='1.0'>" +
+ " <test/>" +
+ "</deployment>";
+
+ StringReader r = new StringReader(specXml);
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(specXml, spec.xmlForm());
+ assertEquals(1, spec.steps().size());
+ assertFalse(spec.majorVersion().isPresent());
+ assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.includes(Environment.test, Optional.empty()));
+ assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertFalse(spec.includes(Environment.staging, Optional.empty()));
+ assertFalse(spec.includes(Environment.prod, Optional.empty()));
+ assertFalse(spec.globalServiceId().isPresent());
+ }
+
+ @Test
+ public void testSpecPinningMajorVersion() {
+ String specXml = "<deployment version='1.0' major-version='6'>" +
+ " <test/>" +
+ "</deployment>";
+
+ StringReader r = new StringReader(specXml);
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(specXml, spec.xmlForm());
+ assertEquals(1, spec.steps().size());
+ assertTrue(spec.majorVersion().isPresent());
+ assertEquals(6, (int)spec.majorVersion().get());
+ }
+
+ @Test
+ public void stagingSpec() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <staging/>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(2, spec.steps().size());
+ assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.steps().get(1).deploysTo(Environment.staging));
+ assertTrue(spec.includes(Environment.test, Optional.empty()));
+ assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.includes(Environment.staging, Optional.empty()));
+ assertFalse(spec.includes(Environment.prod, Optional.empty()));
+ assertFalse(spec.globalServiceId().isPresent());
+ }
+
+ @Test
+ public void minimalProductionSpec() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(4, spec.steps().size());
+
+ assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+
+ assertTrue(spec.steps().get(1).deploysTo(Environment.staging));
+
+ assertTrue(spec.steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertFalse(((DeploymentSpec.DeclaredZone)spec.steps().get(2)).active());
+
+ assertTrue(spec.steps().get(3).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(((DeploymentSpec.DeclaredZone)spec.steps().get(3)).active());
+
+ assertTrue(spec.includes(Environment.test, Optional.empty()));
+ assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.includes(Environment.staging, Optional.empty()));
+ assertTrue(spec.includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(spec.includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertFalse(spec.globalServiceId().isPresent());
+
+ assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, spec.upgradePolicy());
+ }
+
+ @Test
+ public void maximalProductionSpec() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <test/>" +
+ " <staging/>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <delay hours='3' minutes='30'/>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(5, spec.steps().size());
+ assertEquals(4, spec.zones().size());
+
+ assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+
+ assertTrue(spec.steps().get(1).deploysTo(Environment.staging));
+
+ assertTrue(spec.steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertFalse(((DeploymentSpec.DeclaredZone)spec.steps().get(2)).active());
+
+ assertTrue(spec.steps().get(3) instanceof DeploymentSpec.Delay);
+ assertEquals(3 * 60 * 60 + 30 * 60, ((DeploymentSpec.Delay)spec.steps().get(3)).duration().getSeconds());
+
+ assertTrue(spec.steps().get(4).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(((DeploymentSpec.DeclaredZone)spec.steps().get(4)).active());
+
+ assertTrue(spec.includes(Environment.test, Optional.empty()));
+ assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.includes(Environment.staging, Optional.empty()));
+ assertTrue(spec.includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(spec.includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertFalse(spec.globalServiceId().isPresent());
+ }
+
+ @Test
+ public void productionSpecWithGlobalServiceId() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <prod global-service-id='query'>" +
+ " <region active='true'>us-east-1</region>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(spec.globalServiceId(), Optional.of("query"));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void globalServiceIdInTest() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <test global-service-id='query' />" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void globalServiceIdInStaging() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <staging global-service-id='query' />" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ }
+
+ @Test
+ public void productionSpecWithGlobalServiceIdBeforeStaging() {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <test/>" +
+ " <prod global-service-id='qrs'>" +
+ " <region active='true'>us-west-1</region>" +
+ " <region active='true'>us-central-1</region>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ " <staging/>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals("qrs", spec.globalServiceId().get());
+ }
+
+ @Test
+ public void productionSpecWithUpgradePolicy() {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <upgrade policy='canary'/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " <region active='true'>us-central-1</region>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals("canary", spec.upgradePolicy().toString());
+ }
+
+ @Test
+ public void maxDelayExceeded() {
+ try {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <upgrade policy='canary'/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " <delay hours='23'/>" +
+ " <region active='true'>us-central-1</region>" +
+ " <delay minutes='59' seconds='61'/>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ fail("Expected exception due to exceeding the max total delay");
+ }
+ catch (IllegalArgumentException e) {
+ // success
+ assertEquals("The total delay specified is PT24H1S but max 24 hours is allowed", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testEmpty() {
+ assertFalse(DeploymentSpec.empty.globalServiceId().isPresent());
+ assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, DeploymentSpec.empty.upgradePolicy());
+ assertTrue(DeploymentSpec.empty.steps().isEmpty());
+ 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" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ try {
+ DeploymentSpec.fromXml(r);
+ fail("Expected exception");
+ } catch (IllegalArgumentException e) {
+ assertEquals("prod.us-west-1 is listed twice in deployment.xml", e.getMessage());
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void deploymentSpecWithIllegallyOrderedDeploymentSpec1() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ " <block-change days='mon,tue' hours='15-16'/>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void deploymentSpecWithIllegallyOrderedDeploymentSpec2() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
+ " <test/>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ }
+
+ @Test
+ public void deploymentSpecWithChangeBlocker() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <block-change revision='false' days='mon,tue' hours='15-16'/>\n" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(2, spec.changeBlocker().size());
+ assertTrue(spec.changeBlocker().get(0).blocksVersions());
+ assertFalse(spec.changeBlocker().get(0).blocksRevisions());
+ assertEquals(ZoneId.of("UTC"), spec.changeBlocker().get(0).window().zone());
+
+ assertTrue(spec.changeBlocker().get(1).blocksVersions());
+ assertTrue(spec.changeBlocker().get(1).blocksRevisions());
+ assertEquals(ZoneId.of("CET"), spec.changeBlocker().get(1).window().zone());
+
+ assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-18T14:15:30.00Z")));
+ assertFalse(spec.canUpgradeAt(Instant.parse("2017-09-18T15:15:30.00Z")));
+ assertFalse(spec.canUpgradeAt(Instant.parse("2017-09-18T16:15:30.00Z")));
+ assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-18T17:15:30.00Z")));
+
+ assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-23T09:15:30.00Z")));
+ assertFalse(spec.canUpgradeAt(Instant.parse("2017-09-23T08:15:30.00Z"))); // 10 in CET
+ assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-23T10:15:30.00Z")));
+ }
+
+ @Test
+ public void athenz_config_is_read_from_deployment() {
+ StringReader r = new StringReader(
+ "<deployment athenz-domain='domain' athenz-service='service'>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(spec.athenzDomain().get().value(), "domain");
+ assertEquals(spec.athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "service");
+ }
+
+ @Test
+ public void athenz_service_is_overridden_from_environment() {
+ StringReader r = new StringReader(
+ "<deployment athenz-domain='domain' athenz-service='service'>\n" +
+ " <test/>\n" +
+ " <prod athenz-service='prod-service'>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(spec.athenzDomain().get().value(), "domain");
+ assertEquals(spec.athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "prod-service");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void it_fails_when_athenz_service_is_not_defined() {
+ StringReader r = new StringReader(
+ "<deployment athenz-domain='domain'>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void it_fails_when_athenz_service_is_configured_but_not_athenz_domain() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <prod athenz-service='service'>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ }
+
+ @Test
+ public void noNotifications() {
+ assertEquals(Notifications.none(),
+ DeploymentSpec.fromXml("<deployment />").notifications());
+ }
+
+ @Test
+ public void emptyNotifications() {
+ DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
+ " <notifications />" +
+ "</deployment>");
+ assertEquals(Notifications.none(),
+ spec.notifications());
+ }
+
+ @Test
+ public void someNotifications() {
+ DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
+ " <notifications when=\"failing\">\n" +
+ " <email role=\"author\"/>\n" +
+ " <email address=\"john@dev\" when=\"failing-commit\"/>\n" +
+ " <email address=\"jane@dev\"/>\n" +
+ " </notifications>\n" +
+ "</deployment>");
+ assertEquals(ImmutableSet.of(author), spec.notifications().emailRolesFor(failing));
+ assertEquals(ImmutableSet.of(author), spec.notifications().emailRolesFor(failingCommit));
+ assertEquals(ImmutableSet.of("john@dev", "jane@dev"), spec.notifications().emailAddressesFor(failingCommit));
+ assertEquals(ImmutableSet.of("jane@dev"), spec.notifications().emailAddressesFor(failing));
+ }
+
+ @Test
+ public void customTesterFlavor() {
+ DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
+ " <test tester-flavor=\"d-1-4-20\" />\n" +
+ " <prod tester-flavor=\"d-2-8-50\">\n" +
+ " <region active=\"false\">us-north-7</region>\n" +
+ " </prod>\n" +
+ "</deployment>");
+ assertEquals(Optional.of("d-1-4-20"), spec.steps().get(0).zones().get(0).testerFlavor());
+ assertEquals(Optional.empty(), spec.steps().get(1).zones().get(0).testerFlavor());
+ assertEquals(Optional.of("d-2-8-50"), spec.steps().get(2).zones().get(0).testerFlavor());
+ }
+
+ @Test
+ public void noEndpoints() {
+ assertEquals(Collections.emptyList(), DeploymentSpec.fromXml("<deployment />").endpoints());
+ }
+
+ @Test
+ public void emptyEndpoints() {
+ final var spec = DeploymentSpec.fromXml("<deployment><endpoints/></deployment>");
+ assertEquals(Collections.emptyList(), spec.endpoints());
+ }
+
+ @Test
+ public void someEndpoints() {
+ final var spec = DeploymentSpec.fromXml("" +
+ "<deployment>" +
+ " <prod>" +
+ " <region active=\"true\">us-east</region>" +
+ " </prod>" +
+ " <endpoints>" +
+ " <endpoint id=\"foo\" container-id=\"bar\">" +
+ " <region>us-east</region>" +
+ " </endpoint>" +
+ " <endpoint id=\"nalle\" container-id=\"frosk\" />" +
+ " <endpoint container-id=\"quux\" />" +
+ " </endpoints>" +
+ "</deployment>");
+
+ assertEquals(
+ List.of("foo", "nalle", "default"),
+ spec.endpoints().stream().map(Endpoint::endpointId).collect(Collectors.toList())
+ );
+
+ assertEquals(
+ List.of("bar", "frosk", "quux"),
+ spec.endpoints().stream().map(Endpoint::containerId).collect(Collectors.toList())
+ );
+
+ assertEquals(Set.of(RegionName.from("us-east")), spec.endpoints().get(0).regions());
+ }
+ @Test
+ public void invalidEndpoints() {
+ assertInvalid("<endpoint id='FOO' container-id='qrs'/>"); // Uppercase
+ assertInvalid("<endpoint id='123' container-id='qrs'/>"); // Starting with non-character
+ assertInvalid("<endpoint id='foo!' container-id='qrs'/>"); // Non-alphanumeric
+ assertInvalid("<endpoint id='foo.bar' container-id='qrs'/>");
+ assertInvalid("<endpoint id='foo--bar' container-id='qrs'/>"); // Multiple consecutive dashes
+ assertInvalid("<endpoint id='foo-' container-id='qrs'/>"); // Trailing dash
+ assertInvalid("<endpoint id='foooooooooooo' container-id='qrs'/>"); // Too long
+ assertInvalid("<endpoint id='foo' container-id='qrs'/><endpoint id='foo' container-id='qrs'/>"); // Duplicate
+ }
+
+ @Test
+ public void validEndpoints() {
+ assertEquals(List.of("default"), endpointIds("<endpoint container-id='qrs'/>"));
+ assertEquals(List.of("default"), endpointIds("<endpoint id='' container-id='qrs'/>"));
+ assertEquals(List.of("f"), endpointIds("<endpoint id='f' container-id='qrs'/>"));
+ assertEquals(List.of("foo"), endpointIds("<endpoint id='foo' container-id='qrs'/>"));
+ assertEquals(List.of("foo-bar"), endpointIds("<endpoint id='foo-bar' container-id='qrs'/>"));
+ assertEquals(List.of("foo", "bar"), endpointIds("<endpoint id='foo' container-id='qrs'/><endpoint id='bar' container-id='qrs'/>"));
+ assertEquals(List.of("fooooooooooo"), endpointIds("<endpoint id='fooooooooooo' container-id='qrs'/>"));
+ }
+
+ @Test
+ public void endpointDefaultRegions() {
+ var spec = DeploymentSpec.fromXml("" +
+ "<deployment>" +
+ " <prod>" +
+ " <region active=\"true\">us-east</region>" +
+ " <region active=\"true\">us-west</region>" +
+ " </prod>" +
+ " <endpoints>" +
+ " <endpoint id=\"foo\" container-id=\"bar\">" +
+ " <region>us-east</region>" +
+ " </endpoint>" +
+ " <endpoint id=\"nalle\" container-id=\"frosk\" />" +
+ " <endpoint container-id=\"quux\" />" +
+ " </endpoints>" +
+ "</deployment>");
+
+ assertEquals(Set.of("us-east"), endpointRegions("foo", spec));
+ assertEquals(Set.of("us-east", "us-west"), endpointRegions("nalle", spec));
+ assertEquals(Set.of("us-east", "us-west"), endpointRegions("default", spec));
+ }
+
+ private static void assertInvalid(String endpointTag) {
+ try {
+ endpointIds(endpointTag);
+ fail("Expected exception for input '" + endpointTag + "'");
+ } catch (IllegalArgumentException ignored) {}
+ }
+
+ private static Set<String> endpointRegions(String endpointId, DeploymentSpec spec) {
+ return spec.endpoints().stream()
+ .filter(endpoint -> endpoint.endpointId().equals(endpointId))
+ .flatMap(endpoint -> endpoint.regions().stream())
+ .map(RegionName::value)
+ .collect(Collectors.toSet());
+ }
+
+ private static List<String> endpointIds(String endpointTag) {
+ var xml = "<deployment>" +
+ " <prod>" +
+ " <region active=\"true\">us-east</region>" +
+ " </prod>" +
+ " <endpoints>" +
+ endpointTag +
+ " </endpoints>" +
+ "</deployment>";
+
+ return DeploymentSpec.fromXml(xml).endpoints().stream()
+ .map(Endpoint::endpointId)
+ .collect(Collectors.toList());
+ }
+
+}
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 47eaf7a515a..b75801de7ea 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
@@ -14,7 +14,6 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import static com.yahoo.config.application.api.Notifications.Role.author;
import static com.yahoo.config.application.api.Notifications.When.failing;
@@ -32,32 +31,36 @@ public class DeploymentSpecTest {
@Test
public void testSpec() {
String specXml = "<deployment version='1.0'>" +
- " <test/>" +
+ " <instance id='default'>" +
+ " <test/>" +
+ " </instance>" +
"</deployment>";
StringReader r = new StringReader(specXml);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
assertEquals(specXml, spec.xmlForm());
- assertEquals(1, spec.steps().size());
+ assertEquals(1, spec.instance("default").steps().size());
assertFalse(spec.majorVersion().isPresent());
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
- assertTrue(spec.includes(Environment.test, Optional.empty()));
- assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1"))));
- assertFalse(spec.includes(Environment.staging, Optional.empty()));
- assertFalse(spec.includes(Environment.prod, Optional.empty()));
- assertFalse(spec.globalServiceId().isPresent());
+ assertTrue(spec.instance("default").steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.instance("default").includes(Environment.test, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertFalse(spec.instance("default").includes(Environment.staging, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.prod, Optional.empty()));
+ assertFalse(spec.instance("default").globalServiceId().isPresent());
}
@Test
public void testSpecPinningMajorVersion() {
String specXml = "<deployment version='1.0' major-version='6'>" +
- " <test/>" +
+ " <instance id='default'>" +
+ " <test/>" +
+ " </instance>" +
"</deployment>";
StringReader r = new StringReader(specXml);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
assertEquals(specXml, spec.xmlForm());
- assertEquals(1, spec.steps().size());
+ assertEquals(1, spec.instance("default").steps().size());
assertTrue(spec.majorVersion().isPresent());
assertEquals(6, (int)spec.majorVersion().get());
}
@@ -66,164 +69,256 @@ public class DeploymentSpecTest {
public void stagingSpec() {
StringReader r = new StringReader(
"<deployment version='1.0'>" +
- " <staging/>" +
+ " <instance id='default'>" +
+ " <staging/>" +
+ " </instance>" +
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(2, spec.steps().size());
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
- assertTrue(spec.steps().get(1).deploysTo(Environment.staging));
- assertTrue(spec.includes(Environment.test, Optional.empty()));
- assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1"))));
- assertTrue(spec.includes(Environment.staging, Optional.empty()));
- assertFalse(spec.includes(Environment.prod, Optional.empty()));
- assertFalse(spec.globalServiceId().isPresent());
+ assertEquals(2, spec.instance("default").steps().size());
+ assertTrue(spec.instance("default").steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.instance("default").steps().get(1).deploysTo(Environment.staging));
+ assertTrue(spec.instance("default").includes(Environment.test, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.instance("default").includes(Environment.staging, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.prod, Optional.empty()));
+ assertFalse(spec.instance("default").globalServiceId().isPresent());
}
@Test
public void minimalProductionSpec() {
StringReader r = new StringReader(
- "<deployment version='1.0'>" +
- " <prod>" +
- " <region active='false'>us-east1</region>" +
- " <region active='true'>us-west1</region>" +
- " </prod>" +
- "</deployment>"
+ "<deployment version='1.0'>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(4, spec.steps().size());
+ assertEquals(4, spec.instance("default").steps().size());
+
+ assertTrue(spec.instance("default").steps().get(0).deploysTo(Environment.test));
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.instance("default").steps().get(1).deploysTo(Environment.staging));
- assertTrue(spec.steps().get(1).deploysTo(Environment.staging));
+ assertTrue(spec.instance("default").steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertFalse(((DeploymentSpec.DeclaredZone)spec.instance("default").steps().get(2)).active());
- assertTrue(spec.steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
- assertFalse(((DeploymentSpec.DeclaredZone)spec.steps().get(2)).active());
+ assertTrue(spec.instance("default").steps().get(3).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(((DeploymentSpec.DeclaredZone)spec.instance("default").steps().get(3)).active());
- assertTrue(spec.steps().get(3).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
- assertTrue(((DeploymentSpec.DeclaredZone)spec.steps().get(3)).active());
+ assertTrue(spec.instance("default").includes(Environment.test, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.instance("default").includes(Environment.staging, Optional.empty()));
+ assertTrue(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertFalse(spec.instance("default").globalServiceId().isPresent());
- assertTrue(spec.includes(Environment.test, Optional.empty()));
- assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1"))));
- assertTrue(spec.includes(Environment.staging, Optional.empty()));
- assertTrue(spec.includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
- assertTrue(spec.includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
- assertFalse(spec.includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
- assertFalse(spec.globalServiceId().isPresent());
-
- assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, spec.upgradePolicy());
+ assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, spec.instance("default").upgradePolicy());
}
@Test
public void maximalProductionSpec() {
StringReader r = new StringReader(
- "<deployment version='1.0'>" +
- " <test/>" +
- " <staging/>" +
- " <prod>" +
- " <region active='false'>us-east1</region>" +
- " <delay hours='3' minutes='30'/>" +
- " <region active='true'>us-west1</region>" +
- " </prod>" +
- "</deployment>"
+ "<deployment version='1.0'>" +
+ " <instance id='default'>" + // The block checked by assertCorrectFirstInstance
+ " <test/>" +
+ " <staging/>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <delay hours='3' minutes='30'/>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(5, spec.steps().size());
- assertEquals(4, spec.zones().size());
+ assertCorrectFirstInstance(spec.instance("default"));
+ }
- assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+ @Test
+ public void maximalProductionSpecMultipleInstances() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='instance1'>" + // The block checked by assertCorrectFirstInstance
+ " <test/>" +
+ " <staging/>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <delay hours='3' minutes='30'/>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ " </instance>" +
+ " <instance id='instance2'>" +
+ " <prod>" +
+ " <region active='true'>us-central1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
- assertTrue(spec.steps().get(1).deploysTo(Environment.staging));
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertTrue(spec.steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
- assertFalse(((DeploymentSpec.DeclaredZone)spec.steps().get(2)).active());
+ assertCorrectFirstInstance(spec.instance("instance1"));
- assertTrue(spec.steps().get(3) instanceof DeploymentSpec.Delay);
- assertEquals(3 * 60 * 60 + 30 * 60, ((DeploymentSpec.Delay)spec.steps().get(3)).duration().getSeconds());
+ DeploymentInstanceSpec instance2 = spec.instance("instance2");
+ assertEquals(1, instance2.steps().size());
+ assertEquals(1, instance2.zones().size());
- assertTrue(spec.steps().get(4).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
- assertTrue(((DeploymentSpec.DeclaredZone)spec.steps().get(4)).active());
+ assertTrue(instance2.steps().get(0).deploysTo(Environment.prod, Optional.of(RegionName.from("us-central1"))));
+ }
- assertTrue(spec.includes(Environment.test, Optional.empty()));
- assertFalse(spec.includes(Environment.test, Optional.of(RegionName.from("region1"))));
- assertTrue(spec.includes(Environment.staging, Optional.empty()));
- assertTrue(spec.includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
- assertTrue(spec.includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
- assertFalse(spec.includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
- assertFalse(spec.globalServiceId().isPresent());
+ @Test
+ public void testMultipleInstancesShortForm() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='instance1, instance2'>" + // The block checked by assertCorrectFirstInstance
+ " <test/>" +
+ " <staging/>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <delay hours='3' minutes='30'/>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+
+ assertCorrectFirstInstance(spec.instance("instance1"));
+ assertCorrectFirstInstance(spec.instance("instance2"));
+ }
+
+ private void assertCorrectFirstInstance(DeploymentInstanceSpec instance) {
+ assertEquals(5, instance.steps().size());
+ assertEquals(4, instance.zones().size());
+
+ assertTrue(instance.steps().get(0).deploysTo(Environment.test));
+
+ assertTrue(instance.steps().get(1).deploysTo(Environment.staging));
+
+ assertTrue(instance.steps().get(2).deploysTo(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(((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"))));
+ assertFalse(instance.globalServiceId().isPresent());
}
@Test
public void productionSpecWithGlobalServiceId() {
StringReader r = new StringReader(
"<deployment version='1.0'>" +
- " <prod global-service-id='query'>" +
- " <region active='true'>us-east-1</region>" +
- " <region active='true'>us-west-1</region>" +
- " </prod>" +
+ " <instance id='default'>" +
+ " <prod global-service-id='query'>" +
+ " <region active='true'>us-east-1</region>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(spec.globalServiceId(), Optional.of("query"));
+ assertEquals(spec.instance("default").globalServiceId(), Optional.of("query"));
}
@Test(expected=IllegalArgumentException.class)
public void globalServiceIdInTest() {
StringReader r = new StringReader(
"<deployment version='1.0'>" +
- " <test global-service-id='query' />" +
+ " <instance id='default'>" +
+ " <test global-service-id='query' />" +
+ " </instance>" +
"</deployment>"
);
- DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentSpec.fromXml(r);
}
@Test(expected=IllegalArgumentException.class)
public void globalServiceIdInStaging() {
StringReader r = new StringReader(
"<deployment version='1.0'>" +
- " <staging global-service-id='query' />" +
+ " <instance id='default'>" +
+ " <staging global-service-id='query' />" +
+ " </instance>" +
"</deployment>"
);
- DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentSpec.fromXml(r);
}
@Test
public void productionSpecWithGlobalServiceIdBeforeStaging() {
StringReader r = new StringReader(
"<deployment>" +
- " <test/>" +
- " <prod global-service-id='qrs'>" +
- " <region active='true'>us-west-1</region>" +
- " <region active='true'>us-central-1</region>" +
- " <region active='true'>us-east-3</region>" +
- " </prod>" +
- " <staging/>" +
+ " <instance id='default'>" +
+ " <test/>" +
+ " <prod global-service-id='qrs'>" +
+ " <region active='true'>us-west-1</region>" +
+ " <region active='true'>us-central-1</region>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ " <staging/>" +
+ " </instance>" +
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals("qrs", spec.globalServiceId().get());
+ assertEquals("qrs", spec.instance("default").globalServiceId().get());
}
@Test
public void productionSpecWithUpgradePolicy() {
StringReader r = new StringReader(
"<deployment>" +
- " <upgrade policy='canary'/>" +
- " <prod>" +
- " <region active='true'>us-west-1</region>" +
- " <region active='true'>us-central-1</region>" +
- " <region active='true'>us-east-3</region>" +
- " </prod>" +
+ " <instance id='default'>" +
+ " <upgrade policy='canary'/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " <region active='true'>us-central-1</region>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals("canary", spec.upgradePolicy().toString());
+ assertEquals("canary", spec.instance("default").upgradePolicy().toString());
+ }
+
+ @Test
+ public void upgradePolicyDefault() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <upgrade policy='canary'/>" +
+ " <instance id='instance1'>" +
+ " <upgrade policy='conservative'/>" +
+ " </instance>" +
+ " <instance id='instance2'>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals("conservative", spec.instance("instance1").upgradePolicy().toString());
+ assertEquals("canary", spec.instance("instance2").upgradePolicy().toString());
}
@Test
@@ -231,14 +326,16 @@ public class DeploymentSpecTest {
try {
StringReader r = new StringReader(
"<deployment>" +
- " <upgrade policy='canary'/>" +
- " <prod>" +
- " <region active='true'>us-west-1</region>" +
- " <delay hours='23'/>" +
- " <region active='true'>us-central-1</region>" +
- " <delay minutes='59' seconds='61'/>" +
- " <region active='true'>us-east-3</region>" +
- " </prod>" +
+ " <instance id='default'>" +
+ " <upgrade policy='canary'/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " <delay hours='23'/>" +
+ " <region active='true'>us-central-1</region>" +
+ " <delay minutes='59' seconds='61'/>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
DeploymentSpec.fromXml(r);
@@ -252,7 +349,7 @@ public class DeploymentSpecTest {
@Test
public void testEmpty() {
- assertFalse(DeploymentSpec.empty.globalServiceId().isPresent());
+ assertFalse(DeploymentSpec.empty.instance("default").globalServiceId().isPresent());
assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, DeploymentSpec.empty.upgradePolicy());
assertTrue(DeploymentSpec.empty.steps().isEmpty());
assertEquals("<deployment version='1.0'/>", DeploymentSpec.empty.xmlForm());
@@ -261,36 +358,139 @@ public class DeploymentSpecTest {
@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>"
+ "<deployment>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " <parallel>" +
+ " <region active='true'>us-central-1</region>" +
+ " <region active='true'>us-east-3</region>" +
+ " </parallel>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.steps().get(3));
+ DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.instance("default").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 testTestAndStagingOutsideAndInsideInstance() {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <test/>" +
+ " <staging/>" +
+ " <instance id='instance0'>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
+ " <instance id='instance1'>" +
+ " <test/>" +
+ " <staging/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ List<DeploymentSpec.Step> steps = spec.steps();
+ assertEquals(4, steps.size());
+ assertEquals("test", steps.get(0).toString());
+ assertEquals("staging", steps.get(1).toString());
+ assertEquals("instance 'instance0'", steps.get(2).toString());
+ assertEquals("instance 'instance1'", steps.get(3).toString());
+
+ List<DeploymentSpec.Step> instance0Steps = ((DeploymentInstanceSpec)steps.get(2)).steps();
+ assertEquals(1, instance0Steps.size());
+ assertEquals("prod.us-west-1", instance0Steps.get(0).toString());
+
+ List<DeploymentSpec.Step> instance1Steps = ((DeploymentInstanceSpec)steps.get(3)).steps();
+ assertEquals(3, instance1Steps.size());
+ assertEquals("test", instance1Steps.get(0).toString());
+ assertEquals("staging", instance1Steps.get(1).toString());
+ assertEquals("prod.us-west-1", instance1Steps.get(2).toString());
+ }
+
+ @Test
+ public void testParallelInstances() {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <parallel>" +
+ " <instance id='instance0'>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
+ " <instance id='instance1'>" +
+ " <prod>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ " </instance>" +
+ " </parallel>" +
+ "</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("2 parallel steps", steps.get(2).toString());
+
+ List<DeploymentSpec.Step> parallelSteps = steps.get(2).steps();
+ assertEquals("instance 'instance0'", parallelSteps.get(0).toString());
+ assertEquals("instance 'instance1'", parallelSteps.get(1).toString());
+ }
+
+ @Test
+ public void testInstancesWithDelay() {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <instance id='instance0'>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
+ " <delay hours='12'/>" +
+ " <instance id='instance1'>" +
+ " <prod>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ List<DeploymentSpec.Step> steps = spec.steps();
+ assertEquals(5, steps.size());
+ assertEquals("test", steps.get(0).toString());
+ assertEquals("staging", steps.get(1).toString());
+ assertEquals("instance 'instance0'", steps.get(2).toString());
+ assertEquals("delay PT12H", steps.get(3).toString());
+ assertEquals("instance 'instance1'", steps.get(4).toString());
+ }
+
+ @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" +
- " </prod>\n" +
- "</deployment>"
+ "<deployment>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <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>" +
+ " </parallel>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
);
try {
DeploymentSpec.fromXml(r);
@@ -303,197 +503,328 @@ public class DeploymentSpecTest {
@Test(expected = IllegalArgumentException.class)
public void deploymentSpecWithIllegallyOrderedDeploymentSpec1() {
StringReader r = new StringReader(
- "<deployment>\n" +
- " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
- " <prod>\n" +
- " <region active='true'>us-west-1</region>\n" +
- " </prod>\n" +
- " <block-change days='mon,tue' hours='15-16'/>\n" +
+ "<deployment>" +
+ " <instance id='default'>" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " <block-change days='mon,tue' hours='15-16'/>" +
+ " </instance>" +
"</deployment>"
);
- DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentSpec.fromXml(r);
}
@Test(expected = IllegalArgumentException.class)
public void deploymentSpecWithIllegallyOrderedDeploymentSpec2() {
StringReader r = new StringReader(
"<deployment>\n" +
- " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
- " <test/>\n" +
- " <prod>\n" +
- " <region active='true'>us-west-1</region>\n" +
- " </prod>\n" +
+ " <instance id='default'>" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>" +
+ " <test/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
- DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentSpec.fromXml(r);
}
@Test
public void deploymentSpecWithChangeBlocker() {
StringReader r = new StringReader(
- "<deployment>\n" +
- " <block-change revision='false' days='mon,tue' hours='15-16'/>\n" +
- " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
- " <prod>\n" +
- " <region active='true'>us-west-1</region>\n" +
- " </prod>\n" +
+ "<deployment>" +
+ " <instance id='default'>" +
+ " <block-change revision='false' days='mon,tue' hours='15-16'/>" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(2, spec.changeBlocker().size());
- assertTrue(spec.changeBlocker().get(0).blocksVersions());
- assertFalse(spec.changeBlocker().get(0).blocksRevisions());
- assertEquals(ZoneId.of("UTC"), spec.changeBlocker().get(0).window().zone());
+ assertEquals(2, spec.instance("default").changeBlocker().size());
+ assertTrue(spec.instance("default").changeBlocker().get(0).blocksVersions());
+ assertFalse(spec.instance("default").changeBlocker().get(0).blocksRevisions());
+ assertEquals(ZoneId.of("UTC"), spec.instance("default").changeBlocker().get(0).window().zone());
- assertTrue(spec.changeBlocker().get(1).blocksVersions());
- assertTrue(spec.changeBlocker().get(1).blocksRevisions());
- assertEquals(ZoneId.of("CET"), spec.changeBlocker().get(1).window().zone());
+ assertTrue(spec.instance("default").changeBlocker().get(1).blocksVersions());
+ assertTrue(spec.instance("default").changeBlocker().get(1).blocksRevisions());
+ assertEquals(ZoneId.of("CET"), spec.instance("default").changeBlocker().get(1).window().zone());
- assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-18T14:15:30.00Z")));
- assertFalse(spec.canUpgradeAt(Instant.parse("2017-09-18T15:15:30.00Z")));
- assertFalse(spec.canUpgradeAt(Instant.parse("2017-09-18T16:15:30.00Z")));
- assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-18T17:15:30.00Z")));
+ assertTrue(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-18T14:15:30.00Z")));
+ assertFalse(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-18T15:15:30.00Z")));
+ assertFalse(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-18T16:15:30.00Z")));
+ assertTrue(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-18T17:15:30.00Z")));
- assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-23T09:15:30.00Z")));
- assertFalse(spec.canUpgradeAt(Instant.parse("2017-09-23T08:15:30.00Z"))); // 10 in CET
- assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-23T10:15:30.00Z")));
+ assertTrue(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-23T09:15:30.00Z")));
+ assertFalse(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-23T08:15:30.00Z"))); // 10 in CET
+ assertTrue(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-23T10:15:30.00Z")));
+ }
+
+ @Test
+ public void testChangeBlockerInheritance() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <block-change revision='false' days='mon,tue' hours='15-16'/>" +
+ " <instance id='instance1'>" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>" +
+ " </instance>" +
+ " <instance id='instance2'>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+
+ String inheritedChangeBlocker = "change blocker revision=false version=true window=time window for hour(s) [15, 16] on [monday, tuesday] in UTC";
+
+ assertEquals(2, spec.instance("instance1").changeBlocker().size());
+ assertEquals(inheritedChangeBlocker, spec.instance("instance1").changeBlocker().get(0).toString());
+ assertEquals("change blocker revision=true version=true window=time window for hour(s) [10] on [saturday] in CET",
+ spec.instance("instance1").changeBlocker().get(1).toString());
+
+ assertEquals(1, spec.instance("instance2").changeBlocker().size());
+ assertEquals(inheritedChangeBlocker, spec.instance("instance2").changeBlocker().get(0).toString());
}
@Test
public void athenz_config_is_read_from_deployment() {
StringReader r = new StringReader(
- "<deployment athenz-domain='domain' athenz-service='service'>\n" +
- " <prod>\n" +
- " <region active='true'>us-west-1</region>\n" +
- " </prod>\n" +
+ "<deployment athenz-domain='domain' athenz-service='service'>" +
+ " <instance id='instance1'>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(spec.athenzDomain().get().value(), "domain");
- assertEquals(spec.athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "service");
+ assertEquals(spec.instance("instance1").athenzDomain().get().value(), "domain");
+ assertEquals(spec.instance("instance1").athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "service");
+ }
+
+ @Test
+ public void athenz_config_is_read_from_instance() {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <instance id='default' athenz-domain='domain' athenz-service='service'>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(spec.instance("default").athenzDomain().get().value(), "domain");
+ assertEquals(spec.instance("default").athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "service");
}
@Test
public void athenz_service_is_overridden_from_environment() {
StringReader r = new StringReader(
- "<deployment athenz-domain='domain' athenz-service='service'>\n" +
- " <test/>\n" +
- " <prod athenz-service='prod-service'>\n" +
- " <region active='true'>us-west-1</region>\n" +
- " </prod>\n" +
+ "<deployment athenz-domain='domain' athenz-service='service'>" +
+ " <instance id='default' athenz-domain='domain' athenz-service='service'>" +
+ " <test/>" +
+ " <prod athenz-service='prod-service'>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(spec.athenzDomain().get().value(), "domain");
- assertEquals(spec.athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "prod-service");
+ assertEquals(spec.instance("default").athenzDomain().get().value(), "domain");
+ assertEquals(spec.instance("default").athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "prod-service");
}
@Test(expected = IllegalArgumentException.class)
public void it_fails_when_athenz_service_is_not_defined() {
StringReader r = new StringReader(
- "<deployment athenz-domain='domain'>\n" +
- " <prod>\n" +
- " <region active='true'>us-west-1</region>\n" +
- " </prod>\n" +
+ "<deployment>" +
+ " <instance id='default' athenz-domain='domain'>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
- DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentSpec.fromXml(r);
}
@Test(expected = IllegalArgumentException.class)
public void it_fails_when_athenz_service_is_configured_but_not_athenz_domain() {
StringReader r = new StringReader(
- "<deployment>\n" +
- " <prod athenz-service='service'>\n" +
- " <region active='true'>us-west-1</region>\n" +
- " </prod>\n" +
+ "<deployment>" +
+ " <instance id='default'>" +
+ " <prod athenz-service='service'>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>"
);
- DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentSpec.fromXml(r);
}
@Test
public void noNotifications() {
assertEquals(Notifications.none(),
- DeploymentSpec.fromXml("<deployment />").notifications());
+ DeploymentSpec.fromXml("<deployment>" +
+ " <instance id='default'/>" +
+ "</deployment>").instance("default").notifications());
}
@Test
public void emptyNotifications() {
- DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
- " <notifications />" +
+ DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>" +
+ " <instance id='default'>" +
+ " <notifications/>" +
+ " </instance>" +
"</deployment>");
- assertEquals(Notifications.none(),
- spec.notifications());
+ assertEquals(Notifications.none(), spec.instance("default").notifications());
}
@Test
public void someNotifications() {
DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
- " <notifications when=\"failing\">\n" +
- " <email role=\"author\"/>\n" +
- " <email address=\"john@dev\" when=\"failing-commit\"/>\n" +
- " <email address=\"jane@dev\"/>\n" +
- " </notifications>\n" +
+ " <instance id='default'>" +
+ " <notifications when=\"failing\">" +
+ " <email role=\"author\"/>" +
+ " <email address=\"john@dev\" when=\"failing-commit\"/>" +
+ " <email address=\"jane@dev\"/>" +
+ " </notifications>" +
+ " </instance>" +
"</deployment>");
- assertEquals(ImmutableSet.of(author), spec.notifications().emailRolesFor(failing));
- assertEquals(ImmutableSet.of(author), spec.notifications().emailRolesFor(failingCommit));
- assertEquals(ImmutableSet.of("john@dev", "jane@dev"), spec.notifications().emailAddressesFor(failingCommit));
- assertEquals(ImmutableSet.of("jane@dev"), spec.notifications().emailAddressesFor(failing));
+ assertEquals(ImmutableSet.of(author), spec.instance("default").notifications().emailRolesFor(failing));
+ assertEquals(ImmutableSet.of(author), spec.instance("default").notifications().emailRolesFor(failingCommit));
+ assertEquals(ImmutableSet.of("john@dev", "jane@dev"), spec.instance("default").notifications().emailAddressesFor(failingCommit));
+ assertEquals(ImmutableSet.of("jane@dev"), spec.instance("default").notifications().emailAddressesFor(failing));
+ }
+
+ @Test
+ public void notificationsWithMultipleInstances() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <instance id='instance1'>" +
+ " <notifications when=\"failing\">" +
+ " <email role=\"author\"/>" +
+ " <email address=\"john@operator\"/>" +
+ " </notifications>" +
+ " </instance>" +
+ " <instance id='instance2'>" +
+ " <notifications when=\"failing-commit\">" +
+ " <email role=\"author\"/>" +
+ " <email address=\"mary@dev\"/>" +
+ " </notifications>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentInstanceSpec instance1 = spec.instance("instance1");
+ assertEquals(Set.of(author), instance1.notifications().emailRolesFor(failing));
+ assertEquals(Set.of("john@operator"), instance1.notifications().emailAddressesFor(failing));
+
+ DeploymentInstanceSpec instance2 = spec.instance("instance2");
+ assertEquals(Set.of(author), instance2.notifications().emailRolesFor(failingCommit));
+ assertEquals(Set.of("mary@dev"), instance2.notifications().emailAddressesFor(failingCommit));
+ }
+
+ @Test
+ public void notificationsDefault() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <notifications when=\"failing-commit\">" +
+ " <email role=\"author\"/>" +
+ " <email address=\"mary@dev\"/>" +
+ " </notifications>" +
+ " <instance id='instance1'>" +
+ " <notifications when=\"failing\">" +
+ " <email role=\"author\"/>" +
+ " <email address=\"john@operator\"/>" +
+ " </notifications>" +
+ " </instance>" +
+ " <instance id='instance2'>" +
+ " </instance>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentInstanceSpec instance1 = spec.instance("instance1");
+ assertEquals(Set.of(author), instance1.notifications().emailRolesFor(failing));
+ assertEquals(Set.of("john@operator"), instance1.notifications().emailAddressesFor(failing));
+
+ DeploymentInstanceSpec instance2 = spec.instance("instance2");
+ assertEquals(Set.of(author), instance2.notifications().emailRolesFor(failingCommit));
+ assertEquals(Set.of("mary@dev"), instance2.notifications().emailAddressesFor(failingCommit));
}
@Test
public void customTesterFlavor() {
- DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
- " <test tester-flavor=\"d-1-4-20\" />\n" +
- " <prod tester-flavor=\"d-2-8-50\">\n" +
- " <region active=\"false\">us-north-7</region>\n" +
- " </prod>\n" +
+ DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>" +
+ " <instance id='default'>" +
+ " <test tester-flavor=\"d-1-4-20\" />" +
+ " <prod tester-flavor=\"d-2-8-50\">" +
+ " <region active=\"false\">us-north-7</region>" +
+ " </prod>" +
+ " </instance>" +
"</deployment>");
- assertEquals(Optional.of("d-1-4-20"), spec.steps().get(0).zones().get(0).testerFlavor());
- assertEquals(Optional.empty(), spec.steps().get(1).zones().get(0).testerFlavor());
- assertEquals(Optional.of("d-2-8-50"), spec.steps().get(2).zones().get(0).testerFlavor());
+ assertEquals(Optional.of("d-1-4-20"), spec.instance("default").steps().get(0).zones().get(0).testerFlavor());
+ assertEquals(Optional.empty(), spec.instance("default").steps().get(1).zones().get(0).testerFlavor());
+ assertEquals(Optional.of("d-2-8-50"), spec.instance("default").steps().get(2).zones().get(0).testerFlavor());
}
@Test
public void noEndpoints() {
- assertEquals(Collections.emptyList(), DeploymentSpec.fromXml("<deployment />").endpoints());
+ assertEquals(Collections.emptyList(),
+ DeploymentSpec.fromXml("<deployment>" +
+ " <instance id='default'/>" +
+ "</deployment>").instance("default").endpoints());
}
@Test
public void emptyEndpoints() {
- final var spec = DeploymentSpec.fromXml("<deployment><endpoints/></deployment>");
- assertEquals(Collections.emptyList(), spec.endpoints());
+ var spec = DeploymentSpec.fromXml("<deployment>" +
+ " <instance id='default'>" +
+ " <endpoints/>" +
+ " </instance>" +
+ "</deployment>");
+ assertEquals(Collections.emptyList(), spec.instance("default").endpoints());
}
@Test
public void someEndpoints() {
- final var spec = DeploymentSpec.fromXml("" +
- "<deployment>" +
- " <prod>" +
- " <region active=\"true\">us-east</region>" +
- " </prod>" +
- " <endpoints>" +
- " <endpoint id=\"foo\" container-id=\"bar\">" +
- " <region>us-east</region>" +
- " </endpoint>" +
- " <endpoint id=\"nalle\" container-id=\"frosk\" />" +
- " <endpoint container-id=\"quux\" />" +
- " </endpoints>" +
- "</deployment>");
+ var spec = DeploymentSpec.fromXml("" +
+ "<deployment>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <region active=\"true\">us-east</region>" +
+ " </prod>" +
+ " <endpoints>" +
+ " <endpoint id=\"foo\" container-id=\"bar\">" +
+ " <region>us-east</region>" +
+ " </endpoint>" +
+ " <endpoint id=\"nalle\" container-id=\"frosk\" />" +
+ " <endpoint container-id=\"quux\" />" +
+ " </endpoints>" +
+ " </instance>" +
+ "</deployment>");
assertEquals(
List.of("foo", "nalle", "default"),
- spec.endpoints().stream().map(Endpoint::endpointId).collect(Collectors.toList())
+ spec.instance("default").endpoints().stream().map(Endpoint::endpointId).collect(Collectors.toList())
);
assertEquals(
List.of("bar", "frosk", "quux"),
- spec.endpoints().stream().map(Endpoint::containerId).collect(Collectors.toList())
+ spec.instance("default").endpoints().stream().map(Endpoint::containerId).collect(Collectors.toList())
);
- assertEquals(Set.of(RegionName.from("us-east")), spec.endpoints().get(0).regions());
+ assertEquals(Set.of(RegionName.from("us-east")), spec.instance("default").endpoints().get(0).regions());
}
+
@Test
public void invalidEndpoints() {
assertInvalid("<endpoint id='FOO' container-id='qrs'/>"); // Uppercase
@@ -520,19 +851,21 @@ public class DeploymentSpecTest {
@Test
public void endpointDefaultRegions() {
var spec = DeploymentSpec.fromXml("" +
- "<deployment>" +
- " <prod>" +
- " <region active=\"true\">us-east</region>" +
- " <region active=\"true\">us-west</region>" +
- " </prod>" +
- " <endpoints>" +
- " <endpoint id=\"foo\" container-id=\"bar\">" +
- " <region>us-east</region>" +
- " </endpoint>" +
- " <endpoint id=\"nalle\" container-id=\"frosk\" />" +
- " <endpoint container-id=\"quux\" />" +
- " </endpoints>" +
- "</deployment>");
+ "<deployment>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <region active=\"true\">us-east</region>" +
+ " <region active=\"true\">us-west</region>" +
+ " </prod>" +
+ " <endpoints>" +
+ " <endpoint id=\"foo\" container-id=\"bar\">" +
+ " <region>us-east</region>" +
+ " </endpoint>" +
+ " <endpoint id=\"nalle\" container-id=\"frosk\" />" +
+ " <endpoint container-id=\"quux\" />" +
+ " </endpoints>" +
+ " </instance>" +
+ "</deployment>");
assertEquals(Set.of("us-east"), endpointRegions("foo", spec));
assertEquals(Set.of("us-east", "us-west"), endpointRegions("nalle", spec));
@@ -547,7 +880,7 @@ public class DeploymentSpecTest {
}
private static Set<String> endpointRegions(String endpointId, DeploymentSpec spec) {
- return spec.endpoints().stream()
+ return spec.instance("default").endpoints().stream()
.filter(endpoint -> endpoint.endpointId().equals(endpointId))
.flatMap(endpoint -> endpoint.regions().stream())
.map(RegionName::value)
@@ -556,15 +889,17 @@ public class DeploymentSpecTest {
private static List<String> endpointIds(String endpointTag) {
var xml = "<deployment>" +
- " <prod>" +
- " <region active=\"true\">us-east</region>" +
- " </prod>" +
- " <endpoints>" +
+ " <instance id='default'>" +
+ " <prod>" +
+ " <region active=\"true\">us-east</region>" +
+ " </prod>" +
+ " <endpoints>" +
endpointTag +
- " </endpoints>" +
+ " </endpoints>" +
+ " </instance>" +
"</deployment>";
- return DeploymentSpec.fromXml(xml).endpoints().stream()
+ return DeploymentSpec.fromXml(xml).instance("default").endpoints().stream()
.map(Endpoint::endpointId)
.collect(Collectors.toList());
}
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
new file mode 100644
index 00000000000..33ef3f4bea8
--- /dev/null
+++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java
@@ -0,0 +1,526 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.application.api;
+
+import com.google.common.collect.ImmutableSet;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import org.junit.Test;
+
+import java.io.StringReader;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.yahoo.config.application.api.Notifications.Role.author;
+import static com.yahoo.config.application.api.Notifications.When.failing;
+import static com.yahoo.config.application.api.Notifications.When.failingCommit;
+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
+ */
+public class DeploymentSpecWithoutInstanceTest {
+
+ @Test
+ public void testSpec() {
+ String specXml = "<deployment version='1.0'>" +
+ " <test/>" +
+ "</deployment>";
+
+ StringReader r = new StringReader(specXml);
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(specXml, spec.xmlForm());
+ assertEquals(1, spec.steps().size());
+ assertFalse(spec.majorVersion().isPresent());
+ assertTrue(spec.steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.instance("default").includes(Environment.test, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertFalse(spec.instance("default").includes(Environment.staging, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.prod, Optional.empty()));
+ assertFalse(spec.instance("default").globalServiceId().isPresent());
+ }
+
+ @Test
+ public void testSpecPinningMajorVersion() {
+ String specXml = "<deployment version='1.0' major-version='6'>" +
+ " <test/>" +
+ "</deployment>";
+
+ StringReader r = new StringReader(specXml);
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(specXml, spec.xmlForm());
+ assertEquals(1, spec.steps().size());
+ assertTrue(spec.majorVersion().isPresent());
+ assertEquals(6, (int)spec.majorVersion().get());
+ }
+
+ @Test
+ public void stagingSpec() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <staging/>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(2, spec.steps().size());
+ assertTrue(spec.instance("default").steps().get(0).deploysTo(Environment.test));
+ assertTrue(spec.instance("default").steps().get(1).deploysTo(Environment.staging));
+ assertTrue(spec.instance("default").includes(Environment.test, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.instance("default").includes(Environment.staging, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.prod, Optional.empty()));
+ assertFalse(spec.instance("default").globalServiceId().isPresent());
+ }
+
+ @Test
+ public void minimalProductionSpec() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(4, spec.instance("default").steps().size());
+
+ assertTrue(spec.instance("default").steps().get(0).deploysTo(Environment.test));
+
+ assertTrue(spec.instance("default").steps().get(1).deploysTo(Environment.staging));
+
+ assertTrue(spec.instance("default").steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertFalse(((DeploymentSpec.DeclaredZone)spec.instance("default").steps().get(2)).active());
+
+ assertTrue(spec.instance("default").steps().get(3).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(((DeploymentSpec.DeclaredZone)spec.instance("default").steps().get(3)).active());
+
+ assertTrue(spec.instance("default").includes(Environment.test, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.instance("default").includes(Environment.staging, Optional.empty()));
+ assertTrue(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertFalse(spec.instance("default").globalServiceId().isPresent());
+
+ assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, spec.instance("default").upgradePolicy());
+ }
+
+ @Test
+ public void maximalProductionSpec() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <test/>" +
+ " <staging/>" +
+ " <prod>" +
+ " <region active='false'>us-east1</region>" +
+ " <delay hours='3' minutes='30'/>" +
+ " <region active='true'>us-west1</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(5, spec.instance("default").steps().size());
+ assertEquals(4, spec.instance("default").zones().size());
+
+ assertTrue(spec.instance("default").steps().get(0).deploysTo(Environment.test));
+
+ assertTrue(spec.instance("default").steps().get(1).deploysTo(Environment.staging));
+
+ assertTrue(spec.instance("default").steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertFalse(((DeploymentSpec.DeclaredZone)spec.instance("default").steps().get(2)).active());
+
+ assertTrue(spec.instance("default").steps().get(3) instanceof DeploymentSpec.Delay);
+ assertEquals(3 * 60 * 60 + 30 * 60, spec.instance("default").steps().get(3).delay().getSeconds());
+
+ assertTrue(spec.instance("default").steps().get(4).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertTrue(((DeploymentSpec.DeclaredZone)spec.instance("default").steps().get(4)).active());
+
+ assertTrue(spec.instance("default").includes(Environment.test, Optional.empty()));
+ assertFalse(spec.instance("default").includes(Environment.test, Optional.of(RegionName.from("region1"))));
+ assertTrue(spec.instance("default").includes(Environment.staging, Optional.empty()));
+ assertTrue(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("us-east1"))));
+ assertTrue(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("us-west1"))));
+ assertFalse(spec.instance("default").includes(Environment.prod, Optional.of(RegionName.from("no-such-region"))));
+ assertFalse(spec.instance("default").globalServiceId().isPresent());
+ }
+
+ @Test
+ public void productionSpecWithGlobalServiceId() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <prod global-service-id='query'>" +
+ " <region active='true'>us-east-1</region>" +
+ " <region active='true'>us-west-1</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(spec.instance("default").globalServiceId(), Optional.of("query"));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void globalServiceIdInTest() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <test global-service-id='query' />" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void globalServiceIdInStaging() {
+ StringReader r = new StringReader(
+ "<deployment version='1.0'>" +
+ " <staging global-service-id='query' />" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test
+ public void productionSpecWithGlobalServiceIdBeforeStaging() {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <test/>" +
+ " <prod global-service-id='qrs'>" +
+ " <region active='true'>us-west-1</region>" +
+ " <region active='true'>us-central-1</region>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ " <staging/>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals("qrs", spec.instance("default").globalServiceId().get());
+ }
+
+ @Test
+ public void productionSpecWithUpgradePolicy() {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <upgrade policy='canary'/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " <region active='true'>us-central-1</region>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals("canary", spec.instance("default").upgradePolicy().toString());
+ }
+
+ @Test
+ public void maxDelayExceeded() {
+ try {
+ StringReader r = new StringReader(
+ "<deployment>" +
+ " <upgrade policy='canary'/>" +
+ " <prod>" +
+ " <region active='true'>us-west-1</region>" +
+ " <delay hours='23'/>" +
+ " <region active='true'>us-central-1</region>" +
+ " <delay minutes='59' seconds='61'/>" +
+ " <region active='true'>us-east-3</region>" +
+ " </prod>" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ fail("Expected exception due to exceeding the max total delay");
+ }
+ catch (IllegalArgumentException e) {
+ // success
+ assertEquals("The total delay specified is PT24H1S but max 24 hours is allowed", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testEmpty() {
+ assertFalse(DeploymentSpec.empty.instance("default").globalServiceId().isPresent());
+ assertEquals(DeploymentSpec.UpgradePolicy.defaultPolicy, DeploymentSpec.empty.upgradePolicy());
+ assertTrue(DeploymentSpec.empty.steps().isEmpty());
+ 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.instance("default").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" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ try {
+ DeploymentSpec.fromXml(r);
+ fail("Expected exception");
+ } catch (IllegalArgumentException e) {
+ assertEquals("prod.us-west-1 is listed twice in deployment.xml", e.getMessage());
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void deploymentSpecWithIllegallyOrderedDeploymentSpec1() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ " <block-change days='mon,tue' hours='15-16'/>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void deploymentSpecWithIllegallyOrderedDeploymentSpec2() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
+ " <test/>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test
+ public void deploymentSpecWithChangeBlocker() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <block-change revision='false' days='mon,tue' hours='15-16'/>\n" +
+ " <block-change days='sat' hours='10' time-zone='CET'/>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(2, spec.instance("default").changeBlocker().size());
+ assertTrue(spec.instance("default").changeBlocker().get(0).blocksVersions());
+ assertFalse(spec.instance("default").changeBlocker().get(0).blocksRevisions());
+ assertEquals(ZoneId.of("UTC"), spec.instance("default").changeBlocker().get(0).window().zone());
+
+ assertTrue(spec.instance("default").changeBlocker().get(1).blocksVersions());
+ assertTrue(spec.instance("default").changeBlocker().get(1).blocksRevisions());
+ assertEquals(ZoneId.of("CET"), spec.instance("default").changeBlocker().get(1).window().zone());
+
+ assertTrue(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-18T14:15:30.00Z")));
+ assertFalse(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-18T15:15:30.00Z")));
+ assertFalse(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-18T16:15:30.00Z")));
+ assertTrue(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-18T17:15:30.00Z")));
+
+ assertTrue(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-23T09:15:30.00Z")));
+ assertFalse(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-23T08:15:30.00Z"))); // 10 in CET
+ assertTrue(spec.instance("default").canUpgradeAt(Instant.parse("2017-09-23T10:15:30.00Z")));
+ }
+
+ @Test
+ public void athenz_config_is_read_from_deployment() {
+ StringReader r = new StringReader(
+ "<deployment athenz-domain='domain' athenz-service='service'>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(spec.instance("default").athenzDomain().get().value(), "domain");
+ assertEquals(spec.instance("default").athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "service");
+ }
+
+ @Test
+ public void athenz_service_is_overridden_from_environment() {
+ StringReader r = new StringReader(
+ "<deployment athenz-domain='domain' athenz-service='service'>\n" +
+ " <test/>\n" +
+ " <prod athenz-service='prod-service'>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ assertEquals(spec.instance("default").athenzDomain().get().value(), "domain");
+ assertEquals(spec.instance("default").athenzService(Environment.prod, RegionName.from("us-west-1")).get().value(), "prod-service");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void it_fails_when_athenz_service_is_not_defined() {
+ StringReader r = new StringReader(
+ "<deployment athenz-domain='domain'>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void it_fails_when_athenz_service_is_configured_but_not_athenz_domain() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <prod athenz-service='service'>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec.fromXml(r);
+ }
+
+ @Test
+ public void noNotifications() {
+ assertEquals(Notifications.none(),
+ DeploymentSpec.fromXml("<deployment />").instance("default").notifications());
+ }
+
+ @Test
+ public void emptyNotifications() {
+ DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
+ " <notifications />" +
+ "</deployment>");
+ assertEquals(Notifications.none(), spec.instance("default").notifications());
+ }
+
+ @Test
+ public void someNotifications() {
+ DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
+ " <notifications when=\"failing\">\n" +
+ " <email role=\"author\"/>\n" +
+ " <email address=\"john@dev\" when=\"failing-commit\"/>\n" +
+ " <email address=\"jane@dev\"/>\n" +
+ " </notifications>\n" +
+ "</deployment>");
+ assertEquals(ImmutableSet.of(author), spec.instance("default").notifications().emailRolesFor(failing));
+ assertEquals(ImmutableSet.of(author), spec.instance("default").notifications().emailRolesFor(failingCommit));
+ assertEquals(ImmutableSet.of("john@dev", "jane@dev"), spec.instance("default").notifications().emailAddressesFor(failingCommit));
+ assertEquals(ImmutableSet.of("jane@dev"), spec.instance("default").notifications().emailAddressesFor(failing));
+ }
+
+ @Test
+ public void customTesterFlavor() {
+ DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" +
+ " <test tester-flavor=\"d-1-4-20\" />\n" +
+ " <prod tester-flavor=\"d-2-8-50\">\n" +
+ " <region active=\"false\">us-north-7</region>\n" +
+ " </prod>\n" +
+ "</deployment>");
+ assertEquals(Optional.of("d-1-4-20"), spec.instance("default").steps().get(0).zones().get(0).testerFlavor());
+ assertEquals(Optional.empty(), spec.instance("default").steps().get(1).zones().get(0).testerFlavor());
+ assertEquals(Optional.of("d-2-8-50"), spec.instance("default").steps().get(2).zones().get(0).testerFlavor());
+ }
+
+ @Test
+ public void noEndpoints() {
+ assertEquals(Collections.emptyList(), DeploymentSpec.fromXml("<deployment />").instance("default").endpoints());
+ }
+
+ @Test
+ public void emptyEndpoints() {
+ var spec = DeploymentSpec.fromXml("<deployment><endpoints/></deployment>");
+ assertEquals(Collections.emptyList(), spec.instance("default").endpoints());
+ }
+
+ @Test
+ public void someEndpoints() {
+ var spec = DeploymentSpec.fromXml("" +
+ "<deployment>" +
+ " <prod>" +
+ " <region active=\"true\">us-east</region>" +
+ " </prod>" +
+ " <endpoints>" +
+ " <endpoint id=\"foo\" container-id=\"bar\">" +
+ " <region>us-east</region>" +
+ " </endpoint>" +
+ " <endpoint id=\"nalle\" container-id=\"frosk\" />" +
+ " <endpoint container-id=\"quux\" />" +
+ " </endpoints>" +
+ "</deployment>");
+
+ assertEquals(
+ List.of("foo", "nalle", "default"),
+ spec.instance("default").endpoints().stream().map(Endpoint::endpointId).collect(Collectors.toList())
+ );
+
+ assertEquals(
+ List.of("bar", "frosk", "quux"),
+ spec.instance("default").endpoints().stream().map(Endpoint::containerId).collect(Collectors.toList())
+ );
+
+ assertEquals(Set.of(RegionName.from("us-east")), spec.instance("default").endpoints().get(0).regions());
+ }
+
+ @Test
+ public void endpointDefaultRegions() {
+ var spec = DeploymentSpec.fromXml("" +
+ "<deployment>" +
+ " <prod>" +
+ " <region active=\"true\">us-east</region>" +
+ " <region active=\"true\">us-west</region>" +
+ " </prod>" +
+ " <endpoints>" +
+ " <endpoint id=\"foo\" container-id=\"bar\">" +
+ " <region>us-east</region>" +
+ " </endpoint>" +
+ " <endpoint id=\"nalle\" container-id=\"frosk\" />" +
+ " <endpoint container-id=\"quux\" />" +
+ " </endpoints>" +
+ "</deployment>");
+
+ assertEquals(Set.of("us-east"), endpointRegions("foo", spec));
+ assertEquals(Set.of("us-east", "us-west"), endpointRegions("nalle", spec));
+ assertEquals(Set.of("us-east", "us-west"), endpointRegions("default", spec));
+ }
+
+ private static Set<String> endpointRegions(String endpointId, DeploymentSpec spec) {
+ return spec.instance("default").endpoints().stream()
+ .filter(endpoint -> endpoint.endpointId().equals(endpointId))
+ .flatMap(endpoint -> endpoint.regions().stream())
+ .map(RegionName::value)
+ .collect(Collectors.toSet());
+ }
+
+}