use std::collections::BTreeMap;
use std::time::Duration;
use super::host_key_placeholder;
use crate::exec::GROUP_SIGNAL;
const BOOTSTRAP: &str = include_str!("bootstrap.sh");
const WATCHDOG: &str = include_str!("watchdog.sh");
const HEREDOC_END: &str = "OVERBRAINER_WATCHDOG_END";
pub const JOB_ENV: &str = "/etc/overbrainer/job.env";
pub const HOST_KEY_ENV: &str = "OVERBRAINER_HOST_KEY";
#[must_use]
pub fn watchdog_script() -> String {
format!("{GROUP_SIGNAL}{WATCHDOG}")
}
#[must_use]
pub fn bootstrap_functions() -> &'static str {
BOOTSTRAP
}
#[must_use]
pub fn pod_command() -> Vec<String> {
let script = format!(
"{BOOTSTRAP}\nwrite_watchdog() {{\n mkdir -p \"$(dirname \"$1\")\" || return 1\n cat > \"$1.tmp\" <<'{HEREDOC_END}' || return 1\n{}{HEREDOC_END}\n mv -f \"$1.tmp\" \"$1\"\n}}\n\nbootstrap_main\n",
watchdog_script()
);
vec!["bash".to_string(), "-c".to_string(), script]
}
#[derive(Debug, Clone)]
pub struct PodSettings<'a> {
pub run_id: &'a str,
pub workdir: &'a str,
pub deadline_unix: Option<u64>,
pub boot_grace: Duration,
pub retrieve_grace: Duration,
pub keep: bool,
pub volume_dir: Option<&'a str>,
pub volume_gb: Option<u32>,
pub api_url: &'a str,
pub authorized_key: &'a str,
pub claim: &'a str,
}
#[must_use]
pub fn pod_env(settings: &PodSettings<'_>) -> BTreeMap<String, String> {
let mut env = BTreeMap::new();
let mut set = |name: &str, value: String| {
env.insert(name.to_string(), value);
};
set("OVERBRAINER_RUN_ID", settings.run_id.to_string());
set(HOST_KEY_ENV, host_key_placeholder(settings.run_id));
set("OVERBRAINER_WORKDIR", settings.workdir.to_string());
set(
"OVERBRAINER_RUN_DIR",
format!("{}/{}", settings.workdir, settings.run_id),
);
if let Some(deadline) = settings.deadline_unix {
set("OVERBRAINER_DEADLINE", deadline.to_string());
}
set(
"OVERBRAINER_BOOT_GRACE",
settings.boot_grace.as_secs().to_string(),
);
set(
"OVERBRAINER_RETRIEVE_GRACE",
settings.retrieve_grace.as_secs().to_string(),
);
set(
"OVERBRAINER_KEEP_POD",
if settings.keep { "1" } else { "0" }.to_string(),
);
if let Some(volume_dir) = settings.volume_dir {
set("OVERBRAINER_VOLUME_DIR", volume_dir.to_string());
}
if let Some(volume_gb) = settings.volume_gb {
set("OVERBRAINER_VOLUME_GB", volume_gb.to_string());
}
set("OVERBRAINER_API_URL", settings.api_url.to_string());
set(
"OVERBRAINER_AUTHORIZED_KEY",
settings.authorized_key.to_string(),
);
set("OVERBRAINER_CLAIM", settings.claim.to_string());
set("OVERBRAINER_VERSION", env!("CARGO_PKG_VERSION").to_string());
set("JUPYTER_DISABLE", "1".to_string());
env
}
#[cfg(test)]
mod tests {
use std::process::Command;
use super::*;
#[test]
fn the_command_carries_the_whole_watchdog_and_stays_small() {
let command = pod_command();
assert_eq!(command[..2], ["bash", "-c"]);
let script = &command[2];
assert!(script.len() < 100 * 1024, "{} bytes", script.len());
assert!(script.contains(&watchdog_script()));
assert!(script.ends_with("\nbootstrap_main\n"));
assert!(
!WATCHDOG.lines().any(|line| line == HEREDOC_END),
"the watchdog would end its own here-document"
);
}
#[test]
fn every_shell_parses_the_command() {
let script = &pod_command()[2];
for shell in [&["sh"][..], &["dash"], &["busybox", "sh"], &["bash"]] {
let Ok(output) = Command::new(shell[0])
.args(&shell[1..])
.arg("-n")
.arg("-c")
.arg(script)
.output()
else {
eprintln!("skipped: {} is not installed", shell.join(" "));
continue;
};
assert!(
output.status.success(),
"{shell:?}: {}",
String::from_utf8_lossy(&output.stderr)
);
}
}
#[test]
fn the_env_holds_the_watchdog_settings_and_no_secret() {
let settings = PodSettings {
run_id: "r1",
workdir: "/workspace/overbrainer",
deadline_unix: Some(1_790_021_600),
boot_grace: Duration::from_mins(30),
retrieve_grace: Duration::from_secs(3600),
keep: false,
volume_dir: None,
volume_gb: None,
api_url: "https://api.runpod.io/v2",
authorized_key: "ssh-ed25519 AAAAclient overbrainer-r1",
claim: "ssh-ed25519 AAAAhost",
};
let env = pod_env(&settings);
let get = |name: &str| env.get(name).map(String::as_str);
assert_eq!(
get("OVERBRAINER_RUN_DIR"),
Some("/workspace/overbrainer/r1")
);
assert_eq!(get("OVERBRAINER_DEADLINE"), Some("1790021600"));
assert_eq!(get("OVERBRAINER_BOOT_GRACE"), Some("1800"));
assert_eq!(get("OVERBRAINER_KEEP_POD"), Some("0"));
assert_eq!(get("JUPYTER_DISABLE"), Some("1"));
assert_eq!(get("OVERBRAINER_CLAIM"), Some("ssh-ed25519 AAAAhost"));
assert_eq!(
get(HOST_KEY_ENV),
Some("{{ RUNPOD_SECRET_overbrainer_host_key_r1 }}")
);
assert!(!env.contains_key("HF_TOKEN"));
assert!(!env.contains_key("PUBLIC_KEY"));
assert!(env.len() <= 50);
let kept = pod_env(&PodSettings {
keep: true,
deadline_unix: None,
..settings
});
assert_eq!(
kept.get("OVERBRAINER_KEEP_POD").map(String::as_str),
Some("1")
);
assert!(!kept.contains_key("OVERBRAINER_DEADLINE"));
assert!(!kept.contains_key("OVERBRAINER_VOLUME_DIR"));
}
#[test]
fn a_network_volume_hands_its_mount_and_size_to_the_watchdog() {
let settings = PodSettings {
run_id: "r1",
workdir: "/workspace/overbrainer",
deadline_unix: Some(1_790_021_600),
boot_grace: Duration::from_mins(30),
retrieve_grace: Duration::from_secs(3600),
keep: false,
volume_dir: Some("/workspace/data"),
volume_gb: Some(200),
api_url: "https://api.runpod.io/v2",
authorized_key: "ssh-ed25519 AAAAclient overbrainer-r1",
claim: "ssh-ed25519 AAAAhost",
};
let env = pod_env(&settings);
let get = |name: &str| env.get(name).map(String::as_str);
assert_eq!(get("OVERBRAINER_VOLUME_DIR"), Some("/workspace/data"));
assert_eq!(get("OVERBRAINER_VOLUME_GB"), Some("200"));
}
}