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