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