From ec9d641119a6d41656c3902ed375702628ef5c9f Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Sat, 2 May 2020 10:49:56 +0200 Subject: Revert "Merge pull request #13131 from vespa-engine/revert-13128-bratseth/allow-non-allocatable-limits-take-2" This reverts commit 78d7d808067a9189960f3cc2ad83237ba00efec5, reversing changes made to e1d09983f5e952f44358ae5b247e5b76b2898d82. --- .../vespa/hosted/provision/NodeRepository.java | 38 ++++- .../hosted/provision/applications/Cluster.java | 13 -- .../autoscale/AllocatableClusterResources.java | 83 +++++++++- .../provision/autoscale/AllocationOptimizer.java | 172 +++++++++++++++++++++ .../hosted/provision/autoscale/Autoscaler.java | 103 ++---------- .../vespa/hosted/provision/autoscale/Limits.java | 69 +++++++++ .../provision/autoscale/ResourceIterator.java | 155 ------------------- .../hosted/provision/autoscale/ResourceTarget.java | 74 +++++++++ .../hosted/provision/lb/LoadBalancerService.java | 4 +- .../provision/lb/LoadBalancerServiceMock.java | 4 +- .../lb/PassthroughLoadBalancerService.java | 4 +- .../provision/lb/SharedLoadBalancerService.java | 8 +- .../maintenance/AutoscalingMaintainer.java | 3 +- .../maintenance/DynamicProvisioningMaintainer.java | 10 +- .../provision/maintenance/LoadBalancerExpirer.java | 2 +- .../maintenance/NodeRepositoryMaintenance.java | 8 +- .../hosted/provision/maintenance/Rebalancer.java | 7 +- .../maintenance/ScalingSuggestionsMaintainer.java | 3 +- .../provision/provisioning/CapacityPolicies.java | 2 +- .../EmptyProvisionServiceProvider.java | 7 +- .../provision/provisioning/GroupPreparer.java | 22 +-- .../provisioning/HostResourcesCalculator.java | 3 +- .../provisioning/LoadBalancerProvisioner.java | 2 +- .../provision/provisioning/NodePrioritizer.java | 4 + .../provisioning/NodeRepositoryProvisioner.java | 68 +++----- .../hosted/provision/provisioning/Preparer.java | 2 +- .../provision/testutils/MockNodeRepository.java | 8 +- .../testutils/MockProvisionServiceProvider.java | 2 +- 28 files changed, 520 insertions(+), 360 deletions(-) create mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java create mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java delete mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java create mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java (limited to 'node-repository/src/main/java') diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java index d612e8b102f..a459cc2826f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java @@ -38,6 +38,8 @@ import com.yahoo.vespa.hosted.provision.persistence.DnsNameResolver; import com.yahoo.vespa.hosted.provision.persistence.NameResolver; import com.yahoo.vespa.hosted.provision.provisioning.DockerImages; import com.yahoo.vespa.hosted.provision.provisioning.FirmwareChecks; +import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator; +import com.yahoo.vespa.hosted.provision.provisioning.ProvisionServiceProvider; import com.yahoo.vespa.hosted.provision.restapi.NotFoundException; import java.time.Clock; @@ -92,6 +94,7 @@ public class NodeRepository extends AbstractComponent { private final Clock clock; private final Zone zone; private final NodeFlavors flavors; + private final HostResourcesCalculator resourcesCalculator; private final NameResolver nameResolver; private final OsVersions osVersions; private final InfrastructureVersions infrastructureVersions; @@ -105,20 +108,37 @@ public class NodeRepository extends AbstractComponent { * This will use the system time to make time-sensitive decisions */ @Inject - public NodeRepository(NodeRepositoryConfig config, NodeFlavors flavors, Curator curator, Zone zone) { - this(flavors, curator, Clock.systemUTC(), zone, new DnsNameResolver(), DockerImage.fromString(config.dockerImage()), config.useCuratorClientCache()); + public NodeRepository(NodeRepositoryConfig config, + NodeFlavors flavors, + ProvisionServiceProvider provisionServiceProvider, + Curator curator, + Zone zone) { + this(flavors, + provisionServiceProvider.getHostResourcesCalculator(), + curator, + Clock.systemUTC(), + zone, + new DnsNameResolver(), + DockerImage.fromString(config.dockerImage()), config.useCuratorClientCache()); } /** * Creates a node repository from a zookeeper provider and a clock instance * which will be used for time-sensitive decisions. */ - public NodeRepository(NodeFlavors flavors, Curator curator, Clock clock, Zone zone, NameResolver nameResolver, - DockerImage dockerImage, boolean useCuratorClientCache) { + public NodeRepository(NodeFlavors flavors, + HostResourcesCalculator resourcesCalculator, + Curator curator, + Clock clock, + Zone zone, + NameResolver nameResolver, + DockerImage dockerImage, + boolean useCuratorClientCache) { this.db = new CuratorDatabaseClient(flavors, curator, clock, zone, useCuratorClientCache); this.zone = zone; this.clock = clock; this.flavors = flavors; + this.resourcesCalculator = resourcesCalculator; this.nameResolver = nameResolver; this.osVersions = new OsVersions(this); this.infrastructureVersions = new InfrastructureVersions(db); @@ -162,6 +182,12 @@ public class NodeRepository extends AbstractComponent { /** Returns this node repo's view of the applications deployed to it */ public Applications applications() { return applications; } + public NodeFlavors flavors() { + return flavors; + } + + public HostResourcesCalculator resourcesCalculator() { return resourcesCalculator; } + // ---------------- Query API ---------------------------------------------------------------- /** @@ -328,10 +354,6 @@ public class NodeRepository extends AbstractComponent { return Collections.singletonList(getNodeAcl(node, candidates)); } - public NodeFlavors getAvailableFlavors() { - return flavors; - } - // ----------------- Node lifecycle ----------------------------------------------------------- /** Creates a new node object, without adding it to the node repo. If no IP address is given, it will be resolved */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java index 15a5545bc2c..847ec1290f6 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java @@ -72,19 +72,6 @@ public class Cluster { return new Cluster(id, min, max, suggested, target); } - public NodeResources capAtLimits(NodeResources resources) { - resources = resources.withVcpu(between(min.nodeResources().vcpu(), max.nodeResources().vcpu(), resources.vcpu())); - resources = resources.withMemoryGb(between(min.nodeResources().memoryGb(), max.nodeResources().memoryGb(), resources.memoryGb())); - resources = resources.withDiskGb(between(min.nodeResources().diskGb(), max.nodeResources().diskGb(), resources.diskGb())); - return resources; - } - - private double between(double min, double max, double value) { - value = Math.max(min, value); - value = Math.min(max, value); - return value; - } - @Override public int hashCode() { return id.hashCode(); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java index 2414bd95b85..6c143ab4bbd 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java @@ -1,14 +1,20 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.autoscale; +import com.yahoo.config.provision.CloudName; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Flavor; import com.yahoo.config.provision.NodeResources; +import com.yahoo.config.provision.host.FlavorOverrides; import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.applications.Cluster; import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator; +import com.yahoo.vespa.hosted.provision.provisioning.NodeResourceLimits; import java.util.List; +import java.util.Optional; /** * @author bratseth @@ -33,9 +39,19 @@ public class AllocatableClusterResources { private final double fulfilment; - public AllocatableClusterResources(List nodes, HostResourcesCalculator calculator) { + /** Fake allocatable resources from requested capacity */ + public AllocatableClusterResources(ClusterResources requested, ClusterSpec.Type clusterType) { + this.advertisedResources = requested.nodeResources(); + this.realResources = requested.nodeResources(); // we don't know + this.nodes = requested.nodes(); + this.groups = requested.groups(); + this.clusterType = clusterType; + this.fulfilment = 1; + } + + public AllocatableClusterResources(List nodes, NodeRepository nodeRepository) { this.advertisedResources = nodes.get(0).flavor().resources(); - this.realResources = calculator.realResourcesOf(nodes.get(0)); + this.realResources = nodeRepository.resourcesCalculator().realResourcesOf(nodes.get(0), nodeRepository); this.nodes = nodes.size(); this.groups = (int)nodes.stream().map(node -> node.allocation().get().membership().cluster().group()).distinct().count(); this.clusterType = nodes.get(0).allocation().get().membership().cluster().type(); @@ -85,6 +101,12 @@ public class AllocatableClusterResources { public int nodes() { return nodes; } public int groups() { return groups; } + + public int groupSize() { + // ceil: If the division does not produce a whole number we assume some node is missing + return (int)Math.ceil((double)nodes / groups); + } + public ClusterSpec.Type clusterType() { return clusterType; } public double cost() { return nodes * costOf(advertisedResources); } @@ -121,4 +143,61 @@ public class AllocatableClusterResources { (fulfilment < 1.0 ? " (fulfilment " + fulfilment + ")" : ""); } + /** + * Returns the best matching allocatable node resources given ideal node resources, + * or empty if none available within the limits. + */ + public static Optional from(ClusterResources resources, + ClusterSpec.Type clusterType, + Limits limits, + NodeRepository nodeRepository) { + NodeResources cappedNodeResources = limits.cap(resources.nodeResources()); + cappedNodeResources = new NodeResourceLimits(nodeRepository.zone()).enlargeToLegal(cappedNodeResources, clusterType); + + if (allowsHostSharing(nodeRepository.zone().cloud())) { + // return the requested resources, or empty if they cannot fit on existing hosts + for (Flavor flavor : nodeRepository.flavors().getFlavors()) { + if (flavor.resources().satisfies(cappedNodeResources)) + return Optional.of(new AllocatableClusterResources(resources.with(cappedNodeResources), + cappedNodeResources, + resources.nodeResources(), + clusterType)); + } + return Optional.empty(); + } + else { + // return the cheapest flavor satisfying the target resources, if any + Optional best = Optional.empty(); + for (Flavor flavor : nodeRepository.flavors().getFlavors()) { + NodeResources flavorResources = nodeRepository.resourcesCalculator().advertisedResourcesOf(flavor); + if (flavor.resources().storageType() == NodeResources.StorageType.remote) { + flavor = flavor.with(FlavorOverrides.ofDisk(cappedNodeResources.diskGb())); + flavorResources = flavorResources.withDiskGb(cappedNodeResources.diskGb()); // TODO: Do this in resourcesCalculator + } + if ( ! between(limits.min().nodeResources(), limits.max().nodeResources(), flavorResources)) continue; + + var candidate = new AllocatableClusterResources(resources.with(flavor.resources()), + flavor, + resources.nodeResources(), + clusterType, + nodeRepository.resourcesCalculator()); + if (best.isEmpty() || candidate.preferableTo(best.get())) + best = Optional.of(candidate); + } + return best; + } + } + + private static boolean between(NodeResources min, NodeResources max, NodeResources r) { + if ( ! min.isUnspecified() && ! r.justNumbers().satisfies(min.justNumbers())) return false; + if ( ! max.isUnspecified() && ! max.justNumbers().satisfies(r.justNumbers())) return false; + return true; + } + + // TODO: Put this in zone config instead? + private static boolean allowsHostSharing(CloudName cloudName) { + if (cloudName.value().equals("aws")) return false; + return true; + } + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java new file mode 100644 index 00000000000..8d26bb89959 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java @@ -0,0 +1,172 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.autoscale; + +import com.yahoo.config.provision.ClusterResources; +import com.yahoo.config.provision.NodeResources; +import com.yahoo.vespa.hosted.provision.NodeRepository; + +import java.util.Optional; + +/** + * A searcher of the space of possible allocation + * + * @author bratseth + */ +public class AllocationOptimizer { + + private final NodeRepository nodeRepository; + + public AllocationOptimizer(NodeRepository nodeRepository) { + this.nodeRepository = nodeRepository; + } + + /** + * An AllocationSearcher searches the space of possible allocations given a target + * and (optionally) cluster limits and returns the best alternative. + * + * @return the best allocation, if there are any possible legal allocations, fulfilling the target + * fully or partially, within the limits + */ + public Optional findBestAllocation(ResourceTarget target, + AllocatableClusterResources current, + Limits limits) { + Optional bestAllocation = Optional.empty(); + for (ResourceIterator i = new ResourceIterator(target, current, limits); i.hasNext(); ) { + var allocatableResources = AllocatableClusterResources.from(i.next(), current.clusterType(), limits, nodeRepository); + if (allocatableResources.isEmpty()) continue; + if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get())) + bestAllocation = allocatableResources; + } + return bestAllocation; + } + + /** + * Provides iteration over possible cluster resource allocations given a target total load + * and current groups/nodes allocation. + */ + private static class ResourceIterator { + + // The min and max nodes to consider when not using application supplied limits + private static final int minimumNodes = 3; // Since this number includes redundancy it cannot be lower than 2 + private static final int maximumNodes = 150; + + // When a query is issued on a node the cost is the sum of a fixed cost component and a cost component + // proportional to document count. We must account for this when comparing configurations with more or fewer nodes. + // TODO: Measure this, and only take it into account with queries + private static final double fixedCpuCostFraction = 0.1; + + // Given state + private final Limits limits; + private final AllocatableClusterResources current; + private final ResourceTarget target; + + // Derived from the observed state + private final int nodeIncrement; + private final boolean singleGroupMode; + + // Iterator state + private int currentNodes; + + public ResourceIterator(ResourceTarget target, AllocatableClusterResources current, Limits limits) { + this.target = target; + this.current = current; + this.limits = limits; + + // What number of nodes is it effective to add or remove at the time from this cluster? + // This is the group size, since we (for now) assume the group size is decided by someone wiser than us + // and we decide the number of groups. + // The exception is when we only have one group, where we can add and remove single nodes in it. + singleGroupMode = current.groups() == 1; + nodeIncrement = singleGroupMode ? 1 : current.groupSize(); + + // Step to the right starting point + currentNodes = current.nodes(); + if (currentNodes < minNodes()) { // step up + while (currentNodes < minNodes() + && (singleGroupMode || currentNodes + nodeIncrement > current.groupSize())) // group level redundancy + currentNodes += nodeIncrement; + } + else { // step down + while (currentNodes - nodeIncrement >= minNodes() + && (singleGroupMode || currentNodes - nodeIncrement > current.groupSize())) // group level redundancy + currentNodes -= nodeIncrement; + } + } + + public ClusterResources next() { + ClusterResources next = resourcesWith(currentNodes); + currentNodes += nodeIncrement; + return next; + } + + public boolean hasNext() { + return currentNodes <= maxNodes(); + } + + private int minNodes() { + if (limits.isEmpty()) return minimumNodes; + if (singleGroupMode) return limits.min().nodes(); + return Math.max(limits.min().nodes(), limits.min().groups() * current.groupSize() ); + } + + private int maxNodes() { + if (limits.isEmpty()) return maximumNodes; + if (singleGroupMode) return limits.max().nodes(); + return Math.min(limits.max().nodes(), limits.max().groups() * current.groupSize() ); + } + + private ClusterResources resourcesWith(int nodes) { + int nodesAdjustedForRedundancy = nodes; + if (target.adjustForRedundancy()) + nodesAdjustedForRedundancy = nodes - (singleGroupMode ? 1 : current.groupSize()); + return new ClusterResources(nodes, + singleGroupMode ? 1 : nodes / current.groupSize(), + nodeResourcesWith(nodesAdjustedForRedundancy)); + } + + /** + * For the observed load this instance is initialized with, returns the resources needed per node to be at + * ideal load given a target node count + */ + private NodeResources nodeResourcesWith(int nodeCount) { + // Cpu: Scales with cluster size (TODO: Only reads, writes scales with group size) + // Memory and disk: Scales with group size + + double cpu, memory, disk; + if (singleGroupMode) { + // The fixed cost portion of cpu does not scale with changes to the node count + // TODO: Only for the portion of cpu consumed by queries + cpu = fixedCpuCostFraction * target.clusterCpu() / current.groupSize() + + (1 - fixedCpuCostFraction) * target.clusterCpu() / nodeCount; + + if (current.clusterType().isContent()) { // load scales with node share of content + memory = target.groupMemory() / nodeCount; + disk = target.groupDisk() / nodeCount; + } + else { + memory = target.nodeMemory(); + disk = target.nodeDisk(); + } + } + else { + cpu = target.clusterCpu() / nodeCount; + if (current.clusterType().isContent()) { // load scales with node share of content + memory = target.groupMemory() / current.groupSize(); + disk = target.groupDisk() / current.groupSize(); + } + else { + memory = target.nodeMemory(); + disk = target.nodeDisk(); + } + } + + // Combine the scaled resource values computed here + // with the currently configured non-scaled values, given in the limits, if any + NodeResources nonScaled = limits.isEmpty() || limits.min().nodeResources().isUnspecified() + ? current.toAdvertisedClusterResources().nodeResources() + : limits.min().nodeResources(); // min=max for non-scaled + return nonScaled.withVcpu(cpu).withMemoryGb(memory).withDiskGb(disk); + } + + } +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java index 6dca9c9a796..8930bf34f4a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java @@ -1,17 +1,11 @@ // Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.autoscale; -import com.yahoo.config.provision.CloudName; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; -import com.yahoo.config.provision.Flavor; -import com.yahoo.config.provision.NodeResources; -import com.yahoo.config.provision.host.FlavorOverrides; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.applications.Cluster; -import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator; -import com.yahoo.vespa.hosted.provision.provisioning.NodeResourceLimits; import java.time.Duration; import java.util.List; @@ -41,18 +35,14 @@ public class Autoscaler { /** What difference factor for a resource is worth a reallocation? */ private static final double resourceDifferenceWorthReallocation = 0.1; - private final HostResourcesCalculator resourcesCalculator; private final NodeMetricsDb metricsDb; private final NodeRepository nodeRepository; - private final NodeResourceLimits nodeResourceLimits; + private final AllocationOptimizer allocationOptimizer; - public Autoscaler(HostResourcesCalculator resourcesCalculator, - NodeMetricsDb metricsDb, - NodeRepository nodeRepository) { - this.resourcesCalculator = resourcesCalculator; + public Autoscaler(NodeMetricsDb metricsDb, NodeRepository nodeRepository) { this.metricsDb = metricsDb; this.nodeRepository = nodeRepository; - this.nodeResourceLimits = new NodeResourceLimits(nodeRepository.zone()); + this.allocationOptimizer = new AllocationOptimizer(nodeRepository); } /** @@ -63,61 +53,41 @@ public class Autoscaler { * @return a new suggested allocation for this cluster, or empty if it should not be rescaled at this time */ public Optional suggest(Cluster cluster, List clusterNodes) { - return autoscale(cluster, clusterNodes, false) + return autoscale(clusterNodes, Limits.empty()) .map(AllocatableClusterResources::toAdvertisedClusterResources); } /** - * Autoscale a cluster. This returns a better allocation (if found) inside the min and max limits. + * Autoscale a cluster by load. This returns a better allocation (if found) inside the min and max limits. * * @param clusterNodes the list of all the active nodes in a cluster * @return a new suggested allocation for this cluster, or empty if it should not be rescaled at this time */ public Optional autoscale(Cluster cluster, List clusterNodes) { if (cluster.minResources().equals(cluster.maxResources())) return Optional.empty(); // Shortcut - return autoscale(cluster, clusterNodes, true) + return autoscale(clusterNodes, Limits.of(cluster)) .map(AllocatableClusterResources::toAdvertisedClusterResources); } - private Optional autoscale(Cluster cluster, List clusterNodes, boolean respectLimits) { + private Optional autoscale(List clusterNodes, Limits limits) { if (unstable(clusterNodes)) return Optional.empty(); ClusterSpec.Type clusterType = clusterNodes.get(0).allocation().get().membership().cluster().type(); - AllocatableClusterResources currentAllocation = new AllocatableClusterResources(clusterNodes, resourcesCalculator); + AllocatableClusterResources currentAllocation = new AllocatableClusterResources(clusterNodes, nodeRepository); Optional cpuLoad = averageLoad(Resource.cpu, clusterNodes, clusterType); Optional memoryLoad = averageLoad(Resource.memory, clusterNodes, clusterType); Optional diskLoad = averageLoad(Resource.disk, clusterNodes, clusterType); if (cpuLoad.isEmpty() || memoryLoad.isEmpty() || diskLoad.isEmpty()) return Optional.empty(); + var target = ResourceTarget.idealLoad(cpuLoad.get(), memoryLoad.get(), diskLoad.get(), currentAllocation); - Optional bestAllocation = findBestAllocation(cpuLoad.get(), - memoryLoad.get(), - diskLoad.get(), - currentAllocation, - cluster, - respectLimits); + Optional bestAllocation = + allocationOptimizer.findBestAllocation(target, currentAllocation, limits); if (bestAllocation.isEmpty()) return Optional.empty(); if (similar(bestAllocation.get(), currentAllocation)) return Optional.empty(); return bestAllocation; } - private Optional findBestAllocation(double cpuLoad, double memoryLoad, double diskLoad, - AllocatableClusterResources currentAllocation, - Cluster cluster, boolean respectLimits) { - Optional bestAllocation = Optional.empty(); - for (ResourceIterator i = new ResourceIterator(cpuLoad, memoryLoad, diskLoad, currentAllocation, cluster, respectLimits); - i.hasNext(); ) { - Optional allocatableResources = toAllocatableResources(i.next(), - currentAllocation.clusterType(), - cluster, - respectLimits); - if (allocatableResources.isEmpty()) continue; - if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get())) - bestAllocation = allocatableResources; - } - return bestAllocation; - } - /** Returns true if both total real resources and total cost are similar */ private boolean similar(AllocatableClusterResources a, AllocatableClusterResources b) { return similar(a.cost(), b.cost(), costDifferenceWorthReallocation) && @@ -134,51 +104,6 @@ public class Autoscaler { return Math.abs(r1 - r2) / r1 < threshold; } - /** - * Returns the smallest allocatable node resources larger than the given node resources, - * or empty if none available. - */ - private Optional toAllocatableResources(ClusterResources resources, - ClusterSpec.Type clusterType, - Cluster cluster, - boolean respectLimits) { - NodeResources nodeResources = resources.nodeResources(); - if (respectLimits) - nodeResources = cluster.capAtLimits(nodeResources); - nodeResources = nodeResourceLimits.enlargeToLegal(nodeResources, clusterType); // enforce system limits - - if (allowsHostSharing(nodeRepository.zone().cloud())) { - // return the requested resources, or empty if they cannot fit on existing hosts - for (Flavor flavor : nodeRepository.getAvailableFlavors().getFlavors()) { - if (flavor.resources().satisfies(nodeResources)) - return Optional.of(new AllocatableClusterResources(resources.with(nodeResources), - nodeResources, - resources.nodeResources(), - clusterType)); - } - return Optional.empty(); - } - else { - // return the cheapest flavor satisfying the target resources, if any - Optional best = Optional.empty(); - for (Flavor flavor : nodeRepository.getAvailableFlavors().getFlavors()) { - if ( ! flavor.resources().satisfies(nodeResources)) continue; - - if (flavor.resources().storageType() == NodeResources.StorageType.remote) - flavor = flavor.with(FlavorOverrides.ofDisk(nodeResources.diskGb())); - var candidate = new AllocatableClusterResources(resources.with(flavor.resources()), - flavor, - resources.nodeResources(), - clusterType, - resourcesCalculator); - - if (best.isEmpty() || candidate.cost() <= best.get().cost()) - best = Optional.of(candidate); - } - return best; - } - } - /** * Returns the average load of this resource in the measurement window, * or empty if we are not in a position to make decisions from these measurements at this time. @@ -200,12 +125,6 @@ public class Autoscaler { return Duration.ofHours(12); // TODO: Measure much more often to get this down to minutes. And, ideally we should take node startup time into account } - // TODO: Put this in zone config instead? - private boolean allowsHostSharing(CloudName cloudName) { - if (cloudName.value().equals("aws")) return false; - return true; - } - public static boolean unstable(List nodes) { return nodes.stream().anyMatch(node -> node.status().wantToRetire() || node.allocation().get().membership().retired() || diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java new file mode 100644 index 00000000000..7ca60c4c86d --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java @@ -0,0 +1,69 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.autoscale; + +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterResources; +import com.yahoo.config.provision.NodeResources; +import com.yahoo.vespa.hosted.provision.applications.Cluster; + +/** + * Optional allocation limits + * + * @author bratseth + */ +public class Limits { + + private static final Limits empty = new Limits(null, null); + + private final ClusterResources min, max; + + private Limits(ClusterResources min, ClusterResources max) { + this.min = min; + this.max = max; + } + + public static Limits empty() { return empty; } + + public boolean isEmpty() { return this == empty; } + + public ClusterResources min() { + if (isEmpty()) throw new IllegalStateException("Empty: No min"); + return min; + } + + public ClusterResources max() { + if (isEmpty()) throw new IllegalStateException("Empty: No max"); + return max; + } + + /** Caps the given resources at the limits of this. If it is empty the node resources are returned as-is */ + public NodeResources cap(NodeResources resources) { + if (isEmpty()) return resources; + if (min.nodeResources().isUnspecified()) return resources; // means max is also unspecified + resources = resources.withVcpu(between(min.nodeResources().vcpu(), max.nodeResources().vcpu(), resources.vcpu())); + resources = resources.withMemoryGb(between(min.nodeResources().memoryGb(), max.nodeResources().memoryGb(), resources.memoryGb())); + resources = resources.withDiskGb(between(min.nodeResources().diskGb(), max.nodeResources().diskGb(), resources.diskGb())); + return resources; + } + + private double between(double min, double max, double value) { + value = Math.max(min, value); + value = Math.min(max, value); + return value; + } + + public static Limits of(Cluster cluster) { + return new Limits(cluster.minResources(), cluster.maxResources()); + } + + public static Limits of(Capacity capacity) { + return new Limits(capacity.minResources(), capacity.maxResources()); + } + + @Override + public String toString() { + if (isEmpty()) return "no limits"; + return "limits: from " + min + " to " + max; + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java deleted file mode 100644 index 207eecc1871..00000000000 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.provision.autoscale; - -import com.yahoo.config.provision.ClusterResources; -import com.yahoo.config.provision.NodeResources; -import com.yahoo.vespa.hosted.provision.applications.Cluster; - -/** - * Provides iteration over possible cluster resource allocations given a target total load - * and current groups/nodes allocation. - */ -public class ResourceIterator { - - // The min and max nodes to consider when not using application supplied limits - private static final int minimumNodes = 3; // Since this number includes redundancy it cannot be lower than 2 - private static final int maximumNodes = 150; - - // When a query is issued on a node the cost is the sum of a fixed cost component and a cost component - // proportional to document count. We must account for this when comparing configurations with more or fewer nodes. - // TODO: Measure this, and only take it into account with queries - private static final double fixedCpuCostFraction = 0.1; - - // Prescribed state - private final Cluster cluster; - private final boolean respectLimits; - - // Observed state - private final AllocatableClusterResources allocation; - private final double cpuLoad; - private final double memoryLoad; - private final double diskLoad; - private final int groupSize; - - // Derived from the observed state - private final int nodeIncrement; - private final boolean singleGroupMode; - - // Iterator state - private int currentNodes; - - public ResourceIterator(double cpuLoad, double memoryLoad, double diskLoad, - AllocatableClusterResources currentAllocation, - Cluster cluster, - boolean respectLimits) { - this.cpuLoad = cpuLoad; - this.memoryLoad = memoryLoad; - this.diskLoad = diskLoad; - this.respectLimits = respectLimits; - - // ceil: If the division does not produce a whole number we assume some node is missing - groupSize = (int)Math.ceil((double)currentAllocation.nodes() / currentAllocation.groups()); - allocation = currentAllocation; - - this.cluster = cluster; - - // What number of nodes is it effective to add or remove at the time from this cluster? - // This is the group size, since we (for now) assume the group size is decided by someone wiser than us - // and we decide the number of groups. - // The exception is when we only have one group, where we can add and remove single nodes in it. - singleGroupMode = currentAllocation.groups() == 1; - nodeIncrement = singleGroupMode ? 1 : groupSize; - - // Step down to the right starting point - currentNodes = currentAllocation.nodes(); - while (currentNodes - nodeIncrement >= minNodes() - && ( singleGroupMode || currentNodes - nodeIncrement > groupSize)) // group level redundancy - currentNodes -= nodeIncrement; - } - - public ClusterResources next() { - ClusterResources next = resourcesWith(currentNodes); - currentNodes += nodeIncrement; - return next; - } - - public boolean hasNext() { - return currentNodes <= maxNodes(); - } - - private int minNodes() { - if ( ! respectLimits) return minimumNodes; - if (singleGroupMode) return cluster.minResources().nodes(); - return Math.max(cluster.minResources().nodes(), cluster.minResources().groups() * groupSize ); - } - - private int maxNodes() { - if ( ! respectLimits) return maximumNodes; - if (singleGroupMode) return cluster.maxResources().nodes(); - return Math.min(cluster.maxResources().nodes(), cluster.maxResources().groups() * groupSize ); - } - - private ClusterResources resourcesWith(int nodes) { - int nodesWithRedundancy = nodes - (singleGroupMode ? 1 : groupSize); - return new ClusterResources(nodes, - singleGroupMode ? 1 : nodes / groupSize, - nodeResourcesWith(nodesWithRedundancy)); - } - - /** - * For the observed load this instance is initialized with, returns the resources needed per node to be at - * ideal load given a target node count - */ - private NodeResources nodeResourcesWith(int nodeCount) { - // Cpu: Scales with cluster size (TODO: Only reads, writes scales with group size) - // Memory and disk: Scales with group size - - double cpu, memory, disk; - if (singleGroupMode) { - // The fixed cost portion of cpu does not scale with changes to the node count - // TODO: Only for the portion of cpu consumed by queries - double totalCpu = clusterUsage(Resource.cpu, cpuLoad); - cpu = fixedCpuCostFraction * totalCpu / groupSize / Resource.cpu.idealAverageLoad() + - (1 - fixedCpuCostFraction) * totalCpu / nodeCount / Resource.cpu.idealAverageLoad(); - if (allocation.clusterType().isContent()) { // load scales with node share of content - memory = groupUsage(Resource.memory, memoryLoad) / nodeCount / Resource.memory.idealAverageLoad(); - disk = groupUsage(Resource.disk, diskLoad) / nodeCount / Resource.disk.idealAverageLoad(); - } - else { - memory = nodeUsage(Resource.memory, memoryLoad) / Resource.memory.idealAverageLoad(); - disk = nodeUsage(Resource.disk, diskLoad) / Resource.disk.idealAverageLoad(); - } - } - else { - cpu = clusterUsage(Resource.cpu, cpuLoad) / nodeCount / Resource.cpu.idealAverageLoad(); - if (allocation.clusterType().isContent()) { // load scales with node share of content - memory = groupUsage(Resource.memory, memoryLoad) / groupSize / Resource.memory.idealAverageLoad(); - disk = groupUsage(Resource.disk, diskLoad) / groupSize / Resource.disk.idealAverageLoad(); - } - else { - memory = nodeUsage(Resource.memory, memoryLoad) / Resource.memory.idealAverageLoad(); - disk = nodeUsage(Resource.disk, diskLoad) / Resource.disk.idealAverageLoad(); - } - } - - // Combine the scaled resource values computed here - // and the currently combined values of non-scaled resources - return new NodeResources(cpu, memory, disk, - cluster.minResources().nodeResources().bandwidthGbps(), - cluster.minResources().nodeResources().diskSpeed(), - cluster.minResources().nodeResources().storageType()); - } - - private double clusterUsage(Resource resource, double load) { - return nodeUsage(resource, load) * allocation.nodes(); - } - - private double groupUsage(Resource resource, double load) { - return nodeUsage(resource, load) * groupSize; - } - - private double nodeUsage(Resource resource, double load) { - return load * resource.valueFrom(allocation.realResources()); - } - -} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java new file mode 100644 index 00000000000..287cd2ae86a --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceTarget.java @@ -0,0 +1,74 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.autoscale; + +import com.yahoo.config.provision.NodeResources; + +/** + * A resource target to hit for the allocation optimizer. + * The target is measured in cpu, memory and disk per node in the allocation given by current. + */ +public class ResourceTarget { + + private final boolean adjustForRedundancy; + + /** The target resources per node, assuming the node assignment in current */ + private final double cpu, memory, disk; + + /** The current allocation leading to this target */ + private final AllocatableClusterResources current; + + private ResourceTarget(double cpu, double memory, double disk, + boolean adjustForRedundancy, + AllocatableClusterResources current) { + this.cpu = cpu; + this.memory = memory; + this.disk = disk; + this.adjustForRedundancy = adjustForRedundancy; + this.current = current; + } + + /** Are the target resources given by this including redundancy or not */ + public boolean adjustForRedundancy() { return adjustForRedundancy; } + + /** Returns the target total cpu to allocate to the entire cluster */ + public double clusterCpu() { return nodeCpu() * current.nodes(); } + + /** Returns the target total memory to allocate to each group */ + public double groupMemory() { return nodeMemory() * current.groupSize(); } + + /** Returns the target total disk to allocate to each group */ + public double groupDisk() { return nodeDisk() * current.groupSize(); } + + /** Returns the target cpu per node, in terms of the current allocation */ + public double nodeCpu() { return cpu; } + + /** Returns the target memory per node, in terms of the current allocation */ + public double nodeMemory() { return memory; } + + /** Returns the target disk per node, in terms of the current allocation */ + public double nodeDisk() { return disk; } + + private static double nodeUsage(Resource resource, double load, AllocatableClusterResources current) { + return load * resource.valueFrom(current.realResources()); + } + + /** Create a target of achieving ideal load given a current load */ + public static ResourceTarget idealLoad(double currentCpuLoad, double currentMemoryLoad, double currentDiskLoad, + AllocatableClusterResources current) { + return new ResourceTarget(nodeUsage(Resource.cpu, currentCpuLoad, current) / Resource.cpu.idealAverageLoad(), + nodeUsage(Resource.memory, currentMemoryLoad, current) / Resource.memory.idealAverageLoad(), + nodeUsage(Resource.disk, currentDiskLoad, current) / Resource.disk.idealAverageLoad(), + true, + current); + } + + /** Crete a target of preserving a current allocation */ + public static ResourceTarget preserve(AllocatableClusterResources current) { + return new ResourceTarget(current.realResources().vcpu(), + current.realResources().memoryGb(), + current.realResources().diskGb(), + false, + current); + } + +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java index f6398c04e61..09723d83e3e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerService.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.lb; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.vespa.hosted.provision.NodeRepository; import java.util.Set; @@ -23,7 +24,8 @@ public interface LoadBalancerService { * pre-existing load balancer). * @return The provisioned load balancer instance */ - LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set reals, boolean force); + LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set reals, boolean force, + NodeRepository nodeRepository); /** Permanently remove load balancer for given application cluster */ void remove(ApplicationId application, ClusterSpec.Id cluster); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java index 91f02a31f6b..9bd1189420a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancerServiceMock.java @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableSet; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.HostName; +import com.yahoo.vespa.hosted.provision.NodeRepository; import java.util.Collections; import java.util.HashMap; @@ -29,7 +30,8 @@ public class LoadBalancerServiceMock implements LoadBalancerService { } @Override - public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set reals, boolean force) { + public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set reals, boolean force, + NodeRepository nodeRepository) { var id = new LoadBalancerId(application, cluster); var oldInstance = instances.get(id); if (!force && oldInstance != null && !oldInstance.reals().isEmpty() && reals.isEmpty()) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java index 891faceca7a..07074bc45af 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/PassthroughLoadBalancerService.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.lb; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.vespa.hosted.provision.NodeRepository; import java.util.Comparator; import java.util.Optional; @@ -17,7 +18,8 @@ import java.util.Set; public class PassthroughLoadBalancerService implements LoadBalancerService { @Override - public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set reals, boolean force) { + public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set reals, boolean force, + NodeRepository nodeRepository) { var real = reals.stream() .min(Comparator.naturalOrder()) .orElseThrow(() -> new IllegalArgumentException("No reals given")); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java index f670ff93073..42a52601527 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/SharedLoadBalancerService.java @@ -27,15 +27,13 @@ public class SharedLoadBalancerService implements LoadBalancerService { private static final Comparator hostnameComparator = Comparator.comparing(Node::hostname); - private final NodeRepository nodeRepository; - @Inject - public SharedLoadBalancerService(NodeRepository nodeRepository) { - this.nodeRepository = Objects.requireNonNull(nodeRepository, "nodeRepository must be non-null"); + public SharedLoadBalancerService() { } @Override - public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set reals, boolean force) { + public LoadBalancerInstance create(ApplicationId application, ClusterSpec.Id cluster, Set reals, boolean force, + NodeRepository nodeRepository) { var proxyNodes = new ArrayList<>(nodeRepository.getNodes(NodeType.proxy)); proxyNodes.sort(hostnameComparator); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java index da8a0a14171..fa8e8375e23 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java @@ -34,13 +34,12 @@ public class AutoscalingMaintainer extends NodeRepositoryMaintainer { private final Metric metric; public AutoscalingMaintainer(NodeRepository nodeRepository, - HostResourcesCalculator hostResourcesCalculator, NodeMetricsDb metricsDb, Deployer deployer, Metric metric, Duration interval) { super(nodeRepository, interval); - this.autoscaler = new Autoscaler(hostResourcesCalculator, metricsDb, nodeRepository); + this.autoscaler = new Autoscaler(metricsDb, nodeRepository); this.metric = metric; this.deployer = deployer; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java index 5c899f74bdb..85909daa240 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DynamicProvisioningMaintainer.java @@ -44,15 +44,15 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer { private static final ApplicationId preprovisionAppId = ApplicationId.from("hosted-vespa", "tenant-host", "preprovision"); private final HostProvisioner hostProvisioner; - private final HostResourcesCalculator hostResourcesCalculator; private final BooleanFlag dynamicProvisioningEnabled; private final ListFlag preprovisionCapacityFlag; - DynamicProvisioningMaintainer(NodeRepository nodeRepository, Duration interval, HostProvisioner hostProvisioner, - HostResourcesCalculator hostResourcesCalculator, FlagSource flagSource) { + DynamicProvisioningMaintainer(NodeRepository nodeRepository, + Duration interval, + HostProvisioner hostProvisioner, + FlagSource flagSource) { super(nodeRepository, interval); this.hostProvisioner = hostProvisioner; - this.hostResourcesCalculator = hostResourcesCalculator; this.dynamicProvisioningEnabled = Flags.ENABLE_DYNAMIC_PROVISIONING.bindTo(flagSource); this.preprovisionCapacityFlag = Flags.PREPROVISION_CAPACITY.bindTo(flagSource); } @@ -111,7 +111,7 @@ public class DynamicProvisioningMaintainer extends NodeRepositoryMaintainer { NodeResources resources = it.next(); removableHosts.stream() .filter(nodeRepository()::canAllocateTenantNodeTo) - .filter(host -> hostResourcesCalculator.advertisedResourcesOf(host.flavor()).satisfies(resources)) + .filter(host -> nodeRepository().resourcesCalculator().advertisedResourcesOf(host.flavor()).satisfies(resources)) .min(Comparator.comparingInt(n -> n.flavor().cost())) .ifPresent(host -> { removableHosts.remove(host); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java index d793874119a..483b4dc8f84 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java @@ -99,7 +99,7 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer { // Remove any real no longer allocated to this application reals.removeIf(real -> !allocatedNodes.contains(real.hostname().value())); try { - service.create(lb.id().application(), lb.id().cluster(), reals, true); + service.create(lb.id().application(), lb.id().cluster(), reals, true, nodeRepository()); db.writeLoadBalancer(lb.with(lb.instance().withReals(reals))); } catch (Exception e) { failed.add(lb.id()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java index a50e6d10696..d388fb5a967 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java @@ -87,13 +87,13 @@ public class NodeRepositoryMaintenance extends AbstractComponent { loadBalancerExpirer = provisionServiceProvider.getLoadBalancerService().map(lbService -> new LoadBalancerExpirer(nodeRepository, defaults.loadBalancerExpirerInterval, lbService)); dynamicProvisioningMaintainer = provisionServiceProvider.getHostProvisioner().map(hostProvisioner -> - new DynamicProvisioningMaintainer(nodeRepository, defaults.dynamicProvisionerInterval, hostProvisioner, provisionServiceProvider.getHostResourcesCalculator(), flagSource)); + new DynamicProvisioningMaintainer(nodeRepository, defaults.dynamicProvisionerInterval, hostProvisioner, flagSource)); capacityReportMaintainer = new CapacityReportMaintainer(nodeRepository, metric, defaults.capacityReportInterval); osUpgradeActivator = new OsUpgradeActivator(nodeRepository, defaults.osUpgradeActivatorInterval); - rebalancer = new Rebalancer(deployer, nodeRepository, provisionServiceProvider.getHostResourcesCalculator(), provisionServiceProvider.getHostProvisioner(), metric, clock, defaults.rebalancerInterval); + rebalancer = new Rebalancer(deployer, nodeRepository, provisionServiceProvider.getHostProvisioner(), metric, clock, defaults.rebalancerInterval); nodeMetricsDbMaintainer = new NodeMetricsDbMaintainer(nodeRepository, nodeMetrics, nodeMetricsDb, defaults.nodeMetricsCollectionInterval); - autoscalingMaintainer = new AutoscalingMaintainer(nodeRepository, provisionServiceProvider.getHostResourcesCalculator(), nodeMetricsDb, deployer, metric, defaults.autoscalingInterval); - scalingSuggestionsMaintainer = new ScalingSuggestionsMaintainer(nodeRepository, provisionServiceProvider.getHostResourcesCalculator(), nodeMetricsDb, defaults.scalingSuggestionsInterval); + autoscalingMaintainer = new AutoscalingMaintainer(nodeRepository, nodeMetricsDb, deployer, metric, defaults.autoscalingInterval); + scalingSuggestionsMaintainer = new ScalingSuggestionsMaintainer(nodeRepository, nodeMetricsDb, defaults.scalingSuggestionsInterval); // The DuperModel is filled with infrastructure applications by the infrastructure provisioner, so explicitly run that now infrastructureProvisioner.maintainButThrowOnException(); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java index 0ba3765f470..7ffb541be2a 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/Rebalancer.java @@ -25,21 +25,18 @@ import java.util.Optional; public class Rebalancer extends NodeRepositoryMaintainer { private final Deployer deployer; - private final HostResourcesCalculator hostResourcesCalculator; private final Optional hostProvisioner; private final Metric metric; private final Clock clock; public Rebalancer(Deployer deployer, NodeRepository nodeRepository, - HostResourcesCalculator hostResourcesCalculator, Optional hostProvisioner, Metric metric, Clock clock, Duration interval) { super(nodeRepository, interval); this.deployer = deployer; - this.hostResourcesCalculator = hostResourcesCalculator; this.hostProvisioner = hostProvisioner; this.metric = metric; this.clock = clock; @@ -64,7 +61,7 @@ public class Rebalancer extends NodeRepositoryMaintainer { /** We do this here rather than in MetricsReporter because it is expensive and frequent updates are unnecessary */ private void updateSkewMetric(NodeList allNodes) { - DockerHostCapacity capacity = new DockerHostCapacity(allNodes, hostResourcesCalculator); + DockerHostCapacity capacity = new DockerHostCapacity(allNodes, nodeRepository().resourcesCalculator()); double totalSkew = 0; int hostCount = 0; for (Node host : allNodes.nodeType((NodeType.host)).state(Node.State.active)) { @@ -86,7 +83,7 @@ public class Rebalancer extends NodeRepositoryMaintainer { * Returns Move.none if no moves can be made to reduce skew. */ private Move findBestMove(NodeList allNodes) { - DockerHostCapacity capacity = new DockerHostCapacity(allNodes, hostResourcesCalculator); + DockerHostCapacity capacity = new DockerHostCapacity(allNodes, nodeRepository().resourcesCalculator()); Move bestMove = Move.none; for (Node node : allNodes.nodeType(NodeType.tenant).state(Node.State.active)) { if (node.parentHostname().isEmpty()) continue; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java index 332126690be..b68e8eacbaa 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java @@ -30,11 +30,10 @@ public class ScalingSuggestionsMaintainer extends NodeRepositoryMaintainer { private final Autoscaler autoscaler; public ScalingSuggestionsMaintainer(NodeRepository nodeRepository, - HostResourcesCalculator hostResourcesCalculator, NodeMetricsDb metricsDb, Duration interval) { super(nodeRepository, interval); - this.autoscaler = new Autoscaler(hostResourcesCalculator, metricsDb, nodeRepository); + this.autoscaler = new Autoscaler(metricsDb, nodeRepository); } @Override diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java index 648bf52f455..a2bf83eb6c3 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java @@ -46,7 +46,7 @@ public class CapacityPolicies { } public NodeResources decideNodeResources(NodeResources requested, Capacity capacity, ClusterSpec cluster) { - if (requested == NodeResources.unspecified) + if (requested.isUnspecified()) requested = defaultNodeResources(cluster.type()); ensureSufficientResources(requested, cluster); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java index 9566213bc91..da54d89b4e5 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/EmptyProvisionServiceProvider.java @@ -4,6 +4,7 @@ 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; +import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerService; import java.util.Optional; @@ -13,7 +14,7 @@ import java.util.Optional; */ public class EmptyProvisionServiceProvider implements ProvisionServiceProvider { - private final HostResourcesCalculator hostResourcesCalculator = new NoopHostResourcesCalculator(); + private final HostResourcesCalculator hostResourcesCalculator = new IdentityHostResourcesCalculator(); @Override public Optional getLoadBalancerService() { @@ -30,10 +31,10 @@ public class EmptyProvisionServiceProvider implements ProvisionServiceProvider { return hostResourcesCalculator; } - public static class NoopHostResourcesCalculator implements HostResourcesCalculator { + private static class IdentityHostResourcesCalculator implements HostResourcesCalculator { @Override - public NodeResources realResourcesOf(Node node) { + public NodeResources realResourcesOf(Node node, NodeRepository repository) { return node.flavor().resources(); } 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 8143076a3b2..2a39cc333e0 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 @@ -30,15 +30,14 @@ public class GroupPreparer { private final NodeRepository nodeRepository; private final Optional hostProvisioner; - private final HostResourcesCalculator hostResourcesCalculator; private final BooleanFlag dynamicProvisioningEnabledFlag; private final ListFlag preprovisionCapacityFlag; - public GroupPreparer(NodeRepository nodeRepository, Optional hostProvisioner, - HostResourcesCalculator hostResourcesCalculator, FlagSource flagSource) { + public GroupPreparer(NodeRepository nodeRepository, + Optional hostProvisioner, + FlagSource flagSource) { this.nodeRepository = nodeRepository; this.hostProvisioner = hostProvisioner; - this.hostResourcesCalculator = hostResourcesCalculator; this.dynamicProvisioningEnabledFlag = Flags.ENABLE_DYNAMIC_PROVISIONING.bindTo(flagSource); this.preprovisionCapacityFlag = Flags.PREPROVISION_CAPACITY.bindTo(flagSource); } @@ -72,18 +71,23 @@ public class GroupPreparer { // Create a prioritized set of nodes LockedNodeList nodeList = nodeRepository.list(allocationLock); - NodePrioritizer prioritizer = new NodePrioritizer(nodeList, application, cluster, requestedNodes, - spareCount, wantedGroups, nodeRepository.nameResolver(), - hostResourcesCalculator, allocateFully); + NodePrioritizer prioritizer = new NodePrioritizer(nodeList, + application, + cluster, + requestedNodes, + spareCount, + wantedGroups, + nodeRepository.nameResolver(), + nodeRepository.resourcesCalculator(), + allocateFully); prioritizer.addApplicationNodes(); prioritizer.addSurplusNodes(surplusActiveNodes); prioritizer.addReadyNodes(); prioritizer.addNewDockerNodes(nodeRepository::canAllocateTenantNodeTo); - // Allocate from the prioritized list NodeAllocation allocation = new NodeAllocation(nodeList, application, cluster, requestedNodes, - highestIndex, nodeRepository.getAvailableFlavors(), + highestIndex, nodeRepository.flavors(), nodeRepository.zone(), nodeRepository.clock()); allocation.offer(prioritizer.prioritize()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java index b26351062e6..096e58e963e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostResourcesCalculator.java @@ -4,6 +4,7 @@ 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; +import com.yahoo.vespa.hosted.provision.NodeRepository; /** * Some cloud providers advertise that a certain amount of resources are available in a flavor @@ -16,7 +17,7 @@ import com.yahoo.vespa.hosted.provision.Node; public interface HostResourcesCalculator { /** Nodes use advertised resources. This returns the real resources for the node. */ - NodeResources realResourcesOf(Node node); + NodeResources realResourcesOf(Node node, NodeRepository nodeRepository); /** Flavors use real resources. This returns the advertised resources of the flavor. */ NodeResources advertisedResourcesOf(Flavor flavor); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java index 3532d989d85..026788e9d6c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java @@ -169,7 +169,7 @@ public class LoadBalancerProvisioner { log.log(Level.FINE, "Creating load balancer for " + cluster + " in " + application.toShortString() + ", targeting: " + reals); try { - return service.create(application, cluster, reals, force); + return service.create(application, cluster, reals, force, nodeRepository); } catch (Exception e) { throw new LoadBalancerServiceException("Failed to (re)configure load balancer for " + cluster + " in " + application + ", targeting: " + reals + ". The operation will be " + 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 6f3f83c3349..a7d83bbfad9 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 @@ -5,6 +5,8 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; + +import java.util.ArrayList; import java.util.logging.Level; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; @@ -195,6 +197,8 @@ public class NodePrioritizer { .forEach(prioritizableNode -> nodes.put(prioritizableNode.node, prioritizableNode)); } + public List nodes() { return new ArrayList<>(nodes.values()); } + /** * Convert a list of nodes to a list of node priorities. This includes finding, calculating * parameters to the priority sorting procedure. diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java index 4000354243f..6f026254bcd 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java @@ -22,6 +22,10 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.applications.Application; +import com.yahoo.vespa.hosted.provision.autoscale.AllocatableClusterResources; +import com.yahoo.vespa.hosted.provision.autoscale.AllocationOptimizer; +import com.yahoo.vespa.hosted.provision.autoscale.Limits; +import com.yahoo.vespa.hosted.provision.autoscale.ResourceTarget; import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter; import com.yahoo.vespa.hosted.provision.node.filter.NodeHostFilter; @@ -31,7 +35,6 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.logging.Level; import java.util.logging.Logger; /** @@ -47,6 +50,7 @@ public class NodeRepositoryProvisioner implements Provisioner { private static final int SPARE_CAPACITY_NONPROD = 0; private final NodeRepository nodeRepository; + private final AllocationOptimizer allocationOptimizer; private final CapacityPolicies capacityPolicies; private final Zone zone; private final Preparer preparer; @@ -61,6 +65,7 @@ public class NodeRepositoryProvisioner implements Provisioner { public NodeRepositoryProvisioner(NodeRepository nodeRepository, Zone zone, ProvisionServiceProvider provisionServiceProvider, FlagSource flagSource) { this.nodeRepository = nodeRepository; + this.allocationOptimizer = new AllocationOptimizer(nodeRepository); this.capacityPolicies = new CapacityPolicies(zone); this.zone = zone; this.loadBalancerProvisioner = provisionServiceProvider.getLoadBalancerService().map(lbService -> new LoadBalancerProvisioner(nodeRepository, lbService)); @@ -94,7 +99,7 @@ public class NodeRepositoryProvisioner implements Provisioner { NodeResources resources; NodeSpec nodeSpec; if ( requested.type() == NodeType.tenant) { - ClusterResources target = decideTargetResources(application, cluster.id(), requested); + ClusterResources target = decideTargetResources(application, cluster, requested); int nodeCount = capacityPolicies.decideSize(target.nodes(), requested, cluster, application); resources = capacityPolicies.decideNodeResources(target.nodeResources(), requested, cluster); boolean exclusive = capacityPolicies.decideExclusivity(cluster.isExclusive()); @@ -131,66 +136,39 @@ public class NodeRepositoryProvisioner implements Provisioner { * Returns the target cluster resources, a value between the min and max in the requested capacity, * and updates the application store with the received min and max. */ - private ClusterResources decideTargetResources(ApplicationId applicationId, ClusterSpec.Id clusterId, Capacity requested) { + private ClusterResources decideTargetResources(ApplicationId applicationId, ClusterSpec clusterSpec, Capacity requested) { try (Mutex lock = nodeRepository.lock(applicationId)) { Application application = nodeRepository.applications().get(applicationId).orElse(new Application(applicationId)); - application = application.withClusterLimits(clusterId, requested.minResources(), requested.maxResources()); + application = application.withClusterLimits(clusterSpec.id(), requested.minResources(), requested.maxResources()); nodeRepository.applications().put(application, lock); - return application.clusters().get(clusterId).targetResources() - .orElseGet(() -> currentResources(applicationId, clusterId, requested)); + return application.clusters().get(clusterSpec.id()).targetResources() + .orElseGet(() -> currentResources(applicationId, clusterSpec, requested)); } } /** Returns the current resources of this cluster, or the closes */ private ClusterResources currentResources(ApplicationId applicationId, - ClusterSpec.Id clusterId, + ClusterSpec clusterSpec, Capacity requested) { List nodes = NodeList.copyOf(nodeRepository.getNodes(applicationId, Node.State.active)) - .cluster(clusterId) + .cluster(clusterSpec.id()) .not().retired() .not().removable() .asList(); - if (nodes.isEmpty()) return requested.minResources(); // New deployment: Start at min - - long groups = nodes.stream().map(node -> node.allocation().get().membership().cluster().group()).distinct().count(); - var currentResources = new ClusterResources(nodes.size(), (int)groups, nodes.get(0).flavor().resources()); - return ensureWithin(requested.minResources(), requested.maxResources(), currentResources); + AllocatableClusterResources currentResources = + nodes.isEmpty() ? new AllocatableClusterResources(requested.minResources(), clusterSpec.type()) // new deployment: Use min + : new AllocatableClusterResources(nodes, nodeRepository); + return ensureWithin(Limits.of(requested), currentResources); } /** Make the minimal adjustments needed to the current resources to stay within the limits */ - private ClusterResources ensureWithin(ClusterResources min, ClusterResources max, ClusterResources current) { - int nodes = between(min.nodes(), max.nodes(), current.nodes()); - int groups = between(min.groups(), max.groups(), current.groups()); - if (nodes % groups != 0) { - // That didn't work - try to preserve current group size instead. - // Rounding here is needed because a node may be missing due to node failing. - int currentGroupsSize = Math.round((float)current.nodes() / current.groups()); - nodes = currentGroupsSize * groups; - if (nodes != between(min.nodes(), max.nodes(), nodes)) { - // Give up: Use max - nodes = max.nodes(); - groups = max.groups(); - } - } - if (min.nodeResources() != NodeResources.unspecified && max.nodeResources() != NodeResources.unspecified) { - double vcpu = between(min.nodeResources().vcpu(), max.nodeResources().vcpu(), current.nodeResources().vcpu()); - double memoryGb = between(min.nodeResources().memoryGb(), max.nodeResources().memoryGb(), current.nodeResources().memoryGb()); - double diskGb = between(min.nodeResources().diskGb(), max.nodeResources().diskGb(), current.nodeResources().diskGb()); - // Combine computed scaled resources with requested non-scaled resources (for which min=max) - NodeResources nodeResources = min.nodeResources().withVcpu(vcpu).withMemoryGb(memoryGb).withDiskGb(diskGb); - return new ClusterResources(nodes, groups, nodeResources); - } - else { - return new ClusterResources(nodes, groups, current.nodeResources()); - } - } - - private int between(int min, int max, int n) { - return Math.min(max, Math.max(min, n)); - } + private ClusterResources ensureWithin(Limits limits, AllocatableClusterResources current) { + if (limits.isEmpty()) return current.toAdvertisedClusterResources(); + if (limits.min().equals(limits.max())) return limits.min(); - private double between(double min, double max, double n) { - return Math.min(max, Math.max(min, n)); + return allocationOptimizer.findBestAllocation(ResourceTarget.preserve(current), current, limits) + .orElseThrow(() -> new IllegalArgumentException("No allocation possible within " + limits)) + .toAdvertisedClusterResources(); } private void logIfDownscaled(int targetNodes, int actualNodes, ClusterSpec cluster, ProvisionLogger logger) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java index 91c15cdb61b..f88caffa6c6 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/Preparer.java @@ -33,7 +33,7 @@ class Preparer { this.nodeRepository = nodeRepository; this.spareCount = spareCount; this.loadBalancerProvisioner = loadBalancerProvisioner; - this.groupPreparer = new GroupPreparer(nodeRepository, hostProvisioner, hostResourcesCalculator, flagSource); + this.groupPreparer = new GroupPreparer(nodeRepository, hostProvisioner, flagSource); } /** Prepare all required resources for the given application and cluster */ 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 6fafd496174..f5a68f71a1c 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 @@ -27,7 +27,9 @@ import com.yahoo.vespa.hosted.provision.applications.Cluster; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.node.Status; +import com.yahoo.vespa.hosted.provision.provisioning.EmptyProvisionServiceProvider; import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner; +import com.yahoo.vespa.hosted.provision.provisioning.ProvisionServiceProvider; import java.time.Clock; import java.time.Instant; @@ -55,7 +57,11 @@ public class MockNodeRepository extends NodeRepository { * @param flavors flavors to have in node repo */ public MockNodeRepository(MockCurator curator, NodeFlavors flavors) { - super(flavors, curator, Clock.fixed(Instant.ofEpochMilli(123), ZoneId.of("Z")), Zone.defaultZone(), + super(flavors, + new EmptyProvisionServiceProvider().getHostResourcesCalculator(), + curator, + Clock.fixed(Instant.ofEpochMilli(123), ZoneId.of("Z")), + Zone.defaultZone(), new MockNameResolver().mockAnyLookup(), DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"), true); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java index 0d5950fe33a..9a02f65daf9 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisionServiceProvider.java @@ -26,7 +26,7 @@ public class MockProvisionServiceProvider implements ProvisionServiceProvider { } public MockProvisionServiceProvider(LoadBalancerService loadBalancerService, HostProvisioner hostProvisioner) { - this(loadBalancerService, hostProvisioner, new EmptyProvisionServiceProvider.NoopHostResourcesCalculator()); + this(loadBalancerService, hostProvisioner, new EmptyProvisionServiceProvider().getHostResourcesCalculator()); } public MockProvisionServiceProvider(LoadBalancerService loadBalancerService, HostProvisioner hostProvisioner, -- cgit v1.2.3