diff --git a/src/libs/Cargo.lock b/src/libs/Cargo.lock index c9888c433d..7a7ad017a4 100644 --- a/src/libs/Cargo.lock +++ b/src/libs/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.57" @@ -225,6 +234,12 @@ dependencies = [ "slab", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "hashbrown" version = "0.11.2" @@ -240,6 +255,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "indexmap" version = "1.8.1" @@ -287,8 +311,11 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" name = "kata-types" version = "0.1.0" dependencies = [ + "glob", "lazy_static", + "num_cpus", "oci", + "regex", "serde", "slog", "slog-scope", @@ -427,6 +454,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "oci" version = "0.1.0" @@ -584,6 +621,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "remove_dir_all" version = "0.5.3" diff --git a/src/libs/kata-types/Cargo.toml b/src/libs/kata-types/Cargo.toml index 5c6035e36a..ecae6f0184 100644 --- a/src/libs/kata-types/Cargo.toml +++ b/src/libs/kata-types/Cargo.toml @@ -11,7 +11,10 @@ license = "Apache-2.0" edition = "2018" [dependencies] +glob = "0.3.0" lazy_static = "1.4.0" +num_cpus = "1.13.1" +regex = "1.5.4" serde = { version = "1.0.100", features = ["derive"] } slog = "2.5.2" slog-scope = "4.4.0" diff --git a/src/libs/kata-types/src/config/default.rs b/src/libs/kata-types/src/config/default.rs index 4c501c06f5..722156206b 100644 --- a/src/libs/kata-types/src/config/default.rs +++ b/src/libs/kata-types/src/config/default.rs @@ -21,3 +21,40 @@ lazy_static! { } pub const DEFAULT_INTERNETWORKING_MODEL: &str = "tcfilter"; + +pub const DEFAULT_BLOCK_DEVICE_TYPE: &str = "virtio-blk"; +pub const DEFAULT_VHOST_USER_STORE_PATH: &str = "/var/run/vhost-user"; +pub const DEFAULT_BLOCK_NVDIMM_MEM_OFFSET: u64 = 0; + +pub const DEFAULT_SHARED_FS_TYPE: &str = "virtio-9p"; +pub const DEFAULT_VIRTIO_FS_CACHE_MODE: &str = "none"; +pub const DEFAULT_VIRTIO_FS_DAX_SIZE_MB: u32 = 1024; +pub const DEFAULT_SHARED_9PFS_SIZE: u32 = 128 * 1024; +pub const MIN_SHARED_9PFS_SIZE: u32 = 4 * 1024; +pub const MAX_SHARED_9PFS_SIZE: u32 = 8 * 1024 * 1024; + +pub const DEFAULT_GUEST_HOOK_PATH: &str = "/opt"; + +pub const DEFAULT_GUEST_VCPUS: u32 = 1; + +// Default configuration for Dragonball +pub const DEFAULT_DB_GUEST_KENREL_IMAGE: &str = "vmlinuz"; +pub const DEFAULT_DB_GUEST_KENREL_PARAMS: &str = ""; +pub const DEFAULT_DB_ENTROPY_SOURCE: &str = "/dev/urandom"; +pub const DEFAULT_DB_MEMORY_SIZE: u32 = 128; +pub const DEFAULT_DB_MEMORY_SLOTS: u32 = 128; +pub const MAX_DB_VCPUS: u32 = 256; + +// Default configuration for qemu +pub const DEFAULT_QEMU_BINARY_PATH: &str = "qemu"; +pub const DEFAULT_QEMU_CONTROL_PATH: &str = ""; +pub const DEFAULT_QEMU_MACHINE_TYPE: &str = "q35"; +pub const DEFAULT_QEMU_ENTROPY_SOURCE: &str = "/dev/urandom"; +pub const DEFAULT_QEMU_GUEST_KENREL_IMAGE: &str = "vmlinuz"; +pub const DEFAULT_QEMU_GUEST_KENREL_PARAMS: &str = ""; +pub const DEFAULT_QEMU_FIRMWARE_PATH: &str = ""; +pub const DEFAULT_QEMU_MEMORY_SIZE: u32 = 128; +pub const DEFAULT_QEMU_MEMORY_SLOTS: u32 = 128; +pub const DEFAULT_QEMU_PCI_BRIDGES: u32 = 2; +pub const MAX_QEMU_PCI_BRIDGES: u32 = 5; +pub const MAX_QEMU_VCPUS: u32 = 256; diff --git a/src/libs/kata-types/src/config/hypervisor/dragonball.rs b/src/libs/kata-types/src/config/hypervisor/dragonball.rs new file mode 100644 index 0000000000..8bde9cec76 --- /dev/null +++ b/src/libs/kata-types/src/config/hypervisor/dragonball.rs @@ -0,0 +1,176 @@ +// Copyright (c) 2019-2021 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::io::Result; +use std::path::Path; +use std::sync::Arc; + +use super::{default, register_hypervisor_plugin}; +use crate::config::hypervisor::{ + VIRTIO_BLK, VIRTIO_BLK_MMIO, VIRTIO_FS, VIRTIO_FS_INLINE, VIRTIO_PMEM, +}; +use crate::config::{ConfigPlugin, TomlConfig}; +use crate::{eother, resolve_path, validate_path}; + +/// Hypervisor name for qemu, used to index `TomlConfig::hypervisor`. +pub const HYPERVISOR_NAME_DRAGONBALL: &str = "dragonball"; + +/// Configuration information for dragonball. +#[derive(Default, Debug)] +pub struct DragonballConfig {} + +impl DragonballConfig { + /// Create a new instance of `DragonballConfig`. + pub fn new() -> Self { + DragonballConfig {} + } + + /// Register the dragonball plugin. + pub fn register(self) { + let plugin = Arc::new(self); + register_hypervisor_plugin(HYPERVISOR_NAME_DRAGONBALL, plugin); + } +} + +impl ConfigPlugin for DragonballConfig { + fn name(&self) -> &str { + HYPERVISOR_NAME_DRAGONBALL + } + + /// Adjust the configuration information after loading from configuration file. + fn adjust_configuration(&self, conf: &mut TomlConfig) -> Result<()> { + if let Some(db) = conf.hypervisor.get_mut(HYPERVISOR_NAME_DRAGONBALL) { + resolve_path!(db.jailer_path, "Dragonball jailer path {} is invalid: {}")?; + + if db.boot_info.kernel.is_empty() { + db.boot_info.kernel = default::DEFAULT_DB_GUEST_KENREL_IMAGE.to_string(); + } + if db.boot_info.kernel_params.is_empty() { + db.boot_info.kernel_params = default::DEFAULT_DB_GUEST_KENREL_PARAMS.to_string(); + } + + if db.cpu_info.default_maxvcpus > default::MAX_DB_VCPUS { + db.cpu_info.default_maxvcpus = default::MAX_DB_VCPUS; + } + + if db.machine_info.entropy_source.is_empty() { + db.machine_info.entropy_source = default::DEFAULT_DB_ENTROPY_SOURCE.to_string(); + } + + if db.memory_info.default_memory == 0 { + db.memory_info.default_memory = default::DEFAULT_DB_MEMORY_SIZE; + } + if db.memory_info.memory_slots == 0 { + db.memory_info.memory_slots = default::DEFAULT_DB_MEMORY_SLOTS; + } + } + Ok(()) + } + + /// Validate the configuration information. + fn validate(&self, conf: &TomlConfig) -> Result<()> { + if let Some(db) = conf.hypervisor.get(HYPERVISOR_NAME_DRAGONBALL) { + if !db.path.is_empty() { + return Err(eother!("Path for dragonball hypervisor should be empty")); + } + if !db.valid_hypervisor_paths.is_empty() { + return Err(eother!( + "Valid hypervisor path for dragonball hypervisor should be empty" + )); + } + if !db.ctlpath.is_empty() { + return Err(eother!("CtlPath for dragonball hypervisor should be empty")); + } + if !db.valid_ctlpaths.is_empty() { + return Err(eother!("CtlPath for dragonball hypervisor should be empty")); + } + validate_path!(db.jailer_path, "Dragonball jailer path {} is invalid: {}")?; + if db.enable_iothreads { + return Err(eother!("Dragonball hypervisor doesn't support IO threads.")); + } + + if !db.blockdev_info.disable_block_device_use + && db.blockdev_info.block_device_driver != VIRTIO_BLK + && db.blockdev_info.block_device_driver != VIRTIO_BLK_MMIO + && db.blockdev_info.block_device_driver != VIRTIO_PMEM + { + return Err(eother!( + "{} is unsupported block device type.", + db.blockdev_info.block_device_driver + )); + } + + if db.boot_info.kernel.is_empty() { + return Err(eother!( + "Guest kernel image for dragonball hypervisor is empty" + )); + } + if db.boot_info.image.is_empty() { + return Err(eother!( + "Guest boot image for dragonball hypervisor is empty" + )); + } + if !db.boot_info.initrd.is_empty() { + return Err(eother!("Initrd for dragonball hypervisor should be empty")); + } + if !db.boot_info.firmware.is_empty() { + return Err(eother!( + "Firmware for dragonball hypervisor should be empty" + )); + } + + if (db.cpu_info.default_vcpus > 0 + && db.cpu_info.default_vcpus as u32 > default::MAX_DB_VCPUS) + || db.cpu_info.default_maxvcpus > default::MAX_DB_VCPUS + { + return Err(eother!( + "Dragonball hypervisor can not support {} vCPUs", + db.cpu_info.default_maxvcpus + )); + } + + if db.device_info.enable_iommu || db.device_info.enable_iommu_platform { + return Err(eother!("Dragonball hypervisor does not support vIOMMU")); + } + if db.device_info.hotplug_vfio_on_root_bus + || db.device_info.default_bridges > 0 + || db.device_info.pcie_root_port > 0 + { + return Err(eother!( + "Dragonball hypervisor does not support PCI hotplug options" + )); + } + + if !db.machine_info.machine_type.is_empty() { + return Err(eother!( + "Dragonball hypervisor does not support machine_type" + )); + } + if !db.machine_info.pflashes.is_empty() { + return Err(eother!("Dragonball hypervisor does not support pflashes")); + } + + if db.memory_info.enable_guest_swap { + return Err(eother!( + "Dragonball hypervisor doesn't support enable_guest_swap" + )); + } + + if db.security_info.rootless { + return Err(eother!( + "Dragonball hypervisor does not support rootless mode" + )); + } + + if let Some(v) = db.shared_fs.shared_fs.as_ref() { + if v != VIRTIO_FS && v != VIRTIO_FS_INLINE { + return Err(eother!("Dragonball hypervisor doesn't support {}", v)); + } + } + } + + Ok(()) + } +} diff --git a/src/libs/kata-types/src/config/hypervisor/mod.rs b/src/libs/kata-types/src/config/hypervisor/mod.rs new file mode 100644 index 0000000000..23c74c971a --- /dev/null +++ b/src/libs/kata-types/src/config/hypervisor/mod.rs @@ -0,0 +1,1052 @@ +// Copyright (c) 2019-2021 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Configuration information for hypervisors. +//! +//! The configuration information for hypervisors is complex, and different hypervisor requires +//! different configuration information. To make it flexible and extensible, we build a multi-layer +//! architecture to manipulate hypervisor configuration information. +//! - the vendor layer. The `HypervisorVendor` structure provides hook points for vendors to +//! customize the configuration for its deployment. +//! - the hypervisor plugin layer. The hypervisor plugin layer provides hook points for different +//! hypervisors to manipulate the configuration information. +//! - the hypervisor common layer. This layer handles generic logic for all types of hypervisors. +//! +//! These three layers are applied in order. So changes made by the vendor layer will be visible +//! to the hypervisor plugin layer and the common layer. And changes made by the plugin layer will +//! only be visible to the common layer. +//! +//! Ideally the hypervisor configuration information should be split into hypervisor specific +//! part and common part. But the Kata 2.0 has adopted a policy to build a superset for all +//! hypervisors, so let's contain it... + +use std::collections::HashMap; +use std::io::Result; +use std::path::Path; +use std::sync::{Arc, Mutex}; + +use lazy_static::lazy_static; +use regex::RegexSet; + +use super::{default, ConfigOps, ConfigPlugin, TomlConfig}; +use crate::{eother, resolve_path, validate_path}; + +mod dragonball; +pub use self::dragonball::{DragonballConfig, HYPERVISOR_NAME_DRAGONBALL}; + +mod qemu; +pub use self::qemu::{QemuConfig, HYPERVISOR_NAME_QEMU}; + +const VIRTIO_BLK: &str = "virtio-blk"; +const VIRTIO_BLK_MMIO: &str = "virtio-mmio"; +const VIRTIO_BLK_CCW: &str = "virtio-blk-ccw"; +const VIRTIO_SCSI: &str = "virtio-scsi"; +const VIRTIO_PMEM: &str = "nvdimm"; +const VIRTIO_9P: &str = "virtio-9p"; +const VIRTIO_FS: &str = "virtio-fs"; +const VIRTIO_FS_INLINE: &str = "inline-virtio-fs"; + +lazy_static! { + static ref HYPERVISOR_PLUGINS: Mutex>> = + Mutex::new(HashMap::new()); +} + +/// Register a hypervisor plugin with `name`. +pub fn register_hypervisor_plugin(name: &str, plugin: Arc) { + let mut hypervisors = HYPERVISOR_PLUGINS.lock().unwrap(); + hypervisors.insert(name.to_string(), plugin); +} + +/// Get the hypervisor plugin with `name`. +pub fn get_hypervisor_plugin(name: &str) -> Option> { + let hypervisors = HYPERVISOR_PLUGINS.lock().unwrap(); + hypervisors.get(name).cloned() +} + +/// Configuration information for block device. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct BlockDeviceInfo { + /// Disable block device from being used for a container's rootfs. + /// + /// In case of a storage driver like devicemapper where a container's root file system is + /// backed by a block device, the block device is passed directly to the hypervisor for + /// performance reasons. This flag prevents the block device from being passed to the + /// hypervisor, shared fs is used instead to pass the rootfs. + #[serde(default)] + pub disable_block_device_use: bool, + + /// Block storage driver to be used for the hypervisor in case the container rootfs is backed + /// by a block device. This is virtio-scsi, virtio-blk or nvdimm. + #[serde(default)] + pub block_device_driver: String, + + /// Specifies cache-related options will be set to block devices or not. + #[serde(default)] + pub block_device_cache_set: bool, + + /// Specifies cache-related options for block devices. + /// + /// Denotes whether use of O_DIRECT (bypass the host page cache) is enabled. + #[serde(default)] + pub block_device_cache_direct: bool, + + /// Specifies cache-related options for block devices. + /// Denotes whether flush requests for the device are ignored. + #[serde(default)] + pub block_device_cache_noflush: bool, + + /// If false and nvdimm is supported, use nvdimm device to plug guest image. + #[serde(default)] + pub disable_image_nvdimm: bool, + + /// The size in MiB will be plused to max memory of hypervisor. + /// + /// It is the memory address space for the NVDIMM devie. If set block storage driver + /// (block_device_driver) to "nvdimm", should set memory_offset to the size of block device. + #[serde(default)] + pub memory_offset: u64, + + /// Enable vhost-user storage device, default false + /// + /// Enabling this will result in some Linux reserved block type major range 240-254 being + /// chosen to represent vhost-user devices. + #[serde(default)] + pub enable_vhost_user_store: bool, + + /// The base directory specifically used for vhost-user devices. + /// + /// Its sub-path "block" is used for block devices; "block/sockets" is where we expect + /// vhost-user sockets to live; "block/devices" is where simulated block device nodes for + /// vhost-user devices to live. + #[serde(default)] + pub vhost_user_store_path: String, + + /// List of valid annotations values for the vhost user store path. + /// + /// The default if not set is empty (all annotations rejected.) + #[serde(default)] + pub valid_vhost_user_store_paths: Vec, +} + +impl BlockDeviceInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + if self.disable_block_device_use { + self.block_device_driver = "".to_string(); + self.enable_vhost_user_store = false; + self.memory_offset = 0; + return Ok(()); + } + + if self.block_device_driver.is_empty() { + self.block_device_driver = default::DEFAULT_BLOCK_DEVICE_TYPE.to_string(); + } + if self.memory_offset == 0 { + self.memory_offset = default::DEFAULT_BLOCK_NVDIMM_MEM_OFFSET; + } + if !self.enable_vhost_user_store { + self.vhost_user_store_path = String::new(); + } else if self.vhost_user_store_path.is_empty() { + self.vhost_user_store_path = default::DEFAULT_VHOST_USER_STORE_PATH.to_string(); + } + resolve_path!( + self.vhost_user_store_path, + "Invalid vhost-user-store-path {}: {}" + )?; + + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + if self.disable_block_device_use { + return Ok(()); + } + if self.block_device_driver != VIRTIO_BLK + && self.block_device_driver != VIRTIO_BLK_CCW + && self.block_device_driver != VIRTIO_BLK_MMIO + && self.block_device_driver != VIRTIO_SCSI + && self.block_device_driver != VIRTIO_PMEM + { + return Err(eother!( + "{} is unsupported block device type.", + self.block_device_driver + )); + } + validate_path!( + self.vhost_user_store_path, + "Invalid vhost-user-store-path {}: {}" + )?; + + Ok(()) + } + + /// Validate path of vhost-user storage backend. + pub fn validate_vhost_user_store_path>(&self, path: P) -> Result<()> { + validate_path_pattern(&self.valid_vhost_user_store_paths, path) + } +} + +/// Guest kernel boot information. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct BootInfo { + /// Path to guest kernel file on host + #[serde(default)] + pub kernel: String, + /// Guest kernel commandline. + #[serde(default)] + pub kernel_params: String, + /// Path to initrd file on host + #[serde(default)] + pub initrd: String, + /// Path to root device on host + #[serde(default)] + pub image: String, + /// Path to the firmware. + /// + /// If you want that qemu uses the default firmware leave this option empty. + #[serde(default)] + pub firmware: String, +} + +impl BootInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + resolve_path!(self.kernel, "guest kernel image file {} is invalid: {}")?; + resolve_path!(self.image, "guest boot image file {} is invalid: {}")?; + resolve_path!(self.initrd, "guest initrd image file {} is invalid: {}")?; + resolve_path!(self.firmware, "firmware image file {} is invalid: {}")?; + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + validate_path!(self.kernel, "guest kernel image file {} is invalid: {}")?; + validate_path!(self.image, "guest boot image file {} is invalid: {}")?; + validate_path!(self.initrd, "guest initrd image file {} is invalid: {}")?; + validate_path!(self.firmware, "firmware image file {} is invalid: {}")?; + if !self.image.is_empty() && !self.initrd.is_empty() { + return Err(eother!("Can not configure both initrd and image for boot")); + } + Ok(()) + } +} + +/// Virtual CPU configuration information. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct CpuInfo { + /// CPU features, comma-separated list of cpu features to pass to the cpu. + /// For example, `cpu_features = "pmu=off,vmx=off" + #[serde(default)] + pub cpu_features: String, + + /// Default number of vCPUs per SB/VM: + /// - unspecified or 0 --> will be set to @DEFVCPUS@ + /// - < 0 --> will be set to the actual number of physical cores + /// > 0 <= number of physical cores --> will be set to the specified number + /// > number of physical cores --> will be set to the actual number of physical cores + #[serde(default)] + pub default_vcpus: i32, + + /// Default maximum number of vCPUs per SB/VM: + /// - unspecified or == 0 --> will be set to the actual number of physical cores or + /// to the maximum number of vCPUs supported by KVM + /// if that number is exceeded + /// - > 0 <= number of physical cores --> will be set to the specified number + /// - > number of physical cores --> will be set to the actual number of physical cores or + /// to the maximum number of vCPUs supported by KVM + /// if that number is exceeded + /// + /// WARNING: Depending of the architecture, the maximum number of vCPUs supported by KVM is used + /// when the actual number of physical cores is greater than it. + /// + /// WARNING: Be aware that this value impacts the virtual machine's memory footprint and CPU + /// the hotplug functionality. For example, `default_maxvcpus = 240` specifies that until 240 + /// vCPUs can be added to a SB/VM, but the memory footprint will be big. Another example, with + /// `default_maxvcpus = 8` the memory footprint will be small, but 8 will be the maximum number + /// of vCPUs supported by the SB/VM. In general, we recommend that you do not edit this + /// variable, unless you know what are you doing. + /// + /// NOTICE: on arm platform with gicv2 interrupt controller, set it to 8. + #[serde(default)] + pub default_maxvcpus: u32, +} + +impl CpuInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + let features: Vec<&str> = self.cpu_features.split(',').map(|v| v.trim()).collect(); + self.cpu_features = features.join(","); + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + Ok(()) + } + + /// Get default number of guest vCPUs. + pub fn get_default_vcpus(&self) -> u32 { + let cpus = num_cpus::get() as u32; + if self.default_vcpus < 0 || self.default_vcpus as u32 > cpus { + cpus + } else if self.default_vcpus == 0 { + default::DEFAULT_GUEST_VCPUS + } else { + self.default_vcpus as u32 + } + } + + /// Get default maximal number of guest vCPUs. + pub fn get_default_max_vcpus(&self) -> u32 { + let cpus = num_cpus::get() as u32; + if self.default_maxvcpus == 0 || self.default_maxvcpus > cpus { + cpus + } else { + self.default_maxvcpus + } + } +} + +/// Configuration information for shared filesystem, such virtio-9p and virtio-fs. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct DebugInfo { + /// This option changes the default hypervisor and kernel parameters to enable debug output + /// where available. + #[serde(default)] + pub enable_debug: bool, + + /// Enable dumping information about guest page structures if true. + #[serde(default)] + pub guest_memory_dump_paging: bool, + + /// Set where to save the guest memory dump file. + /// + /// If set, when GUEST_PANICKED event occurred, guest memory will be dumped to host filesystem + /// under guest_memory_dump_path. This directory will be created automatically if it does not + /// exist. The dumped file(also called vmcore) can be processed with crash or gdb. + /// + /// # WARNING: + /// Dump guest’s memory can take very long depending on the amount of guest memory and use + /// much disk space. + #[serde(default)] + pub guest_memory_dump_path: String, +} + +impl DebugInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + Ok(()) + } +} + +/// Virtual machine device configuration information. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct DeviceInfo { + /// Bridges can be used to hot plug devices. + /// + /// Limitations: + /// - Currently only pci bridges are supported + /// - Until 30 devices per bridge can be hot plugged. + /// - Until 5 PCI bridges can be cold plugged per VM. + /// + /// This limitation could be a bug in qemu or in the kernel + /// Default number of bridges per SB/VM: + /// - unspecified or 0 --> will be set to @DEFBRIDGES@ + /// - > 1 <= 5 --> will be set to the specified number + /// - > 5 --> will be set to 5 + #[serde(default)] + pub default_bridges: u32, + + /// VFIO devices are hotplugged on a bridge by default. + /// + /// Enable hotplugging on root bus. This may be required for devices with a large PCI bar, + /// as this is a current limitation with hotplugging on a bridge. + #[serde(default)] + pub hotplug_vfio_on_root_bus: bool, + + /// Before hot plugging a PCIe device, you need to add a pcie_root_port device. + /// + /// Use this parameter when using some large PCI bar devices, such as Nvidia GPU. + /// The value means the number of pcie_root_port. + /// This value is valid when hotplug_vfio_on_root_bus is true and machine_type is "q35" + #[serde(default)] + pub pcie_root_port: u32, + + /// Enable vIOMMU, default false + /// + /// Enabling this will result in the VM having a vIOMMU device. This will also add the + /// following options to the kernel's command line: intel_iommu=on,iommu=pt + #[serde(default)] + pub enable_iommu: bool, + + /// Enable IOMMU_PLATFORM, default false + /// + /// Enabling this will result in the VM device having iommu_platform=on set + #[serde(default)] + pub enable_iommu_platform: bool, +} + +impl DeviceInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + if self.default_bridges > 5 { + self.default_bridges = 5; + } + + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + if self.default_bridges > 5 { + return Err(eother!( + "The configured PCI bridges {} is too big", + self.default_bridges + )); + } + Ok(()) + } +} + +/// Configuration information for virtual machine. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct MachineInfo { + /// Virtual machine model/type. + #[serde(default)] + pub machine_type: String, + + /// Machine accelerators. + /// Comma-separated list of machine accelerators to pass to the hypervisor. + /// For example, `machine_accelerators = "nosmm,nosmbus,nosata,nopit,static-prt,nofw"` + #[serde(default)] + pub machine_accelerators: String, + + /// Add flash image file to VM. + /// + /// The arguments of it should be in format of ["/path/to/flash0.img", "/path/to/flash1.img"]. + #[serde(default)] + pub pflashes: Vec, + + /// Default entropy source. + /// The path to a host source of entropy (including a real hardware RNG). + /// `/dev/urandom` and `/dev/random` are two main options. Be aware that `/dev/random` is a + /// blocking source of entropy. If the host runs out of entropy, the VMs boot time will + /// increase leading to get startup timeouts. The source of entropy `/dev/urandom` is + /// non-blocking and provides a generally acceptable source of entropy. It should work well + /// for pretty much all practical purposes. + #[serde(default)] + pub entropy_source: String, + + /// List of valid annotations values for entropy_source. + /// The default if not set is empty (all annotations rejected.) + #[serde(default)] + pub valid_entropy_sources: Vec, +} + +impl MachineInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + let accelerators: Vec<&str> = self + .machine_accelerators + .split(',') + .map(|v| v.trim()) + .collect(); + self.machine_accelerators = accelerators.join(","); + + for pflash in self.pflashes.iter_mut() { + resolve_path!(*pflash, "Flash image file {} is invalide: {}")?; + } + resolve_path!(self.entropy_source, "Entropy source {} is invalid: {}")?; + + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + for pflash in self.pflashes.iter() { + validate_path!(*pflash, "Flash image file {} is invalide: {}")?; + } + validate_path!(self.entropy_source, "Entropy source {} is invalid: {}")?; + Ok(()) + } + + /// Validate path of entropy source. + pub fn validate_entropy_source>(&self, path: P) -> Result<()> { + validate_path_pattern(&self.valid_entropy_sources, path) + } +} + +/// Virtual machine memory configuration information. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct MemoryInfo { + /// Default memory size in MiB for SB/VM. + #[serde(default)] + pub default_memory: u32, + + /// Default memory slots per SB/VM. + /// + /// This is will determine the times that memory will be hotadded to sandbox/VM. + #[serde(default)] + pub memory_slots: u32, + + /// Enable file based guest memory support. + /// + /// The default is an empty string which will disable this feature. In the case of virtio-fs, + /// this is enabled automatically and '/dev/shm' is used as the backing folder. This option + /// will be ignored if VM templating is enabled. + #[serde(default)] + pub file_mem_backend: String, + + /// List of valid annotations values for the file_mem_backend annotation + /// + /// The default if not set is empty (all annotations rejected.) + #[serde(default)] + pub valid_file_mem_backends: Vec, + + /// Enable pre allocation of VM RAM, default false + /// + /// Enabling this will result in lower container density as all of the memory will be allocated + /// and locked. This is useful when you want to reserve all the memory upfront or in the cases + /// where you want memory latencies to be very predictable + #[serde(default)] + pub enable_mem_prealloc: bool, + + /// Enable huge pages for VM RAM, default false + /// + /// Enabling this will result in the VM memory being allocated using huge pages. This is useful + /// when you want to use vhost-user network stacks within the container. This will automatically + /// result in memory pre allocation. + #[serde(default)] + pub enable_hugepages: bool, + + /// Specifies virtio-mem will be enabled or not. + /// + /// Please note that this option should be used with the command + /// "echo 1 > /proc/sys/vm/overcommit_memory". + #[serde(default)] + pub enable_virtio_mem: bool, + + /// Enable swap of vm memory. Default false. + /// + /// The behaviour is undefined if mem_prealloc is also set to true + #[serde(default)] + pub enable_swap: bool, + + /// Enable swap in the guest. Default false. + /// + /// When enable_guest_swap is enabled, insert a raw file to the guest as the swap device if the + /// swappiness of a container (set by annotation "io.katacontainers.container.resource.swappiness") + /// is bigger than 0. + /// + /// The size of the swap device should be swap_in_bytes (set by annotation + /// "io.katacontainers.container.resource.swap_in_bytes") - memory_limit_in_bytes. + /// If swap_in_bytes is not set, the size should be memory_limit_in_bytes. + /// If swap_in_bytes and memory_limit_in_bytes is not set, the size should be default_memory. + #[serde(default)] + pub enable_guest_swap: bool, +} + +impl MemoryInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + resolve_path!( + self.file_mem_backend, + "Memory backend file {} is invalid: {}" + )?; + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + validate_path!( + self.file_mem_backend, + "Memory backend file {} is invalid: {}" + )?; + if self.default_memory == 0 { + return Err(eother!("Configured memory size for guest vm is zero")); + } + if self.memory_slots == 0 { + return Err(eother!("Configured memory slots for guest vm is zero")); + } + + Ok(()) + } + + /// Validate path of memory backend files. + pub fn validate_memory_backend_path>(&self, path: P) -> Result<()> { + validate_path_pattern(&self.valid_file_mem_backends, path) + } +} + +/// Configuration information for virtual machine. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct NetworkInfo { + /// If vhost-net backend for virtio-net is not desired, set to true. + /// + /// Default is false, which trades off security (vhost-net runs ring0) for network I/O + /// performance. + #[serde(default)] + pub disable_vhost_net: bool, + + /// Use rx Rate Limiter to control network I/O inbound bandwidth(size in bits/sec for SB/VM). + /// + /// In Qemu, we use classful qdiscs HTB(Hierarchy Token Bucket) to discipline traffic. + /// Default 0-sized value means unlimited rate. + #[serde(default)] + pub rx_rate_limiter_max_rate: u64, + + /// Use tx Rate Limiter to control network I/O outbound bandwidth(size in bits/sec for SB/VM). + /// + /// In Qemu, we use classful qdiscs HTB(Hierarchy Token Bucket) and ifb(Intermediate Functional + /// Block) to discipline traffic. + /// Default 0-sized value means unlimited rate. + #[serde(default)] + pub tx_rate_limiter_max_rate: u64, +} + +impl NetworkInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + Ok(()) + } +} + +/// Configuration information for virtual machine. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct SecurityInfo { + /// Enable running QEMU VMM as a non-root user. + /// + /// By default QEMU VMM run as root. When this is set to true, QEMU VMM process runs as + /// a non-root random user. See documentation for the limitations of this mode. + #[serde(default)] + pub rootless: bool, + + /// Disable seccomp. + #[serde(default)] + pub disable_seccomp: bool, + + /// Enable confidential guest support. + /// + /// Toggling that setting may trigger different hardware features, ranging from memory + /// encryption to both memory and CPU-state encryption and integrity.The Kata Containers + /// runtime dynamically detects the available feature set and aims at enabling the largest + /// possible one. + #[serde(default)] + pub confidential_guest: bool, + + /// Path to OCI hook binaries in the *guest rootfs*. + /// + /// This does not affect host-side hooks which must instead be added to the OCI spec passed to + /// the runtime. + /// + /// You can create a rootfs with hooks by customizing the osbuilder scripts: + /// https://github.com/kata-containers/kata-containers/tree/main/tools/osbuilder + /// + /// Hooks must be stored in a subdirectory of guest_hook_path according to their hook type, + /// i.e. "guest_hook_path/{prestart,poststart,poststop}". The agent will scan these directories + /// for executable files and add them, in lexicographical order, to the lifecycle of the guest + /// container. + /// + /// Hooks are executed in the runtime namespace of the guest. See the official documentation: + /// https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#posix-platform-hooks + /// + /// Warnings will be logged if any error is encountered while scanning for hooks, but it will + /// not abort container execution. + #[serde(default)] + pub guest_hook_path: String, + + /// List of valid annotation names for the hypervisor. + /// + /// Each member of the list is a regular expression, which is the base name of the annotation, + /// e.g. "path" for io.katacontainers.config.hypervisor.path" + #[serde(default)] + pub enable_annotations: Vec, +} + +impl SecurityInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + if self.guest_hook_path.is_empty() { + self.guest_hook_path = default::DEFAULT_GUEST_HOOK_PATH.to_string(); + } + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + Ok(()) + } + + /// Check whether annotation key is enabled or not. + pub fn is_annotation_enabled(&self, path: &str) -> bool { + if !path.starts_with("io.katacontainers.config.hypervisor.") { + return false; + } + let pos = "io.katacontainers.config.hypervisor.".len(); + let key = &path[pos..]; + if let Ok(set) = RegexSet::new(&self.enable_annotations) { + return set.is_match(key); + } + + false + } +} + +/// Configuration information for shared filesystem, such virtio-9p and virtio-fs. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct SharedFsInfo { + /// Shared file system type: + /// - virtio-fs (default) + /// - virtio-9p` + pub shared_fs: Option, + + /// Path to vhost-user-fs daemon. + #[serde(default)] + pub virtio_fs_daemon: String, + + /// List of valid annotations values for the virtiofs daemon + /// The default if not set is empty (all annotations rejected.) + #[serde(default)] + pub valid_virtio_fs_daemon_paths: Vec, + + /// Extra args for virtiofsd daemon + /// + /// Format example: + /// ["-o", "arg1=xxx,arg2", "-o", "hello world", "--arg3=yyy"] + /// + /// see `virtiofsd -h` for possible options. + #[serde(default)] + pub virtio_fs_extra_args: Vec, + + /// Cache mode: + /// - none: Metadata, data, and pathname lookup are not cached in guest. They are always + /// fetched from host and any changes are immediately pushed to host. + /// - auto: Metadata and pathname lookup cache expires after a configured amount of time + /// (default is 1 second). Data is cached while the file is open (close to open consistency). + /// - always: Metadata, data, and pathname lookup are cached in guest and never expire. + #[serde(default)] + pub virtio_fs_cache: String, + + /// Default size of DAX cache in MiB + #[serde(default)] + pub virtio_fs_cache_size: u32, + + /// Enable virtio-fs DAX window if true. + #[serde(default)] + pub virtio_fs_is_dax: bool, + + /// This is the msize used for 9p shares. It is the number of bytes used for 9p packet payload. + #[serde(default)] + pub msize_9p: u32, +} + +impl SharedFsInfo { + /// Adjust the configuration information after loading from configuration file. + pub fn adjust_configuration(&mut self) -> Result<()> { + if self.shared_fs.as_deref() == Some("") { + self.shared_fs = Some(default::DEFAULT_SHARED_FS_TYPE.to_string()); + } + match self.shared_fs.as_deref() { + Some(VIRTIO_FS) => self.adjust_virtio_fs(false)?, + Some(VIRTIO_FS_INLINE) => self.adjust_virtio_fs(true)?, + Some(VIRTIO_9P) => { + if self.msize_9p == 0 { + self.msize_9p = default::DEFAULT_SHARED_9PFS_SIZE; + } + } + _ => {} + } + + Ok(()) + } + + /// Validate the configuration information. + pub fn validate(&self) -> Result<()> { + match self.shared_fs.as_deref() { + None => Ok(()), + Some(VIRTIO_FS) => self.validate_virtio_fs(false), + Some(VIRTIO_FS_INLINE) => self.validate_virtio_fs(true), + Some(VIRTIO_9P) => { + if self.msize_9p < default::MIN_SHARED_9PFS_SIZE + || self.msize_9p > default::MAX_SHARED_9PFS_SIZE + { + return Err(eother!( + "Invalid 9p configuration msize 0x{:x}", + self.msize_9p + )); + } + Ok(()) + } + Some(v) => Err(eother!("Invalid shared_fs type {}", v)), + } + } + + /// Validate path of virtio-fs daemon, especially for annotations. + pub fn validate_virtiofs_daemon_path>(&self, path: P) -> Result<()> { + validate_path_pattern(&self.valid_virtio_fs_daemon_paths, path) + } + + fn adjust_virtio_fs(&mut self, _inline: bool) -> Result<()> { + resolve_path!( + self.virtio_fs_daemon, + "Virtio-fs daemon path {} is invalid: {}" + )?; + if self.virtio_fs_cache.is_empty() { + self.virtio_fs_cache = default::DEFAULT_VIRTIO_FS_CACHE_MODE.to_string(); + } + if self.virtio_fs_is_dax && self.virtio_fs_cache_size == 0 { + self.virtio_fs_cache_size = default::DEFAULT_VIRTIO_FS_DAX_SIZE_MB; + } + if !self.virtio_fs_is_dax && self.virtio_fs_cache_size != 0 { + self.virtio_fs_is_dax = true; + } + Ok(()) + } + + fn validate_virtio_fs(&self, inline: bool) -> Result<()> { + if inline && !self.virtio_fs_daemon.is_empty() { + return Err(eother!( + "Executable path for inline-virtio-fs is not empty: {}", + &self.virtio_fs_daemon + )); + } + validate_path!( + self.virtio_fs_daemon, + "Virtio-fs daemon path {} is invalid: {}" + )?; + + if self.virtio_fs_cache != "none" + && self.virtio_fs_cache != "auto" + && self.virtio_fs_cache != "always" + { + return Err(eother!( + "Invalid virtio-fs cache mode: {}", + &self.virtio_fs_cache + )); + } + if self.virtio_fs_is_dax && self.virtio_fs_cache_size == 0 { + return Err(eother!( + "Invalid virtio-fs DAX window size: {}", + &self.virtio_fs_cache_size + )); + } + Ok(()) + } +} + +/// Common configuration information for hypervisors. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct Hypervisor { + /// Path to the hypervisor executable. + #[serde(default)] + pub path: String, + /// List of valid annotations values for the hypervisor. + /// + /// Each member of the list is a path pattern as described by glob(3). The default if not set + /// is empty (all annotations rejected.) + #[serde(default)] + pub valid_hypervisor_paths: Vec, + + /// Hypervisor control executable path. + #[serde(default)] + pub ctlpath: String, + /// List of valid annotations values for the hypervisor control executable. + /// + /// Each member of the list is a path pattern as described by glob(3). The default if not set + /// is empty (all annotations rejected.) + #[serde(default)] + pub valid_ctlpaths: Vec, + + /// Control channel path. + #[serde(default)] + pub jailer_path: String, + /// List of valid annotations values for the hypervisor jailer path. + /// + /// Each member of the list is a path pattern as described by glob(3). The default if not set + /// is empty (all annotations rejected.) + #[serde(default)] + pub valid_jailer_paths: Vec, + + /// Disable the customizations done in the runtime when it detects that it is running on top + /// a VMM. This will result in the runtime behaving as it would when running on bare metal. + #[serde(default)] + pub disable_nesting_checks: bool, + + /// Enable iothreads (data-plane) to be used. This causes IO to be handled in a separate IO + /// thread. This is currently only implemented for SCSI. + #[serde(default)] + pub enable_iothreads: bool, + + /// Block device configuration information. + #[serde(default, flatten)] + pub blockdev_info: BlockDeviceInfo, + + /// Guest system boot information. + #[serde(default, flatten)] + pub boot_info: BootInfo, + + /// Guest virtual CPU configuration information. + #[serde(default, flatten)] + pub cpu_info: CpuInfo, + + /// Debug configuration information. + #[serde(default, flatten)] + pub debug_info: DebugInfo, + + /// Device configuration information. + #[serde(default, flatten)] + pub device_info: DeviceInfo, + + /// Virtual machine configuration information. + #[serde(default, flatten)] + pub machine_info: MachineInfo, + + /// Virtual machine memory configuration information. + #[serde(default, flatten)] + pub memory_info: MemoryInfo, + + /// Network configuration information. + #[serde(default, flatten)] + pub network_info: NetworkInfo, + + /// Security configuration information. + #[serde(default, flatten)] + pub security_info: SecurityInfo, + + /// Shared file system configuration information. + #[serde(default, flatten)] + pub shared_fs: SharedFsInfo, + + /// Vendor customized runtime configuration. + #[serde(default, flatten)] + pub vendor: HypervisorVendor, +} + +impl Hypervisor { + /// Validate path of hypervisor executable. + pub fn validate_hypervisor_path>(&self, path: P) -> Result<()> { + validate_path_pattern(&self.valid_hypervisor_paths, path) + } + + /// Validate path of hypervisor control executable. + pub fn validate_hypervisor_ctlpath>(&self, path: P) -> Result<()> { + validate_path_pattern(&self.valid_ctlpaths, path) + } + + /// Validate path of jailer executable. + pub fn validate_jailer_path>(&self, path: P) -> Result<()> { + validate_path_pattern(&self.valid_jailer_paths, path) + } +} + +impl ConfigOps for Hypervisor { + fn adjust_configuration(conf: &mut TomlConfig) -> Result<()> { + HypervisorVendor::adjust_configuration(conf)?; + + let hypervisors: Vec = conf.hypervisor.keys().cloned().collect(); + for hypervisor in hypervisors.iter() { + if let Some(plugin) = get_hypervisor_plugin(hypervisor) { + plugin.adjust_configuration(conf)?; + + // Safe to unwrap() because `hypervisor` is a valid key in the hash map. + let hv = conf.hypervisor.get_mut(hypervisor).unwrap(); + hv.blockdev_info.adjust_configuration()?; + hv.boot_info.adjust_configuration()?; + hv.cpu_info.adjust_configuration()?; + hv.debug_info.adjust_configuration()?; + hv.device_info.adjust_configuration()?; + hv.machine_info.adjust_configuration()?; + hv.memory_info.adjust_configuration()?; + hv.network_info.adjust_configuration()?; + hv.security_info.adjust_configuration()?; + hv.shared_fs.adjust_configuration()?; + } else { + return Err(eother!("Can not find plugin for hypervisor {}", hypervisor)); + } + } + + Ok(()) + } + + fn validate(conf: &TomlConfig) -> Result<()> { + HypervisorVendor::validate(conf)?; + + let hypervisors: Vec = conf.hypervisor.keys().cloned().collect(); + for hypervisor in hypervisors.iter() { + if let Some(plugin) = get_hypervisor_plugin(hypervisor) { + plugin.validate(conf)?; + + // Safe to unwrap() because `hypervisor` is a valid key in the hash map. + let hv = conf.hypervisor.get(hypervisor).unwrap(); + hv.blockdev_info.validate()?; + hv.boot_info.validate()?; + hv.cpu_info.validate()?; + hv.debug_info.validate()?; + hv.device_info.validate()?; + hv.machine_info.validate()?; + hv.memory_info.validate()?; + hv.network_info.validate()?; + hv.security_info.validate()?; + hv.shared_fs.validate()?; + validate_path!(hv.path, "Hypervisor binary path `{}` is invalid: {}")?; + validate_path!( + hv.ctlpath, + "Hypervisor control executable `{}` is invalid: {}" + )?; + validate_path!(hv.jailer_path, "Hypervisor jailer path `{}` is invalid: {}")?; + } else { + return Err(eother!("Can not find plugin for hypervisor {}", hypervisor)); + } + } + + Ok(()) + } +} + +#[cfg(not(feature = "enable-vendor"))] +mod vendor { + use super::*; + + /// Vendor customization runtime configuration. + #[derive(Debug, Default, Deserialize, Serialize)] + pub struct HypervisorVendor {} + + impl ConfigOps for HypervisorVendor {} +} + +#[cfg(feature = "enable-vendor")] +#[path = "vendor.rs"] +mod vendor; + +use crate::config::validate_path_pattern; +pub use vendor::HypervisorVendor; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_register_plugin() { + let db = DragonballConfig::new(); + db.register(); + + let db = Arc::new(DragonballConfig::new()); + register_hypervisor_plugin("dragonball", db); + + assert!(get_hypervisor_plugin("dragonball").is_some()); + assert!(get_hypervisor_plugin("dragonball2").is_none()); + } +} diff --git a/src/libs/kata-types/src/config/hypervisor/qemu.rs b/src/libs/kata-types/src/config/hypervisor/qemu.rs new file mode 100644 index 0000000000..87b4a11bef --- /dev/null +++ b/src/libs/kata-types/src/config/hypervisor/qemu.rs @@ -0,0 +1,135 @@ +// Copyright (c) 2019-2021 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::io::Result; +use std::path::Path; +use std::sync::Arc; + +use super::{default, register_hypervisor_plugin}; +use crate::config::hypervisor::VIRTIO_BLK_MMIO; +use crate::config::{ConfigPlugin, TomlConfig}; +use crate::{eother, resolve_path, validate_path}; + +/// Hypervisor name for qemu, used to index `TomlConfig::hypervisor`. +pub const HYPERVISOR_NAME_QEMU: &str = "qemu"; + +/// Configuration information for qemu. +#[derive(Default, Debug)] +pub struct QemuConfig {} + +impl QemuConfig { + /// Create a new instance of `QemuConfig`. + pub fn new() -> Self { + QemuConfig {} + } + + /// Register the qemu plugin. + pub fn register(self) { + let plugin = Arc::new(self); + register_hypervisor_plugin(HYPERVISOR_NAME_QEMU, plugin); + } +} + +impl ConfigPlugin for QemuConfig { + fn name(&self) -> &str { + HYPERVISOR_NAME_QEMU + } + + /// Adjust the configuration information after loading from configuration file. + fn adjust_configuration(&self, conf: &mut TomlConfig) -> Result<()> { + if let Some(qemu) = conf.hypervisor.get_mut(HYPERVISOR_NAME_QEMU) { + if qemu.path.is_empty() { + qemu.path = default::DEFAULT_QEMU_BINARY_PATH.to_string(); + } + resolve_path!(qemu.path, "Qemu binary path `{}` is invalid: {}")?; + if qemu.ctlpath.is_empty() { + qemu.ctlpath = default::DEFAULT_QEMU_CONTROL_PATH.to_string(); + } + resolve_path!(qemu.ctlpath, "Qemu ctlpath `{}` is invalid: {}")?; + + if qemu.boot_info.kernel.is_empty() { + qemu.boot_info.kernel = default::DEFAULT_QEMU_GUEST_KENREL_IMAGE.to_string(); + } + if qemu.boot_info.kernel_params.is_empty() { + qemu.boot_info.kernel_params = + default::DEFAULT_QEMU_GUEST_KENREL_PARAMS.to_string(); + } + if qemu.boot_info.firmware.is_empty() { + qemu.boot_info.firmware = default::DEFAULT_QEMU_FIRMWARE_PATH.to_string(); + } + + if qemu.device_info.default_bridges == 0 { + qemu.device_info.default_bridges = default::DEFAULT_QEMU_PCI_BRIDGES; + if qemu.device_info.default_bridges > default::MAX_QEMU_PCI_BRIDGES { + qemu.device_info.default_bridges = default::MAX_QEMU_PCI_BRIDGES; + } + } + + if qemu.machine_info.machine_type.is_empty() { + qemu.machine_info.machine_type = default::DEFAULT_QEMU_MACHINE_TYPE.to_string(); + } + if qemu.machine_info.entropy_source.is_empty() { + qemu.machine_info.entropy_source = default::DEFAULT_QEMU_ENTROPY_SOURCE.to_string(); + } + + if qemu.memory_info.default_memory == 0 { + qemu.memory_info.default_memory = default::DEFAULT_QEMU_MEMORY_SIZE; + } + if qemu.memory_info.memory_slots == 0 { + qemu.memory_info.memory_slots = default::DEFAULT_QEMU_MEMORY_SLOTS; + } + } + + Ok(()) + } + + /// Validate the configuration information. + fn validate(&self, conf: &TomlConfig) -> Result<()> { + if let Some(qemu) = conf.hypervisor.get(HYPERVISOR_NAME_QEMU) { + validate_path!(qemu.path, "Qemu binary path `{}` is invalid: {}")?; + validate_path!(qemu.ctlpath, "Qemu control path `{}` is invalid: {}")?; + if !qemu.jailer_path.is_empty() { + return Err(eother!("Path for Qemu jailer should be empty")); + } + if !qemu.valid_jailer_paths.is_empty() { + return Err(eother!("Valid Qemu jailer path list should be empty")); + } + + if !qemu.blockdev_info.disable_block_device_use + && qemu.blockdev_info.block_device_driver == VIRTIO_BLK_MMIO + { + return Err(eother!("Qemu doesn't support virtio-blk-mmio")); + } + + if qemu.boot_info.kernel.is_empty() { + return Err(eother!("Guest kernel image for qemu is empty")); + } + if qemu.boot_info.image.is_empty() && qemu.boot_info.initrd.is_empty() { + return Err(eother!( + "Both guest boot image and initrd for qemu are empty" + )); + } + + if (qemu.cpu_info.default_vcpus > 0 + && qemu.cpu_info.default_vcpus as u32 > default::MAX_QEMU_VCPUS) + || qemu.cpu_info.default_maxvcpus > default::MAX_QEMU_VCPUS + { + return Err(eother!( + "Qemu hypervisor can not support {} vCPUs", + qemu.cpu_info.default_maxvcpus + )); + } + + if qemu.device_info.default_bridges > default::MAX_QEMU_PCI_BRIDGES { + return Err(eother!( + "Qemu hypervisor can not support {} PCI bridges", + qemu.device_info.default_bridges + )); + } + } + + Ok(()) + } +} diff --git a/src/libs/kata-types/src/config/hypervisor/vendor.rs b/src/libs/kata-types/src/config/hypervisor/vendor.rs new file mode 100644 index 0000000000..9b51d10165 --- /dev/null +++ b/src/libs/kata-types/src/config/hypervisor/vendor.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2021 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! A sample for vendor to customize the hypervisor implementation. + +use super::*; + +/// Vendor customization runtime configuration. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct HypervisorVendor {} + +impl ConfigOps for HypervisorVendor {} diff --git a/src/libs/kata-types/src/config/mod.rs b/src/libs/kata-types/src/config/mod.rs index 5d13263016..70fd81a5d8 100644 --- a/src/libs/kata-types/src/config/mod.rs +++ b/src/libs/kata-types/src/config/mod.rs @@ -4,19 +4,38 @@ // SPDX-License-Identifier: Apache-2.0 // +use std::collections::HashMap; use std::fs; use std::io::{self, Result}; use std::path::{Path, PathBuf}; -use crate::sl; +use crate::{eother, sl}; /// Default configuration values. pub mod default; +mod hypervisor; +pub use self::hypervisor::{ + BootInfo, DragonballConfig, Hypervisor, QemuConfig, HYPERVISOR_NAME_DRAGONBALL, + HYPERVISOR_NAME_QEMU, +}; + mod runtime; pub use self::runtime::{Runtime, RuntimeVendor}; /// Trait to manipulate global Kata configuration information. +pub trait ConfigPlugin: Send + Sync { + /// Get the plugin name. + fn name(&self) -> &str; + + /// Adjust the configuration information after loading from configuration file. + fn adjust_configuration(&self, _conf: &mut TomlConfig) -> Result<()>; + + /// Validate the configuration information. + fn validate(&self, _conf: &TomlConfig) -> Result<()>; +} + +/// Trait to manipulate Kata configuration information. pub trait ConfigOps { /// Adjust the configuration information after loading from configuration file. fn adjust_configuration(_conf: &mut TomlConfig) -> Result<()> { @@ -45,6 +64,9 @@ pub trait ConfigObjectOps { /// Kata configuration information. #[derive(Debug, Default, Deserialize, Serialize)] pub struct TomlConfig { + /// Configuration information for hypervisors. + #[serde(default)] + pub hypervisor: HashMap, /// Kata runtime configuration information. #[serde(default)] pub runtime: Runtime, @@ -99,6 +121,7 @@ impl TomlConfig { pub fn load(content: &str) -> Result { let mut config: TomlConfig = toml::from_str(content)?; + Hypervisor::adjust_configuration(&mut config)?; Runtime::adjust_configuration(&mut config)?; info!(sl!(), "get kata config: {:?}", config); @@ -107,6 +130,7 @@ impl TomlConfig { /// Validate Kata configuration information. pub fn validate(&self) -> Result<()> { + Hypervisor::validate(self)?; Runtime::validate(self)?; Ok(()) @@ -123,3 +147,49 @@ impl TomlConfig { Err(io::Error::from(io::ErrorKind::NotFound)) } } + +/// Validate the `path` matches one of the pattern in `patterns`. +/// +/// Each member in `patterns` is a path pattern as described by glob(3) +pub fn validate_path_pattern>(patterns: &[String], path: P) -> Result<()> { + let path = path + .as_ref() + .to_str() + .ok_or_else(|| eother!("Invalid path {}", path.as_ref().to_string_lossy()))?; + + for p in patterns.iter() { + if let Ok(glob) = glob::Pattern::new(p) { + if glob.matches(path) { + return Ok(()); + } + } + } + + Err(eother!("Path {} is not permitted", path)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_validate_path_pattern() { + let patterns = []; + validate_path_pattern(&patterns, "/bin/ls").unwrap_err(); + + let patterns = ["/bin".to_string()]; + validate_path_pattern(&patterns, "/bin/ls").unwrap_err(); + + let patterns = ["/bin/*/ls".to_string()]; + validate_path_pattern(&patterns, "/bin/ls").unwrap_err(); + + let patterns = ["/bin/*".to_string()]; + validate_path_pattern(&patterns, "/bin/ls").unwrap(); + + let patterns = ["/*".to_string()]; + validate_path_pattern(&patterns, "/bin/ls").unwrap(); + + let patterns = ["/usr/share".to_string(), "/bin/*".to_string()]; + validate_path_pattern(&patterns, "/bin/ls").unwrap(); + } +} diff --git a/src/libs/kata-types/tests/test-config.rs b/src/libs/kata-types/tests/test-config.rs new file mode 100644 index 0000000000..e88a250469 --- /dev/null +++ b/src/libs/kata-types/tests/test-config.rs @@ -0,0 +1,41 @@ +use kata_types::config::{QemuConfig, TomlConfig, HYPERVISOR_NAME_QEMU}; +use std::fs; +use std::path::Path; + +#[test] +fn test_load_qemu_config() { + let plugin = QemuConfig::new(); + plugin.register(); + + let path = env!("CARGO_MANIFEST_DIR"); + let path = Path::new(path).join("tests/texture/configuration-qemu.toml"); + let content = fs::read_to_string(&path).unwrap(); + let config = TomlConfig::load(&content).unwrap(); + + let qemu = config.hypervisor.get(HYPERVISOR_NAME_QEMU).unwrap(); + assert_eq!(qemu.path, "/usr/bin/ls"); + assert_eq!(qemu.valid_hypervisor_paths.len(), 2); + assert_eq!(qemu.valid_hypervisor_paths[0], "/usr/bin/qemu*"); + assert_eq!(qemu.valid_hypervisor_paths[1], "/opt/qemu?"); + qemu.validate_hypervisor_path("/usr/bin/qemu0").unwrap(); + qemu.validate_hypervisor_path("/usr/bin/qemu1").unwrap(); + qemu.validate_hypervisor_path("/usr/bin/qemu2222").unwrap(); + qemu.validate_hypervisor_path("/opt/qemu3").unwrap(); + qemu.validate_hypervisor_path("/opt/qemu").unwrap_err(); + qemu.validate_hypervisor_path("/opt/qemu33").unwrap_err(); + assert_eq!(qemu.ctlpath, "/usr/bin/ls"); + assert_eq!(qemu.valid_ctlpaths.len(), 0); + assert!(qemu.jailer_path.is_empty()); + assert_eq!(qemu.valid_jailer_paths.len(), 0); + assert_eq!(qemu.disable_nesting_checks, true); + assert_eq!(qemu.enable_iothreads, true); + + assert_eq!(qemu.boot_info.image, "/usr/bin/echo"); + assert_eq!(qemu.boot_info.kernel, "/usr/bin/id"); + assert_eq!(qemu.boot_info.kernel_params, "ro"); + assert_eq!(qemu.boot_info.firmware, "/etc/hostname"); + + assert_eq!(qemu.cpu_info.cpu_features, "pmu=off,vmx=off"); + assert_eq!(qemu.cpu_info.default_vcpus, 2); + assert_eq!(qemu.cpu_info.default_maxvcpus, 64); +} diff --git a/src/libs/kata-types/tests/texture/configuration-qemu.toml b/src/libs/kata-types/tests/texture/configuration-qemu.toml new file mode 100644 index 0000000000..d30e0858a8 --- /dev/null +++ b/src/libs/kata-types/tests/texture/configuration-qemu.toml @@ -0,0 +1,78 @@ +[hypervisor.qemu] +path = "/usr/bin/ls" +valid_hypervisor_paths = ["/usr/bin/qemu*", "/opt/qemu?"] +ctlpath = "/usr/bin/ls" +disable_nesting_checks = true +enable_iothreads = true + +kernel = "/usr/bin/../bin/id" +image = "/usr/bin/./echo" +kernel_params = "ro" +firmware = "/etc/hostname" + +cpu_features="pmu=off,vmx=off" +default_vcpus = 2 +default_maxvcpus = 64 + +machine_type = "q35" +confidential_guest = true +rootless = true +enable_annotations = ["path", "ctlpath"] +machine_accelerators="noapic" +default_bridges = 2 +default_memory = 128 +memory_slots = 128 +memory_offset = 0x100000 +enable_virtio_mem = true +disable_block_device_use = false +shared_fs = "virtio-fs" +virtio_fs_daemon = "/usr/bin/id" +valid_virtio_fs_daemon_paths = ["/usr/local/bin/virtiofsd*"] +virtio_fs_cache_size = 512 +virtio_fs_extra_args = ["-o", "arg1=xxx,arg2", "-o", "hello world", "--arg3=yyy"] +virtio_fs_cache = "always" +block_device_driver = "virtio-blk" +block_device_cache_set = true +block_device_cache_direct = true +block_device_cache_noflush = true +enable_mem_prealloc = true +enable_hugepages = true +enable_vhost_user_store = true +vhost_user_store_path = "/tmp" +valid_vhost_user_store_paths = ["/var/kata/vhost-user-store*", "/tmp/kata?"] +enable_iommu = true +enable_iommu_platform = true +file_mem_backend = "/dev/shm" +valid_file_mem_backends = ["/dev/shm"] +enable_swap = true +pflashes = ["/proc/mounts"] +enable_debug = true +msize_9p = 16384 +disable_image_nvdimm = true +hotplug_vfio_on_root_bus = true +pcie_root_port = 2 +disable_vhost_net = true +entropy_source= "/dev/urandom" +valid_entropy_sources = ["/dev/urandom", "/dev/random"] +guest_hook_path = "/usr/share/oci/hooks" +rx_rate_limiter_max_rate = 10000 +tx_rate_limiter_max_rate = 10000 +guest_memory_dump_path="/var/crash/kata" +guest_memory_dump_paging = true +enable_guest_swap = true + +[runtime] +enable_debug = true +internetworking_model="macvtap" +disable_guest_seccomp=true +enable_tracing = true +jaeger_endpoint = "localhost:1234" +jaeger_user = "user" +jaeger_password = "pw" +disable_new_netns = true +sandbox_cgroup_only=true +sandbox_bind_mounts=["/proc/self"] +vfio_mode="vfio" +experimental=["a", "b"] +enable_pprof = true +