Переглянути джерело

flare: the dashboard/probe controller pin also resolves outside the lane cluster

pinned_for (web logs, orbit snapshots, fleet probe) still required the pinned
controller to be a node of that cluster, so prod-no4 (controller dgx4, serving
on dgx1-3+dgx8) silently fell back to dgx1: 'orbit status' there is not
authorized and the FLARE row/logs for that lane went blank. It now resolves
across every configured cluster, matching controller_for.
Alexander Korolev 11 годин тому
батько
коміт
06b53e0c78
1 змінених файлів з 52 додано та 2 видалено
  1. 52 2
      src/web/flare_nodes.rs

+ 52 - 2
src/web/flare_nodes.rs

@@ -15,7 +15,13 @@
 use crate::config::{Cluster, Config, Node};
 
 /// 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> {
     let f = config.flare.as_ref()?;
     let want = f
@@ -23,7 +29,13 @@ pub fn pinned_for<'a>(config: &'a Config, cluster: &'a Cluster) -> Option<&'a No
         .get(&cluster.name)
         .and_then(|c| c.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
@@ -92,6 +104,44 @@ mod tests {
         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]
     fn pairing_only_names_clusters_the_config_mentions() {
         use crate::native::flare::orbit_cluster_paired;