mod support;
use secure_exec_bridge::{LoadFilesystemStateRequest, PersistenceBridge};
use secure_exec_kernel::root_fs::{
decode_snapshot as decode_root_snapshot, ROOT_FILESYSTEM_SNAPSHOT_FORMAT,
};
use secure_exec_sidecar::wire::{
BootstrapRootFilesystemRequest, DisposeReason, DisposeVmRequest, GuestRuntimeKind,
RequestPayload, ResponsePayload, RootFilesystemEntry, RootFilesystemEntryKind,
};
use std::time::Duration;
use support::{
assert_node_available, authenticate_wire, collect_process_output_wire_with_timeout,
create_vm_wire, execute_wire, new_sidecar, open_session_wire, temp_dir, wasm_stdout_module,
wire_request, wire_vm, write_fixture,
};
fn root_entry(path: &str, kind: RootFilesystemEntryKind, executable: bool) -> RootFilesystemEntry {
RootFilesystemEntry {
path: path.to_owned(),
kind,
mode: None,
uid: None,
gid: None,
content: None,
encoding: None,
target: None,
executable,
}
}
#[test]
fn native_sidecar_composes_vm_lifecycle_bridge_callbacks_and_guest_execution() {
assert_node_available();
let mut sidecar = new_sidecar("vm-lifecycle");
let cwd = temp_dir("vm-lifecycle-cwd");
let js_entry = cwd.join("entry.mjs");
let wasm_entry = cwd.join("entry.wasm");
write_fixture(
&js_entry,
r#"
console.log(`js:${process.argv.slice(2).join(",")}`);
"#,
);
write_fixture(&wasm_entry, wasm_stdout_module());
let connection_id = authenticate_wire(&mut sidecar, "conn-1");
let session_id = open_session_wire(&mut sidecar, 2, &connection_id);
let (js_vm_id, js_create) = create_vm_wire(
&mut sidecar,
3,
&connection_id,
&session_id,
GuestRuntimeKind::JavaScript,
&cwd,
);
assert_eq!(js_create.events.len(), 2);
let bootstrap = sidecar
.dispatch_wire_blocking(wire_request(
4,
wire_vm(&connection_id, &session_id, &js_vm_id),
RequestPayload::BootstrapRootFilesystemRequest(BootstrapRootFilesystemRequest {
entries: vec![
root_entry("/workspace", RootFilesystemEntryKind::Directory, false),
root_entry("/workspace/run.sh", RootFilesystemEntryKind::File, true),
],
}),
))
.expect("bootstrap root filesystem");
match bootstrap.response.payload {
ResponsePayload::RootFilesystemBootstrappedResponse(response) => {
assert_eq!(response.entry_count, 2);
}
other => panic!("unexpected bootstrap response: {other:?}"),
}
execute_wire(
&mut sidecar,
5,
&connection_id,
&session_id,
&js_vm_id,
"proc-js",
GuestRuntimeKind::JavaScript,
&js_entry,
vec![String::from("alpha"), String::from("beta")],
);
let (js_stdout, js_stderr, js_exit) = collect_process_output_wire_with_timeout(
&mut sidecar,
&connection_id,
&session_id,
&js_vm_id,
"proc-js",
Duration::from_secs(10),
);
assert_eq!(js_stdout.trim(), "js:alpha,beta");
assert!(js_stderr.is_empty());
assert_eq!(js_exit, 0);
let (wasm_vm_id, _) = create_vm_wire(
&mut sidecar,
6,
&connection_id,
&session_id,
GuestRuntimeKind::WebAssembly,
&cwd,
);
execute_wire(
&mut sidecar,
7,
&connection_id,
&session_id,
&wasm_vm_id,
"proc-wasm",
GuestRuntimeKind::WebAssembly,
&wasm_entry,
Vec::new(),
);
let (wasm_stdout, wasm_stderr, wasm_exit) = collect_process_output_wire_with_timeout(
&mut sidecar,
&connection_id,
&session_id,
&wasm_vm_id,
"proc-wasm",
Duration::from_secs(10),
);
assert_eq!(wasm_stdout.trim(), "wasm:ready");
assert!(wasm_stderr.is_empty());
assert_eq!(wasm_exit, 0);
sidecar
.dispatch_wire_blocking(wire_request(
8,
wire_vm(&connection_id, &session_id, &js_vm_id),
RequestPayload::DisposeVmRequest(DisposeVmRequest {
reason: DisposeReason::Requested,
}),
))
.expect("dispose js vm");
sidecar
.dispatch_wire_blocking(wire_request(
9,
wire_vm(&connection_id, &session_id, &wasm_vm_id),
RequestPayload::DisposeVmRequest(DisposeVmRequest {
reason: DisposeReason::Requested,
}),
))
.expect("dispose wasm vm");
sidecar
.with_bridge_mut(|bridge: &mut support::RecordingBridge| {
let command_checks = bridge
.permission_checks
.iter()
.filter(|check| check.starts_with("cmd:"))
.collect::<Vec<_>>();
if !command_checks.is_empty() {
assert!(command_checks.iter().any(|check| {
*check == &format!("cmd:{js_vm_id}:node")
|| *check == &format!("cmd:{wasm_vm_id}:wasm")
}));
}
let js_snapshot = bridge
.load_filesystem_state(LoadFilesystemStateRequest {
vm_id: js_vm_id.clone(),
})
.expect("load js snapshot")
.expect("persisted js snapshot");
assert_eq!(js_snapshot.format, ROOT_FILESYSTEM_SNAPSHOT_FORMAT);
let js_root =
decode_root_snapshot(&js_snapshot.bytes).expect("decode js root snapshot");
assert!(js_root
.entries
.iter()
.any(|entry| entry.path == "/bin/node"));
assert!(js_root
.entries
.iter()
.any(|entry| entry.path == "/workspace/run.sh"));
let wasm_snapshot = bridge
.load_filesystem_state(LoadFilesystemStateRequest {
vm_id: wasm_vm_id.clone(),
})
.expect("load wasm snapshot")
.expect("persisted wasm snapshot");
assert_eq!(wasm_snapshot.format, ROOT_FILESYSTEM_SNAPSHOT_FORMAT);
let wasm_root =
decode_root_snapshot(&wasm_snapshot.bytes).expect("decode wasm root snapshot");
assert!(!wasm_root
.entries
.iter()
.any(|entry| entry.path == "/workspace/run.sh"));
assert!(bridge.lifecycle_events.iter().any(|event| {
event.vm_id == js_vm_id && event.state == secure_exec_bridge::LifecycleState::Busy
}));
})
.expect("inspect bridge");
}