xref: /cloud-hypervisor/src/main.rs (revision 9af2968a7dc47b89bf07ea9dc5e735084efcfa3a)
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