diff options
author | Martin Polden <mpolden@mpolden.no> | 2023-01-13 11:04:34 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2023-01-13 15:42:01 +0100 |
commit | eaac041f0b63b253cb345534370e0fd8d20c7d54 (patch) | |
tree | 400d6d048a0f7fc1df5b0ee3ce7d2c09dd83b39f /node-repository | |
parent | 56f7ff45ceba9a4db0626b96e06c5ecb14d44ce9 (diff) |
Move parent-child cache to NodeList
Diffstat (limited to 'node-repository')
13 files changed, 150 insertions, 185 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java index 742b39e97c1..246a02aa441 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java @@ -9,13 +9,17 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.node.ClusterId; +import com.yahoo.vespa.hosted.provision.node.IP; +import java.util.ArrayList; import java.util.Comparator; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -30,13 +34,22 @@ import static java.util.stream.Collectors.collectingAndThen; */ public class NodeList extends AbstractFilteringList<Node, NodeList> { + private static final NodeList EMPTY = new NodeList(List.of(), false); + + /** + * A lazily populated cache of parent-child relationships. This exists to improve the speed of parent<->child + * lookup which is a frequent operation + */ + private final AtomicReference<Map<String, NodeFamily>> nodeCache = new AtomicReference<>(null); + private final AtomicReference<Set<String>> ipCache = new AtomicReference<>(null); + protected NodeList(List<Node> nodes, boolean negate) { super(nodes, negate, NodeList::new); } /** Returns the node with the given hostname from this list, or empty if it is not present */ public Optional<Node> node(String hostname) { - return matching(node -> node.hostname().equals(hostname)).first(); + return get(hostname).map(NodeFamily::node); } /** Returns the subset of nodes which are retired */ @@ -189,7 +202,9 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { /** Returns the child nodes of the given parent node */ public NodeList childrenOf(String hostname) { - return matching(node -> node.hasParent(hostname)); + NodeList children = get(hostname).map(NodeFamily::children).map(NodeList::copyOf).orElse(EMPTY); + // Fallback, in case the parent itself is not in this list + return children.isEmpty() ? matching(node -> node.hasParent(hostname)) : children; } public NodeList childrenOf(Node parent) { @@ -221,24 +236,21 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { public NodeList parentsOf(NodeList children) { return children.stream() .map(this::parentOf) - .filter(Optional::isPresent) .flatMap(Optional::stream) .collect(collectingAndThen(Collectors.toList(), NodeList::copyOf)); } + /** Returns the parent node of the given child node */ + public Optional<Node> parentOf(Node child) { + return child.parentHostname().flatMap(this::get).map(NodeFamily::node); + } + /** Returns the nodes contained in the group identified by given index */ public NodeList group(int index) { return matching(n -> n.allocation().isPresent() && n.allocation().get().membership().cluster().group().equals(Optional.of(ClusterSpec.Group.from(index)))); } - /** Returns the parent node of the given child node */ - public Optional<Node> parentOf(Node child) { - return child.parentHostname() - .flatMap(parentHostname -> stream().filter(node -> node.hostname().equals(parentHostname)) - .findFirst()); - } - /** Returns the hostnames of nodes in this */ public Set<String> hostnames() { return stream().map(Node::hostname).collect(Collectors.toUnmodifiableSet()); @@ -316,6 +328,31 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { }); } + /** + * Returns the number of unused IP addresses in the pool, assuming any and all unaccounted for hostnames + * in the pool are resolved to exactly 1 IP address (or 2 with {@link IP.IpAddresses.Protocol#dualStack}). + */ + public int eventuallyUnusedIpAddressCount(Node host) { + // The count in this method relies on the size of the IP address pool if that's non-empty, + // otherwise fall back to the address/hostname pool. + if (host.ipConfig().pool().ipSet().isEmpty()) { + Set<String> allHostnames = cache().keySet(); + return (int) host.ipConfig().pool().getAddressList().stream() + .filter(address -> !allHostnames.contains(address.hostname())) + .count(); + } + Set<String> allIps = ipCache.updateAndGet((old) -> { + if (old != null) { + return old; + } + return stream().flatMap(node -> node.ipConfig().primary().stream()) + .collect(Collectors.toUnmodifiableSet()); + }); + return (int) host.ipConfig().pool().ipSet().stream() + .filter(address -> !allIps.contains(address)) + .count(); + } + private void ensureSingleCluster() { if (isEmpty()) return; @@ -336,6 +373,7 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { } public static NodeList copyOf(List<Node> nodes) { + if (nodes.isEmpty()) return EMPTY; return new NodeList(nodes, false); } @@ -354,4 +392,36 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> { return this.asList().equals(((NodeList) other).asList()); } + /** Get node family, by given hostname */ + private Optional<NodeFamily> get(String hostname) { + return Optional.ofNullable(cache().get(hostname)); + } + + private Map<String, NodeFamily> cache() { + return nodeCache.updateAndGet((oldValue) -> { + if (oldValue != null) { + return oldValue; + } + Map<String, NodeFamily> newValue = new HashMap<>(); + for (var node : this) { + NodeFamily family; + if (node.parentHostname().isEmpty()) { + family = new NodeFamily(node, new ArrayList<>()); + for (var child : this) { + if (child.hasParent(node.hostname())) { + family.children.add(child); + } + } + } else { + family = new NodeFamily(node, List.of()); + } + newValue.put(node.hostname(), family); + } + return newValue; + }); + } + + /** A node and its children, if any */ + private record NodeFamily(Node node, List<Node> children) {} + } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodesAndHosts.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodesAndHosts.java deleted file mode 100644 index 6e7fc340231..00000000000 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodesAndHosts.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.provision; - -import com.yahoo.vespa.hosted.provision.node.IP; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Wraps a NodeList and builds a host to children mapping for faster access - * as that is done very frequently. - * - * @author baldersheim - */ -public class NodesAndHosts<NL extends NodeList> { - private final NL nodes; - private final Map<String, HostAndNodes> host2Nodes = new HashMap<>(); - private final Set<String> allPrimaryIps = new HashSet<>(); - private final Set<String> allHostNames; - - public static <L extends NodeList> NodesAndHosts<L> create(L nodes) { - return new NodesAndHosts<L>(nodes); - } - - private NodesAndHosts(NL nodes) { - this.nodes = nodes; - nodes.forEach(node -> allPrimaryIps.addAll(node.ipConfig().primary())); - allHostNames = nodes.stream().map(Node::hostname).collect(Collectors.toSet()); - nodes.forEach(node -> { - node.parentHostname().ifPresentOrElse( - parent -> host2Nodes.computeIfAbsent(parent, key -> new HostAndNodes()).add(node), - () -> host2Nodes.computeIfAbsent(node.hostname(), key -> new HostAndNodes()).setHost(node)); - }); - - } - - /// Return the NodeList used for construction - public NL nodes() { return nodes; } - - public NodeList childrenOf(Node host) { - return childrenOf(host.hostname()); - } - public NodeList childrenOf(String hostname) { - HostAndNodes hostAndNodes = host2Nodes.get(hostname); - return hostAndNodes != null ? NodeList.copyOf(hostAndNodes.children) : NodeList.of(); - } - - public Optional<Node> parentOf(Node node) { - if (node.parentHostname().isEmpty()) return Optional.empty(); - - HostAndNodes hostAndNodes = host2Nodes.get(node.parentHostname().get()); - return hostAndNodes != null ? Optional.ofNullable(hostAndNodes.host) : Optional.empty(); - } - - /** - * Returns the number of unused IP addresses in the pool, assuming any and all unaccounted for hostnames - * in the pool are resolved to exactly 1 IP address (or 2 with {@link IP.IpAddresses.Protocol#dualStack}). - */ - public int eventuallyUnusedIpAddressCount(Node host) { - // The count in this method relies on the size of the IP address pool if that's non-empty, - // otherwise fall back to the address/hostname pool. - return (int) (host.ipConfig().pool().ipSet().isEmpty() - ? host.ipConfig().pool().getAddressList().stream().filter(address -> !allHostNames.contains(address.hostname())).count() - : host.ipConfig().pool().ipSet().stream().filter(address -> !allPrimaryIps.contains(address)).count()); - } - - private static class HostAndNodes { - private Node host; - private final List<Node> children; - HostAndNodes() { - this.host = null; - children = new ArrayList<>(); - } - void setHost(Node host) { this.host = host; } - void add(Node child) { children.add(child); } - } -} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java index 5769f978089..1d5581b511d 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java @@ -22,7 +22,6 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeMutex; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.hosted.provision.provisioning.HostProvisioner; @@ -31,6 +30,7 @@ import com.yahoo.vespa.hosted.provision.provisioning.NodeCandidate; import com.yahoo.vespa.hosted.provision.provisioning.NodePrioritizer; import com.yahoo.vespa.hosted.provision.provisioning.NodeSpec; import com.yahoo.vespa.hosted.provision.provisioning.ProvisionedHost; + import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -222,8 +222,8 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer { ArrayList<Node> mutableNodes) { for (int clusterIndex = 0; clusterIndex < preprovisionCapacity.size(); ++clusterIndex) { ClusterCapacity clusterCapacity = preprovisionCapacity.get(clusterIndex); - NodesAndHosts<LockedNodeList> nodesAndHosts = NodesAndHosts.create(new LockedNodeList(mutableNodes, () -> {})); - List<Node> candidates = findCandidates(clusterCapacity, clusterIndex, nodesAndHosts); + LockedNodeList allNodes = new LockedNodeList(mutableNodes, () -> {}); + List<Node> candidates = findCandidates(clusterCapacity, clusterIndex, allNodes); int deficit = Math.max(0, clusterCapacity.count() - candidates.size()); if (deficit > 0) { return Optional.of(clusterCapacity.withCount(deficit)); @@ -236,7 +236,7 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer { return Optional.empty(); } - private List<Node> findCandidates(ClusterCapacity clusterCapacity, int clusterIndex, NodesAndHosts<LockedNodeList> nodesAndHosts) { + private List<Node> findCandidates(ClusterCapacity clusterCapacity, int clusterIndex, LockedNodeList allNodes) { NodeResources nodeResources = toNodeResources(clusterCapacity); // We'll allocate each ClusterCapacity as a unique cluster in a dummy application @@ -249,7 +249,7 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer { NodeSpec nodeSpec = NodeSpec.from(clusterCapacity.count(), nodeResources, false, true, nodeRepository().zone().cloud().account()); int wantedGroups = 1; - NodePrioritizer prioritizer = new NodePrioritizer(nodesAndHosts, applicationId, clusterSpec, nodeSpec, wantedGroups, + NodePrioritizer prioritizer = new NodePrioritizer(allNodes, applicationId, clusterSpec, nodeSpec, wantedGroups, true, nodeRepository().nameResolver(), nodeRepository().nodes(), nodeRepository().resourcesCalculator(), nodeRepository().spareCount(), nodeSpec.cloudAccount().isEnclave(nodeRepository().zone())); List<NodeCandidate> nodeCandidates = prioritizer.collect(List.of()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMover.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMover.java index 552db84748d..fcc8e904a47 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMover.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMover.java @@ -9,7 +9,6 @@ import com.yahoo.jdisc.Metric; 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.NodesAndHosts; import com.yahoo.vespa.hosted.provision.provisioning.HostCapacity; import java.time.Duration; @@ -40,7 +39,7 @@ public abstract class NodeMover<MOVE> extends NodeRepositoryMaintainer { } /** Returns a suggested move for given node */ - protected abstract MOVE suggestedMove(Node node, Node fromHost, Node toHost, NodesAndHosts<? extends NodeList> allNodes); + protected abstract MOVE suggestedMove(Node node, Node fromHost, Node toHost, NodeList allNodes); private static class HostWithResources { private final Node node; @@ -56,17 +55,17 @@ public abstract class NodeMover<MOVE> extends NodeRepositoryMaintainer { } /** Find the best possible move */ - protected final MOVE findBestMove(NodesAndHosts<? extends NodeList> allNodes) { + protected final MOVE findBestMove(NodeList allNodes) { HostCapacity capacity = new HostCapacity(allNodes, nodeRepository().resourcesCalculator()); MOVE bestMove = emptyMove; // Shuffle nodes to not get stuck if the chosen move is consistently discarded. Node moves happen through // a soft request to retire (preferToRetire), which node allocation can disregard - NodeList activeNodes = allNodes.nodes().nodeType(NodeType.tenant) + NodeList activeNodes = allNodes.nodeType(NodeType.tenant) .state(Node.State.active) .shuffle(random); - Set<Node> spares = capacity.findSpareHosts(allNodes.nodes().asList(), nodeRepository().spareCount()); + Set<Node> spares = capacity.findSpareHosts(allNodes.asList(), nodeRepository().spareCount()); List<HostWithResources> hostResources = new ArrayList<>(); - allNodes.nodes().matching(nodeRepository().nodes()::canAllocateTenantNodeTo).forEach(host -> hostResources.add(new HostWithResources(host, capacity.availableCapacityOf(host)))); + allNodes.matching(nodeRepository().nodes()::canAllocateTenantNodeTo).forEach(host -> hostResources.add(new HostWithResources(host, capacity.availableCapacityOf(host)))); for (Node node : activeNodes) { if (node.parentHostname().isEmpty()) continue; ApplicationId applicationId = node.allocation().get().owner(); 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 c853bfa2abe..3649f921480 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 @@ -8,7 +8,6 @@ import com.yahoo.jdisc.Metric; 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.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.HostCapacity; @@ -42,15 +41,15 @@ public class Rebalancer extends NodeMover<Rebalancer.Move> { if (nodeRepository().zone().system().isCd()) return 1.0; // CD tests assert on # of nodes, avoid rebalnacing as it make tests unstable // Work with an unlocked snapshot as this can take a long time and full consistency is not needed - NodesAndHosts<NodeList> allNodes = NodesAndHosts.create(nodeRepository().nodes().list()); + NodeList allNodes = nodeRepository().nodes().list(); updateSkewMetric(allNodes); - if ( ! zoneIsStable(allNodes.nodes())) return 1.0; + if ( ! zoneIsStable(allNodes)) return 1.0; findBestMove(allNodes).execute(true, Agent.Rebalancer, deployer, metric, nodeRepository()); return 1.0; } @Override - protected Move suggestedMove(Node node, Node fromHost, Node toHost, NodesAndHosts<? extends NodeList> allNodes) { + protected Move suggestedMove(Node node, Node fromHost, Node toHost, NodeList allNodes) { HostCapacity capacity = new HostCapacity(allNodes, nodeRepository().resourcesCalculator()); double skewReductionAtFromHost = skewReductionByRemoving(node, fromHost, capacity); double skewReductionAtToHost = skewReductionByAdding(node, toHost, capacity); @@ -65,11 +64,11 @@ public class Rebalancer extends NodeMover<Rebalancer.Move> { } /** We do this here rather than in MetricsReporter because it is expensive and frequent updates are unnecessary */ - private void updateSkewMetric(NodesAndHosts<? extends NodeList> allNodes) { + private void updateSkewMetric(NodeList allNodes) { HostCapacity capacity = new HostCapacity(allNodes, nodeRepository().resourcesCalculator()); double totalSkew = 0; int hostCount = 0; - for (Node host : allNodes.nodes().nodeType(NodeType.host).state(Node.State.active)) { + for (Node host : allNodes.nodeType(NodeType.host).state(Node.State.active)) { hostCount++; totalSkew += Node.skew(host.flavor().resources(), capacity.unusedCapacityOf(host)); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java index d5b0c3baf19..5ce88346178 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SpareCapacityMaintainer.java @@ -9,7 +9,6 @@ import com.yahoo.jdisc.Metric; 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.NodesAndHosts; import com.yahoo.vespa.hosted.provision.maintenance.MaintenanceDeployment.Move; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.HostCapacity; @@ -117,13 +116,13 @@ public class SpareCapacityMaintainer extends NodeRepositoryMaintainer { if (nodeWhichCantMove.isEmpty()) return List.of(); Node node = nodeWhichCantMove.get(); - NodesAndHosts<NodeList> allNodes = NodesAndHosts.create(nodeRepository().nodes().list()); + NodeList allNodes = nodeRepository().nodes().list(); // Allocation will assign the spareCount most empty nodes as "spares", which will not be allocated on // unless needed for node failing. Our goal here is to make room on these spares for the given node HostCapacity hostCapacity = new HostCapacity(allNodes, nodeRepository().resourcesCalculator()); - Set<Node> spareHosts = hostCapacity.findSpareHosts(allNodes.nodes().hosts().satisfies(node.resources()).asList(), + Set<Node> spareHosts = hostCapacity.findSpareHosts(allNodes.hosts().satisfies(node.resources()).asList(), nodeRepository().spareCount()); - List<Node> hosts = allNodes.nodes().hosts().except(spareHosts).asList(); + List<Node> hosts = allNodes.hosts().except(spareHosts).asList(); CapacitySolver capacitySolver = new CapacitySolver(hostCapacity, maxIterations); List<Move> shortestMitigation = null; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SwitchRebalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SwitchRebalancer.java index f01e8ecd301..9f009b47983 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SwitchRebalancer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/SwitchRebalancer.java @@ -8,7 +8,6 @@ import com.yahoo.jdisc.Metric; 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.NodesAndHosts; import com.yahoo.vespa.hosted.provision.maintenance.MaintenanceDeployment.Move; import com.yahoo.vespa.hosted.provision.node.Agent; @@ -40,8 +39,8 @@ public class SwitchRebalancer extends NodeMover<Move> { protected double maintain() { if (!nodeRepository().nodes().isWorking()) return 0.0; if (!nodeRepository().zone().environment().isProduction()) return 1.0; - NodesAndHosts<NodeList> allNodes = NodesAndHosts.create(nodeRepository().nodes().list()); // Lockless as strong consistency is not needed - if (!zoneIsStable(allNodes.nodes())) return 1.0; + NodeList allNodes = nodeRepository().nodes().list(); // Lockless as strong consistency is not needed + if (!zoneIsStable(allNodes)) return 1.0; Move bestMove = findBestMove(allNodes); if (!bestMove.isEmpty()) { @@ -53,9 +52,9 @@ public class SwitchRebalancer extends NodeMover<Move> { } @Override - protected Move suggestedMove(Node node, Node fromHost, Node toHost, NodesAndHosts<? extends NodeList> allNodes) { - NodeList clusterNodes = clusterOf(node, allNodes.nodes()); - NodeList clusterHosts = allNodes.nodes().parentsOf(clusterNodes); + protected Move suggestedMove(Node node, Node fromHost, Node toHost, NodeList allNodes) { + NodeList clusterNodes = clusterOf(node, allNodes); + NodeList clusterHosts = allNodes.parentsOf(clusterNodes); if (onExclusiveSwitch(node, clusterHosts)) return Move.empty(); if (!increasesExclusiveSwitches(clusterNodes, clusterHosts, toHost)) return Move.empty(); return new Move(node, fromHost, toHost); 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 46cc32c7156..07f9c439fe2 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 @@ -11,7 +11,6 @@ import com.yahoo.transaction.Mutex; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.provisioning.HostProvisioner.HostSharing; @@ -37,17 +36,8 @@ public class GroupPreparer { private final NodeRepository nodeRepository; private final Optional<HostProvisioner> hostProvisioner; - /** - * Contains list of prepared nodes and the NodesAndHost object to use for next prepare call. - */ - public static class PrepareResult { - public final List<Node> prepared; - public final NodesAndHosts<LockedNodeList> allNodesAndHosts; - PrepareResult(List<Node> prepared, NodesAndHosts<LockedNodeList> allNodesAndHosts) { - this.prepared = prepared; - this.allNodesAndHosts = allNodesAndHosts; - } - } + /** Contains list of prepared nodes and the {@link LockedNodeList} object to use for next prepare call */ + record PrepareResult(List<Node> prepared, LockedNodeList allNodes) {} public GroupPreparer(NodeRepository nodeRepository, Optional<HostProvisioner> hostProvisioner) { this.nodeRepository = nodeRepository; @@ -64,8 +54,8 @@ public class GroupPreparer { * This method will remove from this list if it finds it needs additional nodes * @param indices the next available node indices for this cluster. * This method will consume these when it allocates new nodes to the cluster. - * @param allNodesAndHosts list of all nodes and hosts. Use createNodesAndHostUnlocked to create param for - * first invocation. Then use previous PrepareResult.allNodesAndHosts for the following. + * @param allNodes list of all nodes and hosts. Use {@link #createUnlockedNodeList()} to create param for + * first invocation. Then use previous {@link PrepareResult#allNodes()} for the following. * @return the list of nodes this cluster group will have allocated if activated, and */ // Note: This operation may make persisted changes to the set of reserved and inactive nodes, @@ -73,37 +63,37 @@ public class GroupPreparer { // active config model which is changed on activate public PrepareResult prepare(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, List<Node> surplusActiveNodes, NodeIndices indices, int wantedGroups, - NodesAndHosts<LockedNodeList> allNodesAndHosts) { + LockedNodeList allNodes) { log.log(Level.FINE, () -> "Preparing " + cluster.type().name() + " " + cluster.id() + " with requested resources " + requestedNodes.resources().orElse(NodeResources.unspecified())); // Try preparing in memory without global unallocated lock. Most of the time there should be no changes, // and we can return nodes previously allocated. NodeAllocation probeAllocation = prepareAllocation(application, cluster, requestedNodes, surplusActiveNodes, - indices::probeNext, wantedGroups, allNodesAndHosts); + indices::probeNext, wantedGroups, allNodes); if (probeAllocation.fulfilledAndNoChanges()) { List<Node> acceptedNodes = probeAllocation.finalNodes(); surplusActiveNodes.removeAll(acceptedNodes); indices.commitProbe(); - return new PrepareResult(acceptedNodes, allNodesAndHosts); + return new PrepareResult(acceptedNodes, allNodes); } else { // There were some changes, so re-do the allocation with locks indices.resetProbe(); List<Node> prepared = prepareWithLocks(application, cluster, requestedNodes, surplusActiveNodes, indices, wantedGroups); - return new PrepareResult(prepared, createNodesAndHostUnlocked()); + return new PrepareResult(prepared, createUnlockedNodeList()); } } - // Use this to create allNodesAndHosts param to prepare method for first invocation of prepare - public NodesAndHosts<LockedNodeList> createNodesAndHostUnlocked() { return NodesAndHosts.create(nodeRepository.nodes().list(PROBE_LOCK)); } + // Use this to create allNodes param to prepare method for first invocation of prepare + LockedNodeList createUnlockedNodeList() { return nodeRepository.nodes().list(PROBE_LOCK); } /// Note that this will write to the node repo. private List<Node> prepareWithLocks(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, List<Node> surplusActiveNodes, NodeIndices indices, int wantedGroups) { try (Mutex lock = nodeRepository.applications().lock(application); Mutex allocationLock = nodeRepository.nodes().lockUnallocated()) { - NodesAndHosts<LockedNodeList> allNodesAndHosts = NodesAndHosts.create(nodeRepository.nodes().list(allocationLock)); + LockedNodeList allNodes = nodeRepository.nodes().list(allocationLock); NodeAllocation allocation = prepareAllocation(application, cluster, requestedNodes, surplusActiveNodes, - indices::next, wantedGroups, allNodesAndHosts); + indices::next, wantedGroups, allNodes); NodeType hostType = allocation.nodeType().hostType(); if (canProvisionDynamically(hostType) && allocation.hostDeficit().isPresent()) { HostSharing sharing = hostSharing(cluster, hostType); @@ -156,10 +146,10 @@ public class GroupPreparer { private NodeAllocation prepareAllocation(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, List<Node> surplusActiveNodes, Supplier<Integer> nextIndex, int wantedGroups, - NodesAndHosts<LockedNodeList> allNodesAndHosts) { + LockedNodeList allNodes) { - NodeAllocation allocation = new NodeAllocation(allNodesAndHosts, application, cluster, requestedNodes, nextIndex, nodeRepository); - NodePrioritizer prioritizer = new NodePrioritizer(allNodesAndHosts, + NodeAllocation allocation = new NodeAllocation(allNodes, application, cluster, requestedNodes, nextIndex, nodeRepository); + NodePrioritizer prioritizer = new NodePrioritizer(allNodes, application, cluster, requestedNodes, diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java index f29bf61149c..991bc22402d 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/HostCapacity.java @@ -5,7 +5,6 @@ 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 com.yahoo.vespa.hosted.provision.NodesAndHosts; import java.util.ArrayList; import java.util.List; @@ -23,18 +22,15 @@ import java.util.stream.Collectors; */ public class HostCapacity { - private final NodesAndHosts<? extends NodeList> allNodes; + private final NodeList allNodes; private final HostResourcesCalculator hostResourcesCalculator; public HostCapacity(NodeList allNodes, HostResourcesCalculator hostResourcesCalculator) { - this(NodesAndHosts.create(Objects.requireNonNull(allNodes, "allNodes must be non-null")), hostResourcesCalculator); - } - public HostCapacity(NodesAndHosts<? extends 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.nodes(); } + 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 @@ -97,9 +93,9 @@ public class HostCapacity { /** Returns the number of available IP addresses on given host */ int freeIps(Node host) { if (host.type() == NodeType.host) { - return (allNodes.eventuallyUnusedIpAddressCount(host)); + return allNodes.eventuallyUnusedIpAddressCount(host); } - return host.ipConfig().pool().findUnusedIpAddresses(allNodes.nodes()).size(); + return host.ipConfig().pool().findUnusedIpAddresses(allNodes).size(); } /** Returns the capacity of given host that is both free and usable */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java index 63d5db09380..bf3ad5f15fb 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java @@ -14,7 +14,6 @@ import com.yahoo.vespa.flags.PermanentFlags; 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.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.Allocation; @@ -43,7 +42,7 @@ class NodeAllocation { private static final Logger LOG = Logger.getLogger(NodeAllocation.class.getName()); /** List of all nodes in node-repository */ - private final NodesAndHosts<? extends NodeList> allNodesAndHosts; + private final NodeList allNodes; /** The application this list is for */ private final ApplicationId application; @@ -86,9 +85,9 @@ class NodeAllocation { private List<NodeCandidate> lastOffered; - NodeAllocation(NodesAndHosts<? extends NodeList> allNodesAndHosts, ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, + NodeAllocation(NodeList allNodes, ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, Supplier<Integer> nextIndex, NodeRepository nodeRepository) { - this.allNodesAndHosts = allNodesAndHosts; + this.allNodes = allNodes; this.application = application; this.cluster = cluster; this.requestedNodes = requestedNodes; @@ -214,7 +213,7 @@ class NodeAllocation { // In zones with shared hosts we require that if either of the nodes on the host requires exclusivity, // then all the nodes on the host must have the same owner - for (Node nodeOnHost : allNodesAndHosts.childrenOf(candidate.parentHostname().get())) { + for (Node nodeOnHost : allNodes.childrenOf(candidate.parentHostname().get())) { if (nodeOnHost.allocation().isEmpty()) continue; if (requestedNodes.isExclusive() || nodeOnHost.allocation().get().membership().cluster().isExclusive()) { if ( ! nodeOnHost.allocation().get().owner().equals(application)) return true; @@ -289,7 +288,7 @@ class NodeAllocation { } private Node resize(Node node) { - NodeResources hostResources = allNodesAndHosts.parentOf(node).get().flavor().resources(); + NodeResources hostResources = allNodes.parentOf(node).get().flavor().resources(); return node.with(new Flavor(requestedNodes.resources().get() .with(hostResources.diskSpeed()) .with(hostResources.storageType()) @@ -341,7 +340,7 @@ class NodeAllocation { if (hostType == NodeType.host) return nodeRepository.database().readProvisionIndices(count); // Infrastructure hosts have fixed indices, starting at 1 - Set<Integer> currentIndices = allNodesAndHosts.nodes().nodeType(hostType) + Set<Integer> currentIndices = allNodes.nodeType(hostType) .hostnames() .stream() // TODO(mpolden): Use cluster index instead of parsing hostname, once all @@ -441,7 +440,7 @@ class NodeAllocation { if (nodeType() == NodeType.tenant) return accepted; // Infrastructure nodes are always allocated by type. Count all nodes as accepted so that we never exceed // the wanted number of nodes for the type. - return allNodesAndHosts.nodes().nodeType(nodeType()).size(); + return allNodes.nodeType(nodeType()).size(); } /** Prefer to retire nodes we want the least */ 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 79d05ce5c97..5e95d36fcfb 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 @@ -7,7 +7,6 @@ import com.yahoo.config.provision.NodeType; import com.yahoo.vespa.hosted.provision.LockedNodeList; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; -import com.yahoo.vespa.hosted.provision.NodesAndHosts; import com.yahoo.vespa.hosted.provision.node.Nodes; import com.yahoo.vespa.hosted.provision.persistence.NameResolver; @@ -31,7 +30,7 @@ import java.util.stream.Collectors; public class NodePrioritizer { private final List<NodeCandidate> candidates = new ArrayList<>(); - private final NodesAndHosts<LockedNodeList> allNodesAndHosts; + private final LockedNodeList allNodes; private final HostCapacity capacity; private final NodeSpec requestedNodes; private final ApplicationId application; @@ -47,23 +46,23 @@ public class NodePrioritizer { private final Set<Node> spareHosts; private final boolean enclave; - public NodePrioritizer(NodesAndHosts<LockedNodeList> allNodesAndHosts, ApplicationId application, ClusterSpec clusterSpec, NodeSpec nodeSpec, + public NodePrioritizer(LockedNodeList allNodes, ApplicationId application, ClusterSpec clusterSpec, NodeSpec nodeSpec, int wantedGroups, boolean dynamicProvisioning, NameResolver nameResolver, Nodes nodes, HostResourcesCalculator hostResourcesCalculator, int spareCount, boolean enclave) { - this.allNodesAndHosts = allNodesAndHosts; - this.capacity = new HostCapacity(this.allNodesAndHosts, hostResourcesCalculator); + this.allNodes = allNodes; + this.capacity = new HostCapacity(this.allNodes, hostResourcesCalculator); this.requestedNodes = nodeSpec; this.clusterSpec = clusterSpec; this.application = application; this.dynamicProvisioning = dynamicProvisioning; this.spareHosts = dynamicProvisioning ? - capacity.findSpareHostsInDynamicallyProvisionedZones(this.allNodesAndHosts.nodes().asList()) : - capacity.findSpareHosts(this.allNodesAndHosts.nodes().asList(), spareCount); + capacity.findSpareHostsInDynamicallyProvisionedZones(this.allNodes.asList()) : + capacity.findSpareHosts(this.allNodes.asList(), spareCount); this.nameResolver = nameResolver; this.nodes = nodes; this.enclave = enclave; - NodeList nodesInCluster = this.allNodesAndHosts.nodes().owner(application).type(clusterSpec.type()).cluster(clusterSpec.id()); + NodeList nodesInCluster = this.allNodes.owner(application).type(clusterSpec.type()).cluster(clusterSpec.id()); NodeList nonRetiredNodesInCluster = nodesInCluster.not().retired(); long currentGroups = nonRetiredNodesInCluster.state(Node.State.active).stream() .flatMap(node -> node.allocation() @@ -139,7 +138,7 @@ public class NodePrioritizer { private void addCandidatesOnExistingHosts() { if ( !canAllocateNew) return; - for (Node host : allNodesAndHosts.nodes()) { + for (Node host : allNodes) { if ( ! nodes.canAllocateTenantNodeTo(host, dynamicProvisioning)) continue; if (host.reservedTo().isPresent() && !host.reservedTo().get().equals(application.tenant())) continue; if (host.reservedTo().isPresent() && application.instance().isTester()) continue; @@ -147,13 +146,13 @@ public class NodePrioritizer { if ( ! host.exclusiveToClusterType().map(clusterSpec.type()::equals).orElse(true)) continue; if (spareHosts.contains(host) && !canAllocateToSpareHosts) continue; if ( ! capacity.hasCapacity(host, requestedNodes.resources().get())) continue; - if ( ! allNodesAndHosts.childrenOf(host).owner(application).cluster(clusterSpec.id()).isEmpty()) continue; + if ( ! allNodes.childrenOf(host).owner(application).cluster(clusterSpec.id()).isEmpty()) continue; candidates.add(NodeCandidate.createNewChild(requestedNodes.resources().get(), capacity.availableCapacityOf(host), host, spareHosts.contains(host), - allNodesAndHosts.nodes(), + allNodes, nameResolver, !enclave)); } @@ -162,7 +161,7 @@ public class NodePrioritizer { /** Add existing nodes allocated to the application */ private void addApplicationNodes() { EnumSet<Node.State> legalStates = EnumSet.of(Node.State.active, Node.State.inactive, Node.State.reserved); - allNodesAndHosts.nodes().stream() + allNodes.stream() .filter(node -> node.type() == requestedNodes.type()) .filter(node -> legalStates.contains(node.state())) .filter(node -> node.allocation().isPresent()) @@ -175,7 +174,7 @@ public class NodePrioritizer { /** Add nodes already provisioned, but not allocated to any application */ private void addReadyNodes() { - allNodesAndHosts.nodes().stream() + allNodes.stream() .filter(node -> node.type() == requestedNodes.type()) .filter(node -> node.state() == Node.State.ready) .map(node -> candidateFrom(node, false)) @@ -185,7 +184,7 @@ public class NodePrioritizer { /** Create a candidate from given pre-existing node */ private NodeCandidate candidateFrom(Node node, boolean isSurplus) { - Optional<Node> optionalParent = allNodesAndHosts.parentOf(node); + Optional<Node> optionalParent = allNodes.parentOf(node); if (optionalParent.isPresent()) { Node parent = optionalParent.get(); return NodeCandidate.createChild(node, @@ -223,7 +222,7 @@ public class NodePrioritizer { */ private boolean canStillAllocate(Node node) { if (node.type() != NodeType.tenant || node.parentHostname().isEmpty()) return true; - Optional<Node> parent = allNodesAndHosts.parentOf(node); + Optional<Node> parent = allNodes.parentOf(node); return parent.isPresent() && nodes.canAllocateTenantNodeTo(parent.get(), dynamicProvisioning); } 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 139e8848ab1..b6c7324c75c 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 @@ -9,13 +9,11 @@ import com.yahoo.vespa.hosted.provision.LockedNodeList; 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.NodesAndHosts; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Optional; -import java.util.stream.Collectors; /** * Performs preparation of node activation changes for an application. @@ -58,8 +56,8 @@ class Preparer { // active config model which is changed on activate private List<Node> prepareNodes(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, int wantedGroups) { - NodesAndHosts<LockedNodeList> allNodesAndHosts = groupPreparer.createNodesAndHostUnlocked(); - NodeList appNodes = allNodesAndHosts.nodes().owner(application); + LockedNodeList allNodes = groupPreparer.createUnlockedNodeList(); + NodeList appNodes = allNodes.owner(application); List<Node> surplusNodes = findNodesInRemovableGroups(appNodes, cluster, wantedGroups); List<Integer> usedIndices = appNodes.cluster(cluster.id()).mapToList(node -> node.allocation().get().membership().index()); @@ -71,11 +69,11 @@ class Preparer { GroupPreparer.PrepareResult result = groupPreparer.prepare(application, clusterGroup, requestedNodes.fraction(wantedGroups), surplusNodes, indices, wantedGroups, - allNodesAndHosts); - allNodesAndHosts = result.allNodesAndHosts; // Might have changed - List<Node> accepted = result.prepared; + allNodes); + allNodes = result.allNodes(); // Might have changed + List<Node> accepted = result.prepared(); if (requestedNodes.rejectNonActiveParent()) { - NodeList activeHosts = allNodesAndHosts.nodes().state(Node.State.active).parents().nodeType(requestedNodes.type().hostType()); + NodeList activeHosts = allNodes.state(Node.State.active).parents().nodeType(requestedNodes.type().hostType()); accepted = accepted.stream() .filter(node -> node.parentHostname().isEmpty() || activeHosts.parentOf(node).isPresent()) .toList(); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java index 2327b40885f..23c2d0fc47a 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java @@ -547,7 +547,7 @@ public class DynamicAllocationTest { } private List<Node> findSpareCapacity(ProvisioningTester tester) { - NodeList nodes = tester.nodeRepository().nodes().list(State.values()); + NodeList nodes = tester.nodeRepository().nodes().list(); return nodes.nodeType(NodeType.host) .matching(host -> nodes.childrenOf(host).size() == 0) // Hosts without children .asList(); |