All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.vespa.hosted.provision.provisioning.HostCapacity Maven / Gradle / Ivy

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;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy