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

com.yahoo.vespa.hosted.provision.provisioning.Activator 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.ApplicationId;
import com.yahoo.config.provision.ApplicationTransaction;
import com.yahoo.config.provision.ClusterMembership;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.ClusterSpec.Type;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.ParentHostUnavailableException;
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.applications.Application;
import com.yahoo.vespa.hosted.provision.applications.ScalingEvent;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.Allocation;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
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;

/**
 * Performs activation of resources for an application. E.g. nodes or load balancers.
 *
 * @author bratseth
 */
class Activator {

    private final NodeRepository nodeRepository;
    private final Optional loadBalancerProvisioner;

    public Activator(NodeRepository nodeRepository, Optional loadBalancerProvisioner) {
        this.nodeRepository = nodeRepository;
        this.loadBalancerProvisioner = loadBalancerProvisioner;
    }

    /** Activate required resources for application guarded by given lock */
    public void activate(Collection hosts, long generation, ApplicationTransaction transaction) {
        NodeList newActive = activateNodes(hosts, generation, transaction);
        activateLoadBalancers(hosts, newActive, transaction);
    }

    /**
     * Add operations to activates nodes for an application to the given transaction.
     * The operations are not effective until the transaction is committed.
     * 

* Pre condition: The application has a possibly empty set of nodes in each of reserved and active. *

* Post condition: Nodes in reserved which are present in hosts are moved to active. * Nodes in active which are not present in hosts are moved to inactive. * * @param hosts the hosts to make the set of active nodes of this * @param generation the application config generation that is activated * @param transaction transaction with operations to commit together with any operations done within the repository, * while holding the node repository lock on this application * @return the nodes that will be active when transaction is committed */ private NodeList activateNodes(Collection hosts, long generation, ApplicationTransaction transaction) { Instant activationTime = nodeRepository.clock().instant(); // Use one timestamp for all activation changes ApplicationId application = transaction.application(); Set hostnames = hosts.stream().map(HostSpec::hostname).collect(Collectors.toSet()); NodeList allNodes = nodeRepository.nodes().list(); NodeList applicationNodes = allNodes.owner(application); NodeList reserved = applicationNodes.state(Node.State.reserved).matching(node -> hostnames.contains(node.hostname())); reserved = updatePortsFrom(hosts, reserved); nodeRepository.nodes().reserve(reserved.asList()); // Re-reserve nodes to avoid reservation expiry NodeList oldActive = applicationNodes.state(Node.State.active); // All nodes active now NodeList continuedActive = oldActive.matching(node -> hostnames.contains(node.hostname())); NodeList newActive = withHostInfo(continuedActive, hosts, activationTime).and(reserved); // All nodes that will be active when this is committed if ( ! newActive.hostnames().containsAll(hostnames)) throw new RuntimeException("Activation of " + application + " failed, could not find all requested hosts." + "\nRequested: " + hosts + "\nReserved: " + reserved.hostnames() + "\nActive: " + oldActive.hostnames()); validateParentHosts(application, allNodes, reserved); NodeList activeToRemove = oldActive.matching(node -> ! hostnames.contains(node.hostname())); remove(activeToRemove, transaction); // TODO: Pass activation time in this call and next line nodeRepository.nodes().activate(newActive.asList(), transaction); // activate also continued active to update node state rememberResourceChange(transaction, generation, activationTime, oldActive.not().retired(), newActive.not().retired()); unreserveParentsOf(reserved); return newActive; } private void remove(NodeList nodes, ApplicationTransaction transaction) { nodes = NodeList.copyOf(nodes.mapToList(Node::unretire)); // clear retire flag when moving to non-active state nodeRepository.nodes().deactivate(nodes.not().failing().asList(), transaction); nodeRepository.nodes().fail(nodes.failing().asList(), transaction); } private void rememberResourceChange(ApplicationTransaction transaction, long generation, Instant at, NodeList oldNodes, NodeList newNodes) { Optional application = nodeRepository.applications().get(transaction.application()); if (application.isEmpty()) return; // infrastructure app, hopefully :-| Map currentNodesByCluster = newNodes.groupingBy(node -> node.allocation().get().membership().cluster().id()); Application modified = application.get(); for (var clusterEntry : currentNodesByCluster.entrySet()) { var cluster = modified.cluster(clusterEntry.getKey()).get(); var previousResources = oldNodes.cluster(clusterEntry.getKey()).toResources(); var currentResources = clusterEntry.getValue().toResources(); if ( previousResources.nodeResources().isUnspecified() || ! previousResources.justNumbers().equals(currentResources.justNumbers())) { cluster = cluster.with(ScalingEvent.create(previousResources, currentResources, generation, at)); } if (cluster.target().resources().isPresent() && cluster.target().resources().get().justNumbers().equals(currentResources.justNumbers())) { cluster = cluster.withTarget(cluster.target().asIdeal(nodeRepository.clock().instant())); } if (cluster != modified.cluster(clusterEntry.getKey()).get()) modified = modified.with(cluster); } if (modified != application.get()) nodeRepository.applications().put(modified, transaction); } /** When a tenant node is activated on a host, we can open up that host for use by others */ private void unreserveParentsOf(NodeList nodes) { for (Node node : nodes) { if ( node.parentHostname().isEmpty()) continue; Optional parentNode = nodeRepository.nodes().node(node.parentHostname().get()); if (parentNode.isEmpty()) continue; if (parentNode.get().reservedTo().isEmpty()) continue; // Above is an optimization to avoid unnecessary locking - now repeat all conditions under lock Optional parent = nodeRepository.nodes().lockAndGet(node.parentHostname().get()); if (parent.isEmpty()) continue; try (var lock = parent.get()) { if (lock.node().reservedTo().isEmpty()) continue; nodeRepository.nodes().write(lock.node().withoutReservedTo(), lock); } } } /** Activate load balancers */ private void activateLoadBalancers(Collection hosts, NodeList newActive, ApplicationTransaction transaction) { loadBalancerProvisioner.ifPresent(provisioner -> provisioner.activate(allClustersOf(hosts), newActive, transaction)); } private static Set allClustersOf(Collection hosts) { return hosts.stream() .map(HostSpec::membership) .flatMap(Optional::stream) .map(ClusterMembership::cluster) .collect(Collectors.toUnmodifiableSet()); } static void validateParentHosts(ApplicationId application, NodeList allNodes, NodeList potentialChildren) { Set parentHostnames = potentialChildren.stream() .map(Node::parentHostname) .flatMap(Optional::stream) .collect(Collectors.toSet()); Set nonActiveHosts = allNodes.not().state(Node.State.active) .matching(node -> parentHostnames.contains(node.hostname())) .hostnames(); Set applicationParentHostnames = new HashSet<>(parentHostnames); applicationParentHostnames.removeIf(host -> potentialChildren.childrenOf(host).type(Type.combined, Type.container, Type.content).isEmpty()); Set nonActiveApplicationHosts = new HashSet<>(nonActiveHosts); nonActiveApplicationHosts.removeIf(host -> potentialChildren.childrenOf(host).type(Type.combined, Type.container, Type.content).isEmpty()); if (!nonActiveHosts.isEmpty()) { int numActiveApplication = applicationParentHostnames.size() - nonActiveApplicationHosts.size(); int numActiveAdmin = parentHostnames.size() - nonActiveHosts.size() - numActiveApplication; var messageBuilder = new StringBuilder() .append(numActiveApplication).append("/").append(applicationParentHostnames.size()) .append(" application hosts"); if (parentHostnames.size() > applicationParentHostnames.size()) messageBuilder.append(" and ") .append(numActiveAdmin).append("/").append(parentHostnames.size() - applicationParentHostnames.size()) .append(" admin hosts"); messageBuilder.append(" for ").append(application) .append(" have completed provisioning and bootstrapping, still waiting for "); if (nonActiveHosts.size() <= 5) { messageBuilder.append(nonActiveHosts.stream() .sorted() .collect(Collectors.joining(", "))); } else { messageBuilder.append(nonActiveHosts.stream() .sorted() .limit(3) .collect(Collectors.joining(", "))) .append(", and others"); } var message = messageBuilder.toString(); throw new ParentHostUnavailableException(message); } } /** Returns the input nodes with the changes resulting from applying the settings in hosts to the given list of nodes. */ private NodeList withHostInfo(NodeList nodes, Collection hosts, Instant at) { List updated = new ArrayList<>(); for (Node node : nodes) { HostSpec hostSpec = getHost(node.hostname(), hosts); node = hostSpec.membership().get().retired() ? node.retire(at) : node.unretire(); if (! hostSpec.advertisedResources().equals(node.resources())) // A resized node node = node.with(new Flavor(hostSpec.advertisedResources()), Agent.application, at); Allocation allocation = node.allocation().get() .with(hostSpec.membership().get()) .withRequestedResources(hostSpec.requestedResources() .orElse(node.resources())); if (hostSpec.networkPorts().isPresent()) allocation = allocation.withNetworkPorts(hostSpec.networkPorts().get()); node = node.with(allocation); updated.add(node); } return NodeList.copyOf(updated); } /** * Returns the input nodes with any port allocations from the hosts */ private NodeList updatePortsFrom(Collection hosts, NodeList nodes) { List updated = new ArrayList<>(); for (Node node : nodes) { HostSpec hostSpec = getHost(node.hostname(), hosts); Allocation allocation = node.allocation().get(); if (hostSpec.networkPorts().isPresent()) { allocation = allocation.withNetworkPorts(hostSpec.networkPorts().get()); node = node.with(allocation); } updated.add(node); } return NodeList.copyOf(updated); } private HostSpec getHost(String hostname, Collection fromHosts) { for (HostSpec host : fromHosts) if (host.hostname().equals(hostname)) return host; return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy