com.yahoo.vespa.hosted.provision.provisioning.Preparer 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.
// 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.ApplicationId;
import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.lang.MutableInteger;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Optional;
/**
* Performs preparation of node activation changes for an application.
*
* @author bratseth
*/
class Preparer {
private final NodeRepository nodeRepository;
private final GroupPreparer groupPreparer;
private final Optional loadBalancerProvisioner;
private final int spareCount;
public Preparer(NodeRepository nodeRepository, int spareCount, Optional hostProvisioner,
HostResourcesCalculator hostResourcesCalculator, FlagSource flagSource,
Optional loadBalancerProvisioner) {
this.nodeRepository = nodeRepository;
this.spareCount = spareCount;
this.loadBalancerProvisioner = loadBalancerProvisioner;
this.groupPreparer = new GroupPreparer(nodeRepository, hostProvisioner, hostResourcesCalculator, flagSource);
}
/** Prepare all required resources for the given application and cluster */
public List prepare(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, int wantedGroups) {
var nodes = prepareNodes(application, cluster, requestedNodes, wantedGroups);
prepareLoadBalancer(application, cluster, requestedNodes);
return nodes;
}
/**
* Ensure sufficient nodes are reserved or active for the given application and cluster
*
* @return the list of nodes this cluster will have allocated if activated
*/
// Note: This operation may make persisted changes to the set of reserved and inactive nodes,
// but it may not change the set of active nodes, as the active nodes must stay in sync with the
// active config model which is changed on activate
private List prepareNodes(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, int wantedGroups) {
List surplusNodes = findNodesInRemovableGroups(application, cluster, wantedGroups);
MutableInteger highestIndex = new MutableInteger(findHighestIndex(application, cluster));
List acceptedNodes = new ArrayList<>();
for (int groupIndex = 0; groupIndex < wantedGroups; groupIndex++) {
ClusterSpec clusterGroup = cluster.with(Optional.of(ClusterSpec.Group.from(groupIndex)));
List accepted = groupPreparer.prepare(application, clusterGroup,
requestedNodes.fraction(wantedGroups), surplusNodes,
highestIndex, spareCount, wantedGroups);
replace(acceptedNodes, accepted);
}
moveToActiveGroup(surplusNodes, wantedGroups, cluster.group());
replace(acceptedNodes, retire(surplusNodes));
return acceptedNodes;
}
/** Prepare a load balancer for given application and cluster */
public void prepareLoadBalancer(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes) {
loadBalancerProvisioner.ifPresent(provisioner -> provisioner.prepare(application, cluster, requestedNodes));
}
/**
* Returns a list of the nodes which are
* in groups with index number above or equal the group count
*/
private List findNodesInRemovableGroups(ApplicationId application, ClusterSpec requestedCluster, int wantedGroups) {
List surplusNodes = new ArrayList<>(0);
for (Node node : nodeRepository.getNodes(application, Node.State.active)) {
ClusterSpec nodeCluster = node.allocation().get().membership().cluster();
if ( ! nodeCluster.id().equals(requestedCluster.id())) continue;
if ( ! nodeCluster.type().equals(requestedCluster.type())) continue;
if (nodeCluster.group().get().index() >= wantedGroups)
surplusNodes.add(node);
}
return surplusNodes;
}
/** Move nodes from unwanted groups to wanted groups to avoid lingering groups consisting of retired nodes */
private void moveToActiveGroup(List surplusNodes, int wantedGroups, Optional targetGroup) {
for (ListIterator i = surplusNodes.listIterator(); i.hasNext(); ) {
Node node = i.next();
ClusterMembership membership = node.allocation().get().membership();
ClusterSpec cluster = membership.cluster();
if (cluster.group().get().index() >= wantedGroups) {
ClusterSpec.Group newGroup = targetGroup.orElse(ClusterSpec.Group.from(0));
ClusterMembership newGroupMembership = membership.with(cluster.with(Optional.of(newGroup)));
i.set(node.with(node.allocation().get().with(newGroupMembership)));
}
}
}
/**
* Nodes are immutable so when changing attributes to the node we create a new instance.
*
* This method is used to both add new nodes and replaces old node references with the new references.
*/
private List replace(List list, List changed) {
list.removeAll(changed);
list.addAll(changed);
return list;
}
/**
* Returns the highest index number of all active and failed nodes in this cluster, or -1 if there are no nodes.
* We include failed nodes to avoid reusing the index of the failed node in the case where the failed node is the
* node with the highest index.
*/
private int findHighestIndex(ApplicationId application, ClusterSpec cluster) {
int highestIndex = -1;
for (Node node : nodeRepository.getNodes(application,
Node.State.active, Node.State.inactive, Node.State.parked, Node.State.failed)) {
ClusterSpec nodeCluster = node.allocation().get().membership().cluster();
if ( ! nodeCluster.id().equals(cluster.id())) continue;
if ( ! nodeCluster.type().equals(cluster.type())) continue;
highestIndex = Math.max(node.allocation().get().membership().index(), highestIndex);
}
return highestIndex;
}
/** Returns retired copies of the given nodes, unless they are removable */
private List retire(List nodes) {
List retired = new ArrayList<>(nodes.size());
for (Node node : nodes) {
if ( ! node.allocation().get().isRemovable())
retired.add(node.retire(Agent.application, nodeRepository.clock().instant()));
}
return retired;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy