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 = "guest_debug")] 33 #[error("Failed to create Debug EventFd: {0}")] 34 CreateDebugEventFd(#[source] std::io::Error), 35 #[error("Failed to open hypervisor interface (is hypervisor interface available?): {0}")] 36 CreateHypervisor(#[source] hypervisor::HypervisorError), 37 #[error("Failed to start the VMM thread: {0}")] 38 StartVmmThread(#[source] vmm::Error), 39 #[error("Error parsing config: {0}")] 40 ParsingConfig(vmm::config::Error), 41 #[error("Error creating VM: {0:?}")] 42 VmCreate(vmm::api::ApiError), 43 #[error("Error booting VM: {0:?}")] 44 VmBoot(vmm::api::ApiError), 45 #[error("Error restoring VM: {0:?}")] 46 VmRestore(vmm::api::ApiError), 47 #[error("Error parsing restore: {0}")] 48 ParsingRestore(vmm::config::Error), 49 #[error("Failed to join on VMM thread: {0:?}")] 50 ThreadJoin(std::boxed::Box<dyn std::any::Any + std::marker::Send>), 51 #[error("VMM thread exited with error: {0}")] 52 VmmThread(#[source] vmm::Error), 53 #[error("Error parsing --api-socket: {0}")] 54 ParsingApiSocket(std::num::ParseIntError), 55 #[error("Error parsing --event-monitor: {0}")] 56 ParsingEventMonitor(option_parser::OptionParserError), 57 #[error("Error parsing --event-monitor: path or fd required")] 58 BareEventMonitor, 59 #[error("Error doing event monitor I/O: {0}")] 60 EventMonitorIo(std::io::Error), 61 #[cfg(feature = "guest_debug")] 62 #[error("Error parsing --gdb: {0}")] 63 ParsingGdb(option_parser::OptionParserError), 64 #[cfg(feature = "guest_debug")] 65 #[error("Error parsing --gdb: path required")] 66 BareGdb, 67 #[error("Error creating log file: {0}")] 68 LogFileCreation(std::io::Error), 69 #[error("Error setting up logger: {0}")] 70 LoggerSetup(log::SetLoggerError), 71 } 72 73 struct Logger { 74 output: Mutex<Box<dyn std::io::Write + Send>>, 75 start: std::time::Instant, 76 } 77 78 impl log::Log for Logger { 79 fn enabled(&self, _metadata: &log::Metadata) -> bool { 80 true 81 } 82 83 fn log(&self, record: &log::Record) { 84 if !self.enabled(record.metadata()) { 85 return; 86 } 87 88 let now = std::time::Instant::now(); 89 let duration = now.duration_since(self.start); 90 91 if record.file().is_some() && record.line().is_some() { 92 writeln!( 93 *(*(self.output.lock().unwrap())), 94 "cloud-hypervisor: {:?}: <{}> {}:{}:{} -- {}", 95 duration, 96 std::thread::current().name().unwrap_or("anonymous"), 97 record.level(), 98 record.file().unwrap(), 99 record.line().unwrap(), 100 record.args() 101 ) 102 } else { 103 writeln!( 104 *(*(self.output.lock().unwrap())), 105 "cloud-hypervisor: {:?}: <{}> {}:{} -- {}", 106 duration, 107 std::thread::current().name().unwrap_or("anonymous"), 108 record.level(), 109 record.target(), 110 record.args() 111 ) 112 } 113 .ok(); 114 } 115 fn flush(&self) {} 116 } 117 118 fn prepare_default_values() -> (String, String, String) { 119 let default_vcpus = 120 format! {"boot={},max_phys_bits={}", config::DEFAULT_VCPUS,config::DEFAULT_MAX_PHYS_BITS}; 121 let default_memory = format! {"size={}M", config::DEFAULT_MEMORY_MB}; 122 let default_rng = format! {"src={}", config::DEFAULT_RNG_SOURCE}; 123 124 (default_vcpus, default_memory, default_rng) 125 } 126 127 fn create_app<'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>,uuid=<dmi_device_uuid>,oem_strings=<list_of_strings>", 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("firmware") 197 .long("firmware") 198 .help("Path to firmware that is loaded in an architectural specific way") 199 .takes_value(true) 200 .group("vm-config"), 201 ) 202 .arg( 203 Arg::new("kernel") 204 .long("kernel") 205 .help( 206 "Path to kernel to load. 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 = "guest_debug")] 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 app 404 } 405 406 fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> { 407 let log_level = match cmd_arguments.occurrences_of("v") { 408 0 => LevelFilter::Warn, 409 1 => LevelFilter::Info, 410 2 => LevelFilter::Debug, 411 _ => LevelFilter::Trace, 412 }; 413 414 let log_file: Box<dyn std::io::Write + Send> = if let Some(file) = 415 cmd_arguments.value_of("log-file") 416 { 417 Box::new(std::fs::File::create(std::path::Path::new(file)).map_err(Error::LogFileCreation)?) 418 } else { 419 Box::new(std::io::stderr()) 420 }; 421 422 log::set_boxed_logger(Box::new(Logger { 423 output: Mutex::new(log_file), 424 start: std::time::Instant::now(), 425 })) 426 .map(|()| log::set_max_level(log_level)) 427 .map_err(Error::LoggerSetup)?; 428 429 let (api_socket_path, api_socket_fd) = 430 if let Some(socket_config) = cmd_arguments.value_of("api-socket") { 431 let mut parser = OptionParser::new(); 432 parser.add("path").add("fd"); 433 parser.parse(socket_config).unwrap_or_default(); 434 435 if let Some(fd) = parser.get("fd") { 436 ( 437 None, 438 Some(fd.parse::<RawFd>().map_err(Error::ParsingApiSocket)?), 439 ) 440 } else if let Some(path) = parser.get("path") { 441 (Some(path), None) 442 } else { 443 ( 444 cmd_arguments.value_of("api-socket").map(|s| s.to_string()), 445 None, 446 ) 447 } 448 } else { 449 (None, None) 450 }; 451 452 if let Some(monitor_config) = cmd_arguments.value_of("event-monitor") { 453 let mut parser = OptionParser::new(); 454 parser.add("path").add("fd"); 455 parser 456 .parse(monitor_config) 457 .map_err(Error::ParsingEventMonitor)?; 458 459 let file = if parser.is_set("fd") { 460 let fd = parser 461 .convert("fd") 462 .map_err(Error::ParsingEventMonitor)? 463 .unwrap(); 464 unsafe { File::from_raw_fd(fd) } 465 } else if parser.is_set("path") { 466 std::fs::OpenOptions::new() 467 .write(true) 468 .create(true) 469 .open(parser.get("path").unwrap()) 470 .map_err(Error::EventMonitorIo)? 471 } else { 472 return Err(Error::BareEventMonitor); 473 }; 474 event_monitor::set_monitor(file).map_err(Error::EventMonitorIo)?; 475 } 476 477 let (api_request_sender, api_request_receiver) = channel(); 478 let api_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateApiEventFd)?; 479 480 let http_sender = api_request_sender.clone(); 481 let seccomp_action = if let Some(seccomp_value) = cmd_arguments.value_of("seccomp") { 482 match seccomp_value { 483 "true" => SeccompAction::Trap, 484 "false" => SeccompAction::Allow, 485 "log" => SeccompAction::Log, 486 _ => { 487 // The user providing an invalid value will be rejected by clap 488 panic!("Invalid parameter {} for \"--seccomp\" flag", seccomp_value); 489 } 490 } 491 } else { 492 SeccompAction::Trap 493 }; 494 495 if seccomp_action == SeccompAction::Trap { 496 // SAFETY: We only using signal_hook for managing signals and only execute signal 497 // handler safe functions (writing to stderr) and manipulating signals. 498 unsafe { 499 signal_hook::low_level::register(signal_hook::consts::SIGSYS, || { 500 eprint!( 501 "\n==== Possible seccomp violation ====\n\ 502 Try running with `strace -ff` to identify the cause and open an issue: \ 503 https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new\n" 504 ); 505 signal_hook::low_level::emulate_default_handler(SIGSYS).unwrap(); 506 }) 507 } 508 .map_err(|e| eprintln!("Error adding SIGSYS signal handler: {}", e)) 509 .ok(); 510 } 511 512 // Before we start any threads, mask the signals we'll be 513 // installing handlers for, to make sure they only ever run on the 514 // dedicated signal handling thread we'll start in a bit. 515 for sig in &vmm::vm::Vm::HANDLED_SIGNALS { 516 if let Err(e) = block_signal(*sig) { 517 eprintln!("Error blocking signals: {}", e); 518 } 519 } 520 521 for sig in &vmm::Vmm::HANDLED_SIGNALS { 522 if let Err(e) = block_signal(*sig) { 523 eprintln!("Error blocking signals: {}", e); 524 } 525 } 526 527 event!("vmm", "starting"); 528 529 let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?; 530 531 #[cfg(feature = "guest_debug")] 532 let gdb_socket_path = if let Some(gdb_config) = cmd_arguments.value_of("gdb") { 533 let mut parser = OptionParser::new(); 534 parser.add("path"); 535 parser.parse(gdb_config).map_err(Error::ParsingGdb)?; 536 537 if parser.is_set("path") { 538 Some(std::path::PathBuf::from(parser.get("path").unwrap())) 539 } else { 540 return Err(Error::BareGdb); 541 } 542 } else { 543 None 544 }; 545 #[cfg(feature = "guest_debug")] 546 let debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?; 547 #[cfg(feature = "guest_debug")] 548 let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?; 549 550 let vmm_thread = vmm::start_vmm_thread( 551 env!("CARGO_PKG_VERSION").to_string(), 552 &api_socket_path, 553 api_socket_fd, 554 api_evt.try_clone().unwrap(), 555 http_sender, 556 api_request_receiver, 557 #[cfg(feature = "guest_debug")] 558 gdb_socket_path, 559 #[cfg(feature = "guest_debug")] 560 debug_evt.try_clone().unwrap(), 561 #[cfg(feature = "guest_debug")] 562 vm_debug_evt.try_clone().unwrap(), 563 &seccomp_action, 564 hypervisor, 565 ) 566 .map_err(Error::StartVmmThread)?; 567 568 let payload_present = 569 cmd_arguments.is_present("kernel") || cmd_arguments.is_present("firmware"); 570 571 if payload_present { 572 let vm_params = config::VmParams::from_arg_matches(&cmd_arguments); 573 let vm_config = config::VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?; 574 575 // Create and boot the VM based off the VM config we just built. 576 let sender = api_request_sender.clone(); 577 vmm::api::vm_create( 578 api_evt.try_clone().unwrap(), 579 api_request_sender, 580 Arc::new(Mutex::new(vm_config)), 581 ) 582 .map_err(Error::VmCreate)?; 583 vmm::api::vm_boot(api_evt.try_clone().unwrap(), sender).map_err(Error::VmBoot)?; 584 } else if let Some(restore_params) = cmd_arguments.value_of("restore") { 585 vmm::api::vm_restore( 586 api_evt.try_clone().unwrap(), 587 api_request_sender, 588 Arc::new(config::RestoreConfig::parse(restore_params).map_err(Error::ParsingRestore)?), 589 ) 590 .map_err(Error::VmRestore)?; 591 } 592 593 vmm_thread 594 .join() 595 .map_err(Error::ThreadJoin)? 596 .map_err(Error::VmmThread)?; 597 598 Ok(api_socket_path) 599 } 600 601 fn main() { 602 // Ensure all created files (.e.g sockets) are only accessible by this user 603 let _ = unsafe { libc::umask(0o077) }; 604 605 let (default_vcpus, default_memory, default_rng) = prepare_default_values(); 606 let cmd_arguments = create_app(&default_vcpus, &default_memory, &default_rng).get_matches(); 607 let exit_code = match start_vmm(cmd_arguments) { 608 Ok(path) => { 609 path.map(|s| std::fs::remove_file(s).ok()); 610 0 611 } 612 Err(e) => { 613 eprintln!("{}", e); 614 1 615 } 616 }; 617 618 let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0; 619 if on_tty { 620 // Don't forget to set the terminal in canonical mode 621 // before to exit. 622 std::io::stdin().lock().set_canon_mode().unwrap(); 623 } 624 625 std::process::exit(exit_code); 626 } 627 628 #[cfg(test)] 629 mod unit_tests { 630 use crate::config::HotplugMethod; 631 use crate::{create_app, prepare_default_values}; 632 use std::path::PathBuf; 633 use vmm::config::{ 634 CmdlineConfig, ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, 635 PayloadConfig, RngConfig, VmConfig, VmParams, 636 }; 637 638 fn get_vm_config_from_vec(args: &[&str]) -> VmConfig { 639 let (default_vcpus, default_memory, default_rng) = prepare_default_values(); 640 let cmd_arguments = 641 create_app(&default_vcpus, &default_memory, &default_rng).get_matches_from(args); 642 643 let vm_params = VmParams::from_arg_matches(&cmd_arguments); 644 645 VmConfig::parse(vm_params).unwrap() 646 } 647 648 fn compare_vm_config_cli_vs_json( 649 cli: &[&str], 650 openapi: &str, 651 equal: bool, 652 ) -> (VmConfig, VmConfig) { 653 let cli_vm_config = get_vm_config_from_vec(cli); 654 let openapi_vm_config: VmConfig = serde_json::from_str(openapi).unwrap(); 655 656 if equal { 657 assert_eq!(cli_vm_config, openapi_vm_config); 658 } else { 659 assert_ne!(cli_vm_config, openapi_vm_config); 660 } 661 662 (cli_vm_config, openapi_vm_config) 663 } 664 665 #[test] 666 fn test_valid_vm_config_default() { 667 let cli = vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"]; 668 let openapi = r#"{ "payload": {"kernel": "/path/to/kernel"} }"#; 669 670 // First we check we get identical VmConfig structures. 671 let (result_vm_config, _) = compare_vm_config_cli_vs_json(&cli, openapi, true); 672 673 // As a second step, we validate all the default values. 674 let expected_vm_config = VmConfig { 675 cpus: CpusConfig { 676 boot_vcpus: 1, 677 max_vcpus: 1, 678 topology: None, 679 kvm_hyperv: false, 680 max_phys_bits: 46, 681 affinity: None, 682 features: CpuFeatures::default(), 683 }, 684 memory: MemoryConfig { 685 size: 536_870_912, 686 mergeable: false, 687 hotplug_method: HotplugMethod::Acpi, 688 hotplug_size: None, 689 hotplugged_size: None, 690 shared: false, 691 hugepages: false, 692 hugepage_size: None, 693 prefault: false, 694 zones: None, 695 }, 696 kernel: None, 697 cmdline: CmdlineConfig { 698 args: String::default(), 699 }, 700 initramfs: None, 701 payload: Some(PayloadConfig { 702 kernel: Some(PathBuf::from("/path/to/kernel")), 703 ..Default::default() 704 }), 705 disks: None, 706 net: None, 707 rng: RngConfig { 708 src: PathBuf::from("/dev/urandom"), 709 iommu: false, 710 }, 711 balloon: None, 712 fs: None, 713 pmem: None, 714 serial: ConsoleConfig { 715 file: None, 716 mode: ConsoleOutputMode::Null, 717 iommu: false, 718 }, 719 console: ConsoleConfig { 720 file: None, 721 mode: ConsoleOutputMode::Tty, 722 iommu: false, 723 }, 724 devices: None, 725 user_devices: None, 726 vdpa: None, 727 vsock: None, 728 iommu: false, 729 #[cfg(target_arch = "x86_64")] 730 sgx_epc: None, 731 numa: None, 732 watchdog: false, 733 #[cfg(feature = "guest_debug")] 734 gdb: false, 735 platform: None, 736 }; 737 738 assert_eq!(expected_vm_config, result_vm_config); 739 } 740 741 #[test] 742 fn test_valid_vm_config_cpus() { 743 vec![ 744 ( 745 vec![ 746 "cloud-hypervisor", 747 "--kernel", 748 "/path/to/kernel", 749 "--cpus", 750 "boot=1", 751 ], 752 r#"{ 753 "payload": {"kernel": "/path/to/kernel"}, 754 "cpus": {"boot_vcpus": 1, "max_vcpus": 1} 755 }"#, 756 true, 757 ), 758 ( 759 vec![ 760 "cloud-hypervisor", 761 "--kernel", 762 "/path/to/kernel", 763 "--cpus", 764 "boot=1,max=3", 765 ], 766 r#"{ 767 "payload": {"kernel": "/path/to/kernel"}, 768 "cpus": {"boot_vcpus": 1, "max_vcpus": 3} 769 }"#, 770 true, 771 ), 772 ( 773 vec![ 774 "cloud-hypervisor", 775 "--kernel", 776 "/path/to/kernel", 777 "--cpus", 778 "boot=2,max=4", 779 ], 780 r#"{ 781 "payload": {"kernel": "/path/to/kernel"}, 782 "cpus": {"boot_vcpus": 1, "max_vcpus": 3} 783 }"#, 784 false, 785 ), 786 ] 787 .iter() 788 .for_each(|(cli, openapi, equal)| { 789 compare_vm_config_cli_vs_json(cli, openapi, *equal); 790 }); 791 } 792 793 #[test] 794 fn test_valid_vm_config_memory() { 795 vec![ 796 ( 797 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"], 798 r#"{ 799 "payload": {"kernel": "/path/to/kernel"}, 800 "memory": {"size": 1073741824} 801 }"#, 802 true, 803 ), 804 ( 805 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G"], 806 r#"{ 807 "payload": {"kernel": "/path/to/kernel"}, 808 "memory": {"size": 1073741824} 809 }"#, 810 true, 811 ), 812 ( 813 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"], 814 r#"{ 815 "payload": {"kernel": "/path/to/kernel"}, 816 "memory": {"size": 1073741824, "mergeable": true} 817 }"#, 818 true, 819 ), 820 ( 821 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=off"], 822 r#"{ 823 "payload": {"kernel": "/path/to/kernel"}, 824 "memory": {"size": 1073741824, "mergeable": false} 825 }"#, 826 true, 827 ), 828 ( 829 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,mergeable=on"], 830 r#"{ 831 "payload": {"kernel": "/path/to/kernel"}, 832 "memory": {"size": 1073741824, "mergeable": false} 833 }"#, 834 false, 835 ), 836 ( 837 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_size=1G"], 838 r#"{ 839 "payload": {"kernel": "/path/to/kernel"}, 840 "memory": {"size": 1073741824, "hotplug_method": "Acpi", "hotplug_size": 1073741824} 841 }"#, 842 true, 843 ), 844 ( 845 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1G,hotplug_method=virtio-mem,hotplug_size=1G"], 846 r#"{ 847 "payload": {"kernel": "/path/to/kernel"}, 848 "memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824} 849 }"#, 850 true, 851 ), 852 ] 853 .iter() 854 .for_each(|(cli, openapi, equal)| { 855 compare_vm_config_cli_vs_json(cli, openapi, *equal); 856 }); 857 } 858 859 #[test] 860 fn test_valid_vm_config_kernel() { 861 vec![( 862 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 863 r#"{ 864 "payload": {"kernel": "/path/to/kernel"} 865 }"#, 866 true, 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_cmdline() { 876 vec![( 877 vec![ 878 "cloud-hypervisor", 879 "--kernel", 880 "/path/to/kernel", 881 "--cmdline", 882 "arg1=foo arg2=bar", 883 ], 884 r#"{ 885 "payload": {"kernel": "/path/to/kernel", "cmdline": "arg1=foo arg2=bar"} 886 }"#, 887 true, 888 )] 889 .iter() 890 .for_each(|(cli, openapi, equal)| { 891 compare_vm_config_cli_vs_json(cli, openapi, *equal); 892 }); 893 } 894 895 #[test] 896 fn test_valid_vm_config_disks() { 897 vec![ 898 ( 899 vec![ 900 "cloud-hypervisor", 901 "--kernel", 902 "/path/to/kernel", 903 "--disk", 904 "path=/path/to/disk/1", 905 "path=/path/to/disk/2", 906 ], 907 r#"{ 908 "payload": {"kernel": "/path/to/kernel"}, 909 "disks": [ 910 {"path": "/path/to/disk/1"}, 911 {"path": "/path/to/disk/2"} 912 ] 913 }"#, 914 true, 915 ), 916 ( 917 vec![ 918 "cloud-hypervisor", 919 "--kernel", 920 "/path/to/kernel", 921 "--disk", 922 "path=/path/to/disk/1", 923 "path=/path/to/disk/2", 924 ], 925 r#"{ 926 "payload": {"kernel": "/path/to/kernel"}, 927 "disks": [ 928 {"path": "/path/to/disk/1"} 929 ] 930 }"#, 931 false, 932 ), 933 ( 934 vec![ 935 "cloud-hypervisor", 936 "--kernel", 937 "/path/to/kernel", 938 "--memory", 939 "shared=true", 940 "--disk", 941 "vhost_user=true,socket=/tmp/sock1", 942 ], 943 r#"{ 944 "payload": {"kernel": "/path/to/kernel"}, 945 "memory" : { "shared": true, "size": 536870912 }, 946 "disks": [ 947 {"vhost_user":true, "vhost_socket":"/tmp/sock1"} 948 ] 949 }"#, 950 true, 951 ), 952 ( 953 vec![ 954 "cloud-hypervisor", 955 "--kernel", 956 "/path/to/kernel", 957 "--memory", 958 "shared=true", 959 "--disk", 960 "vhost_user=true,socket=/tmp/sock1", 961 ], 962 r#"{ 963 "payload": {"kernel": "/path/to/kernel"}, 964 "memory" : { "shared": true, "size": 536870912 }, 965 "disks": [ 966 {"vhost_user":true, "vhost_socket":"/tmp/sock1"} 967 ] 968 }"#, 969 true, 970 ), 971 ] 972 .iter() 973 .for_each(|(cli, openapi, equal)| { 974 compare_vm_config_cli_vs_json(cli, openapi, *equal); 975 }); 976 } 977 978 #[test] 979 fn test_valid_vm_config_net() { 980 vec![ 981 // This test is expected to fail because the default MAC address is 982 // randomly generated. There's no way we can have twice the same 983 // default value. 984 ( 985 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac="], 986 r#"{ 987 "payload": {"kernel": "/path/to/kernel"}, 988 "net": [] 989 }"#, 990 false, 991 ), 992 ( 993 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--net", "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd"], 994 r#"{ 995 "payload": {"kernel": "/path/to/kernel"}, 996 "net": [ 997 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd"} 998 ] 999 }"#, 1000 true, 1001 ), 1002 ( 1003 vec![ 1004 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1005 "--net", 1006 "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0", 1007 ], 1008 r#"{ 1009 "payload": {"kernel": "/path/to/kernel"}, 1010 "net": [ 1011 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0"} 1012 ] 1013 }"#, 1014 true, 1015 ), 1016 ( 1017 vec![ 1018 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1019 "--net", 1020 "mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4", 1021 ], 1022 r#"{ 1023 "payload": {"kernel": "/path/to/kernel"}, 1024 "net": [ 1025 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"} 1026 ] 1027 }"#, 1028 true, 1029 ), 1030 ( 1031 vec![ 1032 "cloud-hypervisor", "--kernel", "/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,mask=5.6.7.8", 1035 ], 1036 r#"{ 1037 "payload": {"kernel": "/path/to/kernel"}, 1038 "net": [ 1039 {"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"} 1040 ] 1041 }"#, 1042 true, 1043 ), 1044 ( 1045 vec![ 1046 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1047 "--cpus", "boot=2", 1048 "--net", 1049 "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", 1050 ], 1051 r#"{ 1052 "payload": {"kernel": "/path/to/kernel"}, 1053 "cpus": {"boot_vcpus": 2, "max_vcpus": 2}, 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", "num_queues": 4} 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,queue_size=128", 1066 ], 1067 r#"{ 1068 "payload": {"kernel": "/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, "queue_size": 128} 1072 ] 1073 }"#, 1074 true, 1075 ), 1076 ( 1077 vec![ 1078 "cloud-hypervisor", "--kernel", "/path/to/kernel", 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=2,queue_size=256", 1081 ], 1082 r#"{ 1083 "payload": {"kernel": "/path/to/kernel"}, 1084 "net": [ 1085 {"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"} 1086 ] 1087 }"#, 1088 true, 1089 ), 1090 ( 1091 vec![ 1092 "cloud-hypervisor", "--kernel", "/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 r#"{ 1097 "payload": {"kernel": "/path/to/kernel"}, 1098 "net": [ 1099 {"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} 1100 ] 1101 }"#, 1102 true, 1103 ), 1104 #[cfg(target_arch = "x86_64")] 1105 ( 1106 vec![ 1107 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1108 "--net", 1109 "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", 1110 ], 1111 r#"{ 1112 "payload": {"kernel": "/path/to/kernel"}, 1113 "net": [ 1114 {"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} 1115 ] 1116 }"#, 1117 false, 1118 ), 1119 #[cfg(target_arch = "x86_64")] 1120 ( 1121 vec![ 1122 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1123 "--net", 1124 "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", 1125 ], 1126 r#"{ 1127 "payload": {"kernel": "/path/to/kernel"}, 1128 "net": [ 1129 {"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} 1130 ], 1131 "iommu": true 1132 }"#, 1133 true, 1134 ), 1135 ( 1136 vec![ 1137 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1138 "--net", 1139 "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", 1140 ], 1141 r#"{ 1142 "payload": {"kernel": "/path/to/kernel"}, 1143 "net": [ 1144 {"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} 1145 ] 1146 }"#, 1147 true, 1148 ), 1149 ( 1150 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"], 1151 r#"{ 1152 "payload": {"kernel": "/path/to/kernel"}, 1153 "memory" : { "shared": true, "size": 536870912 }, 1154 "net": [ 1155 {"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "vhost_user": true, "vhost_socket": "/tmp/sock"} 1156 ] 1157 }"#, 1158 true, 1159 ), 1160 ] 1161 .iter() 1162 .for_each(|(cli, openapi, equal)| { 1163 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1164 }); 1165 } 1166 1167 #[test] 1168 fn test_valid_vm_config_rng() { 1169 vec![( 1170 vec![ 1171 "cloud-hypervisor", 1172 "--kernel", 1173 "/path/to/kernel", 1174 "--rng", 1175 "src=/path/to/entropy/source", 1176 ], 1177 r#"{ 1178 "payload": {"kernel": "/path/to/kernel"}, 1179 "rng": {"src": "/path/to/entropy/source"} 1180 }"#, 1181 true, 1182 )] 1183 .iter() 1184 .for_each(|(cli, openapi, equal)| { 1185 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1186 }); 1187 } 1188 1189 #[test] 1190 fn test_valid_vm_config_fs() { 1191 vec![ 1192 ( 1193 vec![ 1194 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1195 "--memory", "shared=true", 1196 "--fs", 1197 "tag=virtiofs1,socket=/path/to/sock1", 1198 "tag=virtiofs2,socket=/path/to/sock2", 1199 ], 1200 r#"{ 1201 "payload": {"kernel": "/path/to/kernel"}, 1202 "memory" : { "shared": true, "size": 536870912 }, 1203 "fs": [ 1204 {"tag": "virtiofs1", "socket": "/path/to/sock1"}, 1205 {"tag": "virtiofs2", "socket": "/path/to/sock2"} 1206 ] 1207 }"#, 1208 true, 1209 ), 1210 ( 1211 vec![ 1212 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1213 "--memory", "shared=true", 1214 "--fs", 1215 "tag=virtiofs1,socket=/path/to/sock1", 1216 "tag=virtiofs2,socket=/path/to/sock2", 1217 ], 1218 r#"{ 1219 "payload": {"kernel": "/path/to/kernel"}, 1220 "memory" : { "shared": true, "size": 536870912 }, 1221 "fs": [ 1222 {"tag": "virtiofs1", "socket": "/path/to/sock1"} 1223 ] 1224 }"#, 1225 false, 1226 ), 1227 ( 1228 vec![ 1229 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1230 "--memory", "shared=true", "--cpus", "boot=4", 1231 "--fs", 1232 "tag=virtiofs1,socket=/path/to/sock1,num_queues=4", 1233 ], 1234 r#"{ 1235 "payload": {"kernel": "/path/to/kernel"}, 1236 "memory" : { "shared": true, "size": 536870912 }, 1237 "cpus": {"boot_vcpus": 4, "max_vcpus": 4}, 1238 "fs": [ 1239 {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4} 1240 ] 1241 }"#, 1242 true, 1243 ), 1244 ( 1245 vec![ 1246 "cloud-hypervisor", "--kernel", "/path/to/kernel", 1247 "--memory", "shared=true", "--cpus", "boot=4", 1248 "--fs", 1249 "tag=virtiofs1,socket=/path/to/sock1,num_queues=4,queue_size=128" 1250 ], 1251 r#"{ 1252 "payload": {"kernel": "/path/to/kernel"}, 1253 "memory" : { "shared": true, "size": 536870912 }, 1254 "cpus": {"boot_vcpus": 4, "max_vcpus": 4}, 1255 "fs": [ 1256 {"tag": "virtiofs1", "socket": "/path/to/sock1", "num_queues": 4, "queue_size": 128} 1257 ] 1258 }"#, 1259 true, 1260 ), 1261 ] 1262 .iter() 1263 .for_each(|(cli, openapi, equal)| { 1264 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1265 }); 1266 } 1267 1268 #[test] 1269 fn test_valid_vm_config_pmem() { 1270 vec![ 1271 ( 1272 vec![ 1273 "cloud-hypervisor", 1274 "--kernel", 1275 "/path/to/kernel", 1276 "--pmem", 1277 "file=/path/to/img/1,size=1G", 1278 "file=/path/to/img/2,size=2G", 1279 ], 1280 r#"{ 1281 "payload": {"kernel": "/path/to/kernel"}, 1282 "pmem": [ 1283 {"file": "/path/to/img/1", "size": 1073741824}, 1284 {"file": "/path/to/img/2", "size": 2147483648} 1285 ] 1286 }"#, 1287 true, 1288 ), 1289 #[cfg(target_arch = "x86_64")] 1290 ( 1291 vec![ 1292 "cloud-hypervisor", 1293 "--kernel", 1294 "/path/to/kernel", 1295 "--pmem", 1296 "file=/path/to/img/1,size=1G,iommu=on", 1297 ], 1298 r#"{ 1299 "payload": {"kernel": "/path/to/kernel"}, 1300 "pmem": [ 1301 {"file": "/path/to/img/1", "size": 1073741824, "iommu": true} 1302 ], 1303 "iommu": true 1304 }"#, 1305 true, 1306 ), 1307 #[cfg(target_arch = "x86_64")] 1308 ( 1309 vec![ 1310 "cloud-hypervisor", 1311 "--kernel", 1312 "/path/to/kernel", 1313 "--pmem", 1314 "file=/path/to/img/1,size=1G,iommu=on", 1315 ], 1316 r#"{ 1317 "payload": {"kernel": "/path/to/kernel"}, 1318 "pmem": [ 1319 {"file": "/path/to/img/1", "size": 1073741824, "iommu": true} 1320 ] 1321 }"#, 1322 false, 1323 ), 1324 ] 1325 .iter() 1326 .for_each(|(cli, openapi, equal)| { 1327 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1328 }); 1329 } 1330 1331 #[test] 1332 fn test_valid_vm_config_serial_console() { 1333 vec![ 1334 ( 1335 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 1336 r#"{ 1337 "payload": {"kernel": "/path/to/kernel"}, 1338 "serial": {"mode": "Null"}, 1339 "console": {"mode": "Tty"} 1340 }"#, 1341 true, 1342 ), 1343 ( 1344 vec![ 1345 "cloud-hypervisor", 1346 "--kernel", 1347 "/path/to/kernel", 1348 "--serial", 1349 "null", 1350 "--console", 1351 "tty", 1352 ], 1353 r#"{ 1354 "payload": {"kernel": "/path/to/kernel"} 1355 }"#, 1356 true, 1357 ), 1358 ( 1359 vec![ 1360 "cloud-hypervisor", 1361 "--kernel", 1362 "/path/to/kernel", 1363 "--serial", 1364 "tty", 1365 "--console", 1366 "off", 1367 ], 1368 r#"{ 1369 "payload": {"kernel": "/path/to/kernel"}, 1370 "serial": {"mode": "Tty"}, 1371 "console": {"mode": "Off"} 1372 }"#, 1373 true, 1374 ), 1375 ] 1376 .iter() 1377 .for_each(|(cli, openapi, equal)| { 1378 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1379 }); 1380 } 1381 1382 #[test] 1383 fn test_valid_vm_config_serial_pty_console_pty() { 1384 vec![ 1385 ( 1386 vec!["cloud-hypervisor", "--kernel", "/path/to/kernel"], 1387 r#"{ 1388 "payload": {"kernel": "/path/to/kernel"}, 1389 "serial": {"mode": "Null"}, 1390 "console": {"mode": "Tty"} 1391 }"#, 1392 true, 1393 ), 1394 ( 1395 vec![ 1396 "cloud-hypervisor", 1397 "--kernel", 1398 "/path/to/kernel", 1399 "--serial", 1400 "null", 1401 "--console", 1402 "tty", 1403 ], 1404 r#"{ 1405 "payload": {"kernel": "/path/to/kernel"} 1406 }"#, 1407 true, 1408 ), 1409 ( 1410 vec![ 1411 "cloud-hypervisor", 1412 "--kernel", 1413 "/path/to/kernel", 1414 "--serial", 1415 "pty", 1416 "--console", 1417 "pty", 1418 ], 1419 r#"{ 1420 "payload": {"kernel": "/path/to/kernel"}, 1421 "serial": {"mode": "Pty"}, 1422 "console": {"mode": "Pty"} 1423 }"#, 1424 true, 1425 ), 1426 ] 1427 .iter() 1428 .for_each(|(cli, openapi, equal)| { 1429 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1430 }); 1431 } 1432 1433 #[test] 1434 #[cfg(target_arch = "x86_64")] 1435 fn test_valid_vm_config_devices() { 1436 vec![ 1437 ( 1438 vec![ 1439 "cloud-hypervisor", 1440 "--kernel", 1441 "/path/to/kernel", 1442 "--device", 1443 "path=/path/to/device/1", 1444 "path=/path/to/device/2", 1445 ], 1446 r#"{ 1447 "payload": {"kernel": "/path/to/kernel"}, 1448 "devices": [ 1449 {"path": "/path/to/device/1"}, 1450 {"path": "/path/to/device/2"} 1451 ] 1452 }"#, 1453 true, 1454 ), 1455 ( 1456 vec![ 1457 "cloud-hypervisor", 1458 "--kernel", 1459 "/path/to/kernel", 1460 "--device", 1461 "path=/path/to/device/1", 1462 "path=/path/to/device/2", 1463 ], 1464 r#"{ 1465 "payload": {"kernel": "/path/to/kernel"}, 1466 "devices": [ 1467 {"path": "/path/to/device/1"} 1468 ] 1469 }"#, 1470 false, 1471 ), 1472 ( 1473 vec![ 1474 "cloud-hypervisor", 1475 "--kernel", 1476 "/path/to/kernel", 1477 "--device", 1478 "path=/path/to/device,iommu=on", 1479 ], 1480 r#"{ 1481 "payload": {"kernel": "/path/to/kernel"}, 1482 "devices": [ 1483 {"path": "/path/to/device", "iommu": true} 1484 ], 1485 "iommu": true 1486 }"#, 1487 true, 1488 ), 1489 ( 1490 vec![ 1491 "cloud-hypervisor", 1492 "--kernel", 1493 "/path/to/kernel", 1494 "--device", 1495 "path=/path/to/device,iommu=on", 1496 ], 1497 r#"{ 1498 "payload": {"kernel": "/path/to/kernel"}, 1499 "devices": [ 1500 {"path": "/path/to/device", "iommu": true} 1501 ] 1502 }"#, 1503 false, 1504 ), 1505 ( 1506 vec![ 1507 "cloud-hypervisor", 1508 "--kernel", 1509 "/path/to/kernel", 1510 "--device", 1511 "path=/path/to/device,iommu=off", 1512 ], 1513 r#"{ 1514 "payload": {"kernel": "/path/to/kernel"}, 1515 "devices": [ 1516 {"path": "/path/to/device", "iommu": false} 1517 ] 1518 }"#, 1519 true, 1520 ), 1521 ] 1522 .iter() 1523 .for_each(|(cli, openapi, equal)| { 1524 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1525 }); 1526 } 1527 1528 #[test] 1529 fn test_valid_vm_config_vdpa() { 1530 vec![ 1531 ( 1532 vec![ 1533 "cloud-hypervisor", 1534 "--kernel", 1535 "/path/to/kernel", 1536 "--vdpa", 1537 "path=/path/to/device/1", 1538 "path=/path/to/device/2,num_queues=2", 1539 ], 1540 r#"{ 1541 "payload": {"kernel": "/path/to/kernel"}, 1542 "vdpa": [ 1543 {"path": "/path/to/device/1", "num_queues": 1}, 1544 {"path": "/path/to/device/2", "num_queues": 2} 1545 ] 1546 }"#, 1547 true, 1548 ), 1549 ( 1550 vec![ 1551 "cloud-hypervisor", 1552 "--kernel", 1553 "/path/to/kernel", 1554 "--vdpa", 1555 "path=/path/to/device/1", 1556 "path=/path/to/device/2", 1557 ], 1558 r#"{ 1559 "payload": {"kernel": "/path/to/kernel"}, 1560 "vdpa": [ 1561 {"path": "/path/to/device/1"} 1562 ] 1563 }"#, 1564 false, 1565 ), 1566 ] 1567 .iter() 1568 .for_each(|(cli, openapi, equal)| { 1569 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1570 }); 1571 } 1572 1573 #[test] 1574 fn test_valid_vm_config_vsock() { 1575 vec![ 1576 ( 1577 vec![ 1578 "cloud-hypervisor", 1579 "--kernel", 1580 "/path/to/kernel", 1581 "--vsock", 1582 "cid=123,socket=/path/to/sock/1", 1583 ], 1584 r#"{ 1585 "payload": {"kernel": "/path/to/kernel"}, 1586 "vsock": {"cid": 123, "socket": "/path/to/sock/1"} 1587 }"#, 1588 true, 1589 ), 1590 ( 1591 vec![ 1592 "cloud-hypervisor", 1593 "--kernel", 1594 "/path/to/kernel", 1595 "--vsock", 1596 "cid=124,socket=/path/to/sock/1", 1597 ], 1598 r#"{ 1599 "payload": {"kernel": "/path/to/kernel"}, 1600 "vsock": {"cid": 123, "socket": "/path/to/sock/1"} 1601 }"#, 1602 false, 1603 ), 1604 #[cfg(target_arch = "x86_64")] 1605 ( 1606 vec![ 1607 "cloud-hypervisor", 1608 "--kernel", 1609 "/path/to/kernel", 1610 "--vsock", 1611 "cid=123,socket=/path/to/sock/1,iommu=on", 1612 ], 1613 r#"{ 1614 "payload": {"kernel": "/path/to/kernel"}, 1615 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true}, 1616 "iommu": true 1617 }"#, 1618 true, 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 "payload": {"kernel": "/path/to/kernel"}, 1631 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": true} 1632 }"#, 1633 false, 1634 ), 1635 ( 1636 vec![ 1637 "cloud-hypervisor", 1638 "--kernel", 1639 "/path/to/kernel", 1640 "--vsock", 1641 "cid=123,socket=/path/to/sock/1,iommu=off", 1642 ], 1643 r#"{ 1644 "payload": {"kernel": "/path/to/kernel"}, 1645 "vsock": {"cid": 123, "socket": "/path/to/sock/1", "iommu": false} 1646 }"#, 1647 true, 1648 ), 1649 ] 1650 .iter() 1651 .for_each(|(cli, openapi, equal)| { 1652 compare_vm_config_cli_vs_json(cli, openapi, *equal); 1653 }); 1654 } 1655 } 1656