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