|
@@ -15,7 +15,13 @@
|
|
|
use crate::config::{Cluster, Config, Node};
|
|
use crate::config::{Cluster, Config, Node};
|
|
|
|
|
|
|
|
/// The pinned controller of ONE cluster: a per-cluster override wins, then the
|
|
/// The pinned controller of ONE cluster: a per-cluster override wins, then the
|
|
|
-/// flat `[flare]` field — and either must actually be a node of that cluster.
|
|
|
|
|
|
|
+/// flat `[flare]` field.
|
|
|
|
|
+///
|
|
|
|
|
+/// The controller does **not** have to be a node of that cluster: `prod-no4`
|
|
|
|
|
+/// serves on dgx1+dgx2+dgx3+dgx8 while the orbit controller (dgx4) holds the
|
|
|
|
|
+/// package store outside it. Like `native::flare_orbit::controller_for`, the
|
|
|
|
|
+/// pin is therefore resolved across every configured cluster; only a name that
|
|
|
|
|
+/// matches nothing stays unresolved (and the caller falls back to rank 0).
|
|
|
pub fn pinned_for<'a>(config: &'a Config, cluster: &'a Cluster) -> Option<&'a Node> {
|
|
pub fn pinned_for<'a>(config: &'a Config, cluster: &'a Cluster) -> Option<&'a Node> {
|
|
|
let f = config.flare.as_ref()?;
|
|
let f = config.flare.as_ref()?;
|
|
|
let want = f
|
|
let want = f
|
|
@@ -23,7 +29,13 @@ pub fn pinned_for<'a>(config: &'a Config, cluster: &'a Cluster) -> Option<&'a No
|
|
|
.get(&cluster.name)
|
|
.get(&cluster.name)
|
|
|
.and_then(|c| c.controller_node.clone())
|
|
.and_then(|c| c.controller_node.clone())
|
|
|
.or_else(|| f.controller_node.clone())?;
|
|
.or_else(|| f.controller_node.clone())?;
|
|
|
- cluster.nodes.iter().find(|n| n.name == want)
|
|
|
|
|
|
|
+ cluster.nodes.iter().find(|n| n.name == want).or_else(|| {
|
|
|
|
|
+ config
|
|
|
|
|
+ .clusters
|
|
|
|
|
+ .iter()
|
|
|
|
|
+ .flat_map(|c| c.nodes.iter())
|
|
|
|
|
+ .find(|n| n.name == want)
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Rank-0 candidates of ONE cluster (pinned controller first) — the fleet
|
|
/// Rank-0 candidates of ONE cluster (pinned controller first) — the fleet
|
|
@@ -92,6 +104,44 @@ mod tests {
|
|
|
assert_eq!(pinned_for(&cfg, six).map(|n| n.name.as_str()), Some("n5"));
|
|
assert_eq!(pinned_for(&cfg, six).map(|n| n.name.as_str()), Some("n5"));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn the_controller_may_sit_outside_the_lane_cluster() {
|
|
|
|
|
+ // prod-no4: serves on n1/n2 while dgx4-equivalent n5 (the package
|
|
|
|
|
+ // store + orbit controller) is a node of another cluster only
|
|
|
|
|
+ let cfg: Config = toml::from_str(
|
|
|
|
|
+ r#"
|
|
|
|
|
+ ssh_user = "u"
|
|
|
|
|
+ ssh_key_path = "/k"
|
|
|
|
|
+
|
|
|
|
|
+ [flare]
|
|
|
|
|
+ [flare.clusters.prod-no4]
|
|
|
|
|
+ orbit_cluster = "prod5"
|
|
|
|
|
+ controller_node = "n5"
|
|
|
|
|
+
|
|
|
|
|
+ [[clusters]]
|
|
|
|
|
+ name = "prod-no4"
|
|
|
|
|
+ nodes = [
|
|
|
|
|
+ { name = "n1", ssh_ip = "10.0.0.1", rank = 0 },
|
|
|
|
|
+ { name = "n2", ssh_ip = "10.0.0.2", rank = 1 },
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ [[clusters]]
|
|
|
|
|
+ name = "store"
|
|
|
|
|
+ nodes = [ { name = "n5", ssh_ip = "10.0.0.5", rank = 0 } ]
|
|
|
|
|
+ "#,
|
|
|
|
|
+ )
|
|
|
|
|
+ .expect("config toml");
|
|
|
|
|
+ let no4 = cfg.clusters.iter().find(|c| c.name == "prod-no4").unwrap();
|
|
|
|
|
+ // resolved from the other cluster, not silently dropped to rank 0
|
|
|
|
|
+ assert_eq!(pinned_for(&cfg, no4).map(|n| n.name.as_str()), Some("n5"));
|
|
|
|
|
+ // and the probe asks the controller first, then rank 0 as a fallback
|
|
|
|
|
+ let names: Vec<&str> = candidates_for(&cfg, no4)
|
|
|
|
|
+ .iter()
|
|
|
|
|
+ .map(|n| n.name.as_str())
|
|
|
|
|
+ .collect();
|
|
|
|
|
+ assert_eq!(names, vec!["n5", "n1"]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
#[test]
|
|
#[test]
|
|
|
fn pairing_only_names_clusters_the_config_mentions() {
|
|
fn pairing_only_names_clusters_the_config_mentions() {
|
|
|
use crate::native::flare::orbit_cluster_paired;
|
|
use crate::native::flare::orbit_cluster_paired;
|