@Override
    protected double maintain() {
        NodeList allNodes;
        // Host and child nodes are written in separate transactions, but both are written while holding the
        // unallocated lock. Hold the unallocated lock while reading nodes to ensure we get all the children
        // of newly provisioned hosts.
        try (Mutex ignored = nodeRepository().nodes().lockUnallocated()) {
            allNodes = nodeRepository().nodes().list();
        }

        NodeList hosts = allNodes.state(Node.State.provisioned).nodeType(NodeType.host, NodeType.confighost, NodeType.controllerhost);
        int failures = 0;
        for (Node host : hosts) {
            try {
                HostIpConfig hostIpConfig = hostProvisioner.provision(host);
                setIpConfig(host, hostIpConfig);
            } catch (IllegalArgumentException | IllegalStateException e) {
                log.log(Level.INFO, "Could not provision " + host.hostname() + ", will retry in " +
                                    interval() + ": " + Exceptions.toMessageString(e));
            } catch (ThrottleProvisioningException e) {
                log.log(Level.INFO, "Failed to provision " + host.hostname() + ", will retry in " + interval() + ": " + e.getMessage());
                break;
            } catch (FatalProvisioningException e) {
                // FatalProvisioningException is thrown if node is not found in the cloud, allow for
                // some time for the state to propagate
                if (host.history().age(clock().instant()).getSeconds() < 30) continue;
                failures++;
                log.log(Level.SEVERE, "Failed to provision " + host.hostname() + ", failing out the host recursively", e);
                nodeRepository().nodes().parkRecursively(
                        host.hostname(), Agent.HostResumeProvisioner, true, "Failed by HostResumeProvisioner due to provisioning failure");
            } catch (RuntimeException e) {
                if (e.getCause() instanceof NamingException)
                    log.log(Level.INFO, "Could not provision " + host.hostname() + ", will retry in " + interval() + ": " + Exceptions.toMessageString(e));
                else {
                    failures++;
                    log.log(Level.WARNING, "Failed to provision " + host.hostname() + ", will retry in " + interval(), e);
                }
            }
        }
        return asSuccessFactorDeviation(hosts.size(), failures);
    }
@Test
    public void correctly_fails_if_irrecoverable_failure() {
        deployApplication();
        hostProvisioner.with(MockHostProvisioner.Behaviour.failProvisioning);

        Node host = tester.nodeRepository().nodes().node("host100").orElseThrow();
        Node node = tester.nodeRepository().nodes().node("host100-1").orElseThrow();
        assertTrue("No IP addresses assigned",
                Stream.of(host, node).map(n -> n.ipConfig().primary()).allMatch(List::isEmpty));

        hostResumeProvisioner.maintain();
        assertEquals(Set.of(), tester.nodeRepository().nodes().list(Node.State.parked).deprovisioning().hostnames());
        tester.clock().advance(Duration.ofSeconds(60));

        hostResumeProvisioner.maintain();
        assertEquals(Set.of("host100", "host100-1"), tester.nodeRepository().nodes().list(Node.State.parked).deprovisioning().hostnames());
    }