private double upgradeHostFlavor(NodeList allNodes) {
        NodeList activeNodes = allNodes.nodeType(NodeType.tenant)
                                       .state(Node.State.active)
                                       .shuffle(random); // Shuffle to avoid getting stuck trying to upgrade the same host
        Set<String> exhaustedFlavors = new HashSet<>();
        for (var node : activeNodes) {
            Optional<Node> parent = allNodes.parentOf(node);
            if (parent.isEmpty()) continue;

            // Limit upgrades to exclusive hosts because it is hard to predict which flavor will be provisioned when
            // retiring nodes on shared hosts
            if (parent.get().exclusiveToApplicationId().isEmpty()) continue;

            if (exhaustedFlavors.contains(parent.get().flavor().name())) continue;
            Allocation allocation = node.allocation().get();
            Predicate<NodeResources> realHostResourcesWithinLimits =
                    resources -> nodeRepository().nodeResourceLimits().isWithinRealLimits(resources, allocation.membership().cluster());
            if (!hostProvisioner.canUpgradeFlavor(parent.get(), node, realHostResourcesWithinLimits)) continue;
            if (parent.get().status().wantToUpgradeFlavor() && allocation.membership().retired()) continue; // Already upgrading

            boolean redeployed = false;
            boolean deploymentValid = false;
            try (MaintenanceDeployment deployment = new MaintenanceDeployment(allocation.owner(), deployer, metric, nodeRepository(), true)) {
                deploymentValid = deployment.isValid();
                if (!deploymentValid) continue;

                log.log(Level.INFO, () -> "Redeploying " + allocation.owner() + " to upgrade flavor (" +
                                          parent.get().flavor().name() + ") of " + parent.get());
                upgradeFlavor(parent.get(), true);
                deployment.activate();
                redeployed = true;
                return 1.0;
            } catch (NodeAllocationException e) {
               // Fine, no capacity for upgrade
                exhaustedFlavors.add(parent.get().flavor().name());
            } finally {
                if (deploymentValid && !redeployed) { // Cancel upgrade if redeploy failed
                    upgradeFlavor(parent.get(), false);
                }
            }
        }
        return 1.0;
    }
This maintainer attempts to upgrade a single host running on an older flavor generation. The upgrade happens by
marking and retiring the host on the old generation, and redeploying to provision a replacement host on a newer
generation.

If the cloud provider reports a lack of capacity for the newer generation, retirement of the host is
cancelled, and upgrade is attempted of the next host on an old flavor, if any.

Once a host has been marked for upgrade, {@link HostResumeProvisioner} will complete provisioning of the replacement
host.

@author mpolden
node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostFlavorUpgrader.java