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