From bc4eacdcfe5dfb6592064e53e421ed4f468ce426 Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Sat, 25 Apr 2026 16:10:36 -0500 Subject: [PATCH 01/10] started this and then realized we might want to merge the crates first Signed-off-by: Caleb Jones --- odorobo/src/types.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/odorobo/src/types.rs b/odorobo/src/types.rs index 8e10a25..f91408c 100644 --- a/odorobo/src/types.rs +++ b/odorobo/src/types.rs @@ -105,6 +105,9 @@ pub struct VMData { /// List of volumes to attach to the VM. #[serde(default)] pub volumes: Vec, + /// List of Network IDs. + #[serde(default)] + pub networks: Vec, } #[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] From 46fbede80c092a999a28022a1e32b53198ab202d Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Sun, 26 Apr 2026 19:23:19 -0500 Subject: [PATCH 02/10] start working on affinity rules Signed-off-by: Caleb Jones --- odorobo/src/types.rs | 54 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/odorobo/src/types.rs b/odorobo/src/types.rs index f91408c..095af10 100644 --- a/odorobo/src/types.rs +++ b/odorobo/src/types.rs @@ -166,8 +166,58 @@ pub struct VirtualMachine { /// Metadata pub metadata: Option, - // placement stuff.... - + /// List of Affinity rules for scheduling. These are ANDed / summed together depending on the strictness. + pub affinity: Vec +} + + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub struct AffinityRule { + pub strictness: AffinityStrictness, + pub affinity_type: AffinityType, + pub direction: AffinityDirection, + /// ORed together + pub requirements: Vec +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub enum AffinityStrictness { + Required, + Preferred { weight: i64 } +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub enum AffinityType { + VirtualMachine, + Agent +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub enum AffinityDirection { + Normal, + Anti +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub struct AffinityRequirement { + pub key: String, + pub table: MetadataTable, + pub operator: Operator, + pub values: Vec +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub enum MetadataTable { + Label, + Annotation +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub enum Operator { + In, + NotIn, + Lt, + Gt, } #[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] From 35dbe69c79aeca097820c7024b18b90824a6c672 Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Mon, 27 Apr 2026 15:20:22 -0500 Subject: [PATCH 03/10] remove old ch_api Signed-off-by: Caleb Jones --- odorobo/src/ch_api/ch.rs | 39 ----- odorobo/src/ch_api/console.rs | 259 ---------------------------------- odorobo/src/ch_api/error.rs | 99 ------------- odorobo/src/ch_api/mod.rs | 49 ------- odorobo/src/ch_api/vm.rs | 250 -------------------------------- odorobo/src/main.rs | 1 - odorobo/src/state/mod.rs | 3 +- 7 files changed, 1 insertion(+), 699 deletions(-) delete mode 100644 odorobo/src/ch_api/ch.rs delete mode 100644 odorobo/src/ch_api/console.rs delete mode 100644 odorobo/src/ch_api/error.rs delete mode 100644 odorobo/src/ch_api/mod.rs delete mode 100644 odorobo/src/ch_api/vm.rs diff --git a/odorobo/src/ch_api/ch.rs b/odorobo/src/ch_api/ch.rs deleted file mode 100644 index 6a081cd..0000000 --- a/odorobo/src/ch_api/ch.rs +++ /dev/null @@ -1,39 +0,0 @@ -use axum::{ - body::Body, - extract::{Path, Request}, - response::Response, -}; -use http_body_util::BodyExt; - -use super::error::ApiError; -use crate::state::VMInstance; - -pub async fn passthrough( - Path((vmid, path)): Path<(String, String)>, - request: Request, -) -> Result { - let vm = VMInstance::get(&vmid).ok_or_else(|| ApiError::VmNotFound { vmid: vmid.clone() })?; - - let (mut parts, body) = request.into_parts(); - let body = body - .collect() - .await - .map_err(|e| ApiError::PassthroughFailed { msg: e.to_string() })? - .to_bytes(); - - let path_and_query = match parts.uri.query() { - Some(query) => format!("/{path}?{query}"), - None => format!("/{path}"), - }; - parts.uri = path_and_query - .parse() - .map_err(|e| ApiError::PassthroughFailed { msg: format!("URI parse error: {}", e) })?; - - let response = vm - .call_request(hyper::Request::from_parts(parts, body)) - .await - .map_err(|e| ApiError::PassthroughFailed { msg: e.to_string() })?; - - let (parts, body) = response.into_parts(); - Ok(Response::from_parts(parts, Body::from(body))) -} diff --git a/odorobo/src/ch_api/console.rs b/odorobo/src/ch_api/console.rs deleted file mode 100644 index 5ccf9b5..0000000 --- a/odorobo/src/ch_api/console.rs +++ /dev/null @@ -1,259 +0,0 @@ -use axum::{ - extract::{ - Path, - ws::{Message, WebSocket, WebSocketUpgrade}, - }, - response::Response, -}; -use futures_util::SinkExt; -use serde::{Deserialize, Serialize}; -use stable_eyre::{Result, eyre::eyre}; -use std::io::{self, Read, Write}; -use std::os::fd::AsRawFd; -use tokio::io::unix::AsyncFd; -use tokio::time::{Duration, sleep}; -use tracing::{debug, trace, warn}; - -use super::error::ApiError; -use crate::state::{ConsoleStream, VMInstance}; - -pub async fn console_stream( - vmid: Path, - ws: WebSocketUpgrade, -) -> Result { - let vmid = vmid.0; - let vm = VMInstance::get(&vmid).ok_or_else(|| ApiError::VmNotFound { vmid: vmid.clone() })?; - let console = vm - .open_console() - .await - .map_err(|e| ApiError::ConsoleFailed { msg: e.to_string() })?; - - Ok(ws.on_upgrade(move |socket| proxy_console_socket(vmid, socket, console))) -} - -#[derive(Debug, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum ConsoleControlMessage { - Resize { - cols: u16, - rows: u16, - #[serde(default)] - x_pixels: u16, - #[serde(default)] - y_pixels: u16, - }, - ResetSession, -} - -#[derive(Debug, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum ConsoleServerMessage<'a> { - Connected { vm_id: &'a str }, - Error { message: &'a str }, -} - -async fn proxy_console_socket(vm_id: String, mut socket: WebSocket, console: ConsoleStream) { - if let Err(err) = send_console_event( - &mut socket, - ConsoleServerMessage::Connected { vm_id: &vm_id }, - ) - .await - { - warn!(vm_id, ?err, "Failed to send connected event"); - return; - } - - let console_fd = match AsyncFd::new(console) { - Ok(fd) => fd, - Err(err) => { - warn!(vm_id, ?err, "Failed to wrap console in AsyncFd"); - return; - } - }; - - match proxy_console(&mut socket, console_fd).await { - Ok(()) => debug!(vm_id, "Console websocket disconnected"), - Err(err) => warn!(vm_id, ?err, "Console websocket proxy failed"), - } - - let _ = socket.close().await; -} - -async fn proxy_console(socket: &mut WebSocket, mut console: AsyncFd) -> Result<()> { - let mut buf = [0_u8; 8192]; - - loop { - // trace!("select waiting on ws.recv() and console.read()"); - tokio::select! { - message = socket.recv() => { - // trace!("ws.recv() returned: {:?}", message.as_ref().map(|m| match m { - // Ok(Message::Binary(d)) => format!("Binary({}b)", d.len()), - // Ok(Message::Text(t)) => format!("Text({}b)", t.len()), - // Ok(Message::Close(_)) => "Close".into(), - // Ok(Message::Ping(_)) => "Ping".into(), - // Ok(Message::Pong(_)) => "Pong".into(), - // Err(e) => format!("Error: {}", e), - // })); - match message { - Some(Ok(Message::Binary(data))) => { - // trace!(bytes = data.len(), "ws -> pty (binary)"); - let _ = console.writable().await?; - let data_copy = data.to_vec(); - let write_result = tokio::task::block_in_place(|| { - console.get_mut().write_all(&data_copy)?; - console.get_mut().flush()?; - Ok::<(), io::Error>(()) - }); - write_result?; - // trace!("write and flush done"); - } - Some(Ok(Message::Text(text))) => { - // trace!(len = text.len(), "ws -> pty (text/control)"); - handle_console_control(socket, &mut console, text.to_string()).await? - } - Some(Ok(Message::Close(_))) | None => { - // trace!("ws close or none"); - break; - } - Some(Ok(Message::Ping(payload))) => { - // trace!("ws ping"); - socket.send(Message::Pong(payload)).await? - } - Some(Ok(Message::Pong(_))) => { - // trace!("ws pong"); - } - Some(Err(err)) => { - trace!(?err, "ws error"); - return Err(err.into()); - } - } - } - read_result = async { - let _ = console.readable().await?; - tokio::task::block_in_place(|| { - console.get_mut().read(&mut buf) - }) - } => { - match read_result { - Ok(n) => { - if n == 0 { - // trace!("pty read returned 0 bytes"); - break; - } - // trace!(bytes = n, "pty -> ws"); - socket.send(Message::Binary(buf[..n].to_vec().into())).await?; - } - Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - // trace!("pty read would block, retrying"); - // Continue select loop to try again - } - Err(e) => return Err(e.into()), - } - } - } - } - - Ok(()) -} - -async fn handle_console_control( - socket: &mut WebSocket, - console: &mut AsyncFd, - raw_message: String, -) -> Result<()> { - let message: ConsoleControlMessage = match serde_json::from_str(&raw_message) { - Ok(message) => message, - Err(_) => { - send_console_event( - socket, - ConsoleServerMessage::Error { - message: "Text frames are reserved for JSON control messages such as {\"type\":\"resize\",\"cols\":120,\"rows\":40} or {\"type\":\"reset_session\"}", - }, - ) - .await?; - return Ok(()); - } - }; - - match message { - ConsoleControlMessage::Resize { - cols, - rows, - x_pixels, - y_pixels, - } => { - if let Err(err) = resize_console(console, cols, rows, x_pixels, y_pixels) { - let message = format!("Failed to resize console: {err}"); - send_console_event(socket, ConsoleServerMessage::Error { message: &message }) - .await?; - } - } - ConsoleControlMessage::ResetSession => { - if let Err(err) = reset_console_session(console).await { - let message = format!("Failed to reset console session: {err}"); - send_console_event(socket, ConsoleServerMessage::Error { message: &message }) - .await?; - } - } - } - - Ok(()) -} - -async fn send_console_event(socket: &mut WebSocket, event: ConsoleServerMessage<'_>) -> Result<()> { - let payload = serde_json::to_string(&event)?; - socket.send(Message::Text(payload.into())).await?; - Ok(()) -} - -fn resize_console( - console: &AsyncFd, - cols: u16, - rows: u16, - x_pixels: u16, - y_pixels: u16, -) -> Result<()> { - if cols == 0 || rows == 0 { - return Err(eyre!("Console dimensions must be greater than zero")); - } - - let size = libc::winsize { - ws_row: rows, - ws_col: cols, - ws_xpixel: x_pixels, - ws_ypixel: y_pixels, - }; - - let result = unsafe { libc::ioctl(console.as_raw_fd(), libc::TIOCSWINSZ, &size) }; - if result == -1 { - return Err(std::io::Error::last_os_error().into()); - } - - Ok(()) -} - -async fn reset_console_session(console: &mut AsyncFd) -> Result<()> { - const STEP_DELAY: Duration = Duration::from_millis(75); - - macro_rules! write_to_console { - ($data:expr) => {{ - let _ = console.writable().await?; - let data = $data.to_vec(); - tokio::task::block_in_place(|| { - console.get_mut().write_all(&data)?; - console.get_mut().flush()?; - Ok::<(), io::Error>(()) - })?; - }}; - } - - write_to_console!(&[0x03]); - sleep(STEP_DELAY).await; - - write_to_console!(b"\r"); - sleep(STEP_DELAY).await; - - write_to_console!(&[0x04]); - - Ok(()) -} diff --git a/odorobo/src/ch_api/error.rs b/odorobo/src/ch_api/error.rs deleted file mode 100644 index ab53845..0000000 --- a/odorobo/src/ch_api/error.rs +++ /dev/null @@ -1,99 +0,0 @@ -use axum_responses::{HttpError, thiserror::Error}; -use stable_eyre::Report; - -use crate::state::ChApiError; - -#[derive(Debug, Error, HttpError)] -pub enum ApiError { - #[error("Invalid VM ID: {msg}")] - #[http(code = 400, message = msg)] - InvalidVmId { msg: String }, - - #[error("VM not found: {vmid}")] - #[http(code = 404, message = vmid)] - VmNotFound { vmid: String }, - - #[error("Failed to get VM info: {msg}")] - #[http(code = 500, message = msg, errors = errors)] - VmInfoFailed { msg: String, errors: Vec }, - - #[error("Failed to list VMs: {msg}")] - #[http(code = 500, message = msg)] - ListFailed { msg: String }, - - #[error("Failed to create VM: {msg}")] - #[http(code = 500, message = msg, errors = errors)] - CreateFailed { msg: String, errors: Vec }, - - #[error("Failed to open VM console: {msg}")] - #[http(code = 500, message = msg)] - ConsoleFailed { msg: String }, - - #[error("Failed to proxy Cloud Hypervisor API request: {msg}")] - #[http(code = 500, message = msg)] - PassthroughFailed { msg: String }, - - #[error("Cloud Hypervisor API error: {msg}")] - #[http(code = 502, message = msg, errors = errors)] - ChApiFailed { msg: String, errors: Vec }, - - #[error("Failed to delete VM configuration: configuration: {msg}")] - #[http(code = 500, message = msg, errors = errors)] - DeleteConfigFailed { msg: String, errors: Vec }, - - #[error("Failed to delete VM configuration: configuration: {msg}")] - #[http(code = 500, message = msg, errors = errors)] - CreateConfigFailed { msg: String, errors: Vec }, - - #[error("Failed to migrate VM: {msg}")] - #[http(code = 500, message = msg, errors = errors)] - MigrationFailed { msg: String, errors: Vec }, -} - -impl ApiError { - fn find_ch_api_error(error: &Report) -> Option<&ChApiError> { - error - .chain() - .find_map(|cause| cause.downcast_ref::()) - } - - fn from_report(error: Report, fallback: impl FnOnce(String, Vec) -> Self) -> Self { - if let Some(ch_error) = Self::find_ch_api_error(&error) { - return match ch_error { - ChApiError::Api { status, errors } => fallback( - format!("Cloud Hypervisor API returned status {}", status.as_u16()), - errors.clone(), - ), - ChApiError::Client(_) => fallback(format!("{error:?}"), vec![]), - }; - } - - fallback(format!("{error:?}"), vec![]) - } - - pub fn vm_info(error: Report) -> Self { - Self::from_report(error, |msg, errors| Self::VmInfoFailed { msg, errors }) - } - - pub fn create(error: Report) -> Self { - Self::from_report(error, |msg, errors| Self::CreateFailed { msg, errors }) - } - - pub fn create_config(error: Report) -> Self { - Self::from_report(error, |msg, errors| Self::CreateConfigFailed { - msg, - errors, - }) - } - - pub fn delete_config(error: Report) -> Self { - Self::from_report(error, |msg, errors| Self::DeleteConfigFailed { - msg, - errors, - }) - } - - pub fn migration(error: Report) -> Self { - Self::from_report(error, |msg, errors| Self::MigrationFailed { msg, errors }) - } -} diff --git a/odorobo/src/ch_api/mod.rs b/odorobo/src/ch_api/mod.rs deleted file mode 100644 index 3b9576c..0000000 --- a/odorobo/src/ch_api/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! REST Management API for odorobo-agent -mod ch; -mod console; -mod error; -mod vm; - -// pub fn router(port: u16) -> axum::Router<()> { -// let info_route = axum::Router::new() -// .route("/info", axum::routing::get(info)) -// .with_state(port); - -// axum::Router::new() -// .layer( -// TraceLayer::new_for_http() -// .on_request(DefaultOnRequest::new()) -// .on_response(DefaultOnResponse::new()) -// ) -// .route("/", axum::routing::get(root)) -// .route("/health", axum::routing::get(health)) -// .merge(info_route) -// .nest("/vms", vm::router()) -// } - -// async fn root() -> &'static str { -// env!("CARGO_PKG_VERSION") -// } - -// async fn health() -> &'static str { -// "" -// } - -// #[derive(Serialize)] -// struct AgentInfo { -// version: &'static str, -// listening_addresses: Vec, -// } - -// async fn info(State(port): State) -> Json { -// let listening_addresses = if_addrs::get_if_addrs() -// .unwrap_or_default() -// .into_iter() -// .filter(|i| !i.is_loopback()) -// .map(|i| format!("{}:{}", i.ip(), port)) -// .collect(); -// Json(AgentInfo { -// version: env!("CARGO_PKG_VERSION"), -// listening_addresses, -// }) -// } diff --git a/odorobo/src/ch_api/vm.rs b/odorobo/src/ch_api/vm.rs deleted file mode 100644 index 85b9a6d..0000000 --- a/odorobo/src/ch_api/vm.rs +++ /dev/null @@ -1,250 +0,0 @@ - - -// pub fn router() -> axum::Router<()> { -// axum::Router::new() -// .route("/", axum::routing::get(list_vms)) -// // .route("/{vmid}", axum::routing::put(spawn_vm)) -// // .route("/{vmid}/config", axum::routing::put(create_vm_config)) -// // .route("/{vmid}/config", axum::routing::delete(delete_vm_config)) -// // .route("/{vmid}/shutdown", axum::routing::put(shutdown_vm)) -// // .route("/{vmid}/acpi_shutdown", axum::routing::put(shutdown_acpi)) -// // .route("/{vmid}/boot", axum::routing::put(boot_vm)) -// // .route("/{vmid}/pause", axum::routing::put(pause_vm)) -// // .route("/{vmid}/resume", axum::routing::put(resume_vm)) -// // .route("/{vmid}/migrate/send", axum::routing::put(migrate_send_vm)) -// // .route( -// // "/{vmid}/migrate/receive", -// // axum::routing::put(migrate_receive_vm), -// // ) -// // .route("/{vmid}", axum::routing::get(vm_info)) -// // .route("/{vmid}/ping", axum::routing::get(ping_vm)) -// // .route("/{vmid}", axum::routing::delete(destroy_vm)) -// // .route( -// // "/{vmid}/console", -// // axum::routing::get(super::console::console_stream), -// // ) -// // .route( -// // "/{vmid}/ch/{*path}", -// // axum::routing::any(super::ch::passthrough), -// // ) -// } - -// /// Lists all VMs by their IDs -// async fn list_vms() -> Result>, ApiError> { -// let vms = VMInstance::list().map_err(|e| ApiError::ListFailed { msg: e.to_string() })?; -// Ok(Json(vms.into_iter().map(|i| i.id).collect())) -// } - -// /// Helper function to get a VM instance by ID, returning an error if not found -// fn get_vm(vmid: &str) -> Result { -// use crate::state::VMInstance; -// VMInstance::validate_vmid(vmid).map_err(|e| ApiError::InvalidVmId { msg: e.to_string() })?; -// VMInstance::get(vmid).ok_or_else(|| ApiError::VmNotFound { -// vmid: vmid.to_string(), -// }) -// } - -// /// Gets detailed information about a specific VM -// async fn vm_info( -// vmid: Path, -// ) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; - -// let info = vm.info().await.map_err(ApiError::vm_info)?; -// Ok(Json(info)) -// } - -// /// Pings the VMM to check if it's running -// async fn ping_vm(vmid: Path) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// let res = vm.ping().await.map_err(ApiError::vm_info)?; -// Ok(Json(res)) -// } - -// #[derive(Debug, Deserialize)] -// pub struct CreateVmQuery { -// #[serde(default)] -// /// Whether to boot the VM immediately -// /// after creation -// /// -// /// Defaults to `false`. -// pub boot: bool, -// } -// #[derive(Debug, Deserialize, Serialize)] -// pub struct VmSpawnResponse { -// pub info: Option, -// pub booted: bool, -// pub created_config: bool, -// } - -// #[derive(Debug, Deserialize, Serialize)] -// pub struct VmMigrateSendResponse { -// pub info: Option, -// } -// #[derive(Debug, Deserialize, Serialize)] -// pub struct VmMigrateReceiveResponse { -// pub listening_address: String, -// } - -// #[derive(Debug, Deserialize)] -// pub struct VmMigrateSendRequest { -// pub destination: String, -// #[serde(default)] -// pub local: bool, -// } - -// /// Sends a live migration to the given destination URL. -// /// Note: the source VMM exits after migration completes, so no VM info is returned. -// async fn migrate_send_vm( -// vmid: Path, -// Json(body): Json, -// ) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.send_migration(&body.destination, body.local) -// .await -// .map_err(ApiError::migration)?; -// Ok(Json(VmMigrateSendResponse { info: None })) -// } - -// /// Prepares a VM to receive a live migration, returning the address the sender should connect to -// async fn migrate_receive_vm( -// vmid: Path, -// ) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// let listening_address = vm.receive_migration().await.map_err(ApiError::migration)?; -// Ok(Json(VmMigrateReceiveResponse { listening_address })) -// } - -// /// Spawns a new VM instance with the given ID, optionally creating it with the provided configuration and booting it immediately -// async fn spawn_vm( -// vmid: Path, -// Query(query): Query, -// vm_config: Option>, -// ) -> Result, ApiError> { -// VMInstance::validate_vmid(&vmid.0).map_err(|e| ApiError::InvalidVmId { msg: e.to_string() })?; -// let vm_config = vm_config.map(|Json(vm_config)| vm_config); - -// // check if VM already exists, if so error out for already existing instance -// if VMInstance::get(&vmid.0).is_some() { -// error!(vmid = ?vmid, "VM with this ID already exists"); -// return Err(ApiError::CreateFailed { -// msg: "VM with this ID already exists".to_string(), -// errors: vec![], -// }); -// } - -// trace!(?vmid, ?query, "Creating VM with config"); -// // trace!(?vm_config, "VM config details"); -// let runtime_dir = VMInstance::runtime_dir_for(&vmid.0); -// std::fs::create_dir_all(&runtime_dir).map_err(|e| { -// error!(error = %e, "Failed to create runtime dir"); -// ApiError::CreateFailed { -// msg: e.to_string(), -// errors: vec![], -// } -// })?; -// // trace!(?) - -// let vm = VMInstance::spawn(&vmid.0).await.map_err(|e| { -// error!(error = ?e, "Failed to spawn VM process"); -// ApiError::create(e) -// })?; - -// let mut created = false; -// if vm_config.is_some() { -// trace!(?vmid, "Creating VM with provided config"); -// vm.create(vm_config.clone().unwrap(), query.boot) -// .await -// .map_err(|e| { -// error!(error = ?e, "Failed to create VM"); -// ApiError::create_config(e) -// })?; - -// created = true; -// } else { -// trace!(?vmid, "No VM config provided, skipping creation step"); -// } - -// let vm_info = if vm_config.is_some() { -// Some(vm.info().await.map_err(|e| { -// error!(error = ?e, "Failed to get VM info after creation"); -// ApiError::vm_info(e) -// })?) -// } else { -// None -// }; - -// Ok(Json(VmSpawnResponse { -// info: vm_info, -// booted: query.boot, -// created_config: created, -// })) -// } - -// async fn create_vm_config( -// vmid: Path, -// Query(query): Query, -// Json(vm_config): Json, -// ) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.create(vm_config, query.boot) -// .await -// .map_err(ApiError::create_config)?; -// Ok(Json(())) -// } - -// async fn delete_vm_config(vmid: Path) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.delete_config().await.map_err(ApiError::delete_config)?; -// Ok(Json(())) -// } - -// /// Forces a VM to shut down immediately, without giving it a chance to gracefully clean up resources. -// /// This is equivalent to pulling the power on a physical machine and may lead to data loss or corruption if the VM is running. -// /// -// /// The VM process itself will still be running until the VMM detects that the VM has stopped, -// /// not fully cleaning up resources. -// async fn shutdown_vm(vmid: Path) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.shutdown().await.map_err(ApiError::vm_info)?; -// Ok(Json(())) -// } - -// /// Sends an ACPI shutdown signal to the VM, allowing it to gracefully shut down and clean up resources. -// /// -// /// With the systemd provisioner, this will also fully clean up resources and destroy the VM instance entirely, -// /// allowing them to be re-provisioned again on any other node (if running in a cluster) -// async fn shutdown_acpi(vmid: Path) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.acpi_power_button().await.map_err(ApiError::vm_info)?; -// Ok(Json(())) -// } - -// /// Boots a VM that has been created but not yet started. If the VM is already running, this will return an error. -// async fn boot_vm(vmid: Path) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.boot().await.map_err(ApiError::vm_info)?; -// Ok(Json(())) -// } - -// /// Suspends a running VM, pausing all activity until -// /// it is resumed again. -// async fn pause_vm(vmid: Path) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.pause().await.map_err(ApiError::vm_info)?; -// Ok(Json(())) -// } - -// /// Resumes a paused VM, allowing it to continue running from where it left off. -// async fn resume_vm(vmid: Path) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.resume().await.map_err(ApiError::vm_info)?; -// Ok(Json(())) -// } - -// /// Destroys a VM, stopping it if it's running and cleaning up resources -// async fn destroy_vm(vmid: Path) -> Result, ApiError> { -// let vm = get_vm(&vmid.0)?; -// vm.destroy().await.map_err(ApiError::vm_info)?; -// Ok(Json(())) -// } diff --git a/odorobo/src/main.rs b/odorobo/src/main.rs index 350843f..e7f3215 100644 --- a/odorobo/src/main.rs +++ b/odorobo/src/main.rs @@ -1,5 +1,4 @@ pub mod actors; -mod ch_api; pub mod http_api; pub mod networking; mod state; diff --git a/odorobo/src/state/mod.rs b/odorobo/src/state/mod.rs index 00fa350..91b99c0 100644 --- a/odorobo/src/state/mod.rs +++ b/odorobo/src/state/mod.rs @@ -9,5 +9,4 @@ pub mod instance; pub mod provisioning; pub mod transform; -pub use instance::{ChApiError, ConsoleStream, VMInstance}; -// pub use transform::{ConfigTransform, ConsoleTransform, TransformChain, apply_builtin_transforms}; +pub use instance::{VMInstance}; From 1c60eac03abd0924d02030dea56c232e2813f91f Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Mon, 27 Apr 2026 17:01:54 -0500 Subject: [PATCH 04/10] rename state module to ch_driver to make more sense. start work on isolating ch references. will move this to another crate later. Signed-off-by: Caleb Jones --- odorobo/src/actors/agent_actor.rs | 2 +- odorobo/src/actors/scheduler_actor.rs | 2 +- .../actor/mod.rs => ch_driver/actor.rs} | 95 ++++++++++++++++--- odorobo/src/{state => ch_driver}/api.rs | 0 odorobo/src/{state => ch_driver}/devices.rs | 2 +- odorobo/src/{state => ch_driver}/instance.rs | 2 +- odorobo/src/{state => ch_driver}/mod.rs | 1 + .../provisioning/hooks/mod.rs | 0 .../provisioning/hooks/networking.rs | 0 .../{state => ch_driver}/provisioning/mod.rs | 1 - .../{state => ch_driver}/transform/console.rs | 2 +- .../src/{state => ch_driver}/transform/mod.rs | 0 .../transform/networking.rs | 0 .../transform/path_verify.rs | 0 .../transform/storage/file.rs | 0 .../transform/storage/iscsi.rs | 0 .../transform/storage/mod.rs | 2 +- .../transform/storage/rbd.rs | 0 odorobo/src/main.rs | 2 +- 19 files changed, 92 insertions(+), 19 deletions(-) rename odorobo/src/{state/provisioning/actor/mod.rs => ch_driver/actor.rs} (75%) rename odorobo/src/{state => ch_driver}/api.rs (100%) rename odorobo/src/{state => ch_driver}/devices.rs (96%) rename odorobo/src/{state => ch_driver}/instance.rs (99%) rename odorobo/src/{state => ch_driver}/mod.rs (95%) rename odorobo/src/{state => ch_driver}/provisioning/hooks/mod.rs (100%) rename odorobo/src/{state => ch_driver}/provisioning/hooks/networking.rs (100%) rename odorobo/src/{state => ch_driver}/provisioning/mod.rs (93%) rename odorobo/src/{state => ch_driver}/transform/console.rs (98%) rename odorobo/src/{state => ch_driver}/transform/mod.rs (100%) rename odorobo/src/{state => ch_driver}/transform/networking.rs (100%) rename odorobo/src/{state => ch_driver}/transform/path_verify.rs (100%) rename odorobo/src/{state => ch_driver}/transform/storage/file.rs (100%) rename odorobo/src/{state => ch_driver}/transform/storage/iscsi.rs (100%) rename odorobo/src/{state => ch_driver}/transform/storage/mod.rs (99%) rename odorobo/src/{state => ch_driver}/transform/storage/rbd.rs (100%) diff --git a/odorobo/src/actors/agent_actor.rs b/odorobo/src/actors/agent_actor.rs index 8eb694a..7f0fd6a 100644 --- a/odorobo/src/actors/agent_actor.rs +++ b/odorobo/src/actors/agent_actor.rs @@ -1,4 +1,4 @@ -use crate::{config::Config, types::ObjectMetadata, messages::{Ping, Pong, agent::{AgentStatus, GetAgentStatus}, debug::PanicAgent, vm::*}, networking::actor::NetworkAgentActor, state::provisioning::actor::VMActor, utils::actor_names::{NETWORK, VM, vm_actor_id}}; +use crate::{config::Config, types::ObjectMetadata, messages::{Ping, Pong, agent::{AgentStatus, GetAgentStatus}, debug::PanicAgent, vm::*}, networking::actor::NetworkAgentActor, ch_driver::actor::VMActor, utils::actor_names::{NETWORK, VM, vm_actor_id}}; use ahash::AHashMap; use bytesize::ByteSize; use cloud_hypervisor_client::models::VmConfig; diff --git a/odorobo/src/actors/scheduler_actor.rs b/odorobo/src/actors/scheduler_actor.rs index 3e97ab8..11d6a62 100644 --- a/odorobo/src/actors/scheduler_actor.rs +++ b/odorobo/src/actors/scheduler_actor.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use kameo::prelude::*; use libp2p::futures::TryStreamExt; use crate::actors::agent_actor::AgentActor; -use crate::state::provisioning::actor::VMActor; +use crate::ch_driver::actor::VMActor; use crate::utils::actor_cache::ActorCache; use crate::utils::actor_cache::ActorCacheUpdater; use crate::utils::actor_names::VM; diff --git a/odorobo/src/state/provisioning/actor/mod.rs b/odorobo/src/ch_driver/actor.rs similarity index 75% rename from odorobo/src/state/provisioning/actor/mod.rs rename to odorobo/src/ch_driver/actor.rs index ad32cbf..ca968f7 100644 --- a/odorobo/src/state/provisioning/actor/mod.rs +++ b/odorobo/src/ch_driver/actor.rs @@ -1,5 +1,5 @@ -use crate::state::VMInstance; -use cloud_hypervisor_client::models::VmConfig; +use crate::{ch_driver::VMInstance, types::VirtualMachine}; +use cloud_hypervisor_client::models::{ConsoleConfig, ConsoleMode, CpusConfig, DebugConsoleConfig, DiskConfig, MemoryConfig, NetConfig, PayloadConfig, PlatformConfig, RngConfig, VmConfig}; use kameo::prelude::*; use crate::messages::vm::{ DeleteVM, GetVMInfo, GetVMInfoReply, MigrateVMReceive, MigrateVMReceiveReply, PrepMigration, @@ -9,14 +9,7 @@ use serde::{Deserialize, Serialize}; use stable_eyre::{Report, Result}; use tokio::task::JoinHandle; use tracing::{debug, error, info, trace, warn}; -/* -use std::process::Command; - -let output = Command::new("echo") -.arg("Hello world") -.output() -.expect("Failed to execute command"); - */ + /// A migration state that holds the listening address and VM config for a migration, /// used to pass live migration data between actors. pub struct MigrationState { @@ -39,11 +32,13 @@ pub struct VMActor { impl Actor for VMActor { // tuple of VM ID and optional config - type Args = (ulid::Ulid, Option); + type Args = (ulid::Ulid, Option); type Error = Report; #[tracing::instrument(skip_all)] async fn on_start((vmid, vm_config): Self::Args, actor_ref: ActorRef) -> Result { + + let mut vminstance = VMInstance::spawn(&vmid.to_string(), vm_config, None).await?; // Take the child process out so we can watch for unexpected death. @@ -107,6 +102,84 @@ impl Actor for VMActor { } } +impl From for VmConfig { + fn from(vm: VirtualMachine) -> Self { + VmConfig { + cpus: Some(CpusConfig { + boot_vcpus: todo!(), + max_vcpus: todo!(), + topology: todo!(), + kvm_hyperv: todo!(), + max_phys_bits: todo!(), + nested: todo!(), + affinity: todo!(), + features: todo!(), + core_scheduling: todo!(), + }), + memory: Some(MemoryConfig { + size: todo!(), + hotplug_size: todo!(), + hotplugged_size: todo!(), + mergeable: todo!(), + hotplug_method: todo!(), + shared: todo!(), + hugepages: todo!(), + hugepage_size: todo!(), + prefault: todo!(), + thp: todo!(), + zones: todo!(), + }), + payload: PayloadConfig { + firmware: todo!(), + kernel: None, + cmdline: todo!(), + initramfs: todo!(), + igvm: todo!(), + host_data: todo!(), + }, + disks: Some(vec![ + DiskConfig { + ..Default::default() + } + ]), + net: Some(vec![ + NetConfig { + ..Default::default() + } + ]), + rng: Some( + RngConfig { + src: "/dev/urandom".to_string(), + iommu: Some(false) + } + ), + serial: Some( + ConsoleConfig { + mode: ConsoleMode::Null, + iommu: Some(false), + ..Default::default() + } + ), + debug_console: Some( + DebugConsoleConfig { + mode: ConsoleMode::Off, + iobase: Some(233), + ..Default::default() + } + ), + iommu: Some(false), + watchdog: Some(false), + pvpanic: Some(false), + platform: Some(PlatformConfig { + serial_number: Some("ds=nocloud".to_string()), + ..Default::default() + }), + landlock_enable: Some(false), + ..Default::default() + } + } +} + // allow conversion from VMActor to VMInstance to call API impl From for VMInstance { fn from(actor: VMActor) -> Self { diff --git a/odorobo/src/state/api.rs b/odorobo/src/ch_driver/api.rs similarity index 100% rename from odorobo/src/state/api.rs rename to odorobo/src/ch_driver/api.rs diff --git a/odorobo/src/state/devices.rs b/odorobo/src/ch_driver/devices.rs similarity index 96% rename from odorobo/src/state/devices.rs rename to odorobo/src/ch_driver/devices.rs index 5558eb8..2d02a0c 100644 --- a/odorobo/src/state/devices.rs +++ b/odorobo/src/ch_driver/devices.rs @@ -1,4 +1,4 @@ -use crate::state::VMInstance; +use crate::ch_driver::VMInstance; use cloud_hypervisor_client::{ apis::DefaultApi, models::{DeviceConfig, NetConfig, PciDeviceInfo, VmRemoveDevice}, diff --git a/odorobo/src/state/instance.rs b/odorobo/src/ch_driver/instance.rs similarity index 99% rename from odorobo/src/state/instance.rs rename to odorobo/src/ch_driver/instance.rs index ecbf347..8433f7e 100644 --- a/odorobo/src/state/instance.rs +++ b/odorobo/src/ch_driver/instance.rs @@ -17,7 +17,7 @@ use thiserror::Error; use tokio::task::JoinHandle; use tracing::{debug, error, info, trace, warn}; -use crate::state::{ +use crate::ch_driver::{ provisioning::hooks::HookManager, transform::{ConfigTransform, TransformChain}, }; diff --git a/odorobo/src/state/mod.rs b/odorobo/src/ch_driver/mod.rs similarity index 95% rename from odorobo/src/state/mod.rs rename to odorobo/src/ch_driver/mod.rs index 91b99c0..8701f5b 100644 --- a/odorobo/src/state/mod.rs +++ b/odorobo/src/ch_driver/mod.rs @@ -8,5 +8,6 @@ pub mod devices; pub mod instance; pub mod provisioning; pub mod transform; +pub mod actor; pub use instance::{VMInstance}; diff --git a/odorobo/src/state/provisioning/hooks/mod.rs b/odorobo/src/ch_driver/provisioning/hooks/mod.rs similarity index 100% rename from odorobo/src/state/provisioning/hooks/mod.rs rename to odorobo/src/ch_driver/provisioning/hooks/mod.rs diff --git a/odorobo/src/state/provisioning/hooks/networking.rs b/odorobo/src/ch_driver/provisioning/hooks/networking.rs similarity index 100% rename from odorobo/src/state/provisioning/hooks/networking.rs rename to odorobo/src/ch_driver/provisioning/hooks/networking.rs diff --git a/odorobo/src/state/provisioning/mod.rs b/odorobo/src/ch_driver/provisioning/mod.rs similarity index 93% rename from odorobo/src/state/provisioning/mod.rs rename to odorobo/src/ch_driver/provisioning/mod.rs index be490cc..24bdf32 100644 --- a/odorobo/src/state/provisioning/mod.rs +++ b/odorobo/src/ch_driver/provisioning/mod.rs @@ -2,5 +2,4 @@ //! //! Provides helper functions that calls the necessary hooks and various methods to start a //! Cloud Hypervisor process for a given instance -pub mod actor; pub mod hooks; diff --git a/odorobo/src/state/transform/console.rs b/odorobo/src/ch_driver/transform/console.rs similarity index 98% rename from odorobo/src/state/transform/console.rs rename to odorobo/src/ch_driver/transform/console.rs index b76711a..e675f06 100644 --- a/odorobo/src/state/transform/console.rs +++ b/odorobo/src/ch_driver/transform/console.rs @@ -2,7 +2,7 @@ use cloud_hypervisor_client::models::{ConsoleConfig, VmConfig}; use stable_eyre::Result; use tracing::trace; -use crate::state::VMInstance; +use crate::ch_driver::VMInstance; use super::ConfigTransform; diff --git a/odorobo/src/state/transform/mod.rs b/odorobo/src/ch_driver/transform/mod.rs similarity index 100% rename from odorobo/src/state/transform/mod.rs rename to odorobo/src/ch_driver/transform/mod.rs diff --git a/odorobo/src/state/transform/networking.rs b/odorobo/src/ch_driver/transform/networking.rs similarity index 100% rename from odorobo/src/state/transform/networking.rs rename to odorobo/src/ch_driver/transform/networking.rs diff --git a/odorobo/src/state/transform/path_verify.rs b/odorobo/src/ch_driver/transform/path_verify.rs similarity index 100% rename from odorobo/src/state/transform/path_verify.rs rename to odorobo/src/ch_driver/transform/path_verify.rs diff --git a/odorobo/src/state/transform/storage/file.rs b/odorobo/src/ch_driver/transform/storage/file.rs similarity index 100% rename from odorobo/src/state/transform/storage/file.rs rename to odorobo/src/ch_driver/transform/storage/file.rs diff --git a/odorobo/src/state/transform/storage/iscsi.rs b/odorobo/src/ch_driver/transform/storage/iscsi.rs similarity index 100% rename from odorobo/src/state/transform/storage/iscsi.rs rename to odorobo/src/ch_driver/transform/storage/iscsi.rs diff --git a/odorobo/src/state/transform/storage/mod.rs b/odorobo/src/ch_driver/transform/storage/mod.rs similarity index 99% rename from odorobo/src/state/transform/storage/mod.rs rename to odorobo/src/ch_driver/transform/storage/mod.rs index 1021465..1e04735 100644 --- a/odorobo/src/state/transform/storage/mod.rs +++ b/odorobo/src/ch_driver/transform/storage/mod.rs @@ -1,4 +1,4 @@ -use crate::state::transform::ConfigTransform; +use crate::ch_driver::transform::ConfigTransform; use async_trait::async_trait; use cloud_hypervisor_client::models::VmConfig; use stable_eyre::Result; diff --git a/odorobo/src/state/transform/storage/rbd.rs b/odorobo/src/ch_driver/transform/storage/rbd.rs similarity index 100% rename from odorobo/src/state/transform/storage/rbd.rs rename to odorobo/src/ch_driver/transform/storage/rbd.rs diff --git a/odorobo/src/main.rs b/odorobo/src/main.rs index e7f3215..cd8dd39 100644 --- a/odorobo/src/main.rs +++ b/odorobo/src/main.rs @@ -1,7 +1,7 @@ pub mod actors; pub mod http_api; pub mod networking; -mod state; +mod ch_driver; mod utils; pub mod messages; pub mod config; From 05cb7d9df44e120e9a07e3c7457221a1d051788c Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Tue, 28 Apr 2026 14:44:48 -0500 Subject: [PATCH 05/10] [wip] switch ch_driver to use VirtualMachine instead of VmConfig Signed-off-by: Caleb Jones --- Cargo.lock | 14 +++++++ Cargo.toml | 2 +- README.md | 2 +- odorobo/src/actors/agent_actor.rs | 11 +++-- odorobo/src/actors/http_actor.rs | 28 ------------- odorobo/src/ch_driver/actor.rs | 67 +++++++++++++++++-------------- odorobo/src/http_api/vms.rs | 16 +++----- odorobo/src/messages/vm.rs | 6 ++- odorobo/src/types.rs | 22 +++++----- odoroboctl/src/cli.rs | 17 ++------ 10 files changed, 82 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a573c2..2b522be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -668,6 +668,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -2934,6 +2935,19 @@ dependencies = [ "url", ] +[[package]] +name = "odoroboctl" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "reqwest", + "serde", + "serde_json", + "stable-eyre", + "tokio", +] + [[package]] name = "oid-registry" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index ce70611..5d2725a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["odorobo"] +members = ["odorobo", "odoroboctl"] [workspace.package] rust-version = "1.95.0" diff --git a/README.md b/README.md index c2f7e24..8b05fc8 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ sudo dnf in -y clang-devel nftables cloud-hypervisor cargo build --release # Run the Agent & Manager (requires write permissions to /run/odorobo) -sudo ./target/release/odorobo --manager-enabled=true # or set ODOROBO_MANAGER_ENABLED=true +sudo ./target/release/odorobo --manager-enabled # or set ODOROBO_MANAGER_ENABLED=true # Run on other boxes sudo ./target/release/odorobo diff --git a/odorobo/src/actors/agent_actor.rs b/odorobo/src/actors/agent_actor.rs index 7f0fd6a..3745c2d 100644 --- a/odorobo/src/actors/agent_actor.rs +++ b/odorobo/src/actors/agent_actor.rs @@ -1,7 +1,6 @@ -use crate::{config::Config, types::ObjectMetadata, messages::{Ping, Pong, agent::{AgentStatus, GetAgentStatus}, debug::PanicAgent, vm::*}, networking::actor::NetworkAgentActor, ch_driver::actor::VMActor, utils::actor_names::{NETWORK, VM, vm_actor_id}}; +use crate::{ch_driver::actor::VMActor, config::Config, messages::{Ping, Pong, agent::{AgentStatus, GetAgentStatus}, debug::PanicAgent, vm::*}, networking::actor::NetworkAgentActor, types::{ObjectMetadata, VirtualMachine}, utils::actor_names::{NETWORK, VM, vm_actor_id}}; use ahash::AHashMap; use bytesize::ByteSize; -use cloud_hypervisor_client::models::VmConfig; use kameo::prelude::*; use stable_eyre::{Report, Result}; use std::ops::ControlFlow; @@ -13,7 +12,7 @@ use kameo::error::PanicError; pub struct VMCacheData { actor_ref: ActorRef, - config: VmConfig + config: VirtualMachine } #[derive(RemoteActor)] @@ -137,7 +136,7 @@ impl Message for AgentActor { vmid, VMCacheData { actor_ref: actor_ref.clone(), - config: msg.config.clone() + config: Default::default() } ); @@ -285,12 +284,12 @@ impl Message for AgentActor { ) -> Self::Reply { let vcpus_used_by_vms = self.vms.values() - .map(|vm| vm.config.cpus.as_ref().map(|cpu_config| cpu_config.boot_vcpus).unwrap_or(0)) + .map(|vm| vm.config.data.vcpus) .reduce(|acc, cpus| acc + cpus) .unwrap_or(0) as u32; let ram_used_by_vms = self.vms.values() - .map(|vm| vm.config.memory.as_ref().map(|memory_config| memory_config.size).unwrap_or(0)) + .map(|vm| vm.config.data.memory.as_u64()) .reduce(|acc, memory| acc + memory) .unwrap_or(0) as u64; diff --git a/odorobo/src/actors/http_actor.rs b/odorobo/src/actors/http_actor.rs index a7d3f73..5728d79 100644 --- a/odorobo/src/actors/http_actor.rs +++ b/odorobo/src/actors/http_actor.rs @@ -17,34 +17,6 @@ pub struct HTTPActor { pub scheduler: ActorRef, } -impl HTTPActor { - pub fn create_vm_message(request: CreateVMRequest) -> CreateVM { - CreateVM { - vmid: request.data.id, - config: VmConfig { - cpus: Some(CpusConfig { - boot_vcpus: request.data.vcpus as i32, - max_vcpus: request - .data - .max_vcpus - .map(|v| v as i32) - .unwrap_or(request.data.vcpus as i32), - ..Default::default() - }), - memory: Some(MemoryConfig { - size: request.data.memory.as_u64() as i64, - ..Default::default() - }), - payload: PayloadConfig { - kernel: Some(request.data.image), - ..Default::default() - }, - ..Default::default() - }, - } - } -} - impl Actor for HTTPActor { type Args = ActorRef; type Error = Report; diff --git a/odorobo/src/ch_driver/actor.rs b/odorobo/src/ch_driver/actor.rs index ca968f7..2c716f1 100644 --- a/odorobo/src/ch_driver/actor.rs +++ b/odorobo/src/ch_driver/actor.rs @@ -1,5 +1,5 @@ use crate::{ch_driver::VMInstance, types::VirtualMachine}; -use cloud_hypervisor_client::models::{ConsoleConfig, ConsoleMode, CpusConfig, DebugConsoleConfig, DiskConfig, MemoryConfig, NetConfig, PayloadConfig, PlatformConfig, RngConfig, VmConfig}; +use cloud_hypervisor_client::models::{ConsoleConfig, ConsoleMode, CpuFeatures, CpusConfig, DebugConsoleConfig, DiskConfig, ImageType, MemoryConfig, NetConfig, PayloadConfig, PlatformConfig, RngConfig, VmConfig}; use kameo::prelude::*; use crate::messages::vm::{ DeleteVM, GetVMInfo, GetVMInfoReply, MigrateVMReceive, MigrateVMReceiveReply, PrepMigration, @@ -37,9 +37,7 @@ impl Actor for VMActor { #[tracing::instrument(skip_all)] async fn on_start((vmid, vm_config): Self::Args, actor_ref: ActorRef) -> Result { - - - let mut vminstance = VMInstance::spawn(&vmid.to_string(), vm_config, None).await?; + let mut vminstance = VMInstance::spawn(&vmid.to_string(), vm_config.map(VmConfig::from), None).await?; // Take the child process out so we can watch for unexpected death. // destroy() handles a missing child_process gracefully. @@ -106,44 +104,53 @@ impl From for VmConfig { fn from(vm: VirtualMachine) -> Self { VmConfig { cpus: Some(CpusConfig { - boot_vcpus: todo!(), - max_vcpus: todo!(), - topology: todo!(), - kvm_hyperv: todo!(), - max_phys_bits: todo!(), - nested: todo!(), - affinity: todo!(), - features: todo!(), - core_scheduling: todo!(), + boot_vcpus: 4, + max_vcpus: 4, + kvm_hyperv: Some(false), + max_phys_bits: Some(46), + nested: Some(false), + features: Some(CpuFeatures { + amx: Some(false) + }), + ..Default::default() }), memory: Some(MemoryConfig { - size: todo!(), - hotplug_size: todo!(), - hotplugged_size: todo!(), - mergeable: todo!(), - hotplug_method: todo!(), - shared: todo!(), - hugepages: todo!(), - hugepage_size: todo!(), - prefault: todo!(), - thp: todo!(), - zones: todo!(), + size: 4294967296, + mergeable: Some(false), + hotplug_method: Some("Acpi".to_string()), + shared: Some(true), + hugepages: Some(false), + prefault: Some(false), + thp: Some(true), + ..Default::default() }), payload: PayloadConfig { - firmware: todo!(), - kernel: None, - cmdline: todo!(), - initramfs: todo!(), - igvm: todo!(), - host_data: todo!(), + firmware: Some("/var/lib/odorobo/CLOUDHV.fd".to_string()), + cmdline: Some("odorobo.test=1".to_string()), + ..Default::default() }, disks: Some(vec![ DiskConfig { + // todo: the json i was given by cappy had disable_io_uring and disable_aio in this config, but I can't find these. I assume they were just a mistake. + path: Some("/var/lib/odorobo/f43.raw".to_string()), + readonly: Some(false), + direct: Some(false), + iommu: Some(false), + num_queues: Some(1), + queue_size: Some(128), + vhost_user: Some(false), + id: Some("_disk0".to_string()), + pci_segment: Some(0), + backing_files: Some(false), + sparse: Some(true), + image_type: Some(ImageType::Raw), ..Default::default() } ]), net: Some(vec![ NetConfig { + id: Some("net://devnet".to_string()), + mac: Some("46:59:52:67:67:67".to_string()), ..Default::default() } ]), diff --git a/odorobo/src/http_api/vms.rs b/odorobo/src/http_api/vms.rs index 672614b..22285db 100644 --- a/odorobo/src/http_api/vms.rs +++ b/odorobo/src/http_api/vms.rs @@ -49,27 +49,23 @@ async fn create_vm( State(state): State>, Json(request): Json, ) -> Result { - let vm_data = request.data.clone(); - let message = HTTPActor::create_vm_message(request); + let message = CreateVM { + vmid: request.data.data.id, + config: request.data.clone(), + }; let _reply = state.ask(message).await?; - Ok(Json(VirtualMachine { - data: vm_data, - node: None, - status: VMStatus::Provisioning, - ..Default::default() - })) + Ok(Json(request)) } async fn debug_create_vm( State(state): State>, - Json(request): Json, ) -> Result { let ulid = ulid::Ulid::new(); let message = CreateVM { vmid: ulid, - config: request.vm_config, + config: Default::default(), }; let _reply = state.ask(message).await?; diff --git a/odorobo/src/messages/vm.rs b/odorobo/src/messages/vm.rs index a1dd566..46f08f5 100644 --- a/odorobo/src/messages/vm.rs +++ b/odorobo/src/messages/vm.rs @@ -4,6 +4,8 @@ use kameo::prelude::*; use serde::{Deserialize, Serialize}; use ulid::Ulid; +use crate::types::VirtualMachine; + // TODO: when scheduler does createVM it also stores which server we put the Ulid on so it can do a in memory cache and doesn't need to hit the Server // for failover, the new node when it fails over will need to rebuild this cache via hitting a GetAllVMs message on every server // additionally, when the VmConfig is created, this determines the MAC address of the server. meaning as soon as we have this info, we need to hit the router via the scheduler, because the router might be slow. @@ -20,12 +22,12 @@ pub struct CreateVM { /// node-specific, paths, i.e attach LUNs, networking? /// /// this data would go to state::instance::spawn() - pub config: VmConfig, + pub config: VirtualMachine, } #[derive(Serialize, Deserialize, Reply, Debug)] pub struct CreateVMReply { - pub config: Option, + pub config: Option, } /// Message to delete a VM's config from the agent, shutting it down diff --git a/odorobo/src/types.rs b/odorobo/src/types.rs index 095af10..d799f3b 100644 --- a/odorobo/src/types.rs +++ b/odorobo/src/types.rs @@ -66,7 +66,7 @@ impl Default for StorageUri { #[derive(Serialize, Deserialize, Debug, JsonSchema, Default, Clone)] pub struct CreateVMRequest { /// Data of the VM to create - pub data: VMData, + pub data: VirtualMachine, /// Whether to boot the VM immediately after creation pub boot: bool, } @@ -127,7 +127,7 @@ pub struct UpdateVMRequest { pub volumes: Vec, } -#[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Default, Clone)] pub enum VMStatus { /// VM is currently running and operational. Running, @@ -139,7 +139,7 @@ pub enum VMStatus { Error(String), // error message } -#[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Default, Clone)] pub struct ObjectMetadata { /// Labels associated with the object. pub labels: BTreeMap, @@ -149,7 +149,7 @@ pub struct ObjectMetadata { /// Detailed information about a running VM // probably move this somewhere else -#[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Default, Clone)] pub struct VirtualMachine { /// VM configuration pub data: VMData, @@ -171,7 +171,7 @@ pub struct VirtualMachine { } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] pub struct AffinityRule { pub strictness: AffinityStrictness, pub affinity_type: AffinityType, @@ -180,25 +180,25 @@ pub struct AffinityRule { pub requirements: Vec } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] pub enum AffinityStrictness { Required, Preferred { weight: i64 } } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] pub enum AffinityType { VirtualMachine, Agent } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] pub enum AffinityDirection { Normal, Anti } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] pub struct AffinityRequirement { pub key: String, pub table: MetadataTable, @@ -206,13 +206,13 @@ pub struct AffinityRequirement { pub values: Vec } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] pub enum MetadataTable { Label, Annotation } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] pub enum Operator { In, NotIn, diff --git a/odoroboctl/src/cli.rs b/odoroboctl/src/cli.rs index 98bdbf0..28cada4 100644 --- a/odoroboctl/src/cli.rs +++ b/odoroboctl/src/cli.rs @@ -27,15 +27,7 @@ pub struct Cli { pub enum Command { /// Create a VM via the scheduler debug endpoint, /// optionally also booting it immediately after creation (if `--boot` is specified). - Create { - /// Path to the VM config file - /// (in Cloud Hypervisor JSON format) - config: PathBuf, - - /// Boot the VM after creation - #[arg(long)] - boot: bool, - }, + Create, /// List VMs currently known by the manager/agent. List, @@ -109,12 +101,9 @@ pub async fn run_command(cli: Cli) -> Result<()> { let base_url = cli.manager_addr; match cli.command { - Command::Create { config, boot } => { + Command::Create => { let url = format!("{}/vms", base_url); - let vm_config = - serde_json::from_str::(&std::fs::read_to_string(&config)?)?; - let body = DebugCreateVMRequest { vm_config, boot }; - let response = client.put(&url).json(&body).send().await?; + let response = client.put(&url).send().await?; print_message_response(response, "VM create request sent successfully").await?; } From 5bf7cd971c2d6fac9965bcdc9a070ec91985601b Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Tue, 28 Apr 2026 15:18:16 -0500 Subject: [PATCH 06/10] get vm meta manifest mostly working with odoroboctl Signed-off-by: Caleb Jones --- Cargo.lock | 3 +++ Cargo.toml | 2 ++ odorobo/Cargo.toml | 4 ++-- odorobo/src/actors/http_actor.rs | 3 --- odorobo/src/ch_driver/actor.rs | 8 ++++---- odorobo/src/http_api/vms.rs | 33 +++++--------------------------- odorobo/src/lib.rs | 1 + odorobo/src/messages/vm.rs | 3 ++- odorobo/src/types.rs | 4 ++-- odoroboctl/Cargo.toml | 5 +++++ odoroboctl/src/cli.rs | 31 +++++++++++++++++++++++------- 11 files changed, 50 insertions(+), 47 deletions(-) create mode 100644 odorobo/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2b522be..a7b25ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2939,13 +2939,16 @@ dependencies = [ name = "odoroboctl" version = "0.1.0" dependencies = [ + "bytesize", "chrono", "clap", + "odorobo", "reqwest", "serde", "serde_json", "stable-eyre", "tokio", + "ulid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5d2725a..50129f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ tracing-subscriber = { version = "0.3", features = [ "fmt", "json", ] } +ulid = { version = "1.2", features = ["serde", "uuid"] } +bytesize = { version = "2.3", features = ["serde"] } # our crates diff --git a/odorobo/Cargo.toml b/odorobo/Cargo.toml index 91cee6c..70a6407 100644 --- a/odorobo/Cargo.toml +++ b/odorobo/Cargo.toml @@ -13,6 +13,8 @@ serde_json = { workspace = true } serde = { workspace = true, features = ["derive"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } +ulid = { workspace = true } +bytesize = { workspace = true } http-body-util = "0.1" hyper = { version = "1.9", features = ["full"] } @@ -39,9 +41,7 @@ axum-extra = "0.12" tower-http = { version = "0.6", features = ["trace"] } kameo = { version = "0.20", features = ["remote"] } ahash = { version = "0.8", features = ["serde"] } -ulid = { version = "1.2", features = ["serde", "uuid"] } dirs = "6" -bytesize = { version = "2.3", features = ["serde"] } ipnet = {version = "2.12", features = ["serde", "schemars08"]} url = { version = "2.5", features = ["serde"] } async-trait = "0.1" diff --git a/odorobo/src/actors/http_actor.rs b/odorobo/src/actors/http_actor.rs index 5728d79..53f9ec6 100644 --- a/odorobo/src/actors/http_actor.rs +++ b/odorobo/src/actors/http_actor.rs @@ -1,4 +1,3 @@ -use cloud_hypervisor_client::models::{CpusConfig, MemoryConfig, PayloadConfig, VmConfig}; use kameo::prelude::*; use crate::messages::vm::{ AgentListVMs, AgentListVMsReply, CreateVM, CreateVMReply, DeleteVM, DeleteVMReply, @@ -6,8 +5,6 @@ use crate::messages::vm::{ }; use stable_eyre::{Report, Result}; -use crate::types::CreateVMRequest; - use super::scheduler_actor::SchedulerActor; const EXTERNAL_HTTP_ADDRESS: &str = "0.0.0.0:3000"; diff --git a/odorobo/src/ch_driver/actor.rs b/odorobo/src/ch_driver/actor.rs index 2c716f1..e2d979f 100644 --- a/odorobo/src/ch_driver/actor.rs +++ b/odorobo/src/ch_driver/actor.rs @@ -104,8 +104,8 @@ impl From for VmConfig { fn from(vm: VirtualMachine) -> Self { VmConfig { cpus: Some(CpusConfig { - boot_vcpus: 4, - max_vcpus: 4, + boot_vcpus: vm.data.vcpus as i32, + max_vcpus: vm.data.max_vcpus.unwrap_or(vm.data.vcpus) as i32, kvm_hyperv: Some(false), max_phys_bits: Some(46), nested: Some(false), @@ -115,7 +115,7 @@ impl From for VmConfig { ..Default::default() }), memory: Some(MemoryConfig { - size: 4294967296, + size: vm.data.memory.as_u64() as i64, mergeable: Some(false), hotplug_method: Some("Acpi".to_string()), shared: Some(true), @@ -132,7 +132,7 @@ impl From for VmConfig { disks: Some(vec![ DiskConfig { // todo: the json i was given by cappy had disable_io_uring and disable_aio in this config, but I can't find these. I assume they were just a mistake. - path: Some("/var/lib/odorobo/f43.raw".to_string()), + path: Some(vm.data.image), readonly: Some(false), direct: Some(false), iommu: Some(false), diff --git a/odorobo/src/http_api/vms.rs b/odorobo/src/http_api/vms.rs index 22285db..5efdd27 100644 --- a/odorobo/src/http_api/vms.rs +++ b/odorobo/src/http_api/vms.rs @@ -2,7 +2,7 @@ use crate::{ actors::http_actor::HTTPActor, types::{ - CreateVMRequest, DebugCreateVMRequest, UpdateVMRequest, VMData, VirtualMachine, VMListResponse, VMStatus, VmId + CreateVMRequest, UpdateVMRequest, VMData, VirtualMachine, VMListResponse, VMStatus, VmId }, messages::vm::CreateVM, utils::OdoroboError, }; use aide::axum::{ @@ -20,8 +20,6 @@ pub fn router() -> ApiRouter> { ApiRouter::new() .api_route("/", get(list_vms)) .api_route("/", post(create_vm)) - // undocumented debug route, do not use in prod - .route("/", axum::routing::put(debug_create_vm)) .api_route("/{vmid}", get(vm_info)) .api_route("/{vmid}", patch(update_vm)) .api_route("/{vmid}", delete(delete_vm)) @@ -50,34 +48,13 @@ async fn create_vm( Json(request): Json, ) -> Result { let message = CreateVM { - vmid: request.data.data.id, - config: request.data.clone(), + vmid: request.vm.data.id, + config: request.vm, }; - let _reply = state.ask(message).await?; + let reply = state.ask(message).await?; - Ok(Json(request)) -} - -async fn debug_create_vm( - State(state): State>, -) -> Result { - let ulid = ulid::Ulid::new(); - let message = CreateVM { - vmid: ulid, - config: Default::default(), - }; - - let _reply = state.ask(message).await?; - - Ok(Json(VirtualMachine { - status: VMStatus::Provisioning, - data: VMData { - id: ulid, - ..Default::default() - }, - ..Default::default() - })) + Ok(Json(reply)) } async fn delete_vm( diff --git a/odorobo/src/lib.rs b/odorobo/src/lib.rs new file mode 100644 index 0000000..cd40856 --- /dev/null +++ b/odorobo/src/lib.rs @@ -0,0 +1 @@ +pub mod types; diff --git a/odorobo/src/messages/vm.rs b/odorobo/src/messages/vm.rs index 46f08f5..512a793 100644 --- a/odorobo/src/messages/vm.rs +++ b/odorobo/src/messages/vm.rs @@ -1,6 +1,7 @@ //! VM-related messages use cloud_hypervisor_client::models::VmConfig; use kameo::prelude::*; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use ulid::Ulid; @@ -25,7 +26,7 @@ pub struct CreateVM { pub config: VirtualMachine, } -#[derive(Serialize, Deserialize, Reply, Debug)] +#[derive(Serialize, Deserialize, Reply, Debug, JsonSchema)] pub struct CreateVMReply { pub config: Option, } diff --git a/odorobo/src/types.rs b/odorobo/src/types.rs index d799f3b..2d340ac 100644 --- a/odorobo/src/types.rs +++ b/odorobo/src/types.rs @@ -66,7 +66,7 @@ impl Default for StorageUri { #[derive(Serialize, Deserialize, Debug, JsonSchema, Default, Clone)] pub struct CreateVMRequest { /// Data of the VM to create - pub data: VirtualMachine, + pub vm: VirtualMachine, /// Whether to boot the VM immediately after creation pub boot: bool, } @@ -167,7 +167,7 @@ pub struct VirtualMachine { pub metadata: Option, /// List of Affinity rules for scheduling. These are ANDed / summed together depending on the strictness. - pub affinity: Vec + pub affinity: Option> } diff --git a/odoroboctl/Cargo.toml b/odoroboctl/Cargo.toml index 22be64c..75119ec 100644 --- a/odoroboctl/Cargo.toml +++ b/odoroboctl/Cargo.toml @@ -11,4 +11,9 @@ tokio = { workspace = true, features = ["full"] } reqwest = { workspace = true, features = ["json"] } serde_json = { workspace = true } serde = { workspace = true, features = ["derive"] } +ulid = { workspace = true } +bytesize = { workspace = true } + chrono = { version = "0.4", features = ["serde"] } + +odorobo = { workspace = true } diff --git a/odoroboctl/src/cli.rs b/odoroboctl/src/cli.rs index 28cada4..d551737 100644 --- a/odoroboctl/src/cli.rs +++ b/odoroboctl/src/cli.rs @@ -4,6 +4,9 @@ use clap::{Parser, Subcommand}; use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; use stable_eyre::Result; +use odorobo::types::{CreateVMRequest, VMData, VirtualMachine}; +use ulid::Ulid; +use bytesize::ByteSize; #[derive(Parser)] #[command( @@ -45,12 +48,6 @@ pub enum Command { }, } -#[derive(Debug, Serialize)] -struct DebugCreateVMRequest { - vm_config: serde_json::Value, - boot: bool, -} - #[derive(Debug, Deserialize)] #[serde(transparent)] struct VmId(String); @@ -102,8 +99,28 @@ pub async fn run_command(cli: Cli) -> Result<()> { match cli.command { Command::Create => { + let vm = VirtualMachine { + data: VMData { + id: Ulid::new(), + name: "test_vm".to_string(), + vcpus: 4, + max_vcpus: None, + memory: ByteSize::gib(4), + image: "/var/lib/odorobo/f43.raw".to_string(), + ..Default::default() + }, + ..Default::default() + }; + + let request = CreateVMRequest { + vm, + boot: true + }; + let url = format!("{}/vms", base_url); - let response = client.put(&url).send().await?; + let response = client.post(&url).json(&request).send().await?; + + println!("{:?}", response.url()); print_message_response(response, "VM create request sent successfully").await?; } From 33135c450de9e9a3bdd67bc7ebad88716206f5f9 Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Tue, 28 Apr 2026 15:20:44 -0500 Subject: [PATCH 07/10] fix a few warnings Signed-off-by: Caleb Jones --- odorobo/src/http_api/vms.rs | 2 +- odoroboctl/src/cli.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/odorobo/src/http_api/vms.rs b/odorobo/src/http_api/vms.rs index 5efdd27..6e6b2fb 100644 --- a/odorobo/src/http_api/vms.rs +++ b/odorobo/src/http_api/vms.rs @@ -2,7 +2,7 @@ use crate::{ actors::http_actor::HTTPActor, types::{ - CreateVMRequest, UpdateVMRequest, VMData, VirtualMachine, VMListResponse, VMStatus, VmId + CreateVMRequest, UpdateVMRequest, VirtualMachine, VMListResponse, VmId }, messages::vm::CreateVM, utils::OdoroboError, }; use aide::axum::{ diff --git a/odoroboctl/src/cli.rs b/odoroboctl/src/cli.rs index d551737..e2fd835 100644 --- a/odoroboctl/src/cli.rs +++ b/odoroboctl/src/cli.rs @@ -1,8 +1,6 @@ -use std::path::PathBuf; - use clap::{Parser, Subcommand}; use reqwest::{Client, Response}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize}; use stable_eyre::Result; use odorobo::types::{CreateVMRequest, VMData, VirtualMachine}; use ulid::Ulid; From 5ecc31266b7d0d901216cca405ba7a1c823c1925 Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Tue, 28 Apr 2026 20:26:40 -0500 Subject: [PATCH 08/10] address most of cappy's feedback Signed-off-by: Caleb Jones --- odorobo/src/ch_driver/actor.rs | 18 +++--------------- odorobo/src/types.rs | 1 + odoroboctl/src/cli.rs | 2 +- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/odorobo/src/ch_driver/actor.rs b/odorobo/src/ch_driver/actor.rs index e2d979f..6e9395a 100644 --- a/odorobo/src/ch_driver/actor.rs +++ b/odorobo/src/ch_driver/actor.rs @@ -100,6 +100,7 @@ impl Actor for VMActor { } } +// todo: improve a lot of these config options. most of them should be set by the manifest impl From for VmConfig { fn from(vm: VirtualMachine) -> Self { VmConfig { @@ -126,7 +127,6 @@ impl From for VmConfig { }), payload: PayloadConfig { firmware: Some("/var/lib/odorobo/CLOUDHV.fd".to_string()), - cmdline: Some("odorobo.test=1".to_string()), ..Default::default() }, disks: Some(vec![ @@ -160,20 +160,8 @@ impl From for VmConfig { iommu: Some(false) } ), - serial: Some( - ConsoleConfig { - mode: ConsoleMode::Null, - iommu: Some(false), - ..Default::default() - } - ), - debug_console: Some( - DebugConsoleConfig { - mode: ConsoleMode::Off, - iobase: Some(233), - ..Default::default() - } - ), + serial: None, + debug_console: None, iommu: Some(false), watchdog: Some(false), pvpanic: Some(false), diff --git a/odorobo/src/types.rs b/odorobo/src/types.rs index 2d340ac..b526cf8 100644 --- a/odorobo/src/types.rs +++ b/odorobo/src/types.rs @@ -212,6 +212,7 @@ pub enum MetadataTable { Annotation } +// todo: possibly replace with std::ops #[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] pub enum Operator { In, diff --git a/odoroboctl/src/cli.rs b/odoroboctl/src/cli.rs index e2fd835..7e3cec3 100644 --- a/odoroboctl/src/cli.rs +++ b/odoroboctl/src/cli.rs @@ -96,7 +96,7 @@ pub async fn run_command(cli: Cli) -> Result<()> { let base_url = cli.manager_addr; match cli.command { - Command::Create => { + Command::Create => { // TODO: setup actual cli args for these parameters. or just take in arbitrary json and serialize it into a VirtualMachine. let vm = VirtualMachine { data: VMData { id: Ulid::new(), From 5b666b17dc70347ce96bfcf03b87cdcc4108b3e4 Mon Sep 17 00:00:00 2001 From: Caleb Jones Date: Wed, 29 Apr 2026 12:44:10 -0500 Subject: [PATCH 09/10] resolve some of cappy's feedback. Signed-off-by: Caleb Jones --- odorobo/src/ch_driver/actor.rs | 16 ++-------------- odoroboctl/src/cli.rs | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/odorobo/src/ch_driver/actor.rs b/odorobo/src/ch_driver/actor.rs index 6e9395a..387dc83 100644 --- a/odorobo/src/ch_driver/actor.rs +++ b/odorobo/src/ch_driver/actor.rs @@ -1,5 +1,5 @@ use crate::{ch_driver::VMInstance, types::VirtualMachine}; -use cloud_hypervisor_client::models::{ConsoleConfig, ConsoleMode, CpuFeatures, CpusConfig, DebugConsoleConfig, DiskConfig, ImageType, MemoryConfig, NetConfig, PayloadConfig, PlatformConfig, RngConfig, VmConfig}; +use cloud_hypervisor_client::models::{CpuFeatures, CpusConfig, DiskConfig, ImageType, MemoryConfig, NetConfig, PayloadConfig, PlatformConfig, VmConfig}; use kameo::prelude::*; use crate::messages::vm::{ DeleteVM, GetVMInfo, GetVMInfoReply, MigrateVMReceive, MigrateVMReceiveReply, PrepMigration, @@ -108,7 +108,6 @@ impl From for VmConfig { boot_vcpus: vm.data.vcpus as i32, max_vcpus: vm.data.max_vcpus.unwrap_or(vm.data.vcpus) as i32, kvm_hyperv: Some(false), - max_phys_bits: Some(46), nested: Some(false), features: Some(CpuFeatures { amx: Some(false) @@ -130,7 +129,7 @@ impl From for VmConfig { ..Default::default() }, disks: Some(vec![ - DiskConfig { + DiskConfig { // todo: get cappy to make this auto generate this via the manifest's volumes atribute. // todo: the json i was given by cappy had disable_io_uring and disable_aio in this config, but I can't find these. I assume they were just a mistake. path: Some(vm.data.image), readonly: Some(false), @@ -154,17 +153,6 @@ impl From for VmConfig { ..Default::default() } ]), - rng: Some( - RngConfig { - src: "/dev/urandom".to_string(), - iommu: Some(false) - } - ), - serial: None, - debug_console: None, - iommu: Some(false), - watchdog: Some(false), - pvpanic: Some(false), platform: Some(PlatformConfig { serial_number: Some("ds=nocloud".to_string()), ..Default::default() diff --git a/odoroboctl/src/cli.rs b/odoroboctl/src/cli.rs index 7e3cec3..b769271 100644 --- a/odoroboctl/src/cli.rs +++ b/odoroboctl/src/cli.rs @@ -26,7 +26,7 @@ pub struct Cli { #[derive(Subcommand)] pub enum Command { - /// Create a VM via the scheduler debug endpoint, + /// Create a VM via the scheduler endpoint, /// optionally also booting it immediately after creation (if `--boot` is specified). Create, From 6acc9ae53ba3608ba4af3f49ee182e300591bc76 Mon Sep 17 00:00:00 2001 From: Cappy Ishihara Date: Thu, 30 Apr 2026 09:24:20 +0700 Subject: [PATCH 10/10] infer from ch defaults --- odorobo/src/ch_driver/actor.rs | 37 +++++++--------------------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/odorobo/src/ch_driver/actor.rs b/odorobo/src/ch_driver/actor.rs index 387dc83..4586c87 100644 --- a/odorobo/src/ch_driver/actor.rs +++ b/odorobo/src/ch_driver/actor.rs @@ -107,21 +107,10 @@ impl From for VmConfig { cpus: Some(CpusConfig { boot_vcpus: vm.data.vcpus as i32, max_vcpus: vm.data.max_vcpus.unwrap_or(vm.data.vcpus) as i32, - kvm_hyperv: Some(false), - nested: Some(false), - features: Some(CpuFeatures { - amx: Some(false) - }), ..Default::default() }), memory: Some(MemoryConfig { size: vm.data.memory.as_u64() as i64, - mergeable: Some(false), - hotplug_method: Some("Acpi".to_string()), - shared: Some(true), - hugepages: Some(false), - prefault: Some(false), - thp: Some(true), ..Default::default() }), payload: PayloadConfig { @@ -130,34 +119,22 @@ impl From for VmConfig { }, disks: Some(vec![ DiskConfig { // todo: get cappy to make this auto generate this via the manifest's volumes atribute. - // todo: the json i was given by cappy had disable_io_uring and disable_aio in this config, but I can't find these. I assume they were just a mistake. path: Some(vm.data.image), - readonly: Some(false), - direct: Some(false), - iommu: Some(false), - num_queues: Some(1), - queue_size: Some(128), - vhost_user: Some(false), - id: Some("_disk0".to_string()), - pci_segment: Some(0), - backing_files: Some(false), - sparse: Some(true), image_type: Some(ImageType::Raw), ..Default::default() } ]), - net: Some(vec![ - NetConfig { - id: Some("net://devnet".to_string()), - mac: Some("46:59:52:67:67:67".to_string()), - ..Default::default() - } - ]), + // todo: generate from VM network field + // net: Some(vec![ + // NetConfig { + // id: Some("net://devnet".to_string()), + // ..Default::default() + // } + // ]), platform: Some(PlatformConfig { serial_number: Some("ds=nocloud".to_string()), ..Default::default() }), - landlock_enable: Some(false), ..Default::default() } }