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