Skip to content

Commit f790c54

Browse files
TheRayquazaclaude
andcommitted
runtime-rs: parse block-mounts annotation for volumeDevice passthrough
Parse io.katacontainers.volume.block-mounts annotation and convert matching volumeDevices into agent Storage objects, enabling block device annotation mounts in the Rust shim, mirroring the Go runtime behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9785f2c commit f790c54

2 files changed

Lines changed: 132 additions & 3 deletions

File tree

  • src
    • libs/kata-types/src/annotations
    • runtime-rs/crates/runtimes/virt_container/src/container_manager

src/libs/kata-types/src/annotations/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ use crate::config::TomlConfig;
1717
use crate::initdata::add_hypervisor_initdata_overrides;
1818
use crate::sl;
1919

20-
use self::cri_containerd::{SANDBOX_CPU_PERIOD_KEY, SANDBOX_CPU_QUOTA_KEY, SANDBOX_MEM_KEY};
20+
use self::cri_containerd::{
21+
SANDBOX_CPU_PERIOD_KEY, SANDBOX_CPU_QUOTA_KEY, SANDBOX_CPU_SHARE_KEY, SANDBOX_MEM_KEY,
22+
};
2123

2224
/// CRI-containerd specific annotations.
2325
pub mod cri_containerd;
@@ -443,6 +445,14 @@ impl Annotation {
443445
value.unwrap_or(0)
444446
}
445447

448+
/// Get the annotation of cpu shares for sandbox
449+
pub fn get_sandbox_cpu_shares(&self) -> u64 {
450+
let value = self
451+
.get_value::<u64>(SANDBOX_CPU_SHARE_KEY)
452+
.unwrap_or(Some(0));
453+
value.unwrap_or(0)
454+
}
455+
446456
/// Get the annotation of memory for sandbox
447457
pub fn get_sandbox_mem(&self) -> i64 {
448458
let value = self.get_value::<i64>(SANDBOX_MEM_KEY).unwrap_or(Some(0));

src/runtime-rs/crates/runtimes/virt_container/src/container_manager/container.rs

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use std::collections::HashMap;
88
use std::sync::Arc;
99

10-
use agent::Agent;
10+
use agent::{Agent, Storage};
1111
use anyhow::{anyhow, Context, Result};
1212
use common::{
1313
error::Error,
@@ -26,7 +26,8 @@ use oci_spec::runtime as oci;
2626

2727
use oci::{LinuxResources, Process as OCIProcess};
2828
use resource::{
29-
cdi_devices::container_device::annotate_container_devices, ResourceManager, ResourceUpdateOp,
29+
cdi_devices::{container_device::annotate_container_devices, ContainerDevice},
30+
ResourceManager, ResourceUpdateOp,
3031
};
3132
use tokio::sync::RwLock;
3233

@@ -202,6 +203,15 @@ impl Container {
202203
.resource_manager
203204
.handler_devices(&config.container_id, linux)
204205
.await?;
206+
207+
// Handle annotation-based block device mounts.
208+
// Devices listed in the annotation are mounted as filesystems by the agent
209+
// rather than passed as raw block devices, so filter them out first.
210+
let (container_devices, annotation_storages) =
211+
handle_block_mount_annotation(&updated_annotations, container_devices, &mut spec)
212+
.context("handle block mount annotation")?;
213+
storages.extend(annotation_storages);
214+
205215
let devices_agent = annotate_container_devices(&mut spec, container_devices)
206216
.context("annotate container devices failed")?;
207217

@@ -643,6 +653,115 @@ impl Container {
643653
}
644654
}
645655

656+
const BLOCK_DEVICE_MOUNTS_ANNOTATION: &str = "io.katacontainers.volume.block-mounts";
657+
658+
#[derive(Debug, serde::Deserialize)]
659+
struct BlockMountConfig {
660+
mount: String,
661+
#[serde(default)]
662+
fstype: String,
663+
#[serde(default)]
664+
options: Vec<String>,
665+
}
666+
667+
// Parses the block mount annotation and processes any matching container devices.
668+
//
669+
// Devices listed in the annotation are meant to be mounted as filesystems by the agent
670+
// (not passed as raw block devices). This function:
671+
// 1. Splits container_devices into annotated and remaining sets.
672+
// 2. Creates a Storage object for each annotated device.
673+
// 3. Adds a bind mount in the OCI spec from the guest storage path to the destination.
674+
// 4. Removes matching devices from spec.linux.devices.
675+
//
676+
// Returns (remaining_devices, new_storages).
677+
fn handle_block_mount_annotation(
678+
annotations: &HashMap<String, String>,
679+
container_devices: Vec<ContainerDevice>,
680+
spec: &mut oci::Spec,
681+
) -> Result<(Vec<ContainerDevice>, Vec<Storage>)> {
682+
let raw = match annotations.get(BLOCK_DEVICE_MOUNTS_ANNOTATION) {
683+
Some(v) if !v.is_empty() => v.as_str(),
684+
_ => return Ok((container_devices, vec![])),
685+
};
686+
687+
let block_mounts: HashMap<String, BlockMountConfig> =
688+
serde_json::from_str(raw).context("failed to parse block mount annotation")?;
689+
690+
if block_mounts.is_empty() {
691+
return Ok((container_devices, vec![]));
692+
}
693+
694+
let mut storages = Vec::new();
695+
let mut mounted_paths: HashMap<String, bool> = HashMap::new();
696+
697+
let (annotated, remaining): (Vec<_>, Vec<_>) = container_devices
698+
.into_iter()
699+
.partition(|cd| block_mounts.contains_key(&cd.device.container_path));
700+
701+
for cd in annotated {
702+
let config = block_mounts
703+
.get(&cd.device.container_path)
704+
.expect("partition guarantees key exists");
705+
706+
let source = cd.device.id.clone();
707+
let driver = cd.device.field_type.clone();
708+
let fstype = if config.fstype.is_empty() {
709+
"ext4".to_string()
710+
} else {
711+
config.fstype.clone()
712+
};
713+
let options = if config.options.is_empty() {
714+
vec!["rw".to_string()]
715+
} else {
716+
config.options.clone()
717+
};
718+
719+
// Build a unique, valid path component from the PCI source string.
720+
let sanitized: String = source
721+
.chars()
722+
.map(|c| if c.is_alphanumeric() || c == '-' { c } else { '_' })
723+
.collect();
724+
let guest_mount_point = format!("/run/kata-containers/storage/{}", sanitized);
725+
726+
storages.push(Storage {
727+
driver,
728+
source,
729+
fs_type: fstype,
730+
options,
731+
mount_point: guest_mount_point.clone(),
732+
..Default::default()
733+
});
734+
735+
// Add bind mount: agent mounts the block device at guest_mount_point, then
736+
// the bind mount exposes it at the container destination path.
737+
let mut bind_mount = oci::Mount::default();
738+
bind_mount.set_destination(std::path::PathBuf::from(&config.mount));
739+
bind_mount.set_typ(Some("bind".to_string()));
740+
bind_mount.set_source(Some(std::path::PathBuf::from(&guest_mount_point)));
741+
bind_mount.set_options(Some(vec!["bind".to_string()]));
742+
743+
let mut mounts = spec.mounts().clone().unwrap_or_default();
744+
mounts.push(bind_mount);
745+
spec.set_mounts(Some(mounts));
746+
747+
mounted_paths.insert(cd.device.container_path, true);
748+
}
749+
750+
// Remove matched devices from spec.linux.devices so the agent doesn't
751+
// see them as character/block devices in the container namespace.
752+
if !mounted_paths.is_empty() {
753+
if let Some(linux) = spec.linux_mut() {
754+
if let Some(devices) = linux.devices_mut() {
755+
devices.retain(|d| {
756+
!mounted_paths.contains_key(&d.path().display().to_string())
757+
});
758+
}
759+
}
760+
}
761+
762+
Ok((remaining, storages))
763+
}
764+
646765
fn amend_spec(
647766
spec: &mut oci::Spec,
648767
disable_guest_seccomp: bool,

0 commit comments

Comments
 (0)