diff --git a/monocore/Cargo.toml b/monocore/Cargo.toml index 7a9effe..0b4aef7 100644 --- a/monocore/Cargo.toml +++ b/monocore/Cargo.toml @@ -15,6 +15,10 @@ path = "lib/lib.rs" name = "monocore" path = "bin/monocore.rs" +[[bin]] +name = "mcrun" +path = "bin/mcrun.rs" + [[test]] name = "integration_cli" path = "tests/cli/mod.rs" diff --git a/monocore/bin/mcrun.rs b/monocore/bin/mcrun.rs new file mode 100644 index 0000000..a1b4263 --- /dev/null +++ b/monocore/bin/mcrun.rs @@ -0,0 +1,154 @@ +//! `mcrun` is a polymorphic binary that can operate in two modes: MicroVM or supervisor. +//! +//! # Overview +//! +//! This binary provides a unified interface for running either: +//! - A MicroVM that provides an isolated execution environment +//! - A supervisor process that can manage and monitor child processes +//! +//! ## Usage +//! +//! ### MicroVM Mode +//! +//! To run as a MicroVM: +//! ```bash +//! mcrun microvm \ +//! --root-path=/path/to/rootfs \ +//! --ram-mib=1024 \ +//! --mapped-dirs=/host/path:/guest/path \ +//! --port-map=8080:80 \ +//! --workdir-path=/app \ +//! --exec-path=/usr/bin/python3 \ +//! --args="-m" --args="http.server" --args="8080" +//! ``` +//! +//! ### Supervisor Mode +//! +//! To run as a supervisor: +//! ```bash +//! mcrun supervisor \ +//! --log-dir=/path/to/logs \ +//! --child-name=my_vm \ +//! --db-path=/path/to/mcrun.db +//! ``` + +use std::env; + +use anyhow::Result; +use clap::Parser; +use monocore::{ + cli::{McrunArgs, McrunSubcommand}, + config::{EnvPair, PathPair, PortPair}, + runtime::MicroVmMonitor, + vm::MicroVm, +}; +use monoutils::runtime::Supervisor; + +//-------------------------------------------------------------------------------------------------- +// Constants +//-------------------------------------------------------------------------------------------------- + +/// Log file prefix for mcrun processes +const MCRUN_LOG_PREFIX: &str = "mcrun"; + +//-------------------------------------------------------------------------------------------------- +// Functions: main +//-------------------------------------------------------------------------------------------------- + +#[tokio::main] +async fn main() -> Result<()> { + // Initialize logging without ANSI colors + tracing_subscriber::fmt().with_ansi(false).init(); + + // Parse command line arguments + let args = McrunArgs::parse(); + + match args.subcommand { + McrunSubcommand::Microvm { + root_path, + num_vcpus, + ram_mib, + mapped_dirs, + port_map, + workdir_path, + exec_path, + args, + env, + } => { + // Parse mapped directories + let mapped_dirs: Vec = mapped_dirs + .iter() + .map(|s| s.parse()) + .collect::>()?; + + // Parse port mappings + let port_map: Vec = port_map + .iter() + .map(|s| s.parse()) + .collect::>()?; + + // Parse environment variables + let env: Vec = env.iter().map(|s| s.parse()).collect::>()?; + + // Create and configure MicroVM + let mut builder = MicroVm::builder() + .root_path(root_path) + .num_vcpus(num_vcpus) + .ram_mib(ram_mib) + .mapped_dirs(mapped_dirs) + .port_map(port_map) + .exec_path(exec_path) + .args(args.iter().map(|s| s.as_str())) + .env(env); + + if let Some(workdir_path) = workdir_path { + builder = builder.workdir_path(workdir_path); + } + + // Build and start the MicroVM + let vm = builder.build()?; + + tracing::info!("Starting MicroVM"); + vm.start()?; + } + McrunSubcommand::Supervisor { + log_dir, + child_name, + sandbox_db_path, + } => { + // Get current executable path + let current_exe = env::current_exe()?; + + // Get supervisor PID + let supervisor_pid = std::process::id(); + + // Create microvm monitor + let microvm_monitor = MicroVmMonitor::new(sandbox_db_path, supervisor_pid).await?; + + // Compose child arguments - these are placeholders that will be overridden + let child_args = vec![ + "microvm".to_string(), + "--root-path=/".to_string(), + "--ram-mib=512".to_string(), + ]; + + // Compose child environment variables + let child_envs = vec![("RUST_LOG", "info")]; + + // Create and start supervisor + let mut supervisor = Supervisor::new( + current_exe, + child_args, + child_envs, + child_name, + MCRUN_LOG_PREFIX, + log_dir, + microvm_monitor, + ); + + supervisor.start().await?; + } + } + + Ok(()) +} diff --git a/monocore/lib/cli/args/mcrun.rs b/monocore/lib/cli/args/mcrun.rs new file mode 100644 index 0000000..c6d2272 --- /dev/null +++ b/monocore/lib/cli/args/mcrun.rs @@ -0,0 +1,75 @@ +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; + +use crate::cli::styles; + +//-------------------------------------------------------------------------------------------------- +// Types +//-------------------------------------------------------------------------------------------------- + +/// Arguments for the mcrun command +#[derive(Debug, Parser)] +#[command(name = "mcrun", author, styles=styles::styles())] +pub struct McrunArgs { + /// The subcommand to run + #[command(subcommand)] + pub subcommand: McrunSubcommand, +} + +/// Available subcommands for managing microvms +#[derive(Subcommand, Debug)] +pub enum McrunSubcommand { + /// Run as microvm + Microvm { + /// Root filesystem path + #[arg(long)] + root_path: PathBuf, + + /// Number of virtual CPUs + #[arg(long)] + num_vcpus: u8, + + /// RAM size in MiB + #[arg(long)] + ram_mib: u32, + + /// Directory mappings (host:guest format) + #[arg(long)] + mapped_dirs: Vec, + + /// Port mappings (host:guest format) + #[arg(long)] + port_map: Vec, + + /// Working directory path + #[arg(long)] + workdir_path: Option, + + /// Executable path + #[arg(long)] + exec_path: String, + + /// Arguments for the executable + #[arg(long)] + args: Vec, + + /// Environment variables (KEY=VALUE format) + #[arg(long)] + env: Vec, + }, + /// Run as supervisor + Supervisor { + /// Directory for log files + #[arg(long)] + log_dir: PathBuf, + + /// Name of the child process + #[arg(long)] + child_name: String, + + /// Path to the sandbox metrics and metadata database file + #[arg(long)] + sandbox_db_path: PathBuf, + }, +} diff --git a/monocore/lib/runtime/orchestrator.rs b/monocore/lib/cli/args/mod.rs similarity index 73% rename from monocore/lib/runtime/orchestrator.rs rename to monocore/lib/cli/args/mod.rs index 95f6bec..f6f3a87 100644 --- a/monocore/lib/runtime/orchestrator.rs +++ b/monocore/lib/cli/args/mod.rs @@ -1,7 +1,9 @@ +mod mcrun; +mod monocore; //-------------------------------------------------------------------------------------------------- -// Types +// Exports //-------------------------------------------------------------------------------------------------- -/// TODO: Implement this -pub struct Orchestrator {} +pub use mcrun::*; +pub use monocore::*; diff --git a/monocore/lib/cli/args.rs b/monocore/lib/cli/args/monocore.rs similarity index 99% rename from monocore/lib/cli/args.rs rename to monocore/lib/cli/args/monocore.rs index 4126774..13cf90f 100644 --- a/monocore/lib/cli/args.rs +++ b/monocore/lib/cli/args/monocore.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use super::styles; +use crate::cli::styles; use clap::Parser; use typed_path::Utf8UnixPathBuf; diff --git a/monocore/lib/config/defaults.rs b/monocore/lib/config/defaults.rs index 8bd60f2..9d17ff0 100644 --- a/monocore/lib/config/defaults.rs +++ b/monocore/lib/config/defaults.rs @@ -12,13 +12,16 @@ pub const DEFAULT_NUM_VCPUS: u8 = 1; /// The default amount of RAM in MiB to use for the MicroVm. pub const DEFAULT_RAM_MIB: u32 = 1024; -/// Default port for the HTTP server -pub const DEFAULT_SERVER_PORT: u16 = 3456; - /// The path where all monocore global data is stored. pub static DEFAULT_MONOCORE_HOME: LazyLock = LazyLock::new(|| dirs::home_dir().unwrap().join(MONOCORE_HOME_DIR)); +/// The default path for the mcrun binary. +pub const DEFAULT_MCRUN_BIN_PATH: &str = "./mcrun"; + +/// The default path for the monofs binary. +pub const DEFAULT_MONOFS_BIN_PATH: &str = "./monofs"; + /// The default configuration file content pub(crate) const DEFAULT_CONFIG: &str = r#"# Sandbox configurations sandboxes: [] diff --git a/monocore/lib/runtime/mod.rs b/monocore/lib/runtime/mod.rs index 1afd85e..b9619a2 100644 --- a/monocore/lib/runtime/mod.rs +++ b/monocore/lib/runtime/mod.rs @@ -1,9 +1,9 @@ //! Runtime components for the Monocore runtime. -mod orchestrator; +mod monitor; //-------------------------------------------------------------------------------------------------- // Exports //-------------------------------------------------------------------------------------------------- -pub use orchestrator::*; +pub use monitor::*; diff --git a/monocore/lib/runtime/monitor.rs b/monocore/lib/runtime/monitor.rs new file mode 100644 index 0000000..0d9c4eb --- /dev/null +++ b/monocore/lib/runtime/monitor.rs @@ -0,0 +1,134 @@ +use std::path::Path; +use std::{io::Write, path::PathBuf}; + +use async_trait::async_trait; +use monoutils::{MonoutilsError, MonoutilsResult, ProcessMonitor, RotatingLog}; +use sqlx::{Pool, Sqlite}; +use tokio::io::AsyncReadExt; +use tokio::process::{ChildStderr, ChildStdout}; + +use crate::{management, MonocoreResult}; + +//-------------------------------------------------------------------------------------------------- +// Types +//-------------------------------------------------------------------------------------------------- + +/// A process monitor for MicroVMs +pub struct MicroVmMonitor { + /// The database for tracking sandbox metrics and metadata + sandbox_db: Pool, + + /// The supervisor PID + supervisor_pid: u32, + + /// The MicroVM log path + microvm_log_path: Option, +} + +//-------------------------------------------------------------------------------------------------- +// Methods +//-------------------------------------------------------------------------------------------------- + +impl MicroVmMonitor { + /// Create a new MicroVM monitor + pub async fn new(database: impl AsRef, supervisor_pid: u32) -> MonocoreResult { + Ok(Self { + sandbox_db: management::get_sandbox_db_pool(database.as_ref()).await?, + supervisor_pid, + microvm_log_path: None, + }) + } +} + +//-------------------------------------------------------------------------------------------------- +// Trait Implementations +//-------------------------------------------------------------------------------------------------- + +#[async_trait] +impl ProcessMonitor for MicroVmMonitor { + async fn start( + &mut self, + pid: u32, + mut stdout: ChildStdout, + mut stderr: ChildStderr, + log_path: PathBuf, + ) -> MonoutilsResult<()> { + let microvm_log = RotatingLog::new(&log_path).await?; + let mut stdout_writer = microvm_log.get_sync_writer(); + let mut stderr_writer = microvm_log.get_sync_writer(); + let microvm_pid = pid; + + self.microvm_log_path = Some(log_path); + + // Insert sandbox entry into database + sqlx::query( + r#" + INSERT INTO sandboxes (supervisor_pid, microvm_pid) + VALUES (?, ?) + "#, + ) + .bind(self.supervisor_pid) + .bind(microvm_pid) + .execute(&self.sandbox_db) + .await + .map_err(MonoutilsError::custom)?; + + // Spawn tasks to handle stdout/stderr + tokio::spawn(async move { + let mut buf = [0u8; 1024]; + + while let Ok(n) = stdout.read(&mut buf).await { + if n == 0 { + break; + } + if let Err(e) = stdout_writer.write_all(&buf[..n]) { + tracing::error!(microvm_pid = microvm_pid, error = %e, "Failed to write to microvm stdout log"); + } + if let Err(e) = stdout_writer.flush() { + tracing::error!(microvm_pid = microvm_pid, error = %e, "Failed to flush microvm stdout log"); + } + } + }); + + tokio::spawn(async move { + let mut buf = [0u8; 1024]; + + while let Ok(n) = stderr.read(&mut buf).await { + if n == 0 { + break; + } + if let Err(e) = stderr_writer.write_all(&buf[..n]) { + tracing::error!(microvm_pid = microvm_pid, error = %e, "Failed to write to microvm stderr log"); + } + if let Err(e) = stderr_writer.flush() { + tracing::error!(microvm_pid = microvm_pid, error = %e, "Failed to flush microvm stderr log"); + } + } + }); + + Ok(()) + } + + async fn stop(&mut self) -> MonoutilsResult<()> { + // Remove sandbox entry from database + sqlx::query( + r#" + DELETE FROM sandboxes + WHERE supervisor_pid = ? + "#, + ) + .bind(self.supervisor_pid) + .execute(&self.sandbox_db) + .await + .map_err(MonoutilsError::custom)?; + + // Delete the log file if it exists + if let Some(log_path) = &self.microvm_log_path { + if let Err(e) = tokio::fs::remove_file(log_path).await { + tracing::warn!(error = %e, "Failed to delete microvm log file"); + } + } + + Ok(()) + } +} diff --git a/monofs/bin/mfsrun.rs b/monofs/bin/mfsrun.rs index b7d2db5..eeb1135 100644 --- a/monofs/bin/mfsrun.rs +++ b/monofs/bin/mfsrun.rs @@ -119,7 +119,7 @@ async fn main() -> Result<()> { host, port, store_dir, - db_path, + fs_db_path, mount_dir, } => { // Get current executable path @@ -130,7 +130,7 @@ async fn main() -> Result<()> { // Create nfs server monitor let nfs_server_monitor = - NfsServerMonitor::new(db_path, supervisor_pid, mount_dir).await?; + NfsServerMonitor::new(fs_db_path, supervisor_pid, mount_dir).await?; // Compose child arguments let child_args = vec![ diff --git a/monofs/lib/cli/args/mfsrun.rs b/monofs/lib/cli/args/mfsrun.rs index 8577ff4..4e3b8ab 100644 --- a/monofs/lib/cli/args/mfsrun.rs +++ b/monofs/lib/cli/args/mfsrun.rs @@ -13,7 +13,7 @@ use crate::{ /// Arguments for the mfsrun command #[derive(Debug, Parser)] -#[command(name = "monofs", author, styles=styles::styles())] +#[command(name = "mfsrun", author, styles=styles::styles())] pub struct MfsRuntimeArgs { /// The subcommand to run #[command(subcommand)] @@ -59,9 +59,9 @@ pub enum MfsRuntimeSubcommand { #[arg(long)] store_dir: PathBuf, - /// Path to the metrics database file + /// Path to the filesystem metrics and metadata database file #[arg(long)] - db_path: PathBuf, + fs_db_path: PathBuf, /// Directory where the filesystem is mounted #[arg(long)] diff --git a/monofs/lib/management/mfs.rs b/monofs/lib/management/mfs.rs index 437d22d..4925eb1 100644 --- a/monofs/lib/management/mfs.rs +++ b/monofs/lib/management/mfs.rs @@ -1,10 +1,12 @@ +use crate::config::DEFAULT_MFSRUN_BIN_PATH; use crate::management::db; use crate::management::find; use crate::utils::path::{BLOCKS_SUBDIR, FS_DB_FILENAME, LOG_SUBDIR, MFS_LINK_FILENAME}; +use crate::utils::MFSRUN_BIN_PATH_ENV_VAR; use crate::FsError; use crate::{ config::{DEFAULT_HOST, DEFAULT_NFS_PORT}, - utils::path::{self, MFS_DIR_SUFFIX}, + utils::path::MFS_DIR_SUFFIX, FsResult, }; use nix::sys::signal::{self, Signal}; @@ -79,7 +81,8 @@ pub async fn init_mfs(mount_dir: Option) -> FsResult { .map(|name| name.to_string_lossy().to_string()) .expect("Failed to get file name for mount point"); - let mfsrun_path = path::resolve_mfsrun_bin_path()?; + let mfsrun_path = + monoutils::path::resolve_binary_path(MFSRUN_BIN_PATH_ENV_VAR, DEFAULT_MFSRUN_BIN_PATH)?; tracing::info!("Mounting the filesystem..."); let status = Command::new(mfsrun_path) diff --git a/monofs/lib/runtime/monitor.rs b/monofs/lib/runtime/monitor.rs index 569be5c..e2b7cf4 100644 --- a/monofs/lib/runtime/monitor.rs +++ b/monofs/lib/runtime/monitor.rs @@ -15,8 +15,8 @@ use crate::{management, FsResult}; /// A process monitor for the NFS server pub struct NfsServerMonitor { - /// The database for tracking metrics and metadata. - database: Pool, + /// The database for tracking filesystem metrics and metadata. + fs_db: Pool, /// The supervisor PID supervisor_pid: u32, @@ -28,15 +28,19 @@ pub struct NfsServerMonitor { nfsserver_log_path: Option, } +//-------------------------------------------------------------------------------------------------- +// Methods +//-------------------------------------------------------------------------------------------------- + impl NfsServerMonitor { /// Create a new NFS server monitor pub async fn new( - database: impl AsRef, + fs_db_path: impl AsRef, supervisor_pid: u32, mount_dir: PathBuf, ) -> FsResult { Ok(Self { - database: management::get_fs_db_pool(database.as_ref()).await?, + fs_db: management::get_fs_db_pool(fs_db_path.as_ref()).await?, supervisor_pid, mount_dir, nfsserver_log_path: None, @@ -44,6 +48,10 @@ impl NfsServerMonitor { } } +//-------------------------------------------------------------------------------------------------- +// Trait Implementations +//-------------------------------------------------------------------------------------------------- + #[async_trait] impl ProcessMonitor for NfsServerMonitor { async fn start( @@ -60,7 +68,7 @@ impl ProcessMonitor for NfsServerMonitor { self.nfsserver_log_path = Some(log_path); - // Insert filesystem entry into database + // Insert filesystem entry into fs_db sqlx::query( r#" INSERT INTO filesystems (mount_dir, supervisor_pid, nfsserver_pid) @@ -70,7 +78,7 @@ impl ProcessMonitor for NfsServerMonitor { .bind(self.mount_dir.to_string_lossy().to_string()) .bind(self.supervisor_pid) .bind(nfs_server_pid) - .execute(&self.database) + .execute(&self.fs_db) .await .map_err(MonoutilsError::custom)?; @@ -111,7 +119,7 @@ impl ProcessMonitor for NfsServerMonitor { } async fn stop(&mut self) -> MonoutilsResult<()> { - // Remove filesystem entry from database + // Remove filesystem entry from fs_db sqlx::query( r#" DELETE FROM filesystems @@ -120,7 +128,7 @@ impl ProcessMonitor for NfsServerMonitor { ) .bind(self.mount_dir.to_string_lossy().to_string()) .bind(self.supervisor_pid) - .execute(&self.database) + .execute(&self.fs_db) .await .map_err(MonoutilsError::custom)?; @@ -131,6 +139,9 @@ impl ProcessMonitor for NfsServerMonitor { } } + // Reset the log path + self.nfsserver_log_path = None; + Ok(()) } } diff --git a/monofs/lib/utils/path.rs b/monofs/lib/utils/path.rs index d9a687c..763da51 100644 --- a/monofs/lib/utils/path.rs +++ b/monofs/lib/utils/path.rs @@ -1,12 +1,8 @@ //! Path utilities. -use std::path::PathBuf; - use typed_path::Utf8UnixPath; -use crate::{config::DEFAULT_MFSRUN_BIN_PATH, filesystem::Utf8UnixPathSegment, FsError, FsResult}; - -use super::MFSRUN_BIN_PATH_ENV_VAR; +use crate::{filesystem::Utf8UnixPathSegment, FsError, FsResult}; //-------------------------------------------------------------------------------------------------- // Constants @@ -58,34 +54,6 @@ pub fn split_last(path: &Utf8UnixPath) -> FsResult<(Option<&Utf8UnixPath>, Utf8U Ok((parent, filename)) } -/// Resolves the path to the mfsrun binary, checking both environment variable and default locations. -/// -/// First checks the environment variable specified by MFSRUN_BIN_PATH_ENV_VAR. -/// If that's not set, falls back to DEFAULT_MFSRUN_BIN_PATH. -/// Returns an error if the binary is not found at the resolved location. -pub fn resolve_mfsrun_bin_path() -> FsResult { - const MFSRUN_ENV_SOURCE: &str = "environment variable"; - const MFSRUN_DEFAULT_SOURCE: &str = "default path"; - - let (path, source) = std::env::var(MFSRUN_BIN_PATH_ENV_VAR) - .map(|p| (PathBuf::from(p), MFSRUN_ENV_SOURCE)) - .unwrap_or_else(|_| { - ( - PathBuf::from(DEFAULT_MFSRUN_BIN_PATH), - MFSRUN_DEFAULT_SOURCE, - ) - }); - - if !path.exists() { - return Err(FsError::MfsrunBinaryNotFound { - path: path.to_string_lossy().to_string(), - src: source.to_string(), - }); - } - - Ok(path) -} - //-------------------------------------------------------------------------------------------------- // Tests //-------------------------------------------------------------------------------------------------- diff --git a/monoutils/lib/error.rs b/monoutils/lib/error.rs index ea677e9..448ea11 100644 --- a/monoutils/lib/error.rs +++ b/monoutils/lib/error.rs @@ -19,6 +19,10 @@ pub enum MonoutilsError { #[error("path validation error: {0}")] PathValidation(String), + /// An error that occurred when resolving a binary + #[error("binary not found at: {0}\nSource: {1}")] + BinaryNotFound(String, String), + /// An error that occurred when performing an IO operation #[error("io error: {0}")] IoError(#[from] std::io::Error), diff --git a/monoutils/lib/path.rs b/monoutils/lib/path.rs index 0a193aa..38e766a 100644 --- a/monoutils/lib/path.rs +++ b/monoutils/lib/path.rs @@ -1,5 +1,7 @@ //! `monoutils::path` is a module containing path utilities for the monocore project. +use std::path::PathBuf; + use typed_path::{Utf8UnixComponent, Utf8UnixPathBuf}; use crate::{MonoutilsError, MonoutilsResult}; @@ -128,6 +130,26 @@ pub fn normalize_path(path: &str, path_type: SupportedPathType) -> MonoutilsResu } } +/// Resolves the path to a binary, checking both environment variable and default locations. +/// +/// First checks the environment variable specified by `env_var`. +/// If that's not set, falls back to `default_path`. +/// Returns an error if the binary is not found at the resolved location. +pub fn resolve_binary_path(env_var: &str, default_path: &str) -> MonoutilsResult { + let (path, source) = std::env::var(env_var) + .map(|p| (PathBuf::from(p), "environment variable")) + .unwrap_or_else(|_| (PathBuf::from(default_path), "default path")); + + if !path.exists() { + return Err(MonoutilsError::BinaryNotFound( + path.to_string_lossy().to_string(), + source.to_string(), + )); + } + + Ok(path) +} + //-------------------------------------------------------------------------------------------------- // Tests //--------------------------------------------------------------------------------------------------