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 #[cfg(feature = "dbus_api")] 453 let dbus_options = match (&toplevel.dbus_name, &toplevel.dbus_path) { 454 (Some(ref name), Some(ref path)) => Ok(Some(DBusApiOptions { 455 service_name: name.to_owned(), 456 object_path: path.to_owned(), 457 system_bus: toplevel.dbus_system_bus, 458 })), 459 (Some(_), None) => Err(Error::MissingDBusObjectPath), 460 (None, Some(_)) => Err(Error::MissingDBusServiceName), 461 (None, None) => Ok(None), 462 }?; 463 464 let (api_request_sender, api_request_receiver) = channel(); 465 let api_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateApiEventFd)?; 466 467 let api_request_sender_clone = api_request_sender.clone(); 468 let seccomp_action = match &toplevel.seccomp as &str { 469 "true" => SeccompAction::Trap, 470 "false" => SeccompAction::Allow, 471 "log" => SeccompAction::Log, 472 val => { 473 // The user providing an invalid value will be rejected 474 panic!("Invalid parameter {val} for \"--seccomp\" flag"); 475 } 476 }; 477 478 if seccomp_action == SeccompAction::Trap { 479 // SAFETY: We only using signal_hook for managing signals and only execute signal 480 // handler safe functions (writing to stderr) and manipulating signals. 481 unsafe { 482 signal_hook::low_level::register(signal_hook::consts::SIGSYS, || { 483 eprint!( 484 "\n==== Possible seccomp violation ====\n\ 485 Try running with `strace -ff` to identify the cause and open an issue: \ 486 https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new\n" 487 ); 488 signal_hook::low_level::emulate_default_handler(SIGSYS).unwrap(); 489 }) 490 } 491 .map_err(|e| eprintln!("Error adding SIGSYS signal handler: {e}")) 492 .ok(); 493 } 494 495 // Before we start any threads, mask the signals we'll be 496 // installing handlers for, to make sure they only ever run on the 497 // dedicated signal handling thread we'll start in a bit. 498 for sig in &vmm::vm::Vm::HANDLED_SIGNALS { 499 if let Err(e) = block_signal(*sig) { 500 eprintln!("Error blocking signals: {e}"); 501 } 502 } 503 504 for sig in &vmm::Vmm::HANDLED_SIGNALS { 505 if let Err(e) = block_signal(*sig) { 506 eprintln!("Error blocking signals: {e}"); 507 } 508 } 509 510 let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?; 511 512 #[cfg(feature = "guest_debug")] 513 let gdb_socket_path = if let Some(ref gdb_config) = toplevel.gdb { 514 let mut parser = OptionParser::new(); 515 parser.add("path"); 516 parser.parse(gdb_config).map_err(Error::ParsingGdb)?; 517 518 if parser.is_set("path") { 519 Some(std::path::PathBuf::from(parser.get("path").unwrap())) 520 } else { 521 return Err(Error::BareGdb); 522 } 523 } else { 524 None 525 }; 526 #[cfg(feature = "guest_debug")] 527 let debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?; 528 #[cfg(feature = "guest_debug")] 529 let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?; 530 531 let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?; 532 533 if let Some(ref monitor_config) = toplevel.event_monitor { 534 let mut parser = OptionParser::new(); 535 parser.add("path").add("fd"); 536 parser 537 .parse(monitor_config) 538 .map_err(Error::ParsingEventMonitor)?; 539 540 let file = if parser.is_set("fd") { 541 let fd = parser 542 .convert("fd") 543 .map_err(Error::ParsingEventMonitor)? 544 .unwrap(); 545 // SAFETY: fd is valid 546 unsafe { File::from_raw_fd(fd) } 547 } else if parser.is_set("path") { 548 std::fs::OpenOptions::new() 549 .write(true) 550 .create(true) 551 .open(parser.get("path").unwrap()) 552 .map_err(Error::EventMonitorIo)? 553 } else { 554 return Err(Error::BareEventMonitor); 555 }; 556 557 let monitor = event_monitor::set_monitor(file).map_err(Error::EventMonitorIo)?; 558 vmm::start_event_monitor_thread( 559 monitor, 560 &seccomp_action, 561 hypervisor.hypervisor_type(), 562 exit_evt.try_clone().unwrap(), 563 ) 564 .map_err(Error::EventMonitorThread)?; 565 } 566 567 event!("vmm", "starting"); 568 569 let vmm_thread_handle = vmm::start_vmm_thread( 570 vmm::VmmVersionInfo::new(env!("BUILD_VERSION"), env!("CARGO_PKG_VERSION")), 571 &api_socket_path, 572 api_socket_fd, 573 #[cfg(feature = "dbus_api")] 574 dbus_options, 575 api_evt.try_clone().unwrap(), 576 api_request_sender_clone, 577 api_request_receiver, 578 #[cfg(feature = "guest_debug")] 579 gdb_socket_path, 580 #[cfg(feature = "guest_debug")] 581 debug_evt.try_clone().unwrap(), 582 #[cfg(feature = "guest_debug")] 583 vm_debug_evt.try_clone().unwrap(), 584 exit_evt.try_clone().unwrap(), 585 &seccomp_action, 586 hypervisor, 587 ) 588 .map_err(Error::StartVmmThread)?; 589 590 let r: Result<(), Error> = (|| { 591 let payload_present = toplevel.kernel.is_some() || toplevel.firmware.is_some(); 592 593 if payload_present { 594 let vm_params = toplevel.to_vm_params(); 595 let vm_config = config::VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?; 596 597 // Create and boot the VM based off the VM config we just built. 598 let sender = api_request_sender.clone(); 599 vmm::api::vm_create( 600 api_evt.try_clone().unwrap(), 601 api_request_sender, 602 Arc::new(Mutex::new(vm_config)), 603 ) 604 .map_err(Error::VmCreate)?; 605 vmm::api::vm_boot(api_evt.try_clone().unwrap(), sender).map_err(Error::VmBoot)?; 606 } else if let Some(restore_params) = toplevel.restore { 607 vmm::api::vm_restore( 608 api_evt.try_clone().unwrap(), 609 api_request_sender, 610 Arc::new( 611 config::RestoreConfig::parse(&restore_params).map_err(Error::ParsingRestore)?, 612 ), 613 ) 614 .map_err(Error::VmRestore)?; 615 } 616 617 Ok(()) 618 })(); 619 620 if r.is_err() { 621 if let Err(e) = exit_evt.write(1) { 622 warn!("writing to exit EventFd: {e}"); 623 } 624 } 625 626 vmm_thread_handle 627 .thread_handle 628 .join() 629 .map_err(Error::ThreadJoin)? 630 .map_err(Error::VmmThread)?; 631 632 #[cfg(feature = "dbus_api")] 633 if let Some(chs) = vmm_thread_handle.dbus_shutdown_chs { 634 dbus_api_graceful_shutdown(chs); 635 } 636 637 r.map(|_| api_socket_path) 638 } 639 640 fn main() { 641 #[cfg(feature = "dhat-heap")] 642 let _profiler = dhat::Profiler::new_heap(); 643 644 // Ensure all created files (.e.g sockets) are only accessible by this user 645 // SAFETY: trivially safe 646 let _ = unsafe { libc::umask(0o077) }; 647 648 let toplevel: TopLevel = argh::from_env(); 649 650 if toplevel.version { 651 println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION")); 652 return; 653 } 654 655 let exit_code = match start_vmm(toplevel) { 656 Ok(path) => { 657 path.map(|s| std::fs::remove_file(s).ok()); 658 0 659 } 660 Err(e) => { 661 eprintln!("{e}"); 662 1 663 } 664 }; 665 666 #[cfg(feature = "dhat-heap")] 667 drop(_profiler); 668 669 std::process::exit(exit_code); 670 } 671 672 #[cfg(test)] 673 mod unit_tests { 674 use crate::config::HotplugMethod; 675 use crate::TopLevel; 676 use std::path::PathBuf; 677 use vmm::config::{ 678 ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, PayloadConfig, 679 RngConfig, VmConfig, 680 }; 681 682 // Taken from argh 683 fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str { 684 std::path::Path::new(path) 685 .file_name() 686 .and_then(|s| s.to_str()) 687 .unwrap_or(default) 688 } 689 690 // Some code taken from argh since it does not provide a helper to parse arbitrary strings 691 fn get_vm_config_from_vec(args: &[&str]) -> VmConfig { 692 let strings: Vec<String> = args.iter().map(|x| x.to_string()).collect(); 693 let cmd = cmd(&strings[0], &strings[0]); 694 let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect(); 695 let toplevel = <TopLevel as argh::FromArgs>::from_args(&[cmd], &strs[1..]).unwrap_or_else( 696 |early_exit| { 697 std::process::exit(match early_exit.status { 698 Ok(()) => { 699 println!("{}", early_exit.output); 700 0 701 } 702 Err(()) => { 703 eprintln!( 704 "{}\nRun {} --help for more information.", 705 early_exit.output, cmd 706 ); 707 1 708 } 709 }) 710 }, 711 ); 712 713 let vm_params = toplevel.to_vm_params(); 714 715 VmConfig::parse(vm_params).unwrap() 716 } 717 718 fn compare_vm_config_cli_vs_json( 719 cli: &[&str], 720 openapi: &str, 721 equal: bool, 722 ) -> (VmConfig, VmConfig) { 723 let cli_vm_config = get_vm_config_from_vec(cli); 724 let openapi_vm_config: VmConfig = serde_json::from_str(openapi).unwrap(); 725 726 if equal { 727 assert_eq!(cli_vm_config, openapi_vm_config); 728 } else { 729 assert_ne!(cli_vm_config, openapi_vm_config); 730 } 731 732 (cli_vm_config, openapi_vm_config) 733 } 734 735 #[test] 736 fn test_valid_vm_config_default() { 737 let cli = vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"]; 738 let openapi = r#"{ "payload": {"kernel": "/path/to/kernel"} }"#; 739 740 // First we check we get identical VmConfig structures. 741 let (result_vm_config, _) = compare_vm_config_cli_vs_json(&cli, openapi, true); 742 743 // As a second step, we validate all the default values. 744 let expected_vm_config = VmConfig { 745 cpus: CpusConfig { 746 boot_vcpus: 1, 747 max_vcpus: 1, 748 topology: None, 749 kvm_hyperv: false, 750 max_phys_bits: 46, 751 affinity: None, 752 features: CpuFeatures::default(), 753 }, 754 memory: MemoryConfig { 755 size: 536_870_912, 756 mergeable: false, 757 hotplug_method: HotplugMethod::Acpi, 758 hotplug_size: None, 759 hotplugged_size: None, 760 shared: false, 761 hugepages: false, 762 hugepage_size: None, 763 prefault: false, 764 zones: None, 765 thp: true, 766 }, 767 payload: Some(PayloadConfig { 768 kernel: Some(PathBuf::from("/path/to/kernel")), 769 ..Default::default() 770 }), 771 disks: None, 772 net: None, 773 rng: RngConfig { 774 src: PathBuf::from("/dev/urandom"), 775 iommu: false, 776 }, 777 balloon: None, 778 fs: None, 779 pmem: None, 780 serial: ConsoleConfig { 781 file: None, 782 mode: ConsoleOutputMode::Null, 783 iommu: false, 784 }, 785 console: ConsoleConfig { 786 file: None, 787 mode: ConsoleOutputMode::Tty, 788 iommu: false, 789 }, 790 devices: None, 791 user_devices: None, 792 vdpa: None, 793 vsock: None, 794 pvpanic: false, 795 iommu: false, 796 #[cfg(target_arch = "x86_64")] 797 sgx_epc: None, 798 numa: None, 799 watchdog: false, 800 #[cfg(feature = "guest_debug")] 801 gdb: false, 802 platform: None, 803 tpm: None, 804 preserved_fds: None, 805 }; 806 807 assert_eq!(expected_vm_config, result_vm_config); 808 } 809 810 #[test] 811 fn test_valid_vm_config_cpus() { 812 [ 813 ( 814 vec![ 815 "cloud-hypervisor", 816 "--kernel", 817 "/path/to/kernel", 818 "--cpus", 819 "boot=1", 820 ], 821 r#"{ 822 "payload": {"kernel": "/path/to/kernel"}, 823 "cpus": {"boot_vcpus": 1, "max_vcpus": 1} 824 }"#, 825 true, 826 ), 827 ( 828 vec![ 829 "cloud-hypervisor", 830 "--kernel", 831 "/path/to/kernel", 832 "--cpus", 833 "boot=1,max=3", 834 ], 835 r#"{ 836 "payload": {"kernel": "/path/to/kernel"}, 837 "cpus": {"boot_vcpus": 1, "max_vcpus": 3} 838 }"#, 839 true, 840 ), 841 ( 842 vec![ 843 "cloud-hypervisor", 844 "--kernel", 845 "/path/to/kernel", 846 "--cpus", 847 "boot=2,max=4", 848 ], 849 r#"{ 850 "payload": {"kernel": "/path/to/kernel"}, 851 "cpus": {"boot_vcpus": 1, "max_vcpus": 3} 852 }"#, 853 false, 854 ), 855 ] 856 .iter() 857 .for_each(|(cli, openapi, equal)| { 858 compare_vm_config_cli_vs_json(cli, openapi, *equal); 859 }); 860 } 861 862 #[test] 863 fn test_valid_vm_config_memory() { 864 vec![ 865 ( 866 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"], 867 r#"{ 868 "payload": {"kernel": "/path/to/kernel"}, 869 "memory": {"size": 1073741824} 870 }"#, 871 true, 872 ), 873 ( 874 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G"], 875 r#"{ 876 "payload": {"kernel": "/path/to/kernel"}, 877 "memory": {"size": 1073741824} 878 }"#, 879 true, 880 ), 881 ( 882 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"], 883 r#"{ 884 "payload": {"kernel": "/path/to/kernel"}, 885 "memory": {"size": 1073741824, "mergeable": true} 886 }"#, 887 true, 888 ), 889 ( 890 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=off"], 891 r#"{ 892 "payload": {"kernel": "/path/to/kernel"}, 893 "memory": {"size": 1073741824, "mergeable": false} 894 }"#, 895 true, 896 ), 897 ( 898 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"], 899 r#"{ 900 "payload": {"kernel": "/path/to/kernel"}, 901 "memory": {"size": 1073741824, "mergeable": false} 902 }"#, 903 false, 904 ), 905 ( 906 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_size=1G"], 907 r#"{ 908 "payload": {"kernel": "/path/to/kernel"}, 909 "memory": {"size": 1073741824, "hotplug_method": "Acpi", "hotplug_size": 1073741824} 910 }"#, 911 true, 912 ), 913 ( 914 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_method=virtio-mem,hotplug_size=1G"], 915 r#"{ 916 "payload": {"kernel": "/path/to/kernel"}, 917 "memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824} 918 }"#, 919 true, 920 ), 921 ] 922 .iter() 923 .for_each(|(cli, openapi, equal)| { 924 compare_vm_config_cli_vs_json(cli, openapi, *equal); 925 }); 926 } 927 928 #[test] 929 fn test_valid_vm_config_kernel() { 930 [( 931 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 932 r#"{ 933 "payload": {"kernel": "/path/to/kernel"} 934 }"#, 935 true, 936 )] 937 .iter() 938 .for_each(|(cli, openapi, equal)| { 939 compare_vm_config_cli_vs_json(cli, openapi, *equal); 940 }); 941 } 942 943 #[test] 944 fn test_valid_vm_config_cmdline() { 945 [( 946 vec![ 947 "cloud-hypervisor", 948 "--kernel", 949 "/path/to/kernel", 950 "--cmdline", 951 "arg1=foo arg2=bar", 952 ], 953 r#"{ 954 "payload": {"kernel": "/path/to/kernel", "cmdline": "arg1=foo arg2=bar"} 955 }"#, 956 true, 957 )] 958 .iter() 959 .for_each(|(cli, openapi, equal)| { 960 compare_vm_config_cli_vs_json(cli, openapi, *equal); 961 }); 962 } 963 964 #[test] 965 fn test_valid_vm_config_disks() { 966 [ 967 ( 968 vec![ 969 "cloud-hypervisor", 970 "--kernel", 971 "/path/to/kernel", 972 "--disk", 973 "path=/path/to/disk/1", 974 "--disk", 975 "path=/path/to/disk/2", 976 ], 977 r#"{ 978 "payload": {"kernel": "/path/to/kernel"}, 979 "disks": [ 980 {"path": "/path/to/disk/1"}, 981 {"path": "/path/to/disk/2"} 982 ] 983 }"#, 984 true, 985 ), 986 ( 987 vec![ 988 "cloud-hypervisor", 989 "--kernel", 990 "/path/to/kernel", 991 "--disk", 992 "path=/path/to/disk/1", 993 "--disk", 994 "path=/path/to/disk/2", 995 ], 996 r#"{ 997 "payload": {"kernel": "/path/to/kernel"}, 998 "disks": [ 999 {"path": "/path/to/disk/1"} 1000 ] 1001 }"#, 1002 false, 1003 ), 1004 ( 1005 vec![ 1006 "cloud-hypervisor", 1007 "--kernel", 1008 "/path/to/kernel", 1009 "--memory", 1010 "shared=true", 1011 "--disk", 1012 "vhost_user=true,socket=/tmp/sock1", 1013 ], 1014 r#"{ 1015 "payload": {"kernel": "/path/to/kernel"}, 1016 "memory" : { "shared": true, "size": 536870912 }, 1017 "disks": [ 1018 {"vhost_user":true, "vhost_socket":"/tmp/sock1"} 1019 ] 1020 }"#, 1021 true, 1022 ), 1023 ( 1024 vec![ 1025 "cloud-hypervisor", 1026 "--kernel", 1027 "/path/to/kernel", 1028 "--memory", 1029 "shared=true", 1030 "--disk", 1031 "vhost_user=true,socket=/tmp/sock1", 1032 ], 1033 r#"{ 1034 "payload": {"kernel": "/path/to/kernel"}, 1035 "memory" : { "shared": true, "size": 536870912 }, 1036 "disks": [ 1037 {"vhost_user":true, "vhost_socket":"/tmp/sock1"} 1038 ] 1039 }"#, 1040 true, 1041 ), 1042 ] 1043 .iter() 1044 .for_each(|(cli, openapi, equal)| { 1045 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1046 }); 1047 } 1048 1049 #[test] 1050 fn test_valid_vm_config_net() { 1051 vec![ 1052 // This test is expected to fail because the default MAC address is 1053 // randomly generated. There's no way we can have twice the same 1054 // default value. 1055 ( 1056 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac="], 1057 r#"{ 1058 "payload": {"kernel": "/path/to/kernel"}, 1059 "net": [] 1060 }"#, 1061 false, 1062 ), 1063 ( 1064 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd"], 1065 r#"{ 1066 "payload": {"kernel": "/path/to/kernel"}, 1067 "net": [ 1068 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd"} 1069 ] 1070 }"#, 1071 true, 1072 ), 1073 ( 1074 vec![ 1075 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1076 "--net", 1077 "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0", 1078 ], 1079 r#"{ 1080 "payload": {"kernel": "/path/to/kernel"}, 1081 "net": [ 1082 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0"} 1083 ] 1084 }"#, 1085 true, 1086 ), 1087 ( 1088 vec![ 1089 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1090 "--net", 1091 "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4", 1092 ], 1093 r#"{ 1094 "payload": {"kernel": "/path/to/kernel"}, 1095 "net": [ 1096 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"} 1097 ] 1098 }"#, 1099 true, 1100 ), 1101 ( 1102 vec![ 1103 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1104 "--net", 1105 "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", 1106 ], 1107 r#"{ 1108 "payload": {"kernel": "/path/to/kernel"}, 1109 "net": [ 1110 {"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"} 1111 ] 1112 }"#, 1113 true, 1114 ), 1115 ( 1116 vec![ 1117 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1118 "--cpus", "boot=2", 1119 "--net", 1120 "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", 1121 ], 1122 r#"{ 1123 "payload": {"kernel": "/path/to/kernel"}, 1124 "cpus": {"boot_vcpus": 2, "max_vcpus": 2}, 1125 "net": [ 1126 {"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} 1127 ] 1128 }"#, 1129 true, 1130 ), 1131 ( 1132 vec![ 1133 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1134 "--cpus", "boot=2", 1135 "--net", 1136 "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", 1137 ], 1138 r#"{ 1139 "payload": {"kernel": "/path/to/kernel"}, 1140 "cpus": {"boot_vcpus": 2, "max_vcpus": 2}, 1141 "net": [ 1142 {"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} 1143 ] 1144 }"#, 1145 true, 1146 ), 1147 ( 1148 vec![ 1149 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1150 "--net", 1151 "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", 1152 ], 1153 r#"{ 1154 "payload": {"kernel": "/path/to/kernel"}, 1155 "net": [ 1156 {"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"} 1157 ] 1158 }"#, 1159 true, 1160 ), 1161 ( 1162 vec![ 1163 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1164 "--net", 1165 "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", 1166 ], 1167 r#"{ 1168 "payload": {"kernel": "/path/to/kernel"}, 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": 2, "queue_size": 256} 1171 ] 1172 }"#, 1173 true, 1174 ), 1175 #[cfg(target_arch = "x86_64")] 1176 ( 1177 vec![ 1178 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1179 "--net", 1180 "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", 1181 ], 1182 r#"{ 1183 "payload": {"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, "iommu": true} 1186 ] 1187 }"#, 1188 false, 1189 ), 1190 #[cfg(target_arch = "x86_64")] 1191 ( 1192 vec![ 1193 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1194 "--net", 1195 "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", 1196 ], 1197 r#"{ 1198 "payload": {"kernel": "/path/to/kernel"}, 1199 "net": [ 1200 {"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} 1201 ], 1202 "iommu": true 1203 }"#, 1204 true, 1205 ), 1206 ( 1207 vec![ 1208 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1209 "--net", 1210 "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", 1211 ], 1212 r#"{ 1213 "payload": {"kernel": "/path/to/kernel"}, 1214 "net": [ 1215 {"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} 1216 ] 1217 }"#, 1218 true, 1219 ), 1220 ( 1221 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"], 1222 r#"{ 1223 "payload": {"kernel": "/path/to/kernel"}, 1224 "memory" : { "shared": true, "size": 536870912 }, 1225 "net": [ 1226 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "vhost_user": true, "vhost_socket": "/tmp/sock"} 1227 ] 1228 }"#, 1229 true, 1230 ), 1231 ] 1232 .iter() 1233 .for_each(|(cli, openapi, equal)| { 1234 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1235 }); 1236 } 1237 1238 #[test] 1239 fn test_valid_vm_config_rng() { 1240 [( 1241 vec![ 1242 "cloud-hypervisor", 1243 "--kernel", 1244 "/path/to/kernel", 1245 "--rng", 1246 "src=/path/to/entropy/source", 1247 ], 1248 r#"{ 1249 "payload": {"kernel": "/path/to/kernel"}, 1250 "rng": {"src": "/path/to/entropy/source"} 1251 }"#, 1252 true, 1253 )] 1254 .iter() 1255 .for_each(|(cli, openapi, equal)| { 1256 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1257 }); 1258 } 1259 1260 #[test] 1261 fn test_valid_vm_config_fs() { 1262 [( 1263 vec![ 1264 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1265 "--memory", "shared=true", 1266 "--fs", 1267 "tag=virtiofs1,socket=/path/to/sock1", 1268 "--fs", 1269 "tag=virtiofs2,socket=/path/to/sock2", 1270 ], 1271 r#"{ 1272 "payload": {"kernel": "/path/to/kernel"}, 1273 "memory" : { "shared": true, "size": 536870912 }, 1274 "fs": [ 1275 {"tag": "virtiofs1", "socket": "/path/to/sock1"}, 1276 {"tag": "virtiofs2", "socket": "/path/to/sock2"} 1277 ] 1278 }"#, 1279 true, 1280 ), 1281 ( 1282 vec![ 1283 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1284 "--memory", "shared=true", 1285 "--fs", 1286 "tag=virtiofs1,socket=/path/to/sock1", 1287 "--fs", 1288 "tag=virtiofs2,socket=/path/to/sock2", 1289 ], 1290 r#"{ 1291 "payload": {"kernel": "/path/to/kernel"}, 1292 "memory" : { "shared": true, "size": 536870912 }, 1293 "fs": [ 1294 {"tag": "virtiofs1", "socket": "/path/to/sock1"} 1295 ] 1296 }"#, 1297 false, 1298 ), 1299 ( 1300 vec![ 1301 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1302 "--memory", "shared=true", "--cpus", "boot=4", 1303 "--fs", 1304 "tag=virtiofs1,socket=/path/to/sock1,num_queues=4", 1305 ], 1306 r#"{ 1307 "payload": {"kernel": "/path/to/kernel"}, 1308 "memory" : { "shared": true, "size": 536870912 }, 1309 "cpus": {"boot_vcpus": 4, "max_vcpus": 4}, 1310 "fs": [ 1311 {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4} 1312 ] 1313 }"#, 1314 true, 1315 ), 1316 ( 1317 vec![ 1318 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1319 "--memory", "shared=true", "--cpus", "boot=4", 1320 "--fs", 1321 "tag=virtiofs1,socket=/path/to/sock1,num_queues=4,queue_size=128" 1322 ], 1323 r#"{ 1324 "payload": {"kernel": "/path/to/kernel"}, 1325 "memory" : { "shared": true, "size": 536870912 }, 1326 "cpus": {"boot_vcpus": 4, "max_vcpus": 4}, 1327 "fs": [ 1328 {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4, "queue_size": 128} 1329 ] 1330 }"#, 1331 true, 1332 )] 1333 .iter() 1334 .for_each(|(cli, openapi, equal)| { 1335 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1336 }); 1337 } 1338 1339 #[test] 1340 fn test_valid_vm_config_pmem() { 1341 [ 1342 ( 1343 vec![ 1344 "cloud-hypervisor", 1345 "--kernel", 1346 "/path/to/kernel", 1347 "--pmem", 1348 "file=/path/to/img/1,size=1G", 1349 "--pmem", 1350 "file=/path/to/img/2,size=2G", 1351 ], 1352 r#"{ 1353 "payload": {"kernel": "/path/to/kernel"}, 1354 "pmem": [ 1355 {"file": "/path/to/img/1", "size": 1073741824}, 1356 {"file": "/path/to/img/2", "size": 2147483648} 1357 ] 1358 }"#, 1359 true, 1360 ), 1361 #[cfg(target_arch = "x86_64")] 1362 ( 1363 vec![ 1364 "cloud-hypervisor", 1365 "--kernel", 1366 "/path/to/kernel", 1367 "--pmem", 1368 "file=/path/to/img/1,size=1G,iommu=on", 1369 ], 1370 r#"{ 1371 "payload": {"kernel": "/path/to/kernel"}, 1372 "pmem": [ 1373 {"file": "/path/to/img/1", "size": 1073741824, "iommu": true} 1374 ], 1375 "iommu": true 1376 }"#, 1377 true, 1378 ), 1379 #[cfg(target_arch = "x86_64")] 1380 ( 1381 vec![ 1382 "cloud-hypervisor", 1383 "--kernel", 1384 "/path/to/kernel", 1385 "--pmem", 1386 "file=/path/to/img/1,size=1G,iommu=on", 1387 ], 1388 r#"{ 1389 "payload": {"kernel": "/path/to/kernel"}, 1390 "pmem": [ 1391 {"file": "/path/to/img/1", "size": 1073741824, "iommu": true} 1392 ] 1393 }"#, 1394 false, 1395 ), 1396 ] 1397 .iter() 1398 .for_each(|(cli, openapi, equal)| { 1399 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1400 }); 1401 } 1402 1403 #[test] 1404 fn test_valid_vm_config_serial_console() { 1405 [ 1406 ( 1407 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 1408 r#"{ 1409 "payload": {"kernel": "/path/to/kernel"}, 1410 "serial": {"mode": "Null"}, 1411 "console": {"mode": "Tty"} 1412 }"#, 1413 true, 1414 ), 1415 ( 1416 vec![ 1417 "cloud-hypervisor", 1418 "--kernel", 1419 "/path/to/kernel", 1420 "--serial", 1421 "null", 1422 "--console", 1423 "tty", 1424 ], 1425 r#"{ 1426 "payload": {"kernel": "/path/to/kernel"} 1427 }"#, 1428 true, 1429 ), 1430 ( 1431 vec![ 1432 "cloud-hypervisor", 1433 "--kernel", 1434 "/path/to/kernel", 1435 "--serial", 1436 "tty", 1437 "--console", 1438 "off", 1439 ], 1440 r#"{ 1441 "payload": {"kernel": "/path/to/kernel"}, 1442 "serial": {"mode": "Tty"}, 1443 "console": {"mode": "Off"} 1444 }"#, 1445 true, 1446 ), 1447 ] 1448 .iter() 1449 .for_each(|(cli, openapi, equal)| { 1450 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1451 }); 1452 } 1453 1454 #[test] 1455 fn test_valid_vm_config_serial_pty_console_pty() { 1456 [ 1457 ( 1458 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 1459 r#"{ 1460 "payload": {"kernel": "/path/to/kernel"}, 1461 "serial": {"mode": "Null"}, 1462 "console": {"mode": "Tty"} 1463 }"#, 1464 true, 1465 ), 1466 ( 1467 vec![ 1468 "cloud-hypervisor", 1469 "--kernel", 1470 "/path/to/kernel", 1471 "--serial", 1472 "null", 1473 "--console", 1474 "tty", 1475 ], 1476 r#"{ 1477 "payload": {"kernel": "/path/to/kernel"} 1478 }"#, 1479 true, 1480 ), 1481 ( 1482 vec![ 1483 "cloud-hypervisor", 1484 "--kernel", 1485 "/path/to/kernel", 1486 "--serial", 1487 "pty", 1488 "--console", 1489 "pty", 1490 ], 1491 r#"{ 1492 "payload": {"kernel": "/path/to/kernel"}, 1493 "serial": {"mode": "Pty"}, 1494 "console": {"mode": "Pty"} 1495 }"#, 1496 true, 1497 ), 1498 ] 1499 .iter() 1500 .for_each(|(cli, openapi, equal)| { 1501 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1502 }); 1503 } 1504 1505 #[test] 1506 #[cfg(target_arch = "x86_64")] 1507 fn test_valid_vm_config_devices() { 1508 vec![ 1509 ( 1510 vec![ 1511 "cloud-hypervisor", 1512 "--kernel", 1513 "/path/to/kernel", 1514 "--device", 1515 "path=/path/to/device/1", 1516 "--device", 1517 "path=/path/to/device/2", 1518 ], 1519 r#"{ 1520 "payload": {"kernel": "/path/to/kernel"}, 1521 "devices": [ 1522 {"path": "/path/to/device/1"}, 1523 {"path": "/path/to/device/2"} 1524 ] 1525 }"#, 1526 true, 1527 ), 1528 ( 1529 vec![ 1530 "cloud-hypervisor", 1531 "--kernel", 1532 "/path/to/kernel", 1533 "--device", 1534 "path=/path/to/device/1", 1535 "--device", 1536 "path=/path/to/device/2", 1537 ], 1538 r#"{ 1539 "payload": {"kernel": "/path/to/kernel"}, 1540 "devices": [ 1541 {"path": "/path/to/device/1"} 1542 ] 1543 }"#, 1544 false, 1545 ), 1546 ( 1547 vec![ 1548 "cloud-hypervisor", 1549 "--kernel", 1550 "/path/to/kernel", 1551 "--device", 1552 "path=/path/to/device,iommu=on", 1553 ], 1554 r#"{ 1555 "payload": {"kernel": "/path/to/kernel"}, 1556 "devices": [ 1557 {"path": "/path/to/device", "iommu": true} 1558 ], 1559 "iommu": true 1560 }"#, 1561 true, 1562 ), 1563 ( 1564 vec![ 1565 "cloud-hypervisor", 1566 "--kernel", 1567 "/path/to/kernel", 1568 "--device", 1569 "path=/path/to/device,iommu=on", 1570 ], 1571 r#"{ 1572 "payload": {"kernel": "/path/to/kernel"}, 1573 "devices": [ 1574 {"path": "/path/to/device", "iommu": true} 1575 ] 1576 }"#, 1577 false, 1578 ), 1579 ( 1580 vec![ 1581 "cloud-hypervisor", 1582 "--kernel", 1583 "/path/to/kernel", 1584 "--device", 1585 "path=/path/to/device,iommu=off", 1586 ], 1587 r#"{ 1588 "payload": {"kernel": "/path/to/kernel"}, 1589 "devices": [ 1590 {"path": "/path/to/device", "iommu": false} 1591 ] 1592 }"#, 1593 true, 1594 ), 1595 ] 1596 .iter() 1597 .for_each(|(cli, openapi, equal)| { 1598 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1599 }); 1600 } 1601 1602 #[test] 1603 fn test_valid_vm_config_vdpa() { 1604 [ 1605 ( 1606 vec![ 1607 "cloud-hypervisor", 1608 "--kernel", 1609 "/path/to/kernel", 1610 "--vdpa", 1611 "path=/path/to/device/1", 1612 "--vdpa", 1613 "path=/path/to/device/2,num_queues=2", 1614 ], 1615 r#"{ 1616 "payload": {"kernel": "/path/to/kernel"}, 1617 "vdpa": [ 1618 {"path": "/path/to/device/1", "num_queues": 1}, 1619 {"path": "/path/to/device/2", "num_queues": 2} 1620 ] 1621 }"#, 1622 true, 1623 ), 1624 ( 1625 vec![ 1626 "cloud-hypervisor", 1627 "--kernel", 1628 "/path/to/kernel", 1629 "--vdpa", 1630 "path=/path/to/device/1", 1631 "--vdpa", 1632 "path=/path/to/device/2", 1633 ], 1634 r#"{ 1635 "payload": {"kernel": "/path/to/kernel"}, 1636 "vdpa": [ 1637 {"path": "/path/to/device/1"} 1638 ] 1639 }"#, 1640 false, 1641 ), 1642 ] 1643 .iter() 1644 .for_each(|(cli, openapi, equal)| { 1645 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1646 }); 1647 } 1648 1649 #[test] 1650 fn test_valid_vm_config_vsock() { 1651 [ 1652 ( 1653 vec![ 1654 "cloud-hypervisor", 1655 "--kernel", 1656 "/path/to/kernel", 1657 "--vsock", 1658 "cid=123,socket=/path/to/sock/1", 1659 ], 1660 r#"{ 1661 "payload": {"kernel": "/path/to/kernel"}, 1662 "vsock": {"cid": 123, "socket": "/path/to/sock/1"} 1663 }"#, 1664 true, 1665 ), 1666 ( 1667 vec![ 1668 "cloud-hypervisor", 1669 "--kernel", 1670 "/path/to/kernel", 1671 "--vsock", 1672 "cid=124,socket=/path/to/sock/1", 1673 ], 1674 r#"{ 1675 "payload": {"kernel": "/path/to/kernel"}, 1676 "vsock": {"cid": 123, "socket": "/path/to/sock/1"} 1677 }"#, 1678 false, 1679 ), 1680 #[cfg(target_arch = "x86_64")] 1681 ( 1682 vec![ 1683 "cloud-hypervisor", 1684 "--kernel", 1685 "/path/to/kernel", 1686 "--vsock", 1687 "cid=123,socket=/path/to/sock/1,iommu=on", 1688 ], 1689 r#"{ 1690 "payload": {"kernel": "/path/to/kernel"}, 1691 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true}, 1692 "iommu": true 1693 }"#, 1694 true, 1695 ), 1696 #[cfg(target_arch = "x86_64")] 1697 ( 1698 vec![ 1699 "cloud-hypervisor", 1700 "--kernel", 1701 "/path/to/kernel", 1702 "--vsock", 1703 "cid=123,socket=/path/to/sock/1,iommu=on", 1704 ], 1705 r#"{ 1706 "payload": {"kernel": "/path/to/kernel"}, 1707 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true} 1708 }"#, 1709 false, 1710 ), 1711 ( 1712 vec![ 1713 "cloud-hypervisor", 1714 "--kernel", 1715 "/path/to/kernel", 1716 "--vsock", 1717 "cid=123,socket=/path/to/sock/1,iommu=off", 1718 ], 1719 r#"{ 1720 "payload": {"kernel": "/path/to/kernel"}, 1721 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": false} 1722 }"#, 1723 true, 1724 ), 1725 ] 1726 .iter() 1727 .for_each(|(cli, openapi, equal)| { 1728 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1729 }); 1730 } 1731 1732 #[test] 1733 fn test_valid_vm_config_tpm_socket() { 1734 [( 1735 vec![ 1736 "cloud-hypervisor", 1737 "--kernel", 1738 "/path/to/kernel", 1739 "--tpm", 1740 "socket=/path/to/tpm/sock", 1741 ], 1742 r#"{ 1743 "payload": {"kernel": "/path/to/kernel"}, 1744 "tpm": {"socket": "/path/to/tpm/sock"} 1745 }"#, 1746 true, 1747 )] 1748 .iter() 1749 .for_each(|(cli, openapi, equal)| { 1750 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1751 }); 1752 } 1753 } 1754