@Override
    protected double maintain() {
        if (!nodeRepository().nodes().isWorking()) return 0.0;
        if (!nodeRepository().zone().environment().isProduction()) return 1.0;
        NodeList allNodes = nodeRepository().nodes().list(); // Lockless as strong consistency is not needed
        if (!zoneIsStable(allNodes)) return 1.0;

        Move bestMove = findBestMove(allNodes);
        if (!bestMove.isEmpty()) {
            LOG.info("Trying " + bestMove + " (" + bestMove.fromHost().switchHostname().orElse("<none>") +
                     " -> " + bestMove.toHost().switchHostname().orElse("<none>") + ")");
        }
        bestMove.execute(false, Agent.SwitchRebalancer, deployer, metric, nodeRepository());
        return 1.0;
    }
@Test
    public void rebalance() {
        ClusterSpec.Id cluster1 = ClusterSpec.Id.from("c1");
        ClusterSpec.Id cluster2 = ClusterSpec.Id.from("c2");
        ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east")))
                                                                    .spareCount(1)
                                                                    .build();
        MockDeployer deployer = deployer(tester, cluster1, cluster2);
        SwitchRebalancer rebalancer = new SwitchRebalancer(tester.nodeRepository(), Duration.ofDays(1), new TestMetric(), deployer);

        // Provision initial hosts on same switch
        NodeResources hostResources = new NodeResources(48, 128, 500, 10);
        String switch0 = "switch0";
        provisionHosts(3, switch0, hostResources, tester);

        // Deploy application
        deployer.deployFromLocalActive(app).get().activate();
        tester.assertSwitches(Set.of(switch0), app, cluster1);
        tester.assertSwitches(Set.of(switch0), app, cluster2);

        // Rebalancing does nothing as there are no better moves to perform
        tester.clock().advance(SwitchRebalancer.waitTimeAfterPreviousDeployment);
        assertNoMoves(rebalancer, tester);

        // Provision a single host on a new switch
        provisionHost("switch1", hostResources, tester);

        // Application is redeployed and rebalancer does nothing as not enough time has passed since deployment
        deployer.deployFromLocalActive(app).get().activate();
        assertNoMoves(rebalancer, tester);

        // No rebalancing because the additional host is counted as a spare
        tester.clock().advance(SwitchRebalancer.waitTimeAfterPreviousDeployment);
        assertNoMoves(rebalancer, tester);

        // More hosts are provisioned. Rebalancer now retires one node from non-exclusive switch in each cluster, and
        // allocates a new one
        provisionHost("switch2", hostResources, tester);
        provisionHost("switch3", hostResources, tester);
        Set<ClusterSpec.Id> clusters = Set.of(cluster1, cluster2);
        Set<ClusterSpec.Id> rebalancedClusters = new HashSet<>();
        for (int i = 0; i < clusters.size(); i++) {
            tester.clock().advance(SwitchRebalancer.waitTimeAfterPreviousDeployment);
            rebalancer.maintain();
            NodeList appNodes = tester.nodeRepository().nodes().list().owner(app).state(Node.State.active);
            NodeList retired = appNodes.retired();
            ClusterSpec.Id cluster = retired.first().get().allocation().get().membership().cluster().id();
            assertEquals("Node is retired in " + cluster + " " + retired, 1, retired.size());
            NodeList clusterNodes = appNodes.cluster(cluster);
            assertEquals("Cluster " + cluster + " allocates nodes on distinct switches", 2,
                         tester.switchesOf(clusterNodes, tester.nodeRepository().nodes().list()).size());
            rebalancedClusters.add(cluster);

            // Retired node becomes inactive and makes zone stable
            deactivate(tester, retired);
        }
        assertEquals("Rebalanced all clusters", clusters, rebalancedClusters);

        // Next run does nothing
        tester.clock().advance(SwitchRebalancer.waitTimeAfterPreviousDeployment);
        assertNoMoves(rebalancer, tester);
    }