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