From a0f9e83b34c9b6e683529b8ba4dca1a06bf0a0d9 Mon Sep 17 00:00:00 2001 From: Mark Hurenkamp Date: Thu, 31 Oct 2024 18:47:37 +0100 Subject: [PATCH] Improve Osal & add tests for locking --- Cargo.lock | 187 +++++++++++++++++++++++++++ Cargo.toml | 1 + src/config.rs | 190 ++++++++++++++++++---------- src/config/display.rs | 3 +- src/config/display/looking_glass.rs | 28 ++-- src/config/system/tpm/swtpm.rs | 22 +++- src/main.rs | 41 ++++-- src/osal.rs | 103 +++++++++++++++ src/resource/data_manager.rs | 27 ++-- src/resource/lock.rs | 109 ++++++++-------- src/resource/resource_pool.rs | 14 +- 11 files changed, 550 insertions(+), 175 deletions(-) create mode 100644 src/osal.rs diff --git a/Cargo.lock b/Cargo.lock index cfe2c9d..91955fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,6 +251,7 @@ dependencies = [ "once_cell", "serde", "serde_yaml", + "serial_test", "tokio", "toml", "typetag", @@ -263,6 +264,83 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getopts" version = "0.2.21" @@ -377,6 +455,16 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.21" @@ -478,12 +566,41 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "predicates" version = "3.1.2" @@ -528,6 +645,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.10.4" @@ -569,6 +695,27 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scc" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d25269dd3a12467afe2e510f69fb0b46b698e5afb296b59f2145259deaf8e8" +dependencies = [ + "sdd", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" + [[package]] name = "serde" version = "1.0.200" @@ -611,6 +758,46 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serial_test" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "syn" version = "2.0.60" diff --git a/Cargo.toml b/Cargo.toml index 4fd6ae2..c253826 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ typetag = "0.2.18" derive-getters = "0.5.0" mockall = "0.13.0" mockall_double = "0.3.1" +serial_test = "3.1.1" diff --git a/src/config.rs b/src/config.rs index 9979646..39f7569 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,13 +6,13 @@ mod spice; mod system; mod types; +use std::any::{Any, TypeId}; +use std::ops::Deref; use derive_getters::Getters; -use log::info; +use log::{debug, info}; use serde::{Deserialize, Deserializer}; - use crate::config::display::Display; use crate::config::gpu::Gpu; -use crate::resource::lock::EzkvmError; use crate::yaml::network::Network; use crate::yaml::storage::Storage; @@ -23,6 +23,8 @@ pub use system::System; pub use types::Pci; pub use types::QemuDevice; pub use types::Usb; +pub use display::Gtk; +use crate::osal::OsalError; #[derive(Deserialize, Debug, Default, Getters)] pub struct Config { @@ -45,9 +47,50 @@ pub struct Config { } impl Config { - pub(crate) fn allocate_resources(&self) -> Result, EzkvmError> { + pub(crate) fn allocate_resources(&self) -> Result, OsalError> { Ok(vec![]) } + + fn has_gtk_display_configured(&self) -> bool { + self.get_gtk_display().is_some() + } + fn get_gtk_display(&self) -> Option { + let display = self.display(); + + let gtk_display = if (*display).type_id() == TypeId::of::>() && (*display.deref()).type_id() == TypeId::of::() { + let t = unsafe { &*(display as *const dyn Any as *const Box) }; + Some(t.deref().clone()) + } else { + None + }; + + gtk_display + } + + pub fn get_escalated_uid_and_gid(&self) -> (u32, u32) { + // if gtk display is enabled, qemu (and swtpm) can not be run with escalated privileges + // so in this case return default uid/gid instead + if self.has_gtk_display_configured() { + debug!("get_qemu_uid_and_gid(): Gtk display configured, dropping to default uid/gid"); + return self.get_default_uid_and_gid() + } + + // return euid/egid so that main processes (qemu & swtpm) + // can run with escalated privileges + ( + u32::from(nix::unistd::geteuid()), + u32::from(nix::unistd::getegid()), + ) + } + + pub fn get_default_uid_and_gid(&self) -> (u32, u32) { + // ui processes can not be run as root + // so return actual uid/gid instead of euid/egid + ( + u32::from(nix::unistd::getuid()), + u32::from(nix::unistd::getgid()), + ) + } } impl QemuDevice for Config { @@ -165,48 +208,47 @@ mod tests { assert_eq!(actual, expected); } + const DEFAULT_WINDOWS_CONFIG: &str = r#" + general: + name: wakiza + + system: + bios: { type: "ovmf", uuid: "04d064c3-66a1-4aa7-9589-f8b3ecf91cd7", file: "/dev/vm1/vm-108-efidisk" } + cpu: { model: "qemu64", sockets: 1, cores: 8, flags: "+aes,+pni,+popcnt,+sse4.1,+sse4.2,+ssse3,enforce" } + memory: { max: 16384, balloon: false } + tpm: { type: "swtpm", version: 2.0, disk: "/dev/vm1/vm-108-tpmstate", socket: "/var/ezkvm/wakiza-tpm.socket" } + + spice: + port: 5903 + addr: 0.0.0.0 + + gpu: + type: "passthrough" + pci: + - { vm_id: "0.0", host_id: "0000:03:00.0", multi_function: true } + - { vm_id: "0.1", host_id: "0000:03:00.1" } + + display: + type: "looking_glass" + device: { path: /dev/kvmfr0, size: 128M } + input: { grab_keyboard: true, escape_key: KEY_F12 } + window: { size: 1707x1067, full_screen: true } + + host: + usb: + - { vm_port: "1", host_bus: "1", host_port: "2.2" } + + storage: + - { driver: "scsi-hd", file: "/dev/vm1/vm-108-boot", discard: true, boot_index: "0" } + - { driver: "scsi-hd", file: "/dev/vm1/vm-108-tmp", discard: true } + + network: + - { controller: "bridge", bridge: "vmbr0", driver: "virtio-net-pci", mac: "BC:24:11:3A:21:B7" } + "#; + #[test] fn test_windows_defaults() { - let config: Config = serde_yaml::from_str( - r#" - general: - name: wakiza - - system: - bios: { type: "ovmf", uuid: "04d064c3-66a1-4aa7-9589-f8b3ecf91cd7", file: "/dev/vm1/vm-108-efidisk" } - cpu: { model: "qemu64", sockets: 1, cores: 8, flags: "+aes,+pni,+popcnt,+sse4.1,+sse4.2,+ssse3,enforce" } - memory: { max: 16384, balloon: false } - tpm: { type: "swtpm", version: 2.0, disk: "/dev/vm1/vm-108-tpmstate", socket: "/var/ezkvm/wakiza-tpm.socket" } - - spice: - port: 5903 - addr: 0.0.0.0 - - gpu: - type: "passthrough" - pci: - - { vm_id: "0.0", host_id: "0000:03:00.0", multi_function: true } - - { vm_id: "0.1", host_id: "0000:03:00.1" } - - display: - type: "looking_glass" - device: { path: /dev/kvmfr0, size: 128M } - input: { grab_keyboard: true, escape_key: KEY_F12 } - window: { size: 1707x1067, full_screen: true } - - host: - usb: - - { vm_port: "1", host_bus: "1", host_port: "2.2" } - - storage: - - { driver: "scsi-hd", file: "/dev/vm1/vm-108-boot", discard: true, boot_index: "0" } - - { driver: "scsi-hd", file: "/dev/vm1/vm-108-tmp", discard: true } - - network: - - { controller: "bridge", bridge: "vmbr0", driver: "virtio-net-pci", mac: "BC:24:11:3A:21:B7" } - "#, - ) - .unwrap(); + let config: Config = serde_yaml::from_str(DEFAULT_WINDOWS_CONFIG).unwrap(); let tmp = config.get_qemu_args(0); let actual: Vec<&str> = tmp.iter().map(std::ops::Deref::deref).collect(); @@ -261,33 +303,31 @@ mod tests { assert_eq!(actual, expected); } + const DEFAULT_UBUNTU_CONFIG: &str = r#" + general: + name: gyndine + + system: + bios: { type: "ovmf", uuid: "c0e240a5-859a-4378-a2d9-95088f531142", file: "/dev/vm1/vm-950-disk-0" } + + gpu: + type: "virtio-vga-gl" + memory: 256 + + display: + type: "gtk" + gl: true + + storage: + - { driver: "scsi-hd", file: "/dev/vm1/vm-950-disk-1", discard: true, boot_index: "1" } + + network: + - { controller: "bridge", bridge: "vmbr0", driver: "virtio-net-pci", mac: "BC:24:11:FF:76:89" } + "#; + #[test] fn test_ubuntu_defaults() { - let config: Config = serde_yaml::from_str( - r#" - general: - name: gyndine - - system: - bios: { type: "ovmf", uuid: "c0e240a5-859a-4378-a2d9-95088f531142", file: "/dev/vm1/vm-950-disk-0" } - - gpu: - type: "virtio-vga-gl" - memory: 256 - - display: - type: "gtk" - gl: true - - storage: - - { driver: "scsi-hd", file: "/dev/vm1/vm-950-disk-1", discard: true, boot_index: "1" } - - network: - - { controller: "bridge", bridge: "vmbr0", driver: "virtio-net-pci", mac: "BC:24:11:FF:76:89" } - "#, - ) - .unwrap(); - + let config: Config = serde_yaml::from_str(DEFAULT_UBUNTU_CONFIG).unwrap(); let tmp = config.get_qemu_args(0); let actual: Vec<&str> = tmp.iter().map(std::ops::Deref::deref).collect(); let expected: Vec<&str> = vec![ @@ -327,4 +367,16 @@ mod tests { assert_eq!(actual, expected); } + + #[test] + fn test_has_gtk_display_configured() { + let config: Config = serde_yaml::from_str(DEFAULT_UBUNTU_CONFIG).unwrap(); + assert!(config.has_gtk_display_configured()) + } + + #[test] + fn test_has_no_gtk_display_configured() { + let config: Config = serde_yaml::from_str(DEFAULT_WINDOWS_CONFIG).unwrap(); + assert_eq!(config.has_gtk_display_configured(), false) + } } diff --git a/src/config/display.rs b/src/config/display.rs index f15bc2d..ae0a658 100644 --- a/src/config/display.rs +++ b/src/config/display.rs @@ -1,3 +1,4 @@ +use std::any::Any; #[allow(unused)] pub use crate::config::display::gtk::Gtk; #[allow(unused)] @@ -12,7 +13,7 @@ mod looking_glass; mod no_display; #[typetag::deserialize(tag = "type")] -pub trait Display: QemuDevice {} +pub trait Display: 'static + Any + QemuDevice {} impl Default for Box { fn default() -> Self { NoDisplay::boxed_default() diff --git a/src/config/display/looking_glass.rs b/src/config/display/looking_glass.rs index 7a65d69..fc71e1c 100644 --- a/src/config/display/looking_glass.rs +++ b/src/config/display/looking_glass.rs @@ -1,15 +1,13 @@ use crate::config::display::Display; use crate::config::types::QemuDevice; use crate::config::{default_when_missing, Config}; -use crate::get_lg_uid_and_gid; -use crate::resource::lock::EzkvmError; +//use crate::get_lg_uid_and_gid; use derive_getters::Getters; use log::{debug, warn}; use serde::Deserialize; -use std::fs::File; use std::os::unix::prelude::CommandExt; -use std::process; use std::process::{Child, Command}; +use crate::osal::{Osal, OsalError}; #[derive(Deserialize, Debug, Getters)] pub struct LookingGlass { @@ -48,15 +46,22 @@ impl LookingGlass { result } - fn start_lg_client(&self, config: &Config) -> Result { - debug!("start_lg_client()"); + fn start_lg_client(&self, config: &Config) -> Result { + //let (uid, gid) = get_lg_uid_and_gid(config); + let (uid,gid) = config.get_default_uid_and_gid(); + debug!("start_lg_client() uid: {}, gid: {}", uid, gid); let mut args = vec!["looking-glass-client".to_string()]; args.extend(self.get_lg_client_args(config)); - let (uid, gid) = get_lg_uid_and_gid(config); - debug!("start_lg_client() uid: {}, gid: {}", uid, gid); - + Osal::execute_command( + Command::new("/usr/bin/env") + .args(args) + .uid(uid) + .gid(gid), + Some("looking-glass-client".to_string()) + ) +/* let log_file = File::create("looking-glass-client.log").unwrap(); let log = process::Stdio::from(log_file); let err_file = File::create("looking-glass-client.err").unwrap(); @@ -80,9 +85,8 @@ impl LookingGlass { ); } } - - let name = config.general().name(); - Err(EzkvmError::ExecError { file: name.clone() }) + Err(OsalError::ExecError(Some(config.general().name().clone()))) +*/ } } diff --git a/src/config/system/tpm/swtpm.rs b/src/config/system/tpm/swtpm.rs index 06ee2da..20fea5a 100644 --- a/src/config/system/tpm/swtpm.rs +++ b/src/config/system/tpm/swtpm.rs @@ -1,12 +1,12 @@ use crate::config::system::tpm::Tpm; use crate::config::types::QemuDevice; use crate::config::Config; -use crate::get_swtpm_uid_and_gid; -use crate::resource::lock::EzkvmError; +//use crate::{get_swtpm_uid_and_gid}; use log::{debug, warn}; use serde::Deserialize; use std::os::unix::process::CommandExt; use std::process::{Child, Command}; +use crate::osal::{Osal, OsalError}; #[allow(dead_code)] #[derive(Deserialize, Debug, Clone)] @@ -23,13 +23,24 @@ impl SwTpm { pub fn new(disk: String, socket: String) -> Self { Self { disk, socket } } - fn spawn(&self, uid: u32, gid: u32, name: String) -> Result { + fn spawn(&self, uid: u32, gid: u32, name: String) -> Result { + debug!("SwTpm::spawn() uid: {}, gid: {}, name: {}", uid, gid, name.to_string()); + + Osal::execute_command( + Command::new("/usr/bin/env") + .args(self.get_args()) + .uid(uid) + .gid(gid), + Some("swtpm".to_string()) + ) +/* Command::new("/usr/bin/env") .args(self.get_args()) .uid(uid) .gid(gid) .spawn() - .map_err(|_| EzkvmError::ExecError { file: name }) + .map_err(|_| OsalError::ExecError(Some(name))) + */ } fn get_args(&self) -> Vec { @@ -61,7 +72,8 @@ impl QemuDevice for SwTpm { fn pre_start(&self, config: &Config) { debug!("SwTpm::start()"); - let (uid, gid) = get_swtpm_uid_and_gid(config); + //let (uid, gid) = get_swtpm_uid_and_gid(config); + let (uid,gid) = config.get_escalated_uid_and_gid(); let name = config.general().name().clone(); match self.spawn(uid, gid, name) { Ok(_child) => debug!("SwTpm::pre_start() succeeded"), diff --git a/src/main.rs b/src/main.rs index d2011fc..107e8f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,26 +2,27 @@ mod args; mod config; mod resource; mod yaml; +mod osal; extern crate colored; use std::fs::File; use std::io::Read; -use std::process::Command; -use std::{env, process}; - +use std::process::{Command}; +use std::{env}; use crate::args::{EzkvmArguments, EzkvmCommand}; use crate::colored::Colorize; use crate::config::{Config, QemuDevice}; use crate::resource::data_manager::DataManager; -use crate::resource::lock::{EzkvmError, Lock}; +use crate::resource::lock::{Lock}; use crate::resource::resource_pool::ResourcePool; use chrono::Local; use env_logger::Builder; use log::{debug, Level, LevelFilter}; use std::io::Write; use std::os::unix::prelude::CommandExt; +use crate::osal::{Osal, OsalError}; fn main() { let args = EzkvmArguments::new(env::args().collect()); @@ -51,15 +52,21 @@ fn handle_start_command(name: String) { config.post_start(&config); } - +/* fn get_qemu_uid_and_gid(config: &Config) -> (u32, u32) { let mut uid = u32::from(nix::unistd::geteuid()); let mut gid = u32::from(nix::unistd::getegid()); - /* Todo: fix this again - // if gtk ui is selected, qemu can not be run as root // so drop to actual uid/gid instead of euid/egid + // Todo: Check if this works as alternative for the code commented out below + if let Some(_gtk_display) = ::downcast_ref::(config.display()) { + debug!("get_qemu_uid_and_gid(): Has Gtk display configured"); + uid = u32::from(nix::unistd::getuid()); + gid = u32::from(nix::unistd::getgid()); + } + + /* if let Some(display) = config.display() { if display.get_driver() == "gtk" { uid = u32::from(nix::unistd::getuid()); @@ -85,7 +92,7 @@ fn get_lg_uid_and_gid(_config: &Config) -> (u32, u32) { u32::from(nix::unistd::getgid()), ) } - +*/ fn load_vm(file: &str) -> Config { debug!("load_vm({})", file); @@ -111,12 +118,25 @@ fn load_pool(file: &str) -> ResourcePool { serde_yaml::from_str(contents.as_str()).unwrap() } -fn start_vm(name: &String, config: &Config) -> Result { +fn start_vm(name: &String, config: &Config) -> Result { debug!("start_vm()"); + //let (uid, gid) = get_qemu_uid_and_gid(config); + let (uid,gid) = config.get_escalated_uid_and_gid(); let args = config.get_qemu_args(0); let resources: Vec = config.allocate_resources()?; + match Osal::execute_command( + Command::new("/usr/bin/env") + .args(args) + .uid(uid) + .gid(gid), + Some("qemu".to_string())) + { + Ok(child) => Ok(Lock::new(name.clone(), child.id(), resources)), + Err(error) => Err(error) + } +/* let log_file = File::create("qemu.log").unwrap(); let log = process::Stdio::from(log_file); let err_file = File::create("qemu.err").unwrap(); @@ -135,8 +155,9 @@ fn start_vm(name: &String, config: &Config) -> Result { Ok(Lock::new(name.clone(), child.id(), resources)) } else { debug!("start_vm(): Failed to start qemu"); - Err(EzkvmError::ExecError { file: name.clone() }) + Err(OsalError::ExecError(Some(name.clone()))) } +*/ } fn init_logger(log_level: LevelFilter) { diff --git a/src/osal.rs b/src/osal.rs new file mode 100644 index 0000000..e27c5c1 --- /dev/null +++ b/src/osal.rs @@ -0,0 +1,103 @@ +use std::{fs, process}; +use std::fmt::Display; +use std::fs::File; +use std::path::Path; +use std::process::{Child, Command}; +use log::{debug, error}; + +#[derive(Debug,PartialEq)] +pub enum OsalError { + OpenError(Option), + ReadError(Option), + WriteError(Option), + ExecError(Option), + DeleteError(Option), + ParseError(Option), + Busy(Option) +} + +pub struct Osal {} +#[cfg_attr(test,mockall::automock)] +#[allow(unused)] +impl Osal { + pub fn read_file>(path: P) -> Result + { + let file = format!("{:?}",path.as_ref()); + let content = fs::read(path).map_err(|_| OsalError::ReadError(Some(file.clone())))?; + Ok(String::from_utf8(content).map_err(|_| OsalError::ParseError(Some(file)))?) + } + pub fn write_file, S: 'static + AsRef>(path: P, content: S) -> Result<(), OsalError> + { + let file = format!("{:?}",path.as_ref()); + Ok(fs::write(path, content.as_ref()).map_err(|_| OsalError::WriteError(Some(file)))?) + } + pub fn delete_file>(path: P) -> Result<(), OsalError> + { + let file = format!("{:?}",path.as_ref()); + Ok(fs::remove_file(path).map_err(|_| OsalError::DeleteError(Some(file)))?) + } + pub fn execute_command>(command: &mut Command, log_path: Option

) -> Result + { + if let Some(log_path) = log_path { + let log_file = File::create(format!("{}.log", log_path)).unwrap(); + let err_file = log_file.try_clone().expect("unable to clone log_file"); + let log = process::Stdio::from(log_file); + //let err_file = File::create(format!("{}.err", log_path)).unwrap(); + let err = process::Stdio::from(err_file); + + command.stdout(log).stderr(err); + } + + match command.spawn().map_err(|_| OsalError::ExecError(Some(format!("{:?}", command)))) { + Ok(child) => { + debug!("Osal::execute_command(): Spawned '{:?} {:?}' with pid {}", command.get_program(),command.get_args(), child.id()); + Ok(child) + }, + Err(error) => { + error!("Osal::execute_command(): Unable to spawn '{:?} {:?}' due to error {:?}", command.get_program(),command.get_args(), error); + Err(error) + } + } + } + + pub fn _execute_command(command: &mut Command, log_path: Option) -> Result + { + if let Some(log_path) = log_path { + let log_file = File::create(format!("{}.log", log_path)).unwrap(); + let err_file = log_file.try_clone().expect("unable to clone log_file"); + let log = process::Stdio::from(log_file); + //let err_file = File::create(format!("{}.err", log_path)).unwrap(); + let err = process::Stdio::from(err_file); + + command.stdout(log).stderr(err); + } + + match command.spawn().map_err(|_| OsalError::ExecError(Some(format!("{:?}",command)))) { + Ok(child) => { + debug!("Osal::execute_command(): Spawned '{:?} {:?}' with pid {}", command.get_program(),command.get_args(), child.id()); + Ok(child) + }, + Err(error) => { + error!("Osal::execute_command(): Unable to spawn '{:?} {:?}' due to error {:?}", command.get_program(),command.get_args(), error); + Err(error) + } + } +/* + match log_path + { + Some(log_path) => + { + let log_file = File::create(format!("{}.log", log_path)).unwrap(); + let log = process::Stdio::from(log_file); + let err_file = File::create(format!("{}.err", log_path)).unwrap(); + let err = process::Stdio::from(err_file); + + Ok(command.stdout(log).stderr(err).spawn().map_err(|_| OsalError::ExecError(Some(format!("{:?}",command))))?) + } + _ => { + Ok(command.spawn().map_err(|_| OsalError::ExecError(Some(format!("{:?}",command))))?) + } + } + */ + } +} diff --git a/src/resource/data_manager.rs b/src/resource/data_manager.rs index 0b550e4..85f0516 100644 --- a/src/resource/data_manager.rs +++ b/src/resource/data_manager.rs @@ -1,4 +1,4 @@ -use crate::resource::lock::{EzkvmError, Lock}; +use crate::resource::lock::{Lock}; use crate::resource::resource::Resource; use crate::resource::resource_pool::ResourcePool; use log::{debug, info}; @@ -6,6 +6,7 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use std::fs; use std::sync::{Arc, Mutex}; +use crate::osal::OsalError; static RESOURCE_MANAGER: Lazy>> = Lazy::new(|| Arc::new(Mutex::new(DataManager::new("/etc/ezkvm/resource")))); @@ -45,22 +46,22 @@ impl DataManager { RESOURCE_MANAGER.clone() } - pub fn claim_resource(&mut self, pool: String) -> Result { + pub fn claim_resource(&mut self, pool: String) -> Result { debug!("DataManager::claim_resource('{}')", pool); if let Some(resource_pool) = self.resources.get(&pool) { let id = resource_pool.claim_resource(&self.locked_resources)?; self.current_lock.add_resource(id.clone()); self.locked_resources - .insert(id.clone(), self.current_lock.get_name()); + .insert(id.clone(), self.current_lock.name().clone()); return Ok(id); } debug!( "DataManager::claim_resource() Resource '{}' not available.", - pool + pool.clone() ); - Err(EzkvmError::ResourceNotAvailable { pool }) + Err(OsalError::Busy(Some(pool))) } pub fn get_resource(&self, pool: &String, id: &String) -> Option<&Resource> { @@ -81,13 +82,11 @@ impl DataManager { */ } -fn load_resource_pools() -> Result, EzkvmError> { +fn load_resource_pools() -> Result, OsalError> { debug!("read_locks()"); let mut resource_pools = HashMap::from([]); - let files = fs::read_dir("/etc/ezkvm/resources/").map_err(|_| EzkvmError::OpenError { - file: "/etc/ezkvm/resources/".to_string(), - })?; + let files = fs::read_dir("/etc/ezkvm/resources/").map_err(|_| OsalError::OpenError(None) )?; for file in files { if let Ok(entry) = file { if let Ok(file_name) = entry.file_name().into_string() { @@ -104,13 +103,11 @@ fn load_resource_pools() -> Result, EzkvmError> { Ok(resource_pools) } -fn load_machine_locks() -> Result, EzkvmError> { +fn load_machine_locks() -> Result, OsalError> { debug!("read_locks()"); let mut locks = HashMap::from([]); - let files = fs::read_dir("/var/ezkvm/lock/").map_err(|_| EzkvmError::OpenError { - file: "/var/ezkvm/lock/".to_string(), - })?; + let files = fs::read_dir("/var/ezkvm/lock/").map_err(|_| OsalError::OpenError(None))?; for file in files { if let Ok(entry) = file { if let Ok(file_name) = entry.file_name().into_string() { @@ -134,8 +131,8 @@ fn find_locked_resources( let mut result = HashMap::from([]); for (_, lock) in locks { - for locked_resource in lock.get_resources() { - result.insert(locked_resource, lock.get_name()); + for locked_resource in lock.resources() { + result.insert(locked_resource.clone(), lock.name().clone()); } } diff --git a/src/resource/lock.rs b/src/resource/lock.rs index f56dffc..c5b6784 100644 --- a/src/resource/lock.rs +++ b/src/resource/lock.rs @@ -1,21 +1,11 @@ +use derive_getters::Getters; use log::debug; use serde::{Deserialize, Serialize}; -use std::fs; -use std::fs::File; -use std::io::Read; +use crate::osal::OsalError; +#[mockall_double::double] +use crate::osal::Osal; -#[allow(dead_code)] -pub enum EzkvmError { - OpenError { file: String }, - ReadError { file: String }, - WriteError { file: String }, - ExecError { file: String }, - DeleteError { file: String }, - ParseError { file: String }, - ResourceNotAvailable { pool: String }, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Getters)] pub struct Lock { name: String, pid: u32, @@ -31,62 +21,73 @@ impl Lock { } } - pub fn get_name(&self) -> String { - self.name.clone() - } - - #[allow(dead_code)] - pub fn get_pid(&self) -> u32 { - self.pid - } - - pub fn get_resources(&self) -> Vec { - self.resources.clone() - } - pub fn add_resource(&mut self, id: String) { self.resources.push(id); } - pub fn read(name: &str) -> Result { + pub fn read(name: &str) -> Result { let filename = format!("/var/ezkvm/lock/{}.yaml", name); - let mut file = File::open(&filename).map_err(|_| EzkvmError::OpenError { - file: filename.clone(), - })?; - let mut contents = String::new(); - - file.read_to_string(&mut contents) - .map_err(|_| EzkvmError::ReadError { - file: filename.clone(), - })?; - + let content = Osal::read_file(filename.clone())?; let result = - serde_yaml::from_str(contents.as_str()).map_err(|_| EzkvmError::ParseError { - file: filename.clone(), - })?; - debug!("Lock::read({}): {:?}", name, result); - + serde_yaml::from_str(content.as_str()).map_err(|_| OsalError::ParseError(Some(filename)))?; Ok(result) } #[allow(dead_code)] - pub fn write(&self) -> Result<(), EzkvmError> { + pub fn write(&self) -> Result<(), OsalError> { let filename = format!("/var/ezkvm/lock/{}.yaml", self.name); - let yaml = serde_yaml::to_string(&self).map_err(|_| EzkvmError::ParseError { - file: filename.clone(), - })?; - debug!("Lock[{}].write(): {:?}", self.name, yaml); - fs::write(&filename, yaml).map_err(|_| EzkvmError::WriteError { file: filename })?; + let content = serde_yaml::to_string(&self).map_err(|_| OsalError::ParseError(Some(filename.clone())))?; - Ok(()) + Ok(Osal::write_file(filename, content)?) } #[allow(dead_code)] - pub fn delete(&self) -> Result<(), EzkvmError> { + pub fn delete(&self) -> Result<(), OsalError> { debug!("Lock[{}].delete()", self.name); let filename = format!("/var/ezkvm/lock/{}.yaml", self.name); - fs::remove_file(&filename).map_err(|_| EzkvmError::DeleteError { file: filename })?; - Ok(()) + Ok(Osal::delete_file(filename)?) + } +} + +#[cfg(test)] +mod test { + use serial_test::serial; + use super::*; + + #[test] + #[serial] + pub fn read_empty_lock_file_results_in_parse_error() { + let ctx = Osal::read_file_context(); + ctx.expect() + .returning(|_path: String| + Ok("".to_string()) + ); + + let actual = Lock::read("name"); + let expectation = Err(OsalError::ParseError(Some("/var/ezkvm/lock/name.yaml".to_string()))); + assert_eq!(actual,expectation) + } + + #[test] + #[serial] + pub fn read_valid_lock_file_succeeds() { + let ctx = Osal::read_file_context(); + ctx.expect() + .returning(|_path: String| + Ok(r#" + name: "wakiza" + pid: 12345 + resources: + "#.to_string()) + ); + + let actual = Lock::read("wakiza").unwrap(); + let expectation = Lock { + name: "wakiza".to_string(), + pid: 12345, + resources: vec![], + }; + assert_eq!(actual,expectation) } } diff --git a/src/resource/resource_pool.rs b/src/resource/resource_pool.rs index 7d95fc9..524e2ac 100644 --- a/src/resource/resource_pool.rs +++ b/src/resource/resource_pool.rs @@ -1,10 +1,10 @@ -use crate::resource::lock::EzkvmError; use crate::resource::resource::Resource; use log::debug; use serde::Deserialize; use std::collections::HashMap; use std::fs::File; use std::io::Read; +use crate::osal::OsalError; #[derive(Debug, Deserialize)] pub struct ResourcePool { @@ -12,7 +12,7 @@ pub struct ResourcePool { devices: Vec, } impl ResourcePool { - pub fn read(name: &str) -> Result { + pub fn read(name: &str) -> Result { debug!("ResourcePool::read({})", name); let mut file = @@ -23,9 +23,7 @@ impl ResourcePool { .expect("Unable to read file"); let resource_pool: ResourcePool = - serde_yaml::from_str(contents.as_str()).map_err(|_| EzkvmError::ParseError { - file: name.to_string(), - })?; + serde_yaml::from_str(contents.as_str()).map_err(|_| OsalError::ParseError(Some(name.to_string())))?; Ok(resource_pool) } @@ -36,7 +34,7 @@ impl ResourcePool { pub fn claim_resource( &self, locked_resources: &HashMap, - ) -> Result { + ) -> Result { debug!("ResourcePool::claim_resource()"); for id in self.get_ids() { if !locked_resources.contains_key(&id) { @@ -48,9 +46,7 @@ impl ResourcePool { "ResourcePool::claim_resource() Resource {} not available.", self.id.clone() ); - Err(EzkvmError::ResourceNotAvailable { - pool: self.id.clone(), - }) + Err(OsalError::Busy(Some(self.id.clone()))) } pub fn get_resource(&self, id: &String) -> Option<&Resource> {