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