1 // Copyright © 2019 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 #[macro_use] 7 extern crate event_monitor; 8 9 use argh::FromArgs; 10 use libc::EFD_NONBLOCK; 11 use log::{warn, LevelFilter}; 12 use option_parser::OptionParser; 13 use seccompiler::SeccompAction; 14 use signal_hook::consts::SIGSYS; 15 use std::env; 16 use std::fs::File; 17 use std::os::unix::io::{FromRawFd, RawFd}; 18 use std::sync::mpsc::channel; 19 use std::sync::{Arc, Mutex}; 20 use thiserror::Error; 21 #[cfg(feature = "dbus_api")] 22 use vmm::api::dbus::{dbus_api_graceful_shutdown, DBusApiOptions}; 23 use vmm::config; 24 use vmm_sys_util::eventfd::EventFd; 25 use vmm_sys_util::signal::block_signal; 26 27 #[cfg(feature = "dhat-heap")] 28 #[global_allocator] 29 static ALLOC: dhat::Alloc = dhat::Alloc; 30 31 #[derive(Error, Debug)] 32 enum Error { 33 #[error("Failed to create API EventFd: {0}")] 34 CreateApiEventFd(#[source] std::io::Error), 35 #[cfg(feature = "guest_debug")] 36 #[error("Failed to create Debug EventFd: {0}")] 37 CreateDebugEventFd(#[source] std::io::Error), 38 #[error("Failed to create exit EventFd: {0}")] 39 CreateExitEventFd(#[source] std::io::Error), 40 #[error("Failed to open hypervisor interface (is hypervisor interface available?): {0}")] 41 CreateHypervisor(#[source] hypervisor::HypervisorError), 42 #[error("Failed to start the VMM thread: {0}")] 43 StartVmmThread(#[source] vmm::Error), 44 #[error("Error parsing config: {0}")] 45 ParsingConfig(vmm::config::Error), 46 #[error("Error creating VM: {0:?}")] 47 VmCreate(vmm::api::ApiError), 48 #[error("Error booting VM: {0:?}")] 49 VmBoot(vmm::api::ApiError), 50 #[error("Error restoring VM: {0:?}")] 51 VmRestore(vmm::api::ApiError), 52 #[error("Error parsing restore: {0}")] 53 ParsingRestore(vmm::config::Error), 54 #[error("Failed to join on VMM thread: {0:?}")] 55 ThreadJoin(std::boxed::Box<dyn std::any::Any + std::marker::Send>), 56 #[error("VMM thread exited with error: {0}")] 57 VmmThread(#[source] vmm::Error), 58 #[error("Error parsing --api-socket: {0}")] 59 ParsingApiSocket(std::num::ParseIntError), 60 #[error("Error parsing --event-monitor: {0}")] 61 ParsingEventMonitor(option_parser::OptionParserError), 62 #[cfg(feature = "dbus_api")] 63 #[error("`--dbus-object-path` option isn't provided")] 64 MissingDBusObjectPath, 65 #[cfg(feature = "dbus_api")] 66 #[error("`--dbus-service-name` option isn't provided")] 67 MissingDBusServiceName, 68 #[error("Error parsing --event-monitor: path or fd required")] 69 BareEventMonitor, 70 #[error("Error doing event monitor I/O: {0}")] 71 EventMonitorIo(std::io::Error), 72 #[error("Event monitor thread failed: {0}")] 73 EventMonitorThread(#[source] vmm::Error), 74 #[cfg(feature = "guest_debug")] 75 #[error("Error parsing --gdb: {0}")] 76 ParsingGdb(option_parser::OptionParserError), 77 #[cfg(feature = "guest_debug")] 78 #[error("Error parsing --gdb: path required")] 79 BareGdb, 80 #[error("Error creating log file: {0}")] 81 LogFileCreation(std::io::Error), 82 #[error("Error setting up logger: {0}")] 83 LoggerSetup(log::SetLoggerError), 84 } 85 86 struct Logger { 87 output: Mutex<Box<dyn std::io::Write + Send>>, 88 start: std::time::Instant, 89 } 90 91 impl log::Log for Logger { 92 fn enabled(&self, _metadata: &log::Metadata) -> bool { 93 true 94 } 95 96 fn log(&self, record: &log::Record) { 97 if !self.enabled(record.metadata()) { 98 return; 99 } 100 101 let now = std::time::Instant::now(); 102 let duration = now.duration_since(self.start); 103 104 if record.file().is_some() && record.line().is_some() { 105 write!( 106 *(*(self.output.lock().unwrap())), 107 "cloud-hypervisor: {:.6?}: <{}> {}:{}:{} -- {}\r\n", 108 duration, 109 std::thread::current().name().unwrap_or("anonymous"), 110 record.level(), 111 record.file().unwrap(), 112 record.line().unwrap(), 113 record.args() 114 ) 115 } else { 116 write!( 117 *(*(self.output.lock().unwrap())), 118 "cloud-hypervisor: {:.6?}: <{}> {}:{} -- {}\r\n", 119 duration, 120 std::thread::current().name().unwrap_or("anonymous"), 121 record.level(), 122 record.target(), 123 record.args() 124 ) 125 } 126 .ok(); 127 } 128 fn flush(&self) {} 129 } 130 131 fn default_vcpus() -> String { 132 format!( 133 "boot={},max_phys_bits={}", 134 config::DEFAULT_VCPUS, 135 config::DEFAULT_MAX_PHYS_BITS 136 ) 137 } 138 139 fn default_memory() -> String { 140 format!("size={}M", config::DEFAULT_MEMORY_MB) 141 } 142 143 fn default_rng() -> String { 144 format!("src={}", config::DEFAULT_RNG_SOURCE) 145 } 146 147 #[derive(FromArgs)] 148 /// Launch a cloud-hypervisor VMM. 149 pub struct TopLevel { 150 #[argh(option, long = "cpus", default = "default_vcpus()")] 151 /// 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> 152 cpus: String, 153 154 #[argh(option, long = "platform")] 155 /// 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> 156 platform: Option<String>, 157 158 #[argh(option, long = "memory", default = "default_memory()")] 159 /// 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 160 memory: String, 161 162 #[argh(option, long = "memory-zone")] 163 /// 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 164 memory_zone: Vec<String>, 165 166 #[argh(option, long = "firmware")] 167 /// path to firmware that is loaded in an architectural specific way 168 firmware: Option<String>, 169 170 #[argh(option, long = "kernel")] 171 /// path to kernel or firmware that supports a PVH entry point or architecture equivalent 172 kernel: Option<String>, 173 174 #[argh(option, long = "initramfs")] 175 /// path to initramfs image 176 initramfs: Option<String>, 177 178 #[argh(option, long = "cmdline")] 179 /// kernel command line 180 cmdline: Option<String>, 181 182 #[argh(option, long = "disk")] 183 /// path=<disk_image_path>, readonly=on|off, direct=on|off, iommu=on|off, num_queues=<number_of_queues>, queue_size=<size_of_each_queue>, vhost_user=on|off, socket=<vhost_user_socket_path>, bw_size=<bytes>, bw_one_time_burst=<bytes>, bw_refill_time=<ms>, ops_size=<io_ops>, ops_one_time_burst=<io_ops>, ops_refill_time=<ms>, id=<device_id>, pci_segment=<segment_id> 184 disk: Vec<String>, 185 186 #[argh(option, long = "net")] 187 /// tap=<if_name>, ip=<ip_addr>, mask=<net_mask>, mac=<mac_addr>, fd=<fd1,fd2...>, iommu=on|off, num_queues=<number_of_queues>, queue_size=<size_of_each_queue>, id=<device_id>, vhost_user=<vhost_user_enable>, socket=<vhost_user_socket_path>, vhost_mode=client|server, bw_size=<bytes>, bw_one_time_burst=<bytes>, bw_refill_time=<ms>, ops_size=<io_ops>, ops_one_time_burst=<io_ops>, ops_refill_time=<ms>, pci_segment=<segment_id>, offload_tso=on|off, offload_ufo=on|off, offload_csum=on|off 188 net: Vec<String>, 189 190 #[argh(option, long = "rng", default = "default_rng()")] 191 /// src=<entropy_source_path>, iommu=on|off 192 rng: String, 193 194 #[argh(option, long = "balloon")] 195 /// size=<balloon_size>, deflate_on_oom=on|off, free_page_reporting=on|off 196 balloon: Option<String>, 197 198 #[argh(option, long = "fs")] 199 /// tag=<tag_name>, socket=<socket_path>, num_queues=<number_of_queues>, queue_size=<size_of_each_queue>, id=<device_id>, pci_segment=<segment_id> 200 fs: Vec<String>, 201 202 #[argh(option, long = "pmem")] 203 /// file=<backing_file_path>, size=<persistent_memory_size>, iommu=on|off, discard_writes=on|off, id=<device_id>, pci_segment=<segment_id> 204 pmem: Vec<String>, 205 206 #[argh(option, long = "serial", default = "String::from(\"null\")")] 207 /// off|null|pty|tty|file=/path/to/a/file 208 serial: String, 209 210 #[argh(option, long = "console", default = "String::from(\"tty\")")] 211 /// off|null|pty|tty|file=/path/to/a/file, iommu=on|off 212 console: String, 213 214 #[argh(option, long = "device")] 215 /// path=<device_path>, iommu=on|off, id=<device_id>, pci_segment=<segment_id> 216 device: Vec<String>, 217 218 #[argh(option, long = "user-device")] 219 /// socket=<socket_path>, id=<device_id>, pci_segment=<segment_id> 220 user_device: Vec<String>, 221 222 #[argh(option, long = "vdpa")] 223 /// path=<device_path>, num_queues=<number_of_queues>, iommu=on|off, id=<device_id>, pci_segment=<segment_id> 224 vdpa: Vec<String>, 225 226 #[argh(option, long = "vsock")] 227 /// cid=<context_id>, socket=<socket_path>, iommu=on|off, id=<device_id>, pci_segment=<segment_id> 228 vsock: Option<String>, 229 230 #[argh(switch, long = "pvpanic")] 231 /// enable pvpanic device 232 pvpanic: bool, 233 234 #[argh(option, long = "numa")] 235 /// guest_numa_id=<node_id>, cpus=<cpus_id>, distances=<list_of_distances_to_destination_nodes>, memory_zones=<list_of_memory_zones>, sgx_epc_sections=<list_of_sgx_epc_sections> 236 numa: Vec<String>, 237 238 #[argh(switch, long = "watchdog")] 239 /// enable virtio-watchdog 240 watchdog: bool, 241 242 #[argh(switch, short = 'v')] 243 /// set the level of debugging output 244 verbosity: u8, 245 246 #[argh(option, long = "log-file")] 247 /// path to log file 248 log_file: Option<String>, 249 250 #[argh(option, long = "api-socket")] 251 /// path=<path/to/a/file>|fd=<fd> 252 api_socket: Option<String>, 253 254 #[cfg(feature = "dbus_api")] 255 #[argh(option, long = "dbus-service-name")] 256 /// well known name of the service 257 dbus_name: Option<String>, 258 259 #[cfg(feature = "dbus_api")] 260 #[argh(option, long = "dbus-object-path")] 261 /// object path to serve the dbus interface 262 dbus_path: Option<String>, 263 264 #[cfg(feature = "dbus_api")] 265 #[argh(switch, long = "dbus-system-bus")] 266 /// use the system bus instead of a session bus 267 dbus_system_bus: bool, 268 269 #[argh(option, long = "event-monitor")] 270 /// path=<path/to/a/file>|fd=<fd> 271 event_monitor: Option<String>, 272 273 #[argh(option, long = "restore")] 274 /// source_url=<source_url>, prefault=on|off 275 restore: Option<String>, 276 277 #[argh(option, long = "seccomp", default = "String::from(\"true\")")] 278 /// seccomp configuration (true, false or log) 279 seccomp: String, 280 281 #[argh(option, long = "tpm")] 282 /// socket=<path/to/a/socket> 283 tpm: Option<String>, 284 285 #[cfg(target_arch = "x86_64")] 286 #[argh(option, long = "sgx-epc")] 287 /// id=<epc_section_identifier>, size=<epc_section_size>, prefault=on|off 288 sgx_epc: Vec<String>, 289 290 #[cfg(feature = "guest_debug")] 291 #[argh(option, long = "gdb")] 292 /// path=<path/to/a/file> 293 gdb: Option<String>, 294 295 #[argh(switch, short = 'V', long = "version")] 296 /// print version information 297 version: bool, 298 } 299 300 impl TopLevel { 301 fn to_vm_params(&self) -> config::VmParams<'_> { 302 let cpus = &self.cpus; 303 let memory = &self.memory; 304 let memory_zones = if !self.memory_zone.is_empty() { 305 Some(self.memory_zone.iter().map(|x| x.as_str()).collect()) 306 } else { 307 None 308 }; 309 let rng = &self.rng; 310 let serial = &self.serial; 311 let firmware = self.firmware.as_deref(); 312 let kernel = self.kernel.as_deref(); 313 let initramfs = self.initramfs.as_deref(); 314 let cmdline = self.cmdline.as_deref(); 315 316 let disks = if !self.disk.is_empty() { 317 Some(self.disk.iter().map(|x| x.as_str()).collect()) 318 } else { 319 None 320 }; 321 322 let net = if !self.net.is_empty() { 323 Some(self.net.iter().map(|x| x.as_str()).collect()) 324 } else { 325 None 326 }; 327 328 let console = &self.console; 329 let balloon = self.balloon.as_deref(); 330 let fs = if !self.fs.is_empty() { 331 Some(self.fs.iter().map(|x| x.as_str()).collect()) 332 } else { 333 None 334 }; 335 336 let pmem = if !self.pmem.is_empty() { 337 Some(self.pmem.iter().map(|x| x.as_str()).collect()) 338 } else { 339 None 340 }; 341 let devices = if !self.device.is_empty() { 342 Some(self.device.iter().map(|x| x.as_str()).collect()) 343 } else { 344 None 345 }; 346 let user_devices = if !self.user_device.is_empty() { 347 Some(self.user_device.iter().map(|x| x.as_str()).collect()) 348 } else { 349 None 350 }; 351 let vdpa = if !self.vdpa.is_empty() { 352 Some(self.vdpa.iter().map(|x| x.as_str()).collect()) 353 } else { 354 None 355 }; 356 357 let vsock = self.vsock.as_deref(); 358 359 let pvpanic = self.pvpanic; 360 361 #[cfg(target_arch = "x86_64")] 362 let sgx_epc = if !self.sgx_epc.is_empty() { 363 Some(self.sgx_epc.iter().map(|x| x.as_str()).collect()) 364 } else { 365 None 366 }; 367 368 let numa = if !self.numa.is_empty() { 369 Some(self.numa.iter().map(|x| x.as_str()).collect()) 370 } else { 371 None 372 }; 373 let watchdog = self.watchdog; 374 let platform = self.platform.as_deref(); 375 #[cfg(feature = "guest_debug")] 376 let gdb = self.gdb.is_some(); 377 let tpm = self.tpm.as_deref(); 378 379 config::VmParams { 380 cpus, 381 memory, 382 memory_zones, 383 firmware, 384 kernel, 385 initramfs, 386 cmdline, 387 disks, 388 net, 389 rng, 390 balloon, 391 fs, 392 pmem, 393 serial, 394 console, 395 devices, 396 user_devices, 397 vdpa, 398 vsock, 399 pvpanic, 400 #[cfg(target_arch = "x86_64")] 401 sgx_epc, 402 numa, 403 watchdog, 404 #[cfg(feature = "guest_debug")] 405 gdb, 406 platform, 407 tpm, 408 } 409 } 410 } 411 412 fn start_vmm(toplevel: TopLevel) -> Result<Option<String>, Error> { 413 let log_level = match toplevel.verbosity { 414 0 => LevelFilter::Warn, 415 1 => LevelFilter::Info, 416 2 => LevelFilter::Debug, 417 _ => LevelFilter::Trace, 418 }; 419 420 let log_file: Box<dyn std::io::Write + Send> = if let Some(ref file) = toplevel.log_file { 421 Box::new(std::fs::File::create(std::path::Path::new(file)).map_err(Error::LogFileCreation)?) 422 } else { 423 Box::new(std::io::stderr()) 424 }; 425 426 log::set_boxed_logger(Box::new(Logger { 427 output: Mutex::new(log_file), 428 start: std::time::Instant::now(), 429 })) 430 .map(|()| log::set_max_level(log_level)) 431 .map_err(Error::LoggerSetup)?; 432 433 let (api_socket_path, api_socket_fd) = if let Some(ref socket_config) = toplevel.api_socket { 434 let mut parser = OptionParser::new(); 435 parser.add("path").add("fd"); 436 parser.parse(socket_config).unwrap_or_default(); 437 438 if let Some(fd) = parser.get("fd") { 439 ( 440 None, 441 Some(fd.parse::<RawFd>().map_err(Error::ParsingApiSocket)?), 442 ) 443 } else if let Some(path) = parser.get("path") { 444 (Some(path), None) 445 } else { 446 (toplevel.api_socket.as_ref().map(|s| s.to_string()), None) 447 } 448 } else { 449 (None, None) 450 }; 451 452 let (api_request_sender, api_request_receiver) = channel(); 453 let api_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateApiEventFd)?; 454 455 let api_request_sender_clone = api_request_sender.clone(); 456 let seccomp_action = match &toplevel.seccomp as &str { 457 "true" => SeccompAction::Trap, 458 "false" => SeccompAction::Allow, 459 "log" => SeccompAction::Log, 460 val => { 461 // The user providing an invalid value will be rejected 462 panic!("Invalid parameter {val} for \"--seccomp\" flag"); 463 } 464 }; 465 466 if seccomp_action == SeccompAction::Trap { 467 // SAFETY: We only using signal_hook for managing signals and only execute signal 468 // handler safe functions (writing to stderr) and manipulating signals. 469 unsafe { 470 signal_hook::low_level::register(signal_hook::consts::SIGSYS, || { 471 eprint!( 472 "\n==== Possible seccomp violation ====\n\ 473 Try running with `strace -ff` to identify the cause and open an issue: \ 474 https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new\n" 475 ); 476 signal_hook::low_level::emulate_default_handler(SIGSYS).unwrap(); 477 }) 478 } 479 .map_err(|e| eprintln!("Error adding SIGSYS signal handler: {e}")) 480 .ok(); 481 } 482 483 // Before we start any threads, mask the signals we'll be 484 // installing handlers for, to make sure they only ever run on the 485 // dedicated signal handling thread we'll start in a bit. 486 for sig in &vmm::vm::Vm::HANDLED_SIGNALS { 487 if let Err(e) = block_signal(*sig) { 488 eprintln!("Error blocking signals: {e}"); 489 } 490 } 491 492 for sig in &vmm::Vmm::HANDLED_SIGNALS { 493 if let Err(e) = block_signal(*sig) { 494 eprintln!("Error blocking signals: {e}"); 495 } 496 } 497 498 let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?; 499 500 #[cfg(feature = "guest_debug")] 501 let gdb_socket_path = if let Some(ref gdb_config) = toplevel.gdb { 502 let mut parser = OptionParser::new(); 503 parser.add("path"); 504 parser.parse(gdb_config).map_err(Error::ParsingGdb)?; 505 506 if parser.is_set("path") { 507 Some(std::path::PathBuf::from(parser.get("path").unwrap())) 508 } else { 509 return Err(Error::BareGdb); 510 } 511 } else { 512 None 513 }; 514 #[cfg(feature = "guest_debug")] 515 let debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?; 516 #[cfg(feature = "guest_debug")] 517 let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?; 518 519 let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?; 520 521 #[allow(unused_mut)] 522 let mut event_monitor = toplevel 523 .event_monitor 524 .as_ref() 525 .map(|monitor_config| { 526 let mut parser = OptionParser::new(); 527 parser.add("path").add("fd"); 528 parser 529 .parse(monitor_config) 530 .map_err(Error::ParsingEventMonitor)?; 531 532 if parser.is_set("fd") { 533 let fd = parser 534 .convert("fd") 535 .map_err(Error::ParsingEventMonitor)? 536 .unwrap(); 537 // SAFETY: fd is valid 538 Ok(Some(unsafe { File::from_raw_fd(fd) })) 539 } else if parser.is_set("path") { 540 Ok(Some( 541 std::fs::OpenOptions::new() 542 .write(true) 543 .create(true) 544 .open(parser.get("path").unwrap()) 545 .map_err(Error::EventMonitorIo)?, 546 )) 547 } else { 548 Err(Error::BareEventMonitor) 549 } 550 }) 551 .transpose()? 552 .map(|event_monitor_file| { 553 event_monitor::set_monitor(event_monitor_file).map_err(Error::EventMonitorIo) 554 }) 555 .transpose()?; 556 557 #[cfg(feature = "dbus_api")] 558 let dbus_options = match (&toplevel.dbus_name, &toplevel.dbus_path) { 559 (Some(ref name), Some(ref path)) => { 560 // monitor is either set (file based) or not. 561 // if it's not set, create one without file support. 562 let mut monitor = match event_monitor.take() { 563 Some(monitor) => monitor, 564 None => event_monitor::set_monitor(None).map_err(Error::EventMonitorIo)?, 565 }; 566 let options = DBusApiOptions { 567 service_name: name.to_owned(), 568 object_path: path.to_owned(), 569 system_bus: toplevel.dbus_system_bus, 570 event_monitor_rx: monitor.subscribe(), 571 }; 572 573 event_monitor = Some(monitor); 574 Ok(Some(options)) 575 } 576 (Some(_), None) => Err(Error::MissingDBusObjectPath), 577 (None, Some(_)) => Err(Error::MissingDBusServiceName), 578 (None, None) => Ok(None), 579 }?; 580 581 if let Some(monitor) = event_monitor { 582 vmm::start_event_monitor_thread( 583 monitor, 584 &seccomp_action, 585 hypervisor.hypervisor_type(), 586 exit_evt.try_clone().unwrap(), 587 ) 588 .map_err(Error::EventMonitorThread)?; 589 } 590 591 event!("vmm", "starting"); 592 593 let vmm_thread_handle = vmm::start_vmm_thread( 594 vmm::VmmVersionInfo::new(env!("BUILD_VERSION"), env!("CARGO_PKG_VERSION")), 595 &api_socket_path, 596 api_socket_fd, 597 #[cfg(feature = "dbus_api")] 598 dbus_options, 599 api_evt.try_clone().unwrap(), 600 api_request_sender_clone, 601 api_request_receiver, 602 #[cfg(feature = "guest_debug")] 603 gdb_socket_path, 604 #[cfg(feature = "guest_debug")] 605 debug_evt.try_clone().unwrap(), 606 #[cfg(feature = "guest_debug")] 607 vm_debug_evt.try_clone().unwrap(), 608 exit_evt.try_clone().unwrap(), 609 &seccomp_action, 610 hypervisor, 611 ) 612 .map_err(Error::StartVmmThread)?; 613 614 let r: Result<(), Error> = (|| { 615 let payload_present = toplevel.kernel.is_some() || toplevel.firmware.is_some(); 616 617 if payload_present { 618 let vm_params = toplevel.to_vm_params(); 619 let vm_config = config::VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?; 620 621 // Create and boot the VM based off the VM config we just built. 622 let sender = api_request_sender.clone(); 623 vmm::api::vm_create( 624 api_evt.try_clone().unwrap(), 625 api_request_sender, 626 Arc::new(Mutex::new(vm_config)), 627 ) 628 .map_err(Error::VmCreate)?; 629 vmm::api::vm_boot(api_evt.try_clone().unwrap(), sender).map_err(Error::VmBoot)?; 630 } else if let Some(restore_params) = toplevel.restore { 631 vmm::api::vm_restore( 632 api_evt.try_clone().unwrap(), 633 api_request_sender, 634 Arc::new( 635 config::RestoreConfig::parse(&restore_params).map_err(Error::ParsingRestore)?, 636 ), 637 ) 638 .map_err(Error::VmRestore)?; 639 } 640 641 Ok(()) 642 })(); 643 644 if r.is_err() { 645 if let Err(e) = exit_evt.write(1) { 646 warn!("writing to exit EventFd: {e}"); 647 } 648 } 649 650 vmm_thread_handle 651 .thread_handle 652 .join() 653 .map_err(Error::ThreadJoin)? 654 .map_err(Error::VmmThread)?; 655 656 #[cfg(feature = "dbus_api")] 657 if let Some(chs) = vmm_thread_handle.dbus_shutdown_chs { 658 dbus_api_graceful_shutdown(chs); 659 } 660 661 r.map(|_| api_socket_path) 662 } 663 664 fn main() { 665 #[cfg(all(feature = "tdx", feature = "sev_snp"))] 666 compile_error!("Feature 'tdx' and 'sev_snp' are mutually exclusive."); 667 668 #[cfg(feature = "dhat-heap")] 669 let _profiler = dhat::Profiler::new_heap(); 670 671 // Ensure all created files (.e.g sockets) are only accessible by this user 672 // SAFETY: trivially safe 673 let _ = unsafe { libc::umask(0o077) }; 674 675 let toplevel: TopLevel = argh::from_env(); 676 677 if toplevel.version { 678 println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION")); 679 680 if toplevel.verbosity != 0 { 681 println!("Enabled features: {:?}", vmm::feature_list()); 682 } 683 684 return; 685 } 686 687 let exit_code = match start_vmm(toplevel) { 688 Ok(path) => { 689 path.map(|s| std::fs::remove_file(s).ok()); 690 0 691 } 692 Err(e) => { 693 eprintln!("{e}"); 694 1 695 } 696 }; 697 698 #[cfg(feature = "dhat-heap")] 699 drop(_profiler); 700 701 std::process::exit(exit_code); 702 } 703 704 #[cfg(test)] 705 mod unit_tests { 706 use crate::config::HotplugMethod; 707 use crate::TopLevel; 708 use std::path::PathBuf; 709 use vmm::config::{ 710 ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, PayloadConfig, 711 RngConfig, VmConfig, 712 }; 713 714 // Taken from argh 715 fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str { 716 std::path::Path::new(path) 717 .file_name() 718 .and_then(|s| s.to_str()) 719 .unwrap_or(default) 720 } 721 722 // Some code taken from argh since it does not provide a helper to parse arbitrary strings 723 fn get_vm_config_from_vec(args: &[&str]) -> VmConfig { 724 let strings: Vec<String> = args.iter().map(|x| x.to_string()).collect(); 725 let cmd = cmd(&strings[0], &strings[0]); 726 let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect(); 727 let toplevel = <TopLevel as argh::FromArgs>::from_args(&[cmd], &strs[1..]).unwrap_or_else( 728 |early_exit| { 729 std::process::exit(match early_exit.status { 730 Ok(()) => { 731 println!("{}", early_exit.output); 732 0 733 } 734 Err(()) => { 735 eprintln!( 736 "{}\nRun {} --help for more information.", 737 early_exit.output, cmd 738 ); 739 1 740 } 741 }) 742 }, 743 ); 744 745 let vm_params = toplevel.to_vm_params(); 746 747 VmConfig::parse(vm_params).unwrap() 748 } 749 750 fn compare_vm_config_cli_vs_json( 751 cli: &[&str], 752 openapi: &str, 753 equal: bool, 754 ) -> (VmConfig, VmConfig) { 755 let cli_vm_config = get_vm_config_from_vec(cli); 756 let openapi_vm_config: VmConfig = serde_json::from_str(openapi).unwrap(); 757 758 if equal { 759 assert_eq!(cli_vm_config, openapi_vm_config); 760 } else { 761 assert_ne!(cli_vm_config, openapi_vm_config); 762 } 763 764 (cli_vm_config, openapi_vm_config) 765 } 766 767 #[test] 768 fn test_valid_vm_config_default() { 769 let cli = vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"]; 770 let openapi = r#"{ "payload": {"kernel": "/path/to/kernel"} }"#; 771 772 // First we check we get identical VmConfig structures. 773 let (result_vm_config, _) = compare_vm_config_cli_vs_json(&cli, openapi, true); 774 775 // As a second step, we validate all the default values. 776 let expected_vm_config = VmConfig { 777 cpus: CpusConfig { 778 boot_vcpus: 1, 779 max_vcpus: 1, 780 topology: None, 781 kvm_hyperv: false, 782 max_phys_bits: 46, 783 affinity: None, 784 features: CpuFeatures::default(), 785 }, 786 memory: MemoryConfig { 787 size: 536_870_912, 788 mergeable: false, 789 hotplug_method: HotplugMethod::Acpi, 790 hotplug_size: None, 791 hotplugged_size: None, 792 shared: false, 793 hugepages: false, 794 hugepage_size: None, 795 prefault: false, 796 zones: None, 797 thp: true, 798 }, 799 payload: Some(PayloadConfig { 800 kernel: Some(PathBuf::from("/path/to/kernel")), 801 ..Default::default() 802 }), 803 disks: None, 804 net: None, 805 rng: RngConfig { 806 src: PathBuf::from("/dev/urandom"), 807 iommu: false, 808 }, 809 balloon: None, 810 fs: None, 811 pmem: None, 812 serial: ConsoleConfig { 813 file: None, 814 mode: ConsoleOutputMode::Null, 815 iommu: false, 816 socket: None, 817 }, 818 console: ConsoleConfig { 819 file: None, 820 mode: ConsoleOutputMode::Tty, 821 iommu: false, 822 socket: None, 823 }, 824 devices: None, 825 user_devices: None, 826 vdpa: None, 827 vsock: None, 828 pvpanic: false, 829 iommu: false, 830 #[cfg(target_arch = "x86_64")] 831 sgx_epc: None, 832 numa: None, 833 watchdog: false, 834 #[cfg(feature = "guest_debug")] 835 gdb: false, 836 platform: None, 837 tpm: None, 838 preserved_fds: None, 839 }; 840 841 assert_eq!(expected_vm_config, result_vm_config); 842 } 843 844 #[test] 845 fn test_valid_vm_config_cpus() { 846 [ 847 ( 848 vec![ 849 "cloud-hypervisor", 850 "--kernel", 851 "/path/to/kernel", 852 "--cpus", 853 "boot=1", 854 ], 855 r#"{ 856 "payload": {"kernel": "/path/to/kernel"}, 857 "cpus": {"boot_vcpus": 1, "max_vcpus": 1} 858 }"#, 859 true, 860 ), 861 ( 862 vec![ 863 "cloud-hypervisor", 864 "--kernel", 865 "/path/to/kernel", 866 "--cpus", 867 "boot=1,max=3", 868 ], 869 r#"{ 870 "payload": {"kernel": "/path/to/kernel"}, 871 "cpus": {"boot_vcpus": 1, "max_vcpus": 3} 872 }"#, 873 true, 874 ), 875 ( 876 vec![ 877 "cloud-hypervisor", 878 "--kernel", 879 "/path/to/kernel", 880 "--cpus", 881 "boot=2,max=4", 882 ], 883 r#"{ 884 "payload": {"kernel": "/path/to/kernel"}, 885 "cpus": {"boot_vcpus": 1, "max_vcpus": 3} 886 }"#, 887 false, 888 ), 889 ] 890 .iter() 891 .for_each(|(cli, openapi, equal)| { 892 compare_vm_config_cli_vs_json(cli, openapi, *equal); 893 }); 894 } 895 896 #[test] 897 fn test_valid_vm_config_memory() { 898 vec![ 899 ( 900 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"], 901 r#"{ 902 "payload": {"kernel": "/path/to/kernel"}, 903 "memory": {"size": 1073741824} 904 }"#, 905 true, 906 ), 907 ( 908 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G"], 909 r#"{ 910 "payload": {"kernel": "/path/to/kernel"}, 911 "memory": {"size": 1073741824} 912 }"#, 913 true, 914 ), 915 ( 916 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"], 917 r#"{ 918 "payload": {"kernel": "/path/to/kernel"}, 919 "memory": {"size": 1073741824, "mergeable": true} 920 }"#, 921 true, 922 ), 923 ( 924 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=off"], 925 r#"{ 926 "payload": {"kernel": "/path/to/kernel"}, 927 "memory": {"size": 1073741824, "mergeable": false} 928 }"#, 929 true, 930 ), 931 ( 932 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"], 933 r#"{ 934 "payload": {"kernel": "/path/to/kernel"}, 935 "memory": {"size": 1073741824, "mergeable": false} 936 }"#, 937 false, 938 ), 939 ( 940 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_size=1G"], 941 r#"{ 942 "payload": {"kernel": "/path/to/kernel"}, 943 "memory": {"size": 1073741824, "hotplug_method": "Acpi", "hotplug_size": 1073741824} 944 }"#, 945 true, 946 ), 947 ( 948 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_method=virtio-mem,hotplug_size=1G"], 949 r#"{ 950 "payload": {"kernel": "/path/to/kernel"}, 951 "memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824} 952 }"#, 953 true, 954 ), 955 ] 956 .iter() 957 .for_each(|(cli, openapi, equal)| { 958 compare_vm_config_cli_vs_json(cli, openapi, *equal); 959 }); 960 } 961 962 #[test] 963 fn test_valid_vm_config_kernel() { 964 [( 965 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 966 r#"{ 967 "payload": {"kernel": "/path/to/kernel"} 968 }"#, 969 true, 970 )] 971 .iter() 972 .for_each(|(cli, openapi, equal)| { 973 compare_vm_config_cli_vs_json(cli, openapi, *equal); 974 }); 975 } 976 977 #[test] 978 fn test_valid_vm_config_cmdline() { 979 [( 980 vec![ 981 "cloud-hypervisor", 982 "--kernel", 983 "/path/to/kernel", 984 "--cmdline", 985 "arg1=foo arg2=bar", 986 ], 987 r#"{ 988 "payload": {"kernel": "/path/to/kernel", "cmdline": "arg1=foo arg2=bar"} 989 }"#, 990 true, 991 )] 992 .iter() 993 .for_each(|(cli, openapi, equal)| { 994 compare_vm_config_cli_vs_json(cli, openapi, *equal); 995 }); 996 } 997 998 #[test] 999 fn test_valid_vm_config_disks() { 1000 [ 1001 ( 1002 vec![ 1003 "cloud-hypervisor", 1004 "--kernel", 1005 "/path/to/kernel", 1006 "--disk", 1007 "path=/path/to/disk/1", 1008 "--disk", 1009 "path=/path/to/disk/2", 1010 ], 1011 r#"{ 1012 "payload": {"kernel": "/path/to/kernel"}, 1013 "disks": [ 1014 {"path": "/path/to/disk/1"}, 1015 {"path": "/path/to/disk/2"} 1016 ] 1017 }"#, 1018 true, 1019 ), 1020 ( 1021 vec![ 1022 "cloud-hypervisor", 1023 "--kernel", 1024 "/path/to/kernel", 1025 "--disk", 1026 "path=/path/to/disk/1", 1027 "--disk", 1028 "path=/path/to/disk/2", 1029 ], 1030 r#"{ 1031 "payload": {"kernel": "/path/to/kernel"}, 1032 "disks": [ 1033 {"path": "/path/to/disk/1"} 1034 ] 1035 }"#, 1036 false, 1037 ), 1038 ( 1039 vec![ 1040 "cloud-hypervisor", 1041 "--kernel", 1042 "/path/to/kernel", 1043 "--memory", 1044 "shared=true", 1045 "--disk", 1046 "vhost_user=true,socket=/tmp/sock1", 1047 ], 1048 r#"{ 1049 "payload": {"kernel": "/path/to/kernel"}, 1050 "memory" : { "shared": true, "size": 536870912 }, 1051 "disks": [ 1052 {"vhost_user":true, "vhost_socket":"/tmp/sock1"} 1053 ] 1054 }"#, 1055 true, 1056 ), 1057 ( 1058 vec![ 1059 "cloud-hypervisor", 1060 "--kernel", 1061 "/path/to/kernel", 1062 "--memory", 1063 "shared=true", 1064 "--disk", 1065 "vhost_user=true,socket=/tmp/sock1", 1066 ], 1067 r#"{ 1068 "payload": {"kernel": "/path/to/kernel"}, 1069 "memory" : { "shared": true, "size": 536870912 }, 1070 "disks": [ 1071 {"vhost_user":true, "vhost_socket":"/tmp/sock1"} 1072 ] 1073 }"#, 1074 true, 1075 ), 1076 ] 1077 .iter() 1078 .for_each(|(cli, openapi, equal)| { 1079 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1080 }); 1081 } 1082 1083 #[test] 1084 fn test_valid_vm_config_net() { 1085 vec![ 1086 // This test is expected to fail because the default MAC address is 1087 // randomly generated. There's no way we can have twice the same 1088 // default value. 1089 ( 1090 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac="], 1091 r#"{ 1092 "payload": {"kernel": "/path/to/kernel"}, 1093 "net": [] 1094 }"#, 1095 false, 1096 ), 1097 ( 1098 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd"], 1099 r#"{ 1100 "payload": {"kernel": "/path/to/kernel"}, 1101 "net": [ 1102 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd"} 1103 ] 1104 }"#, 1105 true, 1106 ), 1107 ( 1108 vec![ 1109 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1110 "--net", 1111 "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0", 1112 ], 1113 r#"{ 1114 "payload": {"kernel": "/path/to/kernel"}, 1115 "net": [ 1116 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0"} 1117 ] 1118 }"#, 1119 true, 1120 ), 1121 ( 1122 vec![ 1123 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1124 "--net", 1125 "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4", 1126 ], 1127 r#"{ 1128 "payload": {"kernel": "/path/to/kernel"}, 1129 "net": [ 1130 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"} 1131 ] 1132 }"#, 1133 true, 1134 ), 1135 ( 1136 vec![ 1137 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1138 "--net", 1139 "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", 1140 ], 1141 r#"{ 1142 "payload": {"kernel": "/path/to/kernel"}, 1143 "net": [ 1144 {"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"} 1145 ] 1146 }"#, 1147 true, 1148 ), 1149 ( 1150 vec![ 1151 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1152 "--cpus", "boot=2", 1153 "--net", 1154 "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", 1155 ], 1156 r#"{ 1157 "payload": {"kernel": "/path/to/kernel"}, 1158 "cpus": {"boot_vcpus": 2, "max_vcpus": 2}, 1159 "net": [ 1160 {"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} 1161 ] 1162 }"#, 1163 true, 1164 ), 1165 ( 1166 vec![ 1167 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1168 "--cpus", "boot=2", 1169 "--net", 1170 "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", 1171 ], 1172 r#"{ 1173 "payload": {"kernel": "/path/to/kernel"}, 1174 "cpus": {"boot_vcpus": 2, "max_vcpus": 2}, 1175 "net": [ 1176 {"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} 1177 ] 1178 }"#, 1179 true, 1180 ), 1181 ( 1182 vec![ 1183 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1184 "--net", 1185 "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", 1186 ], 1187 r#"{ 1188 "payload": {"kernel": "/path/to/kernel"}, 1189 "net": [ 1190 {"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"} 1191 ] 1192 }"#, 1193 true, 1194 ), 1195 ( 1196 vec![ 1197 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1198 "--net", 1199 "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", 1200 ], 1201 r#"{ 1202 "payload": {"kernel": "/path/to/kernel"}, 1203 "net": [ 1204 {"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} 1205 ] 1206 }"#, 1207 true, 1208 ), 1209 #[cfg(target_arch = "x86_64")] 1210 ( 1211 vec![ 1212 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1213 "--net", 1214 "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", 1215 ], 1216 r#"{ 1217 "payload": {"kernel": "/path/to/kernel"}, 1218 "net": [ 1219 {"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} 1220 ] 1221 }"#, 1222 false, 1223 ), 1224 #[cfg(target_arch = "x86_64")] 1225 ( 1226 vec![ 1227 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1228 "--net", 1229 "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", 1230 ], 1231 r#"{ 1232 "payload": {"kernel": "/path/to/kernel"}, 1233 "net": [ 1234 {"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} 1235 ], 1236 "iommu": true 1237 }"#, 1238 true, 1239 ), 1240 ( 1241 vec![ 1242 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1243 "--net", 1244 "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", 1245 ], 1246 r#"{ 1247 "payload": {"kernel": "/path/to/kernel"}, 1248 "net": [ 1249 {"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} 1250 ] 1251 }"#, 1252 true, 1253 ), 1254 ( 1255 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"], 1256 r#"{ 1257 "payload": {"kernel": "/path/to/kernel"}, 1258 "memory" : { "shared": true, "size": 536870912 }, 1259 "net": [ 1260 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "vhost_user": true, "vhost_socket": "/tmp/sock"} 1261 ] 1262 }"#, 1263 true, 1264 ), 1265 ] 1266 .iter() 1267 .for_each(|(cli, openapi, equal)| { 1268 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1269 }); 1270 } 1271 1272 #[test] 1273 fn test_valid_vm_config_rng() { 1274 [( 1275 vec![ 1276 "cloud-hypervisor", 1277 "--kernel", 1278 "/path/to/kernel", 1279 "--rng", 1280 "src=/path/to/entropy/source", 1281 ], 1282 r#"{ 1283 "payload": {"kernel": "/path/to/kernel"}, 1284 "rng": {"src": "/path/to/entropy/source"} 1285 }"#, 1286 true, 1287 )] 1288 .iter() 1289 .for_each(|(cli, openapi, equal)| { 1290 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1291 }); 1292 } 1293 1294 #[test] 1295 fn test_valid_vm_config_fs() { 1296 [( 1297 vec![ 1298 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1299 "--memory", "shared=true", 1300 "--fs", 1301 "tag=virtiofs1,socket=/path/to/sock1", 1302 "--fs", 1303 "tag=virtiofs2,socket=/path/to/sock2", 1304 ], 1305 r#"{ 1306 "payload": {"kernel": "/path/to/kernel"}, 1307 "memory" : { "shared": true, "size": 536870912 }, 1308 "fs": [ 1309 {"tag": "virtiofs1", "socket": "/path/to/sock1"}, 1310 {"tag": "virtiofs2", "socket": "/path/to/sock2"} 1311 ] 1312 }"#, 1313 true, 1314 ), 1315 ( 1316 vec![ 1317 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1318 "--memory", "shared=true", 1319 "--fs", 1320 "tag=virtiofs1,socket=/path/to/sock1", 1321 "--fs", 1322 "tag=virtiofs2,socket=/path/to/sock2", 1323 ], 1324 r#"{ 1325 "payload": {"kernel": "/path/to/kernel"}, 1326 "memory" : { "shared": true, "size": 536870912 }, 1327 "fs": [ 1328 {"tag": "virtiofs1", "socket": "/path/to/sock1"} 1329 ] 1330 }"#, 1331 false, 1332 ), 1333 ( 1334 vec![ 1335 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1336 "--memory", "shared=true", "--cpus", "boot=4", 1337 "--fs", 1338 "tag=virtiofs1,socket=/path/to/sock1,num_queues=4", 1339 ], 1340 r#"{ 1341 "payload": {"kernel": "/path/to/kernel"}, 1342 "memory" : { "shared": true, "size": 536870912 }, 1343 "cpus": {"boot_vcpus": 4, "max_vcpus": 4}, 1344 "fs": [ 1345 {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4} 1346 ] 1347 }"#, 1348 true, 1349 ), 1350 ( 1351 vec![ 1352 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1353 "--memory", "shared=true", "--cpus", "boot=4", 1354 "--fs", 1355 "tag=virtiofs1,socket=/path/to/sock1,num_queues=4,queue_size=128" 1356 ], 1357 r#"{ 1358 "payload": {"kernel": "/path/to/kernel"}, 1359 "memory" : { "shared": true, "size": 536870912 }, 1360 "cpus": {"boot_vcpus": 4, "max_vcpus": 4}, 1361 "fs": [ 1362 {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4, "queue_size": 128} 1363 ] 1364 }"#, 1365 true, 1366 )] 1367 .iter() 1368 .for_each(|(cli, openapi, equal)| { 1369 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1370 }); 1371 } 1372 1373 #[test] 1374 fn test_valid_vm_config_pmem() { 1375 [ 1376 ( 1377 vec![ 1378 "cloud-hypervisor", 1379 "--kernel", 1380 "/path/to/kernel", 1381 "--pmem", 1382 "file=/path/to/img/1,size=1G", 1383 "--pmem", 1384 "file=/path/to/img/2,size=2G", 1385 ], 1386 r#"{ 1387 "payload": {"kernel": "/path/to/kernel"}, 1388 "pmem": [ 1389 {"file": "/path/to/img/1", "size": 1073741824}, 1390 {"file": "/path/to/img/2", "size": 2147483648} 1391 ] 1392 }"#, 1393 true, 1394 ), 1395 #[cfg(target_arch = "x86_64")] 1396 ( 1397 vec![ 1398 "cloud-hypervisor", 1399 "--kernel", 1400 "/path/to/kernel", 1401 "--pmem", 1402 "file=/path/to/img/1,size=1G,iommu=on", 1403 ], 1404 r#"{ 1405 "payload": {"kernel": "/path/to/kernel"}, 1406 "pmem": [ 1407 {"file": "/path/to/img/1", "size": 1073741824, "iommu": true} 1408 ], 1409 "iommu": true 1410 }"#, 1411 true, 1412 ), 1413 #[cfg(target_arch = "x86_64")] 1414 ( 1415 vec![ 1416 "cloud-hypervisor", 1417 "--kernel", 1418 "/path/to/kernel", 1419 "--pmem", 1420 "file=/path/to/img/1,size=1G,iommu=on", 1421 ], 1422 r#"{ 1423 "payload": {"kernel": "/path/to/kernel"}, 1424 "pmem": [ 1425 {"file": "/path/to/img/1", "size": 1073741824, "iommu": true} 1426 ] 1427 }"#, 1428 false, 1429 ), 1430 ] 1431 .iter() 1432 .for_each(|(cli, openapi, equal)| { 1433 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1434 }); 1435 } 1436 1437 #[test] 1438 fn test_valid_vm_config_serial_console() { 1439 [ 1440 ( 1441 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 1442 r#"{ 1443 "payload": {"kernel": "/path/to/kernel"}, 1444 "serial": {"mode": "Null"}, 1445 "console": {"mode": "Tty"} 1446 }"#, 1447 true, 1448 ), 1449 ( 1450 vec![ 1451 "cloud-hypervisor", 1452 "--kernel", 1453 "/path/to/kernel", 1454 "--serial", 1455 "null", 1456 "--console", 1457 "tty", 1458 ], 1459 r#"{ 1460 "payload": {"kernel": "/path/to/kernel"} 1461 }"#, 1462 true, 1463 ), 1464 ( 1465 vec![ 1466 "cloud-hypervisor", 1467 "--kernel", 1468 "/path/to/kernel", 1469 "--serial", 1470 "tty", 1471 "--console", 1472 "off", 1473 ], 1474 r#"{ 1475 "payload": {"kernel": "/path/to/kernel"}, 1476 "serial": {"mode": "Tty"}, 1477 "console": {"mode": "Off"} 1478 }"#, 1479 true, 1480 ), 1481 ] 1482 .iter() 1483 .for_each(|(cli, openapi, equal)| { 1484 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1485 }); 1486 } 1487 1488 #[test] 1489 fn test_valid_vm_config_serial_pty_console_pty() { 1490 [ 1491 ( 1492 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 1493 r#"{ 1494 "payload": {"kernel": "/path/to/kernel"}, 1495 "serial": {"mode": "Null"}, 1496 "console": {"mode": "Tty"} 1497 }"#, 1498 true, 1499 ), 1500 ( 1501 vec![ 1502 "cloud-hypervisor", 1503 "--kernel", 1504 "/path/to/kernel", 1505 "--serial", 1506 "null", 1507 "--console", 1508 "tty", 1509 ], 1510 r#"{ 1511 "payload": {"kernel": "/path/to/kernel"} 1512 }"#, 1513 true, 1514 ), 1515 ( 1516 vec![ 1517 "cloud-hypervisor", 1518 "--kernel", 1519 "/path/to/kernel", 1520 "--serial", 1521 "pty", 1522 "--console", 1523 "pty", 1524 ], 1525 r#"{ 1526 "payload": {"kernel": "/path/to/kernel"}, 1527 "serial": {"mode": "Pty"}, 1528 "console": {"mode": "Pty"} 1529 }"#, 1530 true, 1531 ), 1532 ] 1533 .iter() 1534 .for_each(|(cli, openapi, equal)| { 1535 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1536 }); 1537 } 1538 1539 #[test] 1540 #[cfg(target_arch = "x86_64")] 1541 fn test_valid_vm_config_devices() { 1542 vec![ 1543 ( 1544 vec![ 1545 "cloud-hypervisor", 1546 "--kernel", 1547 "/path/to/kernel", 1548 "--device", 1549 "path=/path/to/device/1", 1550 "--device", 1551 "path=/path/to/device/2", 1552 ], 1553 r#"{ 1554 "payload": {"kernel": "/path/to/kernel"}, 1555 "devices": [ 1556 {"path": "/path/to/device/1"}, 1557 {"path": "/path/to/device/2"} 1558 ] 1559 }"#, 1560 true, 1561 ), 1562 ( 1563 vec![ 1564 "cloud-hypervisor", 1565 "--kernel", 1566 "/path/to/kernel", 1567 "--device", 1568 "path=/path/to/device/1", 1569 "--device", 1570 "path=/path/to/device/2", 1571 ], 1572 r#"{ 1573 "payload": {"kernel": "/path/to/kernel"}, 1574 "devices": [ 1575 {"path": "/path/to/device/1"} 1576 ] 1577 }"#, 1578 false, 1579 ), 1580 ( 1581 vec![ 1582 "cloud-hypervisor", 1583 "--kernel", 1584 "/path/to/kernel", 1585 "--device", 1586 "path=/path/to/device,iommu=on", 1587 ], 1588 r#"{ 1589 "payload": {"kernel": "/path/to/kernel"}, 1590 "devices": [ 1591 {"path": "/path/to/device", "iommu": true} 1592 ], 1593 "iommu": true 1594 }"#, 1595 true, 1596 ), 1597 ( 1598 vec![ 1599 "cloud-hypervisor", 1600 "--kernel", 1601 "/path/to/kernel", 1602 "--device", 1603 "path=/path/to/device,iommu=on", 1604 ], 1605 r#"{ 1606 "payload": {"kernel": "/path/to/kernel"}, 1607 "devices": [ 1608 {"path": "/path/to/device", "iommu": true} 1609 ] 1610 }"#, 1611 false, 1612 ), 1613 ( 1614 vec![ 1615 "cloud-hypervisor", 1616 "--kernel", 1617 "/path/to/kernel", 1618 "--device", 1619 "path=/path/to/device,iommu=off", 1620 ], 1621 r#"{ 1622 "payload": {"kernel": "/path/to/kernel"}, 1623 "devices": [ 1624 {"path": "/path/to/device", "iommu": false} 1625 ] 1626 }"#, 1627 true, 1628 ), 1629 ] 1630 .iter() 1631 .for_each(|(cli, openapi, equal)| { 1632 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1633 }); 1634 } 1635 1636 #[test] 1637 fn test_valid_vm_config_vdpa() { 1638 [ 1639 ( 1640 vec![ 1641 "cloud-hypervisor", 1642 "--kernel", 1643 "/path/to/kernel", 1644 "--vdpa", 1645 "path=/path/to/device/1", 1646 "--vdpa", 1647 "path=/path/to/device/2,num_queues=2", 1648 ], 1649 r#"{ 1650 "payload": {"kernel": "/path/to/kernel"}, 1651 "vdpa": [ 1652 {"path": "/path/to/device/1", "num_queues": 1}, 1653 {"path": "/path/to/device/2", "num_queues": 2} 1654 ] 1655 }"#, 1656 true, 1657 ), 1658 ( 1659 vec![ 1660 "cloud-hypervisor", 1661 "--kernel", 1662 "/path/to/kernel", 1663 "--vdpa", 1664 "path=/path/to/device/1", 1665 "--vdpa", 1666 "path=/path/to/device/2", 1667 ], 1668 r#"{ 1669 "payload": {"kernel": "/path/to/kernel"}, 1670 "vdpa": [ 1671 {"path": "/path/to/device/1"} 1672 ] 1673 }"#, 1674 false, 1675 ), 1676 ] 1677 .iter() 1678 .for_each(|(cli, openapi, equal)| { 1679 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1680 }); 1681 } 1682 1683 #[test] 1684 fn test_valid_vm_config_vsock() { 1685 [ 1686 ( 1687 vec![ 1688 "cloud-hypervisor", 1689 "--kernel", 1690 "/path/to/kernel", 1691 "--vsock", 1692 "cid=123,socket=/path/to/sock/1", 1693 ], 1694 r#"{ 1695 "payload": {"kernel": "/path/to/kernel"}, 1696 "vsock": {"cid": 123, "socket": "/path/to/sock/1"} 1697 }"#, 1698 true, 1699 ), 1700 ( 1701 vec![ 1702 "cloud-hypervisor", 1703 "--kernel", 1704 "/path/to/kernel", 1705 "--vsock", 1706 "cid=124,socket=/path/to/sock/1", 1707 ], 1708 r#"{ 1709 "payload": {"kernel": "/path/to/kernel"}, 1710 "vsock": {"cid": 123, "socket": "/path/to/sock/1"} 1711 }"#, 1712 false, 1713 ), 1714 #[cfg(target_arch = "x86_64")] 1715 ( 1716 vec![ 1717 "cloud-hypervisor", 1718 "--kernel", 1719 "/path/to/kernel", 1720 "--vsock", 1721 "cid=123,socket=/path/to/sock/1,iommu=on", 1722 ], 1723 r#"{ 1724 "payload": {"kernel": "/path/to/kernel"}, 1725 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true}, 1726 "iommu": true 1727 }"#, 1728 true, 1729 ), 1730 #[cfg(target_arch = "x86_64")] 1731 ( 1732 vec![ 1733 "cloud-hypervisor", 1734 "--kernel", 1735 "/path/to/kernel", 1736 "--vsock", 1737 "cid=123,socket=/path/to/sock/1,iommu=on", 1738 ], 1739 r#"{ 1740 "payload": {"kernel": "/path/to/kernel"}, 1741 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true} 1742 }"#, 1743 false, 1744 ), 1745 ( 1746 vec![ 1747 "cloud-hypervisor", 1748 "--kernel", 1749 "/path/to/kernel", 1750 "--vsock", 1751 "cid=123,socket=/path/to/sock/1,iommu=off", 1752 ], 1753 r#"{ 1754 "payload": {"kernel": "/path/to/kernel"}, 1755 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": false} 1756 }"#, 1757 true, 1758 ), 1759 ] 1760 .iter() 1761 .for_each(|(cli, openapi, equal)| { 1762 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1763 }); 1764 } 1765 1766 #[test] 1767 fn test_valid_vm_config_tpm_socket() { 1768 [( 1769 vec![ 1770 "cloud-hypervisor", 1771 "--kernel", 1772 "/path/to/kernel", 1773 "--tpm", 1774 "socket=/path/to/tpm/sock", 1775 ], 1776 r#"{ 1777 "payload": {"kernel": "/path/to/kernel"}, 1778 "tpm": {"socket": "/path/to/tpm/sock"} 1779 }"#, 1780 true, 1781 )] 1782 .iter() 1783 .for_each(|(cli, openapi, equal)| { 1784 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1785 }); 1786 } 1787 } 1788