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