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