diff --git a/.github/workflows/static-checks.yaml b/.github/workflows/static-checks.yaml index 007ddcd6e9..f8979912ab 100644 --- a/.github/workflows/static-checks.yaml +++ b/.github/workflows/static-checks.yaml @@ -94,3 +94,26 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'force-skip-ci') }} run: | cd ${GOPATH}/src/github.com/${{ github.repository }} && sudo -E PATH="$PATH" make test + + test-dragonball: + runs-on: self-hosted + env: + RUST_BACKTRACE: "1" + steps: + - uses: actions/checkout@v3 + - name: Set env + if: ${{ !contains(github.event.pull_request.labels.*.name, 'force-skip-ci') }} + run: | + echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV + - name: Install Rust + if: ${{ !contains(github.event.pull_request.labels.*.name, 'force-skip-ci') }} + run: | + ./ci/install_rust.sh + PATH=$PATH:"$HOME/.cargo/bin" + - name: Run Unit Test + if: ${{ !contains(github.event.pull_request.labels.*.name, 'force-skip-ci') }} + run: | + cd src/dragonball + /root/.cargo/bin/cargo version + rustc --version + sudo -E env PATH=$PATH LIBC=gnu SUPPORT_VIRTUALIZATION=true make test diff --git a/Makefile b/Makefile index 4d2be6b4d8..c76af04a91 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ COMPONENTS = COMPONENTS += libs COMPONENTS += agent +COMPONENTS += dragonball COMPONENTS += runtime COMPONENTS += runtime-rs @@ -15,9 +16,10 @@ COMPONENTS += runtime-rs TOOLS = TOOLS += agent-ctl -TOOLS += trace-forwarder -TOOLS += runk +TOOLS += kata-ctl TOOLS += log-parser +TOOLS += runk +TOOLS += trace-forwarder STANDARD_TARGETS = build check clean install test vendor diff --git a/README.md b/README.md index 2b0759a2c0..6972ed2786 100644 --- a/README.md +++ b/README.md @@ -119,10 +119,8 @@ The table below lists the core parts of the project: | [runtime](src/runtime) | core | Main component run by a container manager and providing a containerd shimv2 runtime implementation. | | [runtime-rs](src/runtime-rs) | core | The Rust version runtime. | | [agent](src/agent) | core | Management process running inside the virtual machine / POD that sets up the container environment. | -| [libraries](src/libs) | core | Library crates shared by multiple Kata Container components or published to [`crates.io`](https://crates.io/index.html) | | [`dragonball`](src/dragonball) | core | An optional built-in VMM brings out-of-the-box Kata Containers experience with optimizations on container workloads | | [documentation](docs) | documentation | Documentation common to all components (such as design and install documentation). | -| [libraries](src/libs) | core | Library crates shared by multiple Kata Container components or published to [`crates.io`](https://crates.io/index.html) | | [tests](https://github.com/kata-containers/tests) | tests | Excludes unit tests which live with the main code. | ### Additional components @@ -135,6 +133,7 @@ The table below lists the remaining parts of the project: | [kernel](https://www.kernel.org) | kernel | Linux kernel used by the hypervisor to boot the guest image. Patches are stored [here](tools/packaging/kernel). | | [osbuilder](tools/osbuilder) | infrastructure | Tool to create "mini O/S" rootfs and initrd images and kernel for the hypervisor. | | [`agent-ctl`](src/tools/agent-ctl) | utility | Tool that provides low-level access for testing the agent. | +| [`kata-ctl`](src/tools/kata-ctl) | utility | Tool that provides advanced commands and debug facilities. | | [`trace-forwarder`](src/tools/trace-forwarder) | utility | Agent tracing helper. | | [`runk`](src/tools/runk) | utility | Standard OCI container runtime based on the agent. | | [`ci`](https://github.com/kata-containers/ci) | CI | Continuous Integration configuration files and scripts. | diff --git a/docs/how-to/how-to-run-kata-containers-with-SNP-VMs.md b/docs/how-to/how-to-run-kata-containers-with-SNP-VMs.md index bc4e14a10d..a87c9be62b 100644 --- a/docs/how-to/how-to-run-kata-containers-with-SNP-VMs.md +++ b/docs/how-to/how-to-run-kata-containers-with-SNP-VMs.md @@ -50,7 +50,7 @@ $ qemu_commit="$(get_from_kata_deps "assets.hypervisor.qemu.snp.commit")" $ git clone -b "${qemu_branch}" "${qemu_url}" $ pushd qemu $ git checkout "${qemu_commit}" -$ ./configure --target-list=x86_64-softmmu --enable-debug +$ ./configure --enable-virtfs --target-list=x86_64-softmmu --enable-debug $ make -j "$(nproc)" $ popd ``` diff --git a/docs/install/kata-containers-3.0-rust-runtime-installation-guide.md b/docs/install/kata-containers-3.0-rust-runtime-installation-guide.md index 2675a55996..cf9eee456c 100644 --- a/docs/install/kata-containers-3.0-rust-runtime-installation-guide.md +++ b/docs/install/kata-containers-3.0-rust-runtime-installation-guide.md @@ -83,7 +83,7 @@ $ git clone https://github.com/kata-containers/kata-containers.git $ cd kata-containers/src/runtime-rs $ make && sudo make install ``` -After running the command above, the default config file `configuration.toml` will be installed under `/usr/share/defaults/kata-containers/`, the binary file `containerd-shim-kata-v2` will be installed under `/user/local/bin` . +After running the command above, the default config file `configuration.toml` will be installed under `/usr/share/defaults/kata-containers/`, the binary file `containerd-shim-kata-v2` will be installed under `/usr/local/bin/` . ### Build Kata Containers Kernel Follow the [Kernel installation guide](/tools/packaging/kernel/README.md). diff --git a/src/dragonball/Cargo.toml b/src/dragonball/Cargo.toml index c1a24a4f69..b103d4f149 100644 --- a/src/dragonball/Cargo.toml +++ b/src/dragonball/Cargo.toml @@ -43,6 +43,7 @@ vm-memory = { version = "0.9.0", features = ["backend-mmap"] } [dev-dependencies] slog-term = "2.9.0" slog-async = "2.7.0" +test-utils = { path = "../libs/test-utils" } [features] acpi = [] diff --git a/src/dragonball/Makefile b/src/dragonball/Makefile index ca4216b1f7..279d872bbc 100644 --- a/src/dragonball/Makefile +++ b/src/dragonball/Makefile @@ -2,10 +2,19 @@ # Copyright (c) 2019-2022 Ant Group. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +include ../../utils.mk + +ifeq ($(ARCH), s390x) +default build check test clippy: + @echo "s390x not support currently" + exit 0 +else + default: build build: - cargo build --all-features + @echo "INFO: cargo build..." + cargo build --all-features --target $(TRIPLE) check: clippy format @@ -15,6 +24,9 @@ clippy: -- \ -D warnings +vendor: + @echo "INFO: vendor do nothing.." + format: @echo "INFO: cargo fmt..." cargo fmt -- --check @@ -23,5 +35,13 @@ clean: cargo clean test: - @echo "INFO: testing dragonball for development build" - cargo test --all-features -- --nocapture +ifdef SUPPORT_VIRTUALIZATION + cargo test --all-features --target $(TRIPLE) -- --nocapture +else + @echo "INFO: skip testing dragonball, it need virtualization support." + exit 0 +endif + +endif # ifeq ($(ARCH), s390x) + +.DEFAULT_GOAL := default diff --git a/src/dragonball/src/address_space_manager.rs b/src/dragonball/src/address_space_manager.rs index bcc1083a9d..6e4144618c 100644 --- a/src/dragonball/src/address_space_manager.rs +++ b/src/dragonball/src/address_space_manager.rs @@ -35,8 +35,8 @@ use nix::unistd::dup; #[cfg(feature = "atomic-guest-memory")] use vm_memory::GuestMemoryAtomic; use vm_memory::{ - address::Address, FileOffset, GuestAddress, GuestAddressSpace, GuestMemoryMmap, GuestMemoryRegion, - GuestRegionMmap, GuestUsize, MemoryRegionAddress, MmapRegion, + address::Address, FileOffset, GuestAddress, GuestAddressSpace, GuestMemoryMmap, + GuestMemoryRegion, GuestRegionMmap, GuestUsize, MemoryRegionAddress, MmapRegion, }; use crate::resource_manager::ResourceManager; @@ -270,7 +270,7 @@ impl AddressSpaceMgr { let size = info .size .checked_shl(20) - .ok_or_else(|| AddressManagerError::InvalidOperation)?; + .ok_or(AddressManagerError::InvalidOperation)?; // Guest memory does not intersect with the MMIO hole. // TODO: make it work for ARM (issue #4307) @@ -281,13 +281,13 @@ impl AddressSpaceMgr { regions.push(region); start_addr = start_addr .checked_add(size) - .ok_or_else(|| AddressManagerError::InvalidOperation)?; + .ok_or(AddressManagerError::InvalidOperation)?; } else { // Add guest memory below the MMIO hole, avoid splitting the memory region // if the available address region is small than MINIMAL_SPLIT_SPACE MiB. let mut below_size = dbs_boot::layout::MMIO_LOW_START .checked_sub(start_addr) - .ok_or_else(|| AddressManagerError::InvalidOperation)?; + .ok_or(AddressManagerError::InvalidOperation)?; if below_size < (MINIMAL_SPLIT_SPACE) { below_size = 0; } else { @@ -299,12 +299,12 @@ impl AddressSpaceMgr { let above_start = dbs_boot::layout::MMIO_LOW_END + 1; let above_size = size .checked_sub(below_size) - .ok_or_else(|| AddressManagerError::InvalidOperation)?; + .ok_or(AddressManagerError::InvalidOperation)?; let region = self.create_region(above_start, above_size, info, &mut param)?; regions.push(region); start_addr = above_start .checked_add(above_size) - .ok_or_else(|| AddressManagerError::InvalidOperation)?; + .ok_or(AddressManagerError::InvalidOperation)?; } } @@ -502,7 +502,7 @@ impl AddressSpaceMgr { fn configure_numa(&self, mmap_reg: &MmapRegion, node_id: u32) -> Result<()> { let nodemask = 1_u64 .checked_shl(node_id) - .ok_or_else(|| AddressManagerError::InvalidOperation)?; + .ok_or(AddressManagerError::InvalidOperation)?; let res = unsafe { libc::syscall( libc::SYS_mbind, diff --git a/src/dragonball/src/api/v1/boot_source.rs b/src/dragonball/src/api/v1/boot_source.rs index 8ff7e030dc..612de04a18 100644 --- a/src/dragonball/src/api/v1/boot_source.rs +++ b/src/dragonball/src/api/v1/boot_source.rs @@ -18,7 +18,7 @@ pub const DEFAULT_KERNEL_CMDLINE: &str = "reboot=k panic=1 pci=off nomodules 825 i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd"; /// Strongly typed data structure used to configure the boot source of the microvm. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Default)] #[serde(deny_unknown_fields)] pub struct BootSourceConfig { /// Path of the kernel image. diff --git a/src/dragonball/src/api/v1/instance_info.rs b/src/dragonball/src/api/v1/instance_info.rs index ae159aa614..86174a2fd7 100644 --- a/src/dragonball/src/api/v1/instance_info.rs +++ b/src/dragonball/src/api/v1/instance_info.rs @@ -10,7 +10,7 @@ use serde_derive::{Deserialize, Serialize}; /// When Dragonball starts, the instance state is Uninitialized. Once start_microvm method is /// called, the state goes from Uninitialized to Starting. The state is changed to Running until /// the start_microvm method ends. Halting and Halted are currently unsupported. -#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub enum InstanceState { /// Microvm is not initialized. Uninitialized, @@ -29,7 +29,7 @@ pub enum InstanceState { } /// The state of async actions -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub enum AsyncState { /// Uninitialized Uninitialized, diff --git a/src/dragonball/src/api/v1/machine_config.rs b/src/dragonball/src/api/v1/machine_config.rs index e4ae228679..7d78f3e443 100644 --- a/src/dragonball/src/api/v1/machine_config.rs +++ b/src/dragonball/src/api/v1/machine_config.rs @@ -10,7 +10,7 @@ pub const MAX_SUPPORTED_VCPUS: u8 = 254; pub const MEMORY_HOTPLUG_ALIGHMENT: u8 = 64; /// Errors associated with configuring the microVM. -#[derive(Debug, PartialEq, thiserror::Error)] +#[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum VmConfigError { /// Cannot update the configuration of the microvm post boot. #[error("update operation is not allowed after boot")] diff --git a/src/dragonball/src/api/v1/vmm_action.rs b/src/dragonball/src/api/v1/vmm_action.rs index 06004f3f0c..ac1742e953 100644 --- a/src/dragonball/src/api/v1/vmm_action.rs +++ b/src/dragonball/src/api/v1/vmm_action.rs @@ -83,13 +83,13 @@ pub enum VmmActionError { #[cfg(feature = "virtio-fs")] /// The action `InsertFsDevice` failed either because of bad user input or an internal error. - #[error("virtio-fs device: {0}")] + #[error("virtio-fs device error: {0}")] FsDevice(#[source] FsDeviceError), } /// This enum represents the public interface of the VMM. Each action contains various /// bits of information (ids, paths, etc.). -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum VmmAction { /// Configure the boot source of the microVM using `BootSourceConfig`. /// This action can only be called before the microVM has booted. @@ -298,7 +298,6 @@ impl VmmService { let mut cmdline = linux_loader::cmdline::Cmdline::new(dbs_boot::layout::CMDLINE_MAX_SIZE); let boot_args = boot_source_config .boot_args - .clone() .unwrap_or_else(|| String::from(DEFAULT_KERNEL_CMDLINE)); cmdline .insert_str(boot_args) @@ -634,3 +633,783 @@ fn handle_cpu_topology( Ok(cpu_topology) } + +#[cfg(test)] +mod tests { + use std::sync::mpsc::channel; + use std::sync::{Arc, Mutex}; + + use dbs_utils::epoll_manager::EpollManager; + use test_utils::skip_if_not_root; + use vmm_sys_util::tempfile::TempFile; + + use super::*; + use crate::vmm::tests::create_vmm_instance; + + struct TestData<'a> { + req: Option, + vm_state: InstanceState, + f: &'a dyn Fn(VmmRequestResult), + } + + impl<'a> TestData<'a> { + fn new(req: VmmAction, vm_state: InstanceState, f: &'a dyn Fn(VmmRequestResult)) -> Self { + Self { + req: Some(req), + vm_state, + f, + } + } + + fn check_request(&mut self) { + let (to_vmm, from_api) = channel(); + let (to_api, from_vmm) = channel(); + + let vmm = Arc::new(Mutex::new(create_vmm_instance())); + let mut vservice = VmmService::new(from_api, to_api); + + let epoll_mgr = EpollManager::default(); + let mut event_mgr = EventManager::new(&vmm, epoll_mgr).unwrap(); + let mut v = vmm.lock().unwrap(); + + let vm = v.get_vm_mut().unwrap(); + vm.set_instance_state(self.vm_state); + + to_vmm.send(Box::new(self.req.take().unwrap())).unwrap(); + assert!(vservice.run_vmm_action(&mut v, &mut event_mgr).is_ok()); + + let response = from_vmm.try_recv(); + assert!(response.is_ok()); + (self.f)(*response.unwrap()); + } + } + + #[test] + fn test_vmm_action_receive_unknown() { + skip_if_not_root!(); + + let (_to_vmm, from_api) = channel(); + let (to_api, _from_vmm) = channel(); + let vmm = Arc::new(Mutex::new(create_vmm_instance())); + let mut vservice = VmmService::new(from_api, to_api); + let epoll_mgr = EpollManager::default(); + let mut event_mgr = EventManager::new(&vmm, epoll_mgr).unwrap(); + let mut v = vmm.lock().unwrap(); + + assert!(vservice.run_vmm_action(&mut v, &mut event_mgr).is_ok()); + } + + #[should_panic] + #[test] + fn test_vmm_action_disconnected() { + let (to_vmm, from_api) = channel(); + let (to_api, _from_vmm) = channel(); + let vmm = Arc::new(Mutex::new(create_vmm_instance())); + let mut vservice = VmmService::new(from_api, to_api); + let epoll_mgr = EpollManager::default(); + let mut event_mgr = EventManager::new(&vmm, epoll_mgr).unwrap(); + let mut v = vmm.lock().unwrap(); + + drop(to_vmm); + vservice.run_vmm_action(&mut v, &mut event_mgr).unwrap(); + } + + #[test] + fn test_vmm_action_config_boot_source() { + skip_if_not_root!(); + + let kernel_file = TempFile::new().unwrap(); + + let tests = &mut [ + // invalid state + TestData::new( + VmmAction::ConfigureBootSource(BootSourceConfig::default()), + InstanceState::Running, + &|result| { + if let Err(VmmActionError::BootSource( + BootSourceConfigError::UpdateNotAllowedPostBoot, + )) = result + { + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to configure boot source for VM: \ + the update operation is not allowed after boot", + ); + assert_eq!(err_string, expected_err); + } else { + panic!(); + } + }, + ), + // invalid kernel file path + TestData::new( + VmmAction::ConfigureBootSource(BootSourceConfig::default()), + InstanceState::Uninitialized, + &|result| { + if let Err(VmmActionError::BootSource( + BootSourceConfigError::InvalidKernelPath(_), + )) = result + { + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to configure boot source for VM: \ + the kernel file cannot be opened due to invalid kernel path or invalid permissions: \ + No such file or directory (os error 2)"); + assert_eq!(err_string, expected_err); + } else { + panic!(); + } + }, + ), + //success + TestData::new( + VmmAction::ConfigureBootSource(BootSourceConfig { + kernel_path: kernel_file.as_path().to_str().unwrap().to_string(), + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + assert!(result.is_ok()); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[test] + fn test_vmm_action_set_vm_configuration() { + skip_if_not_root!(); + + let tests = &mut [ + // invalid state + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo::default()), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::MachineConfig( + VmConfigError::UpdateNotAllowedPostBoot + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to set configuration for the VM: \ + update operation is not allowed after boot", + ); + assert_eq!(err_string, expected_err); + }, + ), + // invalid cpu count (0) + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo { + vcpu_count: 0, + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::MachineConfig( + VmConfigError::InvalidVcpuCount(0) + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to set configuration for the VM: \ + the vCPU number '0' can only be 1 or an even number when hyperthreading is enabled"); + assert_eq!(err_string, expected_err); + }, + ), + // invalid max cpu count (too small) + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo { + vcpu_count: 4, + max_vcpu_count: 2, + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::MachineConfig( + VmConfigError::InvalidMaxVcpuCount(2) + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to set configuration for the VM: \ + the max vCPU number '2' shouldn't less than vCPU count and can only be 1 or an even number when hyperthreading is enabled"); + assert_eq!(err_string, expected_err); + }, + ), + // invalid cpu topology (larger than 254) + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo { + vcpu_count: 254, + cpu_topology: CpuTopology { + threads_per_core: 2, + cores_per_die: 128, + dies_per_socket: 1, + sockets: 1, + }, + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::MachineConfig( + VmConfigError::VcpuCountExceedsMaximum + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to set configuration for the VM: \ + the vCPU number shouldn't large than 254", + ); + + assert_eq!(err_string, expected_err) + }, + ), + // cpu topology and max_vcpu_count are not matched - success + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo { + vcpu_count: 16, + max_vcpu_count: 32, + cpu_topology: CpuTopology { + threads_per_core: 1, + cores_per_die: 128, + dies_per_socket: 1, + sockets: 1, + }, + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + result.unwrap(); + }, + ), + // invalid threads_per_core + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo { + vcpu_count: 4, + max_vcpu_count: 4, + cpu_topology: CpuTopology { + threads_per_core: 4, + cores_per_die: 1, + dies_per_socket: 1, + sockets: 1, + }, + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::MachineConfig( + VmConfigError::InvalidThreadsPerCore(4) + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to set configuration for the VM: \ + the threads_per_core number '4' can only be 1 or 2", + ); + + assert_eq!(err_string, expected_err) + }, + ), + // invalid mem size + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo { + mem_size_mib: 3, + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::MachineConfig( + VmConfigError::InvalidMemorySize(3) + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to set configuration for the VM: \ + the memory size 0x3MiB is invalid", + ); + assert_eq!(err_string, expected_err); + }, + ), + // invalid mem path + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo { + mem_type: String::from("hugetlbfs"), + mem_file_path: String::from(""), + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::MachineConfig( + VmConfigError::InvalidMemFilePath(_) + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to set configuration for the VM: \ + the memory file path is invalid", + ); + assert_eq!(err_string, expected_err); + }, + ), + // success + TestData::new( + VmmAction::SetVmConfiguration(VmConfigInfo::default()), + InstanceState::Uninitialized, + &|result| { + assert!(result.is_ok()); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[test] + fn test_vmm_action_start_microvm() { + skip_if_not_root!(); + + let tests = &mut [ + // invalid state (running) + TestData::new(VmmAction::StartMicroVm, InstanceState::Running, &|result| { + assert!(matches!( + result, + Err(VmmActionError::StartMicroVm( + StartMicroVmError::MicroVMAlreadyRunning + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to boot the VM: \ + the virtual machine is already running", + ); + assert_eq!(err_string, expected_err); + }), + // no kernel configuration + TestData::new( + VmmAction::StartMicroVm, + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::StartMicroVm( + StartMicroVmError::MissingKernelConfig + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to boot the VM: \ + cannot start the virtual machine without kernel configuration", + ); + assert_eq!(err_string, expected_err); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[test] + fn test_vmm_action_shutdown_microvm() { + skip_if_not_root!(); + + let tests = &mut [ + // success + TestData::new( + VmmAction::ShutdownMicroVm, + InstanceState::Uninitialized, + &|result| { + assert!(result.is_ok()); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[cfg(feature = "virtio-blk")] + #[test] + fn test_vmm_action_insert_block_device() { + skip_if_not_root!(); + + let dummy_file = TempFile::new().unwrap(); + let dummy_path = dummy_file.as_path().to_owned(); + + let tests = &mut [ + // invalid state + TestData::new( + VmmAction::InsertBlockDevice(BlockDeviceConfigInfo::default()), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::Block( + BlockDeviceError::UpdateNotAllowedPostBoot + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "virtio-blk device error: \ + block device does not support runtime update", + ); + assert_eq!(err_string, expected_err); + }, + ), + // success + TestData::new( + VmmAction::InsertBlockDevice(BlockDeviceConfigInfo { + path_on_host: dummy_path, + device_type: crate::device_manager::blk_dev_mgr::BlockDeviceType::RawBlock, + is_root_device: true, + part_uuid: None, + is_read_only: false, + is_direct: false, + no_drop: false, + drive_id: String::from("1"), + rate_limiter: None, + num_queues: BlockDeviceConfigInfo::default_num_queues(), + queue_size: 256, + use_shared_irq: None, + use_generic_irq: None, + }), + InstanceState::Uninitialized, + &|result| { + assert!(result.is_ok()); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[cfg(feature = "virtio-blk")] + #[test] + fn test_vmm_action_update_block_device() { + skip_if_not_root!(); + + let tests = &mut [ + // invalid id + TestData::new( + VmmAction::UpdateBlockDevice(BlockDeviceConfigUpdateInfo { + drive_id: String::from("1"), + rate_limiter: None, + }), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::Block(BlockDeviceError::InvalidDeviceId(_))) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "virtio-blk device error: \ + invalid block device id '1'", + ); + assert_eq!(err_string, expected_err); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[cfg(feature = "virtio-blk")] + #[test] + fn test_vmm_action_remove_block_device() { + skip_if_not_root!(); + + let tests = &mut [ + // invalid state + TestData::new( + VmmAction::RemoveBlockDevice(String::from("1")), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::Block( + BlockDeviceError::UpdateNotAllowedPostBoot + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "virtio-blk device error: \ + block device does not support runtime update", + ); + assert_eq!(err_string, expected_err); + }, + ), + // invalid id + TestData::new( + VmmAction::RemoveBlockDevice(String::from("1")), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::Block(BlockDeviceError::InvalidDeviceId(_))) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "virtio-blk device error: \ + invalid block device id '1'", + ); + assert_eq!(err_string, expected_err); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[cfg(feature = "virtio-fs")] + #[test] + fn test_vmm_action_insert_fs_device() { + skip_if_not_root!(); + + let tests = &mut [ + // invalid state + TestData::new( + VmmAction::InsertFsDevice(FsDeviceConfigInfo::default()), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::FsDevice( + FsDeviceError::UpdateNotAllowedPostBoot + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "virtio-fs device error: \ + update operation is not allowed after boot", + ); + assert_eq!(err_string, expected_err); + }, + ), + // success + TestData::new( + VmmAction::InsertFsDevice(FsDeviceConfigInfo::default()), + InstanceState::Uninitialized, + &|result| { + assert!(result.is_ok()); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[cfg(feature = "virtio-fs")] + #[test] + fn test_vmm_action_manipulate_fs_device() { + skip_if_not_root!(); + + let tests = &mut [ + // invalid state + TestData::new( + VmmAction::ManipulateFsBackendFs(FsMountConfigInfo::default()), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::FsDevice(FsDeviceError::MicroVMNotRunning)) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "virtio-fs device error: \ + vm is not running when attaching a backend fs", + ); + assert_eq!(err_string, expected_err); + }, + ), + // invalid backend + TestData::new( + VmmAction::ManipulateFsBackendFs(FsMountConfigInfo::default()), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::FsDevice( + FsDeviceError::AttachBackendFailed(_) + )) + )); + let err_string = format!("{}", result.unwrap_err()); + println!("{}", err_string); + let expected_err = String::from( + "virtio-fs device error: \ + Fs device attach a backend fs failed", + ); + assert_eq!(err_string, expected_err); + }, + ), + ]; + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[cfg(feature = "virtio-net")] + #[test] + fn test_vmm_action_insert_network_device() { + skip_if_not_root!(); + + let tests = &mut [ + // hotplug unready + TestData::new( + VmmAction::InsertNetworkDevice(VirtioNetDeviceConfigInfo::default()), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::StartMicroVm( + StartMicroVmError::UpcallMissVsock + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to boot the VM: \ + the upcall client needs a virtio-vsock device for communication", + ); + assert_eq!(err_string, expected_err); + }, + ), + // success + TestData::new( + VmmAction::InsertNetworkDevice(VirtioNetDeviceConfigInfo::default()), + InstanceState::Uninitialized, + &|result| { + assert!(result.is_ok()); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[cfg(feature = "virtio-net")] + #[test] + fn test_vmm_action_update_network_interface() { + skip_if_not_root!(); + + let tests = &mut [ + // invalid id + TestData::new( + VmmAction::UpdateNetworkInterface(VirtioNetDeviceConfigUpdateInfo { + iface_id: String::from("1"), + rx_rate_limiter: None, + tx_rate_limiter: None, + }), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::VirtioNet( + VirtioNetDeviceError::InvalidIfaceId(_) + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "virtio-net device error: \ + invalid virtio-net iface id '1'", + ); + assert_eq!(err_string, expected_err); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } + + #[cfg(feature = "virtio-vsock")] + #[test] + fn test_vmm_action_insert_vsock_device() { + skip_if_not_root!(); + + let tests = &mut [ + // invalid state + TestData::new( + VmmAction::InsertVsockDevice(VsockDeviceConfigInfo::default()), + InstanceState::Running, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::Vsock( + VsockDeviceError::UpdateNotAllowedPostBoot + )) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to add virtio-vsock device: \ + update operation is not allowed after boot", + ); + assert_eq!(err_string, expected_err); + }, + ), + // invalid guest_cid + TestData::new( + VmmAction::InsertVsockDevice(VsockDeviceConfigInfo::default()), + InstanceState::Uninitialized, + &|result| { + assert!(matches!( + result, + Err(VmmActionError::Vsock(VsockDeviceError::GuestCIDInvalid(0))) + )); + let err_string = format!("{}", result.unwrap_err()); + let expected_err = String::from( + "failed to add virtio-vsock device: \ + the guest CID 0 is invalid", + ); + assert_eq!(err_string, expected_err); + }, + ), + // success + TestData::new( + VmmAction::InsertVsockDevice(VsockDeviceConfigInfo { + guest_cid: 3, + ..Default::default() + }), + InstanceState::Uninitialized, + &|result| { + assert!(result.is_ok()); + }, + ), + ]; + + for t in tests.iter_mut() { + t.check_request(); + } + } +} diff --git a/src/dragonball/src/config_manager.rs b/src/dragonball/src/config_manager.rs index fbb66611c3..34a2af2e0f 100644 --- a/src/dragonball/src/config_manager.rs +++ b/src/dragonball/src/config_manager.rs @@ -46,7 +46,7 @@ pub trait ConfigItem { } /// Struct to manage a group of configuration items. -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] pub struct ConfigInfos where T: ConfigItem + Clone, @@ -316,7 +316,7 @@ where } /// Configuration information for RateLimiter token bucket. -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] pub struct TokenBucketConfigInfo { /// The size for the token bucket. A TokenBucket of `size` total capacity will take `refill_time` /// milliseconds to go from zero tokens to total capacity. @@ -349,7 +349,7 @@ impl From<&TokenBucketConfigInfo> for TokenBucket { } /// Configuration information for RateLimiter objects. -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] pub struct RateLimiterConfigInfo { /// Data used to initialize the RateLimiter::bandwidth bucket. pub bandwidth: TokenBucketConfigInfo, diff --git a/src/dragonball/src/device_manager/blk_dev_mgr.rs b/src/dragonball/src/device_manager/blk_dev_mgr.rs index 4f0fa1e4b0..0493a818a4 100644 --- a/src/dragonball/src/device_manager/blk_dev_mgr.rs +++ b/src/dragonball/src/device_manager/blk_dev_mgr.rs @@ -106,7 +106,7 @@ pub enum BlockDeviceError { } /// Type of low level storage device/protocol for virtio-blk devices. -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum BlockDeviceType { /// Unknown low level device type. Unknown, @@ -131,7 +131,7 @@ impl BlockDeviceType { } /// Configuration information for a block device. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct BlockDeviceConfigUpdateInfo { /// Unique identifier of the drive. pub drive_id: String, @@ -151,7 +151,7 @@ impl BlockDeviceConfigUpdateInfo { } /// Configuration information for a block device. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct BlockDeviceConfigInfo { /// Unique identifier of the drive. pub drive_id: String, @@ -285,7 +285,6 @@ impl std::fmt::Debug for BlockDeviceInfo { pub type BlockDeviceInfo = DeviceConfigInfo; /// Wrapper for the collection that holds all the Block Devices Configs -//#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[derive(Clone)] pub struct BlockDeviceMgr { /// A list of `BlockDeviceInfo` objects. @@ -625,7 +624,7 @@ impl BlockDeviceMgr { // we need to satisfy the condition by which a VMM can only have on root device if block_device_config.is_root_device { if self.has_root_block { - return Err(BlockDeviceError::RootBlockDeviceAlreadyAdded); + Err(BlockDeviceError::RootBlockDeviceAlreadyAdded) } else { self.has_root_block = true; self.read_only_root = block_device_config.is_read_only; diff --git a/src/dragonball/src/device_manager/fs_dev_mgr.rs b/src/dragonball/src/device_manager/fs_dev_mgr.rs index 088dc980f1..dca0e649e3 100644 --- a/src/dragonball/src/device_manager/fs_dev_mgr.rs +++ b/src/dragonball/src/device_manager/fs_dev_mgr.rs @@ -89,7 +89,7 @@ pub enum FsDeviceError { } /// Configuration information for a vhost-user-fs device. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct FsDeviceConfigInfo { /// vhost-user socket path. pub sock_path: String, @@ -201,7 +201,7 @@ impl FsDeviceConfigInfo { } /// Configuration information for virtio-fs. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct FsDeviceConfigUpdateInfo { /// virtiofs mount tag name used inside the guest. /// used as the device name during mount. @@ -242,7 +242,7 @@ impl ConfigItem for FsDeviceConfigInfo { } /// Configuration information of manipulating backend fs for a virtiofs device. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Default)] pub struct FsMountConfigInfo { /// Mount operations, mount, update, umount pub ops: String, diff --git a/src/dragonball/src/device_manager/mod.rs b/src/dragonball/src/device_manager/mod.rs index 8e1b3456fc..09689ca52c 100644 --- a/src/dragonball/src/device_manager/mod.rs +++ b/src/dragonball/src/device_manager/mod.rs @@ -147,7 +147,11 @@ pub type Result = ::std::result::Result; /// Type of the dragonball virtio devices. #[cfg(feature = "dbs-virtio-devices")] pub type DbsVirtioDevice = Box< - dyn VirtioDevice, + dyn VirtioDevice< + GuestAddressSpaceImpl, + virtio_queue::QueueStateSync, + vm_memory::GuestRegionMmap, + >, >; /// Type of the dragonball virtio mmio devices. @@ -791,13 +795,14 @@ impl DeviceManager { fn allocate_mmio_device_resource( &self, ) -> std::result::Result { - let mut requests = Vec::new(); - requests.push(ResourceConstraint::MmioAddress { - range: None, - align: MMIO_DEFAULT_CFG_SIZE, - size: MMIO_DEFAULT_CFG_SIZE, - }); - requests.push(ResourceConstraint::LegacyIrq { irq: None }); + let requests = vec![ + ResourceConstraint::MmioAddress { + range: None, + align: MMIO_DEFAULT_CFG_SIZE, + size: MMIO_DEFAULT_CFG_SIZE, + }, + ResourceConstraint::LegacyIrq { irq: None }, + ]; self.res_manager .allocate_device_resources(&requests, false) @@ -997,7 +1002,7 @@ impl DeviceManager { { self.vsock_manager .get_default_connector() - .map(|d| Some(d)) + .map(Some) .unwrap_or(None) } #[cfg(not(feature = "virtio-vsock"))] diff --git a/src/dragonball/src/device_manager/virtio_net_dev_mgr.rs b/src/dragonball/src/device_manager/virtio_net_dev_mgr.rs index 3283a00d91..c0b0f62daa 100644 --- a/src/dragonball/src/device_manager/virtio_net_dev_mgr.rs +++ b/src/dragonball/src/device_manager/virtio_net_dev_mgr.rs @@ -93,7 +93,7 @@ pub enum VirtioNetDeviceError { } /// Configuration information for virtio net devices. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct VirtioNetDeviceConfigUpdateInfo { /// ID of the guest network interface. pub iface_id: String, @@ -123,7 +123,7 @@ impl VirtioNetDeviceConfigUpdateInfo { } /// Configuration information for virtio net devices. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Default)] pub struct VirtioNetDeviceConfigInfo { /// ID of the guest network interface. pub iface_id: String, @@ -264,7 +264,7 @@ impl VirtioNetDeviceMgr { config.use_generic_irq.unwrap_or(USE_GENERIC_IRQ), ) .map_err(VirtioNetDeviceError::DeviceManager)?; - ctx.insert_hotplug_mmio_device(&dev.clone(), None) + ctx.insert_hotplug_mmio_device(&dev, None) .map_err(VirtioNetDeviceError::DeviceManager)?; // live-upgrade need save/restore device from info.device. mgr.info_list[device_index].set_device(dev); diff --git a/src/dragonball/src/device_manager/vsock_dev_mgr.rs b/src/dragonball/src/device_manager/vsock_dev_mgr.rs index 4f0f074134..8588471b71 100644 --- a/src/dragonball/src/device_manager/vsock_dev_mgr.rs +++ b/src/dragonball/src/device_manager/vsock_dev_mgr.rs @@ -70,7 +70,7 @@ pub enum VsockDeviceError { } /// Configuration information for a vsock device. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct VsockDeviceConfigInfo { /// ID of the vsock device. pub id: String, diff --git a/src/dragonball/src/event_manager.rs b/src/dragonball/src/event_manager.rs index f07b786506..69bf4dab4c 100644 --- a/src/dragonball/src/event_manager.rs +++ b/src/dragonball/src/event_manager.rs @@ -101,7 +101,6 @@ impl EventManager { /// Poll pending events and invoke registered event handler. /// /// # Arguments: - /// * max_events: maximum number of pending events to handle /// * timeout: maximum time in milliseconds to wait pub fn handle_events(&self, timeout: i32) -> std::result::Result { self.epoll_mgr diff --git a/src/dragonball/src/kvm_context.rs b/src/dragonball/src/kvm_context.rs index f160b264b8..f4a8408608 100644 --- a/src/dragonball/src/kvm_context.rs +++ b/src/dragonball/src/kvm_context.rs @@ -210,14 +210,19 @@ mod x86_64 { #[cfg(test)] mod tests { - use super::*; - use kvm_ioctls::Kvm; use std::fs::File; use std::os::unix::fs::MetadataExt; use std::os::unix::io::{AsRawFd, FromRawFd}; + use kvm_ioctls::Kvm; + use test_utils::skip_if_not_root; + + use super::*; + #[test] fn test_create_kvm_context() { + skip_if_not_root!(); + let c = KvmContext::new(None).unwrap(); assert!(c.max_memslots >= 32); @@ -234,6 +239,8 @@ mod tests { #[cfg(target_arch = "x86_64")] #[test] fn test_get_supported_cpu_id() { + skip_if_not_root!(); + let c = KvmContext::new(None).unwrap(); let _ = c @@ -244,6 +251,8 @@ mod tests { #[test] fn test_create_vm() { + skip_if_not_root!(); + let c = KvmContext::new(None).unwrap(); let _ = c.create_vm().unwrap(); diff --git a/src/dragonball/src/resource_manager.rs b/src/dragonball/src/resource_manager.rs index 2cb32c0546..4565344826 100644 --- a/src/dragonball/src/resource_manager.rs +++ b/src/dragonball/src/resource_manager.rs @@ -36,7 +36,7 @@ const PIO_MAX: u16 = 0xFFFF; const MMIO_SPACE_RESERVED: u64 = 0x400_0000; /// Errors associated with resource management operations -#[derive(Debug, PartialEq, thiserror::Error)] +#[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum ResourceError { /// Unknown/unsupported resource type. #[error("unsupported resource type")] @@ -569,9 +569,7 @@ impl ResourceManager { Resource::KvmMemSlot(slot) => self.free_kvm_mem_slot(*slot), Resource::MacAddresss(_) => Ok(()), }; - if result.is_err() { - return result; - } + result?; } Ok(()) } @@ -588,9 +586,9 @@ mod tests { // Allocate/free shared IRQs multiple times. assert_eq!(mgr.allocate_legacy_irq(true, None).unwrap(), SHARED_IRQ); assert_eq!(mgr.allocate_legacy_irq(true, None).unwrap(), SHARED_IRQ); - mgr.free_legacy_irq(SHARED_IRQ); - mgr.free_legacy_irq(SHARED_IRQ); - mgr.free_legacy_irq(SHARED_IRQ); + mgr.free_legacy_irq(SHARED_IRQ).unwrap(); + mgr.free_legacy_irq(SHARED_IRQ).unwrap(); + mgr.free_legacy_irq(SHARED_IRQ).unwrap(); // Allocate specified IRQs. assert_eq!( @@ -598,7 +596,7 @@ mod tests { .unwrap(), LEGACY_IRQ_BASE + 10 ); - mgr.free_legacy_irq(LEGACY_IRQ_BASE + 10); + mgr.free_legacy_irq(LEGACY_IRQ_BASE + 10).unwrap(); assert_eq!( mgr.allocate_legacy_irq(false, Some(LEGACY_IRQ_BASE + 10)) .unwrap(), @@ -635,19 +633,19 @@ mod tests { let mgr = ResourceManager::new(None); let msi = mgr.allocate_msi_irq(3).unwrap(); - mgr.free_msi_irq(msi, 3); + mgr.free_msi_irq(msi, 3).unwrap(); let msi = mgr.allocate_msi_irq(3).unwrap(); - mgr.free_msi_irq(msi, 3); + mgr.free_msi_irq(msi, 3).unwrap(); let irq = mgr.allocate_msi_irq_aligned(8).unwrap(); assert_eq!(irq & 0x7, 0); - mgr.free_msi_irq(msi, 8); + mgr.free_msi_irq(msi, 8).unwrap(); let irq = mgr.allocate_msi_irq_aligned(8).unwrap(); assert_eq!(irq & 0x7, 0); let irq = mgr.allocate_msi_irq_aligned(512).unwrap(); assert_eq!(irq, 512); - mgr.free_msi_irq(irq, 512); + mgr.free_msi_irq(irq, 512).unwrap(); let irq = mgr.allocate_msi_irq_aligned(512).unwrap(); assert_eq!(irq, 512); @@ -690,9 +688,9 @@ mod tests { }, ]; let resources = mgr.allocate_device_resources(&requests, false).unwrap(); - mgr.free_device_resources(&resources); + mgr.free_device_resources(&resources).unwrap(); let resources = mgr.allocate_device_resources(&requests, false).unwrap(); - mgr.free_device_resources(&resources); + mgr.free_device_resources(&resources).unwrap(); requests.push(ResourceConstraint::PioAddress { range: Some((0xc000, 0xc000)), align: 0x1000, @@ -702,7 +700,7 @@ mod tests { let resources = mgr .allocate_device_resources(&requests[0..requests.len() - 1], false) .unwrap(); - mgr.free_device_resources(&resources); + mgr.free_device_resources(&resources).unwrap(); } #[test] @@ -721,7 +719,7 @@ mod tests { let mgr = ResourceManager::new(None); assert_eq!(mgr.allocate_kvm_mem_slot(1, None).unwrap(), 0); assert_eq!(mgr.allocate_kvm_mem_slot(1, Some(200)).unwrap(), 200); - mgr.free_kvm_mem_slot(200); + mgr.free_kvm_mem_slot(200).unwrap(); assert_eq!(mgr.allocate_kvm_mem_slot(1, Some(200)).unwrap(), 200); assert_eq!( mgr.allocate_kvm_mem_slot(1, Some(KVM_USER_MEM_SLOTS)) diff --git a/src/dragonball/src/vcpu/aarch64.rs b/src/dragonball/src/vcpu/aarch64.rs index 054a1f65d4..dc4b9c61a6 100644 --- a/src/dragonball/src/vcpu/aarch64.rs +++ b/src/dragonball/src/vcpu/aarch64.rs @@ -39,6 +39,7 @@ impl Vcpu { /// vcpu thread to vmm thread. /// * `create_ts` - A timestamp used by the vcpu to calculate its lifetime. /// * `support_immediate_exit` - whether kvm uses supports immediate_exit flag. + #[allow(clippy::too_many_arguments)] pub fn new_aarch64( id: u8, vcpu_fd: Arc, diff --git a/src/dragonball/src/vcpu/vcpu_impl.rs b/src/dragonball/src/vcpu/vcpu_impl.rs index 513fa435f9..ff3f9e44fe 100644 --- a/src/dragonball/src/vcpu/vcpu_impl.rs +++ b/src/dragonball/src/vcpu/vcpu_impl.rs @@ -533,16 +533,11 @@ impl Vcpu { fn check_io_port_info(&self, addr: u16, data: &[u8]) -> Result { let mut checked = false; - match addr { - // debug info signal - MAGIC_IOPORT_DEBUG_INFO => { - if data.len() == 4 { - let data = unsafe { std::ptr::read(data.as_ptr() as *const u32) }; - log::warn!("KDBG: guest kernel debug info: 0x{:x}", data); - checked = true; - } - } - _ => {} + // debug info signal + if addr == MAGIC_IOPORT_DEBUG_INFO && data.len() == 4 { + let data = unsafe { std::ptr::read(data.as_ptr() as *const u32) }; + log::warn!("KDBG: guest kernel debug info: 0x{:x}", data); + checked = true; }; Ok(checked) @@ -771,6 +766,7 @@ pub mod tests { use dbs_device::device_manager::IoManager; use kvm_ioctls::Kvm; use lazy_static::lazy_static; + use test_utils::skip_if_not_root; use super::*; use crate::kvm_context::KvmContext; @@ -855,7 +851,7 @@ pub mod tests { let kvm = Kvm::new().unwrap(); let vm = Arc::new(kvm.create_vm().unwrap()); - let kvm_context = KvmContext::new(Some(kvm.as_raw_fd())).unwrap(); + let _kvm_context = KvmContext::new(Some(kvm.as_raw_fd())).unwrap(); let vcpu_fd = Arc::new(vm.create_vcpu(0).unwrap()); let io_manager = IoManagerCached::new(Arc::new(ArcSwap::new(Arc::new(IoManager::new())))); let reset_event_fd = EventFd::new(libc::EFD_NONBLOCK).unwrap(); @@ -880,6 +876,8 @@ pub mod tests { #[test] fn test_vcpu_run_emulation() { + skip_if_not_root!(); + let (mut vcpu, _) = create_vcpu(); #[cfg(target_arch = "x86_64")] @@ -964,6 +962,8 @@ pub mod tests { #[cfg(target_arch = "x86_64")] #[test] fn test_vcpu_check_io_port_info() { + skip_if_not_root!(); + let (vcpu, _receiver) = create_vcpu(); // debug info signal diff --git a/src/dragonball/src/vcpu/vcpu_manager.rs b/src/dragonball/src/vcpu/vcpu_manager.rs index f6f3e93ffa..51a790c4db 100644 --- a/src/dragonball/src/vcpu/vcpu_manager.rs +++ b/src/dragonball/src/vcpu/vcpu_manager.rs @@ -774,7 +774,7 @@ impl VcpuManager { self.reset_event_fd.as_ref().unwrap().try_clone().unwrap(), self.vcpu_state_event.try_clone().unwrap(), self.vcpu_state_sender.clone(), - request_ts.clone(), + request_ts, self.support_immediate_exit, ) .map_err(VcpuManagerError::Vcpu) diff --git a/src/dragonball/src/vm/aarch64.rs b/src/dragonball/src/vm/aarch64.rs index 8206920ce7..fddbf9516a 100644 --- a/src/dragonball/src/vm/aarch64.rs +++ b/src/dragonball/src/vm/aarch64.rs @@ -35,6 +35,7 @@ use crate::event_manager::EventManager; /// * `device_info` - A hashmap containing the attached devices for building FDT device nodes. /// * `gic_device` - The GIC device. /// * `initrd` - Information about an optional initrd. +#[allow(clippy::borrowed_box)] fn configure_system( guest_mem: &M, cmdline: &str, @@ -58,8 +59,9 @@ fn configure_system( #[cfg(target_arch = "aarch64")] impl Vm { /// Gets a reference to the irqchip of the VM + #[allow(clippy::borrowed_box)] pub fn get_irqchip(&self) -> &Box { - &self.irqchip_handle.as_ref().unwrap() + self.irqchip_handle.as_ref().unwrap() } /// Creates the irq chip in-kernel device model. diff --git a/src/dragonball/src/vm/mod.rs b/src/dragonball/src/vm/mod.rs index f5f62a0407..39f5483e9c 100644 --- a/src/dragonball/src/vm/mod.rs +++ b/src/dragonball/src/vm/mod.rs @@ -67,7 +67,7 @@ pub enum VmError { } /// Configuration information for user defined NUMA nodes. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct NumaRegionInfo { /// memory size for this region (unit: MiB) pub size: u64, @@ -80,7 +80,7 @@ pub struct NumaRegionInfo { } /// Information for cpu topology to guide guest init -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CpuTopology { /// threads per core to indicate hyperthreading is enabled or not pub threads_per_core: u8, @@ -104,7 +104,7 @@ impl Default for CpuTopology { } /// Configuration information for virtual machine instance. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct VmConfigInfo { /// Number of vcpu to start. pub vcpu_count: u8, @@ -814,3 +814,17 @@ impl Vm { Err(StartMicroVmError::MicroVMAlreadyRunning) } } + +#[cfg(test)] +pub mod tests { + use super::*; + + impl Vm { + pub fn set_instance_state(&mut self, mstate: InstanceState) { + self.shared_info + .write() + .expect("Failed to start microVM because shared info couldn't be written due to poisoned lock") + .state = mstate; + } + } +} diff --git a/src/dragonball/src/vmm.rs b/src/dragonball/src/vmm.rs index a25543e342..1cfbfac584 100644 --- a/src/dragonball/src/vmm.rs +++ b/src/dragonball/src/vmm.rs @@ -189,6 +189,8 @@ impl Vmm { #[cfg(test)] pub(crate) mod tests { + use test_utils::skip_if_not_root; + use super::*; pub fn create_vmm_instance() -> Vmm { @@ -210,6 +212,8 @@ pub(crate) mod tests { #[test] fn test_create_vmm_instance() { + skip_if_not_root!(); + create_vmm_instance(); } } diff --git a/src/libs/kata-sys-util/src/k8s.rs b/src/libs/kata-sys-util/src/k8s.rs index be95d5d330..4ae31921e7 100644 --- a/src/libs/kata-sys-util/src/k8s.rs +++ b/src/libs/kata-sys-util/src/k8s.rs @@ -49,7 +49,7 @@ pub fn is_host_empty_dir(path: &str) -> bool { false } -// set_ephemeral_storage_type sets the mount type to 'ephemeral' +// update_ephemeral_storage_type sets the mount type to 'ephemeral' // if the mount source path is provisioned by k8s for ephemeral storage. // For the given pod ephemeral volume is created only once // backed by tmpfs inside the VM. For successive containers @@ -63,6 +63,8 @@ pub fn update_ephemeral_storage_type(oci_spec: &mut Spec) { if is_ephemeral_volume(&m.source) { m.r#type = String::from(mount::KATA_EPHEMERAL_VOLUME_TYPE); } else if is_host_empty_dir(&m.source) { + // FIXME support disable_guest_empty_dir + // https://github.com/kata-containers/kata-containers/blob/02a51e75a7e0c6fce5e8abe3b991eeac87e09645/src/runtime/pkg/katautils/create.go#L105 m.r#type = String::from(mount::KATA_HOST_DIR_VOLUME_TYPE); } } diff --git a/src/libs/kata-sys-util/src/spec.rs b/src/libs/kata-sys-util/src/spec.rs index 1d41900f6b..b606d1194d 100644 --- a/src/libs/kata-sys-util/src/spec.rs +++ b/src/libs/kata-sys-util/src/spec.rs @@ -49,7 +49,7 @@ pub enum ShimIdInfo { } /// get container type -pub fn get_contaier_type(spec: &oci::Spec) -> Result { +pub fn get_container_type(spec: &oci::Spec) -> Result { for k in CRI_CONTAINER_TYPE_KEY_LIST.iter() { if let Some(type_value) = spec.annotations.get(*k) { match type_value.as_str() { @@ -67,7 +67,7 @@ pub fn get_contaier_type(spec: &oci::Spec) -> Result { /// get shim id info pub fn get_shim_id_info() -> Result { let spec = load_oci_spec()?; - match get_contaier_type(&spec)? { + match get_container_type(&spec)? { ContainerType::PodSandbox => Ok(ShimIdInfo::Sandbox), ContainerType::PodContainer => { for k in CRI_SANDBOX_ID_KEY_LIST { diff --git a/src/libs/kata-types/src/mount.rs b/src/libs/kata-types/src/mount.rs index 2ccc0feed2..b58e810d60 100644 --- a/src/libs/kata-types/src/mount.rs +++ b/src/libs/kata-types/src/mount.rs @@ -13,7 +13,7 @@ pub const KATA_VOLUME_TYPE_PREFIX: &str = "kata:"; pub const KATA_GUEST_MOUNT_PREFIX: &str = "kata:guest-mount:"; /// KATA_EPHEMERAL_DEV_TYPE creates a tmpfs backed volume for sharing files between containers. -pub const KATA_EPHEMERAL_VOLUME_TYPE: &str = "kata:ephemeral"; +pub const KATA_EPHEMERAL_VOLUME_TYPE: &str = "ephemeral"; /// KATA_HOST_DIR_TYPE use for host empty dir pub const KATA_HOST_DIR_VOLUME_TYPE: &str = "kata:hostdir"; diff --git a/src/runtime-rs/Makefile b/src/runtime-rs/Makefile index 482517b3d7..b04f778808 100644 --- a/src/runtime-rs/Makefile +++ b/src/runtime-rs/Makefile @@ -31,7 +31,7 @@ test: else ##TARGET default: build code default: runtime show-header -#TARGET test: run cargo tests +##TARGET test: run cargo tests test: @cargo test --all --target $(TRIPLE) $(EXTRA_RUSTFEATURES) -- --nocapture endif @@ -50,7 +50,6 @@ EXEC_PREFIX := $(PREFIX)/local BINDIR := $(EXEC_PREFIX)/bin else EXEC_PREFIX := $(PREFIX) -##VAR BINDIR= is a directory for installing executable programs # when creating the kata-deploy image, the default installation path for go runtime is $(EXEC_PREFIX)/bin, so we put it here for multiple runtime BINDIR := $(EXEC_PREFIX)/runtime-rs/bin/ endif @@ -73,7 +72,7 @@ HYPERVISOR_CLH = cloud-hypervisor DEFAULT_HYPERVISOR ?= $(HYPERVISOR_DB) -# List of hypervisors this build system can generate configuration for. +##VAR HYPERVISOR= List of hypervisors this build system can generate configuration for. HYPERVISORS := $(HYPERVISOR_DB) $(HYPERVISOR_ACRN) $(HYPERVISOR_FC) $(HYPERVISOR_QEMU) $(HYPERVISOR_CLH) DBVALIDHYPERVISORPATHS := [] @@ -84,28 +83,28 @@ PKGLIBEXECDIR := $(LIBEXECDIR)/$(PROJECT_DIR) FIRMWAREPATH := FIRMWAREVOLUMEPATH := -# Default number of vCPUs +##VAR DEFVCPUS= Default number of vCPUs DEFVCPUS := 1 -# Default maximum number of vCPUs +##VAR DEFMAXVCPUS= Default maximum number of vCPUs DEFMAXVCPUS := 0 -# Default memory size in MiB +##VAR DEFMEMSZ= Default memory size in MiB DEFMEMSZ := 2048 -# Default memory slots +##VAR DEFMEMSLOTS= Default memory slots # Cases to consider : # - nvdimm rootfs image # - preallocated memory # - vm template memory # - hugepage memory DEFMEMSLOTS := 10 -#Default number of bridges +##VAR DEFBRIDGES= Default number of bridges DEFBRIDGES := 0 DEFENABLEANNOTATIONS := [] DEFDISABLEGUESTSECCOMP := true DEFDISABLEGUESTEMPTYDIR := false -#Default experimental features enabled +##VAR DEFAULTEXPFEATURES=[features] Default experimental features enabled DEFAULTEXPFEATURES := [] DEFDISABLESELINUX := false -#Default entropy source +##VAR DEFENTROPYSOURCE=[entropy_source] Default entropy source DEFENTROPYSOURCE := /dev/urandom DEFVALIDENTROPYSOURCES := [\"/dev/urandom\",\"/dev/random\",\"\"] DEFDISABLEBLOCK := false @@ -116,8 +115,8 @@ ifeq ($(ARCH),x86_64) DEFVIRTIOFSDAEMON := $(LIBEXECDIR)/virtiofsd endif DEFVALIDVIRTIOFSDAEMONPATHS := [\"$(DEFVIRTIOFSDAEMON)\"] -# Default DAX mapping cache size in MiB -#if value is 0, DAX is not enabled +##VAR DEFVIRTIOFSCACHESIZE= Default DAX mapping cache size in MiB +# if value is 0, DAX is not enabled DEFVIRTIOFSCACHESIZE ?= 0 DEFVIRTIOFSCACHE ?= auto # Format example: @@ -134,7 +133,7 @@ DEFFILEMEMBACKEND := "" DEFVALIDFILEMEMBACKENDS := [\"$(DEFFILEMEMBACKEND)\"] DEFMSIZE9P := 8192 DEFVFIOMODE := guest-kernel -# Default cgroup model +##VAR DEFSANDBOXCGROUPONLY= Default cgroup model DEFSANDBOXCGROUPONLY ?= false DEFSTATICRESOURCEMGMT_DB ?= false DEFBINDMOUNTS := [] @@ -160,9 +159,9 @@ KNOWN_HYPERVISORS = CONFDIR := $(DEFAULTSDIR)/$(PROJECT_DIR) SYSCONFDIR := $(SYSCONFDIR)/$(PROJECT_DIR) -# Main configuration file location for stateless systems +##VAR CONFIG_PATH= Main configuration file location for stateless systems CONFIG_PATH := $(abspath $(CONFDIR)/$(CONFIG_FILE)) -# Secondary configuration file location. Note that this takes precedence +##VAR SYSCONFIG= Secondary configuration file location. Note that this takes precedence # over CONFIG_PATH. SYSCONFIG := $(abspath $(SYSCONFDIR)/$(CONFIG_FILE)) SHAREDIR := $(SHAREDIR) @@ -454,7 +453,7 @@ endif @printf "\tassets path (PKGDATADIR) : %s\n" $(abspath $(PKGDATADIR)) @printf "\tshim path (PKGLIBEXECDIR) : %s\n" $(abspath $(PKGLIBEXECDIR)) @printf "\n" -## help: Show help comments that start with `##VAR` and `##TARGET` +##TARGET help: Show help comments that start with `##VAR` and `##TARGET` in runtime-rs makefile help: Makefile show-summary @echo "========================== Help =============================" @echo "Variables:" diff --git a/src/runtime-rs/README.md b/src/runtime-rs/README.md index a9f85e45d5..a10e827ba2 100644 --- a/src/runtime-rs/README.md +++ b/src/runtime-rs/README.md @@ -97,6 +97,10 @@ Currently, only built-in `Dragonball` has been implemented. Persist defines traits and functions to help different components save state to disk and load state from disk. +### helper libraries + +Some helper libraries are maintained in [the library directory](../libs) so that they can be shared with other rust components. + ## Build and install ```bash diff --git a/src/runtime-rs/crates/resource/src/share_fs/mod.rs b/src/runtime-rs/crates/resource/src/share_fs/mod.rs index b7611964d6..b9abdb9df6 100644 --- a/src/runtime-rs/crates/resource/src/share_fs/mod.rs +++ b/src/runtime-rs/crates/resource/src/share_fs/mod.rs @@ -55,6 +55,7 @@ pub struct ShareFsVolumeConfig { pub target: String, pub readonly: bool, pub mount_options: Vec, + pub mount: oci::Mount, } pub struct ShareFsMountResult { diff --git a/src/runtime-rs/crates/resource/src/share_fs/share_virtio_fs_standalone.rs b/src/runtime-rs/crates/resource/src/share_fs/share_virtio_fs_standalone.rs index 9c798d7467..b0c9149735 100644 --- a/src/runtime-rs/crates/resource/src/share_fs/share_virtio_fs_standalone.rs +++ b/src/runtime-rs/crates/resource/src/share_fs/share_virtio_fs_standalone.rs @@ -67,7 +67,10 @@ impl ShareVirtioFsStandalone { fn virtiofsd_args(&self, sock_path: &str) -> Result> { let source_path = get_host_ro_shared_path(&self.config.id); if !source_path.exists() { - return Err(anyhow!("The virtiofs shared path didn't exist")); + return Err(anyhow!( + "The virtiofs shared path({:?}) didn't exist", + source_path + )); } let mut args: Vec = vec![ diff --git a/src/runtime-rs/crates/resource/src/share_fs/virtio_fs_share_mount.rs b/src/runtime-rs/crates/resource/src/share_fs/virtio_fs_share_mount.rs index 295554f110..ce32779748 100644 --- a/src/runtime-rs/crates/resource/src/share_fs/virtio_fs_share_mount.rs +++ b/src/runtime-rs/crates/resource/src/share_fs/virtio_fs_share_mount.rs @@ -8,12 +8,15 @@ use agent::Storage; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use kata_types::k8s::is_watchable_mount; +use kata_types::mount; +use nix::sys::stat::stat; use std::fs; use std::os::unix::fs::PermissionsExt; use std::path::Path; const WATCHABLE_PATH_NAME: &str = "watchable"; const WATCHABLE_BIND_DEV_TYPE: &str = "watchable-bind"; +const EPHEMERAL_PATH: &str = "/run/kata-containers/sandbox/ephemeral"; use super::{ utils, ShareFsMount, ShareFsMountResult, ShareFsRootfsConfig, ShareFsVolumeConfig, @@ -105,6 +108,51 @@ impl ShareFsMount for VirtiofsShareMount { let storages = vec![watchable_storage]; + return Ok(ShareFsMountResult { + guest_path, + storages, + }); + } else if config.mount.r#type == mount::KATA_EPHEMERAL_VOLUME_TYPE { + // refer to the golang `handleEphemeralStorage` code at + // https://github.com/kata-containers/kata-containers/blob/9516286f6dd5cfd6b138810e5d7c9e01cf6fc043/src/runtime/virtcontainers/kata_agent.go#L1354 + + let source = &config.mount.source; + let file_stat = + stat(Path::new(source)).with_context(|| format!("mount source {}", source))?; + + // if volume's gid isn't root group(default group), this means there's + // an specific fsGroup is set on this local volume, then it should pass + // to guest. + let dir_options = if file_stat.st_gid != 0 { + vec![format!("fsgid={}", file_stat.st_gid)] + } else { + vec![] + }; + + let file_name = Path::new(source) + .file_name() + .context("get file name from mount.source")?; + let source = Path::new(EPHEMERAL_PATH) + .join(file_name) + .into_os_string() + .into_string() + .map_err(|e| anyhow!("failed to get ephemeral path {:?}", e))?; + + // Create a storage struct so that kata agent is able to create + // tmpfs backed volume inside the VM + let ephemeral_storage = agent::Storage { + driver: String::from(mount::KATA_EPHEMERAL_VOLUME_TYPE), + driver_options: Vec::new(), + source: String::from("tmpfs"), + fs_type: String::from("tmpfs"), + fs_group: None, + options: dir_options, + mount_point: source.clone(), + }; + + guest_path = source; + let storages = vec![ephemeral_storage]; + return Ok(ShareFsMountResult { guest_path, storages, diff --git a/src/runtime-rs/crates/resource/src/volume/share_fs_volume.rs b/src/runtime-rs/crates/resource/src/volume/share_fs_volume.rs index d1a01d0306..c75ea5ca12 100644 --- a/src/runtime-rs/crates/resource/src/volume/share_fs_volume.rs +++ b/src/runtime-rs/crates/resource/src/volume/share_fs_volume.rs @@ -10,6 +10,7 @@ use anyhow::{anyhow, Context, Result}; use super::Volume; use crate::share_fs::{ShareFs, ShareFsVolumeConfig}; +use kata_types::mount; // copy file to container's rootfs if filesystem sharing is not supported, otherwise // bind mount it in the shared directory. @@ -66,6 +67,7 @@ impl ShareFsVolume { target: file_name, readonly: false, mount_options: m.options.clone(), + mount: m.clone(), }) .await .context("share fs volume")?; @@ -101,7 +103,8 @@ impl Volume for ShareFsVolume { } pub(crate) fn is_share_fs_volume(m: &oci::Mount) -> bool { - m.r#type == "bind" && !is_host_device(&m.destination) + (m.r#type == "bind" || m.r#type == mount::KATA_EPHEMERAL_VOLUME_TYPE) + && !is_host_device(&m.destination) } fn is_host_device(dest: &str) -> bool { diff --git a/src/runtime-rs/crates/runtimes/virt_container/src/container_manager/container.rs b/src/runtime-rs/crates/runtimes/virt_container/src/container_manager/container.rs index 834c68b77c..74853aec60 100644 --- a/src/runtime-rs/crates/runtimes/virt_container/src/container_manager/container.rs +++ b/src/runtime-rs/crates/runtimes/virt_container/src/container_manager/container.rs @@ -15,6 +15,7 @@ use common::{ ProcessType, }, }; +use kata_sys_util::k8s::update_ephemeral_storage_type; use oci::{LinuxResources, Process as OCIProcess}; use resource::ResourceManager; use tokio::sync::RwLock; @@ -110,6 +111,7 @@ impl Container { .context("handler volumes")?; let mut oci_mounts = vec![]; let mut storages = vec![]; + for v in volumes { let mut volume_mounts = v.get_volume_mount().context("get volume mount")?; if !volume_mounts.is_empty() { @@ -378,6 +380,9 @@ fn amend_spec(spec: &mut oci::Spec, disable_guest_seccomp: bool) -> Result<()> { // hook should be done on host spec.hooks = None; + // special process K8s ephemeral volumes. + update_ephemeral_storage_type(spec); + if let Some(linux) = spec.linux.as_mut() { if disable_guest_seccomp { linux.seccomp = None; diff --git a/src/runtime-rs/crates/service/src/manager.rs b/src/runtime-rs/crates/service/src/manager.rs index 214db1e47d..1019aa220a 100644 --- a/src/runtime-rs/crates/service/src/manager.rs +++ b/src/runtime-rs/crates/service/src/manager.rs @@ -155,7 +155,7 @@ impl ServiceManager { let handler = RuntimeHandlerManager::new(sid, sender) .await .context("new runtime handler")?; - handler.cleanup().await?; + handler.cleanup().await.context("runtime handler cleanup")?; let temp_dir = [KATA_PATH, sid].join("/"); if std::fs::metadata(temp_dir.as_str()).is_ok() { // try to remove dir and skip the result diff --git a/src/runtime-rs/crates/shim/src/shim_delete.rs b/src/runtime-rs/crates/shim/src/shim_delete.rs index 8429d34aeb..89d65b6101 100644 --- a/src/runtime-rs/crates/shim/src/shim_delete.rs +++ b/src/runtime-rs/crates/shim/src/shim_delete.rs @@ -6,6 +6,9 @@ use anyhow::{Context, Result}; use containerd_shim_protos::api; +use kata_sys_util::spec::{get_bundle_path, get_container_type, load_oci_spec}; +use kata_types::container::ContainerType; +use nix::{sys::signal::kill, sys::signal::SIGKILL, unistd::Pid}; use protobuf::Message; use std::{fs, path::Path}; @@ -14,7 +17,7 @@ use crate::{shim::ShimExecutor, Error}; impl ShimExecutor { pub async fn delete(&mut self) -> Result<()> { self.args.validate(true).context("validate")?; - let rsp = self.do_cleanup().await.context("do cleanup")?; + let rsp = self.do_cleanup().await.context("shim do cleanup")?; rsp.write_to_writer(&mut std::io::stdout()) .context(Error::FileWrite(format!("write {:?} to stdout", rsp)))?; Ok(()) @@ -41,9 +44,28 @@ impl ShimExecutor { info!(sl!(), "remote socket path: {:?}", &file_path); fs::remove_file(file_path).ok(); } - service::ServiceManager::cleanup(&self.args.id) - .await - .context("cleanup")?; + + if let Err(e) = service::ServiceManager::cleanup(&self.args.id).await { + error!( + sl!(), + "failed to cleanup in service manager: {:?}. force shutdown shim process", e + ); + + let bundle_path = get_bundle_path().context("get bundle path")?; + if let Ok(spec) = load_oci_spec() { + if let Ok(ContainerType::PodSandbox) = get_container_type(&spec) { + // only force shutdown for sandbox container + if let Ok(shim_pid) = self.read_pid_file(&bundle_path) { + info!(sl!(), "force to shutdown shim process {}", shim_pid); + let pid = Pid::from_raw(shim_pid as i32); + if let Err(_e) = kill(pid, SIGKILL) { + // ignore kill errors + } + } + } + } + } + Ok(rsp) } } diff --git a/src/tools/kata-ctl/.gitignore b/src/tools/kata-ctl/.gitignore new file mode 100644 index 0000000000..57872d0f1e --- /dev/null +++ b/src/tools/kata-ctl/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/src/tools/kata-ctl/Cargo.toml b/src/tools/kata-ctl/Cargo.toml new file mode 100644 index 0000000000..b92491ecd9 --- /dev/null +++ b/src/tools/kata-ctl/Cargo.toml @@ -0,0 +1,23 @@ +# Copyright (c) 2022 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +[package] +name = "kata-ctl" +version = "0.0.1" +authors = ["The Kata Containers community "] +edition = "2018" + +[dependencies] +anyhow = "1.0.31" +clap = { version = "3.2.20", features = ["derive", "cargo"] } +serde_json = "1.0.85" +thiserror = "1.0.35" + +# See: https://github.com/kata-containers/kata-containers/issues/5438 +[target.'cfg(not(target_arch = "s390x"))'.dependencies] +reqwest = { version = "0.11", default-features = false, features = ["json", "blocking", "rustls-tls"] } + +[dev-dependencies] +semver = "1.0.12" diff --git a/src/tools/kata-ctl/Makefile b/src/tools/kata-ctl/Makefile new file mode 100644 index 0000000000..e11af38457 --- /dev/null +++ b/src/tools/kata-ctl/Makefile @@ -0,0 +1,64 @@ +# Copyright (c) 2022 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +include ../../../utils.mk + +PROJECT_NAME = Kata Containers +PROJECT_URL = https://github.com/kata-containers +PROJECT_COMPONENT = kata-ctl + +TARGET = $(PROJECT_COMPONENT) + +VERSION_FILE := ./VERSION +VERSION := $(shell grep -v ^\# $(VERSION_FILE)) +COMMIT_NO := $(shell git rev-parse HEAD 2>/dev/null || true) +COMMIT_NO_SHORT := $(shell git rev-parse --short HEAD 2>/dev/null || true) +COMMIT := $(if $(shell git status --porcelain --untracked-files=no 2>/dev/null || true),${COMMIT_NO}-dirty,${COMMIT_NO}) + +# Exported to allow cargo to see it +export KATA_CTL_VERSION := $(if $(COMMIT),$(VERSION)-$(COMMIT),$(VERSION)) + +GENERATED_CODE = src/ops/version.rs + +GENERATED_REPLACEMENTS= \ + KATA_CTL_VERSION + +GENERATED_FILES := $(GENERATED_CODE) + +.DEFAULT_GOAL := default + +default: $(TARGET) build + +$(TARGET): $(GENERATED_CODE) + +build: + @RUSTFLAGS="$(EXTRA_RUSTFLAGS) --deny warnings" cargo build --target $(TRIPLE) $(if $(findstring release,$(BUILD_TYPE)),--release) $(EXTRA_RUSTFEATURES) + +$(GENERATED_FILES): %: %.in + @sed $(foreach r,$(GENERATED_REPLACEMENTS),-e 's|@$r@|$($r)|g') "$<" > "$@" + + +clean: + @cargo clean + @rm -f $(GENERATED_FILES) + +vendor: + cargo vendor + +test: + @RUSTFLAGS="$(EXTRA_RUSTFLAGS) --deny warnings" cargo test --target $(TRIPLE) $(if $(findstring release,$(BUILD_TYPE)),--release) $(EXTRA_RUSTFEATURES) + +install: + @RUSTFLAGS="$(EXTRA_RUSTFLAGS) --deny warnings" cargo install --target $(TRIPLE) --path . + +check: standard_rust_check + +.PHONY: \ + build \ + check \ + clean \ + install \ + test \ + vendor diff --git a/src/tools/kata-ctl/README.md b/src/tools/kata-ctl/README.md new file mode 100644 index 0000000000..bf908f60d0 --- /dev/null +++ b/src/tools/kata-ctl/README.md @@ -0,0 +1,49 @@ +# Kata Containers control tool + +## Overview + +The `kata-ctl` tool is a rust rewrite of the +[`kata-runtime`](../../runtime/cmd/kata-runtime) +[utility program](../../../docs/design/architecture/README.md#utility-program). + +The program provides a number of utility commands for: + +- Using advanced Kata Containers features. +- Problem determination and debugging. + +## Audience and environment + +Users and administrators. + +## Build the tool + +```bash +$ make +``` + +## Install the tool + +```bash +$ make install +``` + +## Run the tool + +```bash +$ kata-ctl ... +``` + +For example, to determine if your system is capable of running Kata +Containers, run: + +```bash +$ kata-ctl check all +``` + +### Full details + +For a usage statement, run: + +```bash +$ kata-ctl --help +``` diff --git a/src/tools/kata-ctl/VERSION b/src/tools/kata-ctl/VERSION new file mode 120000 index 0000000000..d62dc733ef --- /dev/null +++ b/src/tools/kata-ctl/VERSION @@ -0,0 +1 @@ +../../../VERSION \ No newline at end of file diff --git a/src/tools/kata-ctl/src/arch/aarch64/mod.rs b/src/tools/kata-ctl/src/arch/aarch64/mod.rs new file mode 100644 index 0000000000..6df39ce748 --- /dev/null +++ b/src/tools/kata-ctl/src/arch/aarch64/mod.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +#[cfg(target_arch = "aarch64")] +pub use arch_specific::*; + +mod arch_specific { + use anyhow::Result; + + pub fn check() -> Result<()> { + unimplemented!("Check not implemented in aarch64") + } +} diff --git a/src/tools/kata-ctl/src/arch/mod.rs b/src/tools/kata-ctl/src/arch/mod.rs new file mode 100644 index 0000000000..b85811a0ca --- /dev/null +++ b/src/tools/kata-ctl/src/arch/mod.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::Result; + +#[cfg(target_arch = "aarch64")] +pub mod aarch64; +#[cfg(target_arch = "aarch64")] +pub use aarch64 as arch_specific; + +#[cfg(target_arch = "powerpc64le")] +pub mod powerpc64le; +#[cfg(target_arch = "powerpc64le")] +pub use powerpc64le as arch_specific; + +#[cfg(target_arch = "s390x")] +pub mod s390x; +#[cfg(target_arch = "s390x")] +pub use s390x as arch_specific; + +#[cfg(target_arch = "x86_64")] +pub mod x86_64; +#[cfg(target_arch = "x86_64")] +pub use x86_64 as arch_specific; + +#[cfg(not(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "s390x", + target_arch = "x86_64" +)))] +compile_error!("unknown architecture"); + +pub fn check() -> Result<()> { + arch_specific::check() +} diff --git a/src/tools/kata-ctl/src/arch/powerpc64le/mod.rs b/src/tools/kata-ctl/src/arch/powerpc64le/mod.rs new file mode 100644 index 0000000000..a87ab02f8c --- /dev/null +++ b/src/tools/kata-ctl/src/arch/powerpc64le/mod.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +#[cfg(target_arch = "powerpc64le")] +pub use arch_specific::*; + +mod arch_specific { + use anyhow::Result; + + pub fn check() -> Result<()> { + unimplemented!("Check not implemented in powerpc64le"); + } +} diff --git a/src/tools/kata-ctl/src/arch/s390x/mod.rs b/src/tools/kata-ctl/src/arch/s390x/mod.rs new file mode 100644 index 0000000000..7f6a424c3b --- /dev/null +++ b/src/tools/kata-ctl/src/arch/s390x/mod.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +#[cfg(target_arch = "s390x")] +pub use arch_specific::*; + +mod arch_specific { + use anyhow::Result; + + pub fn check() -> Result<()> { + unimplemented!("Check not implemented in s390x"); + } +} diff --git a/src/tools/kata-ctl/src/arch/x86_64/mod.rs b/src/tools/kata-ctl/src/arch/x86_64/mod.rs new file mode 100644 index 0000000000..95817981e1 --- /dev/null +++ b/src/tools/kata-ctl/src/arch/x86_64/mod.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +#[cfg(target_arch = "x86_64")] +pub use arch_specific::*; + +mod arch_specific { + use crate::check; + use anyhow::{anyhow, Result}; + + const PROC_CPUINFO: &str = "/proc/cpuinfo"; + const CPUINFO_DELIMITER: &str = "\nprocessor"; + const CPUINFO_FLAGS_TAG: &str = "flags"; + const CPU_FLAGS_INTEL: &[&str] = &["lm", "sse4_1", "vmx"]; + const CPU_ATTRIBS_INTEL: &[&str] = &["GenuineIntel"]; + + // check cpu + fn check_cpu() -> Result<()> { + println!("INFO: check CPU: x86_64"); + + let cpu_info = check::get_single_cpu_info(PROC_CPUINFO, CPUINFO_DELIMITER)?; + + let cpu_flags = check::get_cpu_flags(&cpu_info, CPUINFO_FLAGS_TAG) + .map_err(|e| anyhow!("Error parsing CPU flags, file {:?}, {:?}", PROC_CPUINFO, e))?; + + // perform checks + // TODO: Perform checks based on hypervisor type + // TODO: Add more information to output (see kata-check in go tool); adjust formatting + let missing_cpu_attributes = check::check_cpu_attribs(&cpu_info, CPU_ATTRIBS_INTEL)?; + if !missing_cpu_attributes.is_empty() { + eprintln!( + "WARNING: Missing CPU attributes {:?}", + missing_cpu_attributes + ); + } + let missing_cpu_flags = check::check_cpu_flags(&cpu_flags, CPU_FLAGS_INTEL)?; + if !missing_cpu_flags.is_empty() { + eprintln!("WARNING: Missing CPU flags {:?}", missing_cpu_flags); + } + + Ok(()) + } + + pub fn check() -> Result<()> { + println!("INFO: check: x86_64"); + + let _cpu_result = check_cpu(); + + // TODO: collect outcome of tests to determine if checks pass or not + + Ok(()) + } +} diff --git a/src/tools/kata-ctl/src/args.rs b/src/tools/kata-ctl/src/args.rs new file mode 100644 index 0000000000..5cbd56caac --- /dev/null +++ b/src/tools/kata-ctl/src/args.rs @@ -0,0 +1,86 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use clap::{Args, Parser, Subcommand}; + +use thiserror::Error; + +#[derive(Parser, Debug)] +#[clap(name = "kata-ctl", author, about = "Kata Containers control tool")] +pub struct KataCtlCli { + #[clap(subcommand)] + pub command: Commands, +} + +#[derive(Debug, Subcommand)] +pub enum Commands { + /// Test if system can run Kata Containers + Check(CheckArgument), + + /// Directly assign a volume to Kata Containers to manage + DirectVolume, + + /// Display settings + Env, + + /// Enter into guest VM by debug console + Exec, + + /// Manage VM factory + Factory, + + /// Manage guest VM iptables + Iptables(IptablesCommand), + + /// Gather metrics associated with infrastructure used to run a sandbox + Metrics(MetricsCommand), + + /// Display version details + Version, +} + +#[derive(Debug, Args, Error)] +#[error("Argument is not valid")] +pub struct CheckArgument { + #[clap(subcommand)] + pub command: CheckSubCommand, +} + +#[derive(Debug, Subcommand)] +pub enum CheckSubCommand { + /// Run all checks + All, + + /// Run all checks but excluding network checks. + NoNetworkChecks, + + /// Only compare the current and latest available versions + CheckVersionOnly, +} + +#[derive(Debug, Args)] +pub struct MetricsCommand { + #[clap(subcommand)] + pub metrics_cmd: MetricsSubCommand, +} + +#[derive(Debug, Subcommand)] +pub enum MetricsSubCommand { + /// Arguments for metrics + MetricsArgs, +} + +// #[derive(Parser, Debug)] +#[derive(Debug, Args)] +pub struct IptablesCommand { + #[clap(subcommand)] + pub iptables: IpTablesArguments, +} + +#[derive(Debug, Subcommand)] +pub enum IpTablesArguments { + /// Configure iptables + Metrics, +} diff --git a/src/tools/kata-ctl/src/check.rs b/src/tools/kata-ctl/src/check.rs new file mode 100644 index 0000000000..2dbadeb545 --- /dev/null +++ b/src/tools/kata-ctl/src/check.rs @@ -0,0 +1,246 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Contains checks that are not architecture-specific + +use anyhow::{anyhow, Result}; +// See: https://github.com/kata-containers/kata-containers/issues/5438 +#[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" +))] +use reqwest::header::{CONTENT_TYPE, USER_AGENT}; +use serde_json::Value; +#[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" +))] +use std::collections::HashMap; +use std::fs; + +const KATA_GITHUB_URL: &str = + "https://api.github.com/repos/kata-containers/kata-containers/releases/latest"; + +fn get_cpu_info(cpu_info_file: &str) -> Result { + let contents = fs::read_to_string(cpu_info_file)?; + Ok(contents) +} + +// get_single_cpu_info returns the contents of the first cpu from +// the specified cpuinfo file by parsing based on a specified delimiter +pub fn get_single_cpu_info(cpu_info_file: &str, substring: &str) -> Result { + let contents = get_cpu_info(cpu_info_file)?; + + if contents.is_empty() { + return Err(anyhow!("cpu_info string is empty"))?; + } + + let subcontents: Vec<&str> = contents.split(substring).collect(); + let result = subcontents + .first() + .ok_or("error splitting contents of cpuinfo") + .map_err(|e| anyhow!(e))? + .to_string(); + + Ok(result) +} + +// get_cpu_flags returns a string of cpu flags from cpuinfo, passed in +// as a string +pub fn get_cpu_flags(cpu_info: &str, cpu_flags_tag: &str) -> Result { + if cpu_info.is_empty() { + return Err(anyhow!("cpu_info string is empty"))?; + } + + let subcontents: Vec<&str> = cpu_info.split('\n').collect(); + for line in subcontents { + if line.starts_with(cpu_flags_tag) { + let line_data: Vec<&str> = line.split(':').collect(); + let flags = line_data + .last() + .ok_or("error splitting flags in cpuinfo") + .map_err(|e| anyhow!(e))? + .to_string(); + return Ok(flags); + } + } + + Ok("".to_string()) +} + +// get_missing_strings searches for required (strings) in data and returns +// a vector containing the missing strings +fn get_missing_strings(data: &str, required: &'static [&'static str]) -> Result> { + let mut missing: Vec = Vec::new(); + + for item in required { + if !data.split_whitespace().any(|x| x == *item) { + missing.push(item.to_string()); + } + } + + Ok(missing) +} + +pub fn check_cpu_flags( + retrieved_flags: &str, + required_flags: &'static [&'static str], +) -> Result> { + let missing_flags = get_missing_strings(retrieved_flags, required_flags)?; + + Ok(missing_flags) +} + +pub fn check_cpu_attribs( + cpu_info: &str, + required_attribs: &'static [&'static str], +) -> Result> { + let mut cpu_info_processed = cpu_info.replace('\t', ""); + cpu_info_processed = cpu_info_processed.replace('\n', " "); + + let missing_attribs = get_missing_strings(&cpu_info_processed, required_attribs)?; + Ok(missing_attribs) +} + +pub fn run_network_checks() -> Result<()> { + Ok(()) +} + +#[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" +))] +fn get_kata_version_by_url(url: &str) -> std::result::Result { + let content = reqwest::blocking::Client::new() + .get(url) + .header(CONTENT_TYPE, "application/json") + .header(USER_AGENT, "kata") + .send()? + .json::>()?; + + let version = content["tag_name"].as_str().unwrap(); + Ok(version.to_string()) +} + +#[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" +))] +fn handle_reqwest_error(e: reqwest::Error) -> anyhow::Error { + if e.is_connect() { + return anyhow!(e).context("http connection failure: connection refused"); + } + + if e.is_timeout() { + return anyhow!(e).context("http connection failure: connection timeout"); + } + + if e.is_builder() { + return anyhow!(e).context("http connection failure: url malformed"); + } + + if e.is_decode() { + return anyhow!(e).context("http connection failure: unable to decode response body"); + } + + anyhow!(e).context("unknown http connection failure: {:?}") +} + +#[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" +))] +pub fn check_version() -> Result<()> { + let version = get_kata_version_by_url(KATA_GITHUB_URL).map_err(handle_reqwest_error)?; + + println!("Version: {}", version); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use semver::Version; + + #[test] + fn test_get_cpu_info_empty_input() { + let expected = "No such file or directory (os error 2)"; + let actual = get_cpu_info("").err().unwrap().to_string(); + assert_eq!(expected, actual); + + let actual = get_single_cpu_info("", "\nprocessor") + .err() + .unwrap() + .to_string(); + assert_eq!(expected, actual); + } + + #[test] + fn test_get_cpu_flags_empty_input() { + let expected = "cpu_info string is empty"; + let actual = get_cpu_flags("", "").err().unwrap().to_string(); + assert_eq!(expected, actual); + } + + #[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" + ))] + #[test] + fn check_version_by_empty_url() { + const TEST_URL: &str = "http:"; + let expected = "builder error: empty host"; + let actual = get_kata_version_by_url(TEST_URL).err().unwrap().to_string(); + assert_eq!(expected, actual); + } + + #[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" + ))] + #[test] + fn check_version_by_garbage_url() { + const TEST_URL: &str = "_localhost_"; + let expected = "builder error: relative URL without a base"; + let actual = get_kata_version_by_url(TEST_URL).err().unwrap().to_string(); + assert_eq!(expected, actual); + } + + #[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" + ))] + #[test] + fn check_version_by_invalid_url() { + const TEST_URL: &str = "http://localhost :80"; + let expected = "builder error: invalid domain character"; + let actual = get_kata_version_by_url(TEST_URL).err().unwrap().to_string(); + assert_eq!(expected, actual); + } + + #[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" + ))] + #[test] + fn check_latest_version() { + let version = get_kata_version_by_url(KATA_GITHUB_URL).unwrap(); + + let v = Version::parse(&version).unwrap(); + assert!(!v.major.to_string().is_empty()); + assert!(!v.minor.to_string().is_empty()); + assert!(!v.patch.to_string().is_empty()); + } +} diff --git a/src/tools/kata-ctl/src/main.rs b/src/tools/kata-ctl/src/main.rs new file mode 100644 index 0000000000..30e4b5eb7a --- /dev/null +++ b/src/tools/kata-ctl/src/main.rs @@ -0,0 +1,42 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +mod arch; +mod args; +mod check; +mod ops; + +use anyhow::Result; +use clap::Parser; +use std::process::exit; + +use args::{Commands, KataCtlCli}; + +use ops::check_ops::{ + handle_check, handle_check_volume, handle_env, handle_exec, handle_factory, handle_iptables, + handle_metrics, handle_version, +}; + +fn real_main() -> Result<()> { + let args = KataCtlCli::parse(); + + match args.command { + Commands::Check(args) => handle_check(args), + Commands::DirectVolume => handle_check_volume(), + Commands::Env => handle_env(), + Commands::Exec => handle_exec(), + Commands::Factory => handle_factory(), + Commands::Iptables(args) => handle_iptables(args), + Commands::Metrics(args) => handle_metrics(args), + Commands::Version => handle_version(), + } +} + +fn main() { + if let Err(e) = real_main() { + eprintln!("ERROR: {:#?}", e); + exit(1); + } +} diff --git a/src/tools/kata-ctl/src/ops.rs b/src/tools/kata-ctl/src/ops.rs new file mode 100644 index 0000000000..3e0f9a4e32 --- /dev/null +++ b/src/tools/kata-ctl/src/ops.rs @@ -0,0 +1,7 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub mod check_ops; +pub mod version; diff --git a/src/tools/kata-ctl/src/ops/check_ops.rs b/src/tools/kata-ctl/src/ops/check_ops.rs new file mode 100644 index 0000000000..80556894c8 --- /dev/null +++ b/src/tools/kata-ctl/src/ops/check_ops.rs @@ -0,0 +1,79 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use crate::arch; +use crate::check; +use crate::ops::version; + +use crate::args::{CheckArgument, CheckSubCommand, IptablesCommand, MetricsCommand}; + +use anyhow::Result; + +const NAME: &str = "kata-ctl"; + +pub fn handle_check(checkcmd: CheckArgument) -> Result<()> { + let command = checkcmd.command; + + match command { + CheckSubCommand::All => { + // run architecture-specific tests + arch::check()?; + + // run code that uses network checks + check::run_network_checks()?; + } + + CheckSubCommand::NoNetworkChecks => { + // run architecture-specific tests + arch::check()?; + } + + CheckSubCommand::CheckVersionOnly => { + // retrieve latest release + #[cfg(any( + target_arch = "aarch64", + target_arch = "powerpc64le", + target_arch = "x86_64" + ))] + check::check_version()?; + + // See: https://github.com/kata-containers/kata-containers/issues/5438 + #[cfg(target_arch = "s390x")] + unimplemented!("Network check not implemented on s390x") + } + } + + Ok(()) +} + +pub fn handle_check_volume() -> Result<()> { + Ok(()) +} + +pub fn handle_env() -> Result<()> { + Ok(()) +} + +pub fn handle_exec() -> Result<()> { + Ok(()) +} + +pub fn handle_factory() -> Result<()> { + Ok(()) +} + +pub fn handle_iptables(_args: IptablesCommand) -> Result<()> { + Ok(()) +} + +pub fn handle_metrics(_args: MetricsCommand) -> Result<()> { + Ok(()) +} + +pub fn handle_version() -> Result<()> { + let version = version::get().unwrap(); + println!("{} version {:?} (type: rust)", NAME, version); + Ok(()) +} diff --git a/src/tools/kata-ctl/src/ops/version.rs.in b/src/tools/kata-ctl/src/ops/version.rs.in new file mode 100644 index 0000000000..052eccf168 --- /dev/null +++ b/src/tools/kata-ctl/src/ops/version.rs.in @@ -0,0 +1,39 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// WARNING: This file is auto-generated - DO NOT EDIT! +// + +use clap::crate_version; + +const KATA_CTL_VERSION: &str = "@KATA_CTL_VERSION@"; + +pub fn get() -> Result { + if KATA_CTL_VERSION.trim().is_empty() { + Err("Unable to retrieve kata Version. Check that Kata is properly installed".to_string()) + } else { + let version = format!("{}-{}", KATA_CTL_VERSION, crate_version!()); + + Ok(version) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use semver::Version; + + #[test] + fn test_get() { + let version = get().unwrap(); + let v = Version::parse(&version).unwrap(); + + assert!(!v.major.to_string().is_empty()); + assert!(!v.minor.to_string().is_empty()); + assert!(!v.patch.to_string().is_empty()); + assert!(!v.pre.to_string().is_empty()); + } +} diff --git a/utils.mk b/utils.mk index 85b408bdeb..c156424c18 100644 --- a/utils.mk +++ b/utils.mk @@ -173,6 +173,7 @@ TRIPLE = $(ARCH)-unknown-linux-$(LIBC) CWD := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) standard_rust_check: + @echo "standard rust check..." cargo fmt -- --check cargo clippy --all-targets --all-features --release \ -- \