|
|
@@ -189,6 +189,7 @@ async fn cluster_off_auto_power_on_powers_and_selects() {
|
|
|
poll_interval_secs: 0,
|
|
|
max_poll_secs: 10,
|
|
|
},
|
|
|
+ force_occupied: false,
|
|
|
},
|
|
|
)
|
|
|
.await
|
|
|
@@ -245,6 +246,7 @@ async fn cluster_off_no_auto_power_fails() {
|
|
|
poll_interval_secs: 0,
|
|
|
max_poll_secs: 10,
|
|
|
},
|
|
|
+ force_occupied: false,
|
|
|
},
|
|
|
)
|
|
|
.await;
|
|
|
@@ -300,6 +302,7 @@ async fn cluster_already_on_selects_directly() {
|
|
|
poll_interval_secs: 0,
|
|
|
max_poll_secs: 10,
|
|
|
},
|
|
|
+ force_occupied: false,
|
|
|
},
|
|
|
)
|
|
|
.await
|
|
|
@@ -354,6 +357,7 @@ async fn occupied_nodes_excluded() {
|
|
|
poll_interval_secs: 0,
|
|
|
max_poll_secs: 10,
|
|
|
},
|
|
|
+ force_occupied: false,
|
|
|
},
|
|
|
)
|
|
|
.await
|
|
|
@@ -363,6 +367,63 @@ async fn occupied_nodes_excluded() {
|
|
|
assert_eq!(cluster.nodes[0].name, "n2");
|
|
|
}
|
|
|
|
|
|
+#[tokio::test]
|
|
|
+async fn force_occupied_keeps_occupied_nodes() {
|
|
|
+ // `--force` skips the occupancy filter: an occupied node is kept in the
|
|
|
+ // available set (reachable is still required). Mirror of
|
|
|
+ // `occupied_nodes_excluded` with force_occupied: true.
|
|
|
+ let c = test_cluster("c", &["n1", "n2"]);
|
|
|
+ let config = test_config(vec![c.clone()]);
|
|
|
+ let recipe = test_recipe(Some(2));
|
|
|
+
|
|
|
+ let mut state = ClusterState::default();
|
|
|
+ state.nodes.insert(
|
|
|
+ "n1".into(),
|
|
|
+ NodeState {
|
|
|
+ reachable: true,
|
|
|
+ occupied: true,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ state.nodes.insert(
|
|
|
+ "n2".into(),
|
|
|
+ NodeState {
|
|
|
+ reachable: true,
|
|
|
+ occupied: false,
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ let prober = MockProber { state };
|
|
|
+ let pinger = MockPinger { reachable: true };
|
|
|
+ let power = MockPower {
|
|
|
+ results: HashMap::new(),
|
|
|
+ turned_on: std::sync::Mutex::new(Vec::new()),
|
|
|
+ };
|
|
|
+
|
|
|
+ let (cluster, _nccl) = prepare_cluster(
|
|
|
+ &config,
|
|
|
+ &recipe,
|
|
|
+ "auto",
|
|
|
+ None,
|
|
|
+ &PrepareRequest {
|
|
|
+ prober: &prober,
|
|
|
+ power: Some(&power),
|
|
|
+ pinger: &pinger,
|
|
|
+ auto_power_on: false,
|
|
|
+ boot_timing: BootTiming {
|
|
|
+ boot_wait_secs: 0,
|
|
|
+ poll_interval_secs: 0,
|
|
|
+ max_poll_secs: 10,
|
|
|
+ },
|
|
|
+ force_occupied: true,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+
|
|
|
+ // Both nodes kept (n1 occupied but force_occupied=true).
|
|
|
+ assert_eq!(cluster.nodes.len(), 2);
|
|
|
+}
|
|
|
+
|
|
|
#[test]
|
|
|
fn dry_run_resolves_without_probing_when_all_nodes_unreachable() {
|
|
|
// Dry-run must resolve a cluster even when no node is reachable — the
|