From fc7e562c164665378e7f64212f30d8225eb48312 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Wed, 11 Oct 2017 13:10:35 +0200 Subject: Separate out XML reading --- .../config/application/api/DeploymentSpec.java | 123 +---------------- .../api/xml/DeploymentSpecXmlReader.java | 146 +++++++++++++++++++++ 2 files changed, 151 insertions(+), 118 deletions(-) create mode 100644 config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java (limited to 'config-model-api') 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 d1e8dc21549..d89fde59503 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 @@ -2,6 +2,7 @@ package com.yahoo.config.application.api; import com.google.common.collect.ImmutableList; +import com.yahoo.config.application.api.xml.DeploymentSpecXmlReader; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.io.IOUtils; @@ -57,8 +58,8 @@ public class DeploymentSpec { this(globalServiceId, upgradePolicy, changeBlockers, steps, null); } - private DeploymentSpec(Optional globalServiceId, UpgradePolicy upgradePolicy, - List changeBlockers, List steps, String xmlForm) { + public DeploymentSpec(Optional globalServiceId, UpgradePolicy upgradePolicy, + List changeBlockers, List steps, String xmlForm) { validateTotalDelay(steps); this.globalServiceId = globalServiceId; this.upgradePolicy = upgradePolicy; @@ -196,123 +197,9 @@ public class DeploymentSpec { * @throws IllegalArgumentException if the XML is invalid */ public static DeploymentSpec fromXml(String xmlForm) { - List steps = new ArrayList<>(); - Optional globalServiceId = Optional.empty(); - Element root = XML.getDocument(xmlForm).getDocumentElement(); - for (Element environmentTag : XML.getChildren(root)) { - if ( ! isEnvironmentName(environmentTag.getTagName())) continue; - - Environment environment = Environment.from(environmentTag.getTagName()); - - if (environment == Environment.prod) { - for (Element stepTag : XML.getChildren(environmentTag)) { - if (stepTag.getTagName().equals("delay")) { - steps.add(new Delay(Duration.ofSeconds(longAttribute("hours", stepTag) * 60 * 60 + - longAttribute("minutes", stepTag) * 60 + - longAttribute("seconds", stepTag)))); - } else if (stepTag.getTagName().equals("parallel")) { - List zones = new ArrayList<>(); - for (Element regionTag : XML.getChildren(stepTag)) { - zones.add(readDeclaredZone(environment, regionTag)); - } - steps.add(new ParallelZones(zones)); - } else { // a region: deploy step - steps.add(readDeclaredZone(environment, stepTag)); - } - } - } else { - steps.add(new DeclaredZone(environment)); - } - - if (environment == Environment.prod) - 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); - } - - /** Returns the given attribute as an integer, or 0 if it is not present */ - private static long longAttribute(String attributeName, Element tag) { - String value = tag.getAttribute(attributeName); - if (value == null || value.isEmpty()) return 0; - try { - return Long.parseLong(value); - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Expected an integer for attribute '" + attributeName + - "' but got '" + value + "'"); - } - } - - private static boolean isEnvironmentName(String tagName) { - return tagName.equals("test") || tagName.equals("staging") || tagName.equals("prod"); - } - - private static DeclaredZone readDeclaredZone(Environment environment, Element regionTag) { - return new DeclaredZone(environment, Optional.of(RegionName.from(XML.getValue(regionTag).trim())), - readActive(regionTag)); - } - - private static Optional readGlobalServiceId(Element environmentTag) { - String globalServiceId = environmentTag.getAttribute("global-service-id"); - if (globalServiceId == null || globalServiceId.isEmpty()) { - return Optional.empty(); - } - else { - return Optional.of(globalServiceId); - } - } - - private static List readChangeBlockers(Element root) { - List changeBlockers = new ArrayList<>(); - for (Element tag : XML.getChildren(root)) { - // TODO: Remove block-upgrade on Vespa 7 - if ( ! "block-change".equals(tag.getTagName()) && !"block-upgrade".equals(tag.getTagName())) continue; - - boolean blockVersions = trueOrMissing(tag.getAttribute("version")); - boolean blockRevisions = trueOrMissing(tag.getAttribute("revision")) - && !tag.getTagName().equals("block-upgrade"); // TODO: Remove condition on Vespa 7 - - String daySpec = tag.getAttribute("days"); - String hourSpec = tag.getAttribute("hours"); - String zoneSpec = tag.getAttribute("time-zone"); - if (zoneSpec.isEmpty()) { // Default to UTC time zone - zoneSpec = "UTC"; - } - changeBlockers.add(new ChangeBlocker(blockRevisions, blockVersions, - TimeWindow.from(daySpec, hourSpec, zoneSpec))); - } - return Collections.unmodifiableList(changeBlockers); + return new DeploymentSpecXmlReader().read(xmlForm); } - /** Returns true if the given value is "true", or if it is missing */ - private static boolean trueOrMissing(String value) { - return value == null || value.isEmpty() || value.equals("true"); - } - - private static UpgradePolicy readUpgradePolicy(Element root) { - Element upgradeElement = XML.getChild(root, "upgrade"); - if (upgradeElement == null) return UpgradePolicy.defaultPolicy; - - String policy = upgradeElement.getAttribute("policy"); - switch (policy) { - case "canary" : return UpgradePolicy.canary; - case "default" : return UpgradePolicy.defaultPolicy; - case "conservative" : return UpgradePolicy.conservative; - default : throw new IllegalArgumentException("Illegal upgrade policy '" + policy + "': " + - "Must be one of " + Arrays.toString(UpgradePolicy.values())); - } - } - - private static boolean readActive(Element regionTag) { - String activeValue = regionTag.getAttribute("active"); - if ("true".equals(activeValue)) return true; - if ("false".equals(activeValue)) return false; - throw new IllegalArgumentException("Region tags must have an 'active' attribute set to 'true' or 'false' " + - "to control whether the region should receive production traffic"); - } - public static String toMessageString(Throwable t) { StringBuilder b = new StringBuilder(); String lastMessage = null; @@ -496,7 +383,7 @@ public class DeploymentSpec { private final boolean version; private final TimeWindow window; - private ChangeBlocker(boolean revision, boolean version, TimeWindow window) { + public ChangeBlocker(boolean revision, boolean version, TimeWindow window) { this.revision = revision; this.version = version; this.window = window; 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 new file mode 100644 index 00000000000..d81adab6361 --- /dev/null +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java @@ -0,0 +1,146 @@ +package com.yahoo.config.application.api.xml; + +import com.yahoo.config.application.api.DeploymentSpec; +import com.yahoo.config.application.api.DeploymentSpec.Step; +import com.yahoo.config.application.api.DeploymentSpec.Delay; +import com.yahoo.config.application.api.DeploymentSpec.DeclaredZone; +import com.yahoo.config.application.api.DeploymentSpec.ParallelZones; +import com.yahoo.config.application.api.DeploymentSpec.ChangeBlocker; +import com.yahoo.config.application.api.TimeWindow; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.RegionName; +import com.yahoo.text.XML; +import org.w3c.dom.Element; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * @author bratseth + */ +public class DeploymentSpecXmlReader { + + /** Reads a deployment spec from XML */ + public DeploymentSpec read(String xmlForm) { + List steps = new ArrayList<>(); + Optional globalServiceId = Optional.empty(); + Element root = XML.getDocument(xmlForm).getDocumentElement(); + for (Element environmentTag : XML.getChildren(root)) { + if ( ! isEnvironmentName(environmentTag.getTagName())) continue; + + Environment environment = Environment.from(environmentTag.getTagName()); + + if (environment == Environment.prod) { + for (Element stepTag : XML.getChildren(environmentTag)) { + if (stepTag.getTagName().equals("delay")) { + steps.add(new Delay(Duration.ofSeconds(longAttribute("hours", stepTag) * 60 * 60 + + longAttribute("minutes", stepTag) * 60 + + longAttribute("seconds", stepTag)))); + } else if (stepTag.getTagName().equals("parallel")) { + List zones = new ArrayList<>(); + for (Element regionTag : XML.getChildren(stepTag)) { + zones.add(readDeclaredZone(environment, regionTag)); + } + steps.add(new ParallelZones(zones)); + } else { // a region: deploy step + steps.add(readDeclaredZone(environment, stepTag)); + } + } + } else { + steps.add(new DeclaredZone(environment)); + } + + if (environment == Environment.prod) + 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); + } + + /** Returns the given attribute as an integer, or 0 if it is not present */ + private long longAttribute(String attributeName, Element tag) { + String value = tag.getAttribute(attributeName); + if (value == null || value.isEmpty()) return 0; + try { + return Long.parseLong(value); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("Expected an integer for attribute '" + attributeName + + "' but got '" + value + "'"); + } + } + + private boolean isEnvironmentName(String tagName) { + return tagName.equals("test") || tagName.equals("staging") || tagName.equals("prod"); + } + + private DeclaredZone readDeclaredZone(Environment environment, Element regionTag) { + return new DeclaredZone(environment, Optional.of(RegionName.from(XML.getValue(regionTag).trim())), + readActive(regionTag)); + } + + private Optional readGlobalServiceId(Element environmentTag) { + String globalServiceId = environmentTag.getAttribute("global-service-id"); + if (globalServiceId == null || globalServiceId.isEmpty()) { + return Optional.empty(); + } + else { + return Optional.of(globalServiceId); + } + } + + private List readChangeBlockers(Element root) { + List changeBlockers = new ArrayList<>(); + for (Element tag : XML.getChildren(root)) { + // TODO: Remove block-upgrade on Vespa 7 + if ( ! "block-change".equals(tag.getTagName()) && !"block-upgrade".equals(tag.getTagName())) continue; + + boolean blockVersions = trueOrMissing(tag.getAttribute("version")); + boolean blockRevisions = trueOrMissing(tag.getAttribute("revision")) + && !tag.getTagName().equals("block-upgrade"); // TODO: Remove condition on Vespa 7 + + String daySpec = tag.getAttribute("days"); + String hourSpec = tag.getAttribute("hours"); + String zoneSpec = tag.getAttribute("time-zone"); + if (zoneSpec.isEmpty()) { // Default to UTC time zone + zoneSpec = "UTC"; + } + changeBlockers.add(new DeploymentSpec.ChangeBlocker(blockRevisions, blockVersions, + TimeWindow.from(daySpec, hourSpec, zoneSpec))); + } + return Collections.unmodifiableList(changeBlockers); + } + + /** Returns true if the given value is "true", or if it is missing */ + private boolean trueOrMissing(String value) { + return value == null || value.isEmpty() || value.equals("true"); + } + + private DeploymentSpec.UpgradePolicy readUpgradePolicy(Element root) { + Element upgradeElement = XML.getChild(root, "upgrade"); + if (upgradeElement == null) return DeploymentSpec.UpgradePolicy.defaultPolicy; + + String policy = upgradeElement.getAttribute("policy"); + switch (policy) { + case "canary" : return DeploymentSpec.UpgradePolicy.canary; + case "default" : return DeploymentSpec.UpgradePolicy.defaultPolicy; + case "conservative" : return DeploymentSpec.UpgradePolicy.conservative; + default : throw new IllegalArgumentException("Illegal upgrade policy '" + policy + "': " + + "Must be one of " + Arrays.toString(DeploymentSpec.UpgradePolicy.values())); + } + } + + private boolean readActive(Element regionTag) { + String activeValue = regionTag.getAttribute("active"); + if ("true".equals(activeValue)) return true; + if ("false".equals(activeValue)) return false; + throw new IllegalArgumentException("Region tags must have an 'active' attribute set to 'true' or 'false' " + + "to control whether the region should receive production traffic"); + } + +} -- cgit v1.2.3