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