aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/main
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@verizonmedia.com>2020-03-26 17:19:55 +0100
committerJon Bratseth <bratseth@verizonmedia.com>2020-03-26 17:19:55 +0100
commit77fa3b877833b8dc5957039ee02b1068cf0b0be6 (patch)
treebf0b58465e2ee002b100fb000d0eefa0170b7afe /config-model/src/main
parenta1d7251d0b27a5903ac4c700b5bbb80f9c989599 (diff)
Support specifying resource ranges
Diffstat (limited to 'config-model/src/main')
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java40
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java7
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java82
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java2
-rw-r--r--config-model/src/main/resources/schema/common.rnc4
-rw-r--r--config-model/src/main/resources/schema/containercluster.rnc2
-rw-r--r--config-model/src/main/resources/schema/content.rnc8
8 files changed, 103 insertions, 48 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
index 2a21af606ef..298517b85f6 100644
--- a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
+++ b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
@@ -6,6 +6,7 @@ import com.yahoo.collections.Pair;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterMembership;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.HostSpec;
@@ -57,6 +58,8 @@ public class InMemoryProvisioner implements HostProvisioner {
/** Use this index as start index for all clusters */
private final int startIndexForClusters;
+ private final boolean useMaxResources;
+
/** Creates this with a number of nodes with resources 1, 3, 9, 1 */
public InMemoryProvisioner(int nodeCount) {
this(nodeCount, defaultResources);
@@ -64,27 +67,29 @@ public class InMemoryProvisioner implements HostProvisioner {
/** Creates this with a number of nodes with given resources */
public InMemoryProvisioner(int nodeCount, NodeResources resources) {
- this(Map.of(resources, createHostInstances(nodeCount)), true, 0);
+ this(Map.of(resources, createHostInstances(nodeCount)), true, false, 0);
}
/** Creates this with a set of host names of the flavor 'default' */
public InMemoryProvisioner(boolean failOnOutOfCapacity, String... hosts) {
- this(Map.of(defaultResources, toHostInstances(hosts)), failOnOutOfCapacity, 0);
+ this(Map.of(defaultResources, toHostInstances(hosts)), failOnOutOfCapacity, false, 0);
}
/** Creates this with a set of hosts of the flavor 'default' */
public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, String ... retiredHostNames) {
- this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, 0, retiredHostNames);
+ this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, 0, retiredHostNames);
}
/** Creates this with a set of hosts of the flavor 'default' */
public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, int startIndexForClusters, String ... retiredHostNames) {
- this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, startIndexForClusters, retiredHostNames);
+ this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, startIndexForClusters, retiredHostNames);
}
public InMemoryProvisioner(Map<NodeResources, Collection<Host>> hosts, boolean failOnOutOfCapacity,
+ boolean useMaxResources,
int startIndexForClusters, String ... retiredHostNames) {
this.failOnOutOfCapacity = failOnOutOfCapacity;
+ this.useMaxResources = useMaxResources;
for (Map.Entry<NodeResources, Collection<Host>> hostsWithResources : hosts.entrySet())
for (Host host : hostsWithResources.getValue())
freeNodes.put(hostsWithResources.getKey(), host);
@@ -119,30 +124,37 @@ public class InMemoryProvisioner implements HostProvisioner {
}
@Override
- public List<HostSpec> prepare(ClusterSpec cluster, Capacity requestedCapacity, ProvisionLogger logger) {
- if (cluster.group().isPresent() && requestedCapacity.minResources().groups() > 1)
+ public List<HostSpec> prepare(ClusterSpec cluster, Capacity requested, ProvisionLogger logger) {
+ if (useMaxResources)
+ return prepare(cluster, requested.maxResources(), requested.isRequired(), requested.canFail());
+ else
+ return prepare(cluster, requested.minResources(), requested.isRequired(), requested.canFail());
+ }
+
+ public List<HostSpec> prepare(ClusterSpec cluster, ClusterResources requested, boolean required, boolean canFail) {
+ if (cluster.group().isPresent() && requested.groups() > 1)
throw new IllegalArgumentException("Cannot both be specifying a group and ask for groups to be created");
- int capacity = failOnOutOfCapacity || requestedCapacity.isRequired()
- ? requestedCapacity.minResources().nodes()
- : Math.min(requestedCapacity.minResources().nodes(), freeNodes.get(defaultResources).size() + totalAllocatedTo(cluster));
- int groups = requestedCapacity.minResources().groups() > capacity ? capacity : requestedCapacity.minResources().groups();
+ int capacity = failOnOutOfCapacity || required
+ ? requested.nodes()
+ : Math.min(requested.nodes(), freeNodes.get(defaultResources).size() + totalAllocatedTo(cluster));
+ int groups = requested.groups() > capacity ? capacity : requested.groups();
List<HostSpec> allocation = new ArrayList<>();
if (groups == 1) {
allocation.addAll(allocateHostGroup(cluster.with(Optional.of(ClusterSpec.Group.from(0))),
- requestedCapacity.minResources().nodeResources(),
+ requested.nodeResources(),
capacity,
startIndexForClusters,
- requestedCapacity.canFail()));
+ canFail));
}
else {
for (int i = 0; i < groups; i++) {
allocation.addAll(allocateHostGroup(cluster.with(Optional.of(ClusterSpec.Group.from(i))),
- requestedCapacity.minResources().nodeResources(),
+ requested.nodeResources(),
capacity / groups,
allocation.size(),
- requestedCapacity.canFail()));
+ canFail));
}
}
for (ListIterator<HostSpec> i = allocation.listIterator(); i.hasNext(); ) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
index 338679d314b..804a5442608 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
@@ -65,12 +65,12 @@ public class DomAdminV4Builder extends DomAdminBuilderBase {
createSlobroks(deployLogger, admin, allocateHosts(admin.hostSystem(), "slobroks", nodesSpecification));
}
else {
- createSlobroks(deployLogger, admin, pickContainerHostsForSlobrok(nodesSpecification.resources().nodes(), 2));
+ createSlobroks(deployLogger, admin, pickContainerHostsForSlobrok(nodesSpecification.minResources().nodes(), 2));
}
}
private void assignLogserver(DeployState deployState, NodesSpecification nodesSpecification, Admin admin) {
- if (nodesSpecification.resources().nodes() > 1) throw new IllegalArgumentException("You can only request a single log server");
+ if (nodesSpecification.minResources().nodes() > 1) throw new IllegalArgumentException("You can only request a single log server");
if (deployState.getProperties().applicationId().instance().isTester()) return; // No logserver is needed on tester applications
if (nodesSpecification.isDedicated()) {
Collection<HostResource> hosts = allocateHosts(admin.hostSystem(), "logserver", nodesSpecification);
@@ -79,7 +79,7 @@ public class DomAdminV4Builder extends DomAdminBuilderBase {
Logserver logserver = createLogserver(deployState.getDeployLogger(), admin, hosts);
createContainerOnLogserverHost(deployState, admin, logserver.getHostResource());
} else if (containerModels.iterator().hasNext()) {
- List<HostResource> hosts = sortedContainerHostsFrom(containerModels.iterator().next(), nodesSpecification.resources().nodes(), false);
+ List<HostResource> hosts = sortedContainerHostsFrom(containerModels.iterator().next(), nodesSpecification.minResources().nodes(), false);
if (hosts.isEmpty()) return; // No log server can be created (and none is needed)
createLogserver(deployState.getDeployLogger(), admin, hosts);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java
index d34a11abdf4..80c95ad6b59 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java
@@ -172,7 +172,12 @@ public class ModelElement {
/** Returns the content of the attribute with the given name, or null if none */
public String stringAttribute(String name) {
- if ( ! xml.hasAttribute(name)) return null;
+ return stringAttribute(name, null);
+ }
+
+ /** Returns the content of the attribute with the given name, or the default value if none */
+ public String stringAttribute(String name, String defaultValue) {
+ if ( ! xml.hasAttribute(name)) return defaultValue;
return xml.getAttribute(name);
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java
index 71c04dcbca0..ef37481d0fc 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.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.model.builder.xml.dom;
+import com.yahoo.collections.Pair;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.ConfigModelContext;
@@ -19,6 +20,8 @@ import org.w3c.dom.Node;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Function;
+import java.util.regex.Pattern;
/**
* A common utility class to represent a requirement for nodes during model building.
@@ -28,7 +31,7 @@ import java.util.Optional;
*/
public class NodesSpecification {
- private final ClusterResources resources;
+ private final ClusterResources min, max;
private final boolean dedicated;
@@ -51,12 +54,14 @@ public class NodesSpecification {
/** The ID of the cluster referencing this node specification, if any */
private final Optional<String> combinedId;
- private NodesSpecification(ClusterResources resources,
+ private NodesSpecification(ClusterResources min,
+ ClusterResources max,
boolean dedicated, Version version,
boolean required, boolean canFail, boolean exclusive,
Optional<String> dockerImageRepo,
Optional<String> combinedId) {
- this.resources = resources;
+ this.min = min;
+ this.max = max;
this.dedicated = dedicated;
this.version = version;
this.required = required;
@@ -70,7 +75,9 @@ public class NodesSpecification {
ModelElement nodesElement, Optional<String> dockerImageRepo) {
var resolvedElement = resolveElement(nodesElement);
var combinedId = findCombinedId(nodesElement, resolvedElement);
- return new NodesSpecification(toMinResources(resolvedElement),
+ var resources = toResources(resolvedElement);
+ return new NodesSpecification(resources.getFirst(),
+ resources.getSecond(),
dedicated,
version,
resolvedElement.booleanAttribute("required", false),
@@ -80,10 +87,12 @@ public class NodesSpecification {
combinedId);
}
- private static ClusterResources toMinResources(ModelElement nodesElement) {
- return new ClusterResources(nodesElement.integerAttribute("count", 1),
- nodesElement.integerAttribute("groups", 1),
- getResources(nodesElement));
+ private static Pair<ClusterResources, ClusterResources> toResources(ModelElement nodesElement) {
+ Pair<Integer, Integer> nodes = toRange(nodesElement.stringAttribute("count"), 1, Integer::parseInt);
+ Pair<Integer, Integer> groups = toRange(nodesElement.stringAttribute("groups"), 1, Integer::parseInt);
+ var min = new ClusterResources(nodes.getFirst(), groups.getFirst(), nodeResources(nodesElement).getFirst());
+ var max = new ClusterResources(nodes.getSecond(), groups.getSecond(), nodeResources(nodesElement).getSecond());
+ return new Pair<>(min, max);
}
/** Returns the ID of the cluster referencing this node specification, if any */
@@ -139,6 +148,7 @@ public class NodesSpecification {
*/
public static NodesSpecification nonDedicated(int count, ConfigModelContext context) {
return new NodesSpecification(new ClusterResources(count, 1, NodeResources.unspecified),
+ new ClusterResources(count, 1, NodeResources.unspecified),
false,
context.getDeployState().getWantedNodeVespaVersion(),
false,
@@ -151,6 +161,7 @@ public class NodesSpecification {
/** Returns a requirement from <code>count</code> dedicated nodes in one group */
public static NodesSpecification dedicated(int count, ConfigModelContext context) {
return new NodesSpecification(new ClusterResources(count, 1, NodeResources.unspecified),
+ new ClusterResources(count, 1, NodeResources.unspecified),
true,
context.getDeployState().getWantedNodeVespaVersion(),
false,
@@ -160,7 +171,8 @@ public class NodesSpecification {
Optional.empty());
}
- public ClusterResources resources() { return resources; }
+ public ClusterResources minResources() { return min; }
+ public ClusterResources maxResources() { return max; }
/**
* Returns whether this requires dedicated nodes.
@@ -187,29 +199,36 @@ public class NodesSpecification {
.combinedId(combinedId.map(ClusterSpec.Id::from))
.dockerImageRepo(dockerImageRepo)
.build();
- return hostSystem.allocateHosts(cluster, Capacity.from(resources, required, canFail), logger);
+ return hostSystem.allocateHosts(cluster, Capacity.from(min, max, required, canFail), logger);
}
- private static NodeResources getResources(ModelElement nodesElement) {
+ private static Pair<NodeResources, NodeResources> nodeResources(ModelElement nodesElement) {
ModelElement resources = nodesElement.child("resources");
if (resources != null) {
- return new NodeResources(resources.requiredDoubleAttribute("vcpu"),
- parseGbAmount(resources.requiredStringAttribute("memory"), "B"),
- parseGbAmount(resources.requiredStringAttribute("disk"), "B"),
- Optional.ofNullable(resources.stringAttribute("bandwidth"))
- .map(b -> parseGbAmount(b, "BPS"))
- .orElse(0.3),
- parseOptionalDiskSpeed(resources.stringAttribute("disk-speed")),
- parseOptionalStorageType(resources.stringAttribute("storage-type")));
+ return nodeResourcesFromResorcesElement(resources);
}
else if (nodesElement.stringAttribute("flavor") != null) { // legacy fallback
- return NodeResources.fromLegacyName(nodesElement.stringAttribute("flavor"));
+ var flavorResources = NodeResources.fromLegacyName(nodesElement.stringAttribute("flavor"));
+ return new Pair<>(flavorResources, flavorResources);
}
else {
- return NodeResources.unspecified;
+ return new Pair<>(NodeResources.unspecified, NodeResources.unspecified);
}
}
+ private static Pair<NodeResources, NodeResources> nodeResourcesFromResorcesElement(ModelElement element) {
+ Pair<Double, Double> vcpu = toRange(element.requiredStringAttribute("vcpu"), .0, Double::parseDouble);
+ Pair<Double, Double> memory = toRange(element.requiredStringAttribute("memory"), .0, s -> parseGbAmount(s, "B"));
+ Pair<Double, Double> disk = toRange(element.requiredStringAttribute("disk"), .0, s -> parseGbAmount(s, "B"));
+ Pair<Double, Double> bandwith = toRange(element.stringAttribute("bandwith"), .3, s -> parseGbAmount(s, "BPS"));
+ NodeResources.DiskSpeed diskSpeed = parseOptionalDiskSpeed(element.stringAttribute("disk-speed"));
+ NodeResources.StorageType storageType = parseOptionalStorageType(element.stringAttribute("storage-type"));
+
+ var min = new NodeResources(vcpu.getFirst(), memory.getFirst(), disk.getFirst(), bandwith.getFirst(), diskSpeed, storageType);
+ var max = new NodeResources(vcpu.getSecond(), memory.getSecond(), disk.getSecond(), bandwith.getSecond(), diskSpeed, storageType);
+ return new Pair<>(min, max);
+ }
+
private static double parseGbAmount(String byteAmount, String unit) {
byteAmount = byteAmount.strip();
byteAmount = byteAmount.toUpperCase();
@@ -344,9 +363,28 @@ public class NodesSpecification {
return dockerImageFromElement == null ? dockerImage : Optional.of(dockerImageFromElement);
}
+ /** Parses a value ("value") or value range ("[min-value, max-value]") */
+ private static <T> Pair<T, T> toRange(String s, T defaultValue, Function<String, T> valueParser) {
+ try {
+ if (s == null) return new Pair<>(defaultValue, defaultValue);
+ s = s.trim();
+ if (s.startsWith("[") && s.endsWith("]")) {
+ String[] numbers = s.substring(1, s.length() - 1).split(",");
+ if (numbers.length != 2) throw new IllegalArgumentException();
+ return new Pair<>(valueParser.apply(numbers[0].trim()), valueParser.apply(numbers[1].trim()));
+ } else {
+ return new Pair<>(valueParser.apply(s), valueParser.apply(s));
+ }
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Expected a number or range on the form [min, max], but got '" + s + "'");
+ }
+ }
+
@Override
public String toString() {
- return "specification of " + (dedicated ? "dedicated " : "") + resources;
+ return "specification of " + (dedicated ? "dedicated " : "") +
+ (min.equals(max) ? min : "min " + min + " max " + max);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
index d5d9f6b51bb..6dd3e619ec2 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
@@ -286,7 +286,7 @@ public class ContentCluster extends AbstractConfigProducer implements
.orElse(NodesSpecification.nonDedicated(3, context));
Collection<HostResource> hosts = nodesSpecification.isDedicated() ?
getControllerHosts(nodesSpecification, admin, clusterName, context) :
- drawControllerHosts(nodesSpecification.resources().nodes(), rootGroup, containers);
+ drawControllerHosts(nodesSpecification.minResources().nodes(), rootGroup, containers);
clusterControllers = createClusterControllers(new ClusterControllerCluster(contentCluster, "standalone"),
hosts,
clusterName,
diff --git a/config-model/src/main/resources/schema/common.rnc b/config-model/src/main/resources/schema/common.rnc
index c47983adc12..51a1d5f36b9 100644
--- a/config-model/src/main/resources/schema/common.rnc
+++ b/config-model/src/main/resources/schema/common.rnc
@@ -17,7 +17,7 @@ anyElement = element * {
JavaId = xsd:string { pattern = "([a-zA-Z_$][a-zA-Z\d_$]*\.)*[a-zA-Z_$][a-zA-Z\d_$]*" }
Nodes = element nodes {
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute docker-image { xsd:string }? &
Resources?
@@ -32,7 +32,7 @@ Resources = element resources {
}
OptionalDedicatedNodes = element nodes {
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute required { xsd:boolean }? &
attribute docker-image { xsd:string }? &
diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc
index 726fa849c00..3c8b60fb84b 100644
--- a/config-model/src/main/resources/schema/containercluster.rnc
+++ b/config-model/src/main/resources/schema/containercluster.rnc
@@ -239,7 +239,7 @@ NodesOfContainerCluster = element nodes {
attribute type { xsd:string }
|
(
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute required { xsd:boolean }? &
attribute exclusive { xsd:boolean }? &
diff --git a/config-model/src/main/resources/schema/content.rnc b/config-model/src/main/resources/schema/content.rnc
index ee451185415..b1821680b14 100644
--- a/config-model/src/main/resources/schema/content.rnc
+++ b/config-model/src/main/resources/schema/content.rnc
@@ -221,11 +221,11 @@ ContentNodes = element nodes {
attribute vespamalloc-debug-stacktrace { xsd:string }? &
(
(
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute required { xsd:boolean }? &
attribute docker-image { xsd:string }? &
- attribute groups { xsd:positiveInteger }?
+ attribute groups { xsd:positiveInteger | xsd:string }?
)
|
ContentNode +
@@ -266,12 +266,12 @@ Group = element group {
|
(
element nodes {
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute required { xsd:boolean }? &
attribute exclusive { xsd:boolean }? &
attribute docker-image { xsd:string }? &
- attribute groups { xsd:positiveInteger }?
+ attribute groups { xsd:positiveInteger | xsd:string }?
}
)
|