diff options
author | Valerij Fredriksen <valerij92@gmail.com> | 2019-06-20 19:03:27 +0200 |
---|---|---|
committer | Valerij Fredriksen <valerij92@gmail.com> | 2019-06-20 19:03:27 +0200 |
commit | da767a3f5d2f95d9b17e47446a06294f74522e0c (patch) | |
tree | c7a0b9a3744217651d0e4721f91e77085ab6fd86 /node-repository | |
parent | 86ddeed7f3d62f9703f100a013f7f4b39957f3b9 (diff) |
Remove NodeRetirer
Diffstat (limited to 'node-repository')
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 > reserved (set by {@link SpareNodesPolicy}</li> - * <li>2a: Number of ready nodes of flavor f is > 0</li> - * <li>2b: Verify 1 & 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 > 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" }, { |