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