@Override
    protected double maintain() {
        if ( ! nodeRepository().nodes().isWorking()) return 0.0;

        if ( ! nodeRepository().zone().cloud().allowHostSharing()) return 1.0; // Re-balancing not necessary
        if (nodeRepository().zone().environment().isTest()) return 1.0; // Short-lived deployments; no need to rebalance
        if (nodeRepository().zone().system().isCd()) return 1.0; // CD tests assert on # of nodes, avoid re-balancing as it make tests unstable

        // Work with an unlocked snapshot as this can take a long time and full consistency is not needed
        NodeList allNodes = nodeRepository().nodes().list();
        updateSkewMetric(allNodes);
        if ( ! zoneIsStable(allNodes)) return 1.0;
        findBestMove(allNodes).execute(true, Agent.Rebalancer, deployer, metric, nodeRepository());
        return 1.0;
   }
@Test
    public void testRebalancing() {
        RebalancerTester tester = new RebalancerTester();

        // --- Deploying a cpu heavy application - causing 1 of these nodes to be skewed
        tester.deployApp(cpuApp);
        Node cpuSkewedNode = tester.getNode(cpuApp);

        tester.maintain();
        assertFalse("No better place to move the skewed node, so no action is taken",
                    tester.getNode(cpuSkewedNode.hostname()).get().status().wantToRetire());
        assertEquals(0.00325, tester.metric().values.get("hostedVespa.docker.skew").doubleValue(), 0.00001);

        // --- Making a more suitable node configuration available causes rebalancing
        Node newCpuHost = tester.makeReadyNode("cpu");
        tester.activateTenantHosts();

        tester.maintain();
        assertTrue("Rebalancer retired the node we wanted to move away from", tester.isNodeRetired(cpuSkewedNode));
        assertTrue("... and added a node on the new host instead",
                   tester.getNodes(cpuApp, Node.State.active).stream().anyMatch(node -> node.hasParent(newCpuHost.hostname())));
        assertEquals("Skew is reduced",
                     0.00244, tester.metric().values.get("hostedVespa.docker.skew").doubleValue(), 0.00001);

        // --- Deploying a mem heavy application - allocated to the best option and causing increased skew
        tester.deployApp(memoryApp);
        assertEquals("Assigned to a flat node as that causes least skew", "flat",
                     tester.nodeRepository().nodes().list().parentOf(tester.getNode(memoryApp)).get().flavor().name());
        tester.maintain();
        assertEquals("Deploying the mem skewed app increased skew",
                     0.00734, tester.metric().values.get("hostedVespa.docker.skew").doubleValue(), 0.00001);

        // --- Adding a more suitable node reconfiguration causes no action as the system is not stable
        Node memSkewedNode = tester.getNode(memoryApp);
        Node newMemHost = tester.makeReadyNode("mem");
        tester.activateTenantHosts();

        tester.maintain();
        assertFalse("No rebalancing happens because cpuSkewedNode is still retired", tester.isNodeRetired(memSkewedNode));

        // --- Making the system stable enables rebalancing
        NestedTransaction tx = new NestedTransaction();
        tester.nodeRepository().nodes().deactivate(List.of(cpuSkewedNode),
                                                   new ApplicationTransaction(new ApplicationMutex(cpuApp, () -> {}), tx));
        tx.commit();
        assertEquals(1, tester.getNodes(Node.State.dirty).size());

        //     ... if activation fails when trying, we clean up the state
        tester.deployer().setFailActivate(true);
        tester.maintain();
        assertTrue("Want to retire is reset", tester.getNodes(Node.State.active).stream().noneMatch(node -> node.status().wantToRetire()));
        assertEquals("Reserved node was moved to dirty", 2, tester.getNodes(Node.State.dirty).size());
        String reservedHostname = tester.getNodes(Node.State.dirty).owner(memoryApp).first().get().hostname();
        tester.tester.move(Node.State.ready, reservedHostname);
        tester.nodeRepository().nodes().removeRecursively(reservedHostname);

        //     ... otherwise we successfully rebalance, again reducing skew
        tester.deployer().setFailActivate(false);
        tester.maintain();
        assertTrue("Rebalancer retired the node we wanted to move away from", tester.isNodeRetired(memSkewedNode));
        assertTrue("... and added a node on the new host instead",
                   tester.getNodes(memoryApp, Node.State.active).stream().anyMatch(node -> node.hasParent(newMemHost.hostname())));
        assertEquals("Skew is reduced",
                     0.00587, tester.metric().values.get("hostedVespa.docker.skew").doubleValue(), 0.00001);
    }