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)
}
}
}