Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use thiserror::*;
pub mod firecracker;
pub mod manager;

pub const DEFAULT_BOOT_ARGS: &str = "random.trust_cpu=on reboot=k panic=1 pci=off";
pub const DEFAULT_BOOT_ARGS: &str =
"random.trust_cpu=on reboot=k panic=1 pci=off console=ttyS0";
pub const NETWORK_MAGIC_MAC_START: &str = "06:00";
pub const NETWORK_MASK_SHORT: u8 = 30;
pub const NETWORK_MAX_ALLOCATIONS: u8 = 200;
Expand Down
25 changes: 23 additions & 2 deletions manager/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use config::{
};
use github::GitHub;
use rand::distributions::{Alphanumeric, DistString};
use std::{fs, os::unix::process::CommandExt, path::PathBuf, process::Command};
use std::{fs, io::Read, os::unix::process::CommandExt, path::PathBuf, process::Command};
use tracing::{debug, info, instrument, warn};
use util::mount;

Expand Down Expand Up @@ -93,6 +93,26 @@ impl Instance {
self.work_dir.as_std_path().join("firecracker.pid")
}

pub fn console_log_path(&self) -> Utf8PathBuf {
self.work_dir.join("console.log")
}

/// Return the last `n` lines of the VM serial console log, or an empty string if unavailable.
pub fn read_console_tail(&self, n: usize) -> String {
let path = self.console_log_path();
let mut file = match fs::File::open(&path) {
Ok(f) => f,
Err(_) => return String::new(),
};
let mut content = String::new();
if file.read_to_string(&mut content).is_err() {
return String::new();
}
let lines: Vec<&str> = content.lines().collect();
let start = lines.len().saturating_sub(n);
lines[start..].join("\n")
}

#[instrument(skip(self), fields(role = %self.role, idx = self.idx))]
pub fn setup(&mut self) -> Result<()> {
debug!(work_dir = %self.work_dir, "creating work directory");
Expand Down Expand Up @@ -208,12 +228,13 @@ impl Instance {
self.setup_run()?;

debug!(role = %self.role, idx = self.idx, "spawning Firecracker");
let console_file = fs::File::create(self.console_log_path())?;
let child = unsafe {
Command::new("firecracker")
.args(["--config-file", "config.json", "--no-api"])
.current_dir(&self.work_dir)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stdout(console_file)
.stderr(std::process::Stdio::piped())
.pre_exec(|| {
if libc::setsid() == -1 {
Expand Down
23 changes: 21 additions & 2 deletions manager/src/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use opentelemetry::KeyValue;
use std::io::Read;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use std::time::{Duration, Instant};
use tracing::{error, info, instrument, warn};

use crate::{instance::Instance, network::NetworkAllocation};
Expand Down Expand Up @@ -75,6 +75,7 @@ pub fn run_slot(config: Arc<ManagerConfig>, role: &Role, idx: usize) -> Result<(
error!(role = %role.name, idx, pid, error = %e, "failed to write PID file");
}

let cycle_start = Instant::now();
let status = {
let _running = tracing::info_span!(
"vm_running",
Expand All @@ -86,6 +87,7 @@ pub fn run_slot(config: Arc<ManagerConfig>, role: &Role, idx: usize) -> Result<(
.entered();
child.wait()
};
let cycle_secs = cycle_start.elapsed().as_secs();

match status {
Ok(status) => {
Expand All @@ -99,6 +101,7 @@ pub fn run_slot(config: Arc<ManagerConfig>, role: &Role, idx: usize) -> Result<(
idx,
cycle = cycle_n,
exit_code = status.code(),
duration_secs = cycle_secs,
result = result_str,
"Firecracker exited"
);
Expand All @@ -109,10 +112,26 @@ pub fn run_slot(config: Arc<ManagerConfig>, role: &Role, idx: usize) -> Result<(
idx,
cycle = cycle_n,
exit_code = status.code(),
"firecracker: {}", line
"firecracker stderr: {}", line
);
}
}
// Log console tail for short cycles — these indicate a failure inside the VM
if cycle_secs < 60 {
let tail = instance.read_console_tail(40);
if !tail.is_empty() {
warn!(
role = %role.name,
idx,
cycle = cycle_n,
duration_secs = cycle_secs,
"VM cycled quickly — console tail follows"
);
for line in tail.lines() {
warn!(role = %role.name, idx, cycle = cycle_n, "console: {}", line);
}
}
}
jobs_counter.add(
1,
&[
Expand Down
Loading