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