com.yahoo.vespa.hosted.provision.provisioning.Activator Maven / Gradle / Ivy
// 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.HostSpec;
import com.yahoo.transaction.Mutex;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Performs activation of nodes for an application
*
* @author bratseth
*/
class Activator {
private final NodeRepository nodeRepository;
public Activator(NodeRepository nodeRepository) {
this.nodeRepository = nodeRepository;
}
/**
* 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 transaction Transaction with operations to commit together with any operations done within the repository.
* @param application the application to allocate nodes for
* @param hosts the hosts to make the set of active nodes of this
*/
public void activate(ApplicationId application, Collection hosts, NestedTransaction transaction) {
try (Mutex lock = nodeRepository.lock(application)) {
Set hostnames = hosts.stream().map(HostSpec::hostname).collect(Collectors.toSet());
List reserved = nodeRepository.getNodes(application, Node.State.reserved);
List reservedToActivate = retainHostsInList(hostnames, reserved);
List active = nodeRepository.getNodes(application, Node.State.active);
List continuedActive = retainHostsInList(hostnames, active);
List allActive = new ArrayList<>(continuedActive);
allActive.addAll(reservedToActivate);
if ( ! containsAll(hostnames, allActive))
throw new IllegalArgumentException("Activation of " + application + " failed. " +
"Could not find all requested hosts." +
"\nRequested: " + hosts +
"\nReserved: " + toHostNames(reserved) +
"\nActive: " + toHostNames(active) +
"\nThis might happen if the time from reserving host to activation takes " +
"longer time than reservation expiry (the hosts will then no longer be reserved)");
List activeToRemove = removeHostsFromList(hostnames, active);
activeToRemove = activeToRemove.stream().map(Node::unretire).collect(Collectors.toList()); // only active nodes can be retired
nodeRepository.deactivate(activeToRemove, transaction);
nodeRepository.activate(updateFrom(hosts, continuedActive), transaction); // update active with any changes
nodeRepository.activate(reservedToActivate, transaction);
}
}
private List retainHostsInList(Set hosts, List nodes) {
return nodes.stream().filter(node -> hosts.contains(node.hostname())).collect(Collectors.toList());
}
private List removeHostsFromList(Set hosts, List nodes) {
return nodes.stream().filter(node -> ! hosts.contains(node.hostname())).collect(Collectors.toList());
}
private Set toHostNames(List nodes) {
return nodes.stream().map(Node::hostname).collect(Collectors.toSet());
}
private boolean containsAll(Set hosts, List nodes) {
Set notFoundHosts = new HashSet<>(hosts);
for (Node node : nodes)
notFoundHosts.remove(node.hostname());
return notFoundHosts.isEmpty();
}
/**
* Returns the input nodes with the changes resulting from applying the settings in hosts to the given list of nodes.
*/
private List updateFrom(Collection hosts, List nodes) {
List updated = new ArrayList<>();
for (Node node : nodes) {
HostSpec hostSpec = getHost(node.hostname(), hosts);
node = hostSpec.membership().get().retired() ? node.retire(nodeRepository.clock().instant()) : node.unretire();
node = node.with(node.allocation().get().with(hostSpec.membership().get()));
if (hostSpec.flavor().isPresent()) // Docker nodes may change flavor
node = node.with(hostSpec.flavor().get());
updated.add(node);
}
return updated;
}
private HostSpec getHost(String hostname, Collection fromHosts) {
for (HostSpec host : fromHosts)
if (host.hostname().equals(hostname))
return host;
return null;
}
}