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