summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/Rotations.java50
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java13
-rw-r--r--config-model/src/main/resources/schema/container.rnc6
-rw-r--r--config-model/src/main/resources/schema/containercluster.rnc3
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/RotationsTest.java54
-rw-r--r--config-model/src/test/schema-test-files/services-hosted.xml8
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/RotationName.java57
-rw-r--r--configserver/src/test/apps/hosted/services.xml8
8 files changed, 191 insertions, 8 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/Rotations.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/Rotations.java
new file mode 100644
index 00000000000..429c80f1b2c
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/Rotations.java
@@ -0,0 +1,50 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.builder.xml.dom;
+
+import com.yahoo.config.provision.RotationName;
+import com.yahoo.text.XML;
+import org.w3c.dom.Element;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+/**
+ * Read rotations from the <rotations/> element in services.xml.
+ *
+ * @author mpolden
+ */
+public class Rotations {
+
+ /*
+ * Rotation names must be:
+ * - lowercase
+ * - alphanumeric
+ * - begin with a character
+ * - have a length between 1 and 12
+ */
+ private static final Pattern pattern = Pattern.compile("^[a-z][a-z0-9-]{0,11}$");
+
+ private Rotations() {}
+
+ /** Set the rotations the given cluster should be member of */
+ public static Set<RotationName> from(Element rotationsElement) {
+ Set<RotationName> rotations = new TreeSet<>();
+ List<Element> children = XML.getChildren(rotationsElement, "rotation");
+ for (Element el : children) {
+ String name = el.getAttribute("id");
+ if (name == null || !pattern.matcher(name).find()) {
+ throw new IllegalArgumentException("Rotation ID '" + name + "' is missing or has invalid format");
+ }
+ RotationName rotation = RotationName.from(name);
+ if (rotations.contains(rotation)) {
+ throw new IllegalArgumentException("Rotation ID '" + name + "' is duplicated");
+ }
+ rotations.add(rotation);
+ }
+ return Collections.unmodifiableSet(rotations);
+ }
+
+}
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 bb35b66edef..ab15005ca73 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
@@ -22,6 +22,7 @@ import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Rotation;
+import com.yahoo.config.provision.RotationName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.search.rendering.RendererRegistry;
@@ -33,10 +34,10 @@ import com.yahoo.vespa.model.HostResource;
import com.yahoo.vespa.model.HostSystem;
import com.yahoo.vespa.model.builder.xml.dom.DomClientProviderBuilder;
import com.yahoo.vespa.model.builder.xml.dom.DomComponentBuilder;
-import com.yahoo.vespa.model.builder.xml.dom.DomFilterBuilder;
import com.yahoo.vespa.model.builder.xml.dom.DomHandlerBuilder;
import com.yahoo.vespa.model.builder.xml.dom.ModelElement;
import com.yahoo.vespa.model.builder.xml.dom.NodesSpecification;
+import com.yahoo.vespa.model.builder.xml.dom.Rotations;
import com.yahoo.vespa.model.builder.xml.dom.ServletBuilder;
import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder;
import com.yahoo.vespa.model.builder.xml.dom.chains.docproc.DomDocprocChainsBuilder;
@@ -465,6 +466,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
}
private void addNodesFromXml(ContainerCluster cluster, Element containerElement, ConfigModelContext context) {
Element nodesElement = XML.getChild(containerElement, "nodes");
+ Element rotationsElement = XML.getChild(containerElement, "rotations");
if (nodesElement == null) { // default single node on localhost
Container node = new Container(cluster, "container.0", 0, cluster.isHostedVespa());
HostResource host = allocateSingleNodeHost(cluster, log, containerElement, context);
@@ -472,7 +474,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
node.initService(context.getDeployLogger());
cluster.addContainers(Collections.singleton(node));
} else {
- List<Container> nodes = createNodes(cluster, nodesElement, context);
+ List<Container> nodes = createNodes(cluster, nodesElement, rotationsElement, context);
applyNodesTagJvmArgs(nodes, getJvmOptions(cluster, nodesElement, context.getDeployLogger()));
if ( !cluster.getJvmGCOptions().isPresent()) {
@@ -506,9 +508,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
return sb.toString();
}
- private List<Container> createNodes(ContainerCluster cluster, Element nodesElement, ConfigModelContext context) {
+ private List<Container> createNodes(ContainerCluster cluster, Element nodesElement, Element rotationsElement, ConfigModelContext context) {
if (nodesElement.hasAttribute("count")) // regular, hosted node spec
- return createNodesFromNodeCount(cluster, nodesElement, context);
+ return createNodesFromNodeCount(cluster, nodesElement, rotationsElement, context);
else if (nodesElement.hasAttribute("type")) // internal use for hosted system infrastructure nodes
return createNodesFromNodeType(cluster, nodesElement, context);
else if (nodesElement.hasAttribute("of")) // hosted node spec referencing a content cluster
@@ -573,8 +575,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
}
}
- private List<Container> createNodesFromNodeCount(ContainerCluster cluster, Element nodesElement, ConfigModelContext context) {
+ private List<Container> createNodesFromNodeCount(ContainerCluster cluster, Element nodesElement, Element rotationsElement, ConfigModelContext context) {
NodesSpecification nodesSpecification = NodesSpecification.from(new ModelElement(nodesElement), context);
+ Set<RotationName> rotations = Rotations.from(rotationsElement);
Map<HostResource, ClusterMembership> hosts = nodesSpecification.provision(cluster.getRoot().getHostSystem(),
ClusterSpec.Type.container,
ClusterSpec.Id.from(cluster.getName()),
diff --git a/config-model/src/main/resources/schema/container.rnc b/config-model/src/main/resources/schema/container.rnc
index 3f0e1f626ac..54d030e6bc0 100644
--- a/config-model/src/main/resources/schema/container.rnc
+++ b/config-model/src/main/resources/schema/container.rnc
@@ -50,3 +50,9 @@ FilterConfig = element filter-config {
Renderer = element renderer {
ComponentDefinition
}
+
+Rotations = element rotations {
+ element rotation {
+ attribute id { xsd:Name }
+ }+
+}
diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc
index 76137fd6263..5ac5a0817b6 100644
--- a/config-model/src/main/resources/schema/containercluster.rnc
+++ b/config-model/src/main/resources/schema/containercluster.rnc
@@ -7,7 +7,8 @@ ContainerCluster = element container | jdisc {
ContainerServices &
DocumentBinding* &
Aliases? &
- NodesOfContainerCluster?
+ NodesOfContainerCluster? &
+ Rotations?
}
ContainerServices =
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/RotationsTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/RotationsTest.java
new file mode 100644
index 00000000000..0c3f31be0f7
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/RotationsTest.java
@@ -0,0 +1,54 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.builder.xml.dom;
+
+import com.yahoo.config.provision.RotationName;
+import com.yahoo.text.XML;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author mpolden
+ */
+public class RotationsTest {
+
+ @Test
+ public void invalid_ids() {
+ assertInvalid("<rotation/>");
+ assertInvalid("<rotation id=''/>"); // Blank
+ assertInvalid("<rotation id='FOO'/>"); // Uppercaes
+ assertInvalid("<rotation id='123'/>"); // Starting with non-character
+ assertInvalid("<rotation id='foo!'/>"); // Non-alphanumeric
+ assertInvalid("<rotation id='fooooooooooooo'/>"); // Too long
+ assertInvalid("<rotation id='foo'/><rotation id='foo'/>"); // Duplicate ID
+ }
+
+ @Test
+ public void valid_ids() {
+ assertEquals(rotations(), xml(""));
+ assertEquals(rotations("foo"), xml("<rotation id='foo'/>"));
+ assertEquals(rotations("foo", "bar"), xml("<rotation id='foo'/><rotation id='bar'/>"));
+ }
+
+ private static Set<RotationName> rotations(String... rotation) {
+ return Arrays.stream(rotation).map(RotationName::from).collect(Collectors.toSet());
+ }
+
+ private static void assertInvalid(String rotations) {
+ try {
+ xml(rotations);
+ fail("Expected exception for input '" + rotations + "'");
+ } catch (IllegalArgumentException ignored) {}
+ }
+
+ private static Set<RotationName> xml(String rotations) {
+ return Rotations.from(XML.getDocument("<rotations>" + rotations + "</rotations>")
+ .getDocumentElement());
+ }
+
+}
diff --git a/config-model/src/test/schema-test-files/services-hosted.xml b/config-model/src/test/schema-test-files/services-hosted.xml
index 90894aa253b..e9b1672ce7d 100644
--- a/config-model/src/test/schema-test-files/services-hosted.xml
+++ b/config-model/src/test/schema-test-files/services-hosted.xml
@@ -18,6 +18,14 @@
<nodes of="search"/>
</container>
+ <container id="container3" version="1.0">
+ <rotations>
+ <rotation id="r1"/>
+ <rotation id="r2"/>
+ </rotations>
+ <nodes of="search"/>
+ </container>
+
<content id="search" version="1.0">
<redundancy>2</redundancy>
<nodes count="7" flavor="large" groups="12"/>
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/RotationName.java b/config-provisioning/src/main/java/com/yahoo/config/provision/RotationName.java
new file mode 100644
index 00000000000..5d9ac3699b3
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/RotationName.java
@@ -0,0 +1,57 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Objects;
+
+/**
+ * Represents a rotation name for a container cluster. Typically created from the rotation element in services.xml.
+ *
+ * @author mpolden
+ */
+public class RotationName implements Comparable<RotationName> {
+
+ private final String name;
+
+ private RotationName(String name) {
+ this.name = requireNonBlank(name, "name must be non-empty");
+ }
+
+ public String value() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RotationName that = (RotationName) o;
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public int compareTo(RotationName o) {
+ return name.compareTo(o.name);
+ }
+
+ @Override
+ public String toString() {
+ return "rotation '" + name + "'";
+ }
+
+ public static RotationName from(String name) {
+ return new RotationName(name);
+ }
+
+ private static String requireNonBlank(String s, String message) {
+ if (s == null || s.isBlank()) {
+ throw new IllegalArgumentException(message);
+ }
+ return s;
+ }
+
+}
diff --git a/configserver/src/test/apps/hosted/services.xml b/configserver/src/test/apps/hosted/services.xml
index 2025a177430..7c2920958a2 100644
--- a/configserver/src/test/apps/hosted/services.xml
+++ b/configserver/src/test/apps/hosted/services.xml
@@ -6,7 +6,7 @@
<nodes count='1'/>
</admin>
- <jdisc version="1.0">
+ <container version="1.0">
<http>
<filtering>
<access-control domain="foo" write="true" />
@@ -15,7 +15,11 @@
</http>
<search/>
<nodes count='1'/>
- </jdisc>
+ <rotations>
+ <rotation id='us-cluster'/>
+ <rotation id='eu-cluster'/>
+ </rotations>
+ </container>
<content id="music" version="1.0">
<redundancy>1</redundancy>