|
|
@@ -148,13 +148,37 @@ pub fn attach_cmd(config: &Config, recipe: &Recipe) -> Result<String> {
|
|
|
/// Preflight on every launch node: the agent installed and the orbit cluster
|
|
|
/// visible. Runs before any download or launch — a peer that never paired
|
|
|
/// otherwise fails minutes into a 500 GB fetch.
|
|
|
-pub fn preflight_cmd(config: &Config) -> String {
|
|
|
- format!(
|
|
|
- "{} --version >/dev/null && systemctl --user is-active flare-agent >/dev/null && {} orbit status --cluster {} >/dev/null",
|
|
|
- binary(config),
|
|
|
- binary(config),
|
|
|
- shq(&orbit_cluster(config)),
|
|
|
- )
|
|
|
+///
|
|
|
+/// Only the controller has the orbit file with `[cluster.<name>]` — a peer
|
|
|
+/// just needs the binary and a live agent, so the cluster check is added
|
|
|
+/// there and nowhere else.
|
|
|
+pub fn preflight_cmd(config: &Config, controller: bool) -> String {
|
|
|
+ let base = format!(
|
|
|
+ "{} --version >/dev/null && systemctl --user is-active flare-agent >/dev/null",
|
|
|
+ binary(config)
|
|
|
+ );
|
|
|
+ if controller {
|
|
|
+ format!(
|
|
|
+ "{base} && {} orbit status --cluster {} >/dev/null",
|
|
|
+ binary(config),
|
|
|
+ shq(&orbit_cluster(config)),
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ base
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Pick the package path out of `flare download` output: it prints progress
|
|
|
+/// lines ("Downloading model file: …") and finally the resolved package path
|
|
|
+/// ending in `.safetensors`. Taking the last line blindly handed `orbit run`
|
|
|
+/// a progress line and the group died silently — so scan backwards for a path.
|
|
|
+pub fn parse_package_path(stdout: &str) -> Option<String> {
|
|
|
+ stdout
|
|
|
+ .lines()
|
|
|
+ .rev()
|
|
|
+ .map(str::trim)
|
|
|
+ .find(|l| l.ends_with(".safetensors") && l.contains('/'))
|
|
|
+ .map(str::to_string)
|
|
|
}
|
|
|
|
|
|
fn head(cluster: &crate::config::Cluster) -> Result<&Node> {
|
|
|
@@ -175,15 +199,16 @@ pub async fn serve(
|
|
|
dry_run: bool,
|
|
|
) -> Result<()> {
|
|
|
let controller = head(cluster)?;
|
|
|
- let pre = preflight_cmd(config);
|
|
|
if dry_run {
|
|
|
println!(
|
|
|
- " [dry-run] preflight on {} nodes: {pre}",
|
|
|
- cluster.nodes.len()
|
|
|
+ " [dry-run] preflight on {} nodes (cluster check on the controller only): {}",
|
|
|
+ cluster.nodes.len(),
|
|
|
+ preflight_cmd(config, true)
|
|
|
);
|
|
|
} else {
|
|
|
for node in &cluster.nodes {
|
|
|
- if let Err(e) = crate::ssh::run_ssh(config, node, pre.clone(), false).await {
|
|
|
+ let pre = preflight_cmd(config, node.rank == 0);
|
|
|
+ if let Err(e) = crate::ssh::run_ssh(config, node, pre, false).await {
|
|
|
bail!(
|
|
|
"FLARE preflight failed on {}: {e} — install `flare`, then `flare agent install` and pair the node with the controller",
|
|
|
node.name
|
|
|
@@ -343,9 +368,27 @@ mod tests {
|
|
|
|
|
|
#[test]
|
|
|
fn preflight_checks_agent_and_pairing() {
|
|
|
- let cmd = preflight_cmd(&cfg());
|
|
|
+ let cmd = preflight_cmd(&cfg(), true);
|
|
|
assert!(cmd.contains("is-active flare-agent"), "{cmd}");
|
|
|
assert!(cmd.contains("orbit status --cluster 'sparks'"), "{cmd}");
|
|
|
+ // a peer has no orbit file: binary + agent only
|
|
|
+ assert!(!preflight_cmd(&cfg(), false).contains("orbit status"));
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn package_path_is_taken_from_the_download_output() {
|
|
|
+ let out = "Resolved package: org/repo@abc / pkg.safetensors\n\
|
|
|
+ Downloading model file: aa.safetensors...\n\
|
|
|
+ Downloading model file: bb.safetensors...\n\
|
|
|
+ /mnt/nas/hub/snapshots/abc/pkg.safetensors\n";
|
|
|
+ assert_eq!(
|
|
|
+ parse_package_path(out).as_deref(),
|
|
|
+ Some("/mnt/nas/hub/snapshots/abc/pkg.safetensors")
|
|
|
+ );
|
|
|
+ assert_eq!(
|
|
|
+ parse_package_path("Downloading model file: x.safetensors..."),
|
|
|
+ None
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
#[test]
|