use std::net::TcpListener;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use serde_json::json;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use super::config::{InferConfig, ManagedConfig, RestartPolicy};
use super::runtime::InferRuntime;
use super::spec::{InferFlavor, InferenceSpec};
use crate::model_manifest::{
AdoptInput, ModelFormat, ModelRegistry, ModelSource, scan_dir, scan_file,
};
use crate::workload_pkg::codec::hex_encode;
use crate::workload_pkg::{KeyOrigin, TrustAnchors, key_id_for};
use crate::workload_runtime::host_contract::HostContract;
use crate::workload_runtime::types::{
InstanceHandle, RunMode, VerifiedWorkload, WorkloadConfig, WorkloadRuntime,
};
pub fn free_port() -> u16 {
use std::sync::atomic::{AtomicU16, Ordering};
static NEXT: AtomicU16 = AtomicU16::new(0);
let base = 10_000 + (std::process::id() % 1000) as u16 * 16;
loop {
let n = NEXT.fetch_add(1, Ordering::Relaxed);
let port = base + n % 16;
if TcpListener::bind(("127.0.0.1", port)).is_ok() {
return port;
}
if n > 4096 {
return TcpListener::bind(("127.0.0.1", 0)).unwrap().local_addr().unwrap().port();
}
}
}
pub async fn server_on(port: u16) -> MockServer {
let l = TcpListener::bind(("127.0.0.1", port)).unwrap();
MockServer::builder().listener(l).start().await
}
pub fn port_of(s: &MockServer) -> u16 {
s.address().port()
}
pub async fn mount_llama(s: &MockServer, status: u16, model: &str) {
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(ResponseTemplate::new(status).set_body_json(json!({"status": "ok"})))
.mount(s)
.await;
Mock::given(method("GET"))
.and(path("/v1/models"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({"data": [{"id": model}]})))
.mount(s)
.await;
}
pub async fn mount_ollama(s: &MockServer, tags: &[&str], resident: &[&str]) {
let list =
|v: &[&str]| json!({"models": v.iter().map(|n| json!({"name": n})).collect::<Vec<_>>()});
Mock::given(method("GET"))
.and(path("/api/version"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({"version": "0.0.0-test"})))
.mount(s)
.await;
Mock::given(method("GET"))
.and(path("/api/tags"))
.respond_with(ResponseTemplate::new(200).set_body_json(list(tags)))
.mount(s)
.await;
Mock::given(method("GET"))
.and(path("/api/ps"))
.respond_with(ResponseTemplate::new(200).set_body_json(list(resident)))
.mount(s)
.await;
}
pub fn write_script(dir: &Path) -> PathBuf {
use std::os::unix::fs::PermissionsExt;
let p = dir.join("fake-serve");
std::fs::write(
&p,
"#!/bin/sh\nd=$(dirname \"$0\")\nprintf '%s\\n' \"$@\" > \"$d/argv.txt\"\nprintf '%s' \"$PATH\" > \"$d/path.txt\"\necho $$ > \"$d/pid.txt\"\nexec sleep 600\n",
)
.unwrap();
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(0o755)).unwrap();
p
}
pub fn write_forking_script(script: &Path) {
std::fs::write(
script,
"#!/bin/sh\nd=$(dirname \"$0\")\nsleep 600 &\necho $! > \"$d/gpid.txt\"\necho $$ > \"$d/pid.txt\"\nwait\n",
)
.unwrap();
}
pub async fn script_pid(dir: &Path) -> i32 {
let f = dir.join("pid.txt");
for _ in 0..800 {
if let Ok(s) = std::fs::read_to_string(&f)
&& let Ok(p) = s.trim().parse()
{
return p;
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
panic!("script never wrote its pid");
}
pub fn argv(dir: &Path) -> Vec<String> {
std::fs::read_to_string(dir.join("argv.txt"))
.unwrap()
.lines()
.map(str::to_string)
.collect()
}
pub fn alive(pid: i32) -> bool {
unsafe { libc::kill(pid, 0) == 0 }
}
pub fn crash(pid: i32) {
unsafe { libc::kill(pid, libc::SIGKILL) };
}
pub fn operator() -> (ed25519_dalek::SigningKey, String, TrustAnchors) {
let key = ed25519_dalek::SigningKey::from_bytes(&[9u8; 32]);
let pk = key.verifying_key().to_bytes();
let id = key_id_for(&pk);
let mut anchors = TrustAnchors::default();
anchors
.push_signer(&id, &hex_encode(&pk), KeyOrigin::Operator)
.unwrap();
(key, id, anchors)
}
pub fn adopt_model(reg: &ModelRegistry, root: &Path, name: &str, format: ModelFormat) -> PathBuf {
let (key, id, anchors) = operator();
let dir = root.join(name);
std::fs::create_dir_all(&dir).unwrap();
let mut source = ModelSource {
hf_repo: Some("example-org/example-model".into()),
..ModelSource::default()
};
let scanned = match format {
ModelFormat::Gguf | ModelFormat::Ollama => {
let f = dir.join(format!("{name}.gguf"));
std::fs::write(&f, vec![3u8; 2048]).unwrap();
if format == ModelFormat::Ollama {
source = ModelSource {
ollama_tag: Some(format!("{name}:latest")),
..ModelSource::default()
};
}
scan_file(
&f,
AdoptInput {
name: name.into(),
format,
source,
redistributable: false,
},
)
.unwrap()
}
_ => {
std::fs::write(dir.join("model.safetensors"), vec![4u8; 2048]).unwrap();
std::fs::write(dir.join("tokenizer.json"), b"{}").unwrap();
scan_dir(
&dir,
AdoptInput {
name: name.into(),
format,
source,
redistributable: false,
},
)
.unwrap()
}
};
reg.adopt(scanned, &key, &id, &anchors, false).unwrap();
dir
}
pub fn wl_cfg() -> WorkloadConfig {
WorkloadConfig {
mode: RunMode::Listener,
args: vec![],
host: HostContract::default_feed(),
node_id: "n1".into(),
}
}
pub fn spec(role: &str, flavor: InferFlavor, port: u16) -> InferenceSpec {
let mut s = InferenceSpec::new(role, flavor);
s.serve.port = Some(port);
s
}
pub fn workload(s: InferenceSpec) -> VerifiedWorkload {
VerifiedWorkload::inference(s).unwrap()
}
pub fn adopted(flavor: InferFlavor) -> InferRuntime {
InferRuntime::new(InferConfig::adopted(flavor))
}
pub struct Managed {
pub rt: Arc<InferRuntime>,
pub reg: Arc<ModelRegistry>,
pub tmp: tempfile::TempDir,
pub script_dir: PathBuf,
pub data: PathBuf,
}
pub fn managed(flavor: InferFlavor, restart: RestartPolicy) -> Managed {
managed_with(flavor, restart, |_| {})
}
pub fn managed_with(
flavor: InferFlavor,
restart: RestartPolicy,
tweak: impl FnOnce(&mut ManagedConfig),
) -> Managed {
let tmp = tempfile::tempdir().unwrap();
let script_dir = tmp.path().join("bin");
std::fs::create_dir_all(&script_dir).unwrap();
let script = write_script(&script_dir);
let data = tmp.path().join("data");
let reg = Arc::new(
ModelRegistry::in_memory().with_trust(crate::model_manifest::ModelTrust::new(operator().2)),
);
let mut cfg = ManagedConfig::new(reg.clone(), data.clone()).with_serve_program(script);
cfg.restart = restart;
cfg.env_passthrough = vec![];
tweak(&mut cfg);
let rt = Arc::new(InferRuntime::new(InferConfig::managed(flavor, cfg)));
Managed {
rt,
reg,
tmp,
script_dir,
data,
}
}
pub async fn load(rt: &InferRuntime, s: InferenceSpec) -> InstanceHandle {
rt.load(&workload(s), &wl_cfg()).await.unwrap()
}
pub fn infer_permit() -> crate::workload_governance::WorkloadPermitRule {
let mut r = crate::workload_governance::WorkloadPermitRule::new(
"permit-infer",
["workload.*"],
["inference"],
);
r.min_package_trust = crate::workload_governance::PackageTrust::OperatorAttested;
r
}
pub fn adopted_permit() -> crate::workload_governance::WorkloadPermitRule {
let mut r = infer_permit();
r.id = "permit-adopted".into();
r.min_package_trust = crate::workload_governance::PackageTrust::AdoptedUnverified;
r
}