summaryrefslogtreecommitdiffstats
path: root/config-model-api
diff options
context:
space:
mode:
authorjonmv <venstad@gmail.com>2023-06-02 16:19:43 +0200
committerjonmv <venstad@gmail.com>2023-06-02 16:19:43 +0200
commita82b87143f8dcc1218a0ce5718fcf09c73c1485c (patch)
tree91aa7ec0c267471601b280fd623902fa9e28f452 /config-model-api
parentad9ca41f6d7e54abbce9b279d341f0efaa802781 (diff)
Allow parallell cloud accounts in dep-spec, and simply some usages
Diffstat (limited to 'config-model-api')
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentInstanceSpec.java28
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java61
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java51
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java103
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java28
5 files changed, 129 insertions, 142 deletions
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 fc170db5897..9ce1a48f226 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
@@ -4,6 +4,7 @@ package com.yahoo.config.application.api;
import ai.vespa.validation.Validation;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
@@ -59,7 +60,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
private final List<DeploymentSpec.ChangeBlocker> changeBlockers;
private final Optional<String> globalServiceId;
private final Optional<AthenzService> athenzService;
- private final Optional<CloudAccount> cloudAccount;
+ private final Map<CloudName, CloudAccount> cloudAccounts;
private final Optional<Duration> hostTTL;
private final Notifications notifications;
private final List<Endpoint> endpoints;
@@ -77,7 +78,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
List<DeploymentSpec.ChangeBlocker> changeBlockers,
Optional<String> globalServiceId,
Optional<AthenzService> athenzService,
- Optional<CloudAccount> cloudAccount,
+ Map<CloudName, CloudAccount> cloudAccounts,
Optional<Duration> hostTTL,
Notifications notifications,
List<Endpoint> endpoints,
@@ -101,7 +102,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
this.changeBlockers = Objects.requireNonNull(changeBlockers);
this.globalServiceId = Objects.requireNonNull(globalServiceId);
this.athenzService = Objects.requireNonNull(athenzService);
- this.cloudAccount = Objects.requireNonNull(cloudAccount);
+ this.cloudAccounts = Map.copyOf(cloudAccounts);
this.hostTTL = Objects.requireNonNull(hostTTL);
this.notifications = Objects.requireNonNull(notifications);
this.endpoints = List.copyOf(Objects.requireNonNull(endpoints));
@@ -113,10 +114,7 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
validateEndpoints(globalServiceId, this.endpoints);
validateChangeBlockers(changeBlockers, now);
validateBcp(bcp);
- hostTTL.ifPresent(ttl -> {
- if (cloudAccount.isEmpty()) illegal("Host TTL can only be specified with custom cloud accounts");
- if (ttl.isNegative()) illegal("Host TTL cannot be negative");
- });
+ hostTTL.filter(Duration::isNegative).ifPresent(ttl -> illegal("Host TTL cannot be negative"));
}
public InstanceName name() { return name; }
@@ -266,22 +264,22 @@ public class DeploymentInstanceSpec extends DeploymentSpec.Steps {
.filter(zone -> zone.concerns(environment, Optional.of(region)))
.findFirst()
.flatMap(DeploymentSpec.DeclaredZone::athenzService)
- .or(() -> this.athenzService);
+ .or(() -> athenzService);
}
- /** Returns the cloud account to use for given environment and region, if any */
- public Optional<CloudAccount> cloudAccount(Environment environment, Optional<RegionName> region) {
+ /** Returns the cloud accounts to use for given environment and region, if any */
+ public Map<CloudName, CloudAccount> cloudAccounts(Environment environment, RegionName region) {
return zones().stream()
- .filter(zone -> zone.concerns(environment, region))
+ .filter(zone -> zone.concerns(environment, Optional.of(region)))
.findFirst()
- .flatMap(DeploymentSpec.DeclaredZone::cloudAccount)
- .or(() -> cloudAccount);
+ .map(DeploymentSpec.DeclaredZone::cloudAccounts)
+ .orElse(cloudAccounts);
}
/** Returns the host TTL to use for given environment and region, if any */
- public Optional<Duration> hostTTL(Environment environment, Optional<RegionName> region) {
+ public Optional<Duration> hostTTL(Environment environment, RegionName region) {
return zones().stream()
- .filter(zone -> zone.concerns(environment, region))
+ .filter(zone -> zone.concerns(environment, Optional.of(region)))
.findFirst()
.flatMap(DeploymentSpec.DeclaredZone::hostTTL)
.or(() -> hostTTL);
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 ec79f9e554a..5e79ceb738f 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
@@ -7,6 +7,7 @@ import com.yahoo.config.application.api.xml.DeploymentSpecXmlReader;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
@@ -45,7 +46,7 @@ public class DeploymentSpec {
Optional.empty(),
Optional.empty(),
Optional.empty(),
- Optional.empty(),
+ Map.of(),
Optional.empty(),
List.of(),
"<deployment version='1.0'/>",
@@ -57,7 +58,7 @@ public class DeploymentSpec {
private final Optional<Integer> majorVersion;
private final Optional<AthenzDomain> athenzDomain;
private final Optional<AthenzService> athenzService;
- private final Optional<CloudAccount> cloudAccount;
+ private final Map<CloudName, CloudAccount> cloudAccounts;
private final Optional<Duration> hostTTL;
private final List<Endpoint> endpoints;
private final List<DeprecatedElement> deprecatedElements;
@@ -68,7 +69,7 @@ public class DeploymentSpec {
Optional<Integer> majorVersion,
Optional<AthenzDomain> athenzDomain,
Optional<AthenzService> athenzService,
- Optional<CloudAccount> cloudAccount,
+ Map<CloudName, CloudAccount> cloudAccounts,
Optional<Duration> hostTTL,
List<Endpoint> endpoints,
String xmlForm,
@@ -77,7 +78,7 @@ public class DeploymentSpec {
this.majorVersion = Objects.requireNonNull(majorVersion);
this.athenzDomain = Objects.requireNonNull(athenzDomain);
this.athenzService = Objects.requireNonNull(athenzService);
- this.cloudAccount = Objects.requireNonNull(cloudAccount);
+ this.cloudAccounts = Map.copyOf(cloudAccounts);
this.hostTTL = Objects.requireNonNull(hostTTL);
this.xmlForm = Objects.requireNonNull(xmlForm);
this.endpoints = List.copyOf(Objects.requireNonNull(endpoints));
@@ -86,10 +87,7 @@ public class DeploymentSpec {
validateUpgradePoliciesOfIncreasingConservativeness(steps);
validateAthenz();
validateApplicationEndpoints();
- hostTTL.ifPresent(ttl -> {
- if (cloudAccount.isEmpty()) illegal("Host TTL can only be specified with custom cloud accounts");
- if (ttl.isNegative()) illegal("Host TTL cannot be negative");
- });
+ hostTTL.filter(Duration::isNegative).ifPresent(ttl -> illegal("Host TTL cannot be negative"));
}
public boolean isEmpty() { return this == empty; }
@@ -189,8 +187,20 @@ public class DeploymentSpec {
// to have environment, instance or region variants on those.
public Optional<AthenzService> athenzService() { return athenzService; }
- /** Cloud account set on the deployment root; see discussion for {@link #athenzService}. */
- public Optional<CloudAccount> cloudAccount() { return cloudAccount; }
+ /** The most specific Athenz service for the given arguments. */
+ public Optional<AthenzService> athenzService(InstanceName instance, Environment environment, RegionName region) {
+ return instance(instance).flatMap(spec -> spec.athenzService(environment, region))
+ .or(this::athenzService);
+ }
+
+ /** The most specific Cloud account for the given arguments. */
+ public CloudAccount cloudAccount(CloudName cloud, InstanceName instance, ZoneId zone) {
+ return instance(instance).map(spec -> spec.cloudAccounts(zone.environment(), zone.region()))
+ .orElse(cloudAccounts)
+ .getOrDefault(cloud, CloudAccount.empty);
+ }
+
+ public Map<CloudName, CloudAccount> cloudAccounts() { return cloudAccounts; }
/**
* Additional host time-to-live for this application. Requires a custom cloud account to be set.
@@ -198,6 +208,11 @@ public class DeploymentSpec {
* allowed remain empty, before being deprovisioned. This is useful for applications which frequently
* deploy to, e.g., test and staging zones, and want to avoid the delay of having to provision hosts.
*/
+ public Optional<Duration> hostTTL(InstanceName instance, Environment environment, RegionName region) {
+ return instance(instance).flatMap(spec -> spec.hostTTL(environment, region))
+ .or(this::hostTTL);
+ }
+
public Optional<Duration> hostTTL() { return hostTTL; }
/**
@@ -421,31 +436,28 @@ public class DeploymentSpec {
private final boolean active;
private final Optional<AthenzService> athenzService;
private final Optional<String> testerFlavor;
- private final Optional<CloudAccount> cloudAccount;
+ private final Map<CloudName, CloudAccount> cloudAccounts;
private final Optional<Duration> hostTTL;
public DeclaredZone(Environment environment) {
- this(environment, Optional.empty(), false, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
+ this(environment, Optional.empty(), false, Optional.empty(), Optional.empty(), Map.of(), Optional.empty());
}
public DeclaredZone(Environment environment, Optional<RegionName> region, boolean active,
Optional<AthenzService> athenzService, Optional<String> testerFlavor,
- Optional<CloudAccount> cloudAccount, Optional<Duration> hostTTL) {
+ Map<CloudName, CloudAccount> cloudAccounts, Optional<Duration> hostTTL) {
if (environment != Environment.prod && region.isPresent())
illegal("Non-prod environments cannot specify a region");
if (environment == Environment.prod && region.isEmpty())
illegal("Prod environments must be specified with a region");
+ hostTTL.filter(Duration::isNegative).ifPresent(ttl -> illegal("Host TTL cannot be negative"));
this.environment = Objects.requireNonNull(environment);
this.region = Objects.requireNonNull(region);
this.active = active;
this.athenzService = Objects.requireNonNull(athenzService);
this.testerFlavor = Objects.requireNonNull(testerFlavor);
- this.cloudAccount = Objects.requireNonNull(cloudAccount);
+ this.cloudAccounts = Map.copyOf(cloudAccounts);
this.hostTTL = Objects.requireNonNull(hostTTL);
- hostTTL.ifPresent(ttl -> {
- if (cloudAccount.isEmpty()) illegal("Host TTL can only be specified with custom cloud accounts");
- if (ttl.isNegative()) illegal("Host TTL cannot be negative");
- });
}
public Environment environment() { return environment; }
@@ -458,11 +470,9 @@ public class DeploymentSpec {
public Optional<String> testerFlavor() { return testerFlavor; }
- public Optional<AthenzService> athenzService() { return athenzService; }
+ Optional<AthenzService> athenzService() { return athenzService; }
- public Optional<CloudAccount> cloudAccount() {
- return cloudAccount;
- }
+ Map<CloudName, CloudAccount> cloudAccounts() { return cloudAccounts; }
@Override
public List<DeclaredZone> zones() { return List.of(this); }
@@ -510,13 +520,10 @@ public class DeploymentSpec {
private final RegionName region;
private final Optional<Duration> hostTTL;
- public DeclaredTest(RegionName region, Optional<CloudAccount> cloudAccount, Optional<Duration> hostTTL) {
+ public DeclaredTest(RegionName region, Optional<Duration> hostTTL) {
this.region = Objects.requireNonNull(region);
this.hostTTL = Objects.requireNonNull(hostTTL);
- hostTTL.ifPresent(ttl -> {
- if (cloudAccount.isEmpty()) illegal("Host TTL can only be specified with custom cloud accounts");
- if (ttl.isNegative()) illegal("Host TTL cannot be negative");
- });
+ hostTTL.filter(Duration::isNegative).ifPresent(ttl -> illegal("Host TTL cannot be negative"));
}
@Override
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 43281c90efe..38562eefb03 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
@@ -25,6 +25,7 @@ import com.yahoo.config.application.api.TimeWindow;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
@@ -165,7 +166,7 @@ public class DeploymentSpecXmlReader {
optionalIntegerAttribute(majorVersionAttribute, root),
stringAttribute(athenzDomainAttribute, root).map(AthenzDomain::from),
stringAttribute(athenzServiceAttribute, root).map(AthenzService::from),
- stringAttribute(cloudAccountAttribute, root).map(CloudAccount::from),
+ readCloudAccounts(root),
stringAttribute(hostTTLAttribute, root).map(s -> toDuration(s, "empty host TTL")),
applicationEndpoints,
xmlForm,
@@ -205,7 +206,7 @@ public class DeploymentSpecXmlReader {
int maxIdleHours = getWithFallback(instanceElement, parentTag, upgradeTag, "max-idle-hours", Integer::parseInt, 8);
List<DeploymentSpec.ChangeBlocker> changeBlockers = readChangeBlockers(instanceElement, parentTag);
Optional<AthenzService> athenzService = mostSpecificAttribute(instanceElement, athenzServiceAttribute).map(AthenzService::from);
- Optional<CloudAccount> cloudAccount = readCloudAccount(instanceElement);
+ Map<CloudName, CloudAccount> cloudAccounts = readCloudAccounts(instanceElement);
Optional<Duration> hostTTL = mostSpecificAttribute(instanceElement, hostTTLAttribute).map(s -> toDuration(s, "empty host TTL"));
Notifications notifications = readNotifications(instanceElement, parentTag);
@@ -235,7 +236,7 @@ public class DeploymentSpecXmlReader {
changeBlockers,
Optional.ofNullable(prodAttributes.get(globalServiceIdAttribute)),
athenzService,
- cloudAccount,
+ cloudAccounts,
hostTTL,
notifications,
endpoints,
@@ -277,10 +278,10 @@ public class DeploymentSpecXmlReader {
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()), readCloudAccount(stepTag), readHostTTL(stepTag))); // A production test
+ return List.of(new DeclaredTest(RegionName.from(XML.getValue(stepTag).trim()), readHostTTL(stepTag))); // A production test
}
case devTag, perfTag, stagingTag: // Intentional fallthrough from test tag.
- return List.of(new DeclaredZone(Environment.from(stepTag.getTagName()), Optional.empty(), false, athenzService, testerFlavor, readCloudAccount(stepTag), readHostTTL(stepTag)));
+ return List.of(new DeclaredZone(Environment.from(stepTag.getTagName()), Optional.empty(), false, athenzService, testerFlavor, readCloudAccounts(stepTag), readHostTTL(stepTag)));
case prodTag: // regions, delay and parallel may be nested within, but we can flatten them
return XML.getChildren(stepTag).stream()
.flatMap(child -> readNonInstanceSteps(child, prodAttributes, stepTag, defaultBcp).stream())
@@ -670,8 +671,13 @@ public class DeploymentSpecXmlReader {
/** Returns the given non-blank attribute of tag as a string, if any */
private static Optional<String> stringAttribute(String attributeName, Element tag) {
+ return stringAttribute(attributeName, tag, true);
+ }
+
+ /** Returns the given non-blank attribute of tag as a string, if any */
+ private static Optional<String> stringAttribute(String attributeName, Element tag, boolean ignoreBlanks) {
String value = tag.getAttribute(attributeName);
- return Optional.of(value).filter(s -> !s.isBlank());
+ return Optional.of(value).filter(s -> (tag.getAttributeNode(attributeName) != null && ! ignoreBlanks || ! s.isBlank()));
}
/** Returns the given non-blank attribute of tag or throw */
@@ -685,11 +691,23 @@ public class DeploymentSpecXmlReader {
Optional<String> testerFlavor, Element regionTag) {
return new DeclaredZone(environment, Optional.of(RegionName.from(XML.getValue(regionTag).trim())),
readActive(regionTag), athenzService, testerFlavor,
- readCloudAccount(regionTag), readHostTTL(regionTag));
- }
-
- private Optional<CloudAccount> readCloudAccount(Element tag) {
- return mostSpecificAttribute(tag, cloudAccountAttribute).map(CloudAccount::from);
+ readCloudAccounts(regionTag), readHostTTL(regionTag));
+ }
+
+ private Map<CloudName, CloudAccount> readCloudAccounts(Element tag) {
+ return mostSpecificAttribute(tag, cloudAccountAttribute, false)
+ .map(value -> {
+ Map<CloudName, CloudAccount> accounts = new HashMap<>();
+ for (String part : value.split(",")) {
+ CloudAccount account = CloudAccount.from(part);
+ accounts.merge(account.cloudName(), account, (o, n) -> {
+ throw illegal("both '" + o.account() + "' and '" + n.account() + "' " +
+ "are declared for cloud '" + o.cloudName() + "', in '" + value + "'");
+ });
+ }
+ return accounts;
+ })
+ .orElse(Map.of());
}
private Optional<Duration> readHostTTL(Element tag) {
@@ -802,14 +820,19 @@ public class DeploymentSpecXmlReader {
}
/** Returns the given attribute from the given tag or its closest ancestor with the attribute. */
- private static Optional<String> mostSpecificAttribute(Element tag, String attributeName) {
+ private static Optional<String> mostSpecificAttribute(Element tag, String attributeName, boolean ignoreBlanks) {
return Stream.iterate(tag, Objects::nonNull, Node::getParentNode)
.filter(Element.class::isInstance)
.map(Element.class::cast)
- .flatMap(element -> stringAttribute(attributeName, element).stream())
+ .flatMap(element -> stringAttribute(attributeName, element, ignoreBlanks).stream())
.findFirst();
}
+ /** Returns the given attribute from the given tag or its closest ancestor with the attribute. */
+ private static Optional<String> mostSpecificAttribute(Element tag, String attributeName) {
+ return mostSpecificAttribute(tag, attributeName, true);
+ }
+
/**
* Returns a string consisting of a number followed by "m", "h" or "d" to a duration given in that unit,
* or zero duration if null or blank.
@@ -851,7 +874,7 @@ public class DeploymentSpecXmlReader {
}
}
- private static void illegal(String message) {
+ private static IllegalArgumentException illegal(String message) {
throw new IllegalArgumentException(message);
}
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 721c327e5d4..d4312a0e54e 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
@@ -6,6 +6,7 @@ import com.yahoo.config.application.api.Endpoint.Level;
import com.yahoo.config.application.api.Endpoint.Target;
import com.yahoo.config.application.api.xml.DeploymentSpecXmlReader;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
@@ -25,6 +26,7 @@ import java.time.ZoneId;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -32,6 +34,8 @@ 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 com.yahoo.config.provision.CloudName.AWS;
+import static com.yahoo.config.provision.CloudName.GCP;
import static com.yahoo.config.provision.Environment.dev;
import static com.yahoo.config.provision.Environment.perf;
import static com.yahoo.config.provision.Environment.prod;
@@ -1757,18 +1761,19 @@ public class DeploymentSpecTest {
public void cloudAccount() {
String r =
"""
- <deployment version='1.0' cloud-account='100000000000'>
+ <deployment version='1.0' cloud-account='100000000000,gcp:foobar'>
<instance id='alpha'>
<prod cloud-account='800000000000'>
<region>us-east-1</region>
</prod>
</instance>
<instance id='beta' cloud-account='200000000000'>
- <staging cloud-account='600000000000'/>
+ <staging cloud-account='gcp:barbaz'/>
<perf cloud-account='700000000000'/>
<prod>
<region>us-west-1</region>
<region cloud-account='default'>us-west-2</region>
+ <region cloud-account=''>us-west-3</region>
</prod>
</instance>
<instance id='main'>
@@ -1782,18 +1787,24 @@ public class DeploymentSpecTest {
</deployment>
""";
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(Optional.of(CloudAccount.from("100000000000")), spec.cloudAccount());
- assertCloudAccount("800000000000", spec.requireInstance("alpha"), prod, "us-east-1");
- assertCloudAccount("200000000000", spec.requireInstance("beta"), prod, "us-west-1");
- assertCloudAccount("600000000000", spec.requireInstance("beta"), staging, "");
- assertCloudAccount("700000000000", spec.requireInstance("beta"), perf, "");
- assertCloudAccount("200000000000", spec.requireInstance("beta"), dev, "");
- assertCloudAccount("300000000000", spec.requireInstance("main"), prod, "us-east-1");
- assertCloudAccount("100000000000", spec.requireInstance("main"), prod, "eu-west-1");
- assertCloudAccount("400000000000", spec.requireInstance("main"), dev, "");
- assertCloudAccount("500000000000", spec.requireInstance("main"), test, "");
- assertCloudAccount("100000000000", spec.requireInstance("main"), staging, "");
- assertCloudAccount("default", spec.requireInstance("beta"), prod, "us-west-2");
+ assertEquals(Map.of(AWS, CloudAccount.from("100000000000"),
+ GCP, CloudAccount.from("gcp:foobar")), spec.cloudAccounts());
+ assertCloudAccount("800000000000", spec, AWS, "alpha", prod, "us-east-1");
+ assertCloudAccount("", spec, GCP, "alpha", prod, "us-east-1");
+ assertCloudAccount("200000000000", spec, AWS, "beta", prod, "us-west-1");
+ assertCloudAccount("", spec, AWS, "beta", staging, "default");
+ assertCloudAccount("gcp:barbaz", spec, GCP, "beta", staging, "default");
+ assertCloudAccount("700000000000", spec, AWS, "beta", perf, "default");
+ assertCloudAccount("200000000000", spec, AWS, "beta", dev, "default");
+ assertCloudAccount("300000000000", spec, AWS, "main", prod, "us-east-1");
+ assertCloudAccount("100000000000", spec, AWS, "main", prod, "eu-west-1");
+ assertCloudAccount("400000000000", spec, AWS, "main", dev, "default");
+ assertCloudAccount("500000000000", spec, AWS, "main", test, "default");
+ assertCloudAccount("100000000000", spec, AWS, "main", staging, "default");
+ assertCloudAccount("default", spec, AWS, "beta", prod, "us-west-2");
+ assertCloudAccount("", spec, GCP, "beta", prod, "us-west-2");
+ assertCloudAccount("", spec, AWS, "beta", prod, "us-west-3");
+ assertCloudAccount("", spec, GCP, "beta", prod, "us-west-3");
}
@Test
@@ -1827,7 +1838,7 @@ public class DeploymentSpecTest {
</deployment>
""";
DeploymentSpec spec = DeploymentSpec.fromXml(r);
- assertEquals(Optional.of(CloudAccount.from("100000000000")), spec.cloudAccount());
+ assertEquals(Map.of(AWS, CloudAccount.from("100000000000")), spec.cloudAccounts());
assertHostTTL(Duration.ofHours(1), spec, "alpha", test, null);
assertHostTTL(Duration.ofHours(1), spec, "alpha", staging, null);
@@ -1864,69 +1875,15 @@ public class DeploymentSpecTest {
assertHostTTL(Duration.ofHours(1), spec, "nope", perf, null);
assertHostTTL(Duration.ofHours(1), spec, "nope", prod, "us-east");
assertHostTTL(Duration.ofHours(1), spec, "nope", prod, "us-west");
-
- assertEquals("Host TTL can only be specified with custom cloud accounts",
- assertThrows(IllegalArgumentException.class,
- () -> DeploymentSpec.fromXml("""
- <deployment version='1.0' empty-host-ttl='0m'>
- <instance id='main'>
- <prod>
- <region>us-east-1</region>
- </prod>
- </instance>
- </deployment>
- """))
- .getMessage());
-
- assertEquals("Host TTL can only be specified with custom cloud accounts",
- assertThrows(IllegalArgumentException.class,
- () -> DeploymentSpec.fromXml("""
- <deployment version='1.0'>
- <instance id='main' empty-host-ttl='0m'>
- <prod>
- <region>us-east-1</region>
- </prod>
- </instance>
- </deployment>
- """))
- .getMessage());
-
- assertEquals("Host TTL can only be specified with custom cloud accounts",
- assertThrows(IllegalArgumentException.class,
- () -> DeploymentSpec.fromXml("""
- <deployment version='1.0'>
- <instance id='main'>
- <prod empty-host-ttl='0m'>
- <region>us-east-1</region>
- </prod>
- </instance>
- </deployment>
- """))
- .getMessage());
-
- assertEquals("Host TTL can only be specified with custom cloud accounts",
- assertThrows(IllegalArgumentException.class,
- () -> DeploymentSpec.fromXml("""
- <deployment version='1.0'>
- <instance id='main'>
- <prod>
- <region empty-host-ttl='0m'>us-east-1</region>
- </prod>
- </instance>
- </deployment>
- """))
- .getMessage());
-
}
- private void assertCloudAccount(String expected, DeploymentInstanceSpec instance, Environment environment, String region) {
- assertEquals(Optional.of(expected).map(CloudAccount::from), instance.cloudAccount(environment, Optional.of(region).filter(s -> !s.isEmpty()).map(RegionName::from)));
+ private void assertCloudAccount(String expected, DeploymentSpec spec, CloudName cloud, String instance, Environment environment, String region) {
+ assertEquals(CloudAccount.from(expected),
+ spec.cloudAccount(cloud, InstanceName.from(instance), com.yahoo.config.provision.zone.ZoneId.from(environment, RegionName.from(region))));
}
private void assertHostTTL(Duration expected, DeploymentSpec spec, String instance, Environment environment, String region) {
- assertEquals(Optional.of(expected), spec.instance(InstanceName.from(instance))
- .flatMap(iSpec -> iSpec.hostTTL(environment, Optional.ofNullable(region).map(RegionName::from)))
- .or(spec::hostTTL));
+ assertEquals(Optional.of(expected), spec.hostTTL(InstanceName.from(instance), environment, region == null ? RegionName.defaultName() : RegionName.from(region)));
}
private static void assertInvalid(String deploymentSpec, String errorMessagePart) {
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 ed8074ddc61..013c161ad9b 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
@@ -18,6 +18,7 @@ import java.time.Instant;
import java.time.ZoneId;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -25,6 +26,7 @@ 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 com.yahoo.config.provision.CloudName.AWS;
import static com.yahoo.config.provision.Environment.dev;
import static com.yahoo.config.provision.Environment.prod;
import static com.yahoo.config.provision.Environment.test;
@@ -744,10 +746,10 @@ public class DeploymentSpecWithoutInstanceTest {
);
DeploymentSpec spec = DeploymentSpec.fromXml(r);
DeploymentInstanceSpec instance = spec.requireInstance("default");
- assertEquals(Optional.of(CloudAccount.from("012345678912")), spec.cloudAccount());
- assertEquals(Optional.of(CloudAccount.from("219876543210")), instance.cloudAccount(prod, Optional.of(RegionName.from("us-east-1"))));
- assertEquals(Optional.of(CloudAccount.from("012345678912")), instance.cloudAccount(prod, Optional.of(RegionName.from("us-west-1"))));
- assertEquals(Optional.of(CloudAccount.from("012345678912")), instance.cloudAccount(Environment.staging, Optional.empty()));
+ assertEquals(Map.of(AWS, CloudAccount.from("012345678912")), spec.cloudAccounts());
+ assertEquals(Map.of(AWS, CloudAccount.from("219876543210")), instance.cloudAccounts(prod, RegionName.from("us-east-1")));
+ assertEquals(Map.of(AWS, CloudAccount.from("012345678912")), instance.cloudAccounts(prod, RegionName.from("us-west-1")));
+ assertEquals(Map.of(AWS, CloudAccount.from("012345678912")), instance.cloudAccounts(Environment.staging, RegionName.defaultName()));
r = new StringReader(
"<deployment version='1.0'>" +
@@ -758,9 +760,9 @@ public class DeploymentSpecWithoutInstanceTest {
"</deployment>"
);
spec = DeploymentSpec.fromXml(r);
- assertEquals(Optional.empty(), spec.cloudAccount());
- assertEquals(Optional.of(CloudAccount.from("219876543210")), spec.requireInstance("default").cloudAccount(prod, Optional.of(RegionName.from("us-east-1"))));
- assertEquals(Optional.empty(), spec.requireInstance("default").cloudAccount(prod, Optional.of(RegionName.from("us-west-1"))));
+ assertEquals(Map.of(), spec.cloudAccounts());
+ assertEquals(Map.of(AWS, CloudAccount.from("219876543210")), spec.requireInstance("default").cloudAccounts(prod, RegionName.from("us-east-1")));
+ assertEquals(Map.of(), spec.requireInstance("default").cloudAccounts(prod, RegionName.from("us-west-1")));
}
@Test
@@ -776,9 +778,9 @@ public class DeploymentSpecWithoutInstanceTest {
DeploymentSpec spec = DeploymentSpec.fromXml(r);
assertEquals(Optional.of(Duration.ofDays(1)), spec.hostTTL());
DeploymentInstanceSpec instance = spec.requireInstance("default");
- assertEquals(Optional.of(Duration.ofMinutes(1)), instance.hostTTL(prod, Optional.of(RegionName.from("us-east-1"))));
- assertEquals(Optional.of(Duration.ofDays(1)), instance.hostTTL(prod, Optional.of(RegionName.from("us-west-1"))));
- assertEquals(Optional.of(Duration.ofDays(1)), instance.hostTTL(test, Optional.empty()));
+ assertEquals(Optional.of(Duration.ofMinutes(1)), instance.hostTTL(prod, RegionName.from("us-east-1")));
+ assertEquals(Optional.of(Duration.ofDays(1)), instance.hostTTL(prod, RegionName.from("us-west-1")));
+ assertEquals(Optional.of(Duration.ofDays(1)), instance.hostTTL(test, RegionName.defaultName()));
r = """
<deployment version='1.0' cloud-account='012345678912'>
@@ -791,9 +793,9 @@ public class DeploymentSpecWithoutInstanceTest {
spec = DeploymentSpec.fromXml(r);
assertEquals(Optional.empty(), spec.hostTTL());
instance = spec.requireInstance("default");
- assertEquals(Optional.of(Duration.ofMinutes(1)), instance.hostTTL(prod, Optional.of(RegionName.from("us-east-1"))));
- assertEquals(Optional.of(Duration.ofDays(1)), instance.hostTTL(prod, Optional.of(RegionName.from("us-west-1"))));
- assertEquals(Optional.empty(), instance.hostTTL(test, Optional.empty()));
+ assertEquals(Optional.of(Duration.ofMinutes(1)), instance.hostTTL(prod, RegionName.from("us-east-1")));
+ assertEquals(Optional.of(Duration.ofDays(1)), instance.hostTTL(prod, RegionName.from("us-west-1")));
+ assertEquals(Optional.empty(), instance.hostTTL(test, RegionName.defaultName()));
}
private static Set<String> endpointRegions(String endpointId, DeploymentSpec spec) {