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