aboutsummaryrefslogtreecommitdiffstats
path: root/node-repository
diff options
context:
space:
mode:
authorValerij Fredriksen <valerij92@gmail.com>2019-06-20 19:03:27 +0200
committerValerij Fredriksen <valerij92@gmail.com>2019-06-20 19:03:27 +0200
commitda767a3f5d2f95d9b17e47446a06294f74522e0c (patch)
treec7a0b9a3744217651d0e4721f91e77085ab6fd86 /node-repository
parent86ddeed7f3d62f9703f100a013f7f4b39957f3b9 (diff)
Remove NodeRetirer
Diffstat (limited to 'node-repository')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java14
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java231
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetireIPv4OnlyNodes.java61
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicy.java22
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicyCache.java28
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicyList.java39
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java117
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java103
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTest.java176
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTester.java214
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetireIPv4OnlyNodesTest.java87
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCheckerTest.java229
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCountTest.java121
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json3
16 files changed, 3 insertions, 1448 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
index 684f6dbcd50..d7f41c4d8e2 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
@@ -49,7 +49,7 @@ import java.util.stream.Collectors;
*/
public class FailedExpirer extends Maintainer {
- private static final Logger log = Logger.getLogger(NodeRetirer.class.getName());
+ private static final Logger log = Logger.getLogger(FailedExpirer.class.getName());
private static final int maxAllowedFailures = 5; // Stop recycling nodes after this number of failures
private final NodeRepository nodeRepository;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
index b71c7c7ec81..0ecbfab2b99 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java
@@ -11,11 +11,6 @@ import com.yahoo.config.provision.Zone;
import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.hosted.provision.NodeRepository;
-import com.yahoo.vespa.hosted.provision.maintenance.retire.RetireIPv4OnlyNodes;
-import com.yahoo.vespa.hosted.provision.maintenance.retire.RetirementPolicy;
-import com.yahoo.vespa.hosted.provision.maintenance.retire.RetirementPolicyList;
-import com.yahoo.vespa.hosted.provision.provisioning.FlavorSpareChecker;
-import com.yahoo.vespa.hosted.provision.provisioning.FlavorSpareCount;
import com.yahoo.vespa.hosted.provision.provisioning.ProvisionServiceProvider;
import com.yahoo.vespa.orchestrator.Orchestrator;
import com.yahoo.vespa.service.monitor.ServiceMonitor;
@@ -46,7 +41,6 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
private final DirtyExpirer dirtyExpirer;
private final ProvisionedExpirer provisionedExpirer;
private final NodeRebooter nodeRebooter;
- private final NodeRetirer nodeRetirer;
private final MetricsReporter metricsReporter;
private final InfrastructureProvisioner infrastructureProvisioner;
private final Optional<LoadBalancerExpirer> loadBalancerExpirer;
@@ -90,11 +84,6 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
// The DuperModel is filled with infrastructure applications by the infrastructure provisioner, so explicitly run that now
infrastructureProvisioner.maintain();
-
- RetirementPolicy policy = new RetirementPolicyList(new RetireIPv4OnlyNodes(zone));
- FlavorSpareChecker flavorSpareChecker = new FlavorSpareChecker(
- NodeRetirer.SPARE_NODES_POLICY, FlavorSpareCount.constructFlavorSpareCountGraph(zone.nodeFlavors().get().getFlavors()));
- nodeRetirer = new NodeRetirer(nodeRepository, flavorSpareChecker, durationFromEnv("retire_interval").orElse(defaults.nodeRetirerInterval), deployer, policy);
}
@Override
@@ -108,7 +97,6 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
failedExpirer.deconstruct();
dirtyExpirer.deconstruct();
nodeRebooter.deconstruct();
- nodeRetirer.deconstruct();
provisionedExpirer.deconstruct();
metricsReporter.deconstruct();
infrastructureProvisioner.deconstruct();
@@ -152,7 +140,6 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
private final Duration dirtyExpiry;
private final Duration provisionedExpiry;
private final Duration rebootInterval;
- private final Duration nodeRetirerInterval;
private final Duration metricsInterval;
private final Duration retiredInterval;
private final Duration infrastructureProvisionInterval;
@@ -171,7 +158,6 @@ public class NodeRepositoryMaintenance extends AbstractComponent {
failedExpirerInterval = Duration.ofMinutes(10);
provisionedExpiry = Duration.ofHours(4);
rebootInterval = Duration.ofDays(30);
- nodeRetirerInterval = Duration.ofMinutes(30);
metricsInterval = Duration.ofMinutes(1);
infrastructureProvisionInterval = Duration.ofMinutes(1);
throttlePolicy = NodeFailer.ThrottlePolicy.hosted;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java
deleted file mode 100644
index 0245f2a92a3..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirer.java
+++ /dev/null
@@ -1,231 +0,0 @@
-// 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.maintenance;
-
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.Deployer;
-import com.yahoo.config.provision.Deployment;
-import com.yahoo.config.provision.Flavor;
-import com.yahoo.config.provision.NodeType;
-import com.yahoo.log.LogLevel;
-import com.yahoo.transaction.Mutex;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.NodeRepository;
-import com.yahoo.vespa.hosted.provision.maintenance.retire.RetirementPolicy;
-import com.yahoo.vespa.hosted.provision.node.Agent;
-import com.yahoo.vespa.hosted.provision.provisioning.FlavorSpareChecker;
-
-import java.time.Duration;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * Automatically retires ready and active nodes if they meet a certain criteria given by the {@link RetirementPolicy}
- * and if there are enough remaining nodes to both replace the retiring node as well as to keep enough in spare.
- *
- * @author freva
- */
-public class NodeRetirer extends Maintainer {
-
- public static final FlavorSpareChecker.SpareNodesPolicy SPARE_NODES_POLICY = flavorSpareCount ->
- flavorSpareCount.getNumReadyAmongReplacees() > 2;
-
- private static final long MAX_SIMULTANEOUS_RETIRES_PER_CLUSTER = 1;
- private static final Logger log = Logger.getLogger(NodeRetirer.class.getName());
-
- private final Deployer deployer;
- private final FlavorSpareChecker flavorSpareChecker;
- private final RetirementPolicy retirementPolicy;
-
- NodeRetirer(NodeRepository nodeRepository, FlavorSpareChecker flavorSpareChecker, Duration interval,
- Deployer deployer, RetirementPolicy retirementPolicy) {
- super(nodeRepository, interval);
- this.deployer = deployer;
- this.retirementPolicy = retirementPolicy;
- this.flavorSpareChecker = flavorSpareChecker;
- }
-
- @Override
- protected void maintain() {
- if (! retirementPolicy.isActive()) return;
-
- if (retireUnallocated()) {
- retireAllocated();
- }
- }
-
- /**
- * Retires unallocated nodes by moving them directly to parked.
- * Returns true iff all there are no unallocated nodes that match the retirement policy
- */
- boolean retireUnallocated() {
- try (Mutex lock = nodeRepository().lockAllocation()) {
- List<Node> allNodes = nodeRepository().getNodes(NodeType.tenant);
- Map<Flavor, Map<Node.State, Long>> numSpareNodesByFlavorByState = getNumberOfNodesByFlavorByNodeState(allNodes);
- flavorSpareChecker.updateReadyAndActiveCountsByFlavor(numSpareNodesByFlavorByState);
-
- long numFlavorsWithUnsuccessfullyRetiredNodes = allNodes.stream()
- .filter(node -> node.state() == Node.State.ready)
- .filter(node -> retirementPolicy.shouldRetire(node).isPresent())
- .collect(Collectors.groupingBy(
- Node::flavor,
- Collectors.toSet()))
- .entrySet().stream()
- .filter(entry -> {
- Set<Node> nodesThatShouldBeRetiredForFlavor = entry.getValue();
- for (Iterator<Node> iter = nodesThatShouldBeRetiredForFlavor.iterator(); iter.hasNext(); ) {
- Node nodeToRetire = iter.next();
- if (! flavorSpareChecker.canRetireUnallocatedNodeWithFlavor(nodeToRetire.flavor())) break;
-
- retirementPolicy.shouldRetire(nodeToRetire).ifPresent(reason -> {
- nodeRepository().write(nodeToRetire.with(nodeToRetire.status().withWantToDeprovision(true)), lock);
- nodeRepository().park(nodeToRetire.hostname(), false, Agent.NodeRetirer, reason);
- iter.remove();
- });
- }
-
- if (! nodesThatShouldBeRetiredForFlavor.isEmpty()) {
- String commaSeparatedHostnames = nodesThatShouldBeRetiredForFlavor.stream().map(Node::hostname)
- .collect(Collectors.joining(", "));
- log.info(String.format("Failed to retire %s, wanted to retire %d nodes (%s), but there are no spare nodes left.",
- entry.getKey(), nodesThatShouldBeRetiredForFlavor.size(), commaSeparatedHostnames));
- }
- return ! nodesThatShouldBeRetiredForFlavor.isEmpty();
- }).count();
-
- return numFlavorsWithUnsuccessfullyRetiredNodes == 0;
- }
- }
-
- void retireAllocated() {
- List<Node> allNodes = nodeRepository().getNodes(NodeType.tenant);
- List<ApplicationId> activeApplications = getActiveApplicationIds(allNodes);
- Map<Flavor, Map<Node.State, Long>> numSpareNodesByFlavorByState = getNumberOfNodesByFlavorByNodeState(allNodes);
- flavorSpareChecker.updateReadyAndActiveCountsByFlavor(numSpareNodesByFlavorByState);
-
- // Get all the nodes that we could retire along with their deployments
- Map<Deployment, Set<Node>> nodesToRetireByDeployment = new HashMap<>();
- for (ApplicationId applicationId : activeApplications) {
- Map<ClusterSpec.Id, Set<Node>> nodesByCluster = getNodesBelongingToApplication(allNodes, applicationId).stream()
- .collect(Collectors.groupingBy(
- node -> node.allocation().get().membership().cluster().id(),
- Collectors.toSet()));
- Map<ClusterSpec.Id, Set<Node>> retireableNodesByCluster = nodesByCluster.entrySet().stream()
- .collect(Collectors.toMap(
- Map.Entry::getKey,
- entry -> filterRetireableNodes(entry.getValue())));
- if (retireableNodesByCluster.values().stream().mapToInt(Set::size).sum() == 0) continue;
-
- Optional<Deployment> deployment = deployer.deployFromLocalActive(applicationId);
- if ( ! deployment.isPresent()) continue; // this will be done at another config server
-
- Set<Node> replaceableNodes = retireableNodesByCluster.entrySet().stream()
- .flatMap(entry -> entry.getValue().stream()
- .filter(node -> flavorSpareChecker.canRetireAllocatedNodeWithFlavor(node.flavor()))
- .limit(getNumberNodesAllowToRetireForCluster(nodesByCluster.get(entry.getKey()), MAX_SIMULTANEOUS_RETIRES_PER_CLUSTER)))
- .collect(Collectors.toSet());
- if (! replaceableNodes.isEmpty()) nodesToRetireByDeployment.put(deployment.get(), replaceableNodes);
- }
-
- nodesToRetireByDeployment.forEach(((deployment, nodes) -> {
- ApplicationId app = nodes.iterator().next().allocation().get().owner();
- Set<Node> nodesToRetire;
-
- // While under application lock, get up-to-date node, and make sure that the state and the owner of the
- // node has not changed in the meantime, mutate the up-to-date node (so to not overwrite other fields
- // that may have changed) with wantToRetire and wantToDeprovision.
- try (Mutex lock = nodeRepository().lock(app)) {
- nodesToRetire = nodes.stream()
- .map(node ->
- nodeRepository().getNode(node.hostname())
- .filter(upToDateNode -> node.state() == Node.State.active)
- .filter(upToDateNode -> node.allocation().get().owner().equals(upToDateNode.allocation().get().owner())))
- .flatMap(node -> node.map(Stream::of).orElseGet(Stream::empty))
- .collect(Collectors.toSet());
-
- nodesToRetire.forEach(node ->
- retirementPolicy.shouldRetire(node).ifPresent(reason -> {
- log.info("Setting wantToRetire and wantToDeprovision for host " + node.hostname() +
- " with flavor " + node.flavor().name() +
- " allocated to " + node.allocation().get().owner() + ". Reason: " + reason);
-
- Node updatedNode = node.with(node.status()
- .withWantToRetire(true)
- .withWantToDeprovision(true));
- nodeRepository().write(updatedNode, lock);
- }));
- }
-
- // This takes a while, so do it outside of the application lock
- if (! nodesToRetire.isEmpty()) {
- try {
- deployment.activate();
- } catch (Exception e) {
- log.log(LogLevel.INFO, "Failed to redeploy " + app.serializedForm() + ", will be redeployed later by application maintainer", e);
- }
- }
- }));
- }
-
- private List<Node> getNodesBelongingToApplication(Collection<Node> allNodes, ApplicationId applicationId) {
- return allNodes.stream()
- .filter(node -> node.allocation().isPresent())
- .filter(node -> node.allocation().get().owner().equals(applicationId))
- .collect(Collectors.toList());
- }
-
- /**
- * Returns a list of ApplicationIds sorted by number of active nodes the application has allocated to it
- */
- List<ApplicationId> getActiveApplicationIds(Collection<Node> nodes) {
- return nodes.stream()
- .filter(node -> node.state() == Node.State.active)
- .collect(Collectors.groupingBy(
- node -> node.allocation().get().owner(),
- Collectors.counting()))
- .entrySet().stream()
- .sorted((c1, c2) -> c2.getValue().compareTo(c1.getValue()))
- .map(Map.Entry::getKey)
- .collect(Collectors.toList());
- }
-
- /**
- * @param nodes Collection of nodes that are considered for retirement
- * @return Set of nodes that all should eventually be retired
- */
- Set<Node> filterRetireableNodes(Collection<Node> nodes) {
- return nodes.stream()
- .filter(node -> node.state() == Node.State.active)
- .filter(node -> !node.status().wantToRetire())
- .filter(node -> retirementPolicy.shouldRetire(node).isPresent())
- .collect(Collectors.toSet());
- }
-
- /**
- * @param clusterNodes All the nodes allocated to an application belonging to a single cluster
- * @return number of nodes we can safely start retiring
- */
- long getNumberNodesAllowToRetireForCluster(Collection<Node> clusterNodes, long maxSimultaneousRetires) {
- long numNodesInWantToRetire = clusterNodes.stream()
- .filter(node -> node.status().wantToRetire())
- .filter(node -> node.state() != Node.State.parked)
- .count();
- return Math.max(0, maxSimultaneousRetires - numNodesInWantToRetire);
- }
-
- private Map<Flavor, Map<Node.State, Long>> getNumberOfNodesByFlavorByNodeState(Collection<Node> allNodes) {
- return allNodes.stream()
- .collect(Collectors.groupingBy(
- Node::flavor,
- Collectors.groupingBy(Node::state, Collectors.counting())));
- }
-
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetireIPv4OnlyNodes.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetireIPv4OnlyNodes.java
deleted file mode 100644
index 6562a89c2d6..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetireIPv4OnlyNodes.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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.maintenance.retire;
-
-import com.google.common.net.InetAddresses;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.Flavor;
-import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.Zone;
-import com.yahoo.vespa.hosted.provision.Node;
-
-import java.net.Inet4Address;
-import java.util.Optional;
-
-/**
- * @author freva
- */
-public class RetireIPv4OnlyNodes implements RetirementPolicy {
- private final Zone zone;
-
- public RetireIPv4OnlyNodes(Zone zone) {
- this.zone = zone;
- }
-
- @Override
- public boolean isActive() {
- if(zone.system() == SystemName.cd) {
- return zone.environment() == Environment.dev || zone.environment() == Environment.prod;
- }
-
- if (zone.system() == SystemName.main) {
- if (zone.region().equals(RegionName.from("us-east-3"))) {
- return zone.environment() == Environment.perf || zone.environment() == Environment.prod;
- } else if (zone.region().equals(RegionName.from("us-west-1"))) {
- return zone.environment() == Environment.prod;
- } else if (zone.region().equals(RegionName.from("us-central-1"))) {
- return zone.environment() == Environment.prod;
- } else if (zone.region().equals(RegionName.from("ap-southeast-1"))) {
- return zone.environment() == Environment.prod;
- } else if (zone.region().equals(RegionName.from("ap-northeast-1"))) {
- return zone.environment() == Environment.prod;
- } else if (zone.region().equals(RegionName.from("ap-northeast-2"))) {
- return zone.environment() == Environment.prod;
- } else if (zone.region().equals(RegionName.from("eu-west-1"))) {
- return zone.environment() == Environment.prod;
- }
- }
-
- return false;
- }
-
- @Override
- public Optional<String> shouldRetire(Node node) {
- if (node.flavor().getType() == Flavor.Type.VIRTUAL_MACHINE) return Optional.empty();
- boolean shouldRetire = node.ipAddresses().stream()
- .map(InetAddresses::forString)
- .allMatch(address -> address instanceof Inet4Address);
-
- return shouldRetire ? Optional.of("Node is IPv4-only") : Optional.empty();
- }
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicy.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicy.java
deleted file mode 100644
index ca0419f11c3..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicy.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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.maintenance.retire;
-
-import com.yahoo.vespa.hosted.provision.Node;
-
-import java.util.Optional;
-
-/**
- * @author freva
- */
-public interface RetirementPolicy {
-
- /**
- * Returns whether the policy is currently active. NodeRetirer ask every time before executing.
- */
- boolean isActive();
-
- /**
- * Returns reason for retiring the node, empty if node should not be retired
- */
- Optional<String> shouldRetire(Node node);
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicyCache.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicyCache.java
deleted file mode 100644
index c112daadcc9..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicyCache.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.maintenance.retire;
-
-import com.yahoo.vespa.hosted.provision.Node;
-
-import java.util.Optional;
-
-/**
- * @author freva
- */
-public class RetirementPolicyCache implements RetirementPolicy {
- private final RetirementPolicy policy;
- private final boolean isActiveCached;
-
- RetirementPolicyCache(RetirementPolicy policy) {
- this.policy = policy;
- this.isActiveCached = policy.isActive();
- }
-
- @Override
- public boolean isActive() {
- return isActiveCached;
- }
-
- public Optional<String> shouldRetire(Node node) {
- return policy.shouldRetire(node);
- }
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicyList.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicyList.java
deleted file mode 100644
index 5f4d887b029..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetirementPolicyList.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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.maintenance.retire;
-
-import com.yahoo.vespa.hosted.provision.Node;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * @author freva
- */
-public class RetirementPolicyList implements RetirementPolicy {
- private final List<RetirementPolicy> retirementPolicies;
-
- public RetirementPolicyList(RetirementPolicy... retirementPolicies) {
- this.retirementPolicies = Stream.of(retirementPolicies)
- .map(RetirementPolicyCache::new)
- .collect(Collectors.toList());
- }
-
- @Override
- public boolean isActive() {
- return retirementPolicies.stream().anyMatch(RetirementPolicy::isActive);
- }
-
- @Override
- public Optional<String> shouldRetire(Node node) {
- List<String> retirementReasons = retirementPolicies.stream()
- .filter(RetirementPolicy::isActive)
- .map(retirementPolicy -> retirementPolicy.shouldRetire(node))
- .flatMap(reason -> reason.map(Stream::of).orElse(Stream.empty()))
- .collect(Collectors.toList());
-
- return retirementReasons.isEmpty() ? Optional.empty() :
- Optional.of("[" + String.join(", ", retirementReasons) + "]");
- }
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
index 424889caf72..45fb1e050a7 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java
@@ -385,7 +385,7 @@ public class NodeSerializer {
case "application" : return Agent.application;
case "system" : return Agent.system;
case "operator" : return Agent.operator;
- case "NodeRetirer" : return Agent.NodeRetirer;
+ case "NodeRetirer" : return Agent.system; // TODO: Remove after 7.67
case "NodeFailer" : return Agent.NodeFailer;
}
throw new IllegalArgumentException("Unknown node event agent '" + eventAgentField.asString() + "'");
@@ -395,7 +395,7 @@ public class NodeSerializer {
case application : return "application";
case system : return "system";
case operator : return "operator";
- case NodeRetirer : return "NodeRetirer";
+ case NodeRetirer : return "system"; // TODO: Remove after 7.67
case NodeFailer : return "NodeFailer";
}
throw new IllegalArgumentException("Serialized form of '" + agent + "' not defined");
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java
deleted file mode 100644
index 5f81fed2a04..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareChecker.java
+++ /dev/null
@@ -1,117 +0,0 @@
-// 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.Flavor;
-import com.yahoo.vespa.hosted.provision.Node;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * This class helps answer the question if there are enough nodes to retire a node with flavor f by:
- * <ul>
- * <li>Finding all the possible flavors that the replacement node could end up on</li>
- * <li>Making sure that regardless of which flavor it ends up on, there is still enough spare nodes
- * to handle at unexpected node failures.</li>
- * </ul>
- * <p>
- * Definitions:
- * <ul>
- * <li>Wanted flavor: The flavor that is the node prefers, for example by specifying in services.xml</li>
- * <li>Node-repo flavor: The flavor that the node actually has (Either the wanted flavor or a flavor that transitively
- * replaces the wanted flavor)</li>
- * <li>Replacee flavor: Flavor x is replacee of y iff x transitively replaces y</li>
- * <li>Immediate replacee flavor: Flavor x is an immediate replacee of flavor y iff x directly replaces y.</li>
- * </ul>
- *
- * @author freva
- */
-public class FlavorSpareChecker {
-
- private final SpareNodesPolicy spareNodesPolicy;
- private final Map<Flavor, FlavorSpareCount> spareCountByFlavor;
-
- public FlavorSpareChecker(SpareNodesPolicy spareNodesPolicy, Map<Flavor, FlavorSpareCount> spareCountByFlavor) {
- this.spareNodesPolicy = spareNodesPolicy;
- this.spareCountByFlavor = spareCountByFlavor;
- }
-
- public void updateReadyAndActiveCountsByFlavor(Map<Flavor, Map<Node.State, Long>> numberOfNodesByFlavorByState) {
- spareCountByFlavor.forEach((flavor, flavorSpareCount) -> {
- Map<Node.State, Long> numberOfNodesByState = numberOfNodesByFlavorByState.getOrDefault(flavor, Collections.emptyMap());
- flavorSpareCount.updateReadyAndActiveCounts(
- numberOfNodesByState.getOrDefault(Node.State.ready, 0L),
- numberOfNodesByState.getOrDefault(Node.State.active, 0L));
- });
- }
-
- public boolean canRetireAllocatedNodeWithFlavor(Flavor flavor) {
- Set<FlavorSpareCount> possibleNewFlavors = findPossibleReplacementFlavorFor(spareCountByFlavor.get(flavor));
- possibleNewFlavors.forEach(FlavorSpareCount::decrementNumberOfReady);
- return !possibleNewFlavors.isEmpty();
- }
-
- public boolean canRetireUnallocatedNodeWithFlavor(Flavor flavor) {
- FlavorSpareCount flavorSpareCount = spareCountByFlavor.get(flavor);
- if (flavorSpareCount.hasReady() && spareNodesPolicy.hasSpare(flavorSpareCount)) {
- flavorSpareCount.decrementNumberOfReady();
- return true;
- }
-
- return false;
- }
-
-
- /**
- * Returns a set of possible new flavors that can replace this flavor given current node allocation.
- * If the set is empty, there are not enough spare nodes to safely retire this flavor.
- * <p>
- * The algorithm is:
- * for all possible wanted flavor, check:
- * <ul>
- * <li>1: Sum of ready nodes of flavor f and all replacee flavors of f is &gt; reserved (set by {@link SpareNodesPolicy}</li>
- * <li>2a: Number of ready nodes of flavor f is &gt; 0</li>
- * <li>2b: Verify 1 &amp; 2 for all immediate replacee of f, f_i, where sum of ready nodes of f_i and all
- * replacee flavors of f_i is &gt; 0</li>
- * </ul>
- * Only 2a OR 2b need to be satisfied.
- */
- private Set<FlavorSpareCount> findPossibleReplacementFlavorFor(FlavorSpareCount flavorSpareCount) {
- Set<FlavorSpareCount> possibleReplacementFlavors = new HashSet<>();
- for (FlavorSpareCount possibleWantedFlavor : flavorSpareCount.getPossibleWantedFlavors()) {
- Set<FlavorSpareCount> replacementFlavors = verifyReplacementConditions(possibleWantedFlavor);
- if (replacementFlavors.isEmpty()) return Collections.emptySet();
- else possibleReplacementFlavors.addAll(replacementFlavors);
- }
-
- return possibleReplacementFlavors;
- }
-
- private Set<FlavorSpareCount> verifyReplacementConditions(FlavorSpareCount flavorSpareCount) {
- Set<FlavorSpareCount> possibleReplacementFlavors = new HashSet<>();
- // Breaks condition 1, end
- if (! spareNodesPolicy.hasSpare(flavorSpareCount)) return Collections.emptySet();
-
- // Condition 2a
- if (flavorSpareCount.hasReady()) {
- possibleReplacementFlavors.add(flavorSpareCount);
-
- // Condition 2b
- } else {
- for (FlavorSpareCount possibleNewFlavor : flavorSpareCount.getImmediateReplacees()) {
- if (possibleNewFlavor.getNumReadyAmongReplacees() == 0) continue;
-
- Set<FlavorSpareCount> replacementFlavors = verifyReplacementConditions(possibleNewFlavor);
- if (replacementFlavors.isEmpty()) return Collections.emptySet();
- else possibleReplacementFlavors.addAll(replacementFlavors);
- }
- }
- return possibleReplacementFlavors;
- }
-
- public interface SpareNodesPolicy {
- boolean hasSpare(FlavorSpareCount flavorSpareCount);
- }
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java
deleted file mode 100644
index 217f4999bfb..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCount.java
+++ /dev/null
@@ -1,103 +0,0 @@
-// 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.Flavor;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Keeps track of number of ready and active nodes for a flavor and its replaces neighbors
- *
- * @author freva
- */
-public class FlavorSpareCount {
-
- private final Flavor flavor;
- private Set<FlavorSpareCount> possibleWantedFlavors;
- private Set<FlavorSpareCount> immediateReplacees;
- private long numReady;
- private long numActive;
-
- public static Map<Flavor, FlavorSpareCount> constructFlavorSpareCountGraph(List<Flavor> flavors) {
- Map<Flavor, FlavorSpareCount> spareCountByFlavor = new HashMap<>();
- Map<Flavor, Set<Flavor>> immediateReplaceeFlavorsByFlavor = new HashMap<>();
- for (Flavor flavor : flavors) {
- for (Flavor replaces : flavor.replaces()) {
- if (! immediateReplaceeFlavorsByFlavor.containsKey(replaces)) {
- immediateReplaceeFlavorsByFlavor.put(replaces, new HashSet<>());
- }
- immediateReplaceeFlavorsByFlavor.get(replaces).add(flavor);
- }
-
- spareCountByFlavor.put(flavor, new FlavorSpareCount(flavor));
- }
-
- spareCountByFlavor.forEach((flavor, flavorSpareCount) -> {
- flavorSpareCount.immediateReplacees = ! immediateReplaceeFlavorsByFlavor.containsKey(flavor) ?
- Collections.emptySet() :
- immediateReplaceeFlavorsByFlavor.get(flavor).stream().map(spareCountByFlavor::get).collect(Collectors.toSet());
- flavorSpareCount.possibleWantedFlavors = recursiveReplacements(flavor, new HashSet<>())
- .stream().map(spareCountByFlavor::get).collect(Collectors.toSet());
- });
-
- return spareCountByFlavor;
- }
-
- private static Set<Flavor> recursiveReplacements(Flavor flavor, Set<Flavor> replacements) {
- replacements.add(flavor);
- for (Flavor replaces : flavor.replaces()) {
- recursiveReplacements(replaces, replacements);
- }
-
- return replacements;
- }
-
- private FlavorSpareCount(Flavor flavor) {
- this.flavor = flavor;
- }
-
- public Flavor getFlavor() {
- return flavor;
- }
-
- void updateReadyAndActiveCounts(long numReady, long numActive) {
- this.numReady = numReady;
- this.numActive = numActive;
- }
-
- boolean hasReady() {
- return numReady > 0;
- }
-
- public long getNumReadyAmongReplacees() {
- long sumReadyNodes = numReady;
- for (FlavorSpareCount replacee : immediateReplacees) {
- sumReadyNodes += replacee.getNumReadyAmongReplacees();
- }
-
- return sumReadyNodes;
- }
-
- Set<FlavorSpareCount> getPossibleWantedFlavors() {
- return possibleWantedFlavors;
- }
-
- Set<FlavorSpareCount> getImmediateReplacees() {
- return immediateReplacees;
- }
-
- void decrementNumberOfReady() {
- numReady--;
- }
-
- @Override
- public String toString() {
- return flavor.name() + " has " + numReady + " ready nodes and " + numActive + " active nodes";
- }
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTest.java
deleted file mode 100644
index 93e44164f40..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTest.java
+++ /dev/null
@@ -1,176 +0,0 @@
-// 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.maintenance;
-
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.NodeFlavors;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.maintenance.retire.RetirementPolicy;
-import com.yahoo.vespa.hosted.provision.node.Agent;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-/**
- * @author freva
- */
-public class NodeRetirerTest {
-
- private NodeRetirerTester tester;
- private NodeRetirer retirer;
- private final RetirementPolicy policy = mock(RetirementPolicy.class);
-
- @Before
- public void setup() {
- doAnswer(invoke -> {
- boolean shouldRetire = ((Node) invoke.getArguments()[0]).ipAddresses().equals(Collections.singleton("::1"));
- return shouldRetire ? Optional.of("Some reason") : Optional.empty();
- }).when(policy).shouldRetire(any(Node.class));
- when(policy.isActive()).thenReturn(true);
-
- NodeFlavors nodeFlavors = NodeRetirerTester.makeFlavors(5);
- tester = new NodeRetirerTester(nodeFlavors);
- retirer = spy(tester.makeNodeRetirer(policy));
-
- tester.createReadyNodesByFlavor(21, 42, 27, 15, 8);
- tester.deployApp("vespa", "calendar", new int[]{3}, new int[]{7});
- tester.deployApp("vespa", "notes", new int[]{0}, new int[]{3});
- tester.deployApp("sports", "results", new int[]{0}, new int[]{6});
- tester.deployApp("search", "images", new int[]{3}, new int[]{4});
- tester.deployApp("search", "videos", new int[]{2}, new int[]{2});
- tester.deployApp("tester", "my-app", new int[]{1, 2}, new int[]{4, 6});
- }
-
- @Test
- public void testRetireUnallocated() {
- tester.assertCountsForStateByFlavor(Node.State.ready, 12, 38, 19, 4, 8);
- tester.setNumberAllowedUnallocatedRetirementsPerFlavor(6, 30, 15, 2, 4);
- assertFalse(retirer.retireUnallocated());
- tester.assertCountsForStateByFlavor(Node.State.parked, 6, 30, 15, 2, 4);
-
- tester.assertCountsForStateByFlavor(Node.State.ready, 6, 8, 4, 2, 4);
- tester.setNumberAllowedUnallocatedRetirementsPerFlavor(10, 20, 5, 5, 4);
- assertTrue(retirer.retireUnallocated());
- tester.assertCountsForStateByFlavor(Node.State.parked, 12, 38, 19, 4, 8);
-
- tester.nodeRepository.getNodes().forEach(node ->
- assertEquals(node.status().wantToDeprovision(), node.state() == Node.State.parked));
- }
-
- @Test
- public void testRetireAllocated() {
- // Update IP addresses on ready nodes so that when they are deployed to, we wont retire them
- tester.nodeRepository.getNodes(Node.State.ready)
- .forEach(node -> tester.nodeRepository.write(node.with(node.ipConfig().with(Set.of("::2"))), () -> {}));
-
- tester.assertCountsForStateByFlavor(Node.State.active, 9, 4, 8, 11, -1);
-
- tester.setNumberAllowedAllocatedRetirementsPerFlavor(3, 2, 4, 2);
- retirer.retireAllocated();
- tester.assertParkedCountsByApplication(-1, -1, -1, -1, -1, -1); // Nodes should be in retired, but not yet parked
- tester.assertRetiringCountsByApplication(1, 1, 1, 1, 1, 2);
-
- // Until the nodes we set to retire are fully retired and moved to parked, we should not attempt to retire any other nodes
- retirer.retireAllocated();
- retirer.retireAllocated();
- tester.assertRetiringCountsByApplication(1, 1, 1, 1, 1, 2);
-
- tester.iterateMaintainers();
- tester.assertParkedCountsByApplication(1, 1, 1, 1, 1, 2);
-
- // We can retire 1 more of flavor-0, 1 more of flavor-1, 2 more of flavor-2:
- // app 6 has the most nodes, so it gets to retire flavor-1 and flavor-2
- // app 3 is the largest that is on flavor-0, so it gets the last node
- // app 5 is gets the last node with flavor-2
- retirer.retireAllocated();
- tester.iterateMaintainers();
- tester.assertParkedCountsByApplication(1, 1, 2, 1, 2, 4);
-
- // No more retirements are possible
- retirer.retireAllocated();
- tester.iterateMaintainers();
- tester.assertParkedCountsByApplication(1, 1, 2, 1, 2, 4);
-
- tester.nodeRepository.getNodes().forEach(node ->
- assertEquals(node.status().wantToDeprovision(), node.state() == Node.State.parked));
- }
-
- @Test
- public void testGetActiveApplicationIds() {
- List<String> expectedOrder = Arrays.asList(
- "tester.my-app", "vespa.calendar", "sports.results", "search.images", "vespa.notes", "search.videos");
- List<String> actualOrder = retirer.getActiveApplicationIds(tester.nodeRepository.getNodes()).stream()
- .map(applicationId -> applicationId.toShortString().replace(":default", ""))
- .collect(Collectors.toList());
- assertEquals(expectedOrder, actualOrder);
- }
-
- @Test
- public void testGetRetireableNodesForApplication() {
- ApplicationId app = new ApplicationId.Builder().tenant("vespa").applicationName("calendar").build();
-
- List<Node> nodes = tester.nodeRepository.getNodes(app);
- Set<String> actual = retirer.filterRetireableNodes(nodes).stream().map(Node::hostname).collect(Collectors.toSet());
- Set<String> expected = nodes.stream().map(Node::hostname).collect(Collectors.toSet());
- assertEquals(expected, actual);
-
- Node nodeWantToRetire = tester.nodeRepository.getNode("host3.test.yahoo.com").orElseThrow(RuntimeException::new);
- tester.nodeRepository.write(nodeWantToRetire.with(nodeWantToRetire.status().withWantToRetire(true)), () -> {});
- Node nodeToFail = tester.nodeRepository.getNode("host5.test.yahoo.com").orElseThrow(RuntimeException::new);
- tester.nodeRepository.fail(nodeToFail.hostname(), Agent.system, "Failed for unit testing");
- Node nodeToUpdate = tester.nodeRepository.getNode("host8.test.yahoo.com").orElseThrow(RuntimeException::new);
- tester.nodeRepository.write(nodeToUpdate.with(nodeToUpdate.ipConfig().with(Set.of("::2"))), () -> {});
-
- nodes = tester.nodeRepository.getNodes(app);
- Set<String> excluded = Stream.of(nodeWantToRetire, nodeToFail, nodeToUpdate).map(Node::hostname).collect(Collectors.toSet());
- Set<String> actualAfterUpdates = retirer.filterRetireableNodes(nodes).stream().map(Node::hostname).collect(Collectors.toSet());
- Set<String> expectedAfterUpdates = nodes.stream().map(Node::hostname).filter(node -> !excluded.contains(node)).collect(Collectors.toSet());
- assertEquals(expectedAfterUpdates, actualAfterUpdates);
- }
-
- @Test
- public void testGetNumberNodesAllowToRetireForCluster() {
- ApplicationId app = new ApplicationId.Builder().tenant("vespa").applicationName("calendar").build();
- long actualAllActive = retirer.getNumberNodesAllowToRetireForCluster(tester.nodeRepository.getNodes(app), 2);
- assertEquals(2, actualAllActive);
-
- // Lets put 3 random nodes in wantToRetire
- List<Node> nodesToRetire = tester.nodeRepository.getNodes(app).stream().limit(3).collect(Collectors.toList());
- nodesToRetire.forEach(node -> tester.nodeRepository.write(node.with(node.status().withWantToRetire(true)), () -> {}));
- long actualOneWantToRetire = retirer.getNumberNodesAllowToRetireForCluster(tester.nodeRepository.getNodes(app), 2);
- assertEquals(0, actualOneWantToRetire);
-
- // Now 2 of those finish retiring and go to parked
- nodesToRetire.stream().limit(2).forEach(node ->
- tester.nodeRepository.park(node.hostname(), false, Agent.system, "Parked for unit testing"));
- long actualOneRetired = retirer.getNumberNodesAllowToRetireForCluster(tester.nodeRepository.getNodes(app), 2);
- assertEquals(1, actualOneRetired);
- }
-
- @Test
- public void inactivePolicyDoesNothingTest() {
- when(policy.isActive()).thenReturn(false);
- retirer.maintain();
-
- verify(retirer, never()).retireUnallocated();
- verify(retirer, never()).retireAllocated();
- }
-
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTester.java
deleted file mode 100644
index 832c2fc512b..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRetirerTester.java
+++ /dev/null
@@ -1,214 +0,0 @@
-// 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.maintenance;
-
-import com.yahoo.component.Version;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Capacity;
-import com.yahoo.config.provision.ClusterSpec;
-import com.yahoo.config.provision.DockerImage;
-import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.Flavor;
-import com.yahoo.config.provision.NodeFlavors;
-import com.yahoo.config.provision.NodeType;
-import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.Zone;
-import com.yahoo.test.ManualClock;
-import com.yahoo.vespa.curator.Curator;
-import com.yahoo.vespa.curator.mock.MockCurator;
-import com.yahoo.vespa.flags.InMemoryFlagSource;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.NodeRepository;
-import com.yahoo.vespa.hosted.provision.maintenance.retire.RetirementPolicy;
-import com.yahoo.vespa.hosted.provision.node.Agent;
-import com.yahoo.vespa.hosted.provision.node.IP;
-import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder;
-import com.yahoo.vespa.hosted.provision.provisioning.FlavorSpareChecker;
-import com.yahoo.vespa.hosted.provision.provisioning.NodeRepositoryProvisioner;
-import com.yahoo.vespa.hosted.provision.testutils.MockDeployer;
-import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver;
-import com.yahoo.vespa.hosted.provision.testutils.MockProvisionServiceProvider;
-import com.yahoo.vespa.orchestrator.OrchestrationException;
-import com.yahoo.vespa.orchestrator.Orchestrator;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.LongStream;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-/**
- * @author freva
- */
-public class NodeRetirerTester {
- public static final Zone zone = new Zone(Environment.prod, RegionName.from("us-east"));
-
- // Components with state
- public final ManualClock clock = new ManualClock();
- public final NodeRepository nodeRepository;
- private final FlavorSpareChecker flavorSpareChecker = mock(FlavorSpareChecker.class);
- private final MockDeployer deployer;
- private final List<Flavor> flavors;
-
- // Use LinkedHashMap to keep order in which applications were deployed
- private final Map<ApplicationId, MockDeployer.ApplicationContext> apps = new LinkedHashMap<>();
-
- private final Orchestrator orchestrator = mock(Orchestrator.class);
- private RetiredExpirer retiredExpirer;
- private InactiveExpirer inactiveExpirer;
- private int nextNodeId = 0;
-
- NodeRetirerTester(NodeFlavors nodeFlavors) {
- Curator curator = new MockCurator();
- nodeRepository = new NodeRepository(nodeFlavors, curator, clock, zone, new MockNameResolver().mockAnyLookup(),
- DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa"), true);
- NodeRepositoryProvisioner provisioner = new NodeRepositoryProvisioner(nodeRepository, nodeFlavors, zone, new MockProvisionServiceProvider(), new InMemoryFlagSource());
- deployer = new MockDeployer(provisioner, clock, apps);
- flavors = nodeFlavors.getFlavors().stream().sorted(Comparator.comparing(Flavor::name)).collect(Collectors.toList());
-
- try {
- doThrow(new RuntimeException()).when(orchestrator).acquirePermissionToRemove(any());
- } catch (OrchestrationException e) {
- e.printStackTrace();
- }
- }
-
- NodeRetirer makeNodeRetirer(RetirementPolicy policy) {
- return new NodeRetirer(nodeRepository, flavorSpareChecker, Duration.ofDays(1), deployer, policy);
- }
-
- void createReadyNodesByFlavor(int... nums) {
- List<Node> nodes = new ArrayList<>();
- for (int i = 0; i < nums.length; i++) {
- Flavor flavor = flavors.get(i);
- for (int j = 0; j < nums[i]; j++) {
- int id = nextNodeId++;
- nodes.add(nodeRepository.createNode("node" + id, "host" + id + ".test.yahoo.com",
- new IP.Config(Set.of("::1"), Set.of()), Optional.empty(),
- Optional.empty(), flavor, NodeType.tenant));
- }
- }
-
- nodes = nodeRepository.addNodes(nodes);
- nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
- nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
- }
-
- void deployApp(String tenantName, String applicationName, int[] flavorIds, int[] numNodes) {
- final ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default");
- final List<MockDeployer.ClusterContext> clusterContexts = new ArrayList<>();
-
- for (int i = 0; i < flavorIds.length; i++) {
- Flavor flavor = flavors.get(flavorIds[i]);
- ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("cluster-" + i), Version.fromString("6.99"), false, Collections.emptySet());
- Capacity capacity = Capacity.fromNodeCount(numNodes[i], Optional.of(flavor.name()), false, true);
- // If the number of node the app wants is divisible by 2, make it into 2 groups, otherwise as 1
- int numGroups = numNodes[i] % 2 == 0 ? 2 : 1;
- clusterContexts.add(new MockDeployer.ClusterContext(applicationId, cluster, capacity, numGroups));
- }
-
- apps.put(applicationId, new MockDeployer.ApplicationContext(applicationId, clusterContexts));
- deployer.deployFromLocalActive(applicationId, Duration.ZERO).get().activate();
- }
-
- void iterateMaintainers() {
- if (retiredExpirer == null) {
- retiredExpirer = new RetiredExpirer(nodeRepository, orchestrator, deployer, clock, Duration.ofDays(30), Duration.ofMinutes(10));
- inactiveExpirer = new InactiveExpirer(nodeRepository, clock, Duration.ofMinutes(10));
- }
-
- clock.advance(Duration.ofMinutes(11));
- retiredExpirer.maintain();
-
- clock.advance(Duration.ofMinutes(11));
- inactiveExpirer.maintain();
- }
-
- void setNumberAllowedUnallocatedRetirementsPerFlavor(int... numAllowed) {
- for (int i = 0; i < numAllowed.length; i++) {
- Boolean[] responses = new Boolean[numAllowed[i]];
- Arrays.fill(responses, true);
- responses[responses.length - 1 ] = false;
- when(flavorSpareChecker.canRetireUnallocatedNodeWithFlavor(eq(flavors.get(i)))).thenReturn(true, responses);
- }
- }
-
- void setNumberAllowedAllocatedRetirementsPerFlavor(int... numAllowed) {
- for (int i = 0; i < numAllowed.length; i++) {
- Boolean[] responses = new Boolean[numAllowed[i]];
- Arrays.fill(responses, true);
- responses[responses.length - 1] = false;
- when(flavorSpareChecker.canRetireAllocatedNodeWithFlavor(eq(flavors.get(i)))).thenReturn(true, responses);
- }
- }
-
- void assertCountsForStateByFlavor(Node.State state, long... nums) {
- Map<Flavor, Long> expected = expectedCountsByFlavor(nums);
- Map<Flavor, Long> actual = nodeRepository.getNodes(state).stream()
- .collect(Collectors.groupingBy(Node::flavor, Collectors.counting()));
- assertEquals(expected, actual);
- }
-
- void assertParkedCountsByApplication(long... nums) {
- // Nodes lose allocation when parked, so just do a sum.
- long expected = LongStream.of(nums).filter(value -> value > 0L).sum();
- long actual = (long) nodeRepository.getNodes(Node.State.parked).size();
- assertEquals(expected, actual);
- }
-
- // Nodes that are being retired or about to be retired (wantToRetire flag set), but are not yet fully retired (not parked)
- void assertRetiringCountsByApplication(long... nums) {
- Map<ApplicationId, Long> expected = expectedCountsByApplication(nums);
- Map<ApplicationId, Long> actual = nodeRepository.getNodes().stream()
- .filter(node -> node.status().wantToRetire())
- .filter(node -> node.allocation().isPresent())
- .filter(node -> node.allocation().get().membership().retired())
- .filter(node -> node.state() != Node.State.parked)
- .collect(Collectors.groupingBy(node -> node.allocation().get().owner(), Collectors.counting()));
- assertEquals(expected, actual);
- }
-
- private Map<Flavor, Long> expectedCountsByFlavor(long... nums) {
- Map<Flavor, Long> countsByFlavor = new HashMap<>();
- for (int i = 0; i < nums.length; i++) {
- if (nums[i] < 0) continue;
- Flavor flavor = flavors.get(i);
- countsByFlavor.put(flavor, nums[i]);
- }
- return countsByFlavor;
- }
-
- private Map<ApplicationId, Long> expectedCountsByApplication(long... nums) {
- Map<ApplicationId, Long> countsByApplicationId = new HashMap<>();
- Iterator<ApplicationId> iterator = apps.keySet().iterator();
- for (int i = 0; iterator.hasNext(); i++) {
- ApplicationId applicationId = iterator.next();
- if (nums[i] < 0) continue;
- countsByApplicationId.put(applicationId, nums[i]);
- }
- return countsByApplicationId;
- }
-
- static NodeFlavors makeFlavors(int numFlavors) {
- FlavorConfigBuilder flavorConfigBuilder = new FlavorConfigBuilder();
- for (int i = 0; i < numFlavors; i++) {
- flavorConfigBuilder.addFlavor("flavor-" + i, 1. /* cpu*/, 3. /* mem GB*/, 2. /*disk GB*/, Flavor.Type.BARE_METAL);
- }
- return new NodeFlavors(flavorConfigBuilder.build());
- }
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetireIPv4OnlyNodesTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetireIPv4OnlyNodesTest.java
deleted file mode 100644
index b40d091b346..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/retire/RetireIPv4OnlyNodesTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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.maintenance.retire;
-
-import com.yahoo.config.provision.Flavor;
-import com.yahoo.config.provision.NodeType;
-import com.yahoo.vespa.hosted.provision.Node;
-import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author freva
- */
-public class RetireIPv4OnlyNodesTest {
- private final RetireIPv4OnlyNodes policy = new RetireIPv4OnlyNodes(null);
- private final List<Flavor> nodeFlavors = initFlavors();
-
- @Test
- public void testSingleIPv4Address() {
- Node node = createNodeWithAddresses("127.0.0.1");
- assertTrue(policy.shouldRetire(node).isPresent());
- }
-
- @Test
- public void testSingleIPv6Address() {
- Node node = createNodeWithAddresses("::1");
- assertFalse(policy.shouldRetire(node).isPresent());
- }
-
- @Test
- public void testMultipleIPv4Address() {
- Node node = createNodeWithAddresses("127.0.0.1", "10.0.0.1", "192.168.0.1");
- assertTrue(policy.shouldRetire(node).isPresent());
- }
-
- @Test
- public void testMultipleIPv6Address() {
- Node node = createNodeWithAddresses("::1", "::2", "1234:5678:90ab::cdef");
- assertFalse(policy.shouldRetire(node).isPresent());
- }
-
- @Test
- public void testCombinationAddress() {
- Node node = createNodeWithAddresses("127.0.0.1", "::1", "10.0.0.1", "::2");
- assertFalse(policy.shouldRetire(node).isPresent());
- }
-
- @Test
- public void testNeverRetireVMs() {
- Node node = createVMWithAddresses("127.0.0.1", "10.0.0.1", "192.168.0.1");
- assertFalse(policy.shouldRetire(node).isPresent());
-
- node = createNodeWithAddresses("::1", "::2", "1234:5678:90ab::cdef");
- assertFalse(policy.shouldRetire(node).isPresent());
-
- node = createNodeWithAddresses("127.0.0.1", "::1", "10.0.0.1", "::2");
- assertFalse(policy.shouldRetire(node).isPresent());
- }
-
- private Node createNodeWithAddresses(String... addresses) {
- Set<String> ipAddresses = Arrays.stream(addresses).collect(Collectors.toSet());
- return Node.create("openstackid", ipAddresses, Collections.emptySet(), "hostname", Optional.empty(),
- Optional.empty(), nodeFlavors.get(0), NodeType.tenant);
- }
-
- private Node createVMWithAddresses(String... addresses) {
- Set<String> ipAddresses = Arrays.stream(addresses).collect(Collectors.toSet());
- return Node.create("openstackid", ipAddresses, Collections.emptySet(), "hostname", Optional.empty(),
- Optional.empty(), nodeFlavors.get(1), NodeType.tenant);
- }
-
- private List<Flavor> initFlavors() {
- FlavorConfigBuilder flavorConfigBuilder = new FlavorConfigBuilder();
- flavorConfigBuilder.addFlavor("default", 1. /* cpu*/, 3. /* mem GB*/, 2. /*disk GB*/, Flavor.Type.BARE_METAL);
- flavorConfigBuilder.addFlavor("vm", 1. /* cpu*/, 3. /* mem GB*/, 2. /*disk GB*/, Flavor.Type.VIRTUAL_MACHINE);
- return flavorConfigBuilder.build().flavor().stream().map(Flavor::new).collect(Collectors.toList());
- }
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCheckerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCheckerTest.java
deleted file mode 100644
index c60e1d94cac..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCheckerTest.java
+++ /dev/null
@@ -1,229 +0,0 @@
-// 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.Flavor;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-/**
- * @author freva
- */
-public class FlavorSpareCheckerTest {
- /* Creates flavors where 'replaces' graph that looks like this (largest flavor at the bottom):
- * 5
- * |
- * |
- * 3 4 8
- * \ / \ |
- * \ / \ |
- * 1 6 7
- * / \
- * / \
- * 0 2
- */
- private static final List<Flavor> flavors = FlavorSpareCountTest.makeFlavors(
- Collections.singletonList(1), // 0 -> {1}
- Arrays.asList(3, 4), // 1 -> {3, 4}
- Collections.singletonList(1), // 2 -> {1}
- Collections.singletonList(5), // 3 -> {5}
- Collections.emptyList(), // 4 -> {}
- Collections.emptyList(), // 5 -> {}
- Collections.singletonList(4), // 6 -> {4}
- Collections.singletonList(8), // 7 -> {8}
- Collections.emptyList()); // 8 -> {}
-
- private final Map<Flavor, FlavorSpareCount> flavorSpareCountByFlavor = flavors.stream()
- .collect(Collectors.toMap(
- i -> i,
- i -> mock(FlavorSpareCount.class)));
-
- private final FlavorSpareChecker.SpareNodesPolicy spareNodesPolicy = mock(FlavorSpareChecker.SpareNodesPolicy.class);
- private FlavorSpareChecker flavorSpareChecker = new FlavorSpareChecker(spareNodesPolicy, flavorSpareCountByFlavor);
-
-
- @Test
- public void canRetireUnallocated_Successfully() {
- Flavor flavorToRetire = flavors.get(0);
- FlavorSpareCount flavorSpareCount = flavorSpareCountByFlavor.get(flavorToRetire);
- when(flavorSpareCount.hasReady()).thenReturn(true);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(true);
-
- assertTrue(flavorSpareChecker.canRetireUnallocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement(0);
- }
-
- @Test
- public void canRetireUnallocated_NoReadyForFlavor() {
- Flavor flavorToRetire = flavors.get(0);
- FlavorSpareCount flavorSpareCount = flavorSpareCountByFlavor.get(flavorToRetire);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(true);
-
- assertFalse(flavorSpareChecker.canRetireUnallocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement();
- }
-
- @Test
- public void canRetireUnallocated_NoSpareForFlavor() {
- Flavor flavorToRetire = flavors.get(0);
- FlavorSpareCount flavorSpareCount = flavorSpareCountByFlavor.get(flavorToRetire);
- when(flavorSpareCount.hasReady()).thenReturn(true);
-
- assertFalse(flavorSpareChecker.canRetireUnallocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement();
- }
-
- @Test
- public void canRetireAllocated_LeafFlavor_Successfully() {
- Flavor flavorToRetire = flavors.get(0);
-
- // If we want to retire flavor 0, then we must have enough spares & ready of flavor 0 and all
- // other flavor that it replaces transitively
- Stream.of(0, 1, 3, 4, 5)
- .map(flavors::get)
- .map(flavorSpareCountByFlavor::get)
- .forEach(flavorSpareCount -> {
- when(flavorSpareCount.hasReady()).thenReturn(true);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(true);
- });
-
- assertTrue(flavorSpareChecker.canRetireAllocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement(0, 1, 3, 4, 5);
- }
-
- @Test
- public void canRetireAllocated_LeafFlavor_NoSparesForPossibleWantedFlavor() {
- Flavor flavorToRetire = flavors.get(0);
-
- // Flavor 4 is transitively replaced by flavor 0, even though we have enough spares of flavor 0,
- // we cannot retire it if there are not enough spares of flavor 4
- Stream.of(0, 1, 3, 5)
- .map(flavors::get)
- .map(flavorSpareCountByFlavor::get)
- .forEach(flavorSpareCount -> {
- when(flavorSpareCount.hasReady()).thenReturn(true);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(true);
- });
-
- assertFalse(flavorSpareChecker.canRetireAllocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement();
- }
-
- @Test
- public void canRetireAllocated_CenterNode_Successfully() {
- Flavor flavorToRetire = flavors.get(1);
-
- Stream.of(1, 3, 4, 5)
- .map(flavors::get)
- .map(flavorSpareCountByFlavor::get)
- .forEach(flavorSpareCount -> {
- when(flavorSpareCount.hasReady()).thenReturn(true);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(true);
- });
-
- assertTrue(flavorSpareChecker.canRetireAllocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement(1, 3, 4, 5);
- }
-
- @Test
- public void canRetireAllocated_CenterNode_NoNodeRepoFlavorNodes_Successfully() {
- Flavor flavorToRetire = flavors.get(1);
-
- // If we want to retire a node with node-repo flavor 1, but there are no ready nodes of flavor-1,
- // we must ensure there are spare nodes of flavors that replace flavor 1
- Stream.of(0, 1, 2, 3, 4, 5)
- .map(flavors::get)
- .map(flavorSpareCountByFlavor::get)
- .forEach(flavorSpareCount -> {
- when(flavorSpareCount.hasReady()).thenReturn(true);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(true);
- });
- when(flavorSpareCountByFlavor.get(flavorToRetire).hasReady()).thenReturn(false);
- when(flavorSpareCountByFlavor.get(flavors.get(0)).getNumReadyAmongReplacees()).thenReturn(1L);
- when(flavorSpareCountByFlavor.get(flavors.get(2)).getNumReadyAmongReplacees()).thenReturn(1L);
-
- assertTrue(flavorSpareChecker.canRetireAllocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement(0, 2, 3, 4, 5);
- }
-
- @Test
- public void canRetireAllocated_CenterNode_NoNodeRepoFlavorNodes_NoImmediateSpare() {
- Flavor flavorToRetire = flavors.get(1);
-
- // Same as above, but now one of the flavors that could replace flavor 1 (flavor 2) does not have enough spares
- Stream.of(0, 1, 3, 4, 5)
- .map(flavors::get)
- .map(flavorSpareCountByFlavor::get)
- .forEach(flavorSpareCount -> {
- when(flavorSpareCount.hasReady()).thenReturn(true);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(true);
- });
- when(flavorSpareCountByFlavor.get(flavorToRetire).hasReady()).thenReturn(false);
- when(flavorSpareCountByFlavor.get(flavors.get(0)).getNumReadyAmongReplacees()).thenReturn(1L);
- when(flavorSpareCountByFlavor.get(flavors.get(2)).getNumReadyAmongReplacees()).thenReturn(1L);
-
- assertFalse(flavorSpareChecker.canRetireAllocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement();
- }
-
- @Test
- public void canRetireAllocated_CenterNode_NoNodeRepoFlavorNodes_SkipEmptyImmediate() {
- Flavor flavorToRetire = flavors.get(1);
-
- // Flavor 2 still has no spares, but also the sum of ready nodes in its replaces tree is 0, so we should
- // be able to continue
- Stream.of(0, 1, 3, 4, 5)
- .map(flavors::get)
- .map(flavorSpareCountByFlavor::get)
- .forEach(flavorSpareCount -> {
- when(flavorSpareCount.hasReady()).thenReturn(true);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(true);
- });
- when(flavorSpareCountByFlavor.get(flavorToRetire).hasReady()).thenReturn(false);
- when(flavorSpareCountByFlavor.get(flavors.get(0)).getNumReadyAmongReplacees()).thenReturn(1L);
- when(flavorSpareCountByFlavor.get(flavors.get(2)).getNumReadyAmongReplacees()).thenReturn(0L);
-
- assertTrue(flavorSpareChecker.canRetireAllocatedNodeWithFlavor(flavorToRetire));
- verifyDecrement(0, 3, 4, 5);
- }
-
- private void verifyDecrement(int... decrementFlavorIds) {
- Set<Flavor> decrementedFlavors = Arrays.stream(decrementFlavorIds).boxed().map(flavors::get).collect(Collectors.toSet());
- for (Flavor flavor : flavors) {
- int times = decrementedFlavors.contains(flavor) ? 1 : 0;
- verify(flavorSpareCountByFlavor.get(flavor), times(times)).decrementNumberOfReady();
- }
- }
-
- @Before
- public void setup() {
- Map<Flavor, FlavorSpareCount> flavorSpareCountGraph = FlavorSpareCount.constructFlavorSpareCountGraph(flavors);
- flavorSpareCountByFlavor.forEach((flavor, flavorSpareCount) -> {
- Set<FlavorSpareCount> possibleWantedFlavors = flavorSpareCountGraph.get(flavor).getPossibleWantedFlavors()
- .stream().map(FlavorSpareCount::getFlavor).map(flavorSpareCountByFlavor::get).collect(Collectors.toSet());
- Set<FlavorSpareCount> immediateReplacees = flavorSpareCountGraph.get(flavor).getImmediateReplacees()
- .stream().map(FlavorSpareCount::getFlavor).map(flavorSpareCountByFlavor::get).collect(Collectors.toSet());
-
- doNothing().when(flavorSpareCount).decrementNumberOfReady();
- when(flavorSpareCount.hasReady()).thenReturn(false);
- when(flavorSpareCount.getPossibleWantedFlavors()).thenReturn(possibleWantedFlavors);
- when(flavorSpareCount.getImmediateReplacees()).thenReturn(immediateReplacees);
- when(spareNodesPolicy.hasSpare(flavorSpareCount)).thenReturn(false);
- });
- }
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCountTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCountTest.java
deleted file mode 100644
index cb9c5c02c65..00000000000
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/FlavorSpareCountTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-// 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.Flavor;
-import com.yahoo.config.provision.NodeFlavors;
-import com.yahoo.config.provisioning.FlavorsConfig;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * @author freva
- */
-public class FlavorSpareCountTest {
- /* Creates flavors where 'replaces' graph that looks like this (largest flavor at the bottom):
- * 5
- * |
- * |
- * 3 4 8
- * \ / \ |
- * \ / \ |
- * 1 6 7
- * / \
- * / \
- * 0 2
- */
- private final List<Flavor> flavors = makeFlavors(
- Collections.singletonList(1), // 0 -> {1}
- Arrays.asList(3, 4), // 1 -> {3, 4}
- Collections.singletonList(1), // 2 -> {1}
- Collections.singletonList(5), // 3 -> {5}
- Collections.emptyList(), // 4 -> {}
- Collections.emptyList(), // 5 -> {}
- Collections.singletonList(4), // 6 -> {4}
- Collections.singletonList(8), // 7 -> {8}
- Collections.emptyList()); // 8 -> {}
-
- private final Map<Flavor, FlavorSpareCount> flavorSpareCountByFlavor =
- FlavorSpareCount.constructFlavorSpareCountGraph(flavors);
-
- @Test
- public void testFlavorSpareCountGraph() {
- List<List<Integer>> expectedPossibleWantedFlavorsByFlavorId = Arrays.asList(
- Arrays.asList(0, 1, 3, 4, 5),
- Arrays.asList(1, 3, 4, 5),
- Arrays.asList(1, 2, 3, 4, 5),
- Arrays.asList(3, 5),
- Collections.singletonList(4),
- Collections.singletonList(5),
- Arrays.asList(4, 6),
- Arrays.asList(7, 8),
- Collections.singletonList(8));
-
- List<List<Integer>> expectedImmediateReplaceesByFlavorId = Arrays.asList(
- Collections.emptyList(),
- Arrays.asList(0, 2),
- Collections.emptyList(),
- Collections.singletonList(1),
- Arrays.asList(1, 6),
- Collections.singletonList(3),
- Collections.emptyList(),
- Collections.emptyList(),
- Collections.singletonList(7));
-
- for (int i = 0; i < flavors.size(); i++) {
- Flavor flavor = flavors.get(i);
- FlavorSpareCount flavorSpareCount = flavorSpareCountByFlavor.get(flavor);
- Set<FlavorSpareCount> expectedPossibleWantedFlavors = expectedPossibleWantedFlavorsByFlavorId.get(i)
- .stream().map(flavors::get).map(flavorSpareCountByFlavor::get).collect(Collectors.toSet());
- Set<FlavorSpareCount> expectedImmediateReplacees = expectedImmediateReplaceesByFlavorId.get(i)
- .stream().map(flavors::get).map(flavorSpareCountByFlavor::get).collect(Collectors.toSet());
-
- assertEquals(expectedPossibleWantedFlavors, flavorSpareCount.getPossibleWantedFlavors());
- assertEquals(expectedImmediateReplacees, flavorSpareCount.getImmediateReplacees());
- }
- }
-
- @Test
- public void testSumOfReadyAmongReplacees() {
- long[] numReadyPerFlavor = {3, 5, 2, 6, 2, 7, 4, 3, 4};
- for (int i = 0; i < numReadyPerFlavor.length; i++) {
- flavorSpareCountByFlavor.get(flavors.get(i))
- .updateReadyAndActiveCounts(numReadyPerFlavor[i], (long) (100 * Math.random()));
- }
-
- long[] expectedSumTrees = {3, 10, 2, 16, 16, 23, 4, 3, 7};
- for (int i = 0; i < expectedSumTrees.length; i++) {
- assertEquals(expectedSumTrees[i], flavorSpareCountByFlavor.get(flavors.get(i)).getNumReadyAmongReplacees());
- }
- }
-
- /**
- * Takes in variable number of List of Integers:
- * For each list a flavor is created
- * For each element, n, in list, the new flavor replace n'th flavor
- */
- @SafeVarargs
- static List<Flavor> makeFlavors(List<Integer>... replaces) {
- FlavorConfigBuilder flavorConfigBuilder = new FlavorConfigBuilder();
- for (int i = 0; i < replaces.length; i++) {
- FlavorsConfig.Flavor.Builder builder = flavorConfigBuilder
- .addFlavor("flavor-" + i, 1. /* cpu*/, 3. /* mem GB*/, 2. /*disk GB*/, Flavor.Type.BARE_METAL);
-
- for (Integer replacesId : replaces[i]) {
- flavorConfigBuilder.addReplaces("flavor-" + replacesId, builder);
- }
- }
- return new NodeFlavors(flavorConfigBuilder.build())
- .getFlavors().stream()
- .sorted(Comparator.comparing(Flavor::name))
- .collect(Collectors.toList());
- }
-}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json
index 1432d2f4ea5..b72523963c0 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json
@@ -25,9 +25,6 @@
"name": "NodeRebooter"
},
{
- "name": "NodeRetirer"
- },
- {
"name": "OperatorChangeApplicationMaintainer"
},
{