diff options
author | jonmv <venstad@gmail.com> | 2023-06-02 16:19:43 +0200 |
---|---|---|
committer | jonmv <venstad@gmail.com> | 2023-06-02 16:19:43 +0200 |
commit | a82b87143f8dcc1218a0ce5718fcf09c73c1485c (patch) | |
tree | 91aa7ec0c267471601b280fd623902fa9e28f452 /config-model-api | |
parent | ad9ca41f6d7e54abbce9b279d341f0efaa802781 (diff) |
Allow parallell cloud accounts in dep-spec, and simply some usages
Diffstat (limited to 'config-model-api')
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) { |