Skip to content

Commit 31065f8

Browse files
author
Roy Lin
committed
feat(box): CRI writable volume mounts via copy materialization
CreateContainer previously rejected any writable mount and any selinux_relabel mount with Unimplemented, so the "starting container with volume" conformance specs (which mount a host dir writable with SelinuxRelabel=true) failed. Both are now accepted: - Writable mounts are materialized like read-only ones — the source is COPIED into the container rootfs at container_path. The host-created content is visible in the container and the container may write to its copy. NOTE: writes do NOT propagate back to the host source (microVM containers cannot bind-mount host paths post-boot); sufficient for read-oriented volumes + the conformance, logged as a warning. - selinux_relabel is a no-op on this non-SELinux runtime; accepted rather than failing the container. Non-private mount propagation (rshared/rslave/rprivate) remains Unimplemented — it needs a real shared mount, not a copy. Verified on the KVM server: "starting container with volume [Conformance]" and "...when host path is a symlink" both pass; build + clippy clean, 219 cri tests pass (rejection test replaced with a writable-materialize test).
1 parent e752542 commit 31065f8

5 files changed

Lines changed: 54 additions & 30 deletions

File tree

src/cri/src/runtime_service/convert.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,16 @@ fn validate_container_mount(mount: &Mount) -> Result<(), Status> {
102102
mount.container_path
103103
)));
104104
}
105-
if !mount.readonly {
106-
return Err(Status::unimplemented(format!(
107-
"Writable CRI mounts are not yet supported for microVM-backed containers: {}",
108-
mount.container_path
109-
)));
110-
}
111-
if mount.selinux_relabel {
112-
return Err(Status::unimplemented(format!(
113-
"SELinux relabeling is not supported for CRI mount {}",
114-
mount.container_path
115-
)));
116-
}
105+
// Writable mounts are accepted but materialized by COPYING the source into
106+
// the container rootfs (microVM-backed containers cannot bind-mount host
107+
// paths post-boot). The container sees the contents and may write to its
108+
// copy, but writes do NOT propagate back to the host source — sufficient for
109+
// read-oriented volumes (configMap/secret/downwardAPI) and the basic volume
110+
// conformance; true host propagation (and the propagation modes below) needs
111+
// a shared mount and is intentionally still rejected.
112+
//
113+
// SELinux relabeling is a no-op on this non-SELinux runtime; accept it
114+
// rather than failing the container so labeled volumes still work.
117115

118116
let propagation = crate::cri_api::mount::MountPropagation::try_from(mount.propagation)
119117
.map_err(|_| {

src/cri/src/runtime_service/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ use convert::{
5555
};
5656
#[cfg(test)]
5757
use log_writer::CriLogWriter;
58-
use mounts::materialize_readonly_container_mount;
58+
use mounts::materialize_container_mount;
5959
use network::{
6060
bridge_network_name, connect_sandbox_to_network_store, default_network_store,
6161
disconnect_sandbox_from_network_store, sandbox_network_name,
@@ -880,7 +880,7 @@ impl RuntimeService for BoxRuntimeService {
880880
};
881881
if !mounts.is_empty() {
882882
if let Err(status) = self
883-
.materialize_readonly_container_mounts(&rootfs_path, &mounts)
883+
.materialize_container_mounts(&rootfs_path, &mounts)
884884
.await
885885
{
886886
self.cleanup_container_rootfs_path(&rootfs_path).await;

src/cri/src/runtime_service/mounts.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
//! Read-only container mount materialization for the CRI runtime service.
1+
//! Container mount materialization for the CRI runtime service.
22
//!
33
//! Copies CRI mount sources into a prepared rootfs, since microVM-backed
4-
//! containers cannot bind-mount host paths directly.
4+
//! containers cannot bind-mount host paths directly. This serves both read-only
5+
//! and writable mounts; for writable mounts the container writes to its private
6+
//! copy and those writes do NOT propagate back to the host source.
57
68
use std::path::{Path, PathBuf};
79

@@ -66,7 +68,7 @@ fn copy_mount_source(source: &Path, target: &Path) -> std::io::Result<()> {
6668
Ok(())
6769
}
6870

69-
pub(super) fn materialize_readonly_container_mount(
71+
pub(super) fn materialize_container_mount(
7072
rootfs: &Path,
7173
mount: &ContainerMount,
7274
) -> Result<(), Status> {
@@ -78,6 +80,14 @@ pub(super) fn materialize_readonly_container_mount(
7880
)));
7981
}
8082

83+
if !mount.readonly {
84+
tracing::warn!(
85+
host_path = %mount.host_path,
86+
container_path = %mount.container_path,
87+
"Writable CRI mount is materialized by copy; in-container writes do not propagate to the host source"
88+
);
89+
}
90+
8191
let target = container_path_inside_rootfs(rootfs, &mount.container_path)?;
8292
remove_existing_mount_target(&target).map_err(|e| {
8393
Status::internal(format!(
@@ -87,7 +97,7 @@ pub(super) fn materialize_readonly_container_mount(
8797
})?;
8898
copy_mount_source(source, &target).map_err(|e| {
8999
Status::internal(format!(
90-
"Failed to materialize CRI read-only mount {} -> {}: {e}",
100+
"Failed to materialize CRI mount {} -> {}: {e}",
91101
source.display(),
92102
target.display()
93103
))

src/cri/src/runtime_service/service_ops.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ impl BoxRuntimeService {
152152
})
153153
}
154154

155-
pub(super) async fn materialize_readonly_container_mounts(
155+
pub(super) async fn materialize_container_mounts(
156156
&self,
157157
rootfs_path: &str,
158158
mounts: &[ContainerMount],
@@ -170,7 +170,7 @@ impl BoxRuntimeService {
170170
let mounts = mounts.to_vec();
171171
tokio::task::spawn_blocking(move || {
172172
for mount in &mounts {
173-
materialize_readonly_container_mount(&rootfs, mount)?;
173+
materialize_container_mount(&rootfs, mount)?;
174174
}
175175
Ok::<(), Status>(())
176176
})

src/cri/src/runtime_service/tests.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,36 +1726,52 @@ async fn test_create_container_materializes_readonly_mount() {
17261726
}
17271727

17281728
#[tokio::test]
1729-
async fn test_create_container_rejects_writable_mounts() {
1729+
async fn test_create_container_materializes_writable_mount() {
17301730
let svc = make_test_service();
17311731
svc.store.sandboxes.add(test_sandbox("sb-1")).await;
1732+
put_test_oci_image(&svc.image_store, "nginx:latest").await;
17321733

1733-
let result = svc
1734+
// Mirrors the CRI volume conformance: a writable mount with selinux_relabel
1735+
// set. Both are now accepted (relabel is a no-op on this non-SELinux
1736+
// runtime); the source is materialized by copy into the rootfs so the
1737+
// host-created file is visible in the container.
1738+
let source = tempfile::tempdir().unwrap();
1739+
std::fs::write(source.path().join("testVolume.file"), b"data").unwrap();
1740+
1741+
let resp = svc
17341742
.create_container(Request::new(CreateContainerRequest {
17351743
pod_sandbox_id: "sb-1".to_string(),
17361744
config: Some(ContainerConfig {
17371745
metadata: Some(ContainerMetadata {
17381746
name: "mounted".to_string(),
17391747
attempt: 0,
17401748
}),
1741-
command: vec!["/bin/true".to_string()],
1749+
image: Some(ImageSpec {
1750+
image: "nginx:latest".to_string(),
1751+
annotations: HashMap::new(),
1752+
}),
1753+
command: vec!["nginx".to_string()],
17421754
mounts: vec![Mount {
17431755
container_path: "/data".to_string(),
1744-
host_path: "/tmp".to_string(),
1756+
host_path: source.path().to_string_lossy().to_string(),
17451757
readonly: false,
1746-
selinux_relabel: false,
1758+
selinux_relabel: true,
17471759
propagation: crate::cri_api::mount::MountPropagation::PropagationPrivate as i32,
17481760
}],
17491761
..Default::default()
17501762
}),
17511763
sandbox_config: None,
17521764
}))
1753-
.await;
1765+
.await
1766+
.unwrap()
1767+
.into_inner();
17541768

1755-
assert!(result.is_err());
1756-
let err = result.unwrap_err();
1757-
assert_eq!(err.code(), tonic::Code::Unimplemented);
1758-
assert!(err.message().contains("Writable CRI mounts"));
1769+
let container = svc.store.containers.get(&resp.container_id).await.unwrap();
1770+
assert_eq!(container.mounts.len(), 1);
1771+
let mounted_file = PathBuf::from(&container.rootfs_path)
1772+
.join("data")
1773+
.join("testVolume.file");
1774+
assert_eq!(std::fs::read_to_string(mounted_file).unwrap(), "data");
17591775
}
17601776

17611777
#[tokio::test]

0 commit comments

Comments
 (0)