Prepare, configure, and manage Firecracker microVMs in seconds!
virtualization linux microvm firecracker
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: implement MicroVM management with create, delete, start, and stop functionalities

+506 -46
+76 -4
Cargo.lock
··· 853 853 ] 854 854 855 855 [[package]] 856 + name = "env_filter" 857 + version = "0.1.3" 858 + source = "registry+https://github.com/rust-lang/crates.io-index" 859 + checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 860 + dependencies = [ 861 + "log", 862 + "regex", 863 + ] 864 + 865 + [[package]] 866 + name = "env_logger" 867 + version = "0.11.8" 868 + source = "registry+https://github.com/rust-lang/crates.io-index" 869 + checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" 870 + dependencies = [ 871 + "anstream", 872 + "anstyle", 873 + "env_filter", 874 + "jiff", 875 + "log", 876 + ] 877 + 878 + [[package]] 856 879 name = "equivalent" 857 880 version = "1.0.2" 858 881 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 923 946 dependencies = [ 924 947 "actix-web", 925 948 "anyhow", 949 + "env_logger", 950 + "firecracker-prepare", 951 + "firecracker-process", 952 + "firecracker-state", 953 + "firecracker-vm", 954 + "names", 926 955 "owo-colors", 927 956 "serde", 957 + "serde_json", 958 + "sqlx", 959 + "tokio-stream", 928 960 "utoipa", 929 961 "utoipa-actix-web", 930 962 "utoipa-rapidoc", ··· 1199 1231 1200 1232 [[package]] 1201 1233 name = "glob" 1202 - version = "0.3.2" 1234 + version = "0.3.3" 1203 1235 source = "registry+https://github.com/rust-lang/crates.io-index" 1204 - checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 1236 + checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 1205 1237 1206 1238 [[package]] 1207 1239 name = "h2" ··· 1683 1715 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1684 1716 1685 1717 [[package]] 1718 + name = "jiff" 1719 + version = "0.2.15" 1720 + source = "registry+https://github.com/rust-lang/crates.io-index" 1721 + checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" 1722 + dependencies = [ 1723 + "jiff-static", 1724 + "log", 1725 + "portable-atomic", 1726 + "portable-atomic-util", 1727 + "serde", 1728 + ] 1729 + 1730 + [[package]] 1731 + name = "jiff-static" 1732 + version = "0.2.15" 1733 + source = "registry+https://github.com/rust-lang/crates.io-index" 1734 + checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" 1735 + dependencies = [ 1736 + "proc-macro2", 1737 + "quote", 1738 + "syn 2.0.104", 1739 + ] 1740 + 1741 + [[package]] 1686 1742 name = "jobserver" 1687 1743 version = "0.1.34" 1688 1744 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2166 2222 version = "0.3.32" 2167 2223 source = "registry+https://github.com/rust-lang/crates.io-index" 2168 2224 checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 2225 + 2226 + [[package]] 2227 + name = "portable-atomic" 2228 + version = "1.11.1" 2229 + source = "registry+https://github.com/rust-lang/crates.io-index" 2230 + checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 2231 + 2232 + [[package]] 2233 + name = "portable-atomic-util" 2234 + version = "0.2.4" 2235 + source = "registry+https://github.com/rust-lang/crates.io-index" 2236 + checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" 2237 + dependencies = [ 2238 + "portable-atomic", 2239 + ] 2169 2240 2170 2241 [[package]] 2171 2242 name = "potential_utf" ··· 2752 2823 2753 2824 [[package]] 2754 2825 name = "serde_json" 2755 - version = "1.0.141" 2826 + version = "1.0.145" 2756 2827 source = "registry+https://github.com/rust-lang/crates.io-index" 2757 - checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" 2828 + checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 2758 2829 dependencies = [ 2759 2830 "itoa", 2760 2831 "memchr", 2761 2832 "ryu", 2762 2833 "serde", 2834 + "serde_core", 2763 2835 ] 2764 2836 2765 2837 [[package]]
+16
crates/fire-server/Cargo.toml
··· 9 9 [dependencies] 10 10 actix-web = "4.11.0" 11 11 anyhow = "1.0.99" 12 + env_logger = "0.11.8" 12 13 owo-colors = "4.2.2" 13 14 serde = { version = "1.0.225", features = ["serde_derive", "derive"] } 15 + tokio-stream = "0.1.17" 14 16 utoipa = { version = "5.4.0", features = ["actix_extras"] } 15 17 utoipa-actix-web = "0.1.2" 16 18 utoipa-rapidoc = { version = "6.0.0", features = ["actix-web"] } 17 19 utoipa-redoc = { version = "6.0.0", features = ["actix-web"] } 18 20 utoipa-swagger-ui = { version = "9", features = ["actix-web", "reqwest"] } 21 + firecracker-state = { path = "../firecracker-state" } 22 + firecracker-vm = { path = "../firecracker-vm" } 23 + firecracker-prepare = { path = "../firecracker-prepare" } 24 + firecracker-process = { path = "../firecracker-process" } 25 + serde_json = "1.0.145" 26 + sqlx = { version = "0.8.6", features = [ 27 + "runtime-tokio", 28 + "tls-rustls", 29 + "sqlite", 30 + "chrono", 31 + "derive", 32 + "macros", 33 + ] } 34 + names = "0.14.0"
+98 -26
crates/fire-server/src/api/microvm.rs
··· 1 - use actix_web::{delete, get, post, Responder}; 1 + use std::sync::Arc; 2 + 3 + use actix_web::{delete, get, post, web, HttpResponse, Responder}; 4 + use firecracker_state::repo; 2 5 use serde::{Deserialize, Serialize}; 6 + use sqlx::{Pool, Sqlite}; 7 + use tokio_stream::StreamExt; 3 8 use utoipa::ToSchema; 4 9 use utoipa_actix_web::service_config::ServiceConfig; 5 10 11 + use crate::{ 12 + read_payload, services, 13 + types::microvm::{CreateMicroVM, MicroVM}, 14 + }; 15 + 6 16 const MICRO_VM: &str = "MicroVM"; 7 17 8 18 #[derive(Serialize, Deserialize, Clone, ToSchema)] 9 - pub(super) struct MicroVM { 10 - pub id: String, 11 - pub name: String, 12 - pub status: String, 13 - pub vcpus: u8, 14 - pub memory_mb: u32, 15 - pub kernel_image: String, 16 - pub rootfs_image: String, 17 - pub ssh_keys: Vec<String>, 18 - } 19 - 20 - #[derive(Serialize, Deserialize, Clone, ToSchema)] 21 - pub(super) enum ErrorResponse { 19 + pub enum ErrorResponse { 22 20 NotFound(String), 23 21 Conflict(String), 24 22 Unauthorized(String), ··· 32 30 ) 33 31 )] 34 32 #[post("")] 35 - async fn create_microvm() -> Result<impl Responder, actix_web::Error> { 36 - Ok("MicroVM created") 33 + async fn create_microvm( 34 + mut payload: web::Payload, 35 + pool: web::Data<Arc<Pool<Sqlite>>>, 36 + ) -> Result<impl Responder, actix_web::Error> { 37 + let body = read_payload!(payload); 38 + let params = match body.is_empty() { 39 + true => CreateMicroVM { 40 + name: None, 41 + vcpus: None, 42 + memory: None, 43 + image: None, 44 + vmlinux: None, 45 + rootfs: None, 46 + boot_args: None, 47 + ssh_keys: None, 48 + start: None, 49 + }, 50 + false => serde_json::from_slice::<CreateMicroVM>(&body)?, 51 + }; 52 + let pool = pool.get_ref().clone(); 53 + let vm = services::microvm::create_microvm(pool, params) 54 + .await 55 + .map_err(actix_web::error::ErrorInternalServerError)?; 56 + Ok(HttpResponse::Created().json(vm)) 37 57 } 38 58 39 59 #[utoipa::path( ··· 47 67 ) 48 68 )] 49 69 #[delete("/{id}")] 50 - async fn delete_microvm() -> Result<impl Responder, actix_web::Error> { 51 - Ok("MicroVM deleted") 70 + async fn delete_microvm( 71 + id: web::Path<String>, 72 + pool: web::Data<Arc<Pool<Sqlite>>>, 73 + ) -> Result<impl Responder, actix_web::Error> { 74 + let id = id.into_inner(); 75 + let pool = pool.get_ref().clone(); 76 + let vm = services::microvm::delete_microvm(pool, &id) 77 + .await 78 + .map_err(actix_web::error::ErrorInternalServerError)?; 79 + 80 + if vm.is_none() { 81 + return Ok(HttpResponse::NotFound().json(ErrorResponse::NotFound(id))); 82 + } 83 + 84 + Ok(HttpResponse::Ok().json(vm)) 52 85 } 53 86 54 87 #[utoipa::path( ··· 62 95 ) 63 96 )] 64 97 #[get("/{id}")] 65 - async fn get_microvm() -> Result<impl Responder, actix_web::Error> { 66 - Ok("MicroVM details") 98 + async fn get_microvm( 99 + id: web::Path<String>, 100 + pool: web::Data<Arc<Pool<Sqlite>>>, 101 + ) -> Result<impl Responder, actix_web::Error> { 102 + let id = id.into_inner(); 103 + let pool = pool.get_ref().clone(); 104 + let vm = repo::virtual_machine::find(&pool, &id) 105 + .await 106 + .map_err(actix_web::error::ErrorInternalServerError)?; 107 + 108 + Ok(match vm { 109 + Some(vm) => HttpResponse::Ok().json(vm), 110 + None => HttpResponse::NotFound().json(ErrorResponse::NotFound(id)), 111 + }) 67 112 } 68 113 69 114 #[utoipa::path( ··· 73 118 ) 74 119 )] 75 120 #[get("")] 76 - async fn list_microvms() -> Result<impl Responder, actix_web::Error> { 77 - Ok("List of MicroVMs") 121 + async fn list_microvms( 122 + pool: web::Data<Arc<Pool<Sqlite>>>, 123 + ) -> Result<impl Responder, actix_web::Error> { 124 + let pool = pool.get_ref().clone(); 125 + let results = repo::virtual_machine::all(&pool) 126 + .await 127 + .map_err(actix_web::error::ErrorInternalServerError)?; 128 + Ok(HttpResponse::Ok().json(results)) 78 129 } 79 130 80 131 #[utoipa::path( ··· 88 139 ) 89 140 )] 90 141 #[post("/{id}/start")] 91 - async fn start_microvm() -> Result<impl Responder, actix_web::Error> { 92 - Ok("MicroVM started") 142 + async fn start_microvm( 143 + id: web::Path<String>, 144 + pool: web::Data<Arc<Pool<Sqlite>>>, 145 + ) -> Result<impl Responder, actix_web::Error> { 146 + let id = id.into_inner(); 147 + let pool = pool.get_ref().clone(); 148 + let vm = services::microvm::start_microvm(pool, &id) 149 + .await 150 + .map_err(actix_web::error::ErrorInternalServerError)?; 151 + Ok(HttpResponse::Ok().json(vm)) 93 152 } 94 153 95 154 #[utoipa::path( ··· 103 162 ) 104 163 )] 105 164 #[post("/{id}/stop")] 106 - async fn stop_microvm() -> Result<impl Responder, actix_web::Error> { 107 - Ok("MicroVM stopped") 165 + async fn stop_microvm( 166 + id: web::Path<String>, 167 + pool: web::Data<Arc<Pool<Sqlite>>>, 168 + ) -> Result<impl Responder, actix_web::Error> { 169 + let id = id.into_inner(); 170 + let pool = pool.get_ref().clone(); 171 + let vm = services::microvm::stop_microvm(pool, &id) 172 + .await 173 + .map_err(actix_web::error::ErrorInternalServerError)?; 174 + 175 + if vm.is_none() { 176 + return Ok(HttpResponse::NotFound().json(ErrorResponse::NotFound(id))); 177 + } 178 + 179 + Ok(HttpResponse::Ok().json(vm)) 108 180 } 109 181 110 182 pub fn configure() -> impl FnOnce(&mut ServiceConfig) {
+14
crates/fire-server/src/api/mod.rs
··· 1 1 pub mod microvm; 2 + 3 + #[macro_export] 4 + macro_rules! read_payload { 5 + ($payload:expr) => {{ 6 + let mut body = Vec::new(); 7 + while let Some(chunk) = $payload.next().await { 8 + match chunk { 9 + Ok(bytes) => body.extend_from_slice(&bytes), 10 + Err(err) => return Err(err.into()), 11 + } 12 + } 13 + body 14 + }}; 15 + }
-1
crates/fire-server/src/handlers/microvm.rs
··· 1 -
crates/fire-server/src/handlers/mod.rs crates/fire-server/src/services/mod.rs
+2 -1
crates/fire-server/src/lib.rs
··· 1 1 pub mod api; 2 - pub mod handlers; 3 2 pub mod server; 3 + pub mod services; 4 + pub mod types;
+15 -3
crates/fire-server/src/server.rs
··· 1 - use std::env; 1 + use std::{env, sync::Arc}; 2 2 3 - use actix_web::{App, HttpServer}; 3 + use actix_web::{middleware::Logger, web::Data, App, HttpServer}; 4 4 use anyhow::Error; 5 + use firecracker_process::command::{is_root, run_command}; 5 6 use owo_colors::OwoColorize; 6 7 use utoipa::OpenApi; 7 8 use utoipa_actix_web::AppExt; ··· 13 14 #[derive(OpenApi)] 14 15 #[openapi( 15 16 tags( 16 - (name = "fireup", description = "Firecracker microVM management API") 17 + (name = "fireup", description = "Firecracker MicroVM management API") 17 18 ), 18 19 )] 19 20 struct ApiDoc; 20 21 21 22 pub async fn run() -> Result<(), Error> { 23 + env_logger::init(); 24 + 25 + if !is_root() { 26 + run_command("sudo", &["-v"], false)?; 27 + } 28 + 22 29 let port = env::var("FIREUP_PORT").unwrap_or_else(|_| "9090".to_string()); 23 30 let host = env::var("FIREUP_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); 24 31 let addr = format!("{}:{}", host, port); ··· 26 33 let url = format!("http://{}", addr); 27 34 println!("Starting server at {}", url.green()); 28 35 36 + let pool = firecracker_state::create_connection_pool().await?; 37 + let pool = Arc::new(pool); 38 + 29 39 HttpServer::new(move || { 30 40 App::new() 41 + .app_data(Data::new(pool.clone())) 31 42 .into_utoipa_app() 43 + .map(|app| app.wrap(Logger::default())) 32 44 .openapi(ApiDoc::openapi()) 33 45 .service(utoipa_actix_web::scope("/v1/microvms").configure(microvm::configure())) 34 46 .openapi_service(|api| {
+155
crates/fire-server/src/services/microvm.rs
··· 1 + use std::{sync::Arc, thread}; 2 + 3 + use crate::types::microvm::CreateMicroVM; 4 + use anyhow::Error; 5 + use firecracker_state::{entity::virtual_machine::VirtualMachine, repo}; 6 + use firecracker_vm::{constants::BRIDGE_DEV, types::VmOptions}; 7 + use owo_colors::OwoColorize; 8 + use sqlx::{Pool, Sqlite}; 9 + 10 + pub async fn create_microvm( 11 + pool: Arc<Pool<Sqlite>>, 12 + params: CreateMicroVM, 13 + ) -> Result<VirtualMachine, Error> { 14 + let mut options: VmOptions = params.into(); 15 + 16 + if options.api_socket.is_empty() { 17 + let vm_name = names::Generator::default().next().unwrap(); 18 + options.api_socket = format!("/tmp/firecracker-{}.sock", vm_name); 19 + } 20 + 21 + options.bridge = BRIDGE_DEV.into(); 22 + 23 + let vm = start(pool, options, None).await?; 24 + Ok(vm) 25 + } 26 + 27 + pub async fn delete_microvm( 28 + pool: Arc<Pool<Sqlite>>, 29 + id: &str, 30 + ) -> Result<Option<VirtualMachine>, Error> { 31 + let vm = repo::virtual_machine::find(&pool, id).await?; 32 + if vm.is_none() { 33 + println!("[!] No virtual machine found with the name: {}", id); 34 + return Ok(None); 35 + } 36 + 37 + let mut vm = vm.unwrap(); 38 + firecracker_process::stop(Some(vm.name.clone())).await?; 39 + repo::virtual_machine::delete(&pool, id).await?; 40 + vm.status = "DELETED".into(); 41 + Ok(Some(vm)) 42 + } 43 + 44 + pub async fn start_microvm(pool: Arc<Pool<Sqlite>>, id: &str) -> Result<VirtualMachine, Error> { 45 + let vm = repo::virtual_machine::find(&pool, id).await?; 46 + if vm.is_none() { 47 + println!("[!] No virtual machine found with the name: {}", id); 48 + return Err(Error::msg(format!( 49 + "No virtual machine found with the given id: {}", 50 + id 51 + ))); 52 + } 53 + 54 + let vm = vm.unwrap(); 55 + 56 + let options = VmOptions { 57 + debian: Some(vm.distro == "debian"), 58 + alpine: Some(vm.distro == "alpine"), 59 + ubuntu: Some(vm.distro == "ubuntu"), 60 + nixos: Some(vm.distro == "nixos"), 61 + fedora: Some(vm.distro == "fedora"), 62 + gentoo: Some(vm.distro == "gentoo"), 63 + slackware: Some(vm.distro == "slackware"), 64 + opensuse: Some(vm.distro == "opensuse"), 65 + opensuse_tumbleweed: Some(vm.distro == "opensuse-tumbleweed"), 66 + almalinux: Some(vm.distro == "almalinux"), 67 + rockylinux: Some(vm.distro == "rockylinux"), 68 + archlinux: Some(vm.distro == "archlinux"), 69 + vcpu: vm.vcpu, 70 + memory: vm.memory, 71 + vmlinux: vm.vmlinux.clone(), 72 + rootfs: vm.rootfs.clone(), 73 + bootargs: vm.bootargs.clone(), 74 + bridge: vm.bridge.clone(), 75 + tap: vm.tap.clone(), 76 + api_socket: vm.api_socket.clone(), 77 + mac_address: vm.mac_address.clone(), 78 + etcd: None, 79 + }; 80 + 81 + let vm = start(pool, options, Some(vm.id)).await?; 82 + 83 + Ok(vm) 84 + } 85 + 86 + pub async fn stop_microvm( 87 + pool: Arc<Pool<Sqlite>>, 88 + id: &str, 89 + ) -> Result<Option<VirtualMachine>, Error> { 90 + let vm = repo::virtual_machine::find(&pool, id).await?; 91 + if vm.is_none() { 92 + println!("[!] No virtual machine found with the name: {}", id); 93 + return Ok(None); 94 + } 95 + 96 + let mut vm = vm.unwrap(); 97 + firecracker_process::stop(Some(vm.name.clone())).await?; 98 + repo::virtual_machine::update_status(&pool, id, "STOPPED").await?; 99 + vm.status = "STOPPED".into(); 100 + Ok(Some(vm)) 101 + } 102 + 103 + async fn start( 104 + pool: Arc<Pool<Sqlite>>, 105 + mut options: VmOptions, 106 + vm_id: Option<String>, 107 + ) -> Result<VirtualMachine, Error> { 108 + let vms = repo::virtual_machine::all(&pool).await?; 109 + if options.tap.is_empty() { 110 + let vms = vms 111 + .into_iter() 112 + .filter(|vm| vm.tap.starts_with("tap")) 113 + .collect::<Vec<_>>(); 114 + options.tap = format!("tap{}", vms.len()); 115 + 116 + while vms.iter().any(|vm| vm.tap == options.tap) { 117 + let tap_num: u32 = options 118 + .tap 119 + .trim_start_matches("tap") 120 + .parse::<u32>() 121 + .unwrap_or(0) 122 + .checked_add(1) 123 + .unwrap_or(0); 124 + options.tap = format!("tap{}", tap_num); 125 + } 126 + } else { 127 + if vms 128 + .iter() 129 + .any(|vm| vm.tap == options.tap && vm.api_socket != options.api_socket) 130 + { 131 + println!( 132 + "[!] Tap device name {} is already in use. Please choose a different name.", 133 + options.tap.cyan() 134 + ); 135 + return Err(Error::msg("Tap device name already in use")); 136 + } 137 + } 138 + 139 + let pid = firecracker_process::start(&options).await?; 140 + 141 + loop { 142 + thread::sleep(std::time::Duration::from_secs(1)); 143 + if firecracker_process::is_running() { 144 + println!("[+] Firecracker is running."); 145 + break; 146 + } 147 + } 148 + 149 + firecracker_prepare::prepare(options.clone().into(), options.vmlinux.clone())?; 150 + let vm_id = firecracker_vm::setup(&options, pid, vm_id).await?; 151 + let vm = repo::virtual_machine::find(&pool, &vm_id) 152 + .await? 153 + .ok_or_else(|| Error::msg("Failed to retrieve the created VM"))?; 154 + Ok(vm) 155 + }
+116
crates/fire-server/src/types/microvm.rs
··· 1 + use firecracker_vm::{mac::generate_unique_mac, types::VmOptions}; 2 + use serde::{Deserialize, Serialize}; 3 + use utoipa::ToSchema; 4 + 5 + #[derive(Serialize, Deserialize, Clone, ToSchema)] 6 + pub struct MicroVM { 7 + pub id: String, 8 + pub name: String, 9 + pub image: String, 10 + pub vcpu: u8, 11 + pub memory: u32, 12 + pub vmlinux: String, 13 + pub rootfs: String, 14 + pub boot_args: String, 15 + pub status: String, 16 + pub ssh_keys: Vec<String>, 17 + } 18 + 19 + #[derive(Serialize, Deserialize, Clone, ToSchema)] 20 + pub struct CreateMicroVM { 21 + pub name: Option<String>, 22 + pub vcpus: Option<u8>, 23 + pub memory: Option<u16>, 24 + pub image: Option<String>, 25 + pub vmlinux: Option<String>, 26 + pub rootfs: Option<String>, 27 + pub boot_args: Option<String>, 28 + pub ssh_keys: Option<Vec<String>>, 29 + pub start: Option<bool>, 30 + } 31 + 32 + impl Into<VmOptions> for CreateMicroVM { 33 + fn into(self) -> VmOptions { 34 + VmOptions { 35 + debian: self 36 + .image 37 + .as_ref() 38 + .map(|img| img == "debian") 39 + .unwrap_or(false) 40 + .then_some(true), 41 + alpine: self 42 + .image 43 + .as_ref() 44 + .map(|img| img == "alpine") 45 + .unwrap_or(false) 46 + .then_some(true), 47 + ubuntu: self 48 + .image 49 + .as_ref() 50 + .map(|img| img == "ubuntu") 51 + .unwrap_or(false) 52 + .then_some(true), 53 + nixos: self 54 + .image 55 + .as_ref() 56 + .map(|img| img == "nixos") 57 + .unwrap_or(false) 58 + .then_some(true), 59 + fedora: self 60 + .image 61 + .as_ref() 62 + .map(|img| img == "fedora") 63 + .unwrap_or(false) 64 + .then_some(true), 65 + gentoo: self 66 + .image 67 + .as_ref() 68 + .map(|img| img == "gentoo") 69 + .unwrap_or(false) 70 + .then_some(true), 71 + slackware: self 72 + .image 73 + .as_ref() 74 + .map(|img| img == "slackware") 75 + .unwrap_or(false) 76 + .then_some(true), 77 + opensuse: self 78 + .image 79 + .as_ref() 80 + .map(|img| img == "opensuse") 81 + .unwrap_or(false) 82 + .then_some(true), 83 + opensuse_tumbleweed: self 84 + .image 85 + .as_ref() 86 + .map(|img| img == "opensuse-tumbleweed") 87 + .unwrap_or(false) 88 + .then_some(true), 89 + almalinux: self 90 + .image 91 + .as_ref() 92 + .map(|img| img == "almalinux") 93 + .unwrap_or(false) 94 + .then_some(true), 95 + rockylinux: self 96 + .image 97 + .as_ref() 98 + .map(|img| img == "rockylinux") 99 + .unwrap_or(false) 100 + .then_some(true), 101 + archlinux: self 102 + .image 103 + .as_ref() 104 + .map(|img| img == "archlinux") 105 + .unwrap_or(false) 106 + .then_some(true), 107 + vcpu: self.vcpus.unwrap_or(1) as u16, 108 + memory: self.memory.unwrap_or(512), 109 + vmlinux: self.vmlinux, 110 + rootfs: self.rootfs, 111 + bootargs: self.boot_args, 112 + mac_address: generate_unique_mac(), 113 + ..Default::default() 114 + } 115 + } 116 + }
+1
crates/fire-server/src/types/mod.rs
··· 1 + pub mod microvm;
+4 -4
crates/firecracker-process/src/lib.rs
··· 63 63 64 64 let vm = vm.unwrap(); 65 65 if let Some(pid) = vm.pid { 66 - run_command("kill", &["-s", "KILL", &pid.to_string()], true)?; 66 + if run_command("kill", &["-s", "KILL", &pid.to_string()], true).is_err() { 67 + println!("[!] Failed to kill process with PID {}.", pid); 68 + } 67 69 } 68 70 69 71 run_command("rm", &["-rf", &config.api_socket], true)?; ··· 82 84 if std::path::Path::new(&vm.api_socket).exists() { 83 85 return Ok(true); 84 86 } 85 - if vm.status == "RUNNING" { 86 - return Ok(true); 87 - } 87 + repo::virtual_machine::update_status(&pool, name, "STOPPED").await?; 88 88 } 89 89 90 90 Ok(false)
+3 -2
crates/firecracker-state/src/repo/virtual_machine.rs
··· 17 17 let result: Option<VirtualMachine> = 18 18 sqlx::query_as("SELECT * FROM virtual_machines WHERE name = ? OR id = ?") 19 19 .bind(name) 20 + .bind(name) 20 21 .fetch_optional(pool) 21 22 .await 22 23 .with_context(|| { ··· 40 41 Ok(result) 41 42 } 42 43 43 - pub async fn create(pool: &Pool<Sqlite>, vm: VirtualMachine) -> Result<(), Error> { 44 + pub async fn create(pool: &Pool<Sqlite>, vm: VirtualMachine) -> Result<String, Error> { 44 45 let id = xid::new().to_string(); 45 46 let project_dir = match Path::exists(Path::new("fire.toml")) { 46 47 true => Some(std::env::current_dir()?.display().to_string()), ··· 85 86 .execute(pool) 86 87 .await 87 88 .with_context(|| "Failed to create virtual machine")?; 88 - Ok(()) 89 + Ok(id) 89 90 } 90 91 91 92 pub async fn delete(pool: &Pool<Sqlite>, name: &str) -> Result<(), Error> {
+6 -5
crates/firecracker-vm/src/lib.rs
··· 19 19 mod network; 20 20 pub mod types; 21 21 22 - pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option<String>) -> Result<()> { 22 + pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option<String>) -> Result<String> { 23 23 let distro: Distro = options.clone().into(); 24 24 let app_dir = get_config_dir().with_context(|| "Failed to get configuration directory")?; 25 25 ··· 144 144 .display() 145 145 .to_string(); 146 146 147 - match vm_id { 147 + let vm_id = match vm_id { 148 148 Some(id) => { 149 149 repo::virtual_machine::update( 150 150 &pool, ··· 169 169 }, 170 170 ) 171 171 .await?; 172 + id 172 173 } 173 174 None => { 174 175 repo::virtual_machine::create( ··· 192 193 ..Default::default() 193 194 }, 194 195 ) 195 - .await?; 196 + .await? 196 197 } 197 - } 198 + }; 198 199 199 200 println!("[✓] MicroVM booted and network is configured 🎉"); 200 201 201 202 println!("SSH into the VM using the following command:"); 202 203 println!("{} {}", "fireup ssh".bright_green(), name.bright_green()); 203 204 204 - Ok(()) 205 + Ok(vm_id) 205 206 }