diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-11-06 14:31:10 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-11-06 14:31:10 +0100 |
commit | 4187b1391bf383028a3ffd1480aacd70c1db287b (patch) | |
tree | 29d6249bed368f51752941c7207e94315305e736 | |
parent | b678895ffe2a92cbcf720a698e2a58f1d22c2d9a (diff) | |
parent | aeedefb11672970b69f97cbe9307aba9180cf620 (diff) |
Merge pull request #11216 from vespa-engine/jvenstad/remove-more-deprecated-deployment-spec-usage
Jvenstad/remove more deprecated deployment spec usage
35 files changed, 324 insertions, 724 deletions
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json index f4796372722..edf83fe4475 100644 --- a/config-model-api/abi-spec.json +++ b/config-model-api/abi-spec.json @@ -239,8 +239,6 @@ ], "methods": [ "public void <init>(com.yahoo.config.provision.Environment)", - "public void <init>(com.yahoo.config.provision.Environment, java.util.Optional, boolean)", - "public void <init>(com.yahoo.config.provision.Environment, java.util.Optional, boolean, java.util.Optional)", "public void <init>(com.yahoo.config.provision.Environment, java.util.Optional, boolean, java.util.Optional, java.util.Optional)", "public com.yahoo.config.provision.Environment environment()", "public java.util.Optional region()", @@ -263,7 +261,6 @@ ], "methods": [ "public void <init>(java.time.Duration)", - "public java.time.Duration duration()", "public java.time.Duration delay()", "public boolean deploysTo(com.yahoo.config.provision.Environment, java.util.Optional)", "public java.lang.String toString()" @@ -330,15 +327,12 @@ ], "methods": [ "public void <init>(java.util.List, java.util.Optional, java.util.Optional, java.util.Optional, java.lang.String)", - "public void <init>(java.util.Optional, com.yahoo.config.application.api.DeploymentSpec$UpgradePolicy, java.util.Optional, java.util.List, java.util.List, java.lang.String, java.util.Optional, java.util.Optional, com.yahoo.config.application.api.Notifications, java.util.List)", "public java.util.Optional majorVersion()", "public java.util.List steps()", - "public java.util.List zones()", "public java.util.Optional athenzDomain()", "public java.util.Optional athenzService()", "public java.util.Optional athenzService(com.yahoo.config.provision.InstanceName, com.yahoo.config.provision.Environment, com.yahoo.config.provision.RegionName)", "public java.lang.String xmlForm()", - "public boolean includes(com.yahoo.config.provision.Environment, java.util.Optional)", "public java.util.Optional instance(com.yahoo.config.provision.InstanceName)", "public com.yahoo.config.application.api.DeploymentInstanceSpec requireInstance(java.lang.String)", "public com.yahoo.config.application.api.DeploymentInstanceSpec requireInstance(com.yahoo.config.provision.InstanceName)", @@ -349,8 +343,7 @@ "public static com.yahoo.config.application.api.DeploymentSpec fromXml(java.lang.String, boolean)", "public static java.lang.String toMessageString(java.lang.Throwable)", "public boolean equals(java.lang.Object)", - "public int hashCode()", - "public static void main(java.lang.String[])" + "public int hashCode()" ], "fields": [ "public static final com.yahoo.config.application.api.DeploymentSpec empty" 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 25fd766c8c2..ea7df677e0f 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 @@ -9,11 +9,8 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; -import java.io.BufferedReader; -import java.io.FileReader; import java.io.Reader; import java.time.Duration; -import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -36,16 +33,19 @@ import java.util.stream.Collectors; public class DeploymentSpec { /** The empty deployment spec, specifying no zones or rotation, and defaults for all settings */ - public static final DeploymentSpec empty = new DeploymentSpec(Optional.empty(), - UpgradePolicy.defaultPolicy, + public static final DeploymentSpec empty = new DeploymentSpec(List.of(new DeploymentInstanceSpec(InstanceName.from("default"), + Collections.emptyList(), + UpgradePolicy.defaultPolicy, + Collections.emptyList(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Notifications.none(), + List.of())), Optional.empty(), - Collections.emptyList(), - Collections.emptyList(), - "<deployment version='1.0'/>", Optional.empty(), Optional.empty(), - Notifications.none(), - List.of()); + "<deployment version='1.0'/>"); private final List<Step> steps; @@ -61,13 +61,7 @@ public class DeploymentSpec { Optional<AthenzDomain> athenzDomain, Optional<AthenzService> athenzService, String xmlForm) { - if (hasSingleInstance(steps)) { // TODO: Remove this clause after November 2019 - var singleInstance = singleInstance(steps); - this.steps = List.of(singleInstance.withSteps(completeSteps(singleInstance.steps()))); - } - else { - this.steps = List.copyOf(completeSteps(steps)); - } + this.steps = List.copyOf(completeSteps(steps)); this.majorVersion = majorVersion; this.athenzDomain = athenzDomain; this.athenzService = athenzService; @@ -76,27 +70,6 @@ public class DeploymentSpec { validateUpgradePoliciesOfIncreasingConservativeness(steps); } - // TODO: Remove after October 2019 - public DeploymentSpec(Optional<String> globalServiceId, UpgradePolicy upgradePolicy, Optional<Integer> majorVersion, - List<ChangeBlocker> changeBlockers, List<Step> steps, String xmlForm, - Optional<AthenzDomain> athenzDomain, Optional<AthenzService> athenzService, - Notifications notifications, - List<Endpoint> endpoints) { - this(List.of(new DeploymentInstanceSpec(InstanceName.from("default"), - steps, - upgradePolicy, - changeBlockers, - globalServiceId, - athenzDomain, - athenzService, - notifications, - endpoints)), - majorVersion, - athenzDomain, - athenzService, - xmlForm); - } - /** Adds missing required steps and reorders steps to a permissible order */ private static List<DeploymentSpec.Step> completeSteps(List<DeploymentSpec.Step> inputSteps) { List<Step> steps = new ArrayList<>(inputSteps); @@ -166,36 +139,14 @@ public class DeploymentSpec { } } - // TODO: Remove after October 2019 - private DeploymentInstanceSpec singleInstance() { - return singleInstance(steps); - } - - // TODO: Remove after October 2019 - private static DeploymentInstanceSpec singleInstance(List<DeploymentSpec.Step> steps) { - List<DeploymentInstanceSpec> instances = instances(steps); - if (instances.size() == 1) return instances.get(0); - throw new IllegalArgumentException("This deployment spec does not support the legacy API " + - "as it has multiple instances: " + - instances.stream().map(Step::toString).collect(Collectors.joining(","))); - } - /** Returns the major version this application is pinned to, or empty (default) to allow all major versions */ public Optional<Integer> majorVersion() { return majorVersion; } /** Returns the deployment steps of this in the order they will be performed */ public List<Step> steps() { - if (hasSingleInstance(steps)) return singleInstance().steps(); // TODO: Remove line after November 2019 return steps; } - // TODO: Remove after November 2019 - public List<DeclaredZone> zones() { - return singleInstance().steps().stream() - .flatMap(step -> step.zones().stream()) - .collect(Collectors.toList()); - } - /** Returns the Athenz domain set on the root tag, if any */ public Optional<AthenzDomain> athenzDomain() { return athenzDomain; } @@ -221,16 +172,6 @@ public class DeploymentSpec { /** Returns the XML form of this spec, or null if it was not created by fromXml, nor is empty */ public String xmlForm() { return xmlForm; } - // TODO: Remove after November 2019 - public boolean includes(Environment environment, Optional<RegionName> region) { - return singleInstance().deploysTo(environment, region); - } - - // TODO: Remove after November 2019 - private static boolean hasSingleInstance(List<DeploymentSpec.Step> steps) { - return instances(steps).size() == 1; - } - /** Returns the instance step containing the given instance name */ public Optional<DeploymentInstanceSpec> instance(InstanceName name) { for (DeploymentInstanceSpec instance : instances()) { @@ -328,29 +269,6 @@ public class DeploymentSpec { return Objects.hash(majorVersion, steps, xmlForm); } - /** This may be invoked by a continuous build */ - public static void main(String[] args) { - if (args.length != 2 && args.length != 3) { - System.err.println("Usage: DeploymentSpec [file] [environment] [region]?" + - "Returns 0 if the specified zone matches the deployment spec, 1 otherwise"); - System.exit(1); - } - - try (BufferedReader reader = new BufferedReader(new FileReader(args[0]))) { - DeploymentSpec spec = DeploymentSpec.fromXml(reader); - Environment environment = Environment.from(args[1]); - Optional<RegionName> region = args.length == 3 ? Optional.of(RegionName.from(args[2])) : Optional.empty(); - if (spec.includes(environment, region)) - System.exit(0); - else - System.exit(1); - } - catch (Exception e) { - System.err.println("Exception checking deployment spec: " + toMessageString(e)); - System.exit(1); - } - } - /** A deployment step */ public abstract static class Step { @@ -382,9 +300,6 @@ public class DeploymentSpec { this.duration = duration; } - // TODO: Remove after October 2019 - public Duration duration() { return duration; } - @Override public Duration delay() { return duration; } @@ -408,15 +323,7 @@ public class DeploymentSpec { private final Optional<String> testerFlavor; public DeclaredZone(Environment environment) { - this(environment, Optional.empty(), false); - } - - public DeclaredZone(Environment environment, Optional<RegionName> region, boolean active) { - this(environment, region, active, Optional.empty(), Optional.empty()); - } - - public DeclaredZone(Environment environment, Optional<RegionName> region, boolean active, Optional<AthenzService> athenzService) { - this(environment, region, active, athenzService, Optional.empty()); + this(environment, Optional.empty(), false, Optional.empty(), Optional.empty()); } public DeclaredZone(Environment environment, Optional<RegionName> region, boolean active, 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 deleted file mode 100644 index 317a4224726..00000000000 --- a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecDeprecatedAPITest.java +++ /dev/null @@ -1,355 +0,0 @@ -// 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 November 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())); - } - - @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())); - } - - @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")))); - } - - @Test - public void deploymentSpecWithTest() { - StringReader r = new StringReader( - "<deployment version='1.0'>" + - " <test/>" + - " <staging/>" + - " <prod>" + - " <region active='false'>us-east1</region>" + - " <region active='true'>us-west1</region>" + - " </prod>" + - "</deployment>" - ); - - DeploymentSpec spec = DeploymentSpec.fromXml(r); - assertEquals("[test, staging, prod.us-east1, prod.us-west1]", spec.steps().toString()); - } - - @Test - public void deploymentSpecWithTestInsideInstance() { - StringReader r = new StringReader( - "<deployment version='1.0'>" + - " <instance id='instance1'>" + - " <test/>" + - " <staging/>" + - " <prod>" + - " <region active='false'>us-east1</region>" + - " <region active='true'>us-west1</region>" + - " </prod>" + - " </instance>" + - "</deployment>" - ); - - DeploymentSpec spec = DeploymentSpec.fromXml(r); - assertEquals("[test, staging, prod.us-east1, prod.us-west1]", spec.steps().toString()); - } - - @Test - public void deploymentSpecWithTestOutsideInstance() { - StringReader r = new StringReader( - "<deployment version='1.0'>" + - " <test/>" + - " <staging/>" + - " <instance id='instance1'>" + - " <prod>" + - " <region active='false'>us-east1</region>" + - " <region active='true'>us-west1</region>" + - " </prod>" + - " </instance>" + - "</deployment>" - ); - - DeploymentSpec spec = DeploymentSpec.fromXml(r); - assertEquals("[test, staging, prod.us-east1, prod.us-west1]", spec.steps().toString()); - } - - @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")))); - } - - @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); - } - - @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 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() { - 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.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 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()); - } - -} 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 f181b02a071..987e13c0e86 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 @@ -77,11 +77,11 @@ public class DeploymentSpecTest { ); DeploymentSpec spec = DeploymentSpec.fromXml(r); - assertEquals(2, spec.requireInstance("default").steps().size()); - assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.test)); - assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.staging)); - assertTrue(spec.requireInstance("default").includes(Environment.test, Optional.empty())); - assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.of(RegionName.from("region1")))); + assertEquals(2, spec.steps().size()); + assertEquals(1, spec.requireInstance("default").steps().size()); + assertTrue(spec.steps().get(0).deploysTo(Environment.test)); + assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.staging)); + assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.empty())); assertTrue(spec.requireInstance("default").includes(Environment.staging, Optional.empty())); assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.empty())); assertFalse(spec.requireInstance("default").globalServiceId().isPresent()); @@ -101,21 +101,21 @@ public class DeploymentSpecTest { ); DeploymentSpec spec = DeploymentSpec.fromXml(r); - assertEquals(4, spec.requireInstance("default").steps().size()); + assertEquals(3, spec.steps().size()); + assertEquals(2, spec.requireInstance("default").steps().size()); - assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.test)); + assertTrue(spec.steps().get(0).deploysTo(Environment.test)); - assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.staging)); + assertTrue(spec.steps().get(1).deploysTo(Environment.staging)); - assertTrue(spec.requireInstance("default").steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1")))); - assertFalse(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(2)).active()); + assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1")))); + assertFalse(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(0)).active()); - assertTrue(spec.requireInstance("default").steps().get(3).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1")))); - assertTrue(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(3)).active()); + assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1")))); + assertTrue(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(1)).active()); - assertTrue(spec.requireInstance("default").includes(Environment.test, Optional.empty())); - assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.of(RegionName.from("region1")))); - assertTrue(spec.requireInstance("default").includes(Environment.staging, Optional.empty())); + assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.empty())); + assertFalse(spec.requireInstance("default").includes(Environment.staging, Optional.empty())); assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-east1")))); assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-west1")))); assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("no-such-region")))); @@ -351,7 +351,8 @@ public class DeploymentSpecTest { @Test public void testEmpty() { assertFalse(DeploymentSpec.empty.requireInstance("default").globalServiceId().isPresent()); - assertTrue(DeploymentSpec.empty.steps().isEmpty()); + assertTrue(DeploymentSpec.empty.requireInstance("default").steps().isEmpty()); + assertEquals(1, DeploymentSpec.empty.steps().size()); assertEquals("<deployment version='1.0'/>", DeploymentSpec.empty.xmlForm()); } @@ -371,7 +372,7 @@ public class DeploymentSpecTest { "</deployment>" ); DeploymentSpec spec = DeploymentSpec.fromXml(r); - DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.requireInstance("default").steps().get(3)); + DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.requireInstance("default").steps().get(1)); assertEquals(2, parallelZones.zones().size()); assertEquals(RegionName.from("us-central-1"), parallelZones.zones().get(0).region().get()); assertEquals(RegionName.from("us-east-3"), parallelZones.zones().get(1).region().get()); @@ -902,6 +903,7 @@ public class DeploymentSpecTest { DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>" + " <instance id='default'>" + " <test tester-flavor=\"d-1-4-20\" />" + + " <staging />" + " <prod tester-flavor=\"d-2-8-50\">" + " <region active=\"false\">us-north-7</region>" + " </prod>" + @@ -995,9 +997,9 @@ public class DeploymentSpecTest { " </prod>" + " <endpoints>" + " <endpoint id=\"foo\" container-id=\"bar\">" + - " <region>us-east</region>" + - " </endpoint>" + - " <endpoint id=\"nalle\" container-id=\"frosk\" />" + + " <region>us-east</region>" + + " </endpoint>" + + " <endpoint id=\"nalle\" container-id=\"frosk\" />" + " <endpoint container-id=\"quux\" />" + " </endpoints>" + " </instance>" + diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecWithoutInstanceTest.java index 95e13ee2748..31a30e5bd83 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 @@ -72,10 +72,10 @@ public class DeploymentSpecWithoutInstanceTest { DeploymentSpec spec = DeploymentSpec.fromXml(r); assertEquals(2, spec.steps().size()); - assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.test)); - assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.staging)); - assertTrue(spec.requireInstance("default").includes(Environment.test, Optional.empty())); - assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.of(RegionName.from("region1")))); + assertEquals(1, spec.requireInstance("default").steps().size()); + assertTrue(spec.steps().get(0).deploysTo(Environment.test)); + assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.staging)); + assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.empty())); assertTrue(spec.requireInstance("default").includes(Environment.staging, Optional.empty())); assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.empty())); assertFalse(spec.requireInstance("default").globalServiceId().isPresent()); @@ -93,21 +93,21 @@ public class DeploymentSpecWithoutInstanceTest { ); DeploymentSpec spec = DeploymentSpec.fromXml(r); - assertEquals(4, spec.requireInstance("default").steps().size()); + assertEquals(3, spec.steps().size()); + assertEquals(2, spec.requireInstance("default").steps().size()); - assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.test)); + assertTrue(spec.steps().get(0).deploysTo(Environment.test)); - assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.staging)); + assertTrue(spec.steps().get(1).deploysTo(Environment.staging)); - assertTrue(spec.requireInstance("default").steps().get(2).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1")))); - assertFalse(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(2)).active()); + assertTrue(spec.requireInstance("default").steps().get(0).deploysTo(Environment.prod, Optional.of(RegionName.from("us-east1")))); + assertFalse(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(0)).active()); - assertTrue(spec.requireInstance("default").steps().get(3).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1")))); - assertTrue(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(3)).active()); + assertTrue(spec.requireInstance("default").steps().get(1).deploysTo(Environment.prod, Optional.of(RegionName.from("us-west1")))); + assertTrue(((DeploymentSpec.DeclaredZone)spec.requireInstance("default").steps().get(1)).active()); - assertTrue(spec.requireInstance("default").includes(Environment.test, Optional.empty())); - assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.of(RegionName.from("region1")))); - assertTrue(spec.requireInstance("default").includes(Environment.staging, Optional.empty())); + assertFalse(spec.requireInstance("default").includes(Environment.test, Optional.empty())); + assertFalse(spec.requireInstance("default").includes(Environment.staging, Optional.empty())); assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-east1")))); assertTrue(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("us-west1")))); assertFalse(spec.requireInstance("default").includes(Environment.prod, Optional.of(RegionName.from("no-such-region")))); @@ -253,7 +253,8 @@ public class DeploymentSpecWithoutInstanceTest { @Test public void testEmpty() { assertFalse(DeploymentSpec.empty.requireInstance("default").globalServiceId().isPresent()); - assertTrue(DeploymentSpec.empty.steps().isEmpty()); + assertEquals(1, DeploymentSpec.empty.steps().size()); + assertTrue(DeploymentSpec.empty.requireInstance("default").steps().isEmpty()); assertEquals("<deployment version='1.0'/>", DeploymentSpec.empty.xmlForm()); } @@ -271,7 +272,7 @@ public class DeploymentSpecWithoutInstanceTest { "</deployment>" ); DeploymentSpec spec = DeploymentSpec.fromXml(r); - DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.requireInstance("default").steps().get(3)); + DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.requireInstance("default").steps().get(1)); assertEquals(2, parallelZones.zones().size()); assertEquals(RegionName.from("us-central-1"), parallelZones.zones().get(0).region().get()); assertEquals(RegionName.from("us-east-3"), parallelZones.zones().get(1).region().get()); @@ -470,6 +471,7 @@ public class DeploymentSpecWithoutInstanceTest { public void customTesterFlavor() { DeploymentSpec spec = DeploymentSpec.fromXml("<deployment>\n" + " <test tester-flavor=\"d-1-4-20\" />\n" + + " <staging />\n" + " <prod tester-flavor=\"d-2-8-50\">\n" + " <region active=\"false\">us-north-7</region>\n" + " </prod>\n" + diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java index 5ee269f8448..d7347b46f52 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationPackageValidator.java @@ -50,15 +50,17 @@ public class ApplicationPackageValidator { /** Verify that each of the production zones listed in the deployment spec exist in this system */ private void validateSteps(DeploymentSpec deploymentSpec) { - new DeploymentSteps(deploymentSpec, controller::system).jobs(); - deploymentSpec.instances().stream().flatMap(instance -> instance.zones().stream()) - .filter(zone -> zone.environment() == Environment.prod) - .forEach(zone -> { - if ( ! controller.zoneRegistry().hasZone(ZoneId.from(zone.environment(), - zone.region().orElse(null)))) { - throw new IllegalArgumentException("Zone " + zone + " in deployment spec was not found in this system!"); - } - }); + for (var spec : deploymentSpec.instances()) { + new DeploymentSteps(spec, controller::system).jobs(); + spec.zones().stream() + .filter(zone -> zone.environment() == Environment.prod) + .forEach(zone -> { + if ( ! controller.zoneRegistry().hasZone(ZoneId.from(zone.environment(), + zone.region().orElseThrow()))) { + throw new IllegalArgumentException("Zone " + zone + " in deployment spec was not found in this system!"); + } + }); + } } /** Verify that no single endpoint contains regions in different clouds */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java index 83d1b5ef803..9a6daf026c3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java @@ -145,7 +145,6 @@ public class Endpoint { } private static String instancePart(ApplicationId application, ZoneId zone, String separator) { - if (zone == null) return ""; // Always omit instance for global endpoints if (application.instance().isDefault()) return ""; // Skip "default" return application.instance().value() + separator; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java index 33db6b95db1..1b722c80ec1 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentSteps.java @@ -1,12 +1,14 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.deployment; +import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.application.Deployment; import com.yahoo.vespa.hosted.controller.application.JobStatus; import java.util.Collection; @@ -16,8 +18,8 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; -import static java.util.Collections.singletonList; import static java.util.Comparator.comparingInt; import static java.util.stream.Collectors.collectingAndThen; @@ -28,19 +30,20 @@ import static java.util.stream.Collectors.collectingAndThen; */ public class DeploymentSteps { - private final DeploymentSpec spec; + private final DeploymentInstanceSpec spec; private final Supplier<SystemName> system; - public DeploymentSteps(DeploymentSpec spec, Supplier<SystemName> system) { + public DeploymentSteps(DeploymentInstanceSpec spec, Supplier<SystemName> system) { this.spec = Objects.requireNonNull(spec, "spec cannot be null"); this.system = Objects.requireNonNull(system, "system cannot be null"); } - /** Returns jobs for this, in the order they are declared */ + /** Returns jobs for this, in the order they should run */ public List<JobType> jobs() { - return spec.steps().stream() - .flatMap(step -> toJobs(step).stream()) - .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + return Stream.concat(production().isEmpty() ? Stream.of() : Stream.of(JobType.systemTest, JobType.stagingTest), + spec.steps().stream().flatMap(step -> toJobs(step).stream())) + .distinct() + .collect(Collectors.toUnmodifiableList()); } /** Returns job status sorted according to deployment spec */ @@ -48,7 +51,7 @@ public class DeploymentSteps { List<JobType> sortedJobs = jobs(); return jobStatus.stream() .sorted(comparingInt(job -> sortedJobs.indexOf(job.type()))) - .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + .collect(Collectors.toUnmodifiableList()); } /** Returns deployments sorted according to declared zones */ @@ -56,7 +59,7 @@ public class DeploymentSteps { List<ZoneId> productionZones = spec.zones().stream() .filter(z -> z.region().isPresent()) .map(z -> ZoneId.from(z.environment(), z.region().get())) - .collect(Collectors.toList()); + .collect(Collectors.toUnmodifiableList()); return deployments.stream() .sorted(comparingInt(deployment -> productionZones.indexOf(deployment.zone()))) .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); @@ -67,34 +70,28 @@ public class DeploymentSteps { return step.zones().stream() .map(this::toJob) .flatMap(Optional::stream) - .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + .collect(Collectors.toUnmodifiableList()); } - /** Returns test jobs in this */ + /** Returns test jobs to run for this spec */ public List<JobType> testJobs() { - return toJobs(test()); + return jobs().stream().filter(JobType::isTest).collect(Collectors.toUnmodifiableList()); } - /** Returns production jobs in this */ + /** Returns declared production jobs in this */ public List<JobType> productionJobs() { return toJobs(production()); } - /** Returns test steps in this */ - public List<DeploymentSpec.Step> test() { - if (spec.steps().isEmpty()) { - return singletonList(new DeploymentSpec.DeclaredZone(Environment.test)); - } + /** Returns declared production steps in this */ + public List<DeploymentSpec.Step> production() { return spec.steps().stream() - .filter(step -> step.deploysTo(Environment.test) || step.deploysTo(Environment.staging)) - .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + .filter(step -> ! isTest(step)) + .collect(Collectors.toUnmodifiableList()); } - /** Returns production steps in this */ - public List<DeploymentSpec.Step> production() { - return spec.steps().stream() - .filter(step -> step.deploysTo(Environment.prod) || step.zones().isEmpty()) - .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + private boolean isTest(DeploymentSpec.Step step) { + return step.deploysTo(Environment.test) || step.deploysTo(Environment.staging); } /** Resolve job from deployment zone */ @@ -106,7 +103,7 @@ public class DeploymentSteps { private List<JobType> toJobs(List<DeploymentSpec.Step> steps) { return steps.stream() .flatMap(step -> toJobs(step).stream()) - .collect(collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + .collect(Collectors.toUnmodifiableList()); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java index a1a66570299..ca2db96c14d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.deployment; +import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.DeploymentSpec.Step; import com.yahoo.config.provision.ApplicationId; @@ -77,7 +78,6 @@ public class DeploymentTrigger { */ public static final Duration maxPause = Duration.ofDays(3); - private final static Logger log = Logger.getLogger(DeploymentTrigger.class.getName()); private final Controller controller; @@ -92,7 +92,7 @@ public class DeploymentTrigger { this.jobs = controller.jobController(); } - public DeploymentSteps steps(DeploymentSpec spec) { + public DeploymentSteps steps(DeploymentInstanceSpec spec) { return new DeploymentSteps(spec, controller::system); } @@ -333,7 +333,7 @@ public class DeploymentTrigger { .<Instant>flatMap(job -> job.lastSuccess().map(JobRun::at))); String reason = "New change available"; List<Job> testJobs = null; // null means "uninitialised", while empty means "don't run any jobs". - DeploymentSteps steps = steps(application.deploymentSpec()); + DeploymentSteps steps = steps(application.deploymentSpec().requireInstance(instance.name())); if (change.hasTargets()) { for (Step step : steps.production()) { @@ -541,19 +541,23 @@ public class DeploymentTrigger { } private Change remainingChange(Application application) { - DeploymentSteps steps = steps(application.deploymentSpec()); - List<JobType> jobs = steps.production().isEmpty() - ? steps.testJobs() - : steps.productionJobs(); - Change change = application.change(); - for (Instance instance : application.instances().values()) { - if (jobs.stream().allMatch(job -> isComplete(application.change().withoutApplication(), application.change(), instance, job))) - change = change.withoutPlatform(); - if (jobs.stream().allMatch(job -> isComplete(application.change().withoutPlatform(), application.change(), instance, job))) - change = change.withoutApplication(); - } + if (application.deploymentSpec().instances().stream() + .allMatch(spec -> { + DeploymentSteps steps = new DeploymentSteps(spec, controller::system); + return (steps.productionJobs().isEmpty() ? steps.testJobs() : steps.productionJobs()) + .stream().allMatch(job -> isComplete(application.change().withoutApplication(), application.change(), application.require(spec.name()), job)); + })) + change = change.withoutPlatform(); + + if (application.deploymentSpec().instances().stream() + .allMatch(spec -> { + DeploymentSteps steps = new DeploymentSteps(spec, controller::system); + return (steps.productionJobs().isEmpty() ? steps.testJobs() : steps.productionJobs()) + .stream().allMatch(job -> isComplete(application.change().withoutPlatform(), application.change(), application.require(spec.name()), job)); + })) + change = change.withoutApplication(); return change; } @@ -575,7 +579,7 @@ public class DeploymentTrigger { private List<Job> testJobs(DeploymentSpec deploymentSpec, Change change, Instance instance, Versions versions, String reason, Instant availableSince, Predicate<JobType> condition) { List<Job> jobs = new ArrayList<>(); - for (JobType jobType : steps(deploymentSpec).testJobs()) { + for (JobType jobType : new DeploymentSteps(deploymentSpec.requireInstance(instance.name()), controller::system).testJobs()) { // TODO jonmv: Allow cross-instance validation Optional<JobRun> completion = successOn(instance, jobType, versions) .filter(run -> versions.sourcesMatchIfPresent(run) || jobType == systemTest); if (completion.isEmpty() && condition.test(jobType)) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java index cc483dff765..3deca255bad 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java @@ -469,7 +469,9 @@ public class JobController { /** Returns a URI which points at a badge showing current status for all jobs for the given application. */ public URI overviewBadge(ApplicationId id) { - DeploymentSteps steps = new DeploymentSteps(controller.applications().requireApplication(TenantAndApplicationId.from(id)).deploymentSpec(), controller::system); + DeploymentSteps steps = new DeploymentSteps(controller.applications().requireApplication(TenantAndApplicationId.from(id)) + .deploymentSpec().requireInstance(id.instance()), + controller::system); return badges.overview(id, steps.jobs().stream() .map(type -> last(id, type)) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java index 08142a9a399..75f489d3345 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java @@ -6,7 +6,9 @@ import com.yahoo.config.application.api.DeploymentInstanceSpec; import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.InstanceName; +import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.security.KeyUtils; import com.yahoo.slime.ArrayTraverser; @@ -43,6 +45,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -578,10 +581,7 @@ public class ApplicationSerializer { var endpointId = EndpointId.of(inspector.field(assignedRotationEndpointField).asString()); var rotationId = new RotationId(inspector.field(assignedRotationRotationField).asString()); var regions = deploymentSpec.instance(instance) - .map(spec -> spec.endpoints().stream() - .filter(endpoint -> endpoint.endpointId().equals(endpointId.id())) - .flatMap(endpoint -> endpoint.regions().stream()) - .collect(Collectors.toSet())) + .map(spec -> globalEndpointRegions(spec, endpointId)) .orElse(Set.of()); assignedRotations.putIfAbsent(endpointId, new AssignedRotation(clusterId, endpointId, rotationId, regions)); }); @@ -589,4 +589,16 @@ public class ApplicationSerializer { return List.copyOf(assignedRotations.values()); } + private Set<RegionName> globalEndpointRegions(DeploymentInstanceSpec spec, EndpointId endpointId) { + if (spec.globalServiceId().isPresent()) + return spec.zones().stream() + .flatMap(zone -> zone.region().stream()) + .collect(Collectors.toSet()); + + return spec.endpoints().stream() + .filter(endpoint -> endpoint.endpointId().equals(endpointId.id())) + .flatMap(endpoint -> endpoint.regions().stream()) + .collect(Collectors.toSet()); + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 4537f480706..5cb7d7a6065 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -71,6 +71,7 @@ import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.RoutingPolicy; import com.yahoo.vespa.hosted.controller.application.SystemApplication; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; +import com.yahoo.vespa.hosted.controller.deployment.DeploymentSteps; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.ChangesToCancel; import com.yahoo.vespa.hosted.controller.deployment.TestConfigSerializer; @@ -717,35 +718,38 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private void toSlime(Cursor object, Instance instance, DeploymentSpec deploymentSpec, HttpRequest request) { object.setString("instance", instance.name().value()); - // Jobs sorted according to deployment spec - List<JobStatus> jobStatus = controller.applications().deploymentTrigger() - .steps(deploymentSpec) - .sortedJobs(instance.deploymentJobs().jobStatus().values()); - - - Cursor deploymentJobsArray = object.setArray("deploymentJobs"); - for (JobStatus job : jobStatus) { - Cursor jobObject = deploymentJobsArray.addObject(); - jobObject.setString("type", job.type().jobName()); - jobObject.setBool("success", job.isSuccess()); - job.lastTriggered().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastTriggered"))); - job.lastCompleted().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastCompleted"))); - job.firstFailing().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("firstFailing"))); - job.lastSuccess().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastSuccess"))); - } - // Change blockers - Cursor changeBlockers = object.setArray("changeBlockers"); - deploymentSpec.instance(instance.name()).ifPresent(spec -> spec.changeBlocker().forEach(changeBlocker -> { - Cursor changeBlockerObject = changeBlockers.addObject(); - changeBlockerObject.setBool("versions", changeBlocker.blocksVersions()); - changeBlockerObject.setBool("revisions", changeBlocker.blocksRevisions()); - changeBlockerObject.setString("timeZone", changeBlocker.window().zone().getId()); - Cursor days = changeBlockerObject.setArray("days"); - changeBlocker.window().days().stream().map(DayOfWeek::getValue).forEach(days::addLong); - Cursor hours = changeBlockerObject.setArray("hours"); - changeBlocker.window().hours().forEach(hours::addLong); - })); + if (deploymentSpec.instance(instance.name()).isPresent()) { + // Jobs sorted according to deployment spec + List<JobStatus> jobStatus = controller.applications().deploymentTrigger() + .steps(deploymentSpec.requireInstance(instance.name())) + .sortedJobs(instance.deploymentJobs().jobStatus().values()); + + + Cursor deploymentJobsArray = object.setArray("deploymentJobs"); + for (JobStatus job : jobStatus) { + Cursor jobObject = deploymentJobsArray.addObject(); + jobObject.setString("type", job.type().jobName()); + jobObject.setBool("success", job.isSuccess()); + job.lastTriggered().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastTriggered"))); + job.lastCompleted().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastCompleted"))); + job.firstFailing().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("firstFailing"))); + job.lastSuccess().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastSuccess"))); + } + + // Change blockers + Cursor changeBlockers = object.setArray("changeBlockers"); + deploymentSpec.instance(instance.name()).ifPresent(spec -> spec.changeBlocker().forEach(changeBlocker -> { + Cursor changeBlockerObject = changeBlockers.addObject(); + changeBlockerObject.setBool("versions", changeBlocker.blocksVersions()); + changeBlockerObject.setBool("revisions", changeBlocker.blocksRevisions()); + changeBlockerObject.setString("timeZone", changeBlocker.window().zone().getId()); + Cursor days = changeBlockerObject.setArray("days"); + changeBlocker.window().days().stream().map(DayOfWeek::getValue).forEach(days::addLong); + Cursor hours = changeBlockerObject.setArray("hours"); + changeBlocker.window().hours().forEach(hours::addLong); + })); + } // Rotation Cursor globalRotationsArray = object.setArray("globalRotations"); @@ -772,9 +776,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } // Deployments sorted according to deployment spec - List<Deployment> deployments = controller.applications().deploymentTrigger() - .steps(deploymentSpec) - .sortedDeployments(instance.deployments().values()); + List<Deployment> deployments = deploymentSpec.instance(instance.name()) + .map(spec -> new DeploymentSteps(spec, controller::system)) + .map(steps -> steps.sortedDeployments(instance.deployments().values())) + .orElse(List.copyOf(instance.deployments().values())); Cursor deploymentsArray = object.setArray("deployments"); for (Deployment deployment : deployments) { @@ -822,36 +827,38 @@ public class ApplicationApiHandler extends LoggingRequestHandler { toSlime(object.setObject("outstandingChange"), application.outstandingChange()); } - // Jobs sorted according to deployment spec - List<JobStatus> jobStatus = controller.applications().deploymentTrigger() - .steps(application.deploymentSpec()) - .sortedJobs(instance.deploymentJobs().jobStatus().values()); - - object.setBool("deployedInternally", application.internal()); - Cursor deploymentsArray = object.setArray("deploymentJobs"); - for (JobStatus job : jobStatus) { - Cursor jobObject = deploymentsArray.addObject(); - jobObject.setString("type", job.type().jobName()); - jobObject.setBool("success", job.isSuccess()); - - job.lastTriggered().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastTriggered"))); - job.lastCompleted().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastCompleted"))); - job.firstFailing().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("firstFailing"))); - job.lastSuccess().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastSuccess"))); - } + if (application.deploymentSpec().instance(instance.name()).isPresent()) { + // Jobs sorted according to deployment spec + List<JobStatus> jobStatus = controller.applications().deploymentTrigger() + .steps(application.deploymentSpec().requireInstance(instance.name())) + .sortedJobs(instance.deploymentJobs().jobStatus().values()); + + object.setBool("deployedInternally", application.internal()); + Cursor deploymentsArray = object.setArray("deploymentJobs"); + for (JobStatus job : jobStatus) { + Cursor jobObject = deploymentsArray.addObject(); + jobObject.setString("type", job.type().jobName()); + jobObject.setBool("success", job.isSuccess()); + + job.lastTriggered().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastTriggered"))); + job.lastCompleted().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastCompleted"))); + job.firstFailing().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("firstFailing"))); + job.lastSuccess().ifPresent(jobRun -> toSlime(jobRun, jobObject.setObject("lastSuccess"))); + } - // Change blockers - Cursor changeBlockers = object.setArray("changeBlockers"); - application.deploymentSpec().instance(instance.name()).ifPresent(spec -> spec.changeBlocker().forEach(changeBlocker -> { - Cursor changeBlockerObject = changeBlockers.addObject(); - changeBlockerObject.setBool("versions", changeBlocker.blocksVersions()); - changeBlockerObject.setBool("revisions", changeBlocker.blocksRevisions()); - changeBlockerObject.setString("timeZone", changeBlocker.window().zone().getId()); - Cursor days = changeBlockerObject.setArray("days"); - changeBlocker.window().days().stream().map(DayOfWeek::getValue).forEach(days::addLong); - Cursor hours = changeBlockerObject.setArray("hours"); - changeBlocker.window().hours().forEach(hours::addLong); - })); + // Change blockers + Cursor changeBlockers = object.setArray("changeBlockers"); + application.deploymentSpec().instance(instance.name()).ifPresent(spec -> spec.changeBlocker().forEach(changeBlocker -> { + Cursor changeBlockerObject = changeBlockers.addObject(); + changeBlockerObject.setBool("versions", changeBlocker.blocksVersions()); + changeBlockerObject.setBool("revisions", changeBlocker.blocksRevisions()); + changeBlockerObject.setString("timeZone", changeBlocker.window().zone().getId()); + Cursor days = changeBlockerObject.setArray("days"); + changeBlocker.window().days().stream().map(DayOfWeek::getValue).forEach(days::addLong); + Cursor hours = changeBlockerObject.setArray("hours"); + changeBlocker.window().hours().forEach(hours::addLong); + })); + } // Compile version. The version that should be used when building an application object.setString("compileVersion", compileVersion(application.id()).toFullString()); @@ -883,9 +890,11 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } // Deployments sorted according to deployment spec - List<Deployment> deployments = controller.applications().deploymentTrigger() - .steps(application.deploymentSpec()) - .sortedDeployments(instance.deployments().values()); + List<Deployment> deployments = + application.deploymentSpec().instance(instance.name()) + .map(spec -> new DeploymentSteps(spec, controller::system)) + .map(steps -> steps.sortedDeployments(instance.deployments().values())) + .orElse(List.copyOf(instance.deployments().values())); Cursor instancesArray = object.setArray("instances"); for (Deployment deployment : deployments) { Cursor deploymentObject = instancesArray.addObject(); @@ -1925,8 +1934,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler { ApplicationPackage applicationPackage = new ApplicationPackage(dataParts.get(EnvironmentResource.APPLICATION_ZIP)); if (DeploymentSpec.empty.equals(applicationPackage.deploymentSpec())) throw new IllegalArgumentException("Missing required file 'deployment.xml'"); - if (applicationPackage.deploymentSpec().instances().size() != 1) - throw new IllegalArgumentException("Only single-instance deployment specs are currently supported"); controller.applications().verifyApplicationIdentityConfiguration(TenantName.from(tenant), applicationPackage, diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java index 0f1cd9495bf..9fb20ef4f81 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java @@ -76,7 +76,7 @@ class JobControllerApiHandlerHelper { Application application = controller.applications().requireApplication(TenantAndApplicationId.from(id)); Instance instance = application.require(id.instance()); Change change = application.change(); - DeploymentSteps steps = new DeploymentSteps(application.deploymentSpec(), controller::system); + DeploymentSteps steps = new DeploymentSteps(application.deploymentSpec().requireInstance(id.instance()), controller::system); // The logic for pending runs imitates DeploymentTrigger logic; not good, but the trigger wiring must be re-written to reuse :S Map<JobType, Versions> pendingProduction = diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java index 64a32bce3c0..6ffdea93a1c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java @@ -11,6 +11,7 @@ import com.yahoo.restapi.SlimeJsonResponse; import com.yahoo.slime.Cursor; import com.yahoo.slime.Slime; import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler; import com.yahoo.vespa.hosted.controller.proxy.ConfigServerRestExecutor; @@ -39,10 +40,10 @@ public class ConfigServerApiHandler extends AuditLoggingRequestHandler { private final ZoneRegistry zoneRegistry; private final ConfigServerRestExecutor proxy; - public ConfigServerApiHandler(Context parentCtx, ZoneRegistry zoneRegistry, + public ConfigServerApiHandler(Context parentCtx, ServiceRegistry serviceRegistry, ConfigServerRestExecutor proxy, Controller controller) { super(parentCtx, controller.auditLogger()); - this.zoneRegistry = zoneRegistry; + this.zoneRegistry = serviceRegistry.zoneRegistry(); this.proxy = proxy; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java index 3e96d2f6972..ea97e3e6c71 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java @@ -41,6 +41,10 @@ public class EndpointTest { "https://cd--a1--t1.global.vespa.oath.cloud:4443/", Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.cd), + // Main endpoint in CD + "https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/", + Endpoint.of(app2).named(endpointId).on(Port.tls(4443)).in(SystemName.cd), + // Main endpoint with direct routing and default TLS port "https://a1.t1.global.vespa.oath.cloud/", Endpoint.of(app1).named(endpointId).on(Port.tls()).directRouting().in(SystemName.main), @@ -50,11 +54,11 @@ public class EndpointTest { Endpoint.of(app1).named(EndpointId.of("r1")).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint for custom instance in default rotation - "https://a2.t2.global.vespa.oath.cloud/", + "https://i2.a2.t2.global.vespa.oath.cloud/", Endpoint.of(app2).named(endpointId).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint for custom instance with custom rotation name - "https://r2.a2.t2.global.vespa.oath.cloud/", + "https://r2.i2.a2.t2.global.vespa.oath.cloud/", Endpoint.of(app2).named(EndpointId.of("r2")).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint in public system @@ -82,6 +86,10 @@ public class EndpointTest { Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.main), // Main endpoint in CD + "https://cd--i2--a2--t2.global.vespa.oath.cloud:4443/", + Endpoint.of(app2).named(endpointId).on(Port.tls(4443)).in(SystemName.cd), + + // Main endpoint in CD "https://cd--a1--t1.global.vespa.oath.cloud:4443/", Endpoint.of(app1).named(endpointId).on(Port.tls(4443)).in(SystemName.cd), @@ -94,11 +102,11 @@ public class EndpointTest { Endpoint.of(app1).named(EndpointId.of("r1")).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint for custom instance in default rotation - "https://a2.t2.global.vespa.oath.cloud/", + "https://i2.a2.t2.global.vespa.oath.cloud/", Endpoint.of(app2).named(endpointId).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint for custom instance with custom rotation name - "https://r2.a2.t2.global.vespa.oath.cloud/", + "https://r2.i2.a2.t2.global.vespa.oath.cloud/", Endpoint.of(app2).named(EndpointId.of("r2")).on(Port.tls()).directRouting().in(SystemName.main), // Main endpoint in public system diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java index b8d6689a9d9..d0eb86c3ba1 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java @@ -172,10 +172,11 @@ public class DeploymentContext { .allMatch(deployments -> deployments.stream() .allMatch(deployment -> deployment.version().equals(version)))); - for (JobType type : new DeploymentSteps(application().deploymentSpec(), tester.controller()::system).productionJobs()) - assertTrue(tester.configServer().nodeRepository() - .list(type.zone(tester.controller().system()), applicationId.defaultInstance()).stream() // TODO jonmv: support more - .allMatch(node -> node.currentVersion().equals(version))); + for (var spec : application().deploymentSpec().instances()) + for (JobType type : new DeploymentSteps(spec, tester.controller()::system).productionJobs()) + assertTrue(tester.configServer().nodeRepository() + .list(type.zone(tester.controller().system()), applicationId.defaultInstance()).stream() // TODO jonmv: support more + .allMatch(node -> node.currentVersion().equals(version))); assertFalse(application().change().hasTargets()); return this; @@ -267,7 +268,7 @@ public class DeploymentContext { triggerJobs(); } else - throw new AssertionError("Job '" + run.id().type() + "' was run twice for '" + instanceId + "'"); + throw new AssertionError("Job '" + run.id() + "' was run twice"); assertFalse("Change should have no targets, but was " + application().change(), application().change().hasTargets()); if (!deferDnsUpdates) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java index de5c04af80b..8fb766164c2 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java @@ -862,15 +862,16 @@ public class DeploymentTriggerTest { } @Test - @Ignore public void testMultipleInstances() { ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .instances("instance1,instance2") .environment(Environment.prod) .region("us-east-3") .build(); - var app = tester.newDeploymentContext().submit(applicationPackage); // TODO jonmv: support instances in deployment context> - app.deploy(); + var app = tester.newDeploymentContext("tenant1", "application1", "instance1").submit(applicationPackage); // TODO jonmv: support instances in deployment context> + var otherInstance = tester.newDeploymentContext("tenant1", "application1", "instance2"); + app.runJob(systemTest).runJob(stagingTest).runJob(productionUsEast3); + otherInstance.runJob(systemTest).runJob(stagingTest).runJob(productionUsEast3); assertEquals(2, app.application().instances().size()); assertEquals(2, app.application().productionDeployments().values().stream() .mapToInt(Collection::size) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java index 976f2dead8f..83d9d4d1ad0 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java @@ -2,36 +2,21 @@ package com.yahoo.vespa.hosted.controller.integration; import com.google.inject.Inject; +import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.component.AbstractComponent; +import com.yahoo.config.provision.SystemName; import com.yahoo.test.ManualClock; -import com.yahoo.vespa.hosted.controller.api.integration.BuildService; -import com.yahoo.vespa.hosted.controller.api.integration.RunDataStore; import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry; -import com.yahoo.vespa.hosted.controller.api.integration.aws.AwsEventFetcher; import com.yahoo.vespa.hosted.controller.api.integration.aws.MockAwsEventFetcher; import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificateMock; -import com.yahoo.vespa.hosted.controller.api.integration.certificates.ApplicationCertificateProvider; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationStore; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository; import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService; -import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService; -import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; import com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService; -import com.yahoo.vespa.hosted.controller.api.integration.organization.Billing; -import com.yahoo.vespa.hosted.controller.api.integration.organization.ContactRetriever; -import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues; -import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueHandler; -import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockBilling; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueHandler; -import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; -import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumer; import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock; -import com.yahoo.vespa.hosted.controller.api.integration.resource.MeteringClient; import com.yahoo.vespa.hosted.controller.api.integration.resource.MockTenantCost; -import com.yahoo.vespa.hosted.controller.api.integration.resource.TenantCost; import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingService; import com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator; @@ -43,9 +28,6 @@ import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMeteringClient; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockRunDataStore; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud; -import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; - -import java.time.Clock; /** * A mock implementation of a {@link ServiceRegistry} for testing purposes. @@ -78,14 +60,18 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg private final MockBuildService mockBuildService = new MockBuildService(); private final MockTenantCost mockTenantCost = new MockTenantCost(); + public ServiceRegistryMock(SystemName system) { + this.zoneRegistryMock = new ZoneRegistryMock(system); + this.configServerMock = new ConfigServerMock(zoneRegistryMock); + } + @Inject - public ServiceRegistryMock(ZoneRegistryMock zoneRegistry) { - this.zoneRegistryMock = zoneRegistry; - this.configServerMock = new ConfigServerMock(zoneRegistry); + public ServiceRegistryMock(ConfigserverConfig config) { + this(SystemName.from(config.system())); } public ServiceRegistryMock() { - this(new ZoneRegistryMock()); + this(SystemName.main); } @Override diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java index f5158a1ffa2..07db06164c6 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java @@ -40,15 +40,6 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry private UpgradePolicy upgradePolicy = null; private Map<CloudName, UpgradePolicy> osUpgradePolicies = new HashMap<>(); - @Inject - public ZoneRegistryMock(ConfigserverConfig config) { - this(SystemName.from(config.system())); - } - - public ZoneRegistryMock() { - this(SystemName.main); - } - /** * This sets the default list of zones contained in this. If your test need a particular set of zones, use * {@link #setZones(List)} instead of changing the default set.} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java index 23a6c6286c0..23e86320a78 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/metric/ConfigServerMetricsTest.java @@ -1,6 +1,7 @@ package com.yahoo.vespa.hosted.controller.metric; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; @@ -31,7 +32,7 @@ public class ConfigServerMetricsTest { @Before public void before() { - configServer = new ConfigServerMock(new ZoneRegistryMock()); + configServer = new ConfigServerMock(new ZoneRegistryMock(SystemName.main)); service = new ConfigServerMetrics(configServer); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java index 186e982fd75..9ac73604da5 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java @@ -6,6 +6,7 @@ import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.security.KeyUtils; import com.yahoo.vespa.config.SlimeUtils; @@ -74,8 +75,13 @@ public class ApplicationSerializerTest { @Test public void testSerialization() { - DeploymentSpec deploymentSpec = DeploymentSpec.fromXml("<deployment version='1.0'>" + - " <staging/>" + + DeploymentSpec deploymentSpec = DeploymentSpec.fromXml("<deployment version='1.0'>\n" + + " <staging/>\n" + + " <instance id=\"i1\">\n" + + " <prod global-service-id=\"default\">\n" + + " <region active=\"true\">us-west-1</region>\n" + + " </prod>\n" + + " </instance>\n" + "</deployment>"); ValidationOverrides validationOverrides = ValidationOverrides.fromXml("<validation-overrides version='1.0'>" + " <allow until='2017-06-15'>deployment-removal</allow>" + @@ -100,9 +106,6 @@ public class ApplicationSerializerTest { List<JobStatus> statusList = new ArrayList<>(); - JobStatus.JobRun componentJob = JobStatus.JobRun.triggering(Version.emptyVersion, applicationVersion1, empty(), - empty(), "New commit", Instant.ofEpochMilli(400)) - .completion(100, Instant.ofEpochMilli(500)); statusList.add(JobStatus.initial(JobType.systemTest) .withTriggering(Version.fromString("5.6.7"), ApplicationVersion.unknown, empty(), "Test", Instant.ofEpochMilli(7)) .withCompletion(30, empty(), Instant.ofEpochMilli(8)) @@ -127,7 +130,7 @@ public class ApplicationSerializerTest { List<Instance> instances = List.of(new Instance(id1, deployments, deploymentJobs, - List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of())), + List.of(AssignedRotation.fromStrings("foo", "default", "my-rotation", Set.of("us-west-1"))), rotationStatus), new Instance(id3, List.of(), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 1a53920e8de..dc6abcb2616 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -70,7 +70,6 @@ public class ControllerContainerTest { " <component id='com.yahoo.vespa.curator.mock.MockCurator'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactoryMock'/>\n" + - " <component id='com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.integration.ServiceRegistryMock'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.Controller'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.integration.ConfigServerProxyMock'/>\n" + diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index a1c9aa872fd..5deedaa2fb1 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -661,17 +661,19 @@ public class ApplicationApiTest extends ControllerContainerTest { .data(streamer), "{\"message\":\"Application package version: 1.0.3-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}"); - // Sixth attempt has a multi-instance deployment spec, and fails. + // Sixth attempt has a multi-instance deployment spec, and is accepted. ApplicationPackage multiInstanceSpec = new ApplicationPackageBuilder() .instances("instance1,instance2") .environment(Environment.prod) .region("us-central-1") .parallel("us-west-1", "us-east-3") + .endpoint("default", "foo", "us-central-1", "us-west-1", "us-east-3") .build(); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/submit", POST) .screwdriverIdentity(SCREWDRIVER_ID) .data(createApplicationSubmissionData(multiInstanceSpec, 123)), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Only single-instance deployment specs are currently supported\"}", 400); + "{\"message\":\"Application package version: 1.0.4-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}"); + // GET deployment job overview, after triggering system and staging test jobs. assertEquals(2, tester.controller().applications().deploymentTrigger().triggerReadyJobs()); @@ -730,6 +732,10 @@ public class ApplicationApiTest extends ControllerContainerTest { .userIdentity(USER_ID) .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), "{\"message\":\"Deleted instance tenant1.application1.instance1\"}"); + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance2", DELETE) + .userIdentity(USER_ID) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), + "{\"message\":\"Deleted instance tenant1.application1.instance2\"}"); // DELETE a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json index 267c8b01330..195219c691e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2-with-patches.json @@ -31,8 +31,6 @@ "instances": [ { "instance": "default", - "deploymentJobs": [], - "changeBlockers": [], "globalRotations": [], "deployments": [] }, @@ -80,7 +78,7 @@ ], "changeBlockers": [], "globalRotations": [ - "https://application2--tenant2.global.vespa.oath.cloud:4443/" + "https://instance1--application2--tenant2.global.vespa.oath.cloud:4443/" ], "rotationId": "rotation-id-2", "deployments": [] diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json index 8a1ed245fae..c29944924dc 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json @@ -30,8 +30,6 @@ "instances": [ { "instance": "default", - "deploymentJobs": [], - "changeBlockers": [], "globalRotations": [], "deployments": [] }, @@ -79,7 +77,7 @@ ], "changeBlockers": [], "globalRotations": [ - "https://application2--tenant2.global.vespa.oath.cloud:4443/" + "https://instance1--application2--tenant2.global.vespa.oath.cloud:4443/" ], "rotationId": "rotation-id-2", "deployments": [] diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json index d0abe24fa58..bb81125e54d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-with-routing-policy.json @@ -162,7 +162,7 @@ "changeBlockers": [], "compileVersion": "(ignore)", "globalRotations": [ - "https://c0.application1.tenant1.global.vespa.oath.cloud/" + "https://c0.instance1.application1.tenant1.global.vespa.oath.cloud/" ], "instances": [ { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json index c61932f15fe..75493a30155 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance-without-change-multiple-deployments.json @@ -211,7 +211,7 @@ "changeBlockers": [], "compileVersion": "(ignore)", "globalRotations": [ - "https://application1--tenant1.global.vespa.oath.cloud:4443/" + "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/" ], "rotationId": "rotation-id-1", "instances": [ diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json index b2ab9aa636d..061931336ed 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance.json @@ -216,7 +216,7 @@ ], "compileVersion": "6.0.0", "globalRotations": [ - "https://application1--tenant1.global.vespa.oath.cloud:4443/" + "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/" ], "rotationId": "rotation-id-1", "instances": [ diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json index da73c36b208..2dacacedf51 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/instance1-recursive.json @@ -216,7 +216,7 @@ ], "compileVersion": "(ignore)", "globalRotations": [ - "https://application1--tenant1.global.vespa.oath.cloud:4443/" + "https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/" ], "rotationId": "rotation-id-1", "instances": [ diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json index 8fe38db994d..b73c36c804b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/jobs.json @@ -3,12 +3,12 @@ "platform": { "platform": "6.1", "at": "(ignore)", - "pending": "Waiting for application change to 1.0.3-commit1 to complete" + "pending": "Waiting for application change to 1.0.4-commit1 to complete" }, "application": { "application": { - "hash": "1.0.3-commit1", - "build": 3, + "hash": "1.0.4-commit1", + "build": 4, "source": { "gitRepository": "repository1", "gitBranch": "master", @@ -21,8 +21,8 @@ }, "deploying": { "application": { - "hash": "1.0.3-commit1", - "build": 3, + "hash": "1.0.4-commit1", + "build": 4, "source": { "gitRepository": "repository1", "gitBranch": "master", @@ -43,8 +43,8 @@ "start": "(ignore)", "wantedPlatform": "6.1", "wantedApplication": { - "hash": "1.0.3-commit1", - "build": 3, + "hash": "1.0.4-commit1", + "build": 4, "source": { "gitRepository": "repository1", "gitBranch": "master", @@ -111,8 +111,8 @@ "start": "(ignore)", "wantedPlatform": "6.1", "wantedApplication": { - "hash": "1.0.3-commit1", - "build": 3, + "hash": "1.0.4-commit1", + "build": 4, "source": { "gitRepository": "repository1", "gitBranch": "master", @@ -181,8 +181,8 @@ "status": "pending", "wantedPlatform": "6.1", "wantedApplication": { - "hash": "1.0.3-commit1", - "build": 3, + "hash": "1.0.4-commit1", + "build": 4, "source": { "gitRepository": "repository1", "gitBranch": "master", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json index df55185fde5..a572fa04781 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/system-test-job.json @@ -39,8 +39,8 @@ "start": "(ignore)", "wantedPlatform": "6.1", "wantedApplication": { - "hash": "1.0.3-commit1", - "build": 3, + "hash": "1.0.4-commit1", + "build": 4, "source": { "gitRepository": "repository1", "gitBranch": "master", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java index e723b5a2f30..8aea26f21e3 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java @@ -38,12 +38,11 @@ public class ConfigServerApiHandlerTest extends ControllerContainerTest { @Before public void before() { - ZoneRegistryMock zoneRegistry = (ZoneRegistryMock) container.components() - .getComponent(ZoneRegistryMock.class.getName()); - zoneRegistry.setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2")) - .setZones(zones); - this.tester = new ContainerTester(container, responseFiles); - this.proxy = (ConfigServerProxyMock) container.components().getComponent(ConfigServerProxyMock.class.getName()); + tester = new ContainerTester(container, responseFiles); + tester.serviceRegistry().zoneRegistry() + .setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2")) + .setZones(zones); + proxy = (ConfigServerProxyMock) container.components().getComponent(ConfigServerProxyMock.class.getName()); } @Test diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java index 73c64b0ee5d..eaeba420bc9 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java @@ -34,11 +34,10 @@ public class ZoneApiTest extends ControllerContainerCloudTest { @Before public void before() { - ZoneRegistryMock zoneRegistry = (ZoneRegistryMock) container.components() - .getComponent(ZoneRegistryMock.class.getName()); - zoneRegistry.setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2")) - .setZones(zones); - this.tester = new ContainerTester(container, responseFiles); + tester = new ContainerTester(container, responseFiles); + tester.serviceRegistry().zoneRegistry() + .setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2")) + .setZones(zones); } @Test diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java index 50b1db50c19..f00363989e6 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java @@ -39,12 +39,11 @@ public class ZoneApiTest extends ControllerContainerTest { @Before public void before() { - ZoneRegistryMock zoneRegistry = (ZoneRegistryMock) container.components() - .getComponent(ZoneRegistryMock.class.getName()); - zoneRegistry.setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2")) - .setZones(zones); - this.tester = new ContainerTester(container, responseFiles); - this.proxy = (ConfigServerProxyMock) container.components().getComponent(ConfigServerProxyMock.class.getName()); + tester = new ContainerTester(container, responseFiles); + tester.serviceRegistry().zoneRegistry() + .setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2")) + .setZones(zones); + proxy = (ConfigServerProxyMock) container.components().getComponent(ConfigServerProxyMock.class.getName()); } @Test diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java index c76046b3f67..674f084a8b7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/rotation/RotationRepositoryTest.java @@ -146,6 +146,44 @@ public class RotationRepositoryTest { application2.instance().endpointsIn(SystemName.cd).main().get().url().toString()); } + @Test + public void multiple_instances_with_similar_global_service_id() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .instances("instance1,instance2") + .region("us-central-1") + .parallel("us-west-1", "us-east-3") + .globalServiceId("global") + .build(); + var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1").submit(applicationPackage); + var instance2 = tester.newDeploymentContext("tenant1", "application1", "instance2"); + assertEquals(List.of(new RotationId("foo-1")), rotationIds(instance1.instance().rotations())); + assertEquals(List.of(new RotationId("foo-2")), rotationIds(instance2.instance().rotations())); + assertEquals(URI.create("https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"), + instance1.instance().endpointsIn(SystemName.main).main().get().url()); + assertEquals(URI.create("https://instance2--application1--tenant1.global.vespa.oath.cloud:4443/"), + instance2.instance().endpointsIn(SystemName.main).main().get().url()); + } + + @Test + public void multiple_instances_with_similar_endpoints() { + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .instances("instance1,instance2") + .region("us-central-1") + .parallel("us-west-1", "us-east-3") + .endpoint("default", "foo", "us-central-1", "us-west-1") + .build(); + var instance1 = tester.newDeploymentContext("tenant1", "application1", "instance1").submit(applicationPackage); + var instance2 = tester.newDeploymentContext("tenant1", "application1", "instance2"); + + assertEquals(List.of(new RotationId("foo-1")), rotationIds(instance1.instance().rotations())); + assertEquals(List.of(new RotationId("foo-2")), rotationIds(instance2.instance().rotations())); + + assertEquals(URI.create("https://instance1--application1--tenant1.global.vespa.oath.cloud:4443/"), + instance1.instance().endpointsIn(SystemName.main).main().get().url()); + assertEquals(URI.create("https://instance2--application1--tenant1.global.vespa.oath.cloud:4443/"), + instance2.instance().endpointsIn(SystemName.main).main().get().url()); + } + private void assertSingleRotation(Rotation expected, List<AssignedRotation> assignedRotations, RotationRepository repository) { assertEquals(1, assignedRotations.size()); var rotationId = assignedRotations.get(0).rotationId(); |