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