summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java14
-rw-r--r--config-provisioning/abi-spec.json25
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java25
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java62
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java81
-rw-r--r--config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java52
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeResourceComparator.java (renamed from node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacityComparator.java)13
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java12
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java77
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java1
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java23
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java3
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java36
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java1
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java3
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java63
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacityTest.java55
22 files changed, 357 insertions, 234 deletions
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 b2d9b31e9b2..bf32b944410 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
@@ -178,7 +178,8 @@ public class NodesSpecification {
if (resources != null) {
return Optional.of(new NodeResources(resources.requiredDoubleAttribute("vcpu"),
parseGbAmount(resources.requiredStringAttribute("memory")),
- parseGbAmount(resources.requiredStringAttribute("disk"))));
+ parseGbAmount(resources.requiredStringAttribute("disk")),
+ parseOptionalDiskSpeed(resources.stringAttribute("disk-speed"))));
}
else if (nodesElement.stringAttribute("flavor") != null) { // legacy fallback
return Optional.of(NodeResources.fromLegacyName(nodesElement.stringAttribute("flavor")));
@@ -223,6 +224,17 @@ public class NodesSpecification {
}
}
+ private static NodeResources.DiskSpeed parseOptionalDiskSpeed(String diskSpeedString) {
+ if (diskSpeedString == null) return NodeResources.DiskSpeed.fast;
+ switch (diskSpeedString) {
+ case "fast" : return NodeResources.DiskSpeed.fast;
+ case "slow" : return NodeResources.DiskSpeed.slow;
+ case "any" : return NodeResources.DiskSpeed.any;
+ default: throw new IllegalArgumentException("Illegal disk-speed value '" + diskSpeedString +
+ "': Legal values are 'fast', 'slow' and 'any')");
+ }
+ }
+
@Override
public String toString() {
return "specification of " + count + (dedicated ? " dedicated " : " ") + "nodes" +
diff --git a/config-provisioning/abi-spec.json b/config-provisioning/abi-spec.json
index fc9a3e011d9..f942ae56d64 100644
--- a/config-provisioning/abi-spec.json
+++ b/config-provisioning/abi-spec.json
@@ -384,6 +384,8 @@
"public java.lang.String name()",
"public int cost()",
"public boolean isStock()",
+ "public boolean isConfigured()",
+ "public com.yahoo.config.provision.NodeResources resources()",
"public double getMinMainMemoryAvailableGb()",
"public double getMinDiskAvailableGb()",
"public boolean hasFastDisk()",
@@ -401,8 +403,6 @@
"public boolean hasAtLeast(com.yahoo.config.provision.NodeResources)",
"public void freeze()",
"public boolean isLargerThan(com.yahoo.config.provision.Flavor)",
- "public boolean isConfigured()",
- "public com.yahoo.config.provision.NodeResources resources()",
"public int hashCode()",
"public boolean equals(java.lang.Object)",
"public java.lang.String toString()"
@@ -590,6 +590,24 @@
],
"fields": []
},
+ "com.yahoo.config.provision.NodeResources$DiskSpeed": {
+ "superClass": "java.lang.Enum",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods": [
+ "public static com.yahoo.config.provision.NodeResources$DiskSpeed[] values()",
+ "public static com.yahoo.config.provision.NodeResources$DiskSpeed valueOf(java.lang.String)"
+ ],
+ "fields": [
+ "public static final enum com.yahoo.config.provision.NodeResources$DiskSpeed fast",
+ "public static final enum com.yahoo.config.provision.NodeResources$DiskSpeed slow",
+ "public static final enum com.yahoo.config.provision.NodeResources$DiskSpeed any"
+ ]
+ },
"com.yahoo.config.provision.NodeResources": {
"superClass": "java.lang.Object",
"interfaces": [],
@@ -598,14 +616,17 @@
],
"methods": [
"public void <init>(double, double, double)",
+ "public void <init>(double, double, double, com.yahoo.config.provision.NodeResources$DiskSpeed)",
"public double vcpu()",
"public double memoryGb()",
"public double diskGb()",
+ "public com.yahoo.config.provision.NodeResources$DiskSpeed diskSpeed()",
"public boolean allocateByLegacyName()",
"public java.util.Optional legacyName()",
"public boolean equals(java.lang.Object)",
"public int hashCode()",
"public java.lang.String toString()",
+ "public boolean satisfies(com.yahoo.config.provision.NodeResources)",
"public static com.yahoo.config.provision.NodeResources fromLegacyName(java.lang.String)"
],
"fields": []
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java b/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java
index d90f52e971b..de4f3a555bd 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java
@@ -39,6 +39,7 @@ public class AllocatedHosts {
private static final String vcpuKey = "vcpu";
private static final String memoryKey = "memory";
private static final String diskKey = "disk";
+ private static final String diskSpeedKey = "diskSpeed";
/** Wanted version */
private static final String hostSpecVespaVersionKey = "vespaVersion";
@@ -92,6 +93,7 @@ public class AllocatedHosts {
resourcesObject.setDouble(vcpuKey, resources.vcpu());
resourcesObject.setDouble(memoryKey, resources.memoryGb());
resourcesObject.setDouble(diskKey, resources.diskGb());
+ resourcesObject.setString(diskSpeedKey, diskSpeedToString(resources.diskSpeed()));
}
}
@@ -131,13 +133,34 @@ public class AllocatedHosts {
Inspector resources = object.field(resourcesKey);
return Optional.of(new Flavor(new NodeResources(resources.field(vcpuKey).asDouble(),
resources.field(memoryKey).asDouble(),
- resources.field(diskKey).asDouble())));
+ resources.field(diskKey).asDouble(),
+ diskSpeedFromSlime(resources.field(diskSpeedKey)))));
}
else {
return Optional.empty();
}
}
+ private static NodeResources.DiskSpeed diskSpeedFromSlime(Inspector diskSpeed) {
+ if ( ! diskSpeed.valid()) return NodeResources.DiskSpeed.fast; // TODO: Remove this line after June 2019
+ switch (diskSpeed.asString()) {
+ case "fast" : return NodeResources.DiskSpeed.fast;
+ case "slow" : return NodeResources.DiskSpeed.slow;
+ case "any" : return NodeResources.DiskSpeed.any;
+ default: throw new IllegalStateException("Illegal disk-speed value '" + diskSpeed.asString() + "'");
+ }
+ }
+
+ private static String diskSpeedToString(NodeResources.DiskSpeed diskSpeed) {
+ switch (diskSpeed) {
+ case fast : return "fast";
+ case slow : return "slow";
+ case any : return "any";
+ default: throw new IllegalStateException("Illegal disk-speed value '" + diskSpeed + "'");
+ }
+
+ }
+
private static ClusterMembership membershipFromSlime(Inspector object) {
return ClusterMembership.from(object.field(hostSpecMembershipKey).asString(),
com.yahoo.component.Version.fromString(object.field(hostSpecVespaVersionKey).asString()));
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java
index 74147a55a76..8667707883d 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java
@@ -25,10 +25,6 @@ public class Flavor {
private final int cost;
private final boolean isStock;
private final Type type;
- private final double minCpuCores;
- private final double minMainMemoryAvailableGb;
- private final double minDiskAvailableGb;
- private final boolean fastDisk;
private final double bandwidth;
private final String description;
private final boolean retired;
@@ -45,16 +41,15 @@ public class Flavor {
this.cost = flavorConfig.cost();
this.isStock = flavorConfig.stock();
this.type = Type.valueOf(flavorConfig.environment());
- this.minCpuCores = flavorConfig.minCpuCores();
- this.minMainMemoryAvailableGb = flavorConfig.minMainMemoryAvailableGb();
- this.minDiskAvailableGb = flavorConfig.minDiskAvailableGb();
- this.fastDisk = flavorConfig.fastDisk();
+ this.resources = new NodeResources(flavorConfig.minCpuCores(),
+ flavorConfig.minMainMemoryAvailableGb(),
+ flavorConfig.minDiskAvailableGb(),
+ flavorConfig.fastDisk() ? NodeResources.DiskSpeed.fast : NodeResources.DiskSpeed.slow);
this.bandwidth = flavorConfig.bandwidth();
this.description = flavorConfig.description();
this.retired = flavorConfig.retired();
this.replacesFlavors = new ArrayList<>();
this.idealHeadroom = flavorConfig.idealHeadroom();
- this.resources = new NodeResources(minCpuCores, minMainMemoryAvailableGb, minDiskAvailableGb);
}
/** Creates a *node* flavor from a node resources spec */
@@ -68,10 +63,6 @@ public class Flavor {
this.cost = 0;
this.isStock = true;
this.type = Type.DOCKER_CONTAINER;
- this.minCpuCores = resources.vcpu();
- this.minMainMemoryAvailableGb = resources.memoryGb();
- this.minDiskAvailableGb = resources.diskGb();
- this.fastDisk = true;
this.bandwidth = 1;
this.description = "";
this.retired = false;
@@ -93,15 +84,23 @@ public class Flavor {
public boolean isStock() { return isStock; }
- public double getMinMainMemoryAvailableGb() { return minMainMemoryAvailableGb; }
+ /**
+ * True if this is a configured flavor used for hosts,
+ * false if it is a virtual flavor created on the fly from node resources
+ */
+ public boolean isConfigured() { return configured; }
- public double getMinDiskAvailableGb() { return minDiskAvailableGb; }
+ public NodeResources resources() { return resources; }
+
+ public double getMinMainMemoryAvailableGb() { return resources.memoryGb(); }
- public boolean hasFastDisk() { return fastDisk; }
+ public double getMinDiskAvailableGb() { return resources.diskGb(); }
+
+ public boolean hasFastDisk() { return resources.diskSpeed() == NodeResources.DiskSpeed.fast; }
public double getBandwidth() { return bandwidth; }
- public double getMinCpuCores() { return minCpuCores; }
+ public double getMinCpuCores() { return resources.vcpu(); }
public String getDescription() { return description; }
@@ -170,9 +169,7 @@ public class Flavor {
* as large as the given resources.
*/
public boolean hasAtLeast(NodeResources resources) {
- return this.minCpuCores >= resources.vcpu() &&
- this.minMainMemoryAvailableGb >= resources.memoryGb() &&
- this.minDiskAvailableGb >= resources.diskGb();
+ return this.resources.satisfies(resources);
}
/** Irreversibly freezes the content of this */
@@ -182,28 +179,21 @@ public class Flavor {
/** Returns whether this flavor has at least as much of each hardware resource as the given flavor */
public boolean isLargerThan(Flavor other) {
- return this.minCpuCores >= other.minCpuCores &&
- this.minDiskAvailableGb >= other.minDiskAvailableGb &&
- this.minMainMemoryAvailableGb >= other.minMainMemoryAvailableGb &&
- this.fastDisk || ! other.fastDisk;
+ return hasAtLeast(other.resources);
}
- /**
- * True if this is a configured flavor used for hosts,
- * false if it is a virtual flavor created on the fly from node resources
- */
- public boolean isConfigured() { return configured; }
-
- public NodeResources resources() { return resources; }
-
@Override
public int hashCode() { return name.hashCode(); }
@Override
- public boolean equals(Object other) {
- if (other == this) return true;
- if ( ! (other instanceof Flavor)) return false;
- return ((Flavor)other).name.equals(this.name);
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if ( ! (o instanceof Flavor)) return false;
+ Flavor other = (Flavor)o;
+ if (configured)
+ return other.name.equals(this.name);
+ else
+ return this.resources.equals(other.resources);
}
@Override
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
index 005bfac6b5c..b5d16c35dac 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
@@ -1,6 +1,7 @@
// 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;
import java.util.Optional;
/**
@@ -10,27 +11,42 @@ import java.util.Optional;
*/
public class NodeResources {
+ public enum DiskSpeed {
+ fast, // SSD disk or similar speed is needed
+ slow, // This is tuned to work with the speed of spinning disks
+ any // The performance of the cluster using this does not depend on disk speed
+ }
+
private final double vcpu;
private final double memoryGb;
private final double diskGb;
+ private final DiskSpeed diskSpeed;
private final boolean allocateByLegacyName;
/** The legacy (flavor) name of this, or null if none */
private final String legacyName;
+ /** Create node resources requiring fast disk */
public NodeResources(double vcpu, double memoryGb, double diskGb) {
+ this(vcpu, memoryGb, diskGb, DiskSpeed.fast);
+ }
+
+ public NodeResources(double vcpu, double memoryGb, double diskGb, DiskSpeed diskSpeed) {
this.vcpu = vcpu;
this.memoryGb = memoryGb;
this.diskGb = diskGb;
+ this.diskSpeed = diskSpeed;
this.allocateByLegacyName = false;
this.legacyName = null;
}
- private NodeResources(double vcpu, double memoryGb, double diskGb, boolean allocateByLegacyName, String legacyName) {
+ private NodeResources(double vcpu, double memoryGb, double diskGb, DiskSpeed diskSpeed,
+ boolean allocateByLegacyName, String legacyName) {
this.vcpu = vcpu;
this.memoryGb = memoryGb;
this.diskGb = diskGb;
+ this.diskSpeed = diskSpeed;
this.allocateByLegacyName = allocateByLegacyName;
this.legacyName = legacyName;
}
@@ -38,6 +54,25 @@ public class NodeResources {
public double vcpu() { return vcpu; }
public double memoryGb() { return memoryGb; }
public double diskGb() { return diskGb; }
+ public DiskSpeed diskSpeed() { return diskSpeed; }
+
+ public NodeResources subtract(NodeResources other) {
+ if ( ! this.isInterchangeableWith(other))
+ throw new IllegalArgumentException(this + " and " + other + " are not interchangeable");
+ return new NodeResources(vcpu - other.vcpu,
+ memoryGb - other.memoryGb,
+ diskGb - other.diskGb,
+ combine(this.diskSpeed, other.diskSpeed));
+ }
+
+ public NodeResources add(NodeResources other) {
+ if ( ! this.isInterchangeableWith(other))
+ throw new IllegalArgumentException(this + " and " + other + " are not interchangeable");
+ return new NodeResources(vcpu + other.vcpu,
+ memoryGb + other.memoryGb,
+ diskGb + other.diskGb,
+ combine(this.diskSpeed, other.diskSpeed));
+ }
/**
* If this is true, a non-docker legacy name was used to specify this and we'll respect that by mapping directly.
@@ -50,6 +85,23 @@ public class NodeResources {
return Optional.ofNullable(legacyName);
}
+ private boolean isInterchangeableWith(NodeResources other) {
+ if (this.allocateByLegacyName != other.allocateByLegacyName) return false;
+ if (this.allocateByLegacyName) return legacyName.equals(other.legacyName);
+
+ if (this.diskSpeed != DiskSpeed.any && other.diskSpeed != DiskSpeed.any && this.diskSpeed != other.diskSpeed)
+ return false;
+
+ return true;
+ }
+
+ private DiskSpeed combine(DiskSpeed a, DiskSpeed b) {
+ if (a == DiskSpeed.any) return b;
+ if (b == DiskSpeed.any) return a;
+ if (a == b) return a;
+ throw new IllegalArgumentException(a + " cannot be combined with " + b);
+ }
+
@Override
public boolean equals(Object o) {
if (o == this) return true;
@@ -62,6 +114,7 @@ public class NodeResources {
if (this.vcpu != other.vcpu) return false;
if (this.memoryGb != other.memoryGb) return false;
if (this.diskGb != other.diskGb) return false;
+ if (this.diskSpeed != other.diskSpeed) return false;
return true;
}
}
@@ -71,7 +124,7 @@ public class NodeResources {
if (allocateByLegacyName)
return legacyName.hashCode();
else
- return (int)(2503 * vcpu + 22123 * memoryGb + 26987 * diskGb);
+ return (int)(2503 * vcpu + 22123 * memoryGb + 26987 * diskGb + diskSpeed.hashCode());
}
@Override
@@ -79,7 +132,25 @@ public class NodeResources {
if (allocateByLegacyName)
return "flavor '" + legacyName + "'";
else
- return "[vcpu: " + vcpu + ", memory: " + memoryGb + " Gb, disk " + diskGb + " Gb]";
+ return "[vcpu: " + vcpu + ", memory: " + memoryGb + " Gb, disk " + diskGb + " Gb" +
+ (diskSpeed != DiskSpeed.fast ? ", disk speed: " + diskSpeed : "") + "]";
+ }
+
+ /** Returns true if all the resources of this are the same or larger than the given resources */
+ public boolean satisfies(NodeResources other) {
+ if (this.allocateByLegacyName || other.allocateByLegacyName) // resources are not available
+ return Objects.equals(this.legacyName, other.legacyName);
+
+ if (this.vcpu < other.vcpu()) return false;
+ if (this.memoryGb < other.memoryGb) return false;
+ if (this.diskGb < other.diskGb) return false;
+
+ // Why doesn't a fast disk satisfy a slow disk? Because if slow disk is explicitly specified
+ // (i.e not "any"), you should not randomly, sometimes get a faster disk as that means you may
+ // draw conclusions about performance on the basis of better resources than you think you have
+ if (other.diskSpeed != DiskSpeed.any && other.diskSpeed != this.diskSpeed) return false;
+
+ return true;
}
/**
@@ -96,10 +167,10 @@ public class NodeResources {
if (cpu == 0) cpu = 0.5;
if (cpu == 2 && mem == 8 ) cpu = 1.5;
if (cpu == 2 && mem == 12 ) cpu = 2.3;
- return new NodeResources(cpu, mem, dsk, false, flavorString);
+ return new NodeResources(cpu, mem, dsk, DiskSpeed.fast, false, flavorString);
}
else { // Another legacy flavor: Allocate by direct matching
- return new NodeResources(0, 0, 0, true, flavorString);
+ return new NodeResources(0, 0, 0, DiskSpeed.fast, true, flavorString);
}
}
diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java
index 3a37c96c612..737c1047197 100644
--- a/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java
+++ b/config-provisioning/src/test/java/com/yahoo/config/provision/AllocatedHostsTest.java
@@ -30,8 +30,10 @@ public class AllocatedHostsTest {
List.of("alias1", "alias2")));
hosts.add(new HostSpec("allocated",
Optional.of(ClusterMembership.from("container/test/0/0", com.yahoo.component.Version.fromString("6.73.1")))));
- hosts.add(new HostSpec("flavor-from-resources",
+ hosts.add(new HostSpec("flavor-from-resources-1",
Collections.emptyList(), new Flavor(new NodeResources(0.5, 3.1, 4))));
+ hosts.add(new HostSpec("flavor-from-resources-2",
+ Collections.emptyList(), new Flavor(new NodeResources(0.5, 3.1, 4, NodeResources.DiskSpeed.any))));
hosts.add(new HostSpec("configured-flavor",
Collections.emptyList(), configuredFlavors.getFlavorOrThrow("C/12/45/100")));
hosts.add(new HostSpec("with-version",
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
index cbc84f44a48..d38a6e5031c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
@@ -85,6 +85,7 @@ public class NodeSerializer {
private static final String vcpuKey = "vcpu";
private static final String memoryKey = "memory";
private static final String diskKey = "disk";
+ private static final String diskSpeedKey = "diskSpeed";
// Allocation fields
private static final String tenantIdKey = "tenantId";
@@ -158,6 +159,7 @@ public class NodeSerializer {
resourcesObject.setDouble(vcpuKey, resources.vcpu());
resourcesObject.setDouble(memoryKey, resources.memoryGb());
resourcesObject.setDouble(diskKey, resources.diskGb());
+ resourcesObject.setString(diskSpeedKey, diskSpeedToString(resources.diskSpeed()));
}
}
@@ -232,7 +234,8 @@ public class NodeSerializer {
Inspector resources = object.field(resourcesKey);
return new Flavor(new NodeResources(resources.field(vcpuKey).asDouble(),
resources.field(memoryKey).asDouble(),
- resources.field(diskKey).asDouble()));
+ resources.field(diskKey).asDouble(),
+ diskSpeedFromSlime(resources.field(diskSpeedKey))));
}
}
@@ -426,4 +429,23 @@ public class NodeSerializer {
throw new IllegalArgumentException("Serialized form of '" + type + "' not defined");
}
+ private static NodeResources.DiskSpeed diskSpeedFromSlime(Inspector diskSpeed) {
+ if ( ! diskSpeed.valid()) return NodeResources.DiskSpeed.fast; // TODO: Remove this line after June 2019
+ switch (diskSpeed.asString()) {
+ case "fast" : return NodeResources.DiskSpeed.fast;
+ case "slow" : return NodeResources.DiskSpeed.slow;
+ case "any" : return NodeResources.DiskSpeed.any;
+ default: throw new IllegalStateException("Illegal disk-speed value '" + diskSpeed.asString() + "'");
+ }
+ }
+
+ private static String diskSpeedToString(NodeResources.DiskSpeed diskSpeed) {
+ switch (diskSpeed) {
+ case fast : return "fast";
+ case slow : return "slow";
+ case any : return "any";
+ default: throw new IllegalStateException("Illegal disk-speed value '" + diskSpeed + "'");
+ }
+
+ }
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java
index 5b33f8261a4..5dc41b54b61 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.config.provision.Flavor;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.vespa.hosted.provision.LockedNodeList;
import com.yahoo.vespa.hosted.provision.Node;
@@ -56,16 +57,16 @@ public class DockerHostCapacity {
return comp;
}
- private int compare(ResourceCapacity a, ResourceCapacity b) {
- return ResourceCapacityComparator.defaultOrder().compare(a, b);
+ private int compare(NodeResources a, NodeResources b) {
+ return NodeResourceComparator.defaultOrder().compare(a, b);
}
/**
* Checks the node capacity and free ip addresses to see
* if we could allocate a flavor on the docker host.
*/
- boolean hasCapacity(Node dockerHost, ResourceCapacity requestedCapacity) {
- return freeCapacityOf(dockerHost, false).hasCapacityFor(requestedCapacity) && freeIPs(dockerHost) > 0;
+ boolean hasCapacity(Node dockerHost, NodeResources requestedCapacity) {
+ return freeCapacityOf(dockerHost, false).satisfies(requestedCapacity) && freeIPs(dockerHost) > 0;
}
/**
@@ -75,21 +76,22 @@ public class DockerHostCapacity {
return dockerHost.ipAddressPool().findUnused(allNodes).size();
}
- public ResourceCapacity getFreeCapacityTotal() {
+ // TODO: Capacity cannot be added when some have slow disks
+ public NodeResources getFreeCapacityTotal() {
return allNodes.asList().stream()
.filter(n -> n.type().equals(NodeType.host))
.map(n -> freeCapacityOf(n, false))
- .reduce(ResourceCapacity.NONE, ResourceCapacity::add);
+ .reduce(new NodeResources(0, 0, 0), NodeResources::add);
}
- public ResourceCapacity getCapacityTotal() {
+ // TODO: Capacity cannot be added when some have slow disks
+ public NodeResources getCapacityTotal() {
return allNodes.asList().stream()
.filter(n -> n.type().equals(NodeType.host))
- .map(ResourceCapacity::of)
- .reduce(ResourceCapacity.NONE, ResourceCapacity::add);
+ .map(host -> host.flavor().resources())
+ .reduce(new NodeResources(0, 0, 0), NodeResources::add);
}
-
public int freeCapacityInFlavorEquivalence(Flavor flavor) {
return allNodes.asList().stream()
.filter(n -> n.type().equals(NodeType.host))
@@ -100,19 +102,19 @@ public class DockerHostCapacity {
public long getNofHostsAvailableFor(Flavor flavor) {
return allNodes.asList().stream()
.filter(n -> n.type().equals(NodeType.host))
- .filter(n -> hasCapacity(n, ResourceCapacity.of(flavor)))
+ .filter(n -> hasCapacity(n, flavor.resources()))
.count();
}
private int canFitNumberOf(Node node, Flavor flavor) {
- ResourceCapacity freeCapacity = freeCapacityOf(node, false);
+ NodeResources freeCapacity = freeCapacityOf(node, false);
int capacityFactor = freeCapacityInFlavorEquivalence(freeCapacity, flavor);
int ips = freeIPs(node);
return Math.min(capacityFactor, ips);
}
- int freeCapacityInFlavorEquivalence(ResourceCapacity freeCapacity, Flavor flavor) {
- if ( ! freeCapacity.hasCapacityFor(ResourceCapacity.of(flavor))) return 0;
+ int freeCapacityInFlavorEquivalence(NodeResources freeCapacity, Flavor flavor) {
+ if ( ! freeCapacity.satisfies(flavor.resources())) return 0;
double cpuFactor = Math.floor(freeCapacity.vcpu() / flavor.getMinCpuCores());
double memoryFactor = Math.floor(freeCapacity.memoryGb() / flavor.getMinMainMemoryAvailableGb());
@@ -123,28 +125,24 @@ public class DockerHostCapacity {
/**
* Calculate the remaining capacity for the dockerHost.
- * @param dockerHost The host to find free capacity of.
*
+ * @param dockerHost The host to find free capacity of.
* @return A default (empty) capacity if not a docker host, otherwise the free/unallocated/rest capacity
*/
- public ResourceCapacity freeCapacityOf(Node dockerHost, boolean treatInactiveOrRetiredAsUnusedCapacity) {
+ public NodeResources freeCapacityOf(Node dockerHost, boolean includeInactive) {
// Only hosts have free capacity
- if (!dockerHost.type().equals(NodeType.host)) return ResourceCapacity.NONE;
+ if ( ! dockerHost.type().equals(NodeType.host)) return new NodeResources(0, 0, 0);
return allNodes.childrenOf(dockerHost).asList().stream()
- .filter(container -> !(treatInactiveOrRetiredAsUnusedCapacity && isInactiveOrRetired(container)))
- .map(ResourceCapacity::of)
- .reduce(ResourceCapacity.of(dockerHost), ResourceCapacity::subtract);
+ .filter(container -> !(includeInactive && isInactiveOrRetired(container)))
+ .map(host -> host.flavor().resources())
+ .reduce(dockerHost.flavor().resources(), NodeResources::subtract);
}
private boolean isInactiveOrRetired(Node node) {
- boolean isInactive = node.state().equals(Node.State.inactive);
- boolean isRetired = false;
- if (node.allocation().isPresent()) {
- isRetired = node.allocation().get().membership().retired();
- }
-
- return isInactive || isRetired;
+ if (node.state().equals(Node.State.inactive)) return true;
+ if (node.allocation().isPresent() && node.allocation().get().membership().retired()) return true;
+ return false;
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
index 4a1f4f86711..ff412c6c99b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
@@ -63,9 +63,9 @@ public class GroupPreparer {
// Create a prioritized set of nodes
LockedNodeList nodeList = nodeRepository.list(allocationLock);
- NodePrioritizer prioritizer = new NodePrioritizer(
- nodeList, application, cluster, requestedNodes, spareCount, nodeRepository.nameResolver(),
- nodeRepository.getAvailableFlavors());
+ NodePrioritizer prioritizer = new NodePrioritizer(nodeList, application, cluster, requestedNodes,
+ spareCount, nodeRepository.nameResolver(),
+ nodeRepository.getAvailableFlavors());
prioritizer.addApplicationNodes();
prioritizer.addSurplusNodes(surplusActiveNodes);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java
index 818d3979216..bf8ea6158ee 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodePrioritizer.java
@@ -146,20 +146,19 @@ class NodePrioritizer {
private void addNewDockerNodesOn(LockedNodeList candidates) {
if ( ! isDocker) return;
- ResourceCapacity wantedResourceCapacity = ResourceCapacity.of(resources(requestedNodes));
+ NodeResources wantedResources = resources(requestedNodes);
for (Node node : candidates) {
if (node.type() != NodeType.host) continue;
if (node.status().wantToRetire()) continue;
- boolean hostHasCapacityForWantedFlavor = capacity.hasCapacity(node, wantedResourceCapacity);
+ boolean hostHasCapacityForWantedFlavor = capacity.hasCapacity(node, wantedResources);
boolean conflictingCluster = allNodes.childrenOf(node).owner(appId).asList().stream()
.anyMatch(child -> child.allocation().get().membership().cluster().id().equals(clusterSpec.id()));
if (!hostHasCapacityForWantedFlavor || conflictingCluster) continue;
log.log(LogLevel.DEBUG, "Trying to add new Docker node on " + node);
-
Optional<IP.Allocation> allocation;
try {
allocation = node.ipConfig().pool().findAllocation(allNodes, nameResolver);
@@ -271,8 +270,7 @@ class NodePrioritizer {
private static int compareForRelocation(Node a, Node b) {
// Choose smallest node
- int capacity = ResourceCapacityComparator.defaultOrder().compare(ResourceCapacity.of(a),
- ResourceCapacity.of(b));
+ int capacity = NodeResourceComparator.defaultOrder().compare(a.flavor().resources(), b.flavor().resources());
if (capacity != 0) return capacity;
// Choose unallocated over allocated (this case is when we have ready docker nodes)
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacityComparator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeResourceComparator.java
index c4f605bb9fc..4ed7e063439 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacityComparator.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeResourceComparator.java
@@ -1,6 +1,8 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.provisioning;
+import com.yahoo.config.provision.NodeResources;
+
import java.util.Comparator;
/**
@@ -8,20 +10,21 @@ import java.util.Comparator;
*
* @author bratseth
*/
-public class ResourceCapacityComparator {
+public class NodeResourceComparator {
private static final MemoryDiskCpu memoryDiskCpuComparator = new MemoryDiskCpu();
/** Returns the default ordering */
- public static Comparator<ResourceCapacity> defaultOrder() { return memoryDiskCpuOrder(); }
+ public static Comparator<NodeResources> defaultOrder() { return memoryDiskCpuOrder(); }
/** Returns a comparator comparing by memory, disk, vcpu */
- public static Comparator<ResourceCapacity> memoryDiskCpuOrder() { return memoryDiskCpuComparator; }
+ public static Comparator<NodeResources> memoryDiskCpuOrder() { return memoryDiskCpuComparator; }
- private static class MemoryDiskCpu implements Comparator<ResourceCapacity> {
+ private static class MemoryDiskCpu implements Comparator<NodeResources> {
+ // TODO: Take disk into account
@Override
- public int compare(ResourceCapacity a, ResourceCapacity b) {
+ public int compare(NodeResources a, NodeResources b) {
if (a.memoryGb() > b.memoryGb()) return 1;
if (a.memoryGb() < b.memoryGb()) return -1;
if (a.diskGb() > b.diskGb()) return 1;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java
index ab48e82fbed..d54b1e66708 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java
@@ -97,6 +97,9 @@ public interface NodeSpec {
return true;
}
else {
+ // Note: changing this condition to flavor.resources().satisfies(requestedNodeResources))
+ // is semantically correct, but we only want partial matching when allocating from docker hosts,
+ // which is done separately, so we compare by equality here
if (requestedNodeResources.equals(flavor.resources()))
return true;
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java
index 7abb43374bf..d1a79d0e28d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/PrioritizableNode.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.provision.provisioning;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.hosted.provision.Node;
import java.util.Optional;
@@ -16,7 +17,7 @@ class PrioritizableNode implements Comparable<PrioritizableNode> {
Node node;
/** The free capacity, including retired allocations */
- final ResourceCapacity freeParentCapacity;
+ final NodeResources freeParentCapacity;
/** The parent host (docker or hypervisor) */
final Optional<Node> parent;
@@ -33,7 +34,7 @@ class PrioritizableNode implements Comparable<PrioritizableNode> {
/** True if exact flavor is specified by the allocation request and this node has this flavor */
final boolean preferredOnFlavor;
- private PrioritizableNode(Node node, ResourceCapacity freeParentCapacity, Optional<Node> parent, boolean violatesSpares, boolean isSurplusNode, boolean isNewNode, boolean preferredOnFlavor) {
+ private PrioritizableNode(Node node, NodeResources freeParentCapacity, Optional<Node> parent, boolean violatesSpares, boolean isSurplusNode, boolean isNewNode, boolean preferredOnFlavor) {
this.node = node;
this.freeParentCapacity = freeParentCapacity;
this.parent = parent;
@@ -90,7 +91,7 @@ class PrioritizableNode implements Comparable<PrioritizableNode> {
if (other.parent.isPresent() && !this.parent.isPresent()) return 1;
// Choose the node with parent node with the least capacity (TODO parameterize this as this is pretty much the core of the algorithm)
- int freeCapacity = ResourceCapacityComparator.defaultOrder().compare(this.freeParentCapacity, other.freeParentCapacity);
+ int freeCapacity = NodeResourceComparator.defaultOrder().compare(this.freeParentCapacity, other.freeParentCapacity);
if (freeCapacity != 0) return freeCapacity;
// Choose cheapest node
@@ -108,7 +109,7 @@ class PrioritizableNode implements Comparable<PrioritizableNode> {
static class Builder {
public final Node node;
- private ResourceCapacity freeParentCapacity = ResourceCapacity.NONE;
+ private NodeResources freeParentCapacity = new NodeResources(0, 0, 0);
private Optional<Node> parent = Optional.empty();
private boolean violatesSpares;
private boolean isSurplusNode;
@@ -119,7 +120,7 @@ class PrioritizableNode implements Comparable<PrioritizableNode> {
this.node = node;
}
- Builder withFreeParentCapacity(ResourceCapacity freeParentCapacity) {
+ Builder withFreeParentCapacity(NodeResources freeParentCapacity) {
this.freeParentCapacity = freeParentCapacity;
return this;
}
@@ -153,4 +154,5 @@ class PrioritizableNode implements Comparable<PrioritizableNode> {
return new PrioritizableNode(node, freeParentCapacity, parent, violatesSpares, isSurplusNode, isNewNode, preferredOnFlavor);
}
}
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java
deleted file mode 100644
index 3839d66c8de..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacity.java
+++ /dev/null
@@ -1,77 +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.vespa.hosted.provision.provisioning;
-
-import com.yahoo.config.provision.Flavor;
-import com.yahoo.config.provision.NodeResources;
-import com.yahoo.vespa.hosted.provision.Node;
-
-/**
- * Represent the capacity in terms of physical resources like memory, disk and cpu.
- * Can represent free, aggregate or total capacity of one or several nodes.
- *
- * Immutable.
- *
- * @author smorgrav
- */
-public class ResourceCapacity {
-
- public static final ResourceCapacity NONE = new ResourceCapacity(0, 0, 0);
-
- private final double memoryGb;
- private final double vcpu;
- private final double diskGb;
-
- private ResourceCapacity(double memoryGb, double vcpu, double diskGb) {
- this.memoryGb = memoryGb;
- this.vcpu = vcpu;
- this.diskGb = diskGb;
- }
-
- static ResourceCapacity of(Flavor flavor) {
- return new ResourceCapacity(
- flavor.getMinMainMemoryAvailableGb(), flavor.getMinCpuCores(), flavor.getMinDiskAvailableGb());
- }
-
- static ResourceCapacity of(NodeResources resources) {
- return new ResourceCapacity(resources.memoryGb(), resources.vcpu(), resources.diskGb());
- }
-
- static ResourceCapacity of(Node node) {
- return ResourceCapacity.of(node.flavor());
- }
-
- public double memoryGb() {
- return memoryGb;
- }
-
- public double vcpu() {
- return vcpu;
- }
-
- public double diskGb() {
- return diskGb;
- }
-
- public ResourceCapacity subtract(ResourceCapacity other) {
- return new ResourceCapacity(memoryGb - other.memoryGb,
- vcpu - other.vcpu,
- diskGb - other.diskGb);
- }
-
- public ResourceCapacity add(ResourceCapacity other) {
- return new ResourceCapacity(memoryGb + other.memoryGb,
- vcpu + other.vcpu,
- diskGb + other.diskGb);
- }
-
- boolean hasCapacityFor(ResourceCapacity capacity) {
- return memoryGb >= capacity.memoryGb &&
- vcpu >= capacity.vcpu &&
- diskGb >= capacity.diskGb;
- }
-
- boolean hasCapacityFor(Flavor flavor) {
- return hasCapacityFor(ResourceCapacity.of(flavor));
- }
-
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index eb59d9e7e8e..c9409937d46 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -151,6 +151,7 @@ public class MockNodeRepository extends NodeRepository {
Set.of(RotationName.from("us-cluster")));
activate(provisioner.prepare(zoneApp, zoneCluster, Capacity.fromRequiredNodeType(NodeType.host), 1, null), zoneApp, provisioner);
+
ApplicationId app1 = ApplicationId.from(TenantName.from("tenant1"), ApplicationName.from("application1"), InstanceName.from("instance1"));
ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.container,
ClusterSpec.Id.from("id1"),
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java
index 41bb9c7b6d4..2d753ded98e 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacityTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeFlavors;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.vespa.hosted.provision.LockedNodeList;
import com.yahoo.vespa.hosted.provision.Node;
@@ -61,7 +62,7 @@ public class DockerHostCapacityTest {
@Test
public void compare_used_to_sort_in_decending_order() {
- assertEquals(host1, nodes.get(0)); //Make sure it is unsorted here
+ assertEquals(host1, nodes.get(0)); // Make sure it is unsorted here
Collections.sort(nodes, capacity::compare);
assertEquals(host3, nodes.get(0));
@@ -71,20 +72,20 @@ public class DockerHostCapacityTest {
@Test
public void hasCapacity() {
- assertTrue(capacity.hasCapacity(host1, ResourceCapacity.of(flavorDocker)));
- assertTrue(capacity.hasCapacity(host1, ResourceCapacity.of(flavorDocker2)));
- assertTrue(capacity.hasCapacity(host2, ResourceCapacity.of(flavorDocker)));
- assertTrue(capacity.hasCapacity(host2, ResourceCapacity.of(flavorDocker2)));
- assertFalse(capacity.hasCapacity(host3, ResourceCapacity.of(flavorDocker))); // No ip available
- assertFalse(capacity.hasCapacity(host3, ResourceCapacity.of(flavorDocker2))); // No ip available
+ assertTrue(capacity.hasCapacity(host1, flavorDocker.resources()));
+ assertTrue(capacity.hasCapacity(host1, flavorDocker2.resources()));
+ assertTrue(capacity.hasCapacity(host2, flavorDocker.resources()));
+ assertTrue(capacity.hasCapacity(host2, flavorDocker2.resources()));
+ assertFalse(capacity.hasCapacity(host3, flavorDocker.resources())); // No ip available
+ assertFalse(capacity.hasCapacity(host3, flavorDocker2.resources())); // No ip available
// Add a new node to host1 to deplete the memory resource
Node nodeF = Node.create("nodeF", Collections.singleton("::6"), Collections.emptySet(),
"nodeF", Optional.of("host1"), Optional.empty(), flavorDocker, NodeType.tenant);
nodes.add(nodeF);
capacity = new DockerHostCapacity(new LockedNodeList(nodes, () -> {}));
- assertFalse(capacity.hasCapacity(host1, ResourceCapacity.of(flavorDocker)));
- assertFalse(capacity.hasCapacity(host1, ResourceCapacity.of(flavorDocker2)));
+ assertFalse(capacity.hasCapacity(host1, flavorDocker.resources()));
+ assertFalse(capacity.hasCapacity(host1, flavorDocker2.resources()));
}
@Test
@@ -96,7 +97,7 @@ public class DockerHostCapacityTest {
@Test
public void getCapacityTotal() {
- ResourceCapacity total = capacity.getCapacityTotal();
+ NodeResources total = capacity.getCapacityTotal();
assertEquals(21.0, total.vcpu(), 0.1);
assertEquals(30.0, total.memoryGb(), 0.1);
assertEquals(36.0, total.diskGb(), 0.1);
@@ -104,7 +105,7 @@ public class DockerHostCapacityTest {
@Test
public void getFreeCapacityTotal() {
- ResourceCapacity totalFree = capacity.getFreeCapacityTotal();
+ NodeResources totalFree = capacity.getFreeCapacityTotal();
assertEquals(15.0, totalFree.vcpu(), 0.1);
assertEquals(14.0, totalFree.memoryGb(), 0.1);
assertEquals(24.0, totalFree.diskGb(), 0.1);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
index 003593a7d1d..173fe31a32c 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
@@ -11,6 +11,7 @@ import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.OutOfCapacityException;
import com.yahoo.config.provision.ParentHostUnavailableException;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.Zone;
@@ -75,7 +76,7 @@ public class DockerProvisioningTest {
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
ApplicationId zoneApplication = tester.makeApplicationId();
- List<Node> parents = tester.makeReadyVirtualDockerHosts(10, new NodeResources(2, 2, 2));
+ List<Node> parents = tester.makeDockerHosts(10, new NodeResources(2, 2, 2));
for (Node parent : parents)
tester.makeReadyVirtualDockerNodes(1, dockerFlavor, parent.hostname());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
index a48006facb2..c5a8e5213b6 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
@@ -279,6 +279,42 @@ public class DynamicDockerAllocationTest {
assertEquals(ImmutableSet.of("127.0.127.2", "::2"), activeNodes.get(1).ipAddresses());
}
+ @Test
+ public void provisioning_fast_disk_speed_do_not_get_slow_nodes() {
+ provisionFastAndSlowThenDeploy(NodeResources.DiskSpeed.fast, true);
+ }
+
+ @Test
+ public void provisioning_slow_disk_speed_do_not_get_fast_nodes() {
+ provisionFastAndSlowThenDeploy(NodeResources.DiskSpeed.slow, true);
+ }
+
+ @Test
+ public void provisioning_any_disk_speed_gets_slow_and_fast_nodes() {
+ provisionFastAndSlowThenDeploy(NodeResources.DiskSpeed.any, false);
+ }
+
+ private void provisionFastAndSlowThenDeploy(NodeResources.DiskSpeed requestDiskSpeed, boolean expectOutOfCapacity) {
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build();
+ tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 2, 3, NodeResources.DiskSpeed.fast)), NodeType.host, 10, true);
+ tester.makeReadyNodes(2, new Flavor(new NodeResources(1, 2, 3, NodeResources.DiskSpeed.slow)), NodeType.host, 10, true);
+ deployZoneApp(tester);
+
+ ApplicationId application = tester.makeApplicationId();
+ ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test"), Version.fromString("1"), false);
+ NodeResources resources = new NodeResources(1, 1, 1, requestDiskSpeed);
+
+ try {
+ List<HostSpec> hosts = tester.prepare(application, cluster, 4, 1, resources);
+ if (expectOutOfCapacity) fail("Expected out of capacity");
+ assertEquals(4, hosts.size());
+ tester.activate(application, hosts);
+ }
+ catch (OutOfCapacityException e) {
+ if ( ! expectOutOfCapacity) throw e;
+ }
+ }
+
private ApplicationId makeApplicationId(String tenant, String appName) {
return ApplicationId.from(tenant, appName, "default");
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java
index a5a6f3e678b..7d450018353 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerProvisionTest.java
@@ -104,7 +104,6 @@ public class DynamicDockerProvisionTest {
assertEquals(4, tester.nodeRepository().getNodes(NodeType.tenant, Node.State.reserved).size());
}
-
private static void deployZoneApp(ProvisioningTester tester) {
ApplicationId applicationId = tester.makeApplicationId();
List<HostSpec> list = tester.prepare(applicationId,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
index 08fbe73a89a..3620b424fa6 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
@@ -240,7 +240,6 @@ public class ProvisioningTest {
ApplicationId application1 = tester.makeApplicationId();
tester.makeReadyNodes(12, "d-1-1-1");
- tester.makeReadyNodes(16, "d-2-2-2");
NodeResources small = new NodeResources(1, 1, 1);
NodeResources large = new NodeResources(2, 2, 2);
@@ -253,6 +252,8 @@ public class ProvisioningTest {
SystemState state2 = prepare(application1, 2, 2, 3, 3, small, tester);
tester.activate(application1, state2.allHosts);
+ tester.makeReadyNodes(16, "d-2-2-2");
+
// redeploy with increased sizes and new flavor
SystemState state3 = prepare(application1, 3, 4, 4, 5, large, tester);
assertEquals("New nodes are reserved", 16, tester.nodeRepository().getNodes(application1, Node.State.reserved).size());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
index 7e4cb4e4a6a..d63da52eb49 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
@@ -228,6 +228,9 @@ public class ProvisioningTester {
}
public List<Node> makeReadyNodes(int n, String flavor, NodeType type) {
+ return makeReadyNodes(n, asFlavor(flavor, type), type);
+ }
+ public List<Node> makeReadyNodes(int n, Flavor flavor, NodeType type) {
return makeReadyNodes(n, flavor, type, 0);
}
@@ -235,7 +238,10 @@ public class ProvisioningTester {
return makeProvisionedNodes(count, flavor, type, ipAddressPoolSize, false);
}
- public List<Node> makeProvisionedNodes(int n, String flavorName, NodeType type, int ipAddressPoolSize, boolean dualStack) {
+ public List<Node> makeProvisionedNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
+ return makeProvisionedNodes(n, asFlavor(flavor, type), type, ipAddressPoolSize, dualStack);
+ }
+ public List<Node> makeProvisionedNodes(int n, Flavor flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
List<Node> nodes = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
@@ -272,20 +278,12 @@ public class ProvisioningTester {
nameResolver.addRecord(String.format("node-%d-of-%s", poolIp, hostname), ipv4Addr);
}
}
- Optional<Flavor> flavor = nodeFlavors.getFlavor(flavorName);
- if (flavor.isEmpty()) {
- if (type == NodeType.tenant) // Tenant nodes can have any (docker) flavor
- flavor = Optional.of(new Flavor(NodeResources.fromLegacyName(flavorName)));
- else
- throw new IllegalArgumentException("No flavor '" + flavorName + "'");
- }
-
nodes.add(nodeRepository.createNode(hostname,
hostname,
new IP.Config(hostIps, ipAddressPool),
Optional.empty(),
Optional.empty(),
- flavor.get(),
+ flavor,
type));
}
nodes = nodeRepository.addNodes(nodes);
@@ -326,37 +324,58 @@ public class ProvisioningTester {
}
public List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize) {
+ return makeReadyNodes(n, asFlavor(flavor, type), type, ipAddressPoolSize);
+ }
+ public List<Node> makeReadyNodes(int n, Flavor flavor, NodeType type, int ipAddressPoolSize) {
return makeReadyNodes(n, flavor, type, ipAddressPoolSize, false);
}
public List<Node> makeReadyNodes(int n, String flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
+ return makeReadyNodes(n, asFlavor(flavor, type), type, ipAddressPoolSize, dualStack);
+ }
+ public List<Node> makeReadyNodes(int n, Flavor flavor, NodeType type, int ipAddressPoolSize, boolean dualStack) {
List<Node> nodes = makeProvisionedNodes(n, flavor, type, ipAddressPoolSize, dualStack);
nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
return nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
}
+ private Flavor asFlavor(String flavorString, NodeType type) {
+ Optional<Flavor> flavor = nodeFlavors.getFlavor(flavorString);
+ if (flavor.isEmpty()) {
+ // TODO: Remove the need for this by always adding hosts with a given capacity
+ if (type == NodeType.tenant) // Tenant nodes can have any (docker) flavor
+ flavor = Optional.of(new Flavor(NodeResources.fromLegacyName(flavorString)));
+ else
+ throw new IllegalArgumentException("No flavor '" + flavorString + "'");
+ }
+ return flavor.get();
+ }
+
/** Creates a set of virtual docker hosts */
- public List<Node> makeReadyVirtualDockerHosts(int n, NodeResources flavor) {
- return makeReadyVirtualNodes(n, 1, flavor, Optional.empty(),
- i -> "dockerHost" + i, NodeType.host);
+ public List<Node> makeDockerHosts(int n, NodeResources resources) {
+ return makeDockerHosts(n, resources, "dockerHost");
+ }
+
+ public List<Node> makeDockerHosts(int n, NodeResources resources, String namePrefix) {
+ return makeReadyVirtualNodes(n, 1, resources, Optional.empty(), i -> namePrefix + i, NodeType.host);
}
/** Creates a set of virtual docker nodes on a single docker host starting with index 1 and increasing */
- public List<Node> makeReadyVirtualDockerNodes(int n, NodeResources flavor, String dockerHostId) {
- return makeReadyVirtualNodes(n, 1, flavor, Optional.of(dockerHostId),
- i -> String.format("%s-%03d", dockerHostId, i), NodeType.tenant);
+ public List<Node> makeReadyVirtualDockerNodes(int n, NodeResources resources, String dockerHostId) {
+ return makeReadyVirtualNodes(n, 1, resources, Optional.of(dockerHostId),
+ i -> String.format("%s-%03d", dockerHostId, i), NodeType.tenant);
}
/** Creates a single of virtual docker node on a single parent host */
- public List<Node> makeReadyVirtualDockerNode(int index, NodeResources flavor, String dockerHostId) {
- return makeReadyVirtualNodes(1, index, flavor, Optional.of(dockerHostId),
- i -> String.format("%s-%03d", dockerHostId, i), NodeType.tenant);
+ public List<Node> makeReadyVirtualDockerNode(int index, NodeResources resources, String dockerHostId) {
+ return makeReadyVirtualNodes(1, index, resources, Optional.of(dockerHostId),
+ i -> String.format("%s-%03d", dockerHostId, i), NodeType.tenant);
}
/** Creates a set of virtual nodes without a parent host */
- public List<Node> makeReadyVirtualNodes(int n, NodeResources flavor) {
- return makeReadyVirtualNodes(n, 0, flavor, Optional.empty(),
- i -> UUID.randomUUID().toString(), NodeType.tenant);
+ public List<Node> makeReadyVirtualNodes(int n, NodeResources resources) {
+ return makeReadyVirtualNodes(n, 0, resources, Optional.empty(),
+ i -> UUID.randomUUID().toString(), NodeType.tenant);
}
/** Creates a set of virtual nodes on a single parent host */
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacityTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacityTest.java
index 5a021466e58..f73b0127d8a 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacityTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ResourceCapacityTest.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.config.provision.Flavor;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provisioning.FlavorsConfig;
import org.junit.Test;
@@ -36,49 +37,45 @@ public class ResourceCapacityTest {
Flavor d3MemFlavor = new Flavor(flavors.flavor(6));
Flavor d3CPUFlavor = new Flavor(flavors.flavor(7));
- ResourceCapacity capacityOfHostSmall = ResourceCapacity.of(hostSmallFlavor);
+ NodeResources capacityOfHostSmall = hostSmallFlavor.resources();
// Assert initial capacities
- assertTrue(capacityOfHostSmall.hasCapacityFor(hostSmallFlavor));
- assertTrue(capacityOfHostSmall.hasCapacityFor(d1Flavor));
- assertTrue(capacityOfHostSmall.hasCapacityFor(d2Flavor));
- assertTrue(capacityOfHostSmall.hasCapacityFor(d3Flavor));
- assertFalse(capacityOfHostSmall.hasCapacityFor(hostLargeFlavor));
+ assertTrue(capacityOfHostSmall.satisfies(hostSmallFlavor.resources()));
+ assertTrue(capacityOfHostSmall.satisfies(d1Flavor.resources()));
+ assertTrue(capacityOfHostSmall.satisfies(d2Flavor.resources()));
+ assertTrue(capacityOfHostSmall.satisfies(d3Flavor.resources()));
+ assertFalse(capacityOfHostSmall.satisfies(hostLargeFlavor.resources()));
// Also check that we are taking all three resources into accout
- assertFalse(capacityOfHostSmall.hasCapacityFor(d3DiskFlavor));
- assertFalse(capacityOfHostSmall.hasCapacityFor(d3MemFlavor));
- assertFalse(capacityOfHostSmall.hasCapacityFor(d3CPUFlavor));
+ assertFalse(capacityOfHostSmall.satisfies(d3DiskFlavor.resources()));
+ assertFalse(capacityOfHostSmall.satisfies(d3MemFlavor.resources()));
+ assertFalse(capacityOfHostSmall.satisfies(d3CPUFlavor.resources()));
// Compare it to various flavors
- assertEquals(1, compare(capacityOfHostSmall, nodeCapacity(d1Flavor)));
- assertEquals(1, compare(capacityOfHostSmall, nodeCapacity(d2Flavor)));
- assertEquals(0, compare(capacityOfHostSmall, nodeCapacity(d3Flavor)));
- assertEquals(-1, compare(capacityOfHostSmall, nodeCapacity(d3DiskFlavor)));
- assertEquals(-1, compare(capacityOfHostSmall, nodeCapacity(d3CPUFlavor)));
- assertEquals(-1, compare(capacityOfHostSmall, nodeCapacity(d3MemFlavor)));
+ assertEquals(1, compare(capacityOfHostSmall, d1Flavor.resources()));
+ assertEquals(1, compare(capacityOfHostSmall, d2Flavor.resources()));
+ assertEquals(0, compare(capacityOfHostSmall, d3Flavor.resources()));
+ assertEquals(-1, compare(capacityOfHostSmall, d3DiskFlavor.resources()));
+ assertEquals(-1, compare(capacityOfHostSmall, d3CPUFlavor.resources()));
+ assertEquals(-1, compare(capacityOfHostSmall, d3MemFlavor.resources()));
// Change free capacity and assert on rest capacity
- capacityOfHostSmall = capacityOfHostSmall.subtract(ResourceCapacity.of(d1Flavor));
- assertEquals(0, compare(capacityOfHostSmall, nodeCapacity(d2Flavor)));
+ capacityOfHostSmall = capacityOfHostSmall.subtract(d1Flavor.resources());
+ assertEquals(0, compare(capacityOfHostSmall, d2Flavor.resources()));
// Assert on rest capacity
- assertTrue(capacityOfHostSmall.hasCapacityFor(d1Flavor));
- assertFalse(capacityOfHostSmall.hasCapacityFor(d3Flavor));
+ assertTrue(capacityOfHostSmall.satisfies(d1Flavor.resources()));
+ assertFalse(capacityOfHostSmall.satisfies(d3Flavor.resources()));
// At last compare the disk and cpu and mem variations
- assertEquals(-1, compare(nodeCapacity(d3Flavor), nodeCapacity(d3DiskFlavor)));
- assertEquals(1, compare(nodeCapacity(d3DiskFlavor), nodeCapacity(d3CPUFlavor)));
- assertEquals(-1, compare(nodeCapacity(d3CPUFlavor), nodeCapacity(d3MemFlavor)));
- assertEquals(1, compare(nodeCapacity(d3MemFlavor), nodeCapacity(d3DiskFlavor)));
+ assertEquals(-1, compare(d3Flavor.resources(), d3DiskFlavor.resources()));
+ assertEquals(1, compare(d3DiskFlavor.resources(), d3CPUFlavor.resources()));
+ assertEquals(-1, compare(d3CPUFlavor.resources(), d3MemFlavor.resources()));
+ assertEquals(1, compare(d3MemFlavor.resources(), d3DiskFlavor.resources()));
}
- private ResourceCapacity nodeCapacity(Flavor flavor) {
- return ResourceCapacity.of(flavor);
- }
-
- private int compare(ResourceCapacity a, ResourceCapacity b) {
- return ResourceCapacityComparator.defaultOrder().compare(a, b);
+ private int compare(NodeResources a, NodeResources b) {
+ return NodeResourceComparator.defaultOrder().compare(a, b);
}
}