@Override
    protected double maintain() {
        if (!nodeRepository().zone().cloud().dynamicProvisioning()) return 1.0; // Not relevant in zones with static capacity
        if (nodeRepository().zone().environment().isTest()) return 1.0; // Short-lived deployments
        if (!nodeRepository().nodes().isWorking()) return 0.0;

        NodeList allNodes = nodeRepository().nodes().list();
        if (!NodeMover.zoneIsStable(allNodes)) return 1.0;
        return upgradeHostFlavor(allNodes);
    }
@Test
    public void maintain() {
        String flavor0 = "host";
        String flavor1 = "host2";
        NodeFlavors flavors = FlavorConfigBuilder.createDummies(flavor0, flavor1);
        MockHostProvisioner hostProvisioner = new MockHostProvisioner(flavors.getFlavors());
        ProvisioningTester tester = new ProvisioningTester.Builder().dynamicProvisioning()
                                                                    .flavors(flavors.getFlavors())
                                                                    .hostProvisioner(hostProvisioner)
                                                                    .build();
        ApplicationId app = ProvisioningTester.applicationId();
        NodeResources resources = new NodeResources(4, 8, 100, 1,
                                                    NodeResources.DiskSpeed.fast, NodeResources.StorageType.remote);
        ClusterSpec spec = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("c1")).vespaVersion("1").build();
        Capacity capacity = Capacity.from(new ClusterResources(2, 1, resources));
        List<MockDeployer.ApplicationContext> applications = List.of(new MockDeployer.ApplicationContext(app, spec, capacity));
        MockDeployer deployer = new MockDeployer(tester.provisioner(), tester.clock(), applications);
        HostFlavorUpgrader upgrader = new HostFlavorUpgrader(tester.nodeRepository(), Duration.ofDays(1),
                                                             new TestMetric(), deployer, hostProvisioner);

        // Provision hosts and deploy application
        tester.makeReadyNodes(2, flavor0, NodeType.host);
        tester.activateTenantHosts();
        tester.deploy(app, spec, capacity);
        Node host = tester.nodeRepository().nodes().list().hosts().first().get();
        assertEquals(flavor0, host.flavor().name());

        // Nothing to upgrade initially
        assertEquals(1, upgrader.maintain());
        assertEquals(NodeList.of(), tester.nodeRepository().nodes().list()
                                          .matching(h -> h.status().wantToUpgradeFlavor()));

        // Mark flavor as upgradable, but fail all provisioning requests
        hostProvisioner.addUpgradableFlavor(flavor0)
                       .with(Behaviour.failProvisionRequest);
        assertEquals(1, upgrader.maintain());
        assertEquals(NodeList.of(),
                     tester.nodeRepository().nodes().list()
                           .matching(node -> node.status().wantToUpgradeFlavor() || node.status().wantToRetire()),
                     "No hosts marked for upgrade or retirement");

        // First provision request fails, but we only try once for the same flavor
        hostProvisioner.with(Behaviour.failProvisionRequest, 1);
        assertEquals(1, upgrader.maintain());
        NodeList nodes = tester.nodeRepository().nodes().list();
        assertEquals(0, nodes.matching(node -> node.status().wantToUpgradeFlavor()).size());

        // Second succeeds and a replacement host starts provisioning
        assertEquals(1, upgrader.maintain());
        nodes = tester.nodeRepository().nodes().list();
        NodeList upgradingFlavor = nodes.matching(node -> node.status().wantToUpgradeFlavor());
        assertEquals(1, upgradingFlavor.size());
        assertEquals(1, nodes.state(Node.State.provisioned).size());

        // No more upgrades are started while host is retiring
        assertEquals(1, upgrader.maintain());
        assertEquals(upgradingFlavor, tester.nodeRepository().nodes().list()
                                            .matching(node -> node.status().wantToUpgradeFlavor()));
    }