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