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