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