aboutsummaryrefslogtreecommitdiffstats
path: root/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerHostCapacity.java
blob: 78ea258107b9c8bf6973c216389d8bf2754178f3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// 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.NodeType;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Capacity calculation for docker hosts.
 * <p>
 * The calculations is based on an immutable copy of nodes that represents
 * all capacities in the system - i.e. all nodes in the node repo give or take.
 *
 * @author smorgrav
 */
public class DockerHostCapacity {

    /** Tenant name for headroom nodes - only used internally */
    public static final String HEADROOM_TENANT = "-__!@#$$%THISisHEADroom";

    /**
     * An immutable list of nodes
     */
    private final NodeList allNodes;

    public DockerHostCapacity(List<Node> allNodes) {
        this.allNodes = new NodeList(allNodes);
    }

    /**
     * Compare hosts on free capacity.
     * <p>
     * Used in prioritizing hosts for allocation in <b>descending</b> order.
     */
    int compare(Node hostA, Node hostB) {
        int comp = freeCapacityOf(hostB, false).compare(freeCapacityOf(hostA, false));
        if (comp == 0) {
            comp = freeCapacityOf(hostB, false).compare(freeCapacityOf(hostA, false));
            if (comp == 0) {
                // If resources are equal - we want to assign to the one with the most IPaddresses free
                comp = freeIPs(hostB) - freeIPs(hostA);
            }
        }
        return comp;
    }

    int compareWithoutInactive(Node hostA, Node hostB) {
        int comp = freeCapacityOf(hostB,  true).compare(freeCapacityOf(hostA, true));
        if (comp == 0) {
            comp = freeCapacityOf(hostB, true).compare(freeCapacityOf(hostA, true));
            if (comp == 0) {
                // If resources are equal - we want to assign to the one with the most IPaddresses free
                comp = freeIPs(hostB) - freeIPs(hostA);
            }
        }
        return comp;
    }

    /**
     * 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 hasCapacityWhenRetiredAndInactiveNodesAreGone(Node dockerHost, ResourceCapacity requestedCapacity) {
        return freeCapacityOf(dockerHost, true).hasCapacityFor(requestedCapacity) && freeIPs(dockerHost) > 0;
    }

    /**
     * Number of free (not allocated) IP addresses assigned to the dockerhost.
     */
    int freeIPs(Node dockerHost) {
        return findFreeIps(dockerHost, allNodes.asList()).size();
    }

    public ResourceCapacity getFreeCapacityTotal() {
        return allNodes.asList().stream()
                .filter(n -> n.type().equals(NodeType.host))
                .map(n -> freeCapacityOf(n, false))
                .reduce(new ResourceCapacity(), ResourceCapacity::add);
    }

    public ResourceCapacity getCapacityTotal() {
        return allNodes.asList().stream()
                .filter(n -> n.type().equals(NodeType.host))
                .map(ResourceCapacity::new)
                .reduce(new ResourceCapacity(), ResourceCapacity::add);
    }


    public int freeCapacityInFlavorEquivalence(Flavor flavor) {
        return allNodes.asList().stream()
                .filter(n -> n.type().equals(NodeType.host))
                .map(n -> canFitNumberOf(n, flavor))
                .reduce(0, (a, b) -> a + b);
    }

    public long getNofHostsAvailableFor(Flavor flavor) {
        return allNodes.asList().stream()
                .filter(n -> n.type().equals(NodeType.host))
                .filter(n -> hasCapacity(n, ResourceCapacity.of(flavor)))
                .count();
    }

    private int canFitNumberOf(Node node, Flavor flavor) {
        int capacityFactor = freeCapacityOf(node, false).freeCapacityInFlavorEquivalence(flavor);
        int ips = freeIPs(node);
        return Math.min(capacityFactor, ips);
    }

    /**
     * Calculate the remaining capacity for the dockerHost.
     * @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) {
        // Only hosts have free capacity
        if (!dockerHost.type().equals(NodeType.host)) return new ResourceCapacity();

        ResourceCapacity hostCapacity = new ResourceCapacity(dockerHost);
        for (Node container : allNodes.childNodes(dockerHost).asList()) {
            boolean isUsedCapacity = !(treatInactiveOrRetiredAsUnusedCapacity && isInactiveOrRetired(container));
            if (isUsedCapacity) {
                hostCapacity.subtract(container);
            }
        }
        return hostCapacity;
    }

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

    /**
     * Compare the additional ip addresses against the set of used addresses on
     * child nodes.
     */
    static Set<String> findFreeIps(Node dockerHost, List<Node> allNodes) {
        Set<String> freeIPAddresses = new HashSet<>(dockerHost.additionalIpAddresses());
        for (Node child : new NodeList(allNodes).childNodes(dockerHost).asList()) {
            freeIPAddresses.removeAll(child.ipAddresses());
        }
        return freeIPAddresses;
    }
}