diff options
10 files changed, 188 insertions, 67 deletions
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 4d370ba5039..1a2335c82db 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 @@ -39,28 +39,36 @@ public class DeploymentSpec { UpgradePolicy.defaultPolicy, Collections.emptyList(), Collections.emptyList(), - "<deployment version='1.0'/>"); + "<deployment version='1.0'/>", + Optional.empty(), + Optional.empty()); private final Optional<String> globalServiceId; private final UpgradePolicy upgradePolicy; private final List<ChangeBlocker> changeBlockers; private final List<Step> steps; private final String xmlForm; + private final Optional<String> athenzDomain; + private final Optional<String> athenzService; public DeploymentSpec(Optional<String> globalServiceId, UpgradePolicy upgradePolicy, List<ChangeBlocker> changeBlockers, List<Step> steps) { - this(globalServiceId, upgradePolicy, changeBlockers, steps, null); + this(globalServiceId, upgradePolicy, changeBlockers, steps, null, Optional.empty(), Optional.empty()); } public DeploymentSpec(Optional<String> globalServiceId, UpgradePolicy upgradePolicy, - List<ChangeBlocker> changeBlockers, List<Step> steps, String xmlForm) { + List<ChangeBlocker> changeBlockers, List<Step> steps, String xmlForm, + Optional<String> athenzDomain, Optional<String> athenzService) { validateTotalDelay(steps); this.globalServiceId = globalServiceId; this.upgradePolicy = upgradePolicy; this.changeBlockers = changeBlockers; this.steps = ImmutableList.copyOf(completeSteps(new ArrayList<>(steps))); this.xmlForm = xmlForm; + this.athenzDomain = athenzDomain; + this.athenzService = athenzService; validateZones(this.steps); + validateAthenz(); } /** Throw an IllegalArgumentException if the total delay exceeds 24 hours */ @@ -81,7 +89,30 @@ public class DeploymentSpec { for (DeclaredZone zone : step.zones()) ensureUnique(zone, zones); } - + + /* + * Throw an IllegalArgumentException if Athenz configuration violates: + * domain not configured -> no zone can configure service + * domain configured -> all zones must configure service + */ + private void validateAthenz() { + // If athenz domain is not set, athenz service cannot be set on any level + if (! athenzDomain.isPresent()) { + for (DeclaredZone zone : zones()) { + if(zone.athenzService().isPresent()) { + throw new IllegalArgumentException("Athenz service configured for zone: " + zone + ", but Athenz domain is not configured"); + } + } + // if athenz domain is configured, athenz service must be set implicitly or directly on all zones. + } else if(! athenzService.isPresent()) { + for (DeclaredZone zone : zones()) { + if(! zone.athenzService().isPresent()) { + throw new IllegalArgumentException("Athenz domain is configured, but Athenz service not configured for zone: " + zone); + } + } + } + } + private void ensureUnique(DeclaredZone zone, Set<DeclaredZone> zones) { if ( ! zones.add(zone)) throw new IllegalArgumentException(zone + " is listed twice in deployment.xml"); @@ -212,6 +243,20 @@ public class DeploymentSpec { return b.toString(); } + /** Returns the athenz domain if configured */ + public Optional<String> athenzDomain() { + return athenzDomain; + } + + /** Returns the athenz service for environment/region if configured */ + public Optional<String> athenzService(Environment environment, RegionName region) { + return zones().stream() + .filter(zone -> zone.deploysTo(environment, Optional.of(region))) + .findFirst() + .map(DeclaredZone::athenzService) + .orElse(athenzService); + } + /** This may be invoked by a continuous build */ public static void main(String[] args) { if (args.length != 2 && args.length != 3) { @@ -276,11 +321,17 @@ public class DeploymentSpec { private final boolean active; + private Optional<String> athenzService; + public DeclaredZone(Environment environment) { this(environment, Optional.empty(), false); } public DeclaredZone(Environment environment, Optional<RegionName> region, boolean active) { + this(environment, region, active, Optional.empty()); + } + + public DeclaredZone(Environment environment, Optional<RegionName> region, boolean active, Optional<String> athenzService) { if (environment != Environment.prod && region.isPresent()) throw new IllegalArgumentException("Non-prod environments cannot specify a region"); if (environment == Environment.prod && ! region.isPresent()) @@ -288,6 +339,7 @@ public class DeploymentSpec { this.environment = environment; this.region = region; this.active = active; + this.athenzService = athenzService; } public Environment environment() { return environment; } @@ -298,6 +350,8 @@ public class DeploymentSpec { /** Returns whether this zone should receive production traffic */ public boolean active() { return active; } + public Optional<String> athenzService() { return athenzService; } + @Override public List<DeclaredZone> zones() { return Collections.singletonList(this); } diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java index 5599b257b16..1a970e53713 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java @@ -72,6 +72,7 @@ public class DeploymentSpecXmlReader { if (environment == Environment.prod) { for (Element stepTag : XML.getChildren(environmentTag)) { + Optional<String> athenzService = stringAttribute("athenz-service", environmentTag); if (stepTag.getTagName().equals("delay")) { steps.add(new Delay(Duration.ofSeconds(longAttribute("hours", stepTag) * 60 * 60 + longAttribute("minutes", stepTag) * 60 + @@ -79,11 +80,11 @@ public class DeploymentSpecXmlReader { } else if (stepTag.getTagName().equals("parallel")) { List<DeclaredZone> zones = new ArrayList<>(); for (Element regionTag : XML.getChildren(stepTag)) { - zones.add(readDeclaredZone(environment, regionTag)); + zones.add(readDeclaredZone(environment, athenzService, regionTag)); } steps.add(new ParallelZones(zones)); } else { // a region: deploy step - steps.add(readDeclaredZone(environment, stepTag)); + steps.add(readDeclaredZone(environment, athenzService, stepTag)); } } } else { @@ -94,8 +95,11 @@ public class DeploymentSpecXmlReader { globalServiceId = readGlobalServiceId(environmentTag); else if (readGlobalServiceId(environmentTag).isPresent()) throw new IllegalArgumentException("Attribute 'global-service-id' is only valid on 'prod' tag."); + } - return new DeploymentSpec(globalServiceId, readUpgradePolicy(root), readChangeBlockers(root), steps, xmlForm); + Optional<String> athenzDomain = stringAttribute("athenz-domain", root); + Optional<String> athenzService = stringAttribute("athenz-service", root); + return new DeploymentSpec(globalServiceId, readUpgradePolicy(root), readChangeBlockers(root), steps, xmlForm, athenzDomain, athenzService); } /** Imposes some constraints on tag order which are not expressible in the schema */ @@ -132,13 +136,19 @@ public class DeploymentSpecXmlReader { } } + /** Returns the given attribute as a string, or Optional.empty if it is not present or empty */ + private Optional<String> stringAttribute(String attributeName, Element tag) { + String value = tag.getAttribute(attributeName); + return Optional.ofNullable(value).filter(s -> ! s.equals("")); + } + private boolean isEnvironmentName(String tagName) { return tagName.equals(testTag) || tagName.equals(stagingTag) || tagName.equals(prodTag); } - private DeclaredZone readDeclaredZone(Environment environment, Element regionTag) { + private DeclaredZone readDeclaredZone(Environment environment, Optional<String> athenzService, Element regionTag) { return new DeclaredZone(environment, Optional.of(RegionName.from(XML.getValue(regionTag).trim())), - readActive(regionTag)); + readActive(regionTag), athenzService); } private Optional<String> readGlobalServiceId(Element environmentTag) { 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 5050f88af31..5dcb3bc1ebe 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 @@ -367,4 +367,56 @@ public class DeploymentSpecTest { assertTrue(spec.canUpgradeAt(Instant.parse("2017-09-23T10:15:30.00Z"))); } + @Test + public void deploymentSpecWithAthenzIdentity() { + StringReader r = new StringReader( + "<deployment athenz-domain='domain' athenz-service='service'>\n" + + " <prod>\n" + + " <region active='true'>us-west-1</region>\n" + + " </prod>\n" + + "</deployment>" + ); + DeploymentSpec spec = DeploymentSpec.fromXml(r); + assertEquals(spec.athenzDomain().get(), "domain"); + assertEquals(spec.athenzService(Environment.prod, RegionName.from("us-west-1")).get(), "service"); + } + + @Test + public void deploymentSpecUsesServiceFromEnvironment() { + StringReader r = new StringReader( + "<deployment athenz-domain='domain' athenz-service='service'>\n" + + " <test/>\n" + + " <prod athenz-service='prod-service'>\n" + + " <region active='true'>us-west-1</region>\n" + + " </prod>\n" + + "</deployment>" + ); + DeploymentSpec spec = DeploymentSpec.fromXml(r); + assertEquals(spec.athenzDomain().get(), "domain"); + assertEquals(spec.athenzService(Environment.prod, RegionName.from("us-west-1")).get(), "prodservice"); + } + + @Test(expected = IllegalArgumentException.class) + public void athenzDomainMissingService() { + StringReader r = new StringReader( + "<deployment athenz-domain='domain'>\n" + + " <prod>\n" + + " <region active='true'>us-west-1</region>\n" + + " </prod>\n" + + "</deployment>" + ); + DeploymentSpec spec = DeploymentSpec.fromXml(r); + } + + @Test(expected = IllegalArgumentException.class) + public void athenzDomainMissingDomain() { + StringReader r = new StringReader( + "<deployment>\n" + + " <prod athenz-service='service'>\n" + + " <region active='true'>us-west-1</region>\n" + + " </prod>\n" + + "</deployment>" + ); + DeploymentSpec spec = DeploymentSpec.fromXml(r); + } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java index 91d5b7fe267..1e9dc569ff5 100755 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java @@ -13,8 +13,10 @@ import com.yahoo.config.application.api.DeploymentSpec; import com.yahoo.config.docproc.DocprocConfig; import com.yahoo.config.docproc.SchemamappingConfig; import com.yahoo.config.model.ApplicationConfigProducerRoot; +import com.yahoo.config.model.api.ConfigServerSpec; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; +import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.Zone; import com.yahoo.container.BundlesConfig; @@ -232,6 +234,7 @@ public final class ContainerCluster addSimpleComponent("com.yahoo.container.core.slobrok.SlobrokConfigurator"); addSimpleComponent("com.yahoo.container.handler.VipStatus"); addJaxProviders(); + addIdentity(); } public void setZone(Zone zone) { @@ -304,6 +307,36 @@ public final class ContainerCluster addSimpleComponent(XPathFactoryProvider.class); } + public void addIdentity() { + getDeploymentSpec().ifPresent(deploymentSpec -> { + deploymentSpec.athenzDomain().ifPresent(domain -> { + + String service = deploymentSpec.athenzService(zone.environment(), zone.region()) + .orElseThrow(() -> new RuntimeException("Missing Athenz service configuration")); + + Identity identity = new Identity(domain.trim(), service.trim(), getLoadBalancerName()); + addComponent(identity); + + getContainers().forEach(container -> { + container.setProp("identity.domain", domain); + container.setProp("identity.service", service); + }); + }); + }); + } + + private HostName getLoadBalancerName() { + // Set lbaddress, or use first hostname if not specified. + // TODO: Remove the orElseGet part when this is set up in all zones + return Optional.ofNullable(getRoot().getDeployState().getProperties().loadBalancerName()) + .orElseGet( + () -> HostName.from(getRoot().getDeployState().getProperties().configServerSpecs().stream() + .findFirst() + .map(ConfigServerSpec::getHostName) + .orElse("unknown") // Currently unable to test this, hence the unknown + )); + } + public final void addComponent(Component<?, ?> component) { if (clusterVerifier.acceptComponent(component)) { componentGroup.addComponent(component); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index d59846cd5e2..121039a248d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -7,7 +7,6 @@ import com.yahoo.config.application.Xml; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.ConfigModelContext; -import com.yahoo.config.model.api.ConfigServerSpec; import com.yahoo.config.model.application.provider.IncludeDirs; import com.yahoo.config.model.builder.xml.ConfigModelBuilder; import com.yahoo.config.model.builder.xml.ConfigModelId; @@ -16,7 +15,6 @@ import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; -import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeType; import com.yahoo.container.jdisc.config.MetricDefaultsConfig; import com.yahoo.search.rendering.RendererRegistry; @@ -39,7 +37,6 @@ import com.yahoo.vespa.model.clients.ContainerDocumentApi; import com.yahoo.vespa.model.container.Container; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.container.ContainerModel; -import com.yahoo.vespa.model.container.Identity; import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.FileStatusHandlerComponent; import com.yahoo.vespa.model.container.component.chain.ProcessingHandler; @@ -165,13 +162,6 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { addServerProviders(spec, cluster); addLegacyFilters(spec, cluster); // TODO: Remove for Vespa 7 - // Athenz copper argos - // NOTE: Must be done after addNodes() - addIdentity(spec, - cluster, - context.getDeployState().getProperties().configServerSpecs(), - context.getDeployState().getProperties().loadBalancerName()); - //TODO: overview handler, see DomQrserverClusterBuilder } @@ -698,31 +688,6 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } } - private void addIdentity(Element element, ContainerCluster cluster, List<ConfigServerSpec> configServerSpecs, HostName loadBalancerName) { - Element identityElement = XML.getChild(element, "identity"); - if(identityElement != null) { - String domain = XML.getValue(XML.getChild(identityElement, "domain")); - String service = XML.getValue(XML.getChild(identityElement, "service")); - - // Set lbaddress, or use first hostname if not specified. - HostName lbName = Optional.ofNullable(loadBalancerName) - .orElseGet( - () -> HostName.from(configServerSpecs.stream() - .findFirst() - .map(ConfigServerSpec::getHostName) - .orElse("unknown") // Currently unable to test this, hence the unknown - )); - - Identity identity = new Identity(domain.trim(), service.trim(), lbName); - cluster.addComponent(identity); - - cluster.getContainers().forEach(container -> { - container.setProp("identity.domain", domain); - container.setProp("identity.service", service); - }); - } - } - /** * Disallow renderers named "DefaultRenderer" or "JsonRenderer" */ diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc index 47cf0638d72..6a90bef7bb2 100644 --- a/config-model/src/main/resources/schema/containercluster.rnc +++ b/config-model/src/main/resources/schema/containercluster.rnc @@ -7,8 +7,7 @@ ContainerCluster = element container | jdisc { ContainerServices & DocumentBinding* & Aliases? & - NodesOfContainerCluster? & - Identity? + NodesOfContainerCluster? } ContainerServices = @@ -226,8 +225,3 @@ DocumentBinding = element document { attribute class { xsd:NCName } & attribute bundle { xsd:NCName } } - -Identity = element identity { - element domain { xsd:NCName } & - element service { xsd:NCName } -} diff --git a/config-model/src/main/resources/schema/deployment.rnc b/config-model/src/main/resources/schema/deployment.rnc index 90bff8e31b3..2ecfa781876 100644 --- a/config-model/src/main/resources/schema/deployment.rnc +++ b/config-model/src/main/resources/schema/deployment.rnc @@ -4,6 +4,8 @@ start = element deployment { attribute version { "1.0" } & + attribute athenz-domain { xsd:string }? & + attribute athenz-service { xsd:string }? & Upgrade? & BlockChange* & Test? & @@ -31,15 +33,18 @@ BlockUpgrade = element block-upgrade { # Legacy name - remove on Vespa 7 } Test = element test { - text + attribute athenz-service { xsd:string }? & + text } Staging = element staging { - text + attribute athenz-service { xsd:string }? & + text } Prod = element prod { attribute global-service-id { text }? & + attribute athenz-service { xsd:string }? & Region* & Delay* & Parallel* diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/IdentityBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/IdentityBuilderTest.java index df118b0e349..c8a245e68b0 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/IdentityBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/IdentityBuilderTest.java @@ -1,7 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.xml; +import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.builder.xml.test.DomBuilderTest; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.model.test.MockApplicationPackage; +import com.yahoo.config.model.test.MockRoot; import com.yahoo.container.core.identity.IdentityConfig; import com.yahoo.vespa.model.container.Identity; import org.junit.Test; @@ -17,16 +21,24 @@ import static org.junit.Assert.assertEquals; */ public class IdentityBuilderTest extends ContainerModelBuilderTestBase { @Test - public void identity_config_produced() throws IOException, SAXException { + public void identity_config_produced_from_deployment_spec() throws IOException, SAXException { Element clusterElem = DomBuilderTest.parse( - "<jdisc id='default' version='1.0'>", - " <identity>", - " <domain>domain</domain>", - " <service>service</service>", - " </identity>", - "</jdisc>"); + "<jdisc id='default' version='1.0'/>"); + String deploymentXml = "<deployment version='1.0' athenz-domain='domain' athenz-service='service'>\n" + + " <test/>\n" + + " <prod>\n" + + " <region active='true'>default</region>\n" + + " </prod>\n" + + "</deployment>\n"; + + ApplicationPackage applicationPackage = new MockApplicationPackage.Builder() + .withDeploymentSpec(deploymentXml) + .build(); + + // Override root + root = new MockRoot("root", applicationPackage); + createModel(root, DeployState.createTestState(applicationPackage), clusterElem); - createModel(root, clusterElem); IdentityConfig identityConfig = root.getConfig(IdentityConfig.class, "default/component/" + Identity.CLASS); assertEquals("domain", identityConfig.domain()); assertEquals("service", identityConfig.service()); diff --git a/config-model/src/test/schema-test-files/deployment.xml b/config-model/src/test/schema-test-files/deployment.xml index f469d22b6f0..f9a62eb648f 100644 --- a/config-model/src/test/schema-test-files/deployment.xml +++ b/config-model/src/test/schema-test-files/deployment.xml @@ -1,12 +1,12 @@ <!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> -<deployment version='1.0'> +<deployment version='1.0' athenz-domain='vespa' athenz-service='service'> <upgrade policy='canary'/> <test/> <staging/> <block-change revision='true' version='false' days="mon,tue" hours="14,15"/> <block-change days="mon,tue" hours="14,15" time-zone="CET"/> <block-upgrade days="wed" hours="16" time-zone="CET"/><!-- Tests legacy name. Remove in Vespa 7 --> - <prod global-service-id='qrs'> + <prod global-service-id='qrs' athenz-service='other-service'> <region active='true'>us-west-1</region> <delay hours='3'/> <region active='true'>us-central-1</region> diff --git a/config-model/src/test/schema-test-files/services.xml b/config-model/src/test/schema-test-files/services.xml index 98637c03020..a02346193cc 100644 --- a/config-model/src/test/schema-test-files/services.xml +++ b/config-model/src/test/schema-test-files/services.xml @@ -35,10 +35,6 @@ </config> <jdisc id='qrsCluster_1' version='1.0'> - <identity> - <domain>mydomain</domain> - <service>myservice</service> - </identity> <rest-api path="jersey1"> <components bundle="my-bundle" /> <components bundle="other-bundle"> |