diff options
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); } } |