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