Merge pull request #6201 from jodh-intel/runtime-rs-add-cloud-hypervisor

runtime-rs: Add basic CH implementation
This commit is contained in:
Peng Tao 2023-02-16 11:23:04 +08:00 committed by GitHub
commit 139ad8e95f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2292 additions and 59 deletions

View File

@ -67,3 +67,17 @@ pub const DEFAULT_QEMU_PCI_BRIDGES: u32 = 2;
pub const MAX_QEMU_PCI_BRIDGES: u32 = 5;
pub const MAX_QEMU_VCPUS: u32 = 256;
pub const MIN_QEMU_MEMORY_SIZE_MB: u32 = 64;
// Default configuration for Cloud Hypervisor (CH)
pub const DEFAULT_CH_BINARY_PATH: &str = "/usr/bin/cloud-hypervisor";
pub const DEFAULT_CH_CONTROL_PATH: &str = "";
pub const DEFAULT_CH_ENTROPY_SOURCE: &str = "/dev/urandom";
pub const DEFAULT_CH_GUEST_KERNEL_IMAGE: &str = "vmlinuz";
pub const DEFAULT_CH_GUEST_KERNEL_PARAMS: &str = "";
pub const DEFAULT_CH_FIRMWARE_PATH: &str = "";
pub const DEFAULT_CH_MEMORY_SIZE_MB: u32 = 128;
pub const DEFAULT_CH_MEMORY_SLOTS: u32 = 128;
pub const DEFAULT_CH_PCI_BRIDGES: u32 = 2;
pub const MAX_CH_PCI_BRIDGES: u32 = 5;
pub const MAX_CH_VCPUS: u32 = 256;
pub const MIN_CH_MEMORY_SIZE_MB: u32 = 64;

View File

@ -0,0 +1,146 @@
// Copyright (c) 2019-2021 Alibaba Cloud
// Copyright (c) 2022-2023 Intel Corporation
//
// 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::default::MAX_CH_VCPUS;
use crate::config::default::MIN_CH_MEMORY_SIZE_MB;
use crate::config::hypervisor::VIRTIO_BLK_MMIO;
use crate::config::{ConfigPlugin, TomlConfig};
use crate::{eother, resolve_path, validate_path};
/// Hypervisor name for CH, used to index `TomlConfig::hypervisor`.
pub const HYPERVISOR_NAME_CH: &str = "cloud-hypervisor";
/// Configuration information for CH.
#[derive(Default, Debug)]
pub struct CloudHypervisorConfig {}
impl CloudHypervisorConfig {
/// Create a new instance of `CloudHypervisorConfig`.
pub fn new() -> Self {
CloudHypervisorConfig {}
}
/// Register the CH plugin.
pub fn register(self) {
let plugin = Arc::new(self);
register_hypervisor_plugin(HYPERVISOR_NAME_CH, plugin);
}
}
impl ConfigPlugin for CloudHypervisorConfig {
fn get_max_cpus(&self) -> u32 {
MAX_CH_VCPUS
}
fn get_min_memory(&self) -> u32 {
MIN_CH_MEMORY_SIZE_MB
}
fn name(&self) -> &str {
HYPERVISOR_NAME_CH
}
/// Adjust the configuration information after loading from configuration file.
fn adjust_config(&self, conf: &mut TomlConfig) -> Result<()> {
if let Some(ch) = conf.hypervisor.get_mut(HYPERVISOR_NAME_CH) {
if ch.path.is_empty() {
ch.path = default::DEFAULT_CH_BINARY_PATH.to_string();
}
resolve_path!(ch.path, "CH binary path `{}` is invalid: {}")?;
if ch.ctlpath.is_empty() {
ch.ctlpath = default::DEFAULT_CH_CONTROL_PATH.to_string();
}
resolve_path!(ch.ctlpath, "CH ctlpath `{}` is invalid: {}")?;
if ch.boot_info.kernel.is_empty() {
ch.boot_info.kernel = default::DEFAULT_CH_GUEST_KERNEL_IMAGE.to_string();
}
if ch.boot_info.kernel_params.is_empty() {
ch.boot_info.kernel_params = default::DEFAULT_CH_GUEST_KERNEL_PARAMS.to_string();
}
if ch.boot_info.firmware.is_empty() {
ch.boot_info.firmware = default::DEFAULT_CH_FIRMWARE_PATH.to_string();
}
if ch.device_info.default_bridges == 0 {
ch.device_info.default_bridges = default::DEFAULT_CH_PCI_BRIDGES;
}
if ch.machine_info.entropy_source.is_empty() {
ch.machine_info.entropy_source = default::DEFAULT_CH_ENTROPY_SOURCE.to_string();
}
if ch.memory_info.default_memory == 0 {
ch.memory_info.default_memory = default::DEFAULT_CH_MEMORY_SIZE_MB;
}
if ch.memory_info.memory_slots == 0 {
ch.memory_info.memory_slots = default::DEFAULT_CH_MEMORY_SLOTS;
}
}
Ok(())
}
/// Validate the configuration information.
fn validate(&self, conf: &TomlConfig) -> Result<()> {
if let Some(ch) = conf.hypervisor.get(HYPERVISOR_NAME_CH) {
validate_path!(ch.path, "CH binary path `{}` is invalid: {}")?;
validate_path!(ch.ctlpath, "CH control path `{}` is invalid: {}")?;
if !ch.jailer_path.is_empty() {
return Err(eother!("Path for CH jailer should be empty"));
}
if !ch.valid_jailer_paths.is_empty() {
return Err(eother!("Valid CH jailer path list should be empty"));
}
if !ch.blockdev_info.disable_block_device_use
&& ch.blockdev_info.block_device_driver == VIRTIO_BLK_MMIO
{
return Err(eother!("CH doesn't support virtio-blk-mmio"));
}
if ch.boot_info.kernel.is_empty() {
return Err(eother!("Guest kernel image for CH is empty"));
}
if ch.boot_info.image.is_empty() && ch.boot_info.initrd.is_empty() {
return Err(eother!("Both guest boot image and initrd for CH are empty"));
}
if (ch.cpu_info.default_vcpus > 0
&& ch.cpu_info.default_vcpus as u32 > default::MAX_CH_VCPUS)
|| ch.cpu_info.default_maxvcpus > default::MAX_CH_VCPUS
{
return Err(eother!(
"CH hypervisor cannot support {} vCPUs",
ch.cpu_info.default_maxvcpus
));
}
if ch.device_info.default_bridges > default::MAX_CH_PCI_BRIDGES {
return Err(eother!(
"CH hypervisor cannot support {} PCI bridges",
ch.device_info.default_bridges
));
}
if ch.memory_info.default_memory < MIN_CH_MEMORY_SIZE_MB {
return Err(eother!(
"CH hypervisor has minimal memory limitation {}",
MIN_CH_MEMORY_SIZE_MB
));
}
}
Ok(())
}
}

View File

@ -40,6 +40,9 @@ pub use self::dragonball::{DragonballConfig, HYPERVISOR_NAME_DRAGONBALL};
mod qemu;
pub use self::qemu::{QemuConfig, HYPERVISOR_NAME_QEMU};
mod ch;
pub use self::ch::{CloudHypervisorConfig, HYPERVISOR_NAME_CH};
const VIRTIO_BLK: &str = "virtio-blk";
const VIRTIO_BLK_MMIO: &str = "virtio-mmio";
const VIRTIO_BLK_CCW: &str = "virtio-blk-ccw";

View File

@ -25,8 +25,8 @@ pub mod hypervisor;
pub use self::agent::Agent;
use self::default::DEFAULT_AGENT_DBG_CONSOLE_PORT;
pub use self::hypervisor::{
BootInfo, DragonballConfig, Hypervisor, QemuConfig, HYPERVISOR_NAME_DRAGONBALL,
HYPERVISOR_NAME_QEMU,
BootInfo, CloudHypervisorConfig, DragonballConfig, Hypervisor, QemuConfig,
HYPERVISOR_NAME_DRAGONBALL, HYPERVISOR_NAME_QEMU,
};
mod runtime;

View File

@ -81,9 +81,17 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.57"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "api_client"
version = "0.1.0"
source = "git+https://github.com/cloud-hypervisor/cloud-hypervisor?tag=v27.0#2ba6a9bfcfd79629aecf77504fa554ab821d138e"
dependencies = [
"vmm-sys-util 0.10.0",
]
[[package]]
name = "arc-swap"
@ -412,6 +420,17 @@ dependencies = [
"thiserror",
]
[[package]]
name = "ch-config"
version = "0.1.0"
dependencies = [
"anyhow",
"api_client",
"serde",
"serde_json",
"tokio",
]
[[package]]
name = "chrono"
version = "0.4.22"
@ -934,9 +953,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
[[package]]
name = "futures"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
@ -949,9 +968,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
@ -959,15 +978,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
@ -976,9 +995,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-lite"
@ -997,9 +1016,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
@ -1008,15 +1027,15 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-timer"
@ -1026,9 +1045,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]]
name = "futures-util"
version = "0.3.21"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
@ -1114,7 +1133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df0ee4b237afb71e99f7e2fbd840ffec2d6c4bb569f69b2af18aa1f63077d38"
dependencies = [
"dashmap",
"futures 0.3.21",
"futures 0.3.26",
"futures-timer",
"no-std-compat",
"nonzero_ext",
@ -1236,8 +1255,10 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"ch-config",
"dbs-utils",
"dragonball",
"futures 0.3.26",
"go-flag",
"kata-sys-util",
"kata-types",
@ -1246,6 +1267,7 @@ dependencies = [
"nix 0.24.2",
"persist",
"rand 0.8.5",
"safe-path 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"seccompiler",
"serde",
"serde_json",
@ -1551,14 +1573,14 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
"windows-sys 0.42.0",
]
[[package]]
@ -1612,7 +1634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6"
dependencies = [
"bytes 1.1.0",
"futures 0.3.21",
"futures 0.3.26",
"log",
"netlink-packet-core",
"netlink-sys",
@ -1627,7 +1649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027"
dependencies = [
"bytes 1.1.0",
"futures 0.3.21",
"futures 0.3.26",
"libc",
"log",
"tokio",
@ -1783,7 +1805,7 @@ dependencies = [
"bitflags",
"blake3",
"fuse-backend-rs",
"futures 0.3.21",
"futures 0.3.26",
"lazy_static",
"libc",
"log",
@ -1811,7 +1833,7 @@ dependencies = [
"bitflags",
"dbs-uhttp",
"fuse-backend-rs",
"futures 0.3.21",
"futures 0.3.26",
"governor",
"lazy_static",
"libc",
@ -1931,7 +1953,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
"windows-sys 0.36.1",
]
[[package]]
@ -1955,7 +1977,7 @@ dependencies = [
"kata-sys-util",
"kata-types",
"libc",
"safe-path",
"safe-path 0.1.0",
"serde",
"serde_json",
"shim-interface",
@ -2025,9 +2047,9 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.39"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
@ -2321,7 +2343,7 @@ dependencies = [
"bitflags",
"byte-unit 4.0.17",
"cgroups-rs",
"futures 0.3.21",
"futures 0.3.26",
"hypervisor",
"kata-sys-util",
"kata-types",
@ -2361,7 +2383,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f1cfa18f8cebe685373a2697915d7e0db3b4554918bba118385e0f71f258a7"
dependencies = [
"futures 0.3.21",
"futures 0.3.26",
"log",
"netlink-packet-route",
"netlink-proto",
@ -2432,6 +2454,15 @@ dependencies = [
"libc",
]
[[package]]
name = "safe-path"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "980abdd3220aa19b67ca3ea07b173ca36383f18ae48cde696d90c8af39447ffb"
dependencies = [
"libc",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
@ -2455,18 +2486,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.143"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.143"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
@ -2475,9 +2506,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.83"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [
"itoa",
"ryu",
@ -2729,9 +2760,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.96"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
@ -2857,22 +2888,22 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.19.1"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95eec79ea28c00a365f539f1961e9278fbcaf81c0ff6aaf0e93c181352446948"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg",
"bytes 1.1.0",
"libc",
"memchr",
"mio",
"num_cpus",
"once_cell",
"parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"winapi",
"windows-sys 0.42.0",
]
[[package]]
@ -2907,7 +2938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e0723fc001950a3b018947b05eeb45014fd2b7c6e8f292502193ab74486bdb6"
dependencies = [
"bytes 0.4.12",
"futures 0.3.21",
"futures 0.3.26",
"libc",
"tokio",
"vsock",
@ -2971,7 +3002,7 @@ checksum = "2ecfff459a859c6ba6668ff72b34c2f1d94d9d58f7088414c2674ad0f31cc7d8"
dependencies = [
"async-trait",
"byteorder",
"futures 0.3.21",
"futures 0.3.26",
"libc",
"log",
"nix 0.23.1",
@ -3105,7 +3136,7 @@ dependencies = [
"awaitgroup",
"common",
"containerd-shim-protos",
"futures 0.3.21",
"futures 0.3.26",
"hypervisor",
"kata-sys-util",
"kata-types",
@ -3366,43 +3397,100 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
"windows_aarch64_msvc 0.36.1",
"windows_i686_gnu 0.36.1",
"windows_i686_msvc 0.36.1",
"windows_x86_64_gnu 0.36.1",
"windows_x86_64_msvc 0.36.1",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.1",
"windows_i686_gnu 0.42.1",
"windows_i686_msvc 0.42.1",
"windows_x86_64_gnu 0.42.1",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"

View File

@ -32,4 +32,14 @@ shim-interface = { path = "../../../libs/shim-interface" }
dragonball = { path = "../../../dragonball", features = ["atomic-guest-memory", "virtio-vsock", "hotplug", "virtio-blk", "virtio-net", "virtio-fs","dbs-upcall"] }
ch-config = { path = "ch-config", optional = true }
futures = "0.3.25"
safe-path = "0.1.0"
[features]
default = []
# Feature is not yet complete, so not enabled by default.
# See https://github.com/kata-containers/kata-containers/issues/6264.
cloud-hypervisor = ["ch-config"]

View File

@ -1,4 +1,26 @@
# Multi-vmm support for runtime-rs
## 0. Status
External hypervisor support is currently being developed.
See [the main tracking issue](https://github.com/kata-containers/kata-containers/issues/4634)
for further details.
### Cloud Hypervisor
A basic implementation currently exists for Cloud Hypervisor. However,
since it is not yet fully functional, the feature is disabled by
default. When the implementation matures, the feature will be enabled
by default.
> **Note:**
>
> To enable the feature, follow the instructions on https://github.com/kata-containers/kata-containers/pull/6201.
See the [Cloud Hypervisor tracking issue](https://github.com/kata-containers/kata-containers/issues/6263)
for further details.
Some key points for supporting multi-vmm in rust runtime.
## 1. Hypervisor Config

View File

@ -0,0 +1,22 @@
# Copyright (c) 2022-2023 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
[package]
name = "ch-config"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.68"
serde = { version = "1.0.145", features = ["rc", "derive"] }
serde_json = "1.0.91"
tokio = { version = "1.25.0", features = ["sync", "rt"] }
# Cloud Hypervisor public HTTP API functions
# Note that the version specified is not necessarily the version of CH
# being used. This version is used to pin the CH config structure
# which is relatively static.
api_client = { git = "https://github.com/cloud-hypervisor/cloud-hypervisor", crate = "api_client", tag = "v27.0" }

View File

@ -0,0 +1,274 @@
// Copyright (c) 2022-2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::net_util::MAC_ADDR_LEN;
use crate::{
ConsoleConfig, ConsoleOutputMode, CpuTopology, CpusConfig, DeviceConfig, FsConfig, MacAddr,
MemoryConfig, NetConfig, PayloadConfig, PmemConfig, RngConfig, VmConfig, VsockConfig,
};
use anyhow::{anyhow, Context, Result};
use api_client::simple_api_full_command_and_response;
use std::fmt::Display;
use std::net::Ipv4Addr;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use tokio::task;
pub async fn cloud_hypervisor_vmm_ping(mut socket: UnixStream) -> Result<Option<String>> {
task::spawn_blocking(move || -> Result<Option<String>> {
let response = simple_api_full_command_and_response(&mut socket, "GET", "vmm.ping", None)
.map_err(|e| anyhow!(e))?;
Ok(response)
})
.await?
}
pub async fn cloud_hypervisor_vmm_shutdown(mut socket: UnixStream) -> Result<Option<String>> {
task::spawn_blocking(move || -> Result<Option<String>> {
let response =
simple_api_full_command_and_response(&mut socket, "PUT", "vmm.shutdown", None)
.map_err(|e| anyhow!(e))?;
Ok(response)
})
.await?
}
pub async fn cloud_hypervisor_vm_create(
sandbox_path: String,
vsock_socket_path: String,
mut socket: UnixStream,
shared_fs_devices: Option<Vec<FsConfig>>,
pmem_devices: Option<Vec<PmemConfig>>,
) -> Result<Option<String>> {
let cfg = cloud_hypervisor_vm_create_cfg(
sandbox_path,
vsock_socket_path,
shared_fs_devices,
pmem_devices,
)
.await?;
let serialised = serde_json::to_string_pretty(&cfg)?;
task::spawn_blocking(move || -> Result<Option<String>> {
let data = Some(serialised.as_str());
let response = simple_api_full_command_and_response(&mut socket, "PUT", "vm.create", data)
.map_err(|e| anyhow!(e))?;
Ok(response)
})
.await?
}
pub async fn cloud_hypervisor_vm_start(mut socket: UnixStream) -> Result<Option<String>> {
task::spawn_blocking(move || -> Result<Option<String>> {
let response = simple_api_full_command_and_response(&mut socket, "PUT", "vm.boot", None)
.map_err(|e| anyhow!(e))?;
Ok(response)
})
.await?
}
#[allow(dead_code)]
pub async fn cloud_hypervisor_vm_stop(mut socket: UnixStream) -> Result<Option<String>> {
task::spawn_blocking(move || -> Result<Option<String>> {
let response =
simple_api_full_command_and_response(&mut socket, "PUT", "vm.shutdown", None)
.map_err(|e| anyhow!(e))?;
Ok(response)
})
.await?
}
#[allow(dead_code)]
pub async fn cloud_hypervisor_vm_device_add(mut socket: UnixStream) -> Result<Option<String>> {
let device_config = DeviceConfig::default();
task::spawn_blocking(move || -> Result<Option<String>> {
let response = simple_api_full_command_and_response(
&mut socket,
"PUT",
"vm.add-device",
Some(&serde_json::to_string(&device_config)?),
)
.map_err(|e| anyhow!(e))?;
Ok(response)
})
.await?
}
pub async fn cloud_hypervisor_vm_fs_add(
mut socket: UnixStream,
fs_config: FsConfig,
) -> Result<Option<String>> {
let result = task::spawn_blocking(move || -> Result<Option<String>> {
let response = simple_api_full_command_and_response(
&mut socket,
"PUT",
"vm.add-fs",
Some(&serde_json::to_string(&fs_config)?),
)
.map_err(|e| anyhow!(e))?;
Ok(response)
})
.await?;
result
}
pub async fn cloud_hypervisor_vm_create_cfg(
// FIXME:
_sandbox_path: String,
vsock_socket_path: String,
shared_fs_devices: Option<Vec<FsConfig>>,
pmem_devices: Option<Vec<PmemConfig>>,
) -> Result<VmConfig> {
let topology = CpuTopology {
threads_per_core: 1,
cores_per_die: 12,
dies_per_package: 1,
packages: 1,
};
let cpus = CpusConfig {
boot_vcpus: 1,
max_vcpus: 12,
max_phys_bits: 46,
topology: Some(topology),
..Default::default()
};
let rng = RngConfig {
src: PathBuf::from("/dev/urandom"),
..Default::default()
};
let kernel_args = vec![
"root=/dev/pmem0p1",
"rootflags=dax,data=ordered,errors=remount-ro",
"ro",
"rootfstype=ext4",
"panic=1",
"no_timer_check",
"noreplace-smp",
"console=ttyS0,115200n8",
"systemd.log_target=console",
"systemd.unit=kata-containers",
"systemd.mask=systemd-networkd.service",
"systemd.mask=systemd-networkd.socket",
"agent.log=debug",
];
let cmdline = kernel_args.join(" ");
let kernel = PathBuf::from("/opt/kata/share/kata-containers/vmlinux.container");
// Note that PmemConfig replaces the PayloadConfig.initrd.
let payload = PayloadConfig {
kernel: Some(kernel),
cmdline: Some(cmdline),
..Default::default()
};
let serial = ConsoleConfig {
mode: ConsoleOutputMode::Tty,
..Default::default()
};
let ip = Ipv4Addr::new(192, 168, 10, 10);
let mask = Ipv4Addr::new(255, 255, 255, 0);
let mac_str = "12:34:56:78:90:01";
let mac = parse_mac(mac_str)?;
let network = NetConfig {
ip,
mask,
mac,
..Default::default()
};
let memory = MemoryConfig {
size: (1024 * 1024 * 2048),
// Required
shared: true,
prefault: false,
hugepages: false,
mergeable: false,
// FIXME:
hotplug_size: Some(16475226112),
..Default::default()
};
let fs = shared_fs_devices;
let pmem = pmem_devices;
let vsock = VsockConfig {
cid: 3,
socket: PathBuf::from(vsock_socket_path),
..Default::default()
};
let cfg = VmConfig {
cpus,
memory,
fs,
serial,
pmem,
payload: Some(payload),
vsock: Some(vsock),
rng,
net: Some(vec![network]),
..Default::default()
};
Ok(cfg)
}
fn parse_mac<S>(s: &S) -> Result<MacAddr>
where
S: AsRef<str> + ?Sized + Display,
{
let v: Vec<&str> = s.as_ref().split(':').collect();
let mut bytes = [0u8; MAC_ADDR_LEN];
if v.len() != MAC_ADDR_LEN {
return Err(anyhow!(
"invalid MAC {} (length {}, expected {})",
s,
v.len(),
MAC_ADDR_LEN
));
}
for i in 0..MAC_ADDR_LEN {
if v[i].len() != 2 {
return Err(anyhow!(
"invalid MAC {} (segment {} length {}, expected {})",
s,
i,
v.len(),
2
));
}
bytes[i] =
u8::from_str_radix(v[i], 16).context(format!("failed to parse MAC address: {}", s))?;
}
Ok(MacAddr { bytes })
}

View File

@ -0,0 +1,481 @@
// Copyright (c) 2022-2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::net::Ipv4Addr;
use std::path::PathBuf;
pub mod ch_api;
pub mod net_util;
mod virtio_devices;
use crate::virtio_devices::RateLimiterConfig;
pub use net_util::MacAddr;
pub const MAX_NUM_PCI_SEGMENTS: u16 = 16;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct BalloonConfig {
pub size: u64,
/// Option to deflate the balloon in case the guest is out of memory.
#[serde(default)]
pub deflate_on_oom: bool,
/// Option to enable free page reporting from the guest.
#[serde(default)]
pub free_page_reporting: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct CmdlineConfig {
pub args: String,
}
impl CmdlineConfig {
fn is_empty(&self) -> bool {
self.args.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct ConsoleConfig {
//#[serde(default = "default_consoleconfig_file")]
pub file: Option<PathBuf>,
pub mode: ConsoleOutputMode,
#[serde(default)]
pub iommu: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub enum ConsoleOutputMode {
#[default]
Off,
Pty,
Tty,
File,
Null,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct CpuAffinity {
pub vcpu: u8,
pub host_cpus: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct CpusConfig {
pub boot_vcpus: u8,
pub max_vcpus: u8,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub topology: Option<CpuTopology>,
#[serde(default)]
pub kvm_hyperv: bool,
#[serde(skip_serializing_if = "u8_is_zero")]
pub max_phys_bits: u8,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub affinity: Option<Vec<CpuAffinity>>,
#[serde(default)]
pub features: CpuFeatures,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct CpuFeatures {
#[cfg(target_arch = "x86_64")]
#[serde(default)]
pub amx: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct CpuTopology {
pub threads_per_core: u8,
pub cores_per_die: u8,
pub dies_per_package: u8,
pub packages: u8,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct DeviceConfig {
pub path: PathBuf,
#[serde(default)]
pub iommu: bool,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub pci_segment: u16,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct DiskConfig {
pub path: Option<PathBuf>,
#[serde(default)]
pub readonly: bool,
#[serde(default)]
pub direct: bool,
#[serde(default)]
pub iommu: bool,
//#[serde(default = "default_diskconfig_num_queues")]
pub num_queues: usize,
//#[serde(default = "default_diskconfig_queue_size")]
pub queue_size: u16,
#[serde(default)]
pub vhost_user: bool,
pub vhost_socket: Option<String>,
#[serde(default)]
pub rate_limiter_config: Option<RateLimiterConfig>,
#[serde(default)]
pub id: Option<String>,
// For testing use only. Not exposed in API.
#[serde(default)]
pub disable_io_uring: bool,
#[serde(default)]
pub pci_segment: u16,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct FsConfig {
pub tag: String,
pub socket: PathBuf,
//#[serde(default = "default_fsconfig_num_queues")]
pub num_queues: usize,
//#[serde(default = "default_fsconfig_queue_size")]
pub queue_size: u16,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub pci_segment: u16,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub enum HotplugMethod {
#[default]
Acpi,
VirtioMem,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct InitramfsConfig {
pub path: PathBuf,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct KernelConfig {
pub path: PathBuf,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct MemoryConfig {
pub size: u64,
#[serde(default)]
pub mergeable: bool,
#[serde(default)]
pub hotplug_method: HotplugMethod,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub hotplug_size: Option<u64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub hotplugged_size: Option<u64>,
#[serde(default)]
pub shared: bool,
#[serde(default)]
pub hugepages: bool,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub hugepage_size: Option<u64>,
#[serde(default)]
pub prefault: bool,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub zones: Option<Vec<MemoryZoneConfig>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct MemoryZoneConfig {
pub id: String,
pub size: u64,
#[serde(default)]
pub file: Option<PathBuf>,
#[serde(default)]
pub shared: bool,
#[serde(default)]
pub hugepages: bool,
#[serde(default)]
pub hugepage_size: Option<u64>,
#[serde(default)]
pub host_numa_node: Option<u32>,
#[serde(default)]
pub hotplug_size: Option<u64>,
#[serde(default)]
pub hotplugged_size: Option<u64>,
#[serde(default)]
pub prefault: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct NetConfig {
//#[serde(default = "default_netconfig_tap")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tap: Option<String>,
//#[serde(default = "default_netconfig_ip")]
pub ip: Ipv4Addr,
//#[serde(default = "default_netconfig_mask")]
pub mask: Ipv4Addr,
//#[serde(default = "default_netconfig_mac")]
pub mac: MacAddr,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub host_mac: Option<MacAddr>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
#[serde(default)]
pub iommu: bool,
//#[serde(default = "default_netconfig_num_queues")]
#[serde(skip_serializing_if = "usize_is_zero")]
pub num_queues: usize,
//#[serde(default = "default_netconfig_queue_size")]
#[serde(skip_serializing_if = "u16_is_zero")]
pub queue_size: u16,
#[serde(default)]
pub vhost_user: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub vhost_socket: Option<String>,
#[serde(default)]
pub vhost_mode: VhostMode,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub fds: Option<Vec<i32>>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub rate_limiter_config: Option<RateLimiterConfig>,
#[serde(default)]
#[serde(skip_serializing_if = "u16_is_zero")]
pub pci_segment: u16,
}
impl Default for NetConfig {
fn default() -> Self {
NetConfig {
tap: None,
ip: Ipv4Addr::new(0, 0, 0, 0),
mask: Ipv4Addr::new(0, 0, 0, 0),
mac: MacAddr::default(),
host_mac: None,
mtu: None,
iommu: false,
num_queues: 0,
queue_size: 0,
vhost_user: false,
vhost_socket: None,
vhost_mode: VhostMode::default(),
id: None,
fds: None,
rate_limiter_config: None,
pci_segment: 0,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct NumaConfig {
#[serde(default)]
pub guest_numa_id: u32,
#[serde(default)]
pub cpus: Option<Vec<u8>>,
#[serde(default)]
pub distances: Option<Vec<NumaDistance>>,
#[serde(default)]
pub memory_zones: Option<Vec<String>>,
#[cfg(target_arch = "x86_64")]
#[serde(default)]
pub sgx_epc_sections: Option<Vec<String>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct NumaDistance {
#[serde(default)]
pub destination: u32,
#[serde(default)]
pub distance: u8,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct PayloadConfig {
#[serde(default)]
pub firmware: Option<PathBuf>,
#[serde(default)]
pub kernel: Option<PathBuf>,
#[serde(default)]
pub cmdline: Option<String>,
#[serde(default)]
pub initramfs: Option<PathBuf>,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct PlatformConfig {
//#[serde(default = "default_platformconfig_num_pci_segments")]
pub num_pci_segments: u16,
#[serde(default)]
pub iommu_segments: Option<Vec<u16>>,
#[serde(default)]
pub serial_number: Option<String>,
#[serde(default)]
pub uuid: Option<String>,
#[serde(default)]
pub oem_strings: Option<Vec<String>>,
#[cfg(feature = "tdx")]
#[serde(default)]
pub tdx: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct PmemConfig {
pub file: PathBuf,
#[serde(default)]
pub size: Option<u64>,
#[serde(default)]
pub iommu: bool,
#[serde(default)]
pub discard_writes: bool,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub pci_segment: u16,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct RngConfig {
pub src: PathBuf,
#[serde(default)]
pub iommu: bool,
}
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct SgxEpcConfig {
pub id: String,
#[serde(default)]
pub size: u64,
#[serde(default)]
pub prefault: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct UserDeviceConfig {
pub socket: PathBuf,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub pci_segment: u16,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct VdpaConfig {
pub path: PathBuf,
//#[serde(default = "default_vdpaconfig_num_queues")]
pub num_queues: usize,
#[serde(default)]
pub iommu: bool,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub pci_segment: u16,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub enum VhostMode {
#[default]
Client,
Server,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct VmConfig {
#[serde(default)]
pub cpus: CpusConfig,
#[serde(default)]
pub memory: MemoryConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub kernel: Option<KernelConfig>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub initramfs: Option<InitramfsConfig>,
#[serde(default)]
#[serde(skip_serializing_if = "CmdlineConfig::is_empty")]
pub cmdline: CmdlineConfig,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub payload: Option<PayloadConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disks: Option<Vec<DiskConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub net: Option<Vec<NetConfig>>,
#[serde(default)]
pub rng: RngConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub balloon: Option<BalloonConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fs: Option<Vec<FsConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pmem: Option<Vec<PmemConfig>>,
//#[serde(default = "ConsoleConfig::default_serial")]
pub serial: ConsoleConfig,
//#[serde(default = "ConsoleConfig::default_console")]
pub console: ConsoleConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub devices: Option<Vec<DeviceConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_devices: Option<Vec<UserDeviceConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vdpa: Option<Vec<VdpaConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vsock: Option<VsockConfig>,
#[serde(default)]
pub iommu: bool,
#[cfg(target_arch = "x86_64")]
#[serde(skip_serializing_if = "Option::is_none")]
pub sgx_epc: Option<Vec<SgxEpcConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub numa: Option<Vec<NumaConfig>>,
#[serde(default)]
pub watchdog: bool,
#[cfg(feature = "guest_debug")]
pub gdb: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub platform: Option<PlatformConfig>,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct VsockConfig {
pub cid: u64,
pub socket: PathBuf,
#[serde(default)]
pub iommu: bool,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub pci_segment: u16,
}
//--------------------------------------------------------------------
// For serde serialization
#[allow(clippy::trivially_copy_pass_by_ref)]
fn u8_is_zero(v: &u8) -> bool {
*v == 0
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn usize_is_zero(v: &usize) -> bool {
*v == 0
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn u16_is_zero(v: &u16) -> bool {
*v == 0
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2022-2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize, Serializer};
use std::fmt;
pub const MAC_ADDR_LEN: usize = 6;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Default)]
pub struct MacAddr {
pub bytes: [u8; MAC_ADDR_LEN],
}
// Note: Implements ToString automatically.
impl fmt::Display for MacAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let b = &self.bytes;
write!(
f,
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
b[0], b[1], b[2], b[3], b[4], b[5]
)
}
}
// Requried to remove the `bytes` member from the serialized JSON!
impl Serialize for MacAddr {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.to_string().serialize(serializer)
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2022-2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct TokenBucketConfig {
pub size: u64,
pub one_time_burst: Option<u64>,
pub refill_time: u64,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct RateLimiterConfig {
pub bandwidth: Option<TokenBucketConfig>,
pub ops: Option<TokenBucketConfig>,
}

View File

@ -0,0 +1,148 @@
// Copyright (c) 2019-2022 Alibaba Cloud
// Copyright (c) 2022 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use super::HypervisorState;
use crate::device::Device;
use crate::VmmState;
use anyhow::Result;
use async_trait::async_trait;
use kata_types::capabilities::{Capabilities, CapabilityBits};
use kata_types::config::hypervisor::Hypervisor as HypervisorConfig;
use kata_types::config::hypervisor::HYPERVISOR_NAME_CH;
use persist::sandbox_persist::Persist;
use std::os::unix::net::UnixStream;
use tokio::process::Child;
use tokio::sync::watch::{channel, Receiver, Sender};
use tokio::task::JoinHandle;
#[derive(Debug)]
pub struct CloudHypervisorInner {
pub(crate) state: VmmState,
pub(crate) id: String,
pub(crate) api_socket: Option<UnixStream>,
pub(crate) extra_args: Option<Vec<String>>,
pub(crate) config: Option<HypervisorConfig>,
pub(crate) process: Option<Child>,
pub(crate) pid: Option<u32>,
pub(crate) timeout_secs: i32,
pub(crate) netns: Option<String>,
// Sandbox-specific directory
pub(crate) vm_path: String,
// Hypervisor runtime directory
pub(crate) run_dir: String,
// Subdirectory of vm_path.
pub(crate) jailer_root: String,
/// List of devices that will be added to the VM once it boots
pub(crate) pending_devices: Option<Vec<Device>>,
pub(crate) _capabilities: Capabilities,
pub(crate) shutdown_tx: Option<Sender<bool>>,
pub(crate) shutdown_rx: Option<Receiver<bool>>,
pub(crate) tasks: Option<Vec<JoinHandle<Result<()>>>>,
}
unsafe impl Send for CloudHypervisorInner {}
unsafe impl Sync for CloudHypervisorInner {}
const CH_DEFAULT_TIMEOUT_SECS: u32 = 10;
impl CloudHypervisorInner {
pub fn new() -> Self {
let mut capabilities = Capabilities::new();
capabilities.set(
CapabilityBits::BlockDeviceSupport
| CapabilityBits::BlockDeviceHotplugSupport
| CapabilityBits::FsSharingSupport,
);
let (tx, rx) = channel(true);
Self {
api_socket: None,
extra_args: None,
process: None,
pid: None,
config: None,
state: VmmState::NotReady,
timeout_secs: CH_DEFAULT_TIMEOUT_SECS as i32,
id: String::default(),
jailer_root: String::default(),
vm_path: String::default(),
run_dir: String::default(),
netns: None,
pending_devices: None,
_capabilities: capabilities,
shutdown_tx: Some(tx),
shutdown_rx: Some(rx),
tasks: None,
}
}
pub fn set_hypervisor_config(&mut self, config: HypervisorConfig) {
self.config = Some(config);
}
pub fn hypervisor_config(&self) -> HypervisorConfig {
self.config.clone().unwrap_or_default()
}
}
impl Default for CloudHypervisorInner {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Persist for CloudHypervisorInner {
type State = HypervisorState;
type ConstructorArgs = ();
// Return a state object that will be saved by the caller.
async fn save(&self) -> Result<Self::State> {
Ok(HypervisorState {
hypervisor_type: HYPERVISOR_NAME_CH.to_string(),
id: self.id.clone(),
vm_path: self.vm_path.clone(),
jailed: false,
jailer_root: String::default(),
netns: None,
config: self.hypervisor_config(),
run_dir: self.run_dir.clone(),
cached_block_devices: Default::default(),
..Default::default()
})
}
// Set the hypervisor state to the specified state
async fn restore(
_hypervisor_args: Self::ConstructorArgs,
hypervisor_state: Self::State,
) -> Result<Self> {
let ch = Self {
config: Some(hypervisor_state.config),
state: VmmState::NotReady,
id: hypervisor_state.id,
vm_path: hypervisor_state.vm_path,
run_dir: hypervisor_state.run_dir,
..Default::default()
};
Ok(ch)
}
}

View File

@ -0,0 +1,235 @@
// Copyright (c) 2019-2022 Alibaba Cloud
// Copyright (c) 2019-2022 Ant Group
// Copyright (c) 2022 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use super::inner::CloudHypervisorInner;
use crate::device::{Device, ShareFsDeviceConfig};
use crate::HybridVsockConfig;
use crate::VmmState;
use anyhow::{anyhow, Context, Result};
use ch_config::ch_api::cloud_hypervisor_vm_fs_add;
use ch_config::{FsConfig, PmemConfig};
use safe_path::scoped_join;
use std::convert::TryFrom;
use std::path::PathBuf;
const VIRTIO_FS: &str = "virtio-fs";
impl CloudHypervisorInner {
pub(crate) async fn add_device(&mut self, device: Device) -> Result<()> {
if self.state != VmmState::VmRunning {
let mut devices: Vec<Device> = if let Some(devices) = self.pending_devices.take() {
devices
} else {
vec![]
};
devices.insert(0, device);
self.pending_devices = Some(devices);
return Ok(());
}
self.handle_add_device(device).await?;
Ok(())
}
async fn handle_add_device(&mut self, device: Device) -> Result<()> {
match device {
Device::ShareFsDevice(cfg) => self.handle_share_fs_device(cfg).await,
Device::HybridVsock(cfg) => self.handle_hvsock_device(&cfg).await,
_ => return Err(anyhow!("unhandled device: {:?}", device)),
}
}
/// Add the device that were requested to be added before the VMM was
/// started.
#[allow(dead_code)]
pub(crate) async fn handle_pending_devices_after_boot(&mut self) -> Result<()> {
if self.state != VmmState::VmRunning {
return Err(anyhow!(
"cannot handle pending devices with VMM state {:?}",
self.state
));
}
if let Some(mut devices) = self.pending_devices.take() {
while let Some(dev) = devices.pop() {
self.add_device(dev).await.context("add_device")?;
}
}
Ok(())
}
pub(crate) async fn remove_device(&mut self, _device: Device) -> Result<()> {
Ok(())
}
async fn handle_share_fs_device(&mut self, cfg: ShareFsDeviceConfig) -> Result<()> {
if cfg.fs_type != VIRTIO_FS {
return Err(anyhow!("cannot handle share fs type: {:?}", cfg.fs_type));
}
let socket = self
.api_socket
.as_ref()
.ok_or("missing socket")
.map_err(|e| anyhow!(e))?;
let num_queues: usize = if cfg.queue_num > 0 {
cfg.queue_num as usize
} else {
1
};
let queue_size: u16 = if cfg.queue_num > 0 {
u16::try_from(cfg.queue_size)?
} else {
1024
};
let socket_path = if cfg.sock_path.starts_with('/') {
PathBuf::from(cfg.sock_path)
} else {
scoped_join(&self.vm_path, cfg.sock_path)?
};
let fs_config = FsConfig {
tag: cfg.mount_tag,
socket: socket_path,
num_queues,
queue_size,
..Default::default()
};
let response = cloud_hypervisor_vm_fs_add(
socket.try_clone().context("failed to clone socket")?,
fs_config,
)
.await?;
if let Some(detail) = response {
debug!(sl!(), "fs add response: {:?}", detail);
}
Ok(())
}
async fn handle_hvsock_device(&mut self, _cfg: &HybridVsockConfig) -> Result<()> {
Ok(())
}
pub(crate) async fn get_shared_fs_devices(&mut self) -> Result<Option<Vec<FsConfig>>> {
let pending_root_devices = self.pending_devices.take();
let mut root_devices = Vec::<FsConfig>::new();
if let Some(devices) = pending_root_devices {
for dev in devices {
match dev {
Device::ShareFsDevice(dev) => {
let settings = ShareFsSettings::new(dev, self.vm_path.clone());
let fs_cfg = FsConfig::try_from(settings)?;
root_devices.push(fs_cfg);
}
_ => continue,
};
}
Ok(Some(root_devices))
} else {
Ok(None)
}
}
pub(crate) async fn get_boot_file(&mut self) -> Result<PathBuf> {
if let Some(ref config) = self.config {
let boot_info = &config.boot_info;
let file = if !boot_info.initrd.is_empty() {
boot_info.initrd.clone()
} else if !boot_info.image.is_empty() {
boot_info.image.clone()
} else {
return Err(anyhow!("missing boot file (no image or initrd)"));
};
Ok(PathBuf::from(file))
} else {
Err(anyhow!("no hypervisor config"))
}
}
pub(crate) async fn get_pmem_devices(&mut self) -> Result<Option<Vec<PmemConfig>>> {
let file = self.get_boot_file().await?;
let pmem_cfg = PmemConfig {
file,
size: None,
iommu: false,
discard_writes: true,
id: None,
pci_segment: 0,
};
let pmem_devices = vec![pmem_cfg];
Ok(Some(pmem_devices))
}
}
#[derive(Debug)]
pub struct ShareFsSettings {
cfg: ShareFsDeviceConfig,
vm_path: String,
}
impl ShareFsSettings {
pub fn new(cfg: ShareFsDeviceConfig, vm_path: String) -> Self {
ShareFsSettings { cfg, vm_path }
}
}
impl TryFrom<ShareFsSettings> for FsConfig {
type Error = anyhow::Error;
fn try_from(settings: ShareFsSettings) -> Result<Self, Self::Error> {
let cfg = settings.cfg;
let vm_path = settings.vm_path;
let num_queues: usize = if cfg.queue_num > 0 {
cfg.queue_num as usize
} else {
1
};
let queue_size: u16 = if cfg.queue_num > 0 {
u16::try_from(cfg.queue_size)?
} else {
1024
};
let socket_path = if cfg.sock_path.starts_with('/') {
PathBuf::from(cfg.sock_path)
} else {
PathBuf::from(vm_path).join(cfg.sock_path)
};
let fs_cfg = FsConfig {
tag: cfg.mount_tag,
socket: socket_path,
num_queues,
queue_size,
..Default::default()
};
Ok(fs_cfg)
}
}

View File

@ -0,0 +1,486 @@
// Copyright (c) 2019-2022 Alibaba Cloud
// Copyright (c) 2022 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use super::inner::CloudHypervisorInner;
use crate::ch::utils::get_api_socket_path;
use crate::ch::utils::{get_jailer_root, get_sandbox_path, get_vsock_path};
use crate::Device;
use crate::VsockConfig;
use crate::{VcpuThreadIds, VmmState};
use anyhow::{anyhow, Context, Result};
use ch_config::ch_api::{
cloud_hypervisor_vm_create, cloud_hypervisor_vm_start, cloud_hypervisor_vmm_ping,
cloud_hypervisor_vmm_shutdown,
};
use core::future::poll_fn;
use futures::executor::block_on;
use futures::future::join_all;
use kata_types::capabilities::{Capabilities, CapabilityBits};
use std::fs::create_dir_all;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::process::Stdio;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;
use tokio::process::{Child, Command};
use tokio::sync::watch::Receiver;
use tokio::task;
use tokio::task::JoinHandle;
use tokio::time::Duration;
const CH_NAME: &str = "cloud-hypervisor";
/// Number of milliseconds to wait before retrying a CH operation.
const CH_POLL_TIME_MS: u64 = 50;
impl CloudHypervisorInner {
async fn start_hypervisor(&mut self, timeout_secs: i32) -> Result<()> {
self.cloud_hypervisor_launch(timeout_secs)
.await
.context("launch failed")?;
self.cloud_hypervisor_setup_comms()
.await
.context("comms setup failed")?;
self.cloud_hypervisor_check_running()
.await
.context("hypervisor running check failed")?;
self.state = VmmState::VmmServerReady;
Ok(())
}
async fn boot_vm(&mut self) -> Result<()> {
let shared_fs_devices = self.get_shared_fs_devices().await?;
let pmem_devices = self.get_pmem_devices().await?;
let socket = self
.api_socket
.as_ref()
.ok_or("missing socket")
.map_err(|e| anyhow!(e))?;
let sandbox_path = get_sandbox_path(&self.id)?;
std::fs::create_dir_all(sandbox_path.clone()).context("failed to create sandbox path")?;
let vsock_socket_path = get_vsock_path(&self.id)?;
let response = cloud_hypervisor_vm_create(
sandbox_path,
vsock_socket_path,
socket.try_clone().context("failed to clone socket")?,
shared_fs_devices,
pmem_devices,
)
.await?;
if let Some(detail) = response {
debug!(sl!(), "vm boot response: {:?}", detail);
}
let response =
cloud_hypervisor_vm_start(socket.try_clone().context("failed to clone socket")?)
.await?;
if let Some(detail) = response {
debug!(sl!(), "vm start response: {:?}", detail);
}
self.state = VmmState::VmRunning;
Ok(())
}
async fn cloud_hypervisor_setup_comms(&mut self) -> Result<()> {
let api_socket_path = get_api_socket_path(&self.id)?;
// The hypervisor has just been spawned, but may not yet have created
// the API socket, so repeatedly try to connect for up to
// timeout_secs.
let join_handle: JoinHandle<Result<UnixStream>> =
task::spawn_blocking(move || -> Result<UnixStream> {
let api_socket: UnixStream;
loop {
let result = UnixStream::connect(api_socket_path.clone());
if let Ok(result) = result {
api_socket = result;
break;
}
std::thread::sleep(Duration::from_millis(CH_POLL_TIME_MS));
}
Ok(api_socket)
});
let timeout_msg = format!(
"API socket connect timed out after {} seconds",
self.timeout_secs
);
let result =
tokio::time::timeout(Duration::from_secs(self.timeout_secs as u64), join_handle)
.await
.context(timeout_msg)?;
let result = result?;
let api_socket = result?;
self.api_socket = Some(api_socket);
Ok(())
}
async fn cloud_hypervisor_check_running(&mut self) -> Result<()> {
let timeout_secs = self.timeout_secs;
let timeout_msg = format!(
"API socket connect timed out after {} seconds",
timeout_secs
);
let join_handle = self.cloud_hypervisor_ping_until_ready(CH_POLL_TIME_MS);
let result = tokio::time::timeout(Duration::new(timeout_secs as u64, 0), join_handle)
.await
.context(timeout_msg)?;
result
}
async fn cloud_hypervisor_ensure_not_launched(&self) -> Result<()> {
if let Some(child) = &self.process {
return Err(anyhow!(
"{} already running with PID {}",
CH_NAME,
child.id().unwrap_or(0)
));
}
Ok(())
}
async fn cloud_hypervisor_launch(&mut self, _timeout_secs: i32) -> Result<()> {
self.cloud_hypervisor_ensure_not_launched().await?;
let debug = false;
let disable_seccomp = true;
let api_socket_path = get_api_socket_path(&self.id)?;
let _ = std::fs::remove_file(api_socket_path.clone());
let binary_path = self
.config
.as_ref()
.ok_or("no hypervisor config for CH")
.map_err(|e| anyhow!(e))?
.path
.to_string();
let path = Path::new(&binary_path).canonicalize()?;
let mut cmd = Command::new(path);
cmd.current_dir("/");
cmd.stdin(Stdio::null());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
cmd.env("RUST_BACKTRACE", "full");
cmd.args(["--api-socket", &api_socket_path]);
if let Some(extra_args) = &self.extra_args {
cmd.args(extra_args);
}
if debug {
cmd.arg("-v");
}
if disable_seccomp {
cmd.args(["--seccomp", "false"]);
}
let child = cmd.spawn().context(format!("{} spawn failed", CH_NAME))?;
// Save process PID
self.pid = child.id();
let shutdown = self
.shutdown_rx
.as_ref()
.ok_or("no receiver channel")
.map_err(|e| anyhow!(e))?
.clone();
let ch_outputlogger_task = tokio::spawn(cloud_hypervisor_log_output(child, shutdown));
let tasks = vec![ch_outputlogger_task];
self.tasks = Some(tasks);
Ok(())
}
async fn cloud_hypervisor_shutdown(&mut self) -> Result<()> {
let socket = self
.api_socket
.as_ref()
.ok_or("missing socket")
.map_err(|e| anyhow!(e))?;
let response =
cloud_hypervisor_vmm_shutdown(socket.try_clone().context("shutdown failed")?).await?;
if let Some(detail) = response {
debug!(sl!(), "shutdown response: {:?}", detail);
}
// Trigger a controlled shutdown
self.shutdown_tx
.as_mut()
.ok_or("no shutdown channel")
.map_err(|e| anyhow!(e))?
.send(true)
.map_err(|e| anyhow!(e).context("failed to request shutdown"))?;
let tasks = self
.tasks
.take()
.ok_or("no tasks")
.map_err(|e| anyhow!(e))?;
let results = join_all(tasks).await;
let mut wait_errors: Vec<tokio::task::JoinError> = vec![];
for result in results {
if let Err(e) = result {
eprintln!("wait task error: {:#?}", e);
wait_errors.push(e);
}
}
if wait_errors.is_empty() {
Ok(())
} else {
Err(anyhow!("wait all tasks failed: {:#?}", wait_errors))
}
}
#[allow(dead_code)]
async fn cloud_hypervisor_wait(&mut self) -> Result<()> {
let mut child = self
.process
.take()
.ok_or(format!("{} not running", CH_NAME))
.map_err(|e| anyhow!(e))?;
let _pid = child
.id()
.ok_or(format!("{} missing PID", CH_NAME))
.map_err(|e| anyhow!(e))?;
// Note that this kills _and_ waits for the process!
child.kill().await?;
Ok(())
}
async fn cloud_hypervisor_ping_until_ready(&mut self, _poll_time_ms: u64) -> Result<()> {
let socket = self
.api_socket
.as_ref()
.ok_or("missing socket")
.map_err(|e| anyhow!(e))?;
loop {
let response =
cloud_hypervisor_vmm_ping(socket.try_clone().context("failed to clone socket")?)
.await
.context("ping failed");
if let Ok(response) = response {
if let Some(detail) = response {
debug!(sl!(), "ping response: {:?}", detail);
}
break;
}
tokio::time::sleep(Duration::from_millis(CH_POLL_TIME_MS)).await;
}
Ok(())
}
pub(crate) async fn prepare_vm(&mut self, id: &str, netns: Option<String>) -> Result<()> {
self.id = id.to_string();
self.state = VmmState::NotReady;
self.setup_environment().await?;
self.netns = netns;
let vsock_cfg = VsockConfig::new(self.id.clone()).await?;
let dev = Device::Vsock(vsock_cfg);
self.add_device(dev).await.context("add vsock device")?;
self.start_hypervisor(self.timeout_secs).await?;
Ok(())
}
async fn setup_environment(&mut self) -> Result<()> {
// run_dir and vm_path are the same (shared)
self.run_dir = get_sandbox_path(&self.id)?;
self.vm_path = self.run_dir.to_string();
create_dir_all(&self.run_dir)
.with_context(|| anyhow!("failed to create sandbox directory {}", self.run_dir))?;
if !self.jailer_root.is_empty() {
create_dir_all(self.jailer_root.as_str())
.map_err(|e| anyhow!("Failed to create dir {} err : {:?}", self.jailer_root, e))?;
}
Ok(())
}
pub(crate) async fn start_vm(&mut self, timeout_secs: i32) -> Result<()> {
self.setup_environment().await?;
self.timeout_secs = timeout_secs;
self.boot_vm().await?;
Ok(())
}
pub(crate) fn stop_vm(&mut self) -> Result<()> {
block_on(self.cloud_hypervisor_shutdown())?;
Ok(())
}
pub(crate) fn pause_vm(&self) -> Result<()> {
Ok(())
}
pub(crate) fn resume_vm(&self) -> Result<()> {
Ok(())
}
pub(crate) async fn save_vm(&self) -> Result<()> {
Ok(())
}
pub(crate) async fn get_agent_socket(&self) -> Result<String> {
const HYBRID_VSOCK_SCHEME: &str = "hvsock";
let vsock_path = get_vsock_path(&self.id)?;
let uri = format!("{}://{}", HYBRID_VSOCK_SCHEME, vsock_path);
Ok(uri)
}
pub(crate) async fn disconnect(&mut self) {
self.state = VmmState::NotReady;
}
pub(crate) async fn get_thread_ids(&self) -> Result<VcpuThreadIds> {
Ok(VcpuThreadIds::default())
}
pub(crate) async fn cleanup(&self) -> Result<()> {
Ok(())
}
pub(crate) async fn get_pids(&self) -> Result<Vec<u32>> {
Ok(Vec::<u32>::new())
}
pub(crate) async fn check(&self) -> Result<()> {
Ok(())
}
pub(crate) async fn get_jailer_root(&self) -> Result<String> {
let root_path = get_jailer_root(&self.id)?;
std::fs::create_dir_all(&root_path)?;
Ok(root_path)
}
pub(crate) async fn capabilities(&self) -> Result<Capabilities> {
let mut caps = Capabilities::default();
caps.set(CapabilityBits::FsSharingSupport);
Ok(caps)
}
}
// Log all output from the CH process until a shutdown signal is received.
// When that happens, stop logging and wait for the child process to finish
// before returning.
async fn cloud_hypervisor_log_output(mut child: Child, mut shutdown: Receiver<bool>) -> Result<()> {
let stdout = child
.stdout
.as_mut()
.ok_or("failed to get child stdout")
.map_err(|e| anyhow!(e))?;
let stdout_reader = BufReader::new(stdout);
let mut stdout_lines = stdout_reader.lines();
let stderr = child
.stderr
.as_mut()
.ok_or("failed to get child stderr")
.map_err(|e| anyhow!(e))?;
let stderr_reader = BufReader::new(stderr);
let mut stderr_lines = stderr_reader.lines();
loop {
tokio::select! {
_ = shutdown.changed() => {
info!(sl!(), "got shutdown request");
break;
},
stderr_line = poll_fn(|cx| Pin::new(&mut stderr_lines).poll_next_line(cx)) => {
if let Ok(line) = stderr_line {
let line = line.ok_or("missing stderr line").map_err(|e| anyhow!(e))?;
info!(sl!(), "{:?}", line; "stream" => "stderr");
}
},
stdout_line = poll_fn(|cx| Pin::new(&mut stdout_lines).poll_next_line(cx)) => {
if let Ok(line) = stdout_line {
let line = line.ok_or("missing stdout line").map_err(|e| anyhow!(e))?;
info!(sl!(), "{:?}", line; "stream" => "stdout");
}
},
};
}
// Note that this kills _and_ waits for the process!
child.kill().await?;
Ok(())
}

View File

@ -0,0 +1,163 @@
// Copyright (c) 2019-2022 Alibaba Cloud
// Copyright (c) 2022 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use super::HypervisorState;
use crate::{device::Device, Hypervisor, VcpuThreadIds};
use anyhow::{Context, Result};
use async_trait::async_trait;
use kata_types::capabilities::Capabilities;
use kata_types::config::hypervisor::Hypervisor as HypervisorConfig;
use persist::sandbox_persist::Persist;
use std::sync::Arc;
use tokio::sync::RwLock;
// Convenience macro to obtain the scope logger
#[macro_export]
macro_rules! sl {
() => {
slog_scope::logger().new(o!("subsystem" => "cloud-hypervisor"))
};
}
mod inner;
mod inner_device;
mod inner_hypervisor;
mod utils;
use inner::CloudHypervisorInner;
#[derive(Debug, Default, Clone)]
pub struct CloudHypervisor {
inner: Arc<RwLock<CloudHypervisorInner>>,
}
unsafe impl Send for CloudHypervisor {}
unsafe impl Sync for CloudHypervisor {}
impl CloudHypervisor {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(CloudHypervisorInner::new())),
}
}
pub async fn set_hypervisor_config(&mut self, config: HypervisorConfig) {
let mut inner = self.inner.write().await;
inner.set_hypervisor_config(config)
}
}
#[async_trait]
impl Hypervisor for CloudHypervisor {
async fn prepare_vm(&self, id: &str, netns: Option<String>) -> Result<()> {
let mut inner = self.inner.write().await;
inner.prepare_vm(id, netns).await
}
async fn start_vm(&self, timeout: i32) -> Result<()> {
let mut inner = self.inner.write().await;
inner.start_vm(timeout).await
}
async fn stop_vm(&self) -> Result<()> {
let mut inner = self.inner.write().await;
inner.stop_vm()
}
async fn pause_vm(&self) -> Result<()> {
let inner = self.inner.write().await;
inner.pause_vm()
}
async fn resume_vm(&self) -> Result<()> {
let inner = self.inner.write().await;
inner.resume_vm()
}
async fn save_vm(&self) -> Result<()> {
let inner = self.inner.write().await;
inner.save_vm().await
}
async fn add_device(&self, device: Device) -> Result<()> {
let mut inner = self.inner.write().await;
inner.add_device(device).await
}
async fn remove_device(&self, device: Device) -> Result<()> {
let mut inner = self.inner.write().await;
inner.remove_device(device).await
}
async fn get_agent_socket(&self) -> Result<String> {
let inner = self.inner.write().await;
inner.get_agent_socket().await
}
async fn disconnect(&self) {
let mut inner = self.inner.write().await;
inner.disconnect().await
}
async fn hypervisor_config(&self) -> HypervisorConfig {
let inner = self.inner.write().await;
inner.hypervisor_config()
}
async fn get_thread_ids(&self) -> Result<VcpuThreadIds> {
let inner = self.inner.read().await;
inner.get_thread_ids().await
}
async fn cleanup(&self) -> Result<()> {
let inner = self.inner.read().await;
inner.cleanup().await
}
async fn get_pids(&self) -> Result<Vec<u32>> {
let inner = self.inner.read().await;
inner.get_pids().await
}
async fn check(&self) -> Result<()> {
let inner = self.inner.read().await;
inner.check().await
}
async fn get_jailer_root(&self) -> Result<String> {
let inner = self.inner.read().await;
inner.get_jailer_root().await
}
async fn save_state(&self) -> Result<HypervisorState> {
self.save().await
}
async fn capabilities(&self) -> Result<Capabilities> {
let inner = self.inner.read().await;
inner.capabilities().await
}
}
#[async_trait]
impl Persist for CloudHypervisor {
type State = HypervisorState;
type ConstructorArgs = ();
async fn save(&self) -> Result<Self::State> {
let inner = self.inner.read().await;
inner.save().await.context("save CH hypervisor state")
}
async fn restore(
hypervisor_args: Self::ConstructorArgs,
hypervisor_state: Self::State,
) -> Result<Self> {
let inner = CloudHypervisorInner::restore(hypervisor_args, hypervisor_state).await?;
Ok(Self {
inner: Arc::new(RwLock::new(inner)),
})
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) 2022-2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
use shim_interface::KATA_PATH;
// The socket used to connect to CH. This is used for CH API communications.
const CH_API_SOCKET_NAME: &str = "ch-api.sock";
// The socket that allows runtime-rs to connect direct through to the Kata
// Containers agent running inside the CH hosted VM.
const CH_VM_SOCKET_NAME: &str = "ch-vm.sock";
const CH_JAILER_DIR: &str = "root";
// Return the path for a _hypothetical_ sandbox: the path does *not* exist
// yet, and for this reason safe-path cannot be used.
pub fn get_sandbox_path(id: &str) -> Result<String> {
let path = [KATA_PATH, id].join("/");
Ok(path)
}
// Return the path for a _hypothetical_ API socket path:
// the path does *not* exist yet, and for this reason safe-path cannot be
// used.
pub fn get_api_socket_path(id: &str) -> Result<String> {
let sandbox_path = get_sandbox_path(id)?;
let path = [&sandbox_path, CH_API_SOCKET_NAME].join("/");
Ok(path)
}
// Return the path for a _hypothetical_ sandbox specific VSOCK socket path:
// the path does *not* exist yet, and for this reason safe-path cannot be
// used.
pub fn get_vsock_path(id: &str) -> Result<String> {
let sandbox_path = get_sandbox_path(id)?;
let path = [&sandbox_path, CH_VM_SOCKET_NAME].join("/");
Ok(path)
}
pub fn get_jailer_root(id: &str) -> Result<String> {
let sandbox_path = get_sandbox_path(id)?;
let path = [&sandbox_path, CH_JAILER_DIR].join("/");
Ok(path)
}

View File

@ -8,7 +8,7 @@ use crate::HypervisorConfig;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Serialize, Deserialize, Default)]
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct HypervisorState {
// Type of hypervisor, E.g. dragonball/qemu/firecracker/acrn.
pub hypervisor_type: String,

View File

@ -19,11 +19,17 @@ pub use kernel_param::Param;
mod utils;
use std::collections::HashMap;
#[cfg(feature = "cloud-hypervisor")]
pub mod ch;
use anyhow::Result;
use async_trait::async_trait;
use hypervisor_persist::HypervisorState;
use kata_types::capabilities::Capabilities;
use kata_types::config::hypervisor::Hypervisor as HypervisorConfig;
pub use kata_types::config::hypervisor::HYPERVISOR_NAME_CH;
// Config which driver to use as vm root dev
const VM_ROOTFS_DRIVER_BLK: &str = "virtio-blk";
const VM_ROOTFS_DRIVER_PMEM: &str = "virtio-pmem";
@ -48,7 +54,7 @@ const SHMEM: &str = "shmem";
pub const HYPERVISOR_DRAGONBALL: &str = "dragonball";
pub const HYPERVISOR_QEMU: &str = "qemu";
#[derive(PartialEq)]
#[derive(PartialEq, Debug, Clone)]
pub(crate) enum VmmState {
NotReady,
VmmServerReady,
@ -56,7 +62,7 @@ pub(crate) enum VmmState {
}
// vcpu mapping from vcpu number to thread number
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct VcpuThreadIds {
pub vcpus: HashMap<u32, u32>,
}

View File

@ -35,3 +35,9 @@ oci = { path = "../../../../libs/oci" }
persist = { path = "../../persist"}
resource = { path = "../../resource" }
[features]
default = []
# Feature is not yet complete, so not enabled by default.
# See https://github.com/kata-containers/kata-containers/issues/6264.
cloud-hypervisor = []

View File

@ -25,6 +25,12 @@ use hypervisor::{qemu::Qemu, HYPERVISOR_QEMU};
use kata_types::config::{
hypervisor::register_hypervisor_plugin, DragonballConfig, QemuConfig, TomlConfig,
};
#[cfg(feature = "cloud-hypervisor")]
use hypervisor::ch::CloudHypervisor;
#[cfg(feature = "cloud-hypervisor")]
use kata_types::config::{hypervisor::HYPERVISOR_NAME_CH, CloudHypervisorConfig};
use resource::ResourceManager;
use sandbox::VIRTCONTAINER;
use tokio::sync::mpsc::Sender;
@ -39,8 +45,16 @@ impl RuntimeHandler for VirtContainer {
// register
let dragonball_config = Arc::new(DragonballConfig::new());
register_hypervisor_plugin("dragonball", dragonball_config);
let qemu_config = Arc::new(QemuConfig::new());
register_hypervisor_plugin("qemu", qemu_config);
#[cfg(feature = "cloud-hypervisor")]
{
let ch_config = Arc::new(CloudHypervisorConfig::new());
register_hypervisor_plugin(HYPERVISOR_NAME_CH, ch_config);
}
Ok(())
}
@ -118,6 +132,17 @@ async fn new_hypervisor(toml_config: &TomlConfig) -> Result<Arc<dyn Hypervisor>>
.await;
Ok(Arc::new(hypervisor))
}
#[cfg(feature = "cloud-hypervisor")]
HYPERVISOR_NAME_CH => {
let mut hypervisor = CloudHypervisor::new();
hypervisor
.set_hypervisor_config(hypervisor_config.clone())
.await;
Ok(Arc::new(hypervisor))
}
_ => Err(anyhow!("Unsupported hypervisor {}", &hypervisor_name)),
}
}