Files
cloud-hypervisor/src/main.rs
Thomas Barrett c297d8d796 vmm: use RateLimiterGroup for virtio-blk devices
Add a 'rate_limit_groups' field to VmConfig that defines a set of
named RateLimiterGroups.

When the 'rate_limit_group' field of DiskConfig is defined, all
virtio-blk queues will be rate-limited by a shared RateLimiterGroup.
The lifecycle of all RateLimiterGroups is tied to the Vm.
A RateLimiterGroup may exist even if no Disks are configured to use
the RateLimiterGroup. Disks may be hot-added or hot-removed from the
RateLimiterGroup.

When the 'rate_limiter' field of DiskConfig is defined, we construct
an anonymous RateLimiterGroup whose lifecycle is tied to the Disk.
This is primarily done for api backwards compatability. Importantly,
the behavior is not the same! This implementation rate_limits the
aggregate bandwidth / iops of an individual disk rather than the
bandwidth / iops of an individual queue of a disk.

When neither the 'rate_limit_group' or the 'rate_limiter' fields of
DiskConfig is defined, the Disk is not rate-limited.

Signed-off-by: Thomas Barrett <tbarrett@crusoeenergy.com>
2024-01-03 10:21:06 -08:00

1855 lines
62 KiB
Rust

// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
#[macro_use]
extern crate event_monitor;
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command};
use libc::EFD_NONBLOCK;
use log::{warn, LevelFilter};
use option_parser::OptionParser;
use seccompiler::SeccompAction;
use signal_hook::consts::SIGSYS;
use std::env;
use std::fs::File;
use std::os::unix::io::{FromRawFd, RawFd};
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use thiserror::Error;
#[cfg(feature = "dbus_api")]
use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions};
use vmm::config;
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::signal::block_signal;
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
#[derive(Error, Debug)]
enum Error {
#[error("Failed to create API EventFd: {0}")]
CreateApiEventFd(#[source] std::io::Error),
#[cfg(feature = "guest_debug")]
#[error("Failed to create Debug EventFd: {0}")]
CreateDebugEventFd(#[source] std::io::Error),
#[error("Failed to create exit EventFd: {0}")]
CreateExitEventFd(#[source] std::io::Error),
#[error("Failed to open hypervisor interface (is hypervisor interface available?): {0}")]
CreateHypervisor(#[source] hypervisor::HypervisorError),
#[error("Failed to start the VMM thread: {0}")]
StartVmmThread(#[source] vmm::Error),
#[error("Error parsing config: {0}")]
ParsingConfig(vmm::config::Error),
#[error("Error creating VM: {0:?}")]
VmCreate(vmm::api::ApiError),
#[error("Error booting VM: {0:?}")]
VmBoot(vmm::api::ApiError),
#[error("Error restoring VM: {0:?}")]
VmRestore(vmm::api::ApiError),
#[error("Error parsing restore: {0}")]
ParsingRestore(vmm::config::Error),
#[error("Failed to join on VMM thread: {0:?}")]
ThreadJoin(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
#[error("VMM thread exited with error: {0}")]
VmmThread(#[source] vmm::Error),
#[error("Error parsing --api-socket: {0}")]
ParsingApiSocket(std::num::ParseIntError),
#[error("Error parsing --event-monitor: {0}")]
ParsingEventMonitor(option_parser::OptionParserError),
#[cfg(feature = "dbus_api")]
#[error("`--dbus-object-path` option isn't provided")]
MissingDBusObjectPath,
#[cfg(feature = "dbus_api")]
#[error("`--dbus-service-name` option isn't provided")]
MissingDBusServiceName,
#[error("Error parsing --event-monitor: path or fd required")]
BareEventMonitor,
#[error("Error doing event monitor I/O: {0}")]
EventMonitorIo(std::io::Error),
#[error("Event monitor thread failed: {0}")]
EventMonitorThread(#[source] vmm::Error),
#[cfg(feature = "guest_debug")]
#[error("Error parsing --gdb: {0}")]
ParsingGdb(option_parser::OptionParserError),
#[cfg(feature = "guest_debug")]
#[error("Error parsing --gdb: path required")]
BareGdb,
#[error("Error creating log file: {0}")]
LogFileCreation(std::io::Error),
#[error("Error setting up logger: {0}")]
LoggerSetup(log::SetLoggerError),
}
struct Logger {
output: Mutex<Box<dyn std::io::Write + Send>>,
start: std::time::Instant,
}
impl log::Log for Logger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if !self.enabled(record.metadata()) {
return;
}
let now = std::time::Instant::now();
let duration = now.duration_since(self.start);
if record.file().is_some() && record.line().is_some() {
write!(
*(*(self.output.lock().unwrap())),
"cloud-hypervisor: {:.6?}: <{}> {}:{}:{} -- {}\r\n",
duration,
std::thread::current().name().unwrap_or("anonymous"),
record.level(),
record.file().unwrap(),
record.line().unwrap(),
record.args()
)
} else {
write!(
*(*(self.output.lock().unwrap())),
"cloud-hypervisor: {:.6?}: <{}> {}:{} -- {}\r\n",
duration,
std::thread::current().name().unwrap_or("anonymous"),
record.level(),
record.target(),
record.args()
)
}
.ok();
}
fn flush(&self) {}
}
fn prepare_default_values() -> (String, String, String) {
(default_vcpus(), default_memory(), default_rng())
}
fn default_vcpus() -> String {
format!(
"boot={},max_phys_bits={}",
config::DEFAULT_VCPUS,
config::DEFAULT_MAX_PHYS_BITS
)
}
fn default_memory() -> String {
format!("size={}M", config::DEFAULT_MEMORY_MB)
}
fn default_rng() -> String {
format!("src={}", config::DEFAULT_RNG_SOURCE)
}
fn create_app(default_vcpus: String, default_memory: String, default_rng: String) -> Command {
#[allow(clippy::let_and_return)]
let app = Command::new("cloud-hypervisor")
// 'BUILD_VERSION' is set by the build script 'build.rs' at
// compile time
.author(env!("CARGO_PKG_AUTHORS"))
.about("Launch a cloud-hypervisor VMM.")
.group(ArgGroup::new("vm-config").multiple(true))
.group(ArgGroup::new("vmm-config").multiple(true))
.group(ArgGroup::new("logging").multiple(true))
.arg(
Arg::new("cpus")
.long("cpus")
.help(
"boot=<boot_vcpus>,max=<max_vcpus>,\
topology=<threads_per_core>:<cores_per_die>:<dies_per_package>:<packages>,\
kvm_hyperv=on|off,max_phys_bits=<maximum_number_of_physical_bits>,\
affinity=<list_of_vcpus_with_their_associated_cpuset>,\
features=<list_of_features_to_enable>",
)
.default_value(default_vcpus)
.group("vm-config"),
)
.arg(
Arg::new("platform")
.long("platform")
.help("num_pci_segments=<num_pci_segments>,iommu_segments=<list_of_segments>,serial_number=<dmi_device_serial_number>,uuid=<dmi_device_uuid>,oem_strings=<list_of_strings>")
.num_args(1)
.group("vm-config"),
)
.arg(
Arg::new("memory")
.long("memory")
.help(
"Memory parameters \
\"size=<guest_memory_size>,mergeable=on|off,shared=on|off,\
hugepages=on|off,hugepage_size=<hugepage_size>,\
hotplug_method=acpi|virtio-mem,\
hotplug_size=<hotpluggable_memory_size>,\
hotplugged_size=<hotplugged_memory_size>,\
prefault=on|off,thp=on|off\"",
)
.default_value(default_memory)
.group("vm-config"),
)
.arg(
Arg::new("memory-zone")
.long("memory-zone")
.help(
"User defined memory zone parameters \
\"size=<guest_memory_region_size>,file=<backing_file>,\
shared=on|off,\
hugepages=on|off,hugepage_size=<hugepage_size>,\
host_numa_node=<node_id>,\
id=<zone_identifier>,hotplug_size=<hotpluggable_memory_size>,\
hotplugged_size=<hotplugged_memory_size>,\
prefault=on|off\"",
)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("firmware")
.long("firmware")
.help("Path to firmware that is loaded in an architectural specific way")
.num_args(1)
.group("vm-config"),
)
.arg(
Arg::new("kernel")
.long("kernel")
.help(
"Path to kernel to load. This may be a kernel or firmware that supports a PVH \
entry point (e.g. vmlinux) or architecture equivalent",
)
.num_args(1)
.group("vm-config"),
)
.arg(
Arg::new("initramfs")
.long("initramfs")
.help("Path to initramfs image")
.num_args(1)
.group("vm-config"),
)
.arg(
Arg::new("cmdline")
.long("cmdline")
.help("Kernel command line")
.num_args(1)
.group("vm-config"),
)
.arg(
Arg::new("rate-limit-group")
.long("rate-limit-group")
.help(config::RateLimiterGroupConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("disk")
.long("disk")
.help(config::DiskConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("net")
.long("net")
.help(config::NetConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("rng")
.long("rng")
.help(
"Random number generator parameters \"src=<entropy_source_path>,iommu=on|off\"",
)
.default_value(default_rng)
.group("vm-config"),
)
.arg(
Arg::new("balloon")
.long("balloon")
.help(config::BalloonConfig::SYNTAX)
.num_args(1)
.group("vm-config"),
)
.arg(
Arg::new("fs")
.long("fs")
.help(config::FsConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("pmem")
.long("pmem")
.help(config::PmemConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("serial")
.long("serial")
.help("Control serial port: off|null|pty|tty|file=</path/to/a/file>|socket=</path/to/a/file>")
.default_value("null")
.group("vm-config"),
)
.arg(
Arg::new("console")
.long("console")
.help(
"Control (virtio) console: \"off|null|pty|tty|file=</path/to/a/file>,iommu=on|off\"",
)
.default_value("tty")
.group("vm-config"),
)
.arg(
Arg::new("device")
.long("device")
.help(config::DeviceConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("user-device")
.long("user-device")
.help(config::UserDeviceConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("vdpa")
.long("vdpa")
.help(config::VdpaConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("vsock")
.long("vsock")
.help(config::VsockConfig::SYNTAX)
.num_args(1)
.group("vm-config"),
)
.arg(
Arg::new("pvpanic")
.long("pvpanic")
.help("Enable pvpanic device")
.num_args(0)
.action(ArgAction::SetTrue)
.group("vm-config"),
)
.arg(
Arg::new("numa")
.long("numa")
.help(config::NumaConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("watchdog")
.long("watchdog")
.help("Enable virtio-watchdog")
.num_args(0)
.action(ArgAction::SetTrue)
.group("vm-config"),
)
.arg(
Arg::new("v")
.short('v')
.action(ArgAction::Count)
.help("Sets the level of debugging output")
.group("logging"),
)
.arg(
Arg::new("log-file")
.long("log-file")
.help("Log file. Standard error is used if not specified")
.num_args(1)
.group("logging"),
)
.arg(
Arg::new("api-socket")
.long("api-socket")
.help("HTTP API socket (UNIX domain socket): path=</path/to/a/file> or fd=<fd>.")
.num_args(1)
.group("vmm-config"),
)
.arg(
Arg::new("event-monitor")
.long("event-monitor")
.help("File to report events on: path=</path/to/a/file> or fd=<fd>")
.num_args(1)
.group("vmm-config"),
)
.arg(
Arg::new("restore")
.long("restore")
.help(config::RestoreConfig::SYNTAX)
.num_args(1)
.group("vmm-config"),
)
.arg(
Arg::new("seccomp")
.long("seccomp")
.num_args(1)
.value_parser(["true", "false", "log"])
.default_value("true"),
)
.arg(
Arg::new("tpm")
.long("tpm")
.num_args(1)
.help(config::TpmConfig::SYNTAX)
.group("vmm-config"),
);
#[cfg(target_arch = "x86_64")]
let app = app.arg(
Arg::new("sgx-epc")
.long("sgx-epc")
.help(config::SgxEpcConfig::SYNTAX)
.num_args(1..)
.group("vm-config"),
);
#[cfg(feature = "guest_debug")]
let app = app.arg(
Arg::new("gdb")
.long("gdb")
.help("GDB socket (UNIX domain socket): path=</path/to/a/file>")
.num_args(1)
.group("vmm-config"),
);
#[cfg(feature = "dbus_api")]
let app = app
.arg(
Arg::new("dbus-service-name")
.long("dbus-service-name")
.help("Well known name of the device")
.num_args(1)
.group("vmm-config"),
)
.arg(
Arg::new("dbus-object-path")
.long("dbus-object-path")
.help("Object path to serve the dbus interface")
.num_args(1)
.group("vmm-config"),
)
.arg(
Arg::new("dbus-system-bus")
.long("dbus-system-bus")
.action(ArgAction::SetTrue)
.help("Use the system bus instead of a session bus")
.num_args(0)
.group("vmm-config"),
);
#[cfg(feature = "igvm")]
let app = app.arg(
Arg::new("igvm")
.long("igvm")
.help("Path to IGVM file to load.")
.num_args(1)
.group("vm-config"),
);
app.arg(
Arg::new("version")
.short('V')
.long("version")
.action(ArgAction::SetTrue)
.help("Print version")
.num_args(0),
)
}
fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
let log_level = match cmd_arguments.get_count("v") {
0 => LevelFilter::Warn,
1 => LevelFilter::Info,
2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
let log_file: Box<dyn std::io::Write + Send> = if let Some(ref file) =
cmd_arguments.get_one::<String>("log-file")
{
Box::new(std::fs::File::create(std::path::Path::new(file)).map_err(Error::LogFileCreation)?)
} else {
Box::new(std::io::stderr())
};
log::set_boxed_logger(Box::new(Logger {
output: Mutex::new(log_file),
start: std::time::Instant::now(),
}))
.map(|()| log::set_max_level(log_level))
.map_err(Error::LoggerSetup)?;
let (api_socket_path, api_socket_fd) =
if let Some(socket_config) = cmd_arguments.get_one::<String>("api-socket") {
let mut parser = OptionParser::new();
parser.add("path").add("fd");
parser.parse(socket_config).unwrap_or_default();
if let Some(fd) = parser.get("fd") {
(
None,
Some(fd.parse::<RawFd>().map_err(Error::ParsingApiSocket)?),
)
} else if let Some(path) = parser.get("path") {
(Some(path), None)
} else {
(
cmd_arguments
.get_one::<String>("api-socket")
.map(|s| s.to_string()),
None,
)
}
} else {
(None, None)
};
let (api_request_sender, api_request_receiver) = channel();
let api_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateApiEventFd)?;
let api_request_sender_clone = api_request_sender.clone();
let seccomp_action = if let Some(seccomp_value) = cmd_arguments.get_one::<String>("seccomp") {
match seccomp_value as &str {
"true" => SeccompAction::Trap,
"false" => SeccompAction::Allow,
"log" => SeccompAction::Log,
val => {
// The user providing an invalid value will be rejected
panic!("Invalid parameter {val} for \"--seccomp\" flag");
}
}
} else {
SeccompAction::Trap
};
if seccomp_action == SeccompAction::Trap {
// SAFETY: We only using signal_hook for managing signals and only execute signal
// handler safe functions (writing to stderr) and manipulating signals.
unsafe {
signal_hook::low_level::register(signal_hook::consts::SIGSYS, || {
eprint!(
"\n==== Possible seccomp violation ====\n\
Try running with `strace -ff` to identify the cause and open an issue: \
https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new\n"
);
signal_hook::low_level::emulate_default_handler(SIGSYS).unwrap();
})
}
.map_err(|e| eprintln!("Error adding SIGSYS signal handler: {e}"))
.ok();
}
// Before we start any threads, mask the signals we'll be
// installing handlers for, to make sure they only ever run on the
// dedicated signal handling thread we'll start in a bit.
for sig in &vmm::vm::Vm::HANDLED_SIGNALS {
if let Err(e) = block_signal(*sig) {
eprintln!("Error blocking signals: {e}");
}
}
for sig in &vmm::Vmm::HANDLED_SIGNALS {
if let Err(e) = block_signal(*sig) {
eprintln!("Error blocking signals: {e}");
}
}
let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?;
#[cfg(feature = "guest_debug")]
let gdb_socket_path = if let Some(gdb_config) = cmd_arguments.get_one::<String>("gdb") {
let mut parser = OptionParser::new();
parser.add("path");
parser.parse(gdb_config).map_err(Error::ParsingGdb)?;
if parser.is_set("path") {
Some(std::path::PathBuf::from(parser.get("path").unwrap()))
} else {
return Err(Error::BareGdb);
}
} else {
None
};
#[cfg(feature = "guest_debug")]
let debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
#[cfg(feature = "guest_debug")]
let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?;
#[allow(unused_mut)]
let mut event_monitor = cmd_arguments
.get_one::<String>("event-monitor")
.as_ref()
.map(|monitor_config| {
let mut parser = OptionParser::new();
parser.add("path").add("fd");
parser
.parse(monitor_config)
.map_err(Error::ParsingEventMonitor)?;
if parser.is_set("fd") {
let fd = parser
.convert("fd")
.map_err(Error::ParsingEventMonitor)?
.unwrap();
// SAFETY: fd is valid
Ok(Some(unsafe { File::from_raw_fd(fd) }))
} else if parser.is_set("path") {
Ok(Some(
std::fs::OpenOptions::new()
.write(true)
.create(true)
.open(parser.get("path").unwrap())
.map_err(Error::EventMonitorIo)?,
))
} else {
Err(Error::BareEventMonitor)
}
})
.transpose()?
.map(|event_monitor_file| {
event_monitor::set_monitor(event_monitor_file).map_err(Error::EventMonitorIo)
})
.transpose()?;
#[cfg(feature = "dbus_api")]
let dbus_options = match (
cmd_arguments.get_one::<String>("dbus-service-name"),
cmd_arguments.get_one::<String>("dbus-object-path"),
) {
(Some(name), Some(path)) => {
// monitor is either set (file based) or not.
// if it's not set, create one without file support.
let mut monitor = match event_monitor.take() {
Some(monitor) => monitor,
None => event_monitor::set_monitor(None).map_err(Error::EventMonitorIo)?,
};
let options = DBusApiOptions {
service_name: name.to_string(),
object_path: path.to_string(),
system_bus: cmd_arguments.get_flag("dbus-system-bus"),
event_monitor_rx: monitor.subscribe(),
};
event_monitor = Some(monitor);
Ok(Some(options))
}
(Some(_), None) => Err(Error::MissingDBusObjectPath),
(None, Some(_)) => Err(Error::MissingDBusServiceName),
(None, None) => Ok(None),
}?;
if let Some(monitor) = event_monitor {
vmm::start_event_monitor_thread(
monitor,
&seccomp_action,
hypervisor.hypervisor_type(),
exit_evt.try_clone().unwrap(),
)
.map_err(Error::EventMonitorThread)?;
}
event!("vmm", "starting");
let vmm_thread_handle = vmm::start_vmm_thread(
vmm::VmmVersionInfo::new(env!("BUILD_VERSION"), env!("CARGO_PKG_VERSION")),
&api_socket_path,
api_socket_fd,
#[cfg(feature = "dbus_api")]
dbus_options,
api_evt.try_clone().unwrap(),
api_request_sender_clone,
api_request_receiver,
#[cfg(feature = "guest_debug")]
gdb_socket_path,
#[cfg(feature = "guest_debug")]
debug_evt.try_clone().unwrap(),
#[cfg(feature = "guest_debug")]
vm_debug_evt.try_clone().unwrap(),
exit_evt.try_clone().unwrap(),
&seccomp_action,
hypervisor,
)
.map_err(Error::StartVmmThread)?;
let r: Result<(), Error> = (|| {
let payload_present =
cmd_arguments.contains_id("kernel") || cmd_arguments.contains_id("firmware");
if payload_present {
let vm_params = config::VmParams::from_arg_matches(&cmd_arguments);
let vm_config = config::VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?;
// Create and boot the VM based off the VM config we just built.
let sender = api_request_sender.clone();
vmm::api::vm_create(
api_evt.try_clone().unwrap(),
api_request_sender,
Arc::new(Mutex::new(vm_config)),
)
.map_err(Error::VmCreate)?;
vmm::api::vm_boot(api_evt.try_clone().unwrap(), sender).map_err(Error::VmBoot)?;
} else if let Some(restore_params) = cmd_arguments.get_one::<String>("restore") {
vmm::api::vm_restore(
api_evt.try_clone().unwrap(),
api_request_sender,
Arc::new(
config::RestoreConfig::parse(restore_params).map_err(Error::ParsingRestore)?,
),
)
.map_err(Error::VmRestore)?;
}
Ok(())
})();
if r.is_err() {
if let Err(e) = exit_evt.write(1) {
warn!("writing to exit EventFd: {e}");
}
}
vmm_thread_handle
.thread_handle
.join()
.map_err(Error::ThreadJoin)?
.map_err(Error::VmmThread)?;
#[cfg(feature = "dbus_api")]
if let Some(chs) = vmm_thread_handle.dbus_shutdown_chs {
dbus_api_graceful_shutdown(chs);
}
r.map(|_| api_socket_path)
}
fn main() {
#[cfg(all(feature = "tdx", feature = "sev_snp"))]
compile_error!("Feature 'tdx' and 'sev_snp' are mutually exclusive.");
#[cfg(all(feature = "sev_snp", not(target_arch = "x86_64")))]
compile_error!("Feature 'sev_snp' needs target 'x86_64'");
#[cfg(feature = "dhat-heap")]
let _profiler = dhat::Profiler::new_heap();
// Ensure all created files (.e.g sockets) are only accessible by this user
// SAFETY: trivially safe
let _ = unsafe { libc::umask(0o077) };
let (default_vcpus, default_memory, default_rng) = prepare_default_values();
let cmd_arguments = create_app(default_vcpus, default_memory, default_rng).get_matches();
if cmd_arguments.get_flag("version") {
println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION"));
if cmd_arguments.get_count("v") != 0 {
println!("Enabled features: {:?}", vmm::feature_list());
}
return;
}
let exit_code = match start_vmm(cmd_arguments) {
Ok(path) => {
path.map(|s| std::fs::remove_file(s).ok());
0
}
Err(e) => {
eprintln!("{e}");
1
}
};
#[cfg(feature = "dhat-heap")]
drop(_profiler);
std::process::exit(exit_code);
}
#[cfg(test)]
mod unit_tests {
use crate::config::HotplugMethod;
use crate::{create_app, prepare_default_values};
use std::path::PathBuf;
use vmm::config::{
ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, PayloadConfig,
RngConfig, VmConfig, VmParams,
};
fn get_vm_config_from_vec(args: &[&str]) -> VmConfig {
let (default_vcpus, default_memory, default_rng) = prepare_default_values();
let cmd_arguments =
create_app(default_vcpus, default_memory, default_rng).get_matches_from(args);
let vm_params = VmParams::from_arg_matches(&cmd_arguments);
VmConfig::parse(vm_params).unwrap()
}
fn compare_vm_config_cli_vs_json(
cli: &[&str],
openapi: &str,
equal: bool,
) -> (VmConfig, VmConfig) {
let cli_vm_config = get_vm_config_from_vec(cli);
let openapi_vm_config: VmConfig = serde_json::from_str(openapi).unwrap();
if equal {
assert_eq!(cli_vm_config, openapi_vm_config);
} else {
assert_ne!(cli_vm_config, openapi_vm_config);
}
(cli_vm_config, openapi_vm_config)
}
#[test]
fn test_valid_vm_config_default() {
let cli = vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"];
let openapi = r#"{ "payload": {"kernel": "/path/to/kernel"} }"#;
// First we check we get identical VmConfig structures.
let (result_vm_config, _) = compare_vm_config_cli_vs_json(&cli, openapi, true);
// As a second step, we validate all the default values.
let expected_vm_config = VmConfig {
cpus: CpusConfig {
boot_vcpus: 1,
max_vcpus: 1,
topology: None,
kvm_hyperv: false,
max_phys_bits: 46,
affinity: None,
features: CpuFeatures::default(),
},
memory: MemoryConfig {
size: 536_870_912,
mergeable: false,
hotplug_method: HotplugMethod::Acpi,
hotplug_size: None,
hotplugged_size: None,
shared: false,
hugepages: false,
hugepage_size: None,
prefault: false,
zones: None,
thp: true,
},
payload: Some(PayloadConfig {
kernel: Some(PathBuf::from("/path/to/kernel")),
..Default::default()
}),
rate_limit_groups: None,
disks: None,
net: None,
rng: RngConfig {
src: PathBuf::from("/dev/urandom"),
iommu: false,
},
balloon: None,
fs: None,
pmem: None,
serial: ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Null,
iommu: false,
socket: None,
},
console: ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Tty,
iommu: false,
socket: None,
},
devices: None,
user_devices: None,
vdpa: None,
vsock: None,
pvpanic: false,
iommu: false,
#[cfg(target_arch = "x86_64")]
sgx_epc: None,
numa: None,
watchdog: false,
#[cfg(feature = "guest_debug")]
gdb: false,
platform: None,
tpm: None,
preserved_fds: None,
};
assert_eq!(expected_vm_config, result_vm_config);
}
#[test]
fn test_valid_vm_config_cpus() {
[
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--cpus",
"boot=1",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"cpus": {"boot_vcpus": 1, "max_vcpus": 1}
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--cpus",
"boot=1,max=3",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"cpus": {"boot_vcpus": 1, "max_vcpus": 3}
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--cpus",
"boot=2,max=4",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"cpus": {"boot_vcpus": 1, "max_vcpus": 3}
}"#,
false,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_memory() {
vec![
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory": {"size": 1073741824}
}"#,
true,
),
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory": {"size": 1073741824}
}"#,
true,
),
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory": {"size": 1073741824, "mergeable": true}
}"#,
true,
),
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=off"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory": {"size": 1073741824, "mergeable": false}
}"#,
true,
),
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory": {"size": 1073741824, "mergeable": false}
}"#,
false,
),
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_size=1G"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory": {"size": 1073741824, "hotplug_method": "Acpi", "hotplug_size": 1073741824}
}"#,
true,
),
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_method=virtio-mem,hotplug_size=1G"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824}
}"#,
true,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_kernel() {
[(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
r#"{
"payload": {"kernel": "/path/to/kernel"}
}"#,
true,
)]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_cmdline() {
[(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--cmdline",
"arg1=foo arg2=bar",
],
r#"{
"payload": {"kernel": "/path/to/kernel", "cmdline": "arg1=foo arg2=bar"}
}"#,
true,
)]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_disks() {
[
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--disk",
"path=/path/to/disk/1",
"path=/path/to/disk/2",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"disks": [
{"path": "/path/to/disk/1"},
{"path": "/path/to/disk/2"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--disk",
"path=/path/to/disk/1",
"path=/path/to/disk/2",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"disks": [
{"path": "/path/to/disk/1"}
]
}"#,
false,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--memory",
"shared=true",
"--disk",
"vhost_user=true,socket=/tmp/sock1",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"disks": [
{"vhost_user":true, "vhost_socket":"/tmp/sock1"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--memory",
"shared=true",
"--disk",
"vhost_user=true,socket=/tmp/sock1",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"disks": [
{"vhost_user":true, "vhost_socket":"/tmp/sock1"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--disk",
"path=/path/to/disk/1,rate_limit_group=group0",
"path=/path/to/disk/2,rate_limit_group=group0",
"--rate-limit-group",
"id=group0,bw_size=1000,bw_refill_time=100",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"disks": [
{"path": "/path/to/disk/1", "rate_limit_group": "group0"},
{"path": "/path/to/disk/2", "rate_limit_group": "group0"}
],
"rate_limit_groups": [
{"id": "group0", "rate_limiter_config": {"bandwidth": {"size": 1000, "one_time_burst": 0, "refill_time": 100}}}
]
}"#,
true,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_net() {
vec![
// This test is expected to fail because the default MAC address is
// randomly generated. There's no way we can have twice the same
// default value.
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac="],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": []
}"#,
false,
),
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--cpus", "boot=2",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=4",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"cpus": {"boot_vcpus": 2, "max_vcpus": 2},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 4}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--cpus", "boot=2",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=4,queue_size=128",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"cpus": {"boot_vcpus": 2, "max_vcpus": 2},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 4, "queue_size": 128}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=2,queue_size=256",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 2, "queue_size": 256}
]
}"#,
true,
),
#[cfg(target_arch = "x86_64")]
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=2,queue_size=256,iommu=on",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 2, "queue_size": 256, "iommu": true}
]
}"#,
false,
),
#[cfg(target_arch = "x86_64")]
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=2,queue_size=256,iommu=on",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 2, "queue_size": 256, "iommu": true}
],
"iommu": true
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--net",
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4,mask=5.6.7.8,num_queues=2,queue_size=256,iommu=off",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4", "mask": "5.6.7.8", "num_queues": 2, "queue_size": 256, "iommu": false}
]
}"#,
true,
),
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "shared=true", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,vhost_user=true,socket=/tmp/sock"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"net": [
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "vhost_user": true, "vhost_socket": "/tmp/sock"}
]
}"#,
true,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_rng() {
[(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--rng",
"src=/path/to/entropy/source",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"rng": {"src": "/path/to/entropy/source"}
}"#,
true,
)]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_fs() {
[(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--memory", "shared=true",
"--fs",
"tag=virtiofs1,socket=/path/to/sock1",
"tag=virtiofs2,socket=/path/to/sock2",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"fs": [
{"tag": "virtiofs1", "socket": "/path/to/sock1"},
{"tag": "virtiofs2", "socket": "/path/to/sock2"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--memory", "shared=true",
"--fs",
"tag=virtiofs1,socket=/path/to/sock1",
"tag=virtiofs2,socket=/path/to/sock2",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"fs": [
{"tag": "virtiofs1", "socket": "/path/to/sock1"}
]
}"#,
false,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--memory", "shared=true", "--cpus", "boot=4",
"--fs",
"tag=virtiofs1,socket=/path/to/sock1,num_queues=4",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"cpus": {"boot_vcpus": 4, "max_vcpus": 4},
"fs": [
{"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor", "--kernel", "/path/to/kernel",
"--memory", "shared=true", "--cpus", "boot=4",
"--fs",
"tag=virtiofs1,socket=/path/to/sock1,num_queues=4,queue_size=128"
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"cpus": {"boot_vcpus": 4, "max_vcpus": 4},
"fs": [
{"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4, "queue_size": 128}
]
}"#,
true,
)]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_pmem() {
[
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--pmem",
"file=/path/to/img/1,size=1G",
"file=/path/to/img/2,size=2G",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"pmem": [
{"file": "/path/to/img/1", "size": 1073741824},
{"file": "/path/to/img/2", "size": 2147483648}
]
}"#,
true,
),
#[cfg(target_arch = "x86_64")]
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--pmem",
"file=/path/to/img/1,size=1G,iommu=on",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"pmem": [
{"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
],
"iommu": true
}"#,
true,
),
#[cfg(target_arch = "x86_64")]
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--pmem",
"file=/path/to/img/1,size=1G,iommu=on",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"pmem": [
{"file": "/path/to/img/1", "size": 1073741824, "iommu": true}
]
}"#,
false,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_serial_console() {
[
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"serial": {"mode": "Null"},
"console": {"mode": "Tty"}
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--serial",
"null",
"--console",
"tty",
],
r#"{
"payload": {"kernel": "/path/to/kernel"}
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--serial",
"tty",
"--console",
"off",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"serial": {"mode": "Tty"},
"console": {"mode": "Off"}
}"#,
true,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_serial_pty_console_pty() {
[
(
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"serial": {"mode": "Null"},
"console": {"mode": "Tty"}
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--serial",
"null",
"--console",
"tty",
],
r#"{
"payload": {"kernel": "/path/to/kernel"}
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--serial",
"pty",
"--console",
"pty",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"serial": {"mode": "Pty"},
"console": {"mode": "Pty"}
}"#,
true,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_valid_vm_config_devices() {
vec![
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--device",
"path=/path/to/device/1",
"path=/path/to/device/2",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"devices": [
{"path": "/path/to/device/1"},
{"path": "/path/to/device/2"}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--device",
"path=/path/to/device/1",
"path=/path/to/device/2",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"devices": [
{"path": "/path/to/device/1"}
]
}"#,
false,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--device",
"path=/path/to/device,iommu=on",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"devices": [
{"path": "/path/to/device", "iommu": true}
],
"iommu": true
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--device",
"path=/path/to/device,iommu=on",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"devices": [
{"path": "/path/to/device", "iommu": true}
]
}"#,
false,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--device",
"path=/path/to/device,iommu=off",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"devices": [
{"path": "/path/to/device", "iommu": false}
]
}"#,
true,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_vdpa() {
[
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--vdpa",
"path=/path/to/device/1",
"path=/path/to/device/2,num_queues=2",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"vdpa": [
{"path": "/path/to/device/1", "num_queues": 1},
{"path": "/path/to/device/2", "num_queues": 2}
]
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--vdpa",
"path=/path/to/device/1",
"path=/path/to/device/2",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"vdpa": [
{"path": "/path/to/device/1"}
]
}"#,
false,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_vsock() {
[
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--vsock",
"cid=123,socket=/path/to/sock/1",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"vsock": {"cid": 123, "socket": "/path/to/sock/1"}
}"#,
true,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--vsock",
"cid=124,socket=/path/to/sock/1",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"vsock": {"cid": 123, "socket": "/path/to/sock/1"}
}"#,
false,
),
#[cfg(target_arch = "x86_64")]
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--vsock",
"cid=123,socket=/path/to/sock/1,iommu=on",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true},
"iommu": true
}"#,
true,
),
#[cfg(target_arch = "x86_64")]
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--vsock",
"cid=123,socket=/path/to/sock/1,iommu=on",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true}
}"#,
false,
),
(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--vsock",
"cid=123,socket=/path/to/sock/1,iommu=off",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": false}
}"#,
true,
),
]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
#[test]
fn test_valid_vm_config_tpm_socket() {
[(
vec![
"cloud-hypervisor",
"--kernel",
"/path/to/kernel",
"--tpm",
"socket=/path/to/tpm/sock",
],
r#"{
"payload": {"kernel": "/path/to/kernel"},
"tpm": {"socket": "/path/to/tpm/sock"}
}"#,
true,
)]
.iter()
.for_each(|(cli, openapi, equal)| {
compare_vm_config_cli_vs_json(cli, openapi, *equal);
});
}
}