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