com.yahoo.vespa.hosted.provision.provisioning.HostCapacity Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of node-repository Show documentation
Show all versions of node-repository Show documentation
Keeps track of node assignment in a multi-application setup.
The newest version!
// Copyright Vespa.ai. 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.config.provision.NodeType;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Capacity calculation for hosts.
*
* The calculations are based on an immutable copy of nodes that represents
* all capacities in the system - i.e. all nodes in the node repo.
*
* @author smorgrav
*/
public class HostCapacity {
private final NodeList allNodes;
private final HostResourcesCalculator hostResourcesCalculator;
public HostCapacity(NodeList allNodes, HostResourcesCalculator hostResourcesCalculator) {
this.allNodes = Objects.requireNonNull(allNodes, "allNodes must be non-null");
this.hostResourcesCalculator = Objects.requireNonNull(hostResourcesCalculator, "hostResourcesCalculator must be non-null");
}
public NodeList allNodes() { return allNodes; }
/**
* Spare hosts are the hosts in the system with the most free capacity. A zone may reserve a minimum number of spare
* hosts to increase the chances of having replacements for failed nodes.
*
* We do not count retired or inactive nodes as used capacity (as they could have been
* moved to create space for the spare node in the first place).
*
* @param candidates the candidates to consider. This list may contain all kinds of nodes.
* @param count the max number of spare hosts to return
*/
public Set findSpareHosts(List candidates, int count) {
ArrayList nodesWithIp = new ArrayList<>(candidates.size());
for (Node node : candidates) {
if (node.type().canRun(NodeType.tenant) && (node.state() == Node.State.active) && node.reservedTo().isEmpty()) {
int numFreeIps = freeIps(node);
if (numFreeIps > 0) {
nodesWithIp.add(new NodeWithHostResources(node, availableCapacityOf(node, true, false), numFreeIps));
}
}
}
return nodesWithIp.stream().sorted().limit(count).map(n -> n.node).collect(Collectors.toSet());
}
private static class NodeWithHostResources implements Comparable {
private final Node node;
private final NodeResources hostResources;
private final int numFreeIps;
NodeWithHostResources(Node node, NodeResources hostResources, int freeIps) {
this.node = node;
this.hostResources = hostResources;
this.numFreeIps = freeIps;
}
@Override
public int compareTo(HostCapacity.NodeWithHostResources b) {
int result = compare(b.hostResources, hostResources);
if (result != 0) return result;
return b.numFreeIps - numFreeIps;
}
}
public Set findSpareHostsInDynamicallyProvisionedZones(List candidates) {
return candidates.stream()
.filter(node -> node.type() == NodeType.host)
.filter(host -> host.state() == Node.State.active)
.filter(host -> host.reservedTo().isEmpty())
.filter(host -> allNodes.childrenOf(host).isEmpty())
.collect(Collectors.toSet());
}
private static int compare(NodeResources a, NodeResources b) {
return NodeResourceComparator.defaultOrder().compare(a, b);
}
/** Returns whether host can allocate a node with requested capacity */
public boolean hasCapacity(Node host, NodeResources requestedCapacity) {
return availableCapacityOf(host).satisfies(requestedCapacity);
}
/** Returns the number of available IP addresses on given host */
int freeIps(Node host) {
if (host.type() == NodeType.host) {
return allNodes.eventuallyUnusedIpAddressCount(host);
}
return host.ipConfig().pool().findUnusedIpAddresses(allNodes).size();
}
/** Returns the capacity of given host that is both free and usable */
public NodeResources availableCapacityOf(Node host) {
return availableCapacityOf(host, false, true);
}
/**
* Calculate the unused capacity of given host.
*
* Note that unlike {@link #availableCapacityOf(Node)}, this only considers resources and returns any unused
* capacity even if the host does not have available IP addresses.
*
* @param host the host to find free capacity of
* @return a default (empty) capacity if not host, otherwise the free capacity
*/
public NodeResources unusedCapacityOf(Node host) {
return availableCapacityOf(host, false, false);
}
private NodeResources availableCapacityOf(Node host, boolean excludeInactive, boolean requireIps) {
// Only hosts have free capacity
if ( ! host.type().canRun(NodeType.tenant)) return NodeResources.zero();
if ( requireIps && freeIps(host) == 0) return NodeResources.zero();
NodeResources hostResources = hostResourcesCalculator.advertisedResourcesOf(host.flavor());
return allNodes.childrenOf(host).stream()
.filter(node -> !(excludeInactive && inactiveOrRetired(node)))
.map(node -> node.flavor().resources().justNumbers())
.reduce(hostResources.justNumbers(), NodeResources::subtract)
.with(host.flavor().resources().diskSpeed())
.with(host.flavor().resources().storageType())
.with(host.flavor().resources().architecture());
}
private static boolean inactiveOrRetired(Node node) {
if (node.state() == Node.State.inactive) return true;
if (node.allocation().isPresent() && node.allocation().get().membership().retired()) return true;
return false;
}
}