diff --git a/src/agent/Cargo.lock b/src/agent/Cargo.lock index 9e933a1808..6b51553759 100644 --- a/src/agent/Cargo.lock +++ b/src/agent/Cargo.lock @@ -64,6 +64,20 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "getrandom", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -97,6 +111,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -712,6 +775,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + [[package]] name = "byteorder" version = "1.5.0" @@ -814,6 +883,30 @@ dependencies = [ "libc", ] +[[package]] +name = "cdi" +version = "0.1.0" +source = "git+https://github.com/cncf-tags/container-device-interface-rs?rev=fba5677a8e7cc962fc6e495fcec98d7d765e332a#fba5677a8e7cc962fc6e495fcec98d7d765e332a" +dependencies = [ + "anyhow", + "clap 4.5.13", + "const_format", + "jsonschema", + "lazy_static", + "libc", + "nix 0.24.3", + "notify", + "oci-spec", + "once_cell", + "path-clean", + "regex", + "semver", + "serde", + "serde_derive", + "serde_json", + "serde_yaml", +] + [[package]] name = "cesu8" version = "1.1.0" @@ -914,8 +1007,8 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags 1.3.2", - "clap_derive", - "clap_lex", + "clap_derive 3.2.25", + "clap_lex 0.2.4", "indexmap 1.9.3", "once_cell", "strsim 0.10.0", @@ -923,6 +1016,28 @@ dependencies = [ "textwrap", ] +[[package]] +name = "clap" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +dependencies = [ + "clap_builder", + "clap_derive 4.5.13", +] + +[[package]] +name = "clap_builder" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +dependencies = [ + "anstream", + "anstyle", + "clap_lex 0.7.2", + "strsim 0.11.1", +] + [[package]] name = "clap_derive" version = "3.2.25" @@ -936,6 +1051,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -945,6 +1072,12 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "cmac" version = "0.7.2" @@ -967,6 +1100,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "combine" version = "4.6.7" @@ -1741,6 +1880,17 @@ dependencies = [ "rand", ] +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1827,6 +1977,25 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fraction" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +dependencies = [ + "lazy_static", + "num", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "funty" version = "2.0.0" @@ -2052,7 +2221,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] @@ -2605,6 +2774,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "iso8601" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" +dependencies = [ + "nom", +] + [[package]] name = "itertools" version = "0.10.5" @@ -2709,6 +2893,36 @@ dependencies = [ "utf8-decode", ] +[[package]] +name = "jsonschema" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0afd06142c9bcb03f4a8787c77897a87b6be9c4918f1946c33caa714c27578" +dependencies = [ + "ahash 0.8.11", + "anyhow", + "base64 0.22.1", + "bytecount", + "clap 4.5.13", + "fancy-regex", + "fraction", + "getrandom", + "iso8601", + "itoa", + "memchr", + "num-cmp", + "once_cell", + "parking_lot 0.12.3", + "percent-encoding", + "regex", + "reqwest", + "serde", + "serde_json", + "time", + "url", + "uuid", +] + [[package]] name = "jwt" version = "0.16.0" @@ -2773,9 +2987,10 @@ dependencies = [ "async-std", "async-trait", "capctl", + "cdi", "cfg-if 1.0.0", "cgroups-rs", - "clap", + "clap 3.2.25", "const_format", "derivative", "futures", @@ -2933,6 +3148,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "krata-tokio-tar" version = "0.4.2" @@ -3284,6 +3519,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.2" @@ -3464,6 +3711,25 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.6.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -3514,6 +3780,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + [[package]] name = "num-complex" version = "0.4.6" @@ -3893,6 +4165,12 @@ dependencies = [ "slash-formatter", ] +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + [[package]] name = "path-dedot" version = "1.2.4" @@ -4664,6 +4942,7 @@ dependencies = [ "bytes 1.6.1", "cookie", "cookie_store", + "futures-channel", "futures-core", "futures-util", "http", @@ -5130,7 +5409,7 @@ dependencies = [ "aes", "aes-gcm", "anyhow", - "base64 0.22.1", + "base64 0.21.7", "block-padding", "blowfish", "buffered-reader", @@ -5917,7 +6196,7 @@ dependencies = [ "backtrace", "bytes 1.6.1", "libc", - "mio", + "mio 1.0.2", "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", @@ -6344,6 +6623,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.10.0" @@ -7019,6 +7304,26 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "zerofrom" version = "0.1.4" diff --git a/src/agent/Cargo.toml b/src/agent/Cargo.toml index a8ed5d081c..cfa6a53a64 100644 --- a/src/agent/Cargo.toml +++ b/src/agent/Cargo.toml @@ -84,6 +84,7 @@ regorus = { version = "0.1.4", default-features = false, features = [ "arc", "regex", ], optional = true } +cdi = { git = "https://github.com/cncf-tags/container-device-interface-rs", rev = "fba5677a8e7cc962fc6e495fcec98d7d765e332a" } [dev-dependencies] tempfile = "3.1.0" diff --git a/src/agent/src/device/mod.rs b/src/agent/src/device/mod.rs index 53e77d82c8..dc5c81d057 100644 --- a/src/agent/src/device/mod.rs +++ b/src/agent/src/device/mod.rs @@ -11,6 +11,9 @@ use self::vfio_device_handler::{VfioApDeviceHandler, VfioPciDeviceHandler}; use crate::pci; use crate::sandbox::Sandbox; use anyhow::{anyhow, Context, Result}; +use cdi::annotations::parse_annotations; +use cdi::cache::{new_cache, with_auto_refresh, CdiOption}; +use cdi::spec_dirs::with_spec_dirs; use kata_types::device::DeviceHandlerManager; use nix::sys::stat; use oci::{LinuxDeviceCgroup, Spec}; @@ -25,6 +28,8 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; use tokio::sync::Mutex; +use tokio::time; +use tokio::time::Duration; use tracing::instrument; pub mod block_device_handler; @@ -238,6 +243,69 @@ pub async fn add_devices( update_spec_devices(logger, spec, dev_updates) } +#[instrument] +pub async fn handle_cdi_devices( + logger: &Logger, + spec: &mut Spec, + spec_dir: &str, + cdi_timeout: u64, +) -> Result<()> { + if let Some(container_type) = spec + .annotations() + .as_ref() + .and_then(|a| a.get("io.katacontainers.pkg.oci.container_type")) + { + if container_type == "pod_sandbox" { + return Ok(()); + } + } + + let (_, devices) = parse_annotations(spec.annotations().as_ref().unwrap())?; + + if devices.is_empty() { + info!(logger, "no CDI annotations, no devices to inject"); + return Ok(()); + } + // Explicitly set the cache options to disable auto-refresh and + // to use the single spec dir "/var/run/cdi" for tests it can be overridden + let options: Vec = vec![with_auto_refresh(false), with_spec_dirs(&[spec_dir])]; + let cache: Arc> = new_cache(options); + + for _ in 0..=cdi_timeout { + let inject_result = { + // Lock cache within this scope, std::sync::Mutex has no Send + // and await will not work with time::sleep + let mut cache = cache.lock().unwrap(); + match cache.refresh() { + Ok(_) => {} + Err(e) => { + return Err(anyhow!("error refreshing cache: {:?}", e)); + } + } + cache.inject_devices(Some(spec), devices.clone()) + }; + + match inject_result { + Ok(_) => { + info!( + logger, + "all devices injected successfully, modified CDI container spec: {:?}", &spec + ); + return Ok(()); + } + Err(e) => { + info!(logger, "error injecting devices: {:?}", e); + println!("error injecting devices: {:?}", e); + } + } + time::sleep(Duration::from_millis(1000)).await; + } + Err(anyhow!( + "failed to inject devices after CDI timeout of {} seconds", + cdi_timeout + )) +} + #[instrument] async fn validate_device( logger: &Logger, @@ -1110,4 +1178,95 @@ mod tests { assert!(name.is_ok(), "{}", name.unwrap_err()); assert_eq!(name.unwrap(), devname); } + + #[tokio::test] + async fn test_handle_cdi_devices() { + let logger = slog::Logger::root(slog::Discard, o!()); + let mut spec = Spec::default(); + + let mut annotations = HashMap::new(); + // cdi.k8s.io/vendor1_devices: vendor1.com/device=foo + annotations.insert( + "cdi.k8s.io/vfio17".to_string(), + "kata.com/gpu=0".to_string(), + ); + spec.set_annotations(Some(annotations)); + + // create a file in /tmp/cdi with nvidia.json content + let cdi_dir = PathBuf::from("/tmp/cdi"); + let cdi_file = cdi_dir.join("kata.json"); + + let cdi_version = "0.6.0"; + let kind = "kata.com/gpu"; + let device_name = "0"; + let annotation_whatever = "false"; + let annotation_whenever = "true"; + let inner_env = "TEST_INNER_ENV=TEST_INNER_ENV_VALUE"; + let outer_env = "TEST_OUTER_ENV=TEST_OUTER_ENV_VALUE"; + let inner_device = "/dev/zero"; + let outer_device = "/dev/null"; + + let cdi_content = format!( + r#"{{ + "cdiVersion": "{cdi_version}", + "kind": "{kind}", + "devices": [ + {{ + "name": "{device_name}", + "annotations": {{ + "whatever": "{annotation_whatever}", + "whenever": "{annotation_whenever}" + }}, + "containerEdits": {{ + "env": [ + "{inner_env}" + ], + "deviceNodes": [ + {{ + "path": "{inner_device}" + }} + ] + }} + }} + ], + "containerEdits": {{ + "env": [ + "{outer_env}" + ], + "deviceNodes": [ + {{ + "path": "{outer_device}" + }} + ] + }} + }}"# + ); + + fs::create_dir_all(&cdi_dir).unwrap(); + fs::write(&cdi_file, cdi_content).unwrap(); + + let res = handle_cdi_devices(&logger, &mut spec, "/tmp/cdi", 0).await; + println!("modfied spec {:?}", spec); + assert!(res.is_ok(), "{}", res.err().unwrap()); + + let linux = spec.linux().as_ref().unwrap(); + let devices = linux + .resources() + .as_ref() + .unwrap() + .devices() + .as_ref() + .unwrap(); + assert_eq!(devices.len(), 2); + + let env = spec.process().as_ref().unwrap().env().as_ref().unwrap(); + + // find string TEST_OUTER_ENV in evn + let outer_env = env.iter().find(|e| e.starts_with("TEST_OUTER_ENV")); + assert!(outer_env.is_some(), "TEST_OUTER_ENV not found in env"); + + // find TEST_INNER_ENV in env + let inner_env = env.iter().find(|e| e.starts_with("TEST_INNER_ENV")); + assert!(inner_env.is_some(), "TEST_INNER_ENV not found in env"); + } } diff --git a/src/agent/src/rpc.rs b/src/agent/src/rpc.rs index 67fd95ebf5..81796d78b0 100644 --- a/src/agent/src/rpc.rs +++ b/src/agent/src/rpc.rs @@ -58,7 +58,7 @@ use rustjail::process::ProcessOperations; use crate::cdh; use crate::device::block_device_handler::get_virtio_blk_pci_device_name; use crate::device::network_device_handler::wait_for_net_interface; -use crate::device::{add_devices, update_env_pci}; +use crate::device::{add_devices, handle_cdi_devices, update_env_pci}; use crate::features::get_build_features; use crate::image::KATA_IMAGE_WORK_DIR; use crate::linux_abi::*; @@ -130,6 +130,8 @@ const ERR_NO_SANDBOX_PIDNS: &str = "Sandbox does not have sandbox_pidns"; // not available. const IPTABLES_RESTORE_WAIT_SEC: u64 = 5; +const CDI_TIMEOUT_LIMIT: u64 = 100; + // Convenience function to obtain the scope logger. fn sl() -> slog::Logger { slog_scope::logger() @@ -224,6 +226,15 @@ impl AgentService { // cannot predict everything from the caller. add_devices(&sl(), &req.devices, &mut oci, &self.sandbox).await?; + // In guest-kernel mode some devices need extra handling. Taking the + // GPU as an example the shim will inject CDI annotations that will + // be used by the kata-agent to do containerEdits according to the + // CDI spec coming from a registry that is created on the fly by UDEV + // or other entities for a specifc device. + // In Kata we only consider the directory "/var/run/cdi", "/etc" may be + // readonly + handle_cdi_devices(&sl(), &mut oci, "/var/run/cdi", CDI_TIMEOUT_LIMIT).await?; + cdh_handler(&mut oci).await?; // Both rootfs and volumes (invoked with --volume for instance) will