@Override
    protected double maintain() {
        NodeList candidates = nodeRepository().nodes().list().rebuilding(true);
        if (candidates.isEmpty()) {
            return 0;
        }
        int failures = 0;
        List<Node> rebuilding;
        try (var locked = nodeRepository().nodes().lockAndGetAll(candidates.asList(), Optional.of(Duration.ofSeconds(10)))) {
            rebuilding = locked.nodes().stream().map(NodeMutex::node).toList();
            RebuildResult result = hostProvisioner.replaceRootDisk(rebuilding);

            for (Node updated : result.rebuilt())
                if (!updated.status().wantToRebuild())
                    nodeRepository().nodes().write(updated, () -> { });

            for (var entry : result.failed().entrySet()) {
                ++failures;
                log.log(Level.WARNING, "Failed to rebuild " + entry.getKey() + ", will retry in " +
                                       interval() + ": " + Exceptions.toMessageString(entry.getValue()));
            }
        }
        return asSuccessFactorDeviation(rebuilding.size(), failures);
    }
@Test
    public void rebuild_host() {
        tester.makeReadyHosts(2, new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.fast, NodeResources.StorageType.remote)).activateTenantHosts();

        // No rebuilds in initial run
        diskReplacer.maintain();
        assertEquals(0, tester.nodeRepository().nodes().list().rebuilding(true).size());

        // Host starts rebuilding
        tester.nodeRepository().nodes().rebuild("host-1.yahoo.com", true, Agent.RebuildingOsUpgrader,
                tester.nodeRepository().clock().instant());
        diskReplacer.maintain();
        assertEquals(1, tester.nodeRepository().nodes().list().rebuilding(true).size());

        // Rebuild completes
        hostProvisioner.completeRebuildOf("host-1.yahoo.com");
        diskReplacer.maintain();
        assertEquals(0, tester.nodeRepository().nodes().list().rebuilding(true).size());
    }