@Test
    public void prefer_exclusive_network_switch() {
        // Hosts are provisioned, without switch information
        ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).flavorsConfig(flavorsConfig()).build();
        NodeResources hostResources = new NodeResources(32, 128, 2000, 10);
        List<Node> hosts0 = tester.makeReadyNodes(3, hostResources, NodeType.host, 5);
        tester.activateTenantHosts();

        // Application is deployed
        ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("1").build();
        NodeResources resources = new NodeResources(2, 4, 50, 1, NodeResources.DiskSpeed.any);
        ApplicationId app1 = ApplicationId.from("t1", "a1", "i1");
        tester.activate(app1, tester.prepare(app1, cluster, Capacity.from(new ClusterResources(2, 1, resources))));
        tester.assertSwitches(Set.of(), app1, cluster.id());

        // One host is provisioned on a known switch
        String switch0 = "switch0";
        {
            List<Node> hosts = tester.makeReadyNodes(1, hostResources, NodeType.host, 5);
            tester.activateTenantHosts();
            tester.patchNodes(hosts, (host) -> host.withSwitchHostname(switch0));
        }

        // Redeploy does not change allocation as a host with switch information is no better or worse than hosts
        // without switch information
        NodeList allocatedNodes = tester.nodeRepository().nodes().list().owner(app1);
        tester.activate(app1, tester.prepare(app1, cluster, Capacity.from(new ClusterResources(2, 1, resources))));
        assertEquals("Allocation unchanged", allocatedNodes, tester.nodeRepository().nodes().list().owner(app1));

        // Initial hosts are attached to the same switch
        tester.patchNodes(hosts0, (host) -> host.withSwitchHostname(switch0));

        // Redeploy does not change allocation
        tester.activate(app1, tester.prepare(app1, cluster, Capacity.from(new ClusterResources(2, 1, resources))));
        assertEquals("Allocation unchanged", allocatedNodes, tester.nodeRepository().nodes().list().owner(app1));

        // One regular host and one slow-disk host are provisioned on the same switch
        String switch1 = "switch1";
        Node hostWithSlowDisk;
        {
            NodeResources slowDisk = hostResources.with(NodeResources.DiskSpeed.slow);
            List<Node> hosts = tester.makeReadyNodes(1, slowDisk, NodeType.host, 5);
            hosts.addAll(tester.makeReadyNodes(1, hostResources, NodeType.host, 5));
            tester.patchNodes(hosts, (host) -> host.withSwitchHostname(switch1));
            tester.activateTenantHosts();
            hostWithSlowDisk = hosts.get(0);
        }

        // Redeploy does not change allocation as we prefer to keep our already active nodes
        tester.activate(app1, tester.prepare(app1, cluster, Capacity.from(new ClusterResources(2, 1, resources))));
        tester.assertSwitches(Set.of(switch0), app1, cluster.id());

        // A node is retired
        tester.patchNode(tester.nodeRepository().nodes().list().owner(app1).asList().get(0),
                         (node) -> node.withWantToRetire(true, Agent.system, tester.clock().instant()));

        // Redeploy allocates new node on a distinct switch, and the host with slowest disk (cheapest) on that switch
        tester.activate(app1, tester.prepare(app1, cluster, Capacity.from(new ClusterResources(2, 1, resources))));
        tester.assertSwitches(Set.of(switch0, switch1), app1, cluster.id());
        assertTrue("Host with slow disk on " + switch1 + " is chosen", tester.nodeRepository().nodes().list().owner(app1).state(State.active).stream()
                                                                             .anyMatch(node -> node.hasParent(hostWithSlowDisk.hostname())));

        // Growing cluster picks new node on exclusive switch
        String switch2 = "switch2";
        {
            List<Node> hosts = tester.makeReadyNodes(1, hostResources, NodeType.host, 5);
            tester.activateTenantHosts();
            tester.patchNodes(hosts, (host) -> host.withSwitchHostname(switch2));
        }
        tester.activate(app1, tester.prepare(app1, cluster, Capacity.from(new ClusterResources(3, 1, resources))));
        tester.assertSwitches(Set.of(switch0, switch1, switch2), app1, cluster.id());

        // Growing cluster further can reuse switches as we're now out of exclusive ones
        tester.activate(app1, tester.prepare(app1, cluster, Capacity.from(new ClusterResources(4, 1, resources))));
        tester.assertSwitches(Set.of(switch0, switch1, switch2), app1, cluster.id());

        // Additional cluster can reuse switches of existing cluster
        ClusterSpec cluster2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("content")).vespaVersion("1").build();
        tester.activate(app1, tester.prepare(app1, cluster2, Capacity.from(new ClusterResources(3, 1, resources))));
        tester.assertSwitches(Set.of(switch0, switch1, switch2), app1, cluster2.id());

        // Another application is deployed on exclusive switches
        ApplicationId app2 = ApplicationId.from("t2", "a2", "i2");
        tester.activate(app2, tester.prepare(app2, cluster, Capacity.from(new ClusterResources(3, 1, resources))));
        tester.assertSwitches(Set.of(switch0, switch1, switch2), app2, cluster.id());
    }
Test redeployment of nodes that violates spare headroom - but without alternatives
<p>
Setup 2 hosts and allocate one app with a container on each. 2 spares
<p>
Initial allocation of app 1 --> final allocation:
<p>
|    |    |        |    |    |
|    |    |   -->  |    |    |
| 1a | 1b |        | 1a | 1b |
node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicAllocationTest.java