All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.yahoo.vespa.hosted.provision.NodeList Maven / Gradle / Ivy
Go to download
Keeps track of node assignment in a multi-application setup.
// 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;
import com.yahoo.collections.AbstractFilteringList;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterResources;
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 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;
import static java.util.stream.Collectors.collectingAndThen;
/**
* A filterable node list. The result of a filter operation is immutable.
*
* @author bratseth
* @author mpolden
*/
public class NodeList extends AbstractFilteringList {
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> nodeCache = new AtomicReference<>(null);
private final AtomicReference> ipCache = new AtomicReference<>(null);
protected NodeList(List 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(String hostname) {
return get(hostname).map(NodeFamily::node);
}
/** Returns the subset of nodes which are retired */
public NodeList retired() {
return matching(node -> node.allocation().isPresent() && node.allocation().get().membership().retired());
}
/** Returns the subset of nodes that are being deprovisioned */
public NodeList deprovisioning() {
return matching(node -> node.status().wantToRetire() && node.status().wantToDeprovision());
}
/** Returns the subset of nodes that are being rebuilt */
public NodeList rebuilding(boolean soft) {
return matching(node -> {
if (soft) {
return !node.status().wantToRetire() && node.status().wantToRebuild();
}
return node.status().wantToRetire() && node.status().wantToRebuild();
});
}
/** Returns the subset of nodes which are removable */
public NodeList removable() {
return matching(node -> node.allocation().isPresent() && node.allocation().get().removable());
}
/** Returns the subset of nodes which are reusable immediately after removal */
public NodeList reusable() {
return matching(node -> node.allocation().isPresent() && node.allocation().get().reusable());
}
/** Returns the subset of nodes having exactly the given resources */
public NodeList resources(NodeResources resources) { return matching(node -> node.resources().equals(resources)); }
/** Returns the subset of nodes which have a replaceable root disk */
public NodeList replaceableRootDisk() {
return matching(node -> node.resources().storageType() == NodeResources.StorageType.remote);
}
/** Returns the subset of nodes which satisfy the given resources */
public NodeList satisfies(NodeResources resources) { return matching(node -> node.resources().satisfies(resources)); }
/** Returns the subset of nodes not in the given set */
public NodeList except(Set nodes) {
return matching(node -> ! nodes.contains(node));
}
/** Returns the subset of nodes excluding given node */
public NodeList except(Node node) {
return except(Set.of(node));
}
/** Returns the subset of nodes assigned to the given cluster types */
public NodeList type(ClusterSpec.Type... types) {
return matching(node -> node.allocation().isPresent() && Set.of(types).contains(node.allocation().get().membership().cluster().type()));
}
/** Returns the subset of nodes that run containers */
public NodeList container() {
return matching(node -> node.allocation().isPresent() && node.allocation().get().membership().cluster().type().isContainer());
}
/** Returns the subset of nodes that run a stateless service */
public NodeList stateless() {
return matching(node -> node.allocation().isPresent() && ! node.allocation().get().membership().cluster().isStateful());
}
/** Returns the subset of nodes that run a stateful service */
public NodeList stateful() {
return matching(node -> node.allocation().isPresent() && node.allocation().get().membership().cluster().isStateful());
}
/** Returns the subset of nodes that are currently changing their Vespa version */
public NodeList changingVersion() {
return matching(node -> node.status().vespaVersion().isPresent() &&
node.allocation().isPresent() &&
!node.status().vespaVersion().get().equals(node.allocation().get().membership().cluster().vespaVersion()));
}
/** Returns the subset of nodes with want to fail set to true */
public NodeList failing() {
return matching(node -> node.status().wantToFail());
}
/** Returns the subset of nodes that are currently changing their OS version to given version */
public NodeList changingOsVersionTo(Version version) {
return matching(node -> node.status().osVersion().changingTo(version));
}
/** Returns the subset of nodes that are currently changing their OS version */
public NodeList changingOsVersion() {
return matching(node -> node.status().osVersion().changing());
}
/** Returns a copy of this sorted by current OS version (lowest to highest) */
public NodeList byIncreasingOsVersion() {
return sortedBy(Comparator.comparing(node -> node.status()
.osVersion()
.current()
.orElse(Version.emptyVersion)));
}
/** Returns the subset of nodes that are currently on a lower version than the given version */
public NodeList osVersionIsBefore(Version version) {
return matching(node -> node.status().osVersion().isBefore(version));
}
/** Returns the subset of nodes that are currently on the given OS version */
public NodeList onOsVersion(Version version) {
return matching(node -> node.status().osVersion().matches(version));
}
/** Returns the subset of nodes assigned to the given cluster */
public NodeList cluster(ClusterSpec.Id cluster) {
return matching(node -> node.allocation().isPresent() && node.allocation().get().membership().cluster().id().equals(cluster));
}
/** Returns the subset of nodes owned by the given application */
public NodeList owner(ApplicationId application) {
return matching(node -> node.allocation().map(a -> a.owner().equals(application)).orElse(false));
}
/** Returns the subset of nodes allocated to a tester instance */
public NodeList tester() {
return matching(node -> node.allocation().isPresent() && node.allocation().get().owner().instance().isTester());
}
/** Returns the subset of nodes matching any of the given node type(s) */
public NodeList nodeType(NodeType first, NodeType... rest) {
if (rest.length == 0) {
return matching(node -> node.type() == first);
}
EnumSet nodeTypes = EnumSet.of(first, rest);
return matching(node -> nodeTypes.contains(node.type()));
}
/** Returns the subset of nodes of the host type */
public NodeList hosts() {
return nodeType(NodeType.host);
}
/** Returns the subset of nodes that are parents */
public NodeList parents() {
return matching(node -> node.parentHostname().isEmpty());
}
/** Returns the child nodes of the given parent node */
public NodeList childrenOf(String 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) {
return childrenOf(parent.hostname());
}
/** Returns the subset of nodes that are in any of the given state(s) */
public NodeList state(Node.State first, Node.State... rest) {
if (rest.length == 0) {
return matching(node -> node.state() == first);
}
return state(EnumSet.of(first, rest));
}
/** Returns the subset of nodes that are in any of the given state(s) */
public NodeList state(Set nodeStates) {
return matching(node -> nodeStates.contains(node.state()));
}
/** Returns the subset of nodes which have a record of being down */
public NodeList down() { return matching(node -> node.history().isDown()); }
/** Returns the subset of nodes which are being retired */
public NodeList retiring() {
return matching(node -> node.status().wantToRetire() || node.status().preferToRetire());
}
/** Returns the parent nodes of the given child nodes */
public NodeList parentsOf(NodeList children) {
return children.stream()
.map(this::parentOf)
.flatMap(Optional::stream)
.collect(collectingAndThen(Collectors.toList(), NodeList::copyOf));
}
/** Returns the parent node of the given child node */
public Optional parentOf(Node child) {
return child.parentHostname().flatMap(this::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 hostnames of nodes in this */
public Set hostnames() {
return stream().map(Node::hostname).collect(Collectors.toUnmodifiableSet());
}
/** Returns the stateful clusters on nodes in this */
public Set statefulClusters() {
return stream().filter(node -> node.allocation().isPresent() &&
node.allocation().get().membership().cluster().isStateful())
.map(node -> new ClusterId(node.allocation().get().owner(),
node.allocation().get().membership().cluster().id()))
.collect(Collectors.toUnmodifiableSet());
}
/**
* Returns the requested resources of the nodes in this
*
* @throws IllegalStateException if there are no nodes in this list, or they do not all belong to the same cluster
*/
public NodeResources requestedResources() {
ensureSingleCluster();
if (isEmpty()) throw new IllegalStateException("No nodes");
return first().get().allocation().get().requestedResources();
}
/**
* Returns the cluster spec of the nodes in this, without any group designation
*
* @throws IllegalStateException if there are no nodes in this list, or they do not all belong to the same cluster
*/
public ClusterSpec clusterSpec() {
ensureSingleCluster();
if (isEmpty()) throw new IllegalStateException("No nodes");
return first().get().allocation().get().membership().cluster().with(Optional.empty());
}
/**
* Returns the resources of the nodes of this.
*
* NOTE: If the nodes do not all have the same values of node resources, a random pick among those node resources
* will be returned.
*
* @throws IllegalStateException if the nodes in this do not all belong to the same cluster
*/
public ClusterResources toResources() {
ensureSingleCluster();
if (isEmpty()) return new ClusterResources(0, 0, NodeResources.unspecified());
return new ClusterResources(size(),
(int)stream().map(node -> node.allocation().get().membership().cluster().group().get())
.distinct()
.count(),
first().get().resources());
}
/** Returns the nodes that are allocated on an exclusive network switch within its cluster */
public NodeList onExclusiveSwitch(NodeList clusterHosts) {
ensureSingleCluster();
Map switchCount = clusterHosts.stream()
.flatMap(host -> host.switchHostname().stream())
.collect(Collectors.groupingBy(Function.identity(),
Collectors.counting()));
return matching(node -> {
Optional nodeOnSwitch = clusterHosts.parentOf(node);
if (node.parentHostname().isPresent()) {
if (nodeOnSwitch.isEmpty()) {
throw new IllegalArgumentException("Parent of " + node + ", " + node.parentHostname().get() +
", not found in given cluster hosts");
}
} else {
nodeOnSwitch = Optional.of(node);
}
Optional allocatedSwitch = nodeOnSwitch.flatMap(Node::switchHostname);
return allocatedSwitch.isEmpty() || switchCount.get(allocatedSwitch.get()) == 1;
});
}
/**
* 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 if dual-stack).
*/
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().ips().isEmpty()) {
Set allHostnames = cache().keySet();
return (int) host.ipConfig().pool().hostnames().stream()
.filter(hostname -> !allHostnames.contains(hostname.value()))
.count();
}
Set allIps = ipCache.updateAndGet(old ->
old != null ? old : stream().flatMap(node -> node.ipConfig().primary().stream())
.collect(Collectors.toUnmodifiableSet())
);
return (int) host.ipConfig().pool().ips().stream()
.filter(address -> !allIps.contains(address))
.count();
}
private void ensureSingleCluster() {
if (isEmpty()) return;
if (stream().anyMatch(node -> node.allocation().isEmpty()))
throw new IllegalStateException("Some nodes are not allocated to a cluster");
ClusterSpec firstNodeSpec = first().get().allocation().get().membership().cluster().with(Optional.empty());
if (stream().map(node -> node.allocation().get().membership().cluster().with(Optional.empty()))
.anyMatch(clusterSpec -> ! clusterSpec.id().equals(firstNodeSpec.id())))
throw new IllegalStateException("Nodes belong to multiple clusters");
}
/** Returns the nodes of this as a stream */
public Stream stream() { return asList().stream(); }
public static NodeList of(Node ... nodes) {
return copyOf(List.of(nodes));
}
public static NodeList copyOf(List nodes) {
if (nodes.isEmpty()) return EMPTY;
return new NodeList(nodes, false);
}
@Override
public String toString() {
return asList().toString();
}
@Override
public int hashCode() { return asList().hashCode(); }
@Override
public boolean equals(Object other) {
if (other == this) return true;
if ( ! (other instanceof NodeList)) return false;
return this.asList().equals(((NodeList) other).asList());
}
/** Get node family, by given hostname */
private Optional get(String hostname) {
return Optional.ofNullable(cache().get(hostname));
}
private Map cache() {
return nodeCache.updateAndGet((cached) -> {
if (cached != null)
return cached;
Map> children = new HashMap<>();
for (Node node : this)
node.parentHostname().ifPresent(parent -> children.computeIfAbsent(parent, __ -> new ArrayList<>()).add(node));
Map families = new HashMap<>();
for (Node node : this)
families.put(node.hostname(), new NodeFamily(node, children.getOrDefault(node.hostname(), List.of())));
return families;
});
}
/** A node and its children, if any */
private record NodeFamily(Node node, List children) {}
}