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