Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-fast/src/bin/monetize-fast.rs

//! `monetize-fast`: the fast test's front (see the `monetize_fast` library). It only parses
//! its arguments; the run is the library's. It re-runs itself inside podman's rootless network
//! namespace, where the run is the ONE side of the estate's utility network.
//!
//! ```text
//! monetize-fast --product <id> --bin-dir DIR --medium PREFIX=MEDIUM.json... [--out DIR]
//!               [--image IMAGE] [--budget-s N] [--twin-late-s N] [--tree NAME=DIR]...
//!               [--kit DIR]   the real ONE box (monetize-admin, fast-edge, mock-upcloud,
//!                             fake_stripe_serve, guest-<name>.wasm in DIR; see `one_box`)
//! monetize-fast kit --out DIR [--parent DIR] [--guest NAME=PATH]...   build that kit (`kit`)
//! ```
use std::path::PathBuf;
use std::process::ExitCode;

/// `monetize-fast kit --out DIR [--parent DIR] [--guest NAME=PATH]...`
fn kit(argv: &[String]) -> ExitCode {
    let mut out: Option<PathBuf> = None;
    let mut parent: Option<PathBuf> = None;
    let mut guests: Vec<(String, PathBuf)> = Vec::new();
    let mut it = argv.iter();
    while let Some(a) = it.next() {
        let v = it.next();
        match (a.as_str(), v) {
            ("--out", Some(v)) => out = Some(PathBuf::from(v)),
            ("--parent", Some(v)) => parent = Some(PathBuf::from(v)),
            ("--guest", Some(v)) => match v.split_once('=') {
                Some((n, p)) => guests.push((n.to_string(), PathBuf::from(p))),
                None => {
                    eprintln!("monetize-fast kit: --guest wants NAME=PATH");
                    return ExitCode::from(2);
                }
            },
            (other, _) => {
                eprintln!("monetize-fast kit: unknown or incomplete argument: {other}");
                return ExitCode::from(2);
            }
        }
    }
    let Some(out) = out else {
        eprintln!("monetize-fast kit: --out DIR is required");
        return ExitCode::from(2);
    };
    // The constellation's parent: this crate is monetize/crates/monetize-fast.
    let parent = parent.unwrap_or_else(|| {
        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("../../..")
            .canonicalize()
            .unwrap_or_else(|_| PathBuf::from(".."))
    });
    match monetize_fast::kit::build(&parent, &out, &guests) {
        Ok(got) => {
            println!(
                "fast kit: {} piece(s) in {}: {}",
                got.len(),
                out.display(),
                got.join(", ")
            );
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("fast kit: {e}");
            ExitCode::FAILURE
        }
    }
}

/// Re-run this binary inside podman's rootless network namespace, and judge the run by its
/// receipt (`monetize_fast::outer`): the exit code that comes back is podman's, and podman's
/// own teardown of the namespace can fail after a run that passed.
fn in_the_netns(me: &std::path::Path, argv: &[String]) -> ExitCode {
    use std::io::{BufRead as _, Write as _};
    let out = argv
        .iter()
        .position(|a| a == "--out")
        .and_then(|i| argv.get(i + 1))
        .map(PathBuf::from)
        .unwrap_or_else(|| monetize_fast::Config::default().out);
    let label = format!("fast{}", std::process::id());
    let since = std::time::SystemTime::now();
    let child = std::process::Command::new("podman")
        .args(["unshare", "--rootless-netns"])
        .arg(me)
        .args(argv)
        .env(monetize_fast::IN_NETNS, "1")
        .env(monetize_fast::RUN_LABEL, &label)
        .stderr(std::process::Stdio::piped())
        .spawn();
    let mut child = match child {
        Ok(c) => c,
        Err(e) => {
            eprintln!("monetize-fast: podman unshare --rootless-netns: {e}");
            return ExitCode::from(2);
        }
    };
    // podman's stderr (and the run's) goes on to ours as it comes; the tail is kept for the verdict.
    let mut tail = String::new();
    if let Some(err) = child.stderr.take() {
        for line in std::io::BufReader::new(err).lines().map_while(Result::ok) {
            let _ = writeln!(std::io::stderr(), "{line}");
            tail.push_str(&line);
            tail.push('\n');
            if tail.len() > 64 * 1024 {
                let cut = tail.len() - 32 * 1024;
                let cut = (cut..tail.len())
                    .find(|&i| tail.is_char_boundary(i))
                    .unwrap_or(tail.len());
                tail.drain(..cut);
            }
        }
    }
    let status = match child.wait() {
        Ok(s) => s,
        Err(e) => {
            eprintln!("monetize-fast: podman unshare --rootless-netns: {e}");
            return ExitCode::from(2);
        }
    };
    if status.success() {
        return ExitCode::SUCCESS;
    }
    let ours_left = std::process::Command::new("podman")
        .args([
            "ps",
            "-a",
            "-q",
            "--filter",
            &format!("label=mock-upcloud-container={label}"),
        ])
        .output()
        .ok()
        .filter(|o| o.status.success())
        .map(|o| {
            String::from_utf8_lossy(&o.stdout)
                .lines()
                .filter(|l| !l.trim().is_empty())
                .count()
        });
    let pass = monetize_fast::outer::fresh_receipt_pass(&out, since);
    match monetize_fast::outer::judge(false, pass, &tail, ours_left) {
        monetize_fast::outer::Outer::Green => ExitCode::SUCCESS,
        monetize_fast::outer::Outer::GreenTeardown(words) => {
            eprintln!("monetize-fast: {words}");
            if let Err(e) = monetize_fast::outer::record_warning(&out, &words) {
                eprintln!(
                    "monetize-fast: the warning could not be recorded in the receipt under {}: {e}",
                    out.display()
                );
                return ExitCode::FAILURE;
            }
            println!(
                "fast: PASS with {}, receipt at {}",
                monetize_fast::outer::TEARDOWN_WARNING,
                out.join("receipt.txt").display()
            );
            ExitCode::SUCCESS
        }
        monetize_fast::outer::Outer::Red(why) => {
            eprintln!("monetize-fast: RED: {why}");
            ExitCode::from(status.code().unwrap_or(1).clamp(1, 255) as u8)
        }
    }
}

fn main() -> ExitCode {
    let argv: Vec<String> = std::env::args().skip(1).collect();
    if argv.first().map(String::as_str) == Some("kit") {
        return kit(&argv[1..]);
    }
    if std::env::var_os(monetize_fast::IN_NETNS).is_none() {
        // **The parallel slot** (`--slot auto|N`, default auto): this run's own utility network,
        // ports and work dir, held for the run's whole life so up to four runs share the box.
        let mut argv = argv.clone();
        let wanted = match argv.iter().position(|a| a == "--slot") {
            Some(i) => {
                let v = argv.get(i + 1).cloned().unwrap_or_default();
                argv.drain(i..(i + 2).min(argv.len()));
                if v == "auto" {
                    None
                } else {
                    match v.parse::<u8>() {
                        Ok(n) => Some(n),
                        Err(_) => {
                            eprintln!(
                                "monetize-fast: --slot wants auto or 0..{}",
                                mock_upcloud::container::FAST_SLOTS - 1
                            );
                            return ExitCode::from(2);
                        }
                    }
                }
            }
            None => None,
        };
        let dir = monetize_fast::one_box::slots_dir();
        let mut said = false;
        let (slot, _held) = loop {
            match monetize_fast::one_box::claim_slot(&dir, wanted) {
                Ok(c) => break c,
                Err(e) if wanted.is_none() && e.contains("[fast-slots-busy]") => {
                    if !said {
                        eprintln!("monetize-fast: {e}; waiting for one");
                        said = true;
                    }
                    std::thread::sleep(std::time::Duration::from_secs(10));
                }
                Err(e) => {
                    eprintln!("monetize-fast: {e}");
                    return ExitCode::from(2);
                }
            }
        };
        eprintln!("monetize-fast: slot {slot}");
        argv.extend(["--slot".to_string(), slot.to_string()]);
        // The HOST's view decides the utility network (inside the namespace it cannot be seen).
        if !argv.iter().any(|a| a == "--utility") {
            let trie = std::fs::read_to_string("/proc/net/fib_trie").unwrap_or_default();
            match mock_upcloud::container::free_utility_for_slot(&trie, slot) {
                Some(u) => argv.extend(["--utility".to_string(), u]),
                None => {
                    eprintln!("monetize-fast: every candidate utility /21 of slot {slot} is already held by this host");
                    return ExitCode::from(2);
                }
            }
        }
        let me = match std::env::current_exe() {
            Ok(p) => p,
            Err(e) => {
                eprintln!("monetize-fast: {e}");
                return ExitCode::from(2);
            }
        };
        return in_the_netns(&me, &argv);
    }
    let mut cfg = monetize_fast::Config::default();
    let mut it = argv.into_iter();
    while let Some(a) = it.next() {
        let mut v = |flag: &str| it.next().ok_or_else(|| format!("{flag} wants a value"));
        let r: Result<(), String> = (|| {
            match a.as_str() {
                "--product" => cfg.product = v("--product")?,
                "--out" => cfg.out = PathBuf::from(v("--out")?),
                "--bin-dir" => cfg.bin_dir = PathBuf::from(v("--bin-dir")?),
                "--image" => cfg.image = v("--image")?,
                "--utility" => cfg.utility = v("--utility")?,
                "--slot" => cfg.slot = v("--slot")?.parse().map_err(|e| format!("--slot: {e}"))?,
                "--kit" => cfg.kit = Some(PathBuf::from(v("--kit")?)),
                "--twin-late-s" => {
                    cfg.twin_late_s = v("--twin-late-s")?
                        .parse()
                        .map_err(|e| format!("--twin-late-s: {e}"))?
                }
                "--budget-s" => {
                    cfg.budget_s = v("--budget-s")?
                        .parse()
                        .map_err(|e| format!("--budget-s: {e}"))?
                }
                "--medium" => {
                    let s = v("--medium")?;
                    let (p, f) = s
                        .split_once('=')
                        .ok_or("--medium wants PREFIX=MEDIUM.json")?;
                    cfg.media.push((p.to_string(), PathBuf::from(f)));
                }
                "--tree" => {
                    let s = v("--tree")?;
                    let (n, d) = s.split_once('=').ok_or("--tree wants NAME=DIR")?;
                    cfg.trees.push((n.to_string(), PathBuf::from(d)));
                }
                other => return Err(format!("unknown argument: {other}")),
            }
            Ok(())
        })();
        if let Err(e) = r {
            eprintln!("monetize-fast: {e}");
            return ExitCode::from(2);
        }
    }
    match monetize_fast::run(&cfg) {
        Ok((p, true)) => {
            println!("fast: PASS, receipt at {}", p.display());
            ExitCode::SUCCESS
        }
        Ok((p, false)) => {
            println!("fast: FAIL, receipt at {}", p.display());
            ExitCode::FAILURE
        }
        Err(e) => {
            eprintln!("fast: {e}");
            ExitCode::from(2)
        }
    }
}