From 7bc34521398f042a286b47b1ce7a5fe56050a8bd Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Sat, 8 Jul 2023 01:38:51 +0000 Subject: [PATCH] main: switch command parsing to use clap Partially revert 111225a2a562a465838219a82611b82393308ae7 and add the new dbus and pvpanic arguments. As we are switching back to clap observe the following changes. A few examples: 1. `-v -v -v` needs to be written as`-vvv` 2. `--disk D1 --disk D2` and others need to be written as `--disk D1 D2`. 3. `--option value` needs to be written as `--option=value.` Change integration tests to adapt to the breaking changes. Signed-off-by: Wei Liu Signed-off-by: Ravi kumar Veeramally --- Cargo.lock | 107 ++- Cargo.toml | 1 + fuzz/Cargo.lock | 187 ++++- performance-metrics/src/performance_tests.rs | 2 - src/main.rs | 712 ++++++++++--------- test_data/cloud-init/ubuntu/ci/user-data | 2 +- test_infra/src/lib.rs | 5 +- tests/integration.rs | 118 +-- vmm/Cargo.toml | 1 + vmm/src/config.rs | 141 ++++ 10 files changed, 807 insertions(+), 469 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dea0b002..09e9fa766 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,55 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -77,9 +126,9 @@ dependencies = [ [[package]] name = "argh" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab257697eb9496bf75526f0217b5ed64636a9cfafa78b8365c71bd283fcef93e" +checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" dependencies = [ "argh_derive", "argh_shared", @@ -87,21 +136,24 @@ dependencies = [ [[package]] name = "argh_derive" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.31", ] [[package]] name = "argh_shared" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] [[package]] name = "async-broadcast" @@ -364,6 +416,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + [[package]] name = "cloud-hypervisor" version = "35.0.0" @@ -371,6 +450,7 @@ dependencies = [ "anyhow", "api_client", "argh", + "clap", "dhat", "dirs", "epoll", @@ -395,6 +475,12 @@ dependencies = [ "zbus", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -2084,6 +2170,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "1.3.4" @@ -2364,6 +2456,7 @@ dependencies = [ "block", "blocking", "cfg-if", + "clap", "devices", "epoll", "event_monitor", diff --git a/Cargo.toml b/Cargo.toml index 831cc190e..535fffd0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ debug = true anyhow = "1.0.75" api_client = { path = "api_client" } argh = "0.1.9" +clap = { version = "4.3.11", features = ["string"] } dhat = { version = "0.3.2", optional = true } epoll = "4.3.3" event_monitor = { path = "event_monitor" } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4787862e6..9c1f4381b 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -10,6 +10,54 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -57,37 +105,6 @@ dependencies = [ "vmm-sys-util", ] -[[package]] -name = "argh" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" -dependencies = [ - "argh_derive", - "argh_shared", -] - -[[package]] -name = "argh_derive" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" -dependencies = [ - "argh_shared", - "proc-macro2", - "quote", - "syn 2.0.32", -] - -[[package]] -name = "argh_shared" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" -dependencies = [ - "serde", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -165,13 +182,40 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + [[package]] name = "cloud-hypervisor" version = "35.0.0" dependencies = [ "anyhow", "api_client", - "argh", + "clap", "epoll", "event_monitor", "hypervisor", @@ -213,6 +257,12 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "crc32c" version = "0.6.4" @@ -822,6 +872,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "1.4.1" @@ -1035,6 +1091,7 @@ dependencies = [ "bitflags 2.4.1", "block", "cfg-if", + "clap", "devices", "epoll", "event_monitor", @@ -1165,6 +1222,72 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "zerocopy" version = "0.7.11" diff --git a/performance-metrics/src/performance_tests.rs b/performance-metrics/src/performance_tests.rs index f17d0fad6..91507a388 100644 --- a/performance-metrics/src/performance_tests.rs +++ b/performance-metrics/src/performance_tests.rs @@ -358,13 +358,11 @@ pub fn performance_block_io(control: &PerformanceTestControl) -> f64 { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", format!("path={BLK_IO_TEST_IMG}").as_str(), ]) .default_net() diff --git a/src/main.rs b/src/main.rs index 8574b8f9d..2e51f34d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ #[macro_use] extern crate event_monitor; -use argh::FromArgs; +use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command}; use libc::EFD_NONBLOCK; use log::{warn, LevelFilter}; use option_parser::OptionParser; @@ -128,6 +128,10 @@ impl log::Log for Logger { fn flush(&self) {} } +fn prepare_default_values() -> (String, String, String) { + (default_vcpus(), default_memory(), default_rng()) +} + fn default_vcpus() -> String { format!( "boot={},max_phys_bits={}", @@ -144,280 +148,323 @@ fn default_rng() -> String { format!("src={}", config::DEFAULT_RNG_SOURCE) } -#[derive(FromArgs)] -/// Launch a cloud-hypervisor VMM. -pub struct TopLevel { - #[argh(option, long = "cpus", default = "default_vcpus()")] - /// boot=, max=, topology=:::, kvm_hyperv=on|off, max_phys_bits=, affinity=, features= - cpus: String, - - #[argh(option, long = "platform")] - /// num_pci_segments=, iommu_segments=, serial_number=, uuid=, oem_strings= - platform: Option, - - #[argh(option, long = "memory", default = "default_memory()")] - /// size=, mergeable=on|off, shared=on|off, hugepages=on|off, hugepage_size=, hotplug_method=acpi|virtio-mem, hotplug_size=, hotplugged_size=, prefault=on|off, thp=on|off - memory: String, - - #[argh(option, long = "memory-zone")] - /// size=, file=, shared=on|off, hugepages=on|off, hugepage_size=, host_numa_node=, id=, hotplug_size=, hotplugged_size=, prefault=on|off - memory_zone: Vec, - - #[argh(option, long = "firmware")] - /// path to firmware that is loaded in an architectural specific way - firmware: Option, - - #[argh(option, long = "kernel")] - /// path to kernel or firmware that supports a PVH entry point or architecture equivalent - kernel: Option, - - #[argh(option, long = "initramfs")] - /// path to initramfs image - initramfs: Option, - - #[argh(option, long = "cmdline")] - /// kernel command line - cmdline: Option, - - #[argh(option, long = "disk")] - /// path=, readonly=on|off, direct=on|off, iommu=on|off, num_queues=, queue_size=, vhost_user=on|off, socket=, bw_size=, bw_one_time_burst=, bw_refill_time=, ops_size=, ops_one_time_burst=, ops_refill_time=, id=, pci_segment= - disk: Vec, - - #[argh(option, long = "net")] - /// tap=, ip=, mask=, mac=, fd=, iommu=on|off, num_queues=, queue_size=, id=, vhost_user=, socket=, vhost_mode=client|server, bw_size=, bw_one_time_burst=, bw_refill_time=, ops_size=, ops_one_time_burst=, ops_refill_time=, pci_segment=, offload_tso=on|off, offload_ufo=on|off, offload_csum=on|off - net: Vec, - - #[argh(option, long = "rng", default = "default_rng()")] - /// src=, iommu=on|off - rng: String, - - #[argh(option, long = "balloon")] - /// size=, deflate_on_oom=on|off, free_page_reporting=on|off - balloon: Option, - - #[argh(option, long = "fs")] - /// tag=, socket=, num_queues=, queue_size=, id=, pci_segment= - fs: Vec, - - #[argh(option, long = "pmem")] - /// file=, size=, iommu=on|off, discard_writes=on|off, id=, pci_segment= - pmem: Vec, - - #[argh(option, long = "serial", default = "String::from(\"null\")")] - /// off|null|pty|tty|file=/path/to/a/file - serial: String, - - #[argh(option, long = "console", default = "String::from(\"tty\")")] - /// off|null|pty|tty|file=/path/to/a/file, iommu=on|off - console: String, - - #[argh(option, long = "device")] - /// path=, iommu=on|off, id=, pci_segment= - device: Vec, - - #[argh(option, long = "user-device")] - /// socket=, id=, pci_segment= - user_device: Vec, - - #[argh(option, long = "vdpa")] - /// path=, num_queues=, iommu=on|off, id=, pci_segment= - vdpa: Vec, - - #[argh(option, long = "vsock")] - /// cid=, socket=, iommu=on|off, id=, pci_segment= - vsock: Option, - - #[argh(switch, long = "pvpanic")] - /// enable pvpanic device - pvpanic: bool, - - #[argh(option, long = "numa")] - /// guest_numa_id=, cpus=, distances=, memory_zones=, sgx_epc_sections= - numa: Vec, - - #[argh(switch, long = "watchdog")] - /// enable virtio-watchdog - watchdog: bool, - - #[argh(switch, short = 'v')] - /// set the level of debugging output - verbosity: u8, - - #[argh(option, long = "log-file")] - /// path to log file - log_file: Option, - - #[argh(option, long = "api-socket")] - /// path=|fd= - api_socket: Option, - - #[cfg(feature = "dbus_api")] - #[argh(option, long = "dbus-service-name")] - /// well known name of the service - dbus_name: Option, - - #[cfg(feature = "dbus_api")] - #[argh(option, long = "dbus-object-path")] - /// object path to serve the dbus interface - dbus_path: Option, - - #[cfg(feature = "dbus_api")] - #[argh(switch, long = "dbus-system-bus")] - /// use the system bus instead of a session bus - dbus_system_bus: bool, - - #[argh(option, long = "event-monitor")] - /// path=|fd= - event_monitor: Option, - - #[argh(option, long = "restore")] - /// source_url=, prefault=on|off - restore: Option, - - #[argh(option, long = "seccomp", default = "String::from(\"true\")")] - /// seccomp configuration (true, false or log) - seccomp: String, - - #[argh(option, long = "tpm")] - /// socket= - tpm: Option, +fn create_app(default_vcpus: String, default_memory: String, default_rng: String) -> Command { + #[allow(clippy::let_and_return)] + let app = Command::new("cloud-hypervisor") + // 'BUILD_VERSION' is set by the build script 'build.rs' at + // compile time + .author(env!("CARGO_PKG_AUTHORS")) + .about("Launch a cloud-hypervisor VMM.") + .group(ArgGroup::new("vm-config").multiple(true)) + .group(ArgGroup::new("vmm-config").multiple(true)) + .group(ArgGroup::new("logging").multiple(true)) + .arg( + Arg::new("cpus") + .long("cpus") + .help( + "boot=,max=,\ + topology=:::,\ + kvm_hyperv=on|off,max_phys_bits=,\ + affinity=,\ + features=", + ) + .default_value(default_vcpus) + .group("vm-config"), + ) + .arg( + Arg::new("platform") + .long("platform") + .help("num_pci_segments=,iommu_segments=,serial_number=,uuid=,oem_strings=") + .num_args(1) + .group("vm-config"), + ) + .arg( + Arg::new("memory") + .long("memory") + .help( + "Memory parameters \ + \"size=,mergeable=on|off,shared=on|off,\ + hugepages=on|off,hugepage_size=,\ + hotplug_method=acpi|virtio-mem,\ + hotplug_size=,\ + hotplugged_size=,\ + prefault=on|off,thp=on|off\"", + ) + .default_value(default_memory) + .group("vm-config"), + ) + .arg( + Arg::new("memory-zone") + .long("memory-zone") + .help( + "User defined memory zone parameters \ + \"size=,file=,\ + shared=on|off,\ + hugepages=on|off,hugepage_size=,\ + host_numa_node=,\ + id=,hotplug_size=,\ + hotplugged_size=,\ + prefault=on|off\"", + ) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("firmware") + .long("firmware") + .help("Path to firmware that is loaded in an architectural specific way") + .num_args(1) + .group("vm-config"), + ) + .arg( + Arg::new("kernel") + .long("kernel") + .help( + "Path to kernel to load. This may be a kernel or firmware that supports a PVH \ + entry point (e.g. vmlinux) or architecture equivalent", + ) + .num_args(1) + .group("vm-config"), + ) + .arg( + Arg::new("initramfs") + .long("initramfs") + .help("Path to initramfs image") + .num_args(1) + .group("vm-config"), + ) + .arg( + Arg::new("cmdline") + .long("cmdline") + .help("Kernel command line") + .num_args(1) + .group("vm-config"), + ) + .arg( + Arg::new("disk") + .long("disk") + .help(config::DiskConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("net") + .long("net") + .help(config::NetConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("rng") + .long("rng") + .help( + "Random number generator parameters \"src=,iommu=on|off\"", + ) + .default_value(default_rng) + .group("vm-config"), + ) + .arg( + Arg::new("balloon") + .long("balloon") + .help(config::BalloonConfig::SYNTAX) + .num_args(1) + .group("vm-config"), + ) + .arg( + Arg::new("fs") + .long("fs") + .help(config::FsConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("pmem") + .long("pmem") + .help(config::PmemConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("serial") + .long("serial") + .help("Control serial port: off|null|pty|tty|file=/path/to/a/file") + .default_value("null") + .group("vm-config"), + ) + .arg( + Arg::new("console") + .long("console") + .help( + "Control (virtio) console: \"off|null|pty|tty|file=/path/to/a/file,iommu=on|off\"", + ) + .default_value("tty") + .group("vm-config"), + ) + .arg( + Arg::new("device") + .long("device") + .help(config::DeviceConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("user-device") + .long("user-device") + .help(config::UserDeviceConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("vdpa") + .long("vdpa") + .help(config::VdpaConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("vsock") + .long("vsock") + .help(config::VsockConfig::SYNTAX) + .num_args(1) + .group("vm-config"), + ) + .arg( + Arg::new("pvpanic") + .long("pvpanic") + .help("Enable pvpanic device") + .num_args(0) + .action(ArgAction::SetTrue) + .group("vm-config"), + ) + .arg( + Arg::new("numa") + .long("numa") + .help(config::NumaConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ) + .arg( + Arg::new("watchdog") + .long("watchdog") + .help("Enable virtio-watchdog") + .num_args(0) + .action(ArgAction::SetTrue) + .group("vm-config"), + ) + .arg( + Arg::new("v") + .short('v') + .action(ArgAction::Count) + .help("Sets the level of debugging output") + .group("logging"), + ) + .arg( + Arg::new("log-file") + .long("log-file") + .help("Log file. Standard error is used if not specified") + .num_args(1) + .group("logging"), + ) + .arg( + Arg::new("api-socket") + .long("api-socket") + .help("HTTP API socket (UNIX domain socket): path= or fd=.") + .num_args(1) + .group("vmm-config"), + ) + .arg( + Arg::new("event-monitor") + .long("event-monitor") + .help("File to report events on: path= or fd=") + .num_args(1) + .group("vmm-config"), + ) + .arg( + Arg::new("restore") + .long("restore") + .help(config::RestoreConfig::SYNTAX) + .num_args(1) + .group("vmm-config"), + ) + .arg( + Arg::new("seccomp") + .long("seccomp") + .num_args(1) + .value_parser(["true", "false", "log"]) + .default_value("true"), + ) + .arg( + Arg::new("tpm") + .long("tpm") + .num_args(1) + .help(config::TpmConfig::SYNTAX) + .group("vmm-config"), + ); #[cfg(target_arch = "x86_64")] - #[argh(option, long = "sgx-epc")] - /// id=, size=, prefault=on|off - sgx_epc: Vec, + let app = app.arg( + Arg::new("sgx-epc") + .long("sgx-epc") + .help(config::SgxEpcConfig::SYNTAX) + .num_args(1..) + .group("vm-config"), + ); #[cfg(feature = "guest_debug")] - #[argh(option, long = "gdb")] - /// path= - gdb: Option, + let app = app.arg( + Arg::new("gdb") + .long("gdb") + .help("GDB socket (UNIX domain socket): path=") + .num_args(1) + .group("vmm-config"), + ); - #[argh(switch, short = 'V', long = "version")] - /// print version information - version: bool, + #[cfg(feature = "dbus_api")] + let app = app + .arg( + Arg::new("dbus-service-name") + .long("dbus-service-name") + .help("Well known name of the device") + .num_args(1) + .group("vmm-config"), + ) + .arg( + Arg::new("dbus-object-path") + .long("dbus-object-path") + .help("Object path to serve the dbus interface") + .num_args(1) + .group("vmm-config"), + ) + .arg( + Arg::new("dbus-system-bus") + .long("dbus-system-bus") + .action(ArgAction::SetTrue) + .help("Use the system bus instead of a session bus") + .num_args(0) + .group("vmm-config"), + ); + + app.arg( + Arg::new("version") + .short('V') + .long("version") + .action(ArgAction::SetTrue) + .help("Print version") + .num_args(0), + ) } -impl TopLevel { - fn to_vm_params(&self) -> config::VmParams<'_> { - let cpus = &self.cpus; - let memory = &self.memory; - let memory_zones = if !self.memory_zone.is_empty() { - Some(self.memory_zone.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - let rng = &self.rng; - let serial = &self.serial; - let firmware = self.firmware.as_deref(); - let kernel = self.kernel.as_deref(); - let initramfs = self.initramfs.as_deref(); - let cmdline = self.cmdline.as_deref(); - - let disks = if !self.disk.is_empty() { - Some(self.disk.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - - let net = if !self.net.is_empty() { - Some(self.net.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - - let console = &self.console; - let balloon = self.balloon.as_deref(); - let fs = if !self.fs.is_empty() { - Some(self.fs.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - - let pmem = if !self.pmem.is_empty() { - Some(self.pmem.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - let devices = if !self.device.is_empty() { - Some(self.device.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - let user_devices = if !self.user_device.is_empty() { - Some(self.user_device.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - let vdpa = if !self.vdpa.is_empty() { - Some(self.vdpa.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - - let vsock = self.vsock.as_deref(); - - let pvpanic = self.pvpanic; - - #[cfg(target_arch = "x86_64")] - let sgx_epc = if !self.sgx_epc.is_empty() { - Some(self.sgx_epc.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - - let numa = if !self.numa.is_empty() { - Some(self.numa.iter().map(|x| x.as_str()).collect()) - } else { - None - }; - let watchdog = self.watchdog; - let platform = self.platform.as_deref(); - #[cfg(feature = "guest_debug")] - let gdb = self.gdb.is_some(); - let tpm = self.tpm.as_deref(); - - config::VmParams { - cpus, - memory, - memory_zones, - firmware, - kernel, - initramfs, - cmdline, - disks, - net, - rng, - balloon, - fs, - pmem, - serial, - console, - devices, - user_devices, - vdpa, - vsock, - pvpanic, - #[cfg(target_arch = "x86_64")] - sgx_epc, - numa, - watchdog, - #[cfg(feature = "guest_debug")] - gdb, - platform, - tpm, - } - } -} - -fn start_vmm(toplevel: TopLevel) -> Result, Error> { - let log_level = match toplevel.verbosity { +fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { + let log_level = match cmd_arguments.get_count("v") { 0 => LevelFilter::Warn, 1 => LevelFilter::Info, 2 => LevelFilter::Debug, _ => LevelFilter::Trace, }; - let log_file: Box = if let Some(ref file) = toplevel.log_file { + let log_file: Box = if let Some(ref file) = + cmd_arguments.get_one::("log-file") + { Box::new(std::fs::File::create(std::path::Path::new(file)).map_err(Error::LogFileCreation)?) } else { Box::new(std::io::stderr()) @@ -430,37 +477,47 @@ fn start_vmm(toplevel: TopLevel) -> Result, Error> { .map(|()| log::set_max_level(log_level)) .map_err(Error::LoggerSetup)?; - let (api_socket_path, api_socket_fd) = if let Some(ref socket_config) = toplevel.api_socket { - let mut parser = OptionParser::new(); - parser.add("path").add("fd"); - parser.parse(socket_config).unwrap_or_default(); + let (api_socket_path, api_socket_fd) = + if let Some(socket_config) = cmd_arguments.get_one::("api-socket") { + let mut parser = OptionParser::new(); + parser.add("path").add("fd"); + parser.parse(socket_config).unwrap_or_default(); - if let Some(fd) = parser.get("fd") { - ( - None, - Some(fd.parse::().map_err(Error::ParsingApiSocket)?), - ) - } else if let Some(path) = parser.get("path") { - (Some(path), None) + if let Some(fd) = parser.get("fd") { + ( + None, + Some(fd.parse::().map_err(Error::ParsingApiSocket)?), + ) + } else if let Some(path) = parser.get("path") { + (Some(path), None) + } else { + ( + cmd_arguments + .get_one::("api-socket") + .map(|s| s.to_string()), + None, + ) + } } else { - (toplevel.api_socket.as_ref().map(|s| s.to_string()), None) - } - } else { - (None, None) - }; + (None, None) + }; let (api_request_sender, api_request_receiver) = channel(); let api_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateApiEventFd)?; let api_request_sender_clone = api_request_sender.clone(); - let seccomp_action = match &toplevel.seccomp as &str { - "true" => SeccompAction::Trap, - "false" => SeccompAction::Allow, - "log" => SeccompAction::Log, - val => { - // The user providing an invalid value will be rejected - panic!("Invalid parameter {val} for \"--seccomp\" flag"); + let seccomp_action = if let Some(seccomp_value) = cmd_arguments.get_one::("seccomp") { + match seccomp_value as &str { + "true" => SeccompAction::Trap, + "false" => SeccompAction::Allow, + "log" => SeccompAction::Log, + val => { + // The user providing an invalid value will be rejected + panic!("Invalid parameter {val} for \"--seccomp\" flag"); + } } + } else { + SeccompAction::Trap }; if seccomp_action == SeccompAction::Trap { @@ -498,7 +555,7 @@ fn start_vmm(toplevel: TopLevel) -> Result, Error> { let hypervisor = hypervisor::new().map_err(Error::CreateHypervisor)?; #[cfg(feature = "guest_debug")] - let gdb_socket_path = if let Some(ref gdb_config) = toplevel.gdb { + let gdb_socket_path = if let Some(gdb_config) = cmd_arguments.get_one::("gdb") { let mut parser = OptionParser::new(); parser.add("path"); parser.parse(gdb_config).map_err(Error::ParsingGdb)?; @@ -519,8 +576,8 @@ fn start_vmm(toplevel: TopLevel) -> Result, Error> { let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?; #[allow(unused_mut)] - let mut event_monitor = toplevel - .event_monitor + let mut event_monitor = cmd_arguments + .get_one::("event-monitor") .as_ref() .map(|monitor_config| { let mut parser = OptionParser::new(); @@ -555,8 +612,11 @@ fn start_vmm(toplevel: TopLevel) -> Result, Error> { .transpose()?; #[cfg(feature = "dbus_api")] - let dbus_options = match (&toplevel.dbus_name, &toplevel.dbus_path) { - (Some(ref name), Some(ref path)) => { + let dbus_options = match ( + cmd_arguments.get_one::("dbus-service-name"), + cmd_arguments.get_one::("dbus-object-path"), + ) { + (Some(name), Some(path)) => { // monitor is either set (file based) or not. // if it's not set, create one without file support. let mut monitor = match event_monitor.take() { @@ -564,9 +624,9 @@ fn start_vmm(toplevel: TopLevel) -> Result, Error> { None => event_monitor::set_monitor(None).map_err(Error::EventMonitorIo)?, }; let options = DBusApiOptions { - service_name: name.to_owned(), - object_path: path.to_owned(), - system_bus: toplevel.dbus_system_bus, + service_name: name.to_string(), + object_path: path.to_string(), + system_bus: cmd_arguments.get_flag("dbus-system-bus"), event_monitor_rx: monitor.subscribe(), }; @@ -612,10 +672,11 @@ fn start_vmm(toplevel: TopLevel) -> Result, Error> { .map_err(Error::StartVmmThread)?; let r: Result<(), Error> = (|| { - let payload_present = toplevel.kernel.is_some() || toplevel.firmware.is_some(); + let payload_present = + cmd_arguments.contains_id("kernel") || cmd_arguments.contains_id("firmware"); if payload_present { - let vm_params = toplevel.to_vm_params(); + let vm_params = config::VmParams::from_arg_matches(&cmd_arguments); let vm_config = config::VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?; // Create and boot the VM based off the VM config we just built. @@ -627,12 +688,12 @@ fn start_vmm(toplevel: TopLevel) -> Result, Error> { ) .map_err(Error::VmCreate)?; vmm::api::vm_boot(api_evt.try_clone().unwrap(), sender).map_err(Error::VmBoot)?; - } else if let Some(restore_params) = toplevel.restore { + } else if let Some(restore_params) = cmd_arguments.get_one::("restore") { vmm::api::vm_restore( api_evt.try_clone().unwrap(), api_request_sender, Arc::new( - config::RestoreConfig::parse(&restore_params).map_err(Error::ParsingRestore)?, + config::RestoreConfig::parse(restore_params).map_err(Error::ParsingRestore)?, ), ) .map_err(Error::VmRestore)?; @@ -672,19 +733,20 @@ fn main() { // SAFETY: trivially safe let _ = unsafe { libc::umask(0o077) }; - let toplevel: TopLevel = argh::from_env(); + let (default_vcpus, default_memory, default_rng) = prepare_default_values(); + let cmd_arguments = create_app(default_vcpus, default_memory, default_rng).get_matches(); - if toplevel.version { + if cmd_arguments.get_flag("version") { println!("{} {}", env!("CARGO_BIN_NAME"), env!("BUILD_VERSION")); - if toplevel.verbosity != 0 { + if cmd_arguments.get_count("v") != 0 { println!("Enabled features: {:?}", vmm::feature_list()); } return; } - let exit_code = match start_vmm(toplevel) { + let exit_code = match start_vmm(cmd_arguments) { Ok(path) => { path.map(|s| std::fs::remove_file(s).ok()); 0 @@ -704,45 +766,18 @@ fn main() { #[cfg(test)] mod unit_tests { use crate::config::HotplugMethod; - use crate::TopLevel; + use crate::{create_app, prepare_default_values}; use std::path::PathBuf; use vmm::config::{ ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpusConfig, MemoryConfig, PayloadConfig, - RngConfig, VmConfig, + RngConfig, VmConfig, VmParams, }; - // Taken from argh - fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str { - std::path::Path::new(path) - .file_name() - .and_then(|s| s.to_str()) - .unwrap_or(default) - } - - // Some code taken from argh since it does not provide a helper to parse arbitrary strings fn get_vm_config_from_vec(args: &[&str]) -> VmConfig { - let strings: Vec = args.iter().map(|x| x.to_string()).collect(); - let cmd = cmd(&strings[0], &strings[0]); - let strs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect(); - let toplevel = ::from_args(&[cmd], &strs[1..]).unwrap_or_else( - |early_exit| { - std::process::exit(match early_exit.status { - Ok(()) => { - println!("{}", early_exit.output); - 0 - } - Err(()) => { - eprintln!( - "{}\nRun {} --help for more information.", - early_exit.output, cmd - ); - 1 - } - }) - }, - ); - - let vm_params = toplevel.to_vm_params(); + let (default_vcpus, default_memory, default_rng) = prepare_default_values(); + let cmd_arguments = + create_app(default_vcpus, default_memory, default_rng).get_matches_from(args); + let vm_params = VmParams::from_arg_matches(&cmd_arguments); VmConfig::parse(vm_params).unwrap() } @@ -1005,7 +1040,6 @@ mod unit_tests { "/path/to/kernel", "--disk", "path=/path/to/disk/1", - "--disk", "path=/path/to/disk/2", ], r#"{ @@ -1024,7 +1058,6 @@ mod unit_tests { "/path/to/kernel", "--disk", "path=/path/to/disk/1", - "--disk", "path=/path/to/disk/2", ], r#"{ @@ -1299,7 +1332,6 @@ mod unit_tests { "--memory", "shared=true", "--fs", "tag=virtiofs1,socket=/path/to/sock1", - "--fs", "tag=virtiofs2,socket=/path/to/sock2", ], r#"{ @@ -1318,7 +1350,6 @@ mod unit_tests { "--memory", "shared=true", "--fs", "tag=virtiofs1,socket=/path/to/sock1", - "--fs", "tag=virtiofs2,socket=/path/to/sock2", ], r#"{ @@ -1380,7 +1411,6 @@ mod unit_tests { "/path/to/kernel", "--pmem", "file=/path/to/img/1,size=1G", - "--pmem", "file=/path/to/img/2,size=2G", ], r#"{ @@ -1547,7 +1577,6 @@ mod unit_tests { "/path/to/kernel", "--device", "path=/path/to/device/1", - "--device", "path=/path/to/device/2", ], r#"{ @@ -1566,7 +1595,6 @@ mod unit_tests { "/path/to/kernel", "--device", "path=/path/to/device/1", - "--device", "path=/path/to/device/2", ], r#"{ @@ -1643,7 +1671,6 @@ mod unit_tests { "/path/to/kernel", "--vdpa", "path=/path/to/device/1", - "--vdpa", "path=/path/to/device/2,num_queues=2", ], r#"{ @@ -1662,7 +1689,6 @@ mod unit_tests { "/path/to/kernel", "--vdpa", "path=/path/to/device/1", - "--vdpa", "path=/path/to/device/2", ], r#"{ diff --git a/test_data/cloud-init/ubuntu/ci/user-data b/test_data/cloud-init/ubuntu/ci/user-data index 3c3dd4778..433f6dbc8 100644 --- a/test_data/cloud-init/ubuntu/ci/user-data +++ b/test_data/cloud-init/ubuntu/ci/user-data @@ -47,7 +47,7 @@ write_files: # 1G ram requires 512 pages echo 512 | sudo tee /proc/sys/vm/nr_hugepages sudo chmod a+rwX /dev/hugepages - /mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules root=/dev/vda1 VFIOTAG" --disk path=/mnt/focal-server-cloudimg-amd64-custom-20210609-0.raw --disk path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,hotplug_size=1G,hugepages=on --device path=/sys/bus/pci/devices/0000:00:05.0/ --device path=/sys/bus/pci/devices/0000:00:07.0/ --device path=/sys/bus/pci/devices/0000:00:08.0/ --api-socket /tmp/ch_api.sock + /mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules root=/dev/vda1 VFIOTAG" --disk path=/mnt/focal-server-cloudimg-amd64-custom-20210609-0.raw path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,hotplug_size=1G,hugepages=on --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:07.0/ path=/sys/bus/pci/devices/0000:00:08.0/ --api-socket=/tmp/ch_api.sock - path: /etc/systemd/system/notify-booted.service diff --git a/test_infra/src/lib.rs b/test_infra/src/lib.rs index 6857d13b5..b6237bf29 100644 --- a/test_infra/src/lib.rs +++ b/test_infra/src/lib.rs @@ -1152,7 +1152,7 @@ impl ToString for VerbosityLevel { match self { Warn => "".to_string(), Info => "-v".to_string(), - Debug => "-v -v".to_string(), + Debug => "-vv".to_string(), } } } @@ -1203,7 +1203,7 @@ impl<'a> GuestCommand<'a> { self.command.arg("-v"); } Debug => { - self.command.args(["-v", "-v"]); + self.command.args(["-vv"]); } }; @@ -1271,7 +1271,6 @@ impl<'a> GuestCommand<'a> { .unwrap() ) .as_str(), - "--disk", format!( "path={}", self.guest.disk_config.disk(DiskType::CloudInit).unwrap() diff --git a/tests/integration.rs b/tests/integration.rs index 68418fb2c..1e0beacb8 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -102,23 +102,21 @@ impl TargetApi { ) } - fn guest_args(&self) -> Vec<&str> { + fn guest_args(&self) -> Vec { match self { TargetApi::HttpApi(api_socket) => { - vec!["--api-socket", api_socket.as_str()] + vec![format!("--api-socket={}", api_socket.as_str())] } TargetApi::DBusApi(service_name, object_path) => { vec![ - "--dbus-service-name", - service_name.as_str(), - "--dbus-object-path", - object_path.as_str(), + format!("--dbus-service-name={}", service_name.as_str()), + format!("--dbus-object-path={}", object_path.as_str()), ] } } } - fn remote_args(&self) -> Vec<&str> { + fn remote_args(&self) -> Vec { // `guest_args` and `remote_args` are consistent with each other self.guest_args() } @@ -625,7 +623,7 @@ fn prepare_swtpm_daemon(tmp_dir: &TempDir) -> (std::process::Command, String) { fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args(["--api-socket", api_socket, command]); + cmd.args([&format!("--api-socket={api_socket}"), command]); if let Some(arg) = arg { cmd.arg(arg); @@ -643,7 +641,7 @@ fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool { fn remote_command_w_output(api_socket: &str, command: &str, arg: Option<&str>) -> (bool, Vec) { let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args(["--api-socket", api_socket, command]); + cmd.args([&format!("--api-socket={api_socket}"), command]); if let Some(arg) = arg { cmd.arg(arg); @@ -662,18 +660,18 @@ fn resize_command( event_file: Option<&str>, ) -> bool { let mut cmd = Command::new(clh_command("ch-remote")); - cmd.args(["--api-socket", api_socket, "resize"]); + cmd.args([&format!("--api-socket={api_socket}"), "resize"]); if let Some(desired_vcpus) = desired_vcpus { - cmd.args(["--cpus", &format!("{desired_vcpus}")]); + cmd.arg(format!("--cpus={desired_vcpus}")); } if let Some(desired_ram) = desired_ram { - cmd.args(["--memory", &format!("{desired_ram}")]); + cmd.arg(format!("--memory={desired_ram}")); } if let Some(desired_balloon) = desired_balloon { - cmd.args(["--balloon", &format!("{desired_balloon}")]); + cmd.arg(format!("--balloon={desired_balloon}")); } let ret = cmd.status().expect("Failed to launch ch-remote").success(); @@ -698,13 +696,10 @@ fn resize_command( fn resize_zone_command(api_socket: &str, id: &str, desired_size: &str) -> bool { let mut cmd = Command::new(clh_command("ch-remote")); cmd.args([ - "--api-socket", - api_socket, + &format!("--api-socket={api_socket}"), "resize-zone", - "--id", - id, - "--size", - desired_size, + &format!("--id={id}"), + &format!("--size={desired_size}"), ]); cmd.status().expect("Failed to launch ch-remote").success() @@ -757,7 +752,7 @@ fn setup_ovs_dpdk_guests( .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() - .args(["--net", guest1.default_net_string().as_str(), "--net", "vhost_user=true,socket=/tmp/dpdkvhostclient1,num_queues=2,queue_size=256,vhost_mode=server"]) + .args(["--net", guest1.default_net_string().as_str(), "vhost_user=true,socket=/tmp/dpdkvhostclient1,num_queues=2,queue_size=256,vhost_mode=server"]) .capture_output() .spawn() .unwrap(); @@ -807,7 +802,7 @@ fn setup_ovs_dpdk_guests( .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .default_disks() - .args(["--net", guest2.default_net_string().as_str(), "--net", "vhost_user=true,socket=/tmp/dpdkvhostclient2,num_queues=2,queue_size=256,vhost_mode=server"]) + .args(["--net", guest2.default_net_string().as_str(), "vhost_user=true,socket=/tmp/dpdkvhostclient2,num_queues=2,queue_size=256,vhost_mode=server"]) .capture_output() .spawn() .unwrap(); @@ -1040,17 +1035,13 @@ fn _test_guest_numa_nodes(acpi: bool) { .args([ "--memory-zone", "id=mem0,size=1G,hotplug_size=3G", - "--memory-zone", "id=mem1,size=2G,hotplug_size=3G", - "--memory-zone", "id=mem2,size=3G,hotplug_size=3G", ]) .args([ "--numa", "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", - "--numa", "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", - "--numa", "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", ]) .args(["--kernel", kernel_path.to_str().unwrap()]) @@ -1353,13 +1344,11 @@ fn test_vhost_user_blk( guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", blk_params.as_str(), ]) .default_net() @@ -1499,7 +1488,6 @@ fn test_boot_from_vhost_user_blk( .args([ "--disk", blk_boot_params.as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() @@ -2195,7 +2183,6 @@ fn _test_virtio_iommu(acpi: bool) { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={},iommu=on", guest.disk_config.disk(DiskType::CloudInit).unwrap() @@ -2617,9 +2604,7 @@ mod common_parallel { .args([ "--memory-zone", "id=mem0,size=1G,hotplug_size=2G", - "--memory-zone", "id=mem1,size=1G,shared=on", - "--memory-zone", "id=mem2,size=1G,host_numa_node=0,hotplug_size=2G", ]) .args(["--kernel", kernel_path.to_str().unwrap()]) @@ -2876,13 +2861,11 @@ mod common_parallel { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", format!("path={test_disk_path},pci_segment=15").as_str(), ]) .capture_output() @@ -3103,13 +3086,11 @@ mod common_parallel { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", format!( "path={},readonly=on,direct=on,num_queues=4,_disable_io_uring={}", blk_file_path.to_str().unwrap(), @@ -3276,13 +3257,11 @@ mod common_parallel { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", format!("path={vhdx_path}").as_str(), ]) .default_net() @@ -3352,7 +3331,6 @@ mod common_parallel { .args([ "--disk", format!("path={},direct=on", os_path.as_path().to_str().unwrap()).as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() @@ -3730,9 +3708,7 @@ mod common_parallel { .args([ "--net", guest.default_net_string().as_str(), - "--net", "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.0", - "--net", "tap=mytap1,mac=fe:1f:9e:e1:60:f2,ip=192.168.4.1,mask=255.255.255.0", ]) .capture_output() @@ -4294,15 +4270,12 @@ mod common_parallel { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", format!("path={}", vfio_disk_path.to_str().unwrap()).as_str(), - "--disk", format!("path={},iommu=on", blk_file_path.to_str().unwrap()).as_str(), ]) .args([ @@ -4315,19 +4288,16 @@ mod common_parallel { .args([ "--net", format!("tap={},mac={}", vfio_tap0, guest.network.guest_mac).as_str(), - "--net", format!( "tap={},mac={},iommu=on", vfio_tap1, guest.network.l2_guest_mac1 ) .as_str(), - "--net", format!( "tap={},mac={},iommu=on", vfio_tap2, guest.network.l2_guest_mac2 ) .as_str(), - "--net", format!( "tap={},mac={},iommu=on", vfio_tap3, guest.network.l2_guest_mac3 @@ -4405,7 +4375,7 @@ mod common_parallel { let vfio_hotplug_output = guest .ssh_command_l1( "sudo /mnt/ch-remote \ - --api-socket /tmp/ch_api.sock \ + --api-socket=/tmp/ch_api.sock \ add-device path=/sys/bus/pci/devices/0000:00:09.0,id=vfio123", ) .unwrap(); @@ -4445,7 +4415,7 @@ mod common_parallel { guest .ssh_command_l1( "sudo /mnt/ch-remote \ - --api-socket /tmp/ch_api.sock \ + --api-socket=/tmp/ch_api.sock \ remove-device vfio123", ) .unwrap(); @@ -4476,8 +4446,8 @@ mod common_parallel { guest .ssh_command_l1( "sudo /mnt/ch-remote \ - --api-socket /tmp/ch_api.sock \ - resize --memory 1073741824", + --api-socket=/tmp/ch_api.sock \ + resize --memory=1073741824", ) .unwrap(); assert!(guest.get_total_memory_l2().unwrap_or_default() > 960_000); @@ -4600,7 +4570,6 @@ mod common_parallel { .args([ "--net", guest.default_net_string().as_str(), - "--net", "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.0", ]) .capture_output() @@ -5339,13 +5308,11 @@ mod common_parallel { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", format!("path={}", &loop_dev).as_str(), ]) .default_net() @@ -5917,7 +5884,6 @@ mod common_parallel { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", cloudinit_params.as_str(), ]) .args(["--net", net_params.as_str()]) @@ -8213,9 +8179,7 @@ mod windows { .args([ "--net", windows_guest.guest().default_net_string().as_str(), - "--net", "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.0", - "--net", "tap=mytap42,mac=fe:1f:9e:e1:60:f2,ip=192.168.4.1,mask=255.255.255.0", ]) .capture_output() @@ -8368,15 +8332,12 @@ mod vfio { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", format!("path={}", vfio_disk_path.to_str().unwrap()).as_str(), - "--disk", format!("path={},iommu=on", blk_file_path.to_str().unwrap()).as_str(), ]) .args([ @@ -8389,19 +8350,16 @@ mod vfio { .args([ "--net", format!("tap={},mac={}", vfio_tap0, guest.network.guest_mac).as_str(), - "--net", format!( "tap={},mac={},iommu=on", vfio_tap1, guest.network.l2_guest_mac1 ) .as_str(), - "--net", format!( "tap={},mac={},iommu=on", vfio_tap2, guest.network.l2_guest_mac2 ) .as_str(), - "--net", format!( "tap={},mac={},iommu=on", vfio_tap3, guest.network.l2_guest_mac3 @@ -8479,7 +8437,7 @@ mod vfio { let vfio_hotplug_output = guest .ssh_command_l1( "sudo /mnt/ch-remote \ - --api-socket /tmp/ch_api.sock \ + --api-socket=/tmp/ch_api.sock \ add-device path=/sys/bus/pci/devices/0000:00:09.0,id=vfio123", ) .unwrap(); @@ -8519,7 +8477,7 @@ mod vfio { guest .ssh_command_l1( "sudo /mnt/ch-remote \ - --api-socket /tmp/ch_api.sock \ + --api-socket=/tmp/ch_api.sock \ remove-device vfio123", ) .unwrap(); @@ -8550,8 +8508,8 @@ mod vfio { guest .ssh_command_l1( "sudo /mnt/ch-remote \ - --api-socket /tmp/ch_api.sock \ - resize --memory 1073741824", + --api-socket=/tmp/ch_api.sock \ + resize --memory=1073741824", ) .unwrap(); assert!(guest.get_total_memory_l2().unwrap_or_default() > 960_000); @@ -8709,8 +8667,7 @@ mod live_migration { // Start to receive migration from the destintion VM let mut receive_migration = Command::new(clh_command("ch-remote")) .args([ - "--api-socket", - dest_api_socket, + &format!("--api-socket={dest_api_socket}"), "receive-migration", &format! {"unix:{migration_socket}"}, ]) @@ -8723,15 +8680,14 @@ mod live_migration { // Start to send migration from the source VM let mut args = [ - "--api-socket".to_string(), - src_api_socket.to_string(), + format!("--api-socket={}", &src_api_socket), "send-migration".to_string(), format! {"unix:{migration_socket}"}, ] .to_vec(); if local { - args.insert(3, "--local".to_string()); + args.insert(2, "--local".to_string()); } let mut send_migration = Command::new(clh_command("ch-remote")) @@ -9190,15 +9146,11 @@ mod live_migration { "size=0,hotplug_method=virtio-mem,shared=on", "--memory-zone", "id=mem0,size=1G,hotplug_size=4G,shared=on", - "--memory-zone", "id=mem1,size=1G,hotplug_size=4G,shared=on", - "--memory-zone", "id=mem2,size=2G,hotplug_size=4G,shared=on", "--numa", "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", - "--numa", "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", - "--numa", "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", ] } else { @@ -9207,15 +9159,11 @@ mod live_migration { "size=0,hotplug_method=virtio-mem", "--memory-zone", "id=mem0,size=1G,hotplug_size=4G", - "--memory-zone", "id=mem1,size=1G,hotplug_size=4G", - "--memory-zone", "id=mem2,size=2G,hotplug_size=4G", "--numa", "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0", - "--numa", "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1", - "--numa", "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2", ] }; @@ -9773,43 +9721,51 @@ mod live_migration { } #[test] + #[ignore = "See #5791"] fn test_live_upgrade_basic() { _test_live_migration(true, false) } #[test] + #[ignore = "See #5791"] fn test_live_upgrade_local() { _test_live_migration(true, true) } #[test] #[cfg(not(feature = "mshv"))] + #[ignore = "See #5791"] fn test_live_upgrade_numa() { _test_live_migration_numa(true, false) } #[test] #[cfg(not(feature = "mshv"))] + #[ignore = "See #5791"] fn test_live_upgrade_numa_local() { _test_live_migration_numa(true, true) } #[test] + #[ignore = "See #5791"] fn test_live_upgrade_watchdog() { _test_live_migration_watchdog(true, false) } #[test] + #[ignore = "See #5791"] fn test_live_upgrade_watchdog_local() { _test_live_migration_watchdog(true, true) } #[test] + #[ignore = "See #5791"] fn test_live_upgrade_balloon() { _test_live_migration_balloon(true, false) } #[test] + #[ignore = "See #5791"] fn test_live_upgrade_balloon_local() { _test_live_migration_balloon(true, true) } @@ -9838,6 +9794,7 @@ mod live_migration { #[test] #[cfg(target_arch = "x86_64")] #[cfg(not(feature = "mshv"))] + #[ignore = "See #5791"] fn test_live_upgrade_ovs_dpdk() { _test_live_migration_ovs_dpdk(true, false); } @@ -9845,6 +9802,7 @@ mod live_migration { #[test] #[cfg(target_arch = "x86_64")] #[cfg(not(feature = "mshv"))] + #[ignore = "See #5791"] fn test_live_upgrade_ovs_dpdk_local() { _test_live_migration_ovs_dpdk(true, true); } @@ -10043,13 +10001,11 @@ mod rate_limiter { guest.disk_config.disk(DiskType::OperatingSystem).unwrap() ) .as_str(), - "--disk", format!( "path={}", guest.disk_config.disk(DiskType::CloudInit).unwrap() ) .as_str(), - "--disk", test_blk_params.as_str(), ]) .default_net() diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 73030e49e..51ad38375 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -24,6 +24,7 @@ bitflags = "2.4.1" block = { path = "../block" } blocking = { version = "1.3.0", optional = true } cfg-if = "1.0.0" +clap = "4.3.11" devices = { path = "../devices" } epoll = "4.3.3" event_monitor = { path = "../event_monitor" } diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 93b1f67bb..6a005ba0f 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -4,6 +4,7 @@ // pub use crate::vm_config::*; +use clap::ArgMatches; use option_parser::{ ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple, }; @@ -402,6 +403,90 @@ pub struct VmParams<'a> { pub tpm: Option<&'a str>, } +impl<'a> VmParams<'a> { + pub fn from_arg_matches(args: &'a ArgMatches) -> Self { + // These .unwrap()s cannot fail as there is a default value defined + let cpus = args.get_one::("cpus").unwrap(); + let memory = args.get_one::("memory").unwrap(); + let memory_zones: Option> = args + .get_many::("memory-zone") + .map(|x| x.map(|y| y as &str).collect()); + let rng = args.get_one::("rng").unwrap(); + let serial = args.get_one::("serial").unwrap(); + let firmware = args.get_one::("firmware").map(|x| x as &str); + let kernel = args.get_one::("kernel").map(|x| x as &str); + let initramfs = args.get_one::("initramfs").map(|x| x as &str); + let cmdline = args.get_one::("cmdline").map(|x| x as &str); + let disks: Option> = args + .get_many::("disk") + .map(|x| x.map(|y| y as &str).collect()); + let net: Option> = args + .get_many::("net") + .map(|x| x.map(|y| y as &str).collect()); + let console = args.get_one::("console").unwrap(); + let balloon = args.get_one::("balloon").map(|x| x as &str); + let fs: Option> = args + .get_many::("fs") + .map(|x| x.map(|y| y as &str).collect()); + let pmem: Option> = args + .get_many::("pmem") + .map(|x| x.map(|y| y as &str).collect()); + let devices: Option> = args + .get_many::("device") + .map(|x| x.map(|y| y as &str).collect()); + let user_devices: Option> = args + .get_many::("user-device") + .map(|x| x.map(|y| y as &str).collect()); + let vdpa: Option> = args + .get_many::("vdpa") + .map(|x| x.map(|y| y as &str).collect()); + let vsock: Option<&str> = args.get_one::("vsock").map(|x| x as &str); + let pvpanic = args.get_flag("pvpanic"); + #[cfg(target_arch = "x86_64")] + let sgx_epc: Option> = args + .get_many::("sgx-epc") + .map(|x| x.map(|y| y as &str).collect()); + let numa: Option> = args + .get_many::("numa") + .map(|x| x.map(|y| y as &str).collect()); + let watchdog = args.get_flag("watchdog"); + let platform = args.get_one::("platform").map(|x| x as &str); + #[cfg(feature = "guest_debug")] + let gdb = args.contains_id("gdb"); + let tpm: Option<&str> = args.get_one::("tpm").map(|x| x as &str); + VmParams { + cpus, + memory, + memory_zones, + firmware, + kernel, + initramfs, + cmdline, + disks, + net, + rng, + balloon, + fs, + pmem, + serial, + console, + devices, + user_devices, + vdpa, + vsock, + pvpanic, + #[cfg(target_arch = "x86_64")] + sgx_epc, + numa, + watchdog, + #[cfg(feature = "guest_debug")] + gdb, + platform, + tpm, + } + } +} + #[derive(Debug)] pub enum ParseHotplugMethodError { InvalidValue(String), @@ -776,6 +861,14 @@ impl MemoryConfig { } impl DiskConfig { + pub const SYNTAX: &'static str = "Disk parameters \ + \"path=,readonly=on|off,direct=on|off,iommu=on|off,\ + num_queues=,queue_size=,\ + vhost_user=on|off,socket=,\ + bw_size=,bw_one_time_burst=,bw_refill_time=,\ + ops_size=,ops_one_time_burst=,ops_refill_time=,\ + id=,pci_segment=\""; + pub fn parse(disk: &str) -> Result { let mut parser = OptionParser::new(); parser @@ -951,6 +1044,14 @@ impl FromStr for VhostMode { } impl NetConfig { + pub const SYNTAX: &'static str = "Network parameters \ + \"tap=,ip=,mask=,mac=,fd=,iommu=on|off,\ + num_queues=,queue_size=,id=,\ + vhost_user=,socket=,vhost_mode=client|server,\ + bw_size=,bw_one_time_burst=,bw_refill_time=,\ + ops_size=,ops_one_time_burst=,ops_refill_time=,pci_segment=\ + offload_tso=on|off,offload_ufo=on|off,offload_csum=on|off\""; + pub fn parse(net: &str) -> Result { let mut parser = OptionParser::new(); @@ -1191,6 +1292,10 @@ impl RngConfig { } impl BalloonConfig { + pub const SYNTAX: &'static str = + "Balloon parameters \"size=,deflate_on_oom=on|off,\ + free_page_reporting=on|off\""; + pub fn parse(balloon: &str) -> Result { let mut parser = OptionParser::new(); parser.add("size"); @@ -1225,6 +1330,10 @@ impl BalloonConfig { } impl FsConfig { + pub const SYNTAX: &'static str = "virtio-fs parameters \ + \"tag=,socket=,num_queues=,\ + queue_size=,id=,pci_segment=\""; + pub fn parse(fs: &str) -> Result { let mut parser = OptionParser::new(); parser @@ -1289,6 +1398,10 @@ impl FsConfig { } impl PmemConfig { + pub const SYNTAX: &'static str = "Persistent memory parameters \ + \"file=,size=,iommu=on|off,\ + discard_writes=on|off,id=,pci_segment=\""; + pub fn parse(pmem: &str) -> Result { let mut parser = OptionParser::new(); parser @@ -1402,6 +1515,9 @@ impl ConsoleConfig { } impl DeviceConfig { + pub const SYNTAX: &'static str = + "Direct device assignment parameters \"path=,iommu=on|off,id=,pci_segment=\""; + pub fn parse(device: &str) -> Result { let mut parser = OptionParser::new(); parser.add("path").add("id").add("iommu").add("pci_segment"); @@ -1448,6 +1564,9 @@ impl DeviceConfig { } impl UserDeviceConfig { + pub const SYNTAX: &'static str = + "Userspace device socket=,id=,pci_segment=\""; + pub fn parse(user_device: &str) -> Result { let mut parser = OptionParser::new(); parser.add("socket").add("id").add("pci_segment"); @@ -1490,6 +1609,10 @@ impl UserDeviceConfig { } impl VdpaConfig { + pub const SYNTAX: &'static str = "vDPA device \ + \"path=,num_queues=,iommu=on|off,\ + id=,pci_segment=\""; + pub fn parse(vdpa: &str) -> Result { let mut parser = OptionParser::new(); parser @@ -1546,6 +1669,9 @@ impl VdpaConfig { } impl VsockConfig { + pub const SYNTAX: &'static str = "Virtio VSOCK parameters \ + \"cid=,socket=,iommu=on|off,id=,pci_segment=\""; + pub fn parse(vsock: &str) -> Result { let mut parser = OptionParser::new(); parser @@ -1603,6 +1729,9 @@ impl VsockConfig { #[cfg(target_arch = "x86_64")] impl SgxEpcConfig { + pub const SYNTAX: &'static str = "SGX EPC parameters \ + \"id=,size=,prefault=on|off\""; + pub fn parse(sgx_epc: &str) -> Result { let mut parser = OptionParser::new(); parser.add("id").add("size").add("prefault"); @@ -1625,6 +1754,10 @@ impl SgxEpcConfig { } impl NumaConfig { + pub const SYNTAX: &'static str = "Settings related to a given NUMA node \ + \"guest_numa_id=,cpus=,distances=,\ + memory_zones=,sgx_epc_sections=\""; + pub fn parse(numa: &str) -> Result { let mut parser = OptionParser::new(); parser @@ -1689,6 +1822,11 @@ pub struct RestoreConfig { } impl RestoreConfig { + pub const SYNTAX: &'static str = "Restore from a VM snapshot. \ + \nRestore parameters \"source_url=,prefault=on|off\" \ + \n`source_url` should be a valid URL (e.g file:///foo/bar or tcp://192.168.1.10/foo) \ + \n`prefault` brings memory pages in when enabled (disabled by default)"; + pub fn parse(restore: &str) -> Result { let mut parser = OptionParser::new(); parser.add("source_url").add("prefault"); @@ -1712,6 +1850,9 @@ impl RestoreConfig { } impl TpmConfig { + pub const SYNTAX: &'static str = "TPM device \ + \"(UNIX Domain Socket from swtpm) socket=\""; + pub fn parse(tpm: &str) -> Result { let mut parser = OptionParser::new(); parser.add("socket");