From 8630e3d9192421f91c8cf7d771441b9e4841adbe Mon Sep 17 00:00:00 2001 From: Guvenc Gulce Date: Fri, 26 Sep 2025 13:43:57 +0200 Subject: [PATCH 1/7] Unify error handling across all services Signed-off-by: Guvenc Gulce --- Cargo.lock | 22 +- feos/services/host-service/Cargo.toml | 1 + feos/services/host-service/src/api.rs | 153 +++-------- feos/services/host-service/src/error.rs | 38 +++ feos/services/host-service/src/lib.rs | 16 +- feos/services/host-service/src/worker/info.rs | 150 ++++++----- feos/services/host-service/src/worker/ops.rs | 26 +- .../services/host-service/src/worker/power.rs | 6 +- feos/services/image-service/Cargo.toml | 1 + feos/services/image-service/src/api.rs | 78 +++--- feos/services/image-service/src/error.rs | 44 +++ feos/services/image-service/src/lib.rs | 17 +- feos/services/image-service/src/worker.rs | 99 ++++--- feos/services/vm-service/Cargo.toml | 2 +- feos/services/vm-service/src/api.rs | 211 +++++---------- feos/services/vm-service/src/dispatcher.rs | 5 +- .../vm-service/src/dispatcher_handlers.rs | 250 +++++++----------- feos/services/vm-service/src/lib.rs | 64 ++++- .../vm-service/src/persistence/mod.rs | 18 ++ .../vm-service/src/persistence/repository.rs | 34 +-- feos/services/vm-service/src/worker.rs | 38 +-- 21 files changed, 625 insertions(+), 648 deletions(-) create mode 100644 feos/services/host-service/src/error.rs create mode 100644 feos/services/image-service/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index d78b66a..bbe68f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,7 +428,7 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "thiserror 2.0.12", + "thiserror 2.0.16", "url", "uuid", ] @@ -620,7 +620,7 @@ dependencies = [ "hickory-proto", "ipnet", "rand 0.9.2", - "thiserror 2.0.12", + "thiserror 2.0.16", "url", ] @@ -1138,7 +1138,7 @@ dependencies = [ "ipnet", "once_cell", "rand 0.9.2", - "thiserror 2.0.12", + "thiserror 2.0.16", "tinyvec", "tracing", "url", @@ -1191,6 +1191,7 @@ dependencies = [ "rustls-pki-types", "sha2", "tempfile", + "thiserror 2.0.16", "tokio", "tokio-stream", "tonic", @@ -1513,6 +1514,7 @@ dependencies = [ "serde", "serde_json", "tempfile", + "thiserror 2.0.16", "tokio", "tokio-stream", "tonic", @@ -1850,7 +1852,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -3242,11 +3244,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -3262,9 +3264,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -3653,7 +3655,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 1.0.69", + "thiserror 2.0.16", "tokio", "tokio-stream", "tonic", diff --git a/feos/services/host-service/Cargo.toml b/feos/services/host-service/Cargo.toml index a4eaf4b..a694349 100644 --- a/feos/services/host-service/Cargo.toml +++ b/feos/services/host-service/Cargo.toml @@ -15,6 +15,7 @@ hyper-util = { version = "0.1.3", features = ["full"] } hyper-rustls = "0.27.2" http-body-util = "0.1.2" rustls-pki-types = "1.0" +thiserror = "2.0.16" # Workspace dependencies tokio = { workspace = true } diff --git a/feos/services/host-service/src/api.rs b/feos/services/host-service/src/api.rs index 7b473cc..ef73f72 100644 --- a/feos/services/host-service/src/api.rs +++ b/feos/services/host-service/src/api.rs @@ -25,6 +25,30 @@ impl HostApiHandler { } } +async fn dispatch_and_wait( + dispatcher: &mpsc::Sender, + command_constructor: impl FnOnce(oneshot::Sender>) -> Command, +) -> Result, Status> +where + E: Into, +{ + let (resp_tx, resp_rx) = oneshot::channel(); + let cmd = command_constructor(resp_tx); + + dispatcher + .send(cmd) + .await + .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; + + match resp_rx.await { + Ok(Ok(result)) => Ok(Response::new(result)), + Ok(Err(e)) => Err(e.into()), + Err(_) => Err(Status::internal( + "Dispatcher task dropped response channel.", + )), + } +} + #[tonic::async_trait] impl HostService for HostApiHandler { type StreamKernelLogsStream = @@ -36,20 +60,7 @@ impl HostService for HostApiHandler { _request: Request, ) -> Result, Status> { info!("HostApi: Received Hostname request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::GetHostname(resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, Command::GetHostname).await } async fn get_memory( @@ -57,20 +68,7 @@ impl HostService for HostApiHandler { _request: Request, ) -> Result, Status> { info!("HostApi: Received GetMemory request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::GetMemory(resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, Command::GetMemory).await } async fn get_cpu_info( @@ -78,20 +76,7 @@ impl HostService for HostApiHandler { _request: Request, ) -> Result, Status> { info!("HostApi: Received GetCPUInfo request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::GetCPUInfo(resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, Command::GetCPUInfo).await } async fn get_network_info( @@ -99,20 +84,7 @@ impl HostService for HostApiHandler { _request: Request, ) -> Result, Status> { info!("HostApi: Received GetNetworkInfo request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::GetNetworkInfo(resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, Command::GetNetworkInfo).await } async fn shutdown( @@ -120,20 +92,10 @@ impl HostService for HostApiHandler { request: Request, ) -> Result, Status> { info!("HostApi: Received Shutdown request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::Shutdown(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::Shutdown(request.into_inner(), resp_tx) + }) + .await } async fn reboot( @@ -141,20 +103,10 @@ impl HostService for HostApiHandler { request: Request, ) -> Result, Status> { info!("HostApi: Received Reboot request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::Reboot(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::Reboot(request.into_inner(), resp_tx) + }) + .await } async fn upgrade_feos_binary( @@ -162,20 +114,10 @@ impl HostService for HostApiHandler { request: Request, ) -> Result, Status> { info!("HostApi: Received UpgradeFeosBinary request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::UpgradeFeosBinary(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::UpgradeFeosBinary(request.into_inner(), resp_tx) + }) + .await } async fn stream_kernel_logs( @@ -213,19 +155,6 @@ impl HostService for HostApiHandler { _request: Request, ) -> Result, Status> { info!("HostApi: Received GetVersionInfo request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::GetVersionInfo(resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, Command::GetVersionInfo).await } } diff --git a/feos/services/host-service/src/error.rs b/feos/services/host-service/src/error.rs new file mode 100644 index 0000000..ac652a3 --- /dev/null +++ b/feos/services/host-service/src/error.rs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +use tonic::Status; + +#[derive(Debug, thiserror::Error)] +pub enum HostError { + #[error("Failed to get system hostname")] + Hostname(#[from] nix::Error), + + #[error("Failed to read system info from {path}")] + SystemInfoRead { + #[source] + source: std::io::Error, + path: String, + }, + + #[error("Host power operation failed")] + PowerOperation(#[source] nix::Error), + + #[error("Failed to create log reader: {0}")] + LogReader(String), +} + +impl From for Status { + fn from(err: HostError) -> Self { + log::error!("HostServiceError: {}", err); + match err { + HostError::SystemInfoRead { path, .. } => { + Status::internal(format!("Failed to read system info from {}", path)) + } + HostError::Hostname(_) | HostError::PowerOperation(_) => { + Status::internal("An internal host error occurred") + } + HostError::LogReader(msg) => Status::internal(msg), + } + } +} diff --git a/feos/services/host-service/src/lib.rs b/feos/services/host-service/src/lib.rs index eb947ba..ea4c8d6 100644 --- a/feos/services/host-service/src/lib.rs +++ b/feos/services/host-service/src/lib.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 +use crate::error::HostError; use feos_proto::host_service::{ FeosLogEntry, GetCpuInfoResponse, GetNetworkInfoResponse, GetVersionInfoResponse, HostnameResponse, KernelLogEntry, MemoryResponse, RebootRequest, RebootResponse, @@ -12,15 +13,16 @@ use tonic::Status; pub mod api; pub mod dispatcher; +pub mod error; pub mod worker; #[derive(Debug)] pub enum Command { - GetHostname(oneshot::Sender>), - GetMemory(oneshot::Sender>), - GetCPUInfo(oneshot::Sender>), - GetNetworkInfo(oneshot::Sender>), - GetVersionInfo(oneshot::Sender>), + GetHostname(oneshot::Sender>), + GetMemory(oneshot::Sender>), + GetCPUInfo(oneshot::Sender>), + GetNetworkInfo(oneshot::Sender>), + GetVersionInfo(oneshot::Sender>), UpgradeFeosBinary( UpgradeFeosBinaryRequest, oneshot::Sender>, @@ -29,11 +31,11 @@ pub enum Command { StreamFeOSLogs(mpsc::Sender>), Shutdown( ShutdownRequest, - oneshot::Sender>, + oneshot::Sender>, ), Reboot( RebootRequest, - oneshot::Sender>, + oneshot::Sender>, ), } diff --git a/feos/services/host-service/src/worker/info.rs b/feos/services/host-service/src/worker/info.rs index 42077b4..c4b5236 100644 --- a/feos/services/host-service/src/worker/info.rs +++ b/feos/services/host-service/src/worker/info.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 +use crate::error::HostError; use feos_proto::host_service::{ CpuInfo, GetCpuInfoResponse, GetNetworkInfoResponse, GetVersionInfoResponse, HostnameResponse, MemInfo, MemoryResponse, NetDev, @@ -12,37 +13,43 @@ use std::path::Path; use tokio::fs::{self, File}; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::sync::oneshot; -use tonic::Status; -pub async fn handle_hostname(responder: oneshot::Sender>) { +pub async fn handle_hostname(responder: oneshot::Sender>) { info!("HostWorker: Processing Hostname request."); - let result = match unistd::gethostname() { - Ok(host) => { - let hostname = host - .into_string() - .unwrap_or_else(|_| "Invalid UTF-8".into()); - Ok(HostnameResponse { hostname }) - } - Err(e) => { - let msg = format!("Failed to get system hostname: {e}"); - error!("HostWorker: {msg}"); - Err(Status::internal(msg)) - } - }; + let result = (|| { + let host = unistd::gethostname()?; + let hostname = host + .into_string() + .unwrap_or_else(|_| "Invalid UTF-8".into()); + Ok(HostnameResponse { hostname }) + })(); if responder.send(result).is_err() { error!("HostWorker: Failed to send response for Hostname. API handler may have timed out."); } } -async fn read_and_parse_meminfo() -> Result { - let file = File::open("/proc/meminfo").await?; +async fn read_and_parse_meminfo() -> Result { + let path = "/proc/meminfo"; + let file = File::open(path) + .await + .map_err(|e| HostError::SystemInfoRead { + source: e, + path: path.to_string(), + })?; let reader = BufReader::new(file); let mut lines = reader.lines(); let mut values = HashMap::new(); - while let Some(line) = lines.next_line().await? { + while let Some(line) = lines + .next_line() + .await + .map_err(|e| HostError::SystemInfoRead { + source: e, + path: path.to_string(), + })? + { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() >= 2 { let key = parts[0].trim_end_matches(':'); @@ -111,17 +118,13 @@ async fn read_and_parse_meminfo() -> Result { }) } -pub async fn handle_get_memory(responder: oneshot::Sender>) { +pub async fn handle_get_memory(responder: oneshot::Sender>) { info!("HostWorker: Processing GetMemory request."); - let result = match read_and_parse_meminfo().await { - Ok(mem_info) => Ok(MemoryResponse { + let result = read_and_parse_meminfo() + .await + .map(|mem_info| MemoryResponse { mem_info: Some(mem_info), - }), - Err(e) => { - error!("HostWorker: Failed to get memory info: {e}"); - Err(Status::internal(format!("Failed to get memory info: {e}"))) - } - }; + }); if responder.send(result).is_err() { error!( @@ -170,15 +173,28 @@ fn parse_map_to_cpu_info(map: &HashMap) -> CpuInfo { } } -async fn read_and_parse_cpuinfo() -> Result, std::io::Error> { - let file = File::open("/proc/cpuinfo").await?; +async fn read_and_parse_cpuinfo() -> Result, HostError> { + let path = "/proc/cpuinfo"; + let file = File::open(path) + .await + .map_err(|e| HostError::SystemInfoRead { + source: e, + path: path.to_string(), + })?; let reader = BufReader::new(file); let mut lines = reader.lines(); let mut cpus = Vec::new(); let mut current_cpu_map = HashMap::new(); - while let Some(line) = lines.next_line().await? { + while let Some(line) = lines + .next_line() + .await + .map_err(|e| HostError::SystemInfoRead { + source: e, + path: path.to_string(), + })? + { if line.trim().is_empty() { if !current_cpu_map.is_empty() { let cpu_info = parse_map_to_cpu_info(¤t_cpu_map); @@ -204,15 +220,13 @@ async fn read_and_parse_cpuinfo() -> Result, std::io::Error> { Ok(cpus) } -pub async fn handle_get_cpu_info(responder: oneshot::Sender>) { +pub async fn handle_get_cpu_info( + responder: oneshot::Sender>, +) { info!("HostWorker: Processing GetCPUInfo request."); - let result = match read_and_parse_cpuinfo().await { - Ok(cpu_info) => Ok(GetCpuInfoResponse { cpu_info }), - Err(e) => { - error!("HostWorker: Failed to get CPU info: {e}"); - Err(Status::internal(format!("Failed to get CPU info: {e}"))) - } - }; + let result = read_and_parse_cpuinfo() + .await + .map(|cpu_info| GetCpuInfoResponse { cpu_info }); if responder.send(result).is_err() { error!( @@ -230,11 +244,24 @@ async fn read_net_stat(base_path: &Path, stat_name: &str) -> u64 { .unwrap_or(0) } -async fn read_all_net_stats() -> Result, std::io::Error> { +async fn read_all_net_stats() -> Result, HostError> { + let path = "/sys/class/net"; let mut devices = Vec::new(); - let mut entries = fs::read_dir("/sys/class/net").await?; + let mut entries = fs::read_dir(path) + .await + .map_err(|e| HostError::SystemInfoRead { + source: e, + path: path.to_string(), + })?; - while let Some(entry) = entries.next_entry().await? { + while let Some(entry) = entries + .next_entry() + .await + .map_err(|e| HostError::SystemInfoRead { + source: e, + path: path.to_string(), + })? + { let path = entry.path(); if !path.is_dir() { continue; @@ -276,18 +303,12 @@ async fn read_all_net_stats() -> Result, std::io::Error> { } pub async fn handle_get_network_info( - responder: oneshot::Sender>, + responder: oneshot::Sender>, ) { info!("HostWorker: Processing GetNetworkInfo request."); - let result = match read_all_net_stats().await { - Ok(devices) => Ok(GetNetworkInfoResponse { devices }), - Err(e) => { - error!("HostWorker: Failed to get network info: {e}"); - Err(Status::internal(format!( - "Failed to get network info from sysfs: {e}" - ))) - } - }; + let result = read_all_net_stats() + .await + .map(|devices| GetNetworkInfoResponse { devices }); if responder.send(result).is_err() { error!( @@ -297,26 +318,23 @@ pub async fn handle_get_network_info( } pub async fn handle_get_version_info( - responder: oneshot::Sender>, + responder: oneshot::Sender>, ) { info!("HostWorker: Processing GetVersionInfo request."); - - let kernel_version_res = fs::read_to_string("/proc/version").await; - - let result = match kernel_version_res { - Ok(kernel_version) => { + let path = "/proc/version"; + let result = fs::read_to_string(path) + .await + .map(|kernel_version| { let feos_version = env!("CARGO_PKG_VERSION").to_string(); - Ok(GetVersionInfoResponse { + GetVersionInfoResponse { kernel_version: kernel_version.trim().to_string(), feos_version, - }) - } - Err(e) => { - let msg = format!("Failed to read kernel version from /proc/version: {e}"); - error!("HostWorker: {msg}"); - Err(Status::internal(msg)) - } - }; + } + }) + .map_err(|e| HostError::SystemInfoRead { + source: e, + path: path.to_string(), + }); if responder.send(result).is_err() { error!( diff --git a/feos/services/host-service/src/worker/ops.rs b/feos/services/host-service/src/worker/ops.rs index f2a17fd..c0cf711 100644 --- a/feos/services/host-service/src/worker/ops.rs +++ b/feos/services/host-service/src/worker/ops.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 -use crate::RestartSignal; +use crate::{error::HostError, RestartSignal}; use digest::Digest; use feos_proto::host_service::{ FeosLogEntry, KernelLogEntry, UpgradeFeosBinaryRequest, UpgradeFeosBinaryResponse, @@ -36,12 +36,9 @@ pub async fn handle_stream_feos_logs( let mut reader = match log_handle.new_reader().await { Ok(r) => r, Err(e) => { - error!("HostWorker: Failed to create log reader: {e}"); - if grpc_tx - .send(Err(Status::internal("Failed to create log reader"))) - .await - .is_err() - { + let err = HostError::LogReader(e.to_string()); + error!("HostWorker: {err}"); + if grpc_tx.send(Err(err.into())).await.is_err() { warn!("HostWorker: gRPC client for FeOS logs disconnected before error could be sent."); } return; @@ -74,9 +71,12 @@ pub async fn handle_stream_kernel_logs(grpc_tx: mpsc::Sender f, Err(e) => { - let msg = format!("Failed to open {KMSG_PATH}: {e}"); - error!("HostWorker: {msg}"); - if grpc_tx.send(Err(Status::internal(msg))).await.is_err() { + let err = HostError::SystemInfoRead { + source: e, + path: KMSG_PATH.to_string(), + }; + error!("HostWorker: {err}"); + if grpc_tx.send(Err(err.into())).await.is_err() { warn!("HostWorker: gRPC client for kernel logs disconnected before error could be sent."); } return; @@ -107,9 +107,9 @@ pub async fn handle_stream_kernel_logs(grpc_tx: mpsc::Sender { - let msg = format!("Error reading from {KMSG_PATH}: {e}"); - error!("HostWorker: {msg}"); - let _ = grpc_tx.send(Err(Status::internal(msg))).await; + let err = HostError::SystemInfoRead { source: e, path: KMSG_PATH.to_string() }; + error!("HostWorker: {err}"); + let _ = grpc_tx.send(Err(err.into())).await; break; } } diff --git a/feos/services/host-service/src/worker/power.rs b/feos/services/host-service/src/worker/power.rs index a4cb3d0..b0be448 100644 --- a/feos/services/host-service/src/worker/power.rs +++ b/feos/services/host-service/src/worker/power.rs @@ -1,15 +1,15 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 +use crate::error::HostError; use feos_proto::host_service::{RebootRequest, RebootResponse, ShutdownRequest, ShutdownResponse}; use log::{error, info}; use nix::sys::reboot::{reboot, RebootMode}; use tokio::sync::oneshot; -use tonic::Status; pub async fn handle_shutdown( _req: ShutdownRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, ) { info!("HostWorker: Processing Shutdown request."); @@ -32,7 +32,7 @@ pub async fn handle_shutdown( pub async fn handle_reboot( _req: RebootRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, ) { info!("HostWorker: Processing Reboot request."); diff --git a/feos/services/image-service/Cargo.toml b/feos/services/image-service/Cargo.toml index 967e56e..e472452 100644 --- a/feos/services/image-service/Cargo.toml +++ b/feos/services/image-service/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true feos-proto = { workspace = true } oci-distribution = { workspace = true } tempfile = { workspace = true } +thiserror = "2.0.16" # Workspace dependencies tokio = { workspace = true } diff --git a/feos/services/image-service/src/api.rs b/feos/services/image-service/src/api.rs index 2f6ff9b..2f8ef2c 100644 --- a/feos/services/image-service/src/api.rs +++ b/feos/services/image-service/src/api.rs @@ -23,6 +23,30 @@ impl ImageApiHandler { } } +async fn dispatch_and_wait( + dispatcher: &mpsc::Sender, + command_constructor: impl FnOnce(oneshot::Sender>) -> Command, +) -> Result, Status> +where + E: Into, +{ + let (resp_tx, resp_rx) = oneshot::channel(); + let cmd = command_constructor(resp_tx); + + dispatcher + .send(cmd) + .await + .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; + + match resp_rx.await { + Ok(Ok(result)) => Ok(Response::new(result)), + Ok(Err(e)) => Err(e.into()), + Err(_) => Err(Status::internal( + "Dispatcher task dropped response channel.", + )), + } +} + #[tonic::async_trait] impl ImageService for ImageApiHandler { type WatchImageStatusStream = @@ -33,20 +57,10 @@ impl ImageService for ImageApiHandler { request: Request, ) -> Result, Status> { info!("ImageApi: Received PullImage request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::PullImage(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::PullImage(request.into_inner(), resp_tx) + }) + .await } async fn watch_image_status( @@ -69,20 +83,10 @@ impl ImageService for ImageApiHandler { request: Request, ) -> Result, Status> { info!("ImageApi: Received ListImages request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::ListImages(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::ListImages(request.into_inner(), resp_tx) + }) + .await } async fn delete_image( @@ -90,19 +94,9 @@ impl ImageService for ImageApiHandler { request: Request, ) -> Result, Status> { info!("ImageApi: Received DeleteImage request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::DeleteImage(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - - match resp_rx.await { - Ok(Ok(_result)) => Ok(Response::new(DeleteImageResponse {})), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::DeleteImage(request.into_inner(), resp_tx) + }) + .await } } diff --git a/feos/services/image-service/src/error.rs b/feos/services/image-service/src/error.rs new file mode 100644 index 0000000..eead3fa --- /dev/null +++ b/feos/services/image-service/src/error.rs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +use oci_distribution::{errors::OciDistributionError, ParseError}; +use tonic::Status; + +#[derive(Debug, thiserror::Error)] +pub enum ImageServiceError { + #[error("Failed to pull OCI image")] + OciPull(#[from] OciDistributionError), + + #[error("Failed to parse OCI image reference")] + OciParse(#[from] ParseError), + + #[error("Required image layer '{0}' not found in manifest")] + MissingLayer(String), + + #[error("A file storage error occurred")] + Storage(#[from] std::io::Error), + + #[error("Image with ID '{0}' not found")] + NotFound(String), + + #[error("An internal orchestrator error occurred: {0}")] + Internal(String), +} + +impl From for Status { + fn from(err: ImageServiceError) -> Self { + log::error!("ImageServiceError: {}", err); + match err { + ImageServiceError::NotFound(id) => { + Status::not_found(format!("Image with ID '{}' not found", id)) + } + ImageServiceError::OciParse(_) => Status::invalid_argument(err.to_string()), + ImageServiceError::OciPull(_) | ImageServiceError::MissingLayer(_) => { + Status::unavailable(err.to_string()) + } + ImageServiceError::Storage(_) | ImageServiceError::Internal(_) => { + Status::internal(err.to_string()) + } + } + } +} diff --git a/feos/services/image-service/src/lib.rs b/feos/services/image-service/src/lib.rs index 8a562c1..15eeffb 100644 --- a/feos/services/image-service/src/lib.rs +++ b/feos/services/image-service/src/lib.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 +use crate::error::ImageServiceError; use feos_proto::image_service::{ DeleteImageRequest, DeleteImageResponse, ImageInfo, ImageState, ImageStatusResponse, ListImagesRequest, ListImagesResponse, PullImageRequest, PullImageResponse, @@ -11,6 +12,7 @@ use tokio::sync::{mpsc, oneshot}; use tonic::Status; pub mod api; pub mod dispatcher; +pub mod error; pub mod filestore; pub mod worker; @@ -21,13 +23,14 @@ pub const IMAGE_SERVICE_SOCKET: &str = "/tmp/feos/image_service.sock"; pub struct ImageStateEvent { pub image_uuid: String, pub state: ImageState, + pub message: String, } #[derive(Debug)] pub enum Command { PullImage( PullImageRequest, - oneshot::Sender>, + oneshot::Sender>, ), WatchImageStatus( WatchImageStatusRequest, @@ -35,11 +38,11 @@ pub enum Command { ), ListImages( ListImagesRequest, - oneshot::Sender>, + oneshot::Sender>, ), DeleteImage( DeleteImageRequest, - oneshot::Sender>, + oneshot::Sender>, ), } @@ -47,7 +50,7 @@ pub enum Command { pub enum OrchestratorCommand { PullImage { image_ref: String, - responder: oneshot::Sender>, + responder: oneshot::Sender>, }, FinalizePull { image_uuid: String, @@ -56,18 +59,18 @@ pub enum OrchestratorCommand { }, FailPull { image_uuid: String, - error: String, + error: ImageServiceError, }, WatchImageStatus { image_uuid: String, stream_sender: mpsc::Sender>, }, ListImages { - responder: oneshot::Sender>, + responder: oneshot::Sender>, }, DeleteImage { image_uuid: String, - responder: oneshot::Sender>, + responder: oneshot::Sender>, }, } diff --git a/feos/services/image-service/src/worker.rs b/feos/services/image-service/src/worker.rs index ad68dc7..deea4cf 100644 --- a/feos/services/image-service/src/worker.rs +++ b/feos/services/image-service/src/worker.rs @@ -1,15 +1,13 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 -use crate::{FileCommand, ImageStateEvent, OrchestratorCommand}; +use crate::{error::ImageServiceError, FileCommand, ImageStateEvent, OrchestratorCommand}; use feos_proto::image_service::{ DeleteImageResponse, ImageInfo, ImageState, ImageStatusResponse, ListImagesResponse, PullImageResponse, }; use log::{error, info, warn}; -use oci_distribution::{ - client::ClientConfig, errors::OciDistributionError, secrets, Client, ParseError, Reference, -}; +use oci_distribution::{client::ClientConfig, secrets, Client, Reference}; use std::collections::HashMap; use tokio::sync::{broadcast, mpsc, oneshot}; use tonic::Status; @@ -82,7 +80,11 @@ impl Orchestrator { state: ImageState::Downloading as i32, }, ); - self.broadcast_state_change(image_uuid.clone(), ImageState::Downloading); + self.broadcast_state_change( + image_uuid.clone(), + ImageState::Downloading, + "Pull initiated".to_string(), + ); let _ = responder.send(Ok(PullImageResponse { image_uuid: image_uuid.clone(), @@ -109,29 +111,49 @@ impl Orchestrator { }; if self.filestore_tx.send(file_cmd).await.is_err() { - error!("Orchestrator: Failed to send StoreImage command to FileStore."); - self.update_and_broadcast_state(image_uuid, ImageState::PullFailed); + let err_msg = "Failed to send StoreImage command to FileStore."; + error!("Orchestrator: {err_msg}"); + self.update_and_broadcast_state( + image_uuid, + ImageState::PullFailed, + err_msg.to_string(), + ); return; } match resp_rx.await { Ok(Ok(())) => { info!("Orchestrator: FileStore successfully stored image {image_uuid}"); - self.update_and_broadcast_state(image_uuid, ImageState::Ready); + self.update_and_broadcast_state( + image_uuid, + ImageState::Ready, + "Image is ready".to_string(), + ); } Ok(Err(e)) => { - error!("Orchestrator: FileStore failed to store image {image_uuid}: {e}"); - self.update_and_broadcast_state(image_uuid, ImageState::PullFailed); + let err_msg = format!("FileStore failed to store image: {e}"); + error!("Orchestrator: {err_msg} ({image_uuid})"); + self.update_and_broadcast_state( + image_uuid, + ImageState::PullFailed, + err_msg, + ); } Err(_) => { - error!("Orchestrator: FileStore actor dropped response channel for {image_uuid}"); - self.update_and_broadcast_state(image_uuid, ImageState::PullFailed); + let err_msg = "FileStore actor dropped response channel."; + error!("Orchestrator: {err_msg} ({image_uuid})"); + self.update_and_broadcast_state( + image_uuid, + ImageState::PullFailed, + err_msg.to_string(), + ); } } } OrchestratorCommand::FailPull { image_uuid, error } => { - error!("Orchestrator: Pull failed for {image_uuid}: {error}"); - self.update_and_broadcast_state(image_uuid, ImageState::PullFailed); + let err_msg = format!("Pull failed: {error}"); + error!("Orchestrator: {err_msg} ({image_uuid})"); + self.update_and_broadcast_state(image_uuid, ImageState::PullFailed, err_msg); } OrchestratorCommand::ListImages { responder } => { let images = self.store.values().cloned().collect(); @@ -158,7 +180,11 @@ impl Orchestrator { } } - self.broadcast_state_change(image_uuid, ImageState::NotFound); + self.broadcast_state_change( + image_uuid, + ImageState::NotFound, + "Image deleted".to_string(), + ); let _ = responder.send(Ok(DeleteImageResponse {})); } OrchestratorCommand::WatchImageStatus { @@ -181,37 +207,33 @@ impl Orchestrator { } } - fn update_and_broadcast_state(&mut self, image_uuid: String, new_state: ImageState) { + fn update_and_broadcast_state( + &mut self, + image_uuid: String, + new_state: ImageState, + message: String, + ) { if let Some(info) = self.store.get_mut(&image_uuid) { info.state = new_state as i32; } - self.broadcast_state_change(image_uuid, new_state); + self.broadcast_state_change(image_uuid, new_state, message); } - fn broadcast_state_change(&self, image_uuid: String, state: ImageState) { - let event = ImageStateEvent { image_uuid, state }; + fn broadcast_state_change(&self, image_uuid: String, state: ImageState, message: String) { + let event = ImageStateEvent { + image_uuid, + state, + message, + }; if self.broadcast_tx.send(event).is_err() { info!("Orchestrator: Broadcast failed, no active listeners."); } } } -#[derive(Debug)] -pub enum PullError { - Oci(OciDistributionError), - Parse(ParseError), - MissingLayer(String), -} - -impl From for String { - fn from(err: PullError) -> Self { - format!("{err:?}") - } -} - -async fn download_layer_data(image_ref: &str) -> Result, PullError> { +async fn download_layer_data(image_ref: &str) -> Result, ImageServiceError> { info!("ImagePuller: fetching image: {image_ref}"); - let reference = Reference::try_from(image_ref.to_string()).map_err(PullError::Parse)?; + let reference = Reference::try_from(image_ref.to_string())?; let accepted_media_types = vec![ ROOTFS_MEDIA_TYPE, SQUASHFS_MEDIA_TYPE, @@ -231,15 +253,14 @@ async fn download_layer_data(image_ref: &str) -> Result, PullError> { &secrets::RegistryAuth::Anonymous, accepted_media_types, ) - .await - .map_err(PullError::Oci)?; + .await?; info!("ImagePuller: image data pulled for {image_ref}"); let rootfs_layer = image_data .layers .into_iter() .find(|l| l.media_type == ROOTFS_MEDIA_TYPE) - .ok_or_else(|| PullError::MissingLayer(ROOTFS_MEDIA_TYPE.to_string()))?; + .ok_or_else(|| ImageServiceError::MissingLayer(ROOTFS_MEDIA_TYPE.to_string()))?; Ok(rootfs_layer.data) } @@ -263,7 +284,7 @@ pub async fn pull_oci_image( Err(e) => { let cmd = OrchestratorCommand::FailPull { image_uuid, - error: e.into(), + error: e, }; if command_tx.send(cmd).await.is_err() { error!("ImagePuller: Failed to send FailPull command. Actor may be down."); @@ -321,7 +342,7 @@ pub async fn watch_image_status_stream( } else { 0 }, - message: format!("New state: {:?}", event.state), + message: event.message, }; if stream_sender.send(Ok(response)).await.is_err() { diff --git a/feos/services/vm-service/Cargo.toml b/feos/services/vm-service/Cargo.toml index 53837ca..da761be 100644 --- a/feos/services/vm-service/Cargo.toml +++ b/feos/services/vm-service/Cargo.toml @@ -11,7 +11,7 @@ cloud-hypervisor-client = { version = "0.3.3"} once_cell = "1.19" hyperlocal = "0.9.1" hyper-util = { version = "0.1.14" } -thiserror = "1.0" +thiserror = "2.0.16" urlencoding = "2.1.3" dotenvy = "0.15" sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "macros", "uuid"] } diff --git a/feos/services/vm-service/src/api.rs b/feos/services/vm-service/src/api.rs index 3343958..73a41cd 100644 --- a/feos/services/vm-service/src/api.rs +++ b/feos/services/vm-service/src/api.rs @@ -26,6 +26,30 @@ impl VmApiHandler { } } +async fn dispatch_and_wait( + dispatcher: &mpsc::Sender, + command_constructor: impl FnOnce(oneshot::Sender>) -> Command, +) -> Result, Status> +where + E: Into, +{ + let (resp_tx, resp_rx) = oneshot::channel(); + let cmd = command_constructor(resp_tx); + + dispatcher + .send(cmd) + .await + .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; + + match resp_rx.await { + Ok(Ok(result)) => Ok(Response::new(result)), + Ok(Err(e)) => Err(e.into()), + Err(_) => Err(Status::internal( + "Dispatcher task dropped response channel.", + )), + } +} + #[tonic::async_trait] impl VmService for VmApiHandler { type StreamVmEventsStream = Pin> + Send>>; @@ -37,19 +61,10 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received CreateVm request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::CreateVm(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::CreateVm(request.into_inner(), resp_tx) + }) + .await } async fn start_vm( @@ -57,36 +72,18 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received StartVm request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::StartVm(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::StartVm(request.into_inner(), resp_tx) + }) + .await } async fn get_vm(&self, request: Request) -> Result, Status> { info!("VmApi: Received GetVm request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::GetVm(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::GetVm(request.into_inner(), resp_tx) + }) + .await } async fn stream_vm_events( @@ -109,19 +106,10 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received DeleteVm request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::DeleteVm(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::DeleteVm(request.into_inner(), resp_tx) + }) + .await } async fn stream_vm_console( @@ -145,19 +133,10 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received ListVms request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::ListVms(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::ListVms(request.into_inner(), resp_tx) + }) + .await } async fn ping_vm( @@ -165,19 +144,10 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received PingVm request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::PingVm(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::PingVm(request.into_inner(), resp_tx) + }) + .await } async fn shutdown_vm( @@ -185,19 +155,10 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received ShutdownVm request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::ShutdownVm(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::ShutdownVm(request.into_inner(), resp_tx) + }) + .await } async fn pause_vm( @@ -205,19 +166,10 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received PauseVm request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::PauseVm(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::PauseVm(request.into_inner(), resp_tx) + }) + .await } async fn resume_vm( @@ -225,19 +177,10 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received ResumeVm request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::ResumeVm(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::ResumeVm(request.into_inner(), resp_tx) + }) + .await } async fn attach_disk( @@ -245,19 +188,10 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received AttachDisk request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::AttachDisk(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::AttachDisk(request.into_inner(), resp_tx) + }) + .await } async fn remove_disk( @@ -265,18 +199,9 @@ impl VmService for VmApiHandler { request: Request, ) -> Result, Status> { info!("VmApi: Received RemoveDisk request."); - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::RemoveDisk(request.into_inner(), resp_tx); - self.dispatcher_tx - .send(cmd) - .await - .map_err(|e| Status::internal(format!("Failed to send command to dispatcher: {e}")))?; - match resp_rx.await { - Ok(Ok(result)) => Ok(Response::new(result)), - Ok(Err(status)) => Err(status), - Err(_) => Err(Status::internal( - "Dispatcher task dropped response channel.", - )), - } + dispatch_and_wait(&self.dispatcher_tx, |resp_tx| { + Command::RemoveDisk(request.into_inner(), resp_tx) + }) + .await } } diff --git a/feos/services/vm-service/src/dispatcher.rs b/feos/services/vm-service/src/dispatcher.rs index dd7d52f..19d3057 100644 --- a/feos/services/vm-service/src/dispatcher.rs +++ b/feos/services/vm-service/src/dispatcher.rs @@ -8,9 +8,8 @@ use crate::{ }, persistence::repository::VmRepository, vmm::{factory, Hypervisor, VmmType}, - worker, Command, VmEventWrapper, + worker, Command, VmEventWrapper, VmServiceError, }; -use anyhow::Result; use feos_proto::vm_service::{VmState, VmStateChangedEvent}; use log::{debug, error, info}; use prost::Message; @@ -29,7 +28,7 @@ pub struct VmServiceDispatcher { } impl VmServiceDispatcher { - pub async fn new(rx: mpsc::Receiver, db_url: &str) -> Result { + pub async fn new(rx: mpsc::Receiver, db_url: &str) -> Result { let (event_bus_tx, event_bus_rx_for_dispatcher) = mpsc::channel(32); let (status_channel_tx, _) = broadcast::channel(32); let (healthcheck_cancel_bus, _) = broadcast::channel::(32); diff --git a/feos/services/vm-service/src/dispatcher_handlers.rs b/feos/services/vm-service/src/dispatcher_handlers.rs index 0587903..f46c0c8 100644 --- a/feos/services/vm-service/src/dispatcher_handlers.rs +++ b/feos/services/vm-service/src/dispatcher_handlers.rs @@ -4,7 +4,7 @@ use crate::{ persistence::{repository::VmRepository, VmRecord, VmStatus}, vmm::Hypervisor, - worker, VmEventWrapper, + worker, VmEventWrapper, VmServiceError, }; use feos_proto::{ image_service::{image_service_client::ImageServiceClient, PullImageRequest}, @@ -46,12 +46,12 @@ pub(crate) async fn get_image_service_client( .map(ImageServiceClient::new) } -async fn initiate_image_pull_for_vm(req: &CreateVmRequest) -> Result { +async fn initiate_image_pull_for_vm(req: &CreateVmRequest) -> Result { let image_ref = match req.config.as_ref() { Some(config) if !config.image_ref.is_empty() => config.image_ref.clone(), _ => { - return Err(Status::invalid_argument( - "VmConfig with a non-empty image_ref is required", + return Err(VmServiceError::InvalidArgument( + "VmConfig with a non-empty image_ref is required".to_string(), )); } }; @@ -59,7 +59,7 @@ async fn initiate_image_pull_for_vm(req: &CreateVmRequest) -> Result Result Result>, - hypervisor: Arc, - event_bus_tx: mpsc::Sender, -) { - let vm_id_res: Result<(Uuid, bool), Status> = + req: &CreateVmRequest, +) -> Result<(Uuid, String), VmServiceError> { + let vm_id_res: Result<(Uuid, bool), VmServiceError> = if let Some(id_str) = req.vm_id.as_deref().filter(|s| !s.is_empty()) { match Uuid::parse_str(id_str) { Ok(id) if !id.is_nil() => Ok((id, true)), - Ok(_) => Err(Status::invalid_argument( - "Provided vm_id cannot be the nil UUID.", + Ok(_) => Err(VmServiceError::InvalidArgument( + "Provided vm_id cannot be the nil UUID.".to_string(), )), - Err(_) => Err(Status::invalid_argument( - "Provided vm_id is not a valid UUID format.", + Err(_) => Err(VmServiceError::InvalidArgument( + "Provided vm_id is not a valid UUID format.".to_string(), )), } } else { Ok((Uuid::new_v4(), false)) }; - let (vm_id, is_user_provided) = match vm_id_res { - Ok(val) => val, - Err(status) => { - if responder.send(Err(status)).is_err() { - error!( - "VmDispatcher: Failed to send error response for CreateVm. Responder closed." - ); - } - return; - } + let (vm_id, is_user_provided) = vm_id_res?; + + if is_user_provided && repository.get_vm(vm_id).await?.is_some() { + return Err(VmServiceError::AlreadyExists(format!( + "VM with ID {vm_id} already exists." + ))); + } + + let image_uuid_str = initiate_image_pull_for_vm(req).await?; + let image_uuid = Uuid::parse_str(&image_uuid_str) + .map_err(|e| VmServiceError::ImageService(format!("Failed to parse image UUID: {e}")))?; + + let record = VmRecord { + vm_id, + image_uuid, + status: VmStatus { + state: VmState::Creating, + last_msg: "VM creation initiated".to_string(), + process_id: None, + }, + config: req.config.clone().unwrap(), }; - if is_user_provided { - match repository.get_vm(vm_id).await { - Ok(Some(_)) => { - let status = Status::already_exists(format!("VM with ID {vm_id} already exists.")); - if responder.send(Err(status)).is_err() { - error!("VmDispatcher: Failed to send error response for CreateVm. Responder closed."); - } - return; - } - Ok(None) => {} - Err(e) => { - let status = Status::internal(format!("Failed to check DB for existing VM: {e}")); - if responder.send(Err(status)).is_err() { - error!("VmDispatcher: Failed to send error response for CreateVm. Responder closed."); - } - return; - } - } + repository.save_vm(&record).await?; + info!("VmDispatcher: Saved initial record for VM {vm_id}"); + Ok((vm_id, image_uuid_str)) +} + +async fn get_vm_info( + repository: &VmRepository, + req: &GetVmRequest, +) -> Result { + let vm_id = Uuid::parse_str(&req.vm_id) + .map_err(|_| VmServiceError::InvalidArgument("Invalid VM ID format.".to_string()))?; + + match repository.get_vm(vm_id).await? { + Some(record) => Ok(VmInfo { + vm_id: record.vm_id.to_string(), + state: record.status.state as i32, + config: Some(record.config), + }), + None => Err(VmServiceError::Vmm(crate::vmm::VmmError::VmNotFound( + vm_id.to_string(), + ))), } +} - match initiate_image_pull_for_vm(&req).await { - Ok(image_uuid_str) => { - let image_uuid = match Uuid::parse_str(&image_uuid_str) { - Ok(uuid) => uuid, - Err(e) => { - let status = Status::internal(format!("Failed to parse image UUID: {e}")); - if responder.send(Err(status)).is_err() { - error!("VmDispatcher: Failed to send error response for CreateVm. Responder closed."); - } - return; - } - }; - - let record = VmRecord { - vm_id, - image_uuid, - status: VmStatus { - state: VmState::Creating, - last_msg: "VM creation initiated".to_string(), - process_id: None, - }, - config: req.config.clone().unwrap(), - }; - - if let Err(e) = repository.save_vm(&record).await { - let status = Status::internal(format!("Failed to save VM to database: {e}")); - error!("VmDispatcher: {message}", message = status.message()); - if responder.send(Err(status)).is_err() { - error!("VmDispatcher: Failed to send error response for CreateVm. Responder closed."); - } - return; - } - info!("VmDispatcher: Saved initial record for VM {vm_id}"); +pub(crate) async fn handle_create_vm_command( + repository: &VmRepository, + req: CreateVmRequest, + responder: oneshot::Sender>, + hypervisor: Arc, + event_bus_tx: mpsc::Sender, +) { + let result = prepare_vm_creation(repository, &req).await; + match result { + Ok((vm_id, image_uuid_str)) => { tokio::spawn(worker::handle_create_vm( vm_id.to_string(), req, @@ -174,8 +161,9 @@ pub(crate) async fn handle_create_vm_command( event_bus_tx, )); } - Err(status) => { - if responder.send(Err(status)).is_err() { + Err(e) => { + error!("VmDispatcher: Failed to handle CreateVm command: {}", e); + if responder.send(Err(e)).is_err() { error!( "VmDispatcher: Failed to send error response for CreateVm. Responder closed." ); @@ -187,34 +175,12 @@ pub(crate) async fn handle_create_vm_command( pub(crate) async fn handle_get_vm_command( repository: &VmRepository, req: GetVmRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, ) { - let vm_id = match Uuid::parse_str(&req.vm_id) { - Ok(id) => id, - Err(_) => { - let _ = responder.send(Err(Status::invalid_argument("Invalid VM ID format."))); - return; - } - }; + let result = get_vm_info(repository, &req).await; - match repository.get_vm(vm_id).await { - Ok(Some(record)) => { - let vm_info = VmInfo { - vm_id: record.vm_id.to_string(), - state: record.status.state as i32, - config: Some(record.config), - }; - let _ = responder.send(Ok(vm_info)); - } - Ok(None) => { - let _ = responder.send(Err(Status::not_found(format!( - "VM with ID {vm_id} not found" - )))); - } - Err(e) => { - error!("Failed to get VM from database: {e}"); - let _ = responder.send(Err(Status::internal("Failed to retrieve VM information."))); - } + if responder.send(result).is_err() { + error!("VmDispatcher: Failed to send response for GetVm."); } } @@ -228,14 +194,9 @@ pub(crate) async fn handle_stream_vm_events_command( let vm_id = match Uuid::parse_str(&vm_id_str) { Ok(id) => id, Err(_) => { - if stream_tx - .send(Err(Status::invalid_argument("Invalid VM ID format."))) - .await - .is_err() - { - warn!( - "StreamEvents: Client for {vm_id_str} disconnected before error could be sent." - ); + let status = Status::invalid_argument("Invalid VM ID format."); + if stream_tx.send(Err(status)).await.is_err() { + warn!("StreamEvents: Client for {vm_id_str} disconnected before error could be sent."); } return; } @@ -290,9 +251,7 @@ pub(crate) async fn handle_stream_vm_events_command( } } Err(e) => { - error!( - "StreamEvents: Failed to get VM {vm_id_str} from database for event stream: {e}" - ); + error!("StreamEvents: Failed to get VM {vm_id_str} from database for event stream: {e}"); if stream_tx .send(Err(Status::internal( "Failed to retrieve VM information for event stream.", @@ -300,9 +259,7 @@ pub(crate) async fn handle_stream_vm_events_command( .await .is_err() { - warn!( - "StreamEvents: Client for {vm_id_str} disconnected before internal-error could be sent." - ); + warn!("StreamEvents: Client for {vm_id_str} disconnected before internal-error could be sent."); } } } @@ -364,14 +321,16 @@ pub(crate) async fn handle_delete_vm_command( repository: &VmRepository, healthcheck_cancel_bus: &broadcast::Sender, req: DeleteVmRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, event_bus_tx: mpsc::Sender, ) { let vm_id = match Uuid::parse_str(&req.vm_id) { Ok(id) => id, Err(_) => { - let _ = responder.send(Err(Status::invalid_argument("Invalid VM ID format."))); + let _ = responder.send(Err(VmServiceError::InvalidArgument( + "Invalid VM ID format.".to_string(), + ))); return; } }; @@ -383,7 +342,7 @@ pub(crate) async fn handle_delete_vm_command( if let Err(e) = repository.delete_vm(vm_id).await { error!("Failed to delete VM {vm_id} from database: {e}"); - let _ = responder.send(Err(Status::internal("Failed to delete VM from database."))); + let _ = responder.send(Err(e.into())); return; } info!("VmDispatcher: Deleted record for VM {vm_id} from database."); @@ -420,7 +379,7 @@ pub(crate) async fn handle_delete_vm_command( } Err(e) => { error!("Failed to get VM {vm_id} from database: {e}"); - let _ = responder.send(Err(Status::internal("Failed to retrieve VM for deletion."))); + let _ = responder.send(Err(e.into())); } } } @@ -428,31 +387,22 @@ pub(crate) async fn handle_delete_vm_command( pub(crate) async fn handle_list_vms_command( repository: &VmRepository, _req: ListVmsRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, ) { - match repository.list_all_vms().await { - Ok(records) => { - let vms = records - .into_iter() - .map(|record| VmInfo { - vm_id: record.vm_id.to_string(), - state: record.status.state as i32, - config: Some(record.config), - }) - .collect(); - - let response = ListVmsResponse { vms }; - if responder.send(Ok(response)).is_err() { - error!("VmDispatcher: Failed to send response for ListVms."); - } - } - Err(e) => { - error!("VmDispatcher: Failed to list VMs from database: {e}"); - let status = Status::internal("Failed to retrieve VM list."); - if responder.send(Err(status)).is_err() { - error!("VmDispatcher: Failed to send error response for ListVms."); - } - } + let result = repository.list_all_vms().await.map(|records| { + let vms = records + .into_iter() + .map(|record| VmInfo { + vm_id: record.vm_id.to_string(), + state: record.status.state as i32, + config: Some(record.config), + }) + .collect(); + ListVmsResponse { vms } + }); + + if responder.send(result.map_err(Into::into)).is_err() { + error!("VmDispatcher: Failed to send response for ListVms."); } } diff --git a/feos/services/vm-service/src/lib.rs b/feos/services/vm-service/src/lib.rs index 82601af..7c3fa82 100644 --- a/feos/services/vm-service/src/lib.rs +++ b/feos/services/vm-service/src/lib.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 +use crate::persistence::PersistenceError; use feos_proto::vm_service::{ AttachDiskRequest, AttachDiskResponse, CreateVmRequest, CreateVmResponse, DeleteVmRequest, DeleteVmResponse, GetVmRequest, ListVmsRequest, ListVmsResponse, PauseVmRequest, @@ -25,6 +26,44 @@ pub const VM_CH_BIN: &str = "cloud-hypervisor"; pub const IMAGE_DIR: &str = "/tmp/feos/images"; pub const VM_CONSOLE_DIR: &str = "/tmp/feos/consoles"; +#[derive(Debug, thiserror::Error)] +pub enum VmServiceError { + #[error("VMM Error: {0}")] + Vmm(#[from] crate::vmm::VmmError), + + #[error("Persistence Error: {0}")] + Persistence(#[from] PersistenceError), + + #[error("Image Service Error: {0}")] + ImageService(String), + + #[error("Invalid argument: {0}")] + InvalidArgument(String), + + #[error("VM with ID {0} already exists")] + AlreadyExists(String), +} + +impl From for Status { + fn from(err: VmServiceError) -> Self { + log::error!("VmServiceError: {}", err); + match err { + VmServiceError::Vmm(vmm_err) => vmm_err.into(), + VmServiceError::Persistence(PersistenceError::Database(ref e)) + if matches!(e, sqlx::Error::RowNotFound) => + { + Status::not_found("Record not found in database") + } + VmServiceError::Persistence(_) => Status::internal("A database error occurred"), + VmServiceError::ImageService(msg) => { + Status::unavailable(format!("Image service unavailable: {}", msg)) + } + VmServiceError::InvalidArgument(msg) => Status::invalid_argument(msg), + VmServiceError::AlreadyExists(msg) => Status::already_exists(msg), + } + } +} + #[derive(Debug, Clone)] pub struct VmEventWrapper { pub event: VmEvent, @@ -34,17 +73,20 @@ pub struct VmEventWrapper { pub enum Command { CreateVm( CreateVmRequest, - oneshot::Sender>, + oneshot::Sender>, ), StartVm( StartVmRequest, - oneshot::Sender>, + oneshot::Sender>, + ), + GetVm( + GetVmRequest, + oneshot::Sender>, ), - GetVm(GetVmRequest, oneshot::Sender>), StreamVmEvents(StreamVmEventsRequest, mpsc::Sender>), DeleteVm( DeleteVmRequest, - oneshot::Sender>, + oneshot::Sender>, ), StreamVmConsole( Box>, @@ -52,31 +94,31 @@ pub enum Command { ), ListVms( ListVmsRequest, - oneshot::Sender>, + oneshot::Sender>, ), PingVm( PingVmRequest, - oneshot::Sender>, + oneshot::Sender>, ), ShutdownVm( ShutdownVmRequest, - oneshot::Sender>, + oneshot::Sender>, ), PauseVm( PauseVmRequest, - oneshot::Sender>, + oneshot::Sender>, ), ResumeVm( ResumeVmRequest, - oneshot::Sender>, + oneshot::Sender>, ), AttachDisk( AttachDiskRequest, - oneshot::Sender>, + oneshot::Sender>, ), RemoveDisk( RemoveDiskRequest, - oneshot::Sender>, + oneshot::Sender>, ), } diff --git a/feos/services/vm-service/src/persistence/mod.rs b/feos/services/vm-service/src/persistence/mod.rs index 91d5087..c386192 100644 --- a/feos/services/vm-service/src/persistence/mod.rs +++ b/feos/services/vm-service/src/persistence/mod.rs @@ -6,6 +6,24 @@ use uuid::Uuid; pub mod repository; +#[derive(Debug, thiserror::Error)] +pub enum PersistenceError { + #[error("A database error occurred")] + Database(#[from] sqlx::Error), + + #[error("Database migration failed")] + Migration(#[from] sqlx::migrate::MigrateError), + + #[error("Failed to decode VmConfig blob")] + Decode(#[from] prost::DecodeError), + + #[error("Failed to encode VmConfig blob")] + Encode(#[from] prost::EncodeError), + + #[error("Invalid state string '{0}' in database")] + InvalidStateString(String), +} + #[derive(Debug, Clone)] pub struct VmStatus { pub state: VmState, diff --git a/feos/services/vm-service/src/persistence/repository.rs b/feos/services/vm-service/src/persistence/repository.rs index e4a53fa..dba10cf 100644 --- a/feos/services/vm-service/src/persistence/repository.rs +++ b/feos/services/vm-service/src/persistence/repository.rs @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 -use crate::persistence::{VmRecord, VmStatus}; -use anyhow::{anyhow, Result}; +use crate::persistence::{PersistenceError, VmRecord, VmStatus}; use feos_proto::vm_service::{VmConfig, VmState}; use log::info; use prost::Message; @@ -24,7 +23,7 @@ struct DbVmRow { config_blob: Vec, } -fn string_to_vm_state(s: &str) -> Result { +fn string_to_vm_state(s: &str) -> Result { match s { "VM_STATE_CREATING" => Ok(VmState::Creating), "VM_STATE_CREATED" => Ok(VmState::Created), @@ -33,12 +32,12 @@ fn string_to_vm_state(s: &str) -> Result { "VM_STATE_STOPPED" => Ok(VmState::Stopped), "VM_STATE_CRASHED" => Ok(VmState::Crashed), "VM_STATE_UNSPECIFIED" => Ok(VmState::Unspecified), - _ => Err(anyhow!("Invalid state string '{s}' in database")), + _ => Err(PersistenceError::InvalidStateString(s.to_string())), } } impl VmRepository { - pub async fn connect(db_url: &str) -> Result { + pub async fn connect(db_url: &str) -> Result { let pool = SqlitePoolOptions::new() .max_connections(1) .connect(db_url) @@ -51,7 +50,7 @@ impl VmRepository { Ok(Self { pool }) } - pub async fn get_vm(&self, vm_id: Uuid) -> Result> { + pub async fn get_vm(&self, vm_id: Uuid) -> Result, PersistenceError> { let row_opt = sqlx::query_as::<_, DbVmRow>( "SELECT vm_id, image_uuid, state, last_msg, pid, config_blob FROM vms WHERE vm_id = ?1", ) @@ -60,9 +59,7 @@ impl VmRepository { .await?; if let Some(row) = row_opt { - let config = VmConfig::decode(&*row.config_blob) - .map_err(|e| anyhow!("Failed to decode VmConfig blob: {e}"))?; - + let config = VmConfig::decode(&*row.config_blob)?; let state = string_to_vm_state(&row.state)?; let record = VmRecord { @@ -81,7 +78,7 @@ impl VmRepository { } } - pub async fn list_all_vms(&self) -> Result> { + pub async fn list_all_vms(&self) -> Result, PersistenceError> { let rows = sqlx::query_as::<_, DbVmRow>( "SELECT vm_id, image_uuid, state, last_msg, pid, config_blob FROM vms", ) @@ -90,14 +87,7 @@ impl VmRepository { let mut records = Vec::with_capacity(rows.len()); for row in rows { - let config = VmConfig::decode(&*row.config_blob).map_err(|e| { - anyhow!( - "Failed to decode VmConfig blob for vm_id {}: {}", - row.vm_id, - e - ) - })?; - + let config = VmConfig::decode(&*row.config_blob)?; let state = string_to_vm_state(&row.state)?; let record = VmRecord { @@ -116,7 +106,7 @@ impl VmRepository { Ok(records) } - pub async fn save_vm(&self, vm: &VmRecord) -> Result<()> { + pub async fn save_vm(&self, vm: &VmRecord) -> Result<(), PersistenceError> { let mut config_blob = Vec::new(); vm.config.encode(&mut config_blob)?; @@ -145,7 +135,7 @@ impl VmRepository { vm_id: Uuid, new_state: VmState, message: &str, - ) -> Result { + ) -> Result { let state_str = format!("VM_STATE_{new_state:?}").to_uppercase(); let result = sqlx::query!( @@ -164,14 +154,14 @@ impl VmRepository { Ok(result.rows_affected() > 0) } - pub async fn update_vm_pid(&self, vm_id: Uuid, pid: i64) -> Result<()> { + pub async fn update_vm_pid(&self, vm_id: Uuid, pid: i64) -> Result<(), PersistenceError> { sqlx::query!("UPDATE vms SET pid = ?1 WHERE vm_id = ?2", pid, vm_id) .execute(&self.pool) .await?; Ok(()) } - pub async fn delete_vm(&self, vm_id: Uuid) -> Result<()> { + pub async fn delete_vm(&self, vm_id: Uuid) -> Result<(), PersistenceError> { let result = sqlx::query!("DELETE FROM vms WHERE vm_id = ?1", vm_id) .execute(&self.pool) .await?; diff --git a/feos/services/vm-service/src/worker.rs b/feos/services/vm-service/src/worker.rs index dd883a7..a7b1c9c 100644 --- a/feos/services/vm-service/src/worker.rs +++ b/feos/services/vm-service/src/worker.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - dispatcher_handlers::get_image_service_client, vmm::Hypervisor, vmm::VmmError, VmEventWrapper, + dispatcher_handlers::get_image_service_client, vmm::Hypervisor, VmEventWrapper, VmServiceError, }; use feos_proto::{ image_service::{ImageState as OciImageState, WatchImageStatusRequest}, @@ -27,10 +27,10 @@ use tokio_stream::StreamExt; use tonic::{Status, Streaming}; use uuid::Uuid; -async fn wait_for_image_ready(image_uuid: &str, image_ref: &str) -> Result<(), VmmError> { - let mut client = get_image_service_client().await.map_err(|e| { - VmmError::ImageServiceFailed(format!("Failed to connect to ImageService: {e}")) - })?; +async fn wait_for_image_ready(image_uuid: &str, image_ref: &str) -> Result<(), VmServiceError> { + let mut client = get_image_service_client() + .await + .map_err(|e| VmServiceError::ImageService(format!("Failed to connect: {e}")))?; let mut stream = client .watch_image_status(WatchImageStatusRequest { @@ -38,7 +38,7 @@ async fn wait_for_image_ready(image_uuid: &str, image_ref: &str) -> Result<(), V }) .await .map_err(|e| { - VmmError::ImageServiceFailed(format!( + VmServiceError::ImageService(format!( "WatchImageStatus RPC failed for {image_uuid}: {e}" )) })? @@ -46,13 +46,13 @@ async fn wait_for_image_ready(image_uuid: &str, image_ref: &str) -> Result<(), V while let Some(status_res) = stream.next().await { let status = status_res.map_err(|e| { - VmmError::ImageServiceFailed(format!("Image stream error for {image_uuid}: {e}")) + VmServiceError::ImageService(format!("Image stream error for {image_uuid}: {e}")) })?; let state = OciImageState::try_from(status.state).unwrap_or(OciImageState::Unspecified); match state { OciImageState::Ready => return Ok(()), OciImageState::PullFailed => { - return Err(VmmError::ImageServiceFailed(format!( + return Err(VmServiceError::ImageService(format!( "Image pull failed for {image_ref} (uuid: {image_uuid}): {}", status.message ))) @@ -60,7 +60,7 @@ async fn wait_for_image_ready(image_uuid: &str, image_ref: &str) -> Result<(), V _ => continue, } } - Err(VmmError::ImageServiceFailed(format!( + Err(VmServiceError::ImageService(format!( "Image watch stream for {image_uuid} ended before reaching a terminal state." ))) } @@ -69,7 +69,7 @@ pub async fn handle_create_vm( vm_id: String, req: CreateVmRequest, image_uuid: String, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, broadcast_tx: mpsc::Sender, ) { @@ -175,7 +175,7 @@ pub fn start_healthcheck_monitor( pub async fn handle_start_vm( req: StartVmRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, broadcast_tx: mpsc::Sender, cancel_bus: broadcast::Receiver, @@ -206,7 +206,7 @@ pub async fn handle_start_vm( pub async fn handle_get_vm( req: GetVmRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, ) { let result = hypervisor.get_vm(req).await; @@ -256,7 +256,7 @@ pub async fn handle_delete_vm( req: DeleteVmRequest, image_uuid: String, process_id: Option, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, _broadcast_tx: mpsc::Sender, ) { @@ -320,7 +320,7 @@ pub async fn handle_stream_vm_console( pub async fn handle_ping_vm( req: PingVmRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, ) { let result = hypervisor.ping_vm(req).await; @@ -331,7 +331,7 @@ pub async fn handle_ping_vm( pub async fn handle_shutdown_vm( req: ShutdownVmRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, broadcast_tx: mpsc::Sender, ) { @@ -359,7 +359,7 @@ pub async fn handle_shutdown_vm( pub async fn handle_pause_vm( req: PauseVmRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, broadcast_tx: mpsc::Sender, ) { @@ -387,7 +387,7 @@ pub async fn handle_pause_vm( pub async fn handle_resume_vm( req: ResumeVmRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, broadcast_tx: mpsc::Sender, ) { @@ -415,7 +415,7 @@ pub async fn handle_resume_vm( pub async fn handle_attach_disk( req: AttachDiskRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, ) { let result = hypervisor.attach_disk(req).await; @@ -426,7 +426,7 @@ pub async fn handle_attach_disk( pub async fn handle_remove_disk( req: RemoveDiskRequest, - responder: oneshot::Sender>, + responder: oneshot::Sender>, hypervisor: Arc, ) { let result = hypervisor.remove_disk(req).await; From 8db2fd95b0474c797baf50272657f7b3244d7528 Mon Sep 17 00:00:00 2001 From: Guvenc Gulce Date: Fri, 26 Sep 2025 13:49:23 +0200 Subject: [PATCH 2/7] thiserror crate should respect the cargo hierarachy Signed-off-by: Guvenc Gulce --- Cargo.toml | 1 + feos/services/host-service/Cargo.toml | 17 ++++++++--------- feos/services/image-service/Cargo.toml | 6 ++---- feos/services/vm-service/Cargo.toml | 4 ++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d22d711..c54788b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,5 +45,6 @@ dhcproto = "0.13.0" socket2 = "0.6.0" futures = "0.3.31" chrono = "0.4.42" +thiserror = "2.0.16" hyper-util = { version = "0.1.14", features = ["tokio"] } feos-proto = { path = "feos/proto" } \ No newline at end of file diff --git a/feos/services/host-service/Cargo.toml b/feos/services/host-service/Cargo.toml index a694349..b986f8d 100644 --- a/feos/services/host-service/Cargo.toml +++ b/feos/services/host-service/Cargo.toml @@ -10,14 +10,6 @@ tempfile = { workspace = true } sha2 = { workspace = true } hex = { workspace = true } digest = { workspace = true } -hyper = "1.4.0" -hyper-util = { version = "0.1.3", features = ["full"] } -hyper-rustls = "0.27.2" -http-body-util = "0.1.2" -rustls-pki-types = "1.0" -thiserror = "2.0.16" - -# Workspace dependencies tokio = { workspace = true } tokio-stream = { workspace = true } tonic = { workspace = true } @@ -25,4 +17,11 @@ anyhow = { workspace = true } nix = { workspace = true , features = ["hostname", "reboot"] } log = { workspace = true } prost = { workspace = true } -prost-types = { workspace = true } \ No newline at end of file +prost-types = { workspace = true } +thiserror = { workspace = true } + +hyper = "1.4.0" +hyper-util = { version = "0.1.3", features = ["full"] } +hyper-rustls = "0.27.2" +http-body-util = "0.1.2" +rustls-pki-types = "1.0" \ No newline at end of file diff --git a/feos/services/image-service/Cargo.toml b/feos/services/image-service/Cargo.toml index e472452..e05d108 100644 --- a/feos/services/image-service/Cargo.toml +++ b/feos/services/image-service/Cargo.toml @@ -7,9 +7,6 @@ edition.workspace = true feos-proto = { workspace = true } oci-distribution = { workspace = true } tempfile = { workspace = true } -thiserror = "2.0.16" - -# Workspace dependencies tokio = { workspace = true } tokio-stream = { workspace = true } tonic = { workspace = true } @@ -19,4 +16,5 @@ log = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } prost = { workspace = true } -prost-types = { workspace = true } \ No newline at end of file +prost-types = { workspace = true } +thiserror = { workspace = true } \ No newline at end of file diff --git a/feos/services/vm-service/Cargo.toml b/feos/services/vm-service/Cargo.toml index da761be..c91fc45 100644 --- a/feos/services/vm-service/Cargo.toml +++ b/feos/services/vm-service/Cargo.toml @@ -11,7 +11,6 @@ cloud-hypervisor-client = { version = "0.3.3"} once_cell = "1.19" hyperlocal = "0.9.1" hyper-util = { version = "0.1.14" } -thiserror = "2.0.16" urlencoding = "2.1.3" dotenvy = "0.15" sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "macros", "uuid"] } @@ -29,4 +28,5 @@ serde = { workspace = true } serde_json = { workspace = true } uuid = { workspace = true } log = { workspace = true } -tower = { workspace = true } \ No newline at end of file +tower = { workspace = true } +thiserror = { workspace = true } \ No newline at end of file From 4db6c7464c47dffff45bf733573a96048564dbca Mon Sep 17 00:00:00 2001 From: Guvenc Gulce Date: Fri, 26 Sep 2025 14:27:20 +0200 Subject: [PATCH 3/7] Use also error.rs with vm-service similar to the rest Signed-off-by: Guvenc Gulce --- feos/services/vm-service/src/dispatcher.rs | 3 +- .../vm-service/src/dispatcher_handlers.rs | 3 +- feos/services/vm-service/src/error.rs | 43 +++++++++++++++++++ feos/services/vm-service/src/lib.rs | 41 +----------------- feos/services/vm-service/src/worker.rs | 3 +- 5 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 feos/services/vm-service/src/error.rs diff --git a/feos/services/vm-service/src/dispatcher.rs b/feos/services/vm-service/src/dispatcher.rs index 19d3057..b2c690d 100644 --- a/feos/services/vm-service/src/dispatcher.rs +++ b/feos/services/vm-service/src/dispatcher.rs @@ -6,9 +6,10 @@ use crate::{ handle_create_vm_command, handle_delete_vm_command, handle_get_vm_command, handle_list_vms_command, handle_stream_vm_events_command, perform_startup_sanity_check, }, + error::VmServiceError, persistence::repository::VmRepository, vmm::{factory, Hypervisor, VmmType}, - worker, Command, VmEventWrapper, VmServiceError, + worker, Command, VmEventWrapper, }; use feos_proto::vm_service::{VmState, VmStateChangedEvent}; use log::{debug, error, info}; diff --git a/feos/services/vm-service/src/dispatcher_handlers.rs b/feos/services/vm-service/src/dispatcher_handlers.rs index f46c0c8..ec5071c 100644 --- a/feos/services/vm-service/src/dispatcher_handlers.rs +++ b/feos/services/vm-service/src/dispatcher_handlers.rs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ + error::VmServiceError, persistence::{repository::VmRepository, VmRecord, VmStatus}, vmm::Hypervisor, - worker, VmEventWrapper, VmServiceError, + worker, VmEventWrapper, }; use feos_proto::{ image_service::{image_service_client::ImageServiceClient, PullImageRequest}, diff --git a/feos/services/vm-service/src/error.rs b/feos/services/vm-service/src/error.rs new file mode 100644 index 0000000..7072f8f --- /dev/null +++ b/feos/services/vm-service/src/error.rs @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::persistence::PersistenceError; +use tonic::Status; + +#[derive(Debug, thiserror::Error)] +pub enum VmServiceError { + #[error("VMM Error: {0}")] + Vmm(#[from] crate::vmm::VmmError), + + #[error("Persistence Error: {0}")] + Persistence(#[from] PersistenceError), + + #[error("Image Service Error: {0}")] + ImageService(String), + + #[error("Invalid argument: {0}")] + InvalidArgument(String), + + #[error("VM with ID {0} already exists")] + AlreadyExists(String), +} + +impl From for Status { + fn from(err: VmServiceError) -> Self { + log::error!("VmServiceError: {}", err); + match err { + VmServiceError::Vmm(vmm_err) => vmm_err.into(), + VmServiceError::Persistence(PersistenceError::Database(ref e)) + if matches!(e, sqlx::Error::RowNotFound) => + { + Status::not_found("Record not found in database") + } + VmServiceError::Persistence(_) => Status::internal("A database error occurred"), + VmServiceError::ImageService(msg) => { + Status::unavailable(format!("Image service unavailable: {}", msg)) + } + VmServiceError::InvalidArgument(msg) => Status::invalid_argument(msg), + VmServiceError::AlreadyExists(msg) => Status::already_exists(msg), + } + } +} diff --git a/feos/services/vm-service/src/lib.rs b/feos/services/vm-service/src/lib.rs index 7c3fa82..6d0160d 100644 --- a/feos/services/vm-service/src/lib.rs +++ b/feos/services/vm-service/src/lib.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 -use crate::persistence::PersistenceError; +use crate::error::VmServiceError; use feos_proto::vm_service::{ AttachDiskRequest, AttachDiskResponse, CreateVmRequest, CreateVmResponse, DeleteVmRequest, DeleteVmResponse, GetVmRequest, ListVmsRequest, ListVmsResponse, PauseVmRequest, @@ -16,6 +16,7 @@ use tonic::{Status, Streaming}; pub mod api; pub mod dispatcher; pub mod dispatcher_handlers; +pub mod error; pub mod persistence; pub mod vmm; pub mod worker; @@ -26,44 +27,6 @@ pub const VM_CH_BIN: &str = "cloud-hypervisor"; pub const IMAGE_DIR: &str = "/tmp/feos/images"; pub const VM_CONSOLE_DIR: &str = "/tmp/feos/consoles"; -#[derive(Debug, thiserror::Error)] -pub enum VmServiceError { - #[error("VMM Error: {0}")] - Vmm(#[from] crate::vmm::VmmError), - - #[error("Persistence Error: {0}")] - Persistence(#[from] PersistenceError), - - #[error("Image Service Error: {0}")] - ImageService(String), - - #[error("Invalid argument: {0}")] - InvalidArgument(String), - - #[error("VM with ID {0} already exists")] - AlreadyExists(String), -} - -impl From for Status { - fn from(err: VmServiceError) -> Self { - log::error!("VmServiceError: {}", err); - match err { - VmServiceError::Vmm(vmm_err) => vmm_err.into(), - VmServiceError::Persistence(PersistenceError::Database(ref e)) - if matches!(e, sqlx::Error::RowNotFound) => - { - Status::not_found("Record not found in database") - } - VmServiceError::Persistence(_) => Status::internal("A database error occurred"), - VmServiceError::ImageService(msg) => { - Status::unavailable(format!("Image service unavailable: {}", msg)) - } - VmServiceError::InvalidArgument(msg) => Status::invalid_argument(msg), - VmServiceError::AlreadyExists(msg) => Status::already_exists(msg), - } - } -} - #[derive(Debug, Clone)] pub struct VmEventWrapper { pub event: VmEvent, diff --git a/feos/services/vm-service/src/worker.rs b/feos/services/vm-service/src/worker.rs index a7b1c9c..ade2e3b 100644 --- a/feos/services/vm-service/src/worker.rs +++ b/feos/services/vm-service/src/worker.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - dispatcher_handlers::get_image_service_client, vmm::Hypervisor, VmEventWrapper, VmServiceError, + dispatcher_handlers::get_image_service_client, error::VmServiceError, vmm::Hypervisor, + VmEventWrapper, }; use feos_proto::{ image_service::{ImageState as OciImageState, WatchImageStatusRequest}, From 250d235e380178c42b7fa1fe3ab664a85cf9fb7d Mon Sep 17 00:00:00 2001 From: Guvenc Gulce Date: Fri, 26 Sep 2025 14:37:53 +0200 Subject: [PATCH 4/7] Remove unused crate dependencies Signed-off-by: Guvenc Gulce --- Cargo.lock | 8 -------- cli/Cargo.toml | 12 ++++-------- feos/Cargo.toml | 1 - feos/services/image-service/Cargo.toml | 1 - feos/services/vm-service/Cargo.toml | 3 --- 5 files changed, 4 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbe68f9..8823b1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -781,7 +781,6 @@ dependencies = [ "pnet", "prost", "prost-types", - "regex", "rtnetlink", "socket2 0.6.0", "tempfile", @@ -801,14 +800,11 @@ dependencies = [ "chrono", "clap", "crossterm", - "digest", "env_logger", "feos-proto", - "hex", "hyper-util", "log", "prost", - "sha2", "tokio", "tokio-stream", "tonic", @@ -1513,7 +1509,6 @@ dependencies = [ "prost-types", "serde", "serde_json", - "tempfile", "thiserror 2.0.16", "tokio", "tokio-stream", @@ -3640,7 +3635,6 @@ version = "0.5.0" dependencies = [ "anyhow", "cloud-hypervisor-client", - "dotenvy", "feos-proto", "hyper", "hyper-util", @@ -3648,7 +3642,6 @@ dependencies = [ "image-service", "log", "nix", - "once_cell", "openssl", "prost", "prost-types", @@ -3660,7 +3653,6 @@ dependencies = [ "tokio-stream", "tonic", "tower 0.4.13", - "urlencoding", "uuid", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index af0ad0c..9b0fbc4 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -5,12 +5,8 @@ edition.workspace = true description = "A gRPC CLI for the FeOS control plane" [dependencies] -feos-proto = { workspace = true } -sha2 = { workspace = true } -hex = { workspace = true } -digest = { workspace = true } - # Workspace dependencies +feos-proto = { workspace = true } tokio = { workspace = true } tonic = { workspace = true } anyhow = { workspace = true } @@ -20,8 +16,8 @@ hyper-util = { workspace = true } prost = { workspace = true } tower = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } +env_logger = { workspace = true } +chrono = { workspace = true } # CLI specific dependencies -env_logger = { workspace = true } -crossterm = "0.29" -chrono = { workspace = true } \ No newline at end of file +crossterm = "0.29" \ No newline at end of file diff --git a/feos/Cargo.toml b/feos/Cargo.toml index 5b0dfd9..7530470 100644 --- a/feos/Cargo.toml +++ b/feos/Cargo.toml @@ -55,6 +55,5 @@ tokio-stream = { workspace = true } prost = { workspace = true } hyper-util = { workspace = true } once_cell = "1.19" -regex = "1.10" tower = { workspace = true } tempfile = "3.10.1" \ No newline at end of file diff --git a/feos/services/image-service/Cargo.toml b/feos/services/image-service/Cargo.toml index e05d108..aa04842 100644 --- a/feos/services/image-service/Cargo.toml +++ b/feos/services/image-service/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true [dependencies] feos-proto = { workspace = true } oci-distribution = { workspace = true } -tempfile = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } tonic = { workspace = true } diff --git a/feos/services/vm-service/Cargo.toml b/feos/services/vm-service/Cargo.toml index c91fc45..4dbb000 100644 --- a/feos/services/vm-service/Cargo.toml +++ b/feos/services/vm-service/Cargo.toml @@ -8,11 +8,8 @@ feos-proto = { workspace = true } image-service = { path = "../image-service" } hyper = { version = "1.6.0" } cloud-hypervisor-client = { version = "0.3.3"} -once_cell = "1.19" hyperlocal = "0.9.1" hyper-util = { version = "0.1.14" } -urlencoding = "2.1.3" -dotenvy = "0.15" sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "macros", "uuid"] } # Workspace dependencies From 5c9e62f453e1b929d31063546b49fff326b4ae10 Mon Sep 17 00:00:00 2001 From: Guvenc Gulce Date: Fri, 26 Sep 2025 15:07:27 +0200 Subject: [PATCH 5/7] Fix clippy warnings Signed-off-by: Guvenc Gulce --- feos/services/host-service/src/error.rs | 4 ++-- feos/services/image-service/src/error.rs | 4 ++-- feos/services/vm-service/src/dispatcher_handlers.rs | 2 +- feos/services/vm-service/src/error.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/feos/services/host-service/src/error.rs b/feos/services/host-service/src/error.rs index ac652a3..96ea080 100644 --- a/feos/services/host-service/src/error.rs +++ b/feos/services/host-service/src/error.rs @@ -24,10 +24,10 @@ pub enum HostError { impl From for Status { fn from(err: HostError) -> Self { - log::error!("HostServiceError: {}", err); + log::error!("HostServiceError: {err}"); match err { HostError::SystemInfoRead { path, .. } => { - Status::internal(format!("Failed to read system info from {}", path)) + Status::internal(format!("Failed to read system info from {path}")) } HostError::Hostname(_) | HostError::PowerOperation(_) => { Status::internal("An internal host error occurred") diff --git a/feos/services/image-service/src/error.rs b/feos/services/image-service/src/error.rs index eead3fa..c3c613a 100644 --- a/feos/services/image-service/src/error.rs +++ b/feos/services/image-service/src/error.rs @@ -27,10 +27,10 @@ pub enum ImageServiceError { impl From for Status { fn from(err: ImageServiceError) -> Self { - log::error!("ImageServiceError: {}", err); + log::error!("ImageServiceError: {err}"); match err { ImageServiceError::NotFound(id) => { - Status::not_found(format!("Image with ID '{}' not found", id)) + Status::not_found(format!("Image with ID '{id}' not found")) } ImageServiceError::OciParse(_) => Status::invalid_argument(err.to_string()), ImageServiceError::OciPull(_) | ImageServiceError::MissingLayer(_) => { diff --git a/feos/services/vm-service/src/dispatcher_handlers.rs b/feos/services/vm-service/src/dispatcher_handlers.rs index ec5071c..0f8d565 100644 --- a/feos/services/vm-service/src/dispatcher_handlers.rs +++ b/feos/services/vm-service/src/dispatcher_handlers.rs @@ -163,7 +163,7 @@ pub(crate) async fn handle_create_vm_command( )); } Err(e) => { - error!("VmDispatcher: Failed to handle CreateVm command: {}", e); + error!("VmDispatcher: Failed to handle CreateVm command: {e}"); if responder.send(Err(e)).is_err() { error!( "VmDispatcher: Failed to send error response for CreateVm. Responder closed." diff --git a/feos/services/vm-service/src/error.rs b/feos/services/vm-service/src/error.rs index 7072f8f..b09121c 100644 --- a/feos/services/vm-service/src/error.rs +++ b/feos/services/vm-service/src/error.rs @@ -24,7 +24,7 @@ pub enum VmServiceError { impl From for Status { fn from(err: VmServiceError) -> Self { - log::error!("VmServiceError: {}", err); + log::error!("VmServiceError: {err}"); match err { VmServiceError::Vmm(vmm_err) => vmm_err.into(), VmServiceError::Persistence(PersistenceError::Database(ref e)) @@ -34,7 +34,7 @@ impl From for Status { } VmServiceError::Persistence(_) => Status::internal("A database error occurred"), VmServiceError::ImageService(msg) => { - Status::unavailable(format!("Image service unavailable: {}", msg)) + Status::unavailable(format!("Image service unavailable: {msg}")) } VmServiceError::InvalidArgument(msg) => Status::invalid_argument(msg), VmServiceError::AlreadyExists(msg) => Status::already_exists(msg), From 5c0ff8c0145b839fb875914a4b8da9d9e206a4b8 Mon Sep 17 00:00:00 2001 From: Guvenc Gulce Date: Fri, 26 Sep 2025 16:06:48 +0200 Subject: [PATCH 6/7] Bump crates hyper, hyper-util, tower to latest versions Signed-off-by: Guvenc Gulce --- Cargo.lock | 112 +++++++++++++------------- Cargo.toml | 9 ++- feos/Cargo.toml | 6 +- feos/services/host-service/Cargo.toml | 5 +- feos/services/vm-service/Cargo.toml | 6 +- feos/utils/Cargo.toml | 2 +- 6 files changed, 72 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8823b1f..cb640bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,7 +186,7 @@ dependencies = [ "rustversion", "serde", "sync_wrapper", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", ] @@ -788,7 +788,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic", - "tower 0.4.13", + "tower", "vm-service", ] @@ -808,7 +808,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic", - "tower 0.4.13", + "tower", ] [[package]] @@ -1049,19 +1049,13 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.9.0", + "indexmap", "slab", "tokio", "tokio-util", "tracing", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -1261,13 +1255,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http 1.3.1", "http-body", @@ -1275,6 +1270,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -1329,9 +1325,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64 0.22.1", "bytes", @@ -1345,10 +1341,12 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1516,16 +1514,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.9.0" @@ -2106,7 +2094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.9.0", + "indexmap", ] [[package]] @@ -2504,7 +2492,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-util", - "tower 0.5.2", + "tower", "tower-http", "tower-service", "url", @@ -2968,7 +2956,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.9.0", + "indexmap", "log", "memchr", "once_cell", @@ -3206,11 +3194,32 @@ dependencies = [ "syn 2.0.102", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", @@ -3391,7 +3400,7 @@ dependencies = [ "socket2 0.5.10", "tokio", "tokio-stream", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", "tracing", @@ -3411,27 +3420,6 @@ dependencies = [ "syn 2.0.102", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "hdrhistogram", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.2" @@ -3440,7 +3428,8 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.9.0", + "hdrhistogram", + "indexmap", "pin-project-lite", "slab", "sync_wrapper", @@ -3464,7 +3453,7 @@ dependencies = [ "http-body", "iri-string", "pin-project-lite", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", ] @@ -3652,7 +3641,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic", - "tower 0.4.13", + "tower", "uuid", ] @@ -3874,6 +3863,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index c54788b..089583d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ log = "0.4.28" env_logger = "0.11" openssl = { version = "0.10.72", features = ["vendored"] } oci-distribution = "0.11.0" -tempfile = "3.22.0" -tower = { version = "0.4", features = ["full"] } +tempfile = "3.23.0" +tower = { version = "0.5.2", features = ["full"] } sha2 = "0.10" hex = "0.4" digest = "0.10" @@ -46,5 +46,8 @@ socket2 = "0.6.0" futures = "0.3.31" chrono = "0.4.42" thiserror = "2.0.16" -hyper-util = { version = "0.1.14", features = ["tokio"] } +hyper = "1.7.0" +hyper-util = { version = "0.1.17", features = ["full"] } +termcolor = "1.1" +once_cell = "1.19" feos-proto = { path = "feos/proto" } \ No newline at end of file diff --git a/feos/Cargo.toml b/feos/Cargo.toml index 7530470..ef88b04 100644 --- a/feos/Cargo.toml +++ b/feos/Cargo.toml @@ -38,7 +38,7 @@ dhcproto = { workspace = true } socket2 = { workspace = true } futures = { workspace = true } chrono = { workspace = true } -termcolor = "1.1" +termcolor = { workspace = true } [dev-dependencies] feos-utils = { path = "utils" } @@ -54,6 +54,6 @@ env_logger = { workspace = true } tokio-stream = { workspace = true } prost = { workspace = true } hyper-util = { workspace = true } -once_cell = "1.19" +once_cell = { workspace = true } tower = { workspace = true } -tempfile = "3.10.1" \ No newline at end of file +tempfile = { workspace = true } \ No newline at end of file diff --git a/feos/services/host-service/Cargo.toml b/feos/services/host-service/Cargo.toml index b986f8d..831d786 100644 --- a/feos/services/host-service/Cargo.toml +++ b/feos/services/host-service/Cargo.toml @@ -19,9 +19,10 @@ log = { workspace = true } prost = { workspace = true } prost-types = { workspace = true } thiserror = { workspace = true } +hyper = {workspace = true} +hyper-util = { workspace = true } + -hyper = "1.4.0" -hyper-util = { version = "0.1.3", features = ["full"] } hyper-rustls = "0.27.2" http-body-util = "0.1.2" rustls-pki-types = "1.0" \ No newline at end of file diff --git a/feos/services/vm-service/Cargo.toml b/feos/services/vm-service/Cargo.toml index 4dbb000..9a677bd 100644 --- a/feos/services/vm-service/Cargo.toml +++ b/feos/services/vm-service/Cargo.toml @@ -6,10 +6,8 @@ edition.workspace = true [dependencies] feos-proto = { workspace = true } image-service = { path = "../image-service" } -hyper = { version = "1.6.0" } cloud-hypervisor-client = { version = "0.3.3"} hyperlocal = "0.9.1" -hyper-util = { version = "0.1.14" } sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "macros", "uuid"] } # Workspace dependencies @@ -26,4 +24,6 @@ serde_json = { workspace = true } uuid = { workspace = true } log = { workspace = true } tower = { workspace = true } -thiserror = { workspace = true } \ No newline at end of file +thiserror = { workspace = true } +hyper = {workspace = true} +hyper-util = { workspace = true } \ No newline at end of file diff --git a/feos/utils/Cargo.toml b/feos/utils/Cargo.toml index 0ff81c2..89fa0ce 100644 --- a/feos/utils/Cargo.toml +++ b/feos/utils/Cargo.toml @@ -8,7 +8,7 @@ log = { workspace = true } nix = { workspace = true } chrono = { workspace = true } tokio = { workspace = true } -termcolor = "1.1" +termcolor = { workspace = true } dhcproto = { workspace = true } futures = { workspace = true } netlink-packet-route = { workspace = true } From b43384e4cc7c309cacda2240076641f5380e10c8 Mon Sep 17 00:00:00 2001 From: Guvenc Gulce Date: Fri, 26 Sep 2025 17:41:07 +0200 Subject: [PATCH 7/7] Bump nix crate to 0.30.1 Signed-off-by: Guvenc Gulce --- Cargo.lock | 22 +++++++++++++++++----- Cargo.toml | 2 +- feos/utils/src/filesystem/move.rs | 11 +++-------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb640bf..9e86922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -776,7 +776,7 @@ dependencies = [ "libc", "log", "netlink-packet-route", - "nix", + "nix 0.30.1", "once_cell", "pnet", "prost", @@ -831,7 +831,7 @@ dependencies = [ "libc", "log", "netlink-packet-route", - "nix", + "nix 0.30.1", "pnet", "rtnetlink", "socket2 0.6.0", @@ -1175,7 +1175,7 @@ dependencies = [ "hyper-rustls", "hyper-util", "log", - "nix", + "nix 0.30.1", "prost", "prost-types", "rustls-pki-types", @@ -1856,6 +1856,18 @@ name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", @@ -2548,7 +2560,7 @@ dependencies = [ "netlink-packet-route", "netlink-proto", "netlink-sys", - "nix", + "nix 0.29.0", "thiserror 1.0.69", "tokio", ] @@ -3630,7 +3642,7 @@ dependencies = [ "hyperlocal", "image-service", "log", - "nix", + "nix 0.30.1", "openssl", "prost", "prost-types", diff --git a/Cargo.toml b/Cargo.toml index 089583d..b2f907a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ tonic = "0.13.0" prost = "0.13.5" prost-types = "0.13.5" anyhow = "1.0.100" -nix = { version = "0.29.0", features = ["mount", "user", "reboot", "feature", "net", "aio", "signal", "process", "fs", "hostname"] } +nix = { version = "0.30.1", features = ["mount", "user", "reboot", "feature", "net", "aio", "signal", "process", "fs", "hostname"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.132" uuid = { version = "1.18.1", features = ["v4"] } diff --git a/feos/utils/src/filesystem/move.rs b/feos/utils/src/filesystem/move.rs index c6ee5df..6809ef8 100644 --- a/feos/utils/src/filesystem/move.rs +++ b/feos/utils/src/filesystem/move.rs @@ -28,13 +28,8 @@ pub fn get_root_fstype() -> Result> { let proc_dir_raw = fsmount(proc_fs.as_fd(), 0, 0)?; let proc_dir = unsafe { OwnedFd::from_raw_fd(proc_dir_raw) }; - let mounts_raw = openat( - Some(proc_dir.as_raw_fd()), - "mounts", - OFlag::O_RDONLY, - Mode::empty(), - )?; - let mounts = unsafe { File::from_raw_fd(mounts_raw) }; + let mounts_fd = openat(&proc_dir, "mounts", OFlag::O_RDONLY, Mode::empty())?; + let mounts = unsafe { File::from_raw_fd(mounts_fd.as_raw_fd()) }; let reader = BufReader::new(mounts); @@ -67,7 +62,7 @@ pub fn move_root() -> Result<(), Box> { let tmp_dir_raw = fsmount(tmp_fs.as_fd(), 0, 0)?; let tmp_dir = unsafe { OwnedFd::from_raw_fd(tmp_dir_raw) }; - fchdir(tmp_dir.as_raw_fd())?; + fchdir(&tmp_dir)?; move_recursively(Path::new("/"), Path::new("."))?; mount(