mirror of
https://github.com/kata-containers/kata-containers.git
synced 2026-07-01 14:38:33 +00:00
runtime-rs: Implement ShareVirtioFsNydus for standalone mode
Introduce `ShareVirtioFsNydus` to enable standalone Nydus rootfs support. This implementation acts as the bridge between runtime-rs and the external `nydusd` daemon. Key Capabilities: (1) Trait Implementation: Implements `ShareFs` (for VM device/storage) and `NydusShareFs` (for RAFS lifecycle) traits. (2) Daemon Lifecycle Management: Handles `nydusd` spawning, supervision, and graceful shutdown. (3) Native Overlay Support: Configures `nydusd` with `passthrough_fs` backend to provide native overlay (upperdir/workdir) support. (4) API Integration: Utilizes `NydusClient` for granular control over RAFS mount/umount operations. (5) QEMU Integration: Enables `virtio-fs-nydus` device support, facilitating standalone mode execution. This implementation allows Kata containers to utilize an external `nydusd` process for Nydus rootfs management, providing a cleaner separation between the runtime and the Nydus daemon lifecycle. Signed-off-by: Alex Lyn <alex.lyn@antgroup.com>
This commit is contained in:
committed by
Fabiano Fidêncio
parent
edfe9ea403
commit
fa84eecd2d
@@ -40,7 +40,7 @@ use crate::{
|
||||
network::{self, dan_config_path, Network, NetworkConfig, NetworkWithNetNsConfig},
|
||||
resource_persist::ResourceState,
|
||||
rootfs::{RootFsResource, Rootfs},
|
||||
share_fs::{self, sandbox_bind_mounts::SandboxBindMounts, ShareFs},
|
||||
share_fs::{self, sandbox_bind_mounts::SandboxBindMounts, NydusShareFs, ShareFs},
|
||||
volume::{Volume, VolumeResource},
|
||||
ResourceConfig, ResourceUpdateOp,
|
||||
};
|
||||
@@ -53,6 +53,7 @@ pub(crate) struct ResourceManagerInner {
|
||||
device_manager: Arc<RwLock<DeviceManager>>,
|
||||
network: Option<Arc<dyn Network>>,
|
||||
share_fs: Option<Arc<dyn ShareFs>>,
|
||||
nydus_share_fs: Option<Arc<dyn NydusShareFs>>,
|
||||
|
||||
pub rootfs_resource: RootFsResource,
|
||||
pub volume_resource: VolumeResource,
|
||||
@@ -124,6 +125,7 @@ impl ResourceManagerInner {
|
||||
device_manager,
|
||||
network: None,
|
||||
share_fs: None,
|
||||
nydus_share_fs: None,
|
||||
rootfs_resource: RootFsResource::new(),
|
||||
volume_resource: VolumeResource::new(),
|
||||
cgroups_resource,
|
||||
@@ -148,14 +150,15 @@ impl ResourceManagerInner {
|
||||
for dc in device_configs {
|
||||
match dc {
|
||||
ResourceConfig::ShareFs(c) => {
|
||||
self.share_fs = if self
|
||||
if self
|
||||
.hypervisor
|
||||
.capabilities()
|
||||
.await?
|
||||
.is_fs_sharing_supported()
|
||||
{
|
||||
let share_fs = share_fs::new(&self.sid, &c).context("new share fs")?;
|
||||
share_fs
|
||||
let instance = share_fs::new(&self.sid, &c).context("new share fs")?;
|
||||
instance
|
||||
.share_fs
|
||||
.setup_device_before_start_vm(
|
||||
self.hypervisor.as_ref(),
|
||||
&self.device_manager,
|
||||
@@ -168,9 +171,8 @@ impl ResourceManagerInner {
|
||||
.await
|
||||
.context("failed setup sandbox bindmounts")?;
|
||||
|
||||
Some(share_fs)
|
||||
} else {
|
||||
None
|
||||
self.share_fs = Some(instance.share_fs);
|
||||
self.nydus_share_fs = instance.nydus_share_fs;
|
||||
};
|
||||
}
|
||||
ResourceConfig::Network(c) => {
|
||||
@@ -468,6 +470,7 @@ impl ResourceManagerInner {
|
||||
self.rootfs_resource
|
||||
.handler_rootfs(
|
||||
&self.share_fs,
|
||||
&self.nydus_share_fs,
|
||||
self.device_manager.as_ref(),
|
||||
self.hypervisor.as_ref(),
|
||||
&self.sid,
|
||||
@@ -915,6 +918,14 @@ impl ResourceManagerInner {
|
||||
.await
|
||||
.context("failed to cleanup sandbox bindmounts")?;
|
||||
|
||||
// stop share fs daemon (e.g., virtiofsd, nydusd) before cleaning up mount
|
||||
if let Some(share_fs) = &self.share_fs {
|
||||
share_fs
|
||||
.stop()
|
||||
.await
|
||||
.context("failed to stop share fs daemon")?;
|
||||
}
|
||||
|
||||
// clean up share fs mount
|
||||
if let Some(share_fs) = &self.share_fs {
|
||||
share_fs
|
||||
@@ -1069,6 +1080,7 @@ impl Persist for ResourceManagerInner {
|
||||
device_manager,
|
||||
network: None,
|
||||
share_fs: None,
|
||||
nydus_share_fs: None,
|
||||
rootfs_resource: RootFsResource::new(),
|
||||
volume_resource: VolumeResource::new(),
|
||||
cgroups_resource: CgroupsResource::restore(
|
||||
|
||||
@@ -24,11 +24,12 @@ use self::{
|
||||
block_rootfs::is_block_rootfs, erofs_rootfs::ErofsMultiLayerRootfs,
|
||||
nydus_rootfs::NYDUS_ROOTFS_TYPE,
|
||||
};
|
||||
use crate::{rootfs::erofs_rootfs::is_erofs_multi_layer, share_fs::ShareFs};
|
||||
use crate::rootfs::erofs_rootfs::is_erofs_multi_layer;
|
||||
use crate::share_fs::{NydusShareFs, ShareFs};
|
||||
use oci_spec::runtime as oci;
|
||||
|
||||
const ROOTFS: &str = "rootfs";
|
||||
const HYBRID_ROOTFS_LOWER_DIR: &str = "rootfs_lower";
|
||||
pub const HYBRID_ROOTFS_LOWER_DIR: &str = "rootfs_lower";
|
||||
const TYPE_OVERLAY_FS: &str = "overlay";
|
||||
|
||||
#[async_trait]
|
||||
@@ -66,6 +67,7 @@ impl RootFsResource {
|
||||
pub async fn handler_rootfs(
|
||||
&self,
|
||||
share_fs: &Option<Arc<dyn ShareFs>>,
|
||||
nydus_share_fs: &Option<Arc<dyn NydusShareFs>>,
|
||||
device_manager: &RwLock<DeviceManager>,
|
||||
h: &dyn Hypervisor,
|
||||
sid: &str,
|
||||
@@ -136,12 +138,13 @@ impl RootFsResource {
|
||||
);
|
||||
Ok(block_rootfs)
|
||||
} else if let Some(share_fs) = share_fs {
|
||||
// handle nydus rootfs
|
||||
// handle nydus rootfs (unified implementation for both inline and standalone modes)
|
||||
let share_rootfs: Arc<dyn Rootfs> = if layer.fs_type == NYDUS_ROOTFS_TYPE {
|
||||
Arc::new(
|
||||
nydus_rootfs::NydusRootfs::new(
|
||||
device_manager,
|
||||
share_fs,
|
||||
nydus_share_fs,
|
||||
h,
|
||||
sid,
|
||||
cid,
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
// Copyright (c) 2019-2026 Alibaba Cloud
|
||||
// Copyright (c) 2019-2026 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
//! Nydus Rootfs Implementation
|
||||
//!
|
||||
//! This module provides a unified implementation for nydus rootfs that supports two modes:
|
||||
//! - **Inline mode**: Used with Dragonball VMM where nydus is built-in
|
||||
//! - **Standalone mode**: Used with QEMU/Cloud-Hypervisor where nydusd runs as a separate process
|
||||
//!
|
||||
//! The mode is determined by whether a `NydusShareFs` instance is provided:
|
||||
//! - `Some(nydus_fs)`: Standalone mode (external nydusd with guest kernel overlay)
|
||||
//! - `None`: Inline mode (built-in nydusd with guest kernel overlay)
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, path::Path, sync::Arc};
|
||||
|
||||
use super::{Rootfs, TYPE_OVERLAY_FS};
|
||||
use crate::rootfs::HYBRID_ROOTFS_LOWER_DIR;
|
||||
use crate::{
|
||||
rootfs::{HYBRID_ROOTFS_LOWER_DIR, ROOTFS},
|
||||
rootfs::ROOTFS,
|
||||
share_fs::{
|
||||
do_get_guest_path, do_get_guest_share_path, get_host_rw_shared_path, rafs_mount, ShareFs,
|
||||
ShareFsRootfsConfig, PASSTHROUGH_FS_DIR,
|
||||
do_get_guest_path, get_host_rw_shared_path, kata_guest_nydus_root_dir,
|
||||
kata_guest_share_dir, NydusShareFs, ShareFs, ShareFsRootfsConfig, PASSTHROUGH_FS_DIR,
|
||||
},
|
||||
};
|
||||
use agent::Storage;
|
||||
@@ -20,27 +33,45 @@ use hypervisor::{device::device_manager::DeviceManager, Hypervisor};
|
||||
use kata_types::mount::{Mount, NydusExtraOptions};
|
||||
use oci_spec::runtime as oci;
|
||||
use tokio::sync::RwLock;
|
||||
// Used for nydus rootfs
|
||||
|
||||
/// Used for nydus rootfs type detection
|
||||
pub(crate) const NYDUS_ROOTFS_TYPE: &str = "fuse.nydus-overlayfs";
|
||||
// Used for Nydus v5 rootfs version
|
||||
|
||||
/// Nydus v5 rootfs version
|
||||
const NYDUS_ROOTFS_V5: &str = "v5";
|
||||
// Used for Nydus v6 rootfs version
|
||||
/// Nydus v6 rootfs version
|
||||
const NYDUS_ROOTFS_V6: &str = "v6";
|
||||
|
||||
/// Snapshot directory name
|
||||
const SNAPSHOT_DIR: &str = "snapshotdir";
|
||||
/// Overlay device type for kata-agent
|
||||
const KATA_OVERLAY_DEV_TYPE: &str = "overlayfs";
|
||||
// nydus prefetch file list name
|
||||
/// Nydus prefetch file list name
|
||||
const NYDUS_PREFETCH_FILE_LIST: &str = "prefetch_file.list";
|
||||
/// The lower directory name used in the rafs mountpoint path within the nydusd namespace.
|
||||
const LOWER_DIR: &str = "lowerdir";
|
||||
/// The nydus image directory under the guest share root: /run/kata-containers/shared/rafs/.
|
||||
const NYDUS_RAFS_DIR: &str = "rafs";
|
||||
|
||||
/// Unified Nydus Rootfs implementation supporting both inline and standalone modes.
|
||||
pub(crate) struct NydusRootfs {
|
||||
guest_path: String,
|
||||
rootfs: Storage,
|
||||
rootfs: Option<Storage>,
|
||||
sid: String,
|
||||
cid: String,
|
||||
/// Nydus-specific share fs reference for standalone mode cleanup.
|
||||
/// None in inline mode.
|
||||
nydus_share_fs: Option<Arc<dyn NydusShareFs>>,
|
||||
/// It's used to track the rafs mount point for cleanup.
|
||||
/// None in inline mode.
|
||||
rafs_mountpoint: Option<String>,
|
||||
}
|
||||
|
||||
impl NydusRootfs {
|
||||
pub async fn new(
|
||||
d: &RwLock<DeviceManager>,
|
||||
device_manager: &RwLock<DeviceManager>,
|
||||
share_fs: &Arc<dyn ShareFs>,
|
||||
nydus_share_fs: &Option<Arc<dyn NydusShareFs>>,
|
||||
h: &dyn Hypervisor,
|
||||
sid: &str,
|
||||
cid: &str,
|
||||
@@ -49,94 +80,295 @@ impl NydusRootfs {
|
||||
let prefetch_list_path =
|
||||
get_nydus_prefetch_files(h.hypervisor_config().await.prefetch_list_path).await;
|
||||
|
||||
let share_fs_mount = share_fs.get_share_fs_mount();
|
||||
let extra_options =
|
||||
NydusExtraOptions::new(rootfs).context("failed to parse nydus extra options")?;
|
||||
info!(sl!(), "extra_option {:?}", &extra_options);
|
||||
info!(
|
||||
sl!(),
|
||||
"nydus rootfs extra_options: {:?}, is_standalone_nydus: {}",
|
||||
&extra_options,
|
||||
nydus_share_fs.is_some()
|
||||
);
|
||||
|
||||
let rafs_meta = &extra_options.source;
|
||||
let (rootfs_storage, rootfs_guest_path) = match extra_options.fs_version.as_str() {
|
||||
// both nydus v5 and v6 can be handled by the builtin nydus in dragonball by using the rafs mode.
|
||||
// nydus v6 could also be handled by the guest kernel as well, but some kernel patch is not support in the upstream community. We will add an option to let runtime-rs handle nydus v6 in the guest kernel optionally once the patch is ready
|
||||
// see this issue (https://github.com/kata-containers/kata-containers/issues/5143)
|
||||
NYDUS_ROOTFS_V5 | NYDUS_ROOTFS_V6 => {
|
||||
// rafs mount the metadata of nydus rootfs
|
||||
let rafs_mnt = do_get_guest_share_path(HYBRID_ROOTFS_LOWER_DIR, cid, true);
|
||||
rafs_mount(
|
||||
d,
|
||||
sid,
|
||||
rafs_meta.to_string(),
|
||||
rafs_mnt,
|
||||
extra_options.config.clone(),
|
||||
prefetch_list_path,
|
||||
)
|
||||
.await
|
||||
.context("failed to do rafs mount")?;
|
||||
// create rootfs under the share directory
|
||||
let container_share_dir = get_host_rw_shared_path(sid)
|
||||
.join(PASSTHROUGH_FS_DIR)
|
||||
.join(cid);
|
||||
let rootfs_dir = container_share_dir.join(ROOTFS);
|
||||
fs::create_dir_all(rootfs_dir).context("failed to create directory")?;
|
||||
// mount point inside the guest
|
||||
let rootfs_guest_path = do_get_guest_path(ROOTFS, cid, false, false);
|
||||
// bind mount the snapshot dir under the share directory
|
||||
share_fs_mount
|
||||
.share_rootfs(&ShareFsRootfsConfig {
|
||||
cid: cid.to_string(),
|
||||
source: extra_options.snapshot_dir.clone(),
|
||||
target: SNAPSHOT_DIR.to_string(),
|
||||
readonly: false,
|
||||
is_rafs: false,
|
||||
})
|
||||
.await
|
||||
.context("share nydus rootfs")?;
|
||||
let mut options: Vec<String> = Vec::new();
|
||||
options.push(
|
||||
"lowerdir=".to_string()
|
||||
+ &do_get_guest_path(HYBRID_ROOTFS_LOWER_DIR, cid, false, true),
|
||||
);
|
||||
options.push(
|
||||
"workdir=".to_string()
|
||||
+ &do_get_guest_path(
|
||||
format!("{}/{}", SNAPSHOT_DIR, "work").as_str(),
|
||||
let (rootfs_storage, rootfs_guest_path, rafs_mountpoint) =
|
||||
match extra_options.fs_version.as_str() {
|
||||
// both nydus v5 and v6 can be handled by the builtin nydus in dragonball by using the rafs mode.
|
||||
// nydus v6 could also be handled by the guest kernel as well, but some kernel patch is not support in the upstream community. We will add an option to let runtime-rs handle nydus v6 in the guest kernel optionally once the patch is ready
|
||||
// see this issue (https://github.com/kata-containers/kata-containers/issues/5143)
|
||||
NYDUS_ROOTFS_V5 | NYDUS_ROOTFS_V6 => {
|
||||
// Determine the mode based on whether NydusShareFs is available
|
||||
if let Some(nydus_fs) = nydus_share_fs {
|
||||
// Standalone mode: external nydusd with guest kernel overlay
|
||||
Self::create_standalone_rootfs(
|
||||
nydus_fs.as_ref(),
|
||||
sid,
|
||||
cid,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
);
|
||||
options.push(
|
||||
"upperdir=".to_string()
|
||||
+ &do_get_guest_path(
|
||||
format!("{}/{}", SNAPSHOT_DIR, "fs").as_str(),
|
||||
rafs_meta,
|
||||
&extra_options,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
// Inline mode: built-in nydusd with guest kernel overlay
|
||||
Self::create_inline_rootfs(
|
||||
device_manager,
|
||||
share_fs,
|
||||
sid,
|
||||
cid,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
);
|
||||
options.push("index=off".to_string());
|
||||
Ok((
|
||||
Storage {
|
||||
driver: KATA_OVERLAY_DEV_TYPE.to_string(),
|
||||
source: TYPE_OVERLAY_FS.to_string(),
|
||||
fs_type: TYPE_OVERLAY_FS.to_string(),
|
||||
options,
|
||||
mount_point: rootfs_guest_path.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
rootfs_guest_path,
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
let errstr: &str = "new_nydus_rootfs: invalid nydus rootfs type";
|
||||
error!(sl!(), "{}", errstr);
|
||||
Err(anyhow!(errstr))
|
||||
}
|
||||
}?;
|
||||
rafs_meta,
|
||||
&extra_options,
|
||||
prefetch_list_path,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let errstr = "invalid nydus rootfs version, expected v5 or v6";
|
||||
error!(sl!(), "{}", errstr);
|
||||
return Err(anyhow!(errstr));
|
||||
}
|
||||
};
|
||||
|
||||
info!(
|
||||
sl!(),
|
||||
"nydus rootfs created: guest_path={}, storage={:?}", rootfs_guest_path, rootfs_storage
|
||||
);
|
||||
|
||||
Ok(NydusRootfs {
|
||||
guest_path: rootfs_guest_path,
|
||||
rootfs: rootfs_storage,
|
||||
sid: sid.to_string(),
|
||||
cid: cid.to_string(),
|
||||
nydus_share_fs: nydus_share_fs.clone(),
|
||||
rafs_mountpoint,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create rootfs in standalone mode (external nydusd).
|
||||
///
|
||||
/// In this mode:
|
||||
/// - nydusd runs as a separate process
|
||||
/// - nydusd exposes the RAFS lowerdir
|
||||
/// - the writable overlay is assembled by kata-agent in the guest kernel
|
||||
/// - virtiofs is mounted at `/run/kata-containers/shared/`
|
||||
/// - passthrough_fs is mounted at `/containers` within nydusd namespace
|
||||
async fn create_standalone_rootfs(
|
||||
nydus_fs: &dyn crate::share_fs::NydusShareFs,
|
||||
sid: &str,
|
||||
cid: &str,
|
||||
rafs_meta: &str,
|
||||
extra_options: &NydusExtraOptions,
|
||||
) -> Result<(Option<Storage>, String, Option<String>)> {
|
||||
// Create rootfs directory on the host under the share directory.
|
||||
// Host/Guest Mapping in Standalone Mode:
|
||||
// - Host: get_host_rw_shared_path(sid)/<cid>/rootfs = .../rw/<cid>/rootfs
|
||||
// - Guest: /run/kata-containers/shared/containers/<cid>/rootfs
|
||||
let container_share_dir = get_host_rw_shared_path(sid).join(cid);
|
||||
let rootfs_dir = container_share_dir.join(ROOTFS);
|
||||
fs::create_dir_all(&rootfs_dir).context("failed to create rootfs directory")?;
|
||||
|
||||
// The guest mount point for the overlay rootfs: /run/kata-containers/shared/containers/<cid>/rootfs
|
||||
let rootfs_guest_path = Self::guest_shared_path(cid, ROOTFS);
|
||||
|
||||
// Bind mount the snapshot dir (allocated by the snapshotter on the host) to the shared directory
|
||||
// so it becomes visible in the guest.
|
||||
let snapshot_share_dir = container_share_dir.join(SNAPSHOT_DIR);
|
||||
kata_sys_util::mount::bind_mount_unchecked(
|
||||
&extra_options.snapshot_dir,
|
||||
&snapshot_share_dir,
|
||||
false,
|
||||
nix::mount::MsFlags::MS_SLAVE,
|
||||
)
|
||||
.context("failed to bind mount snapshot dir")?;
|
||||
|
||||
// Guest paths for overlay upper and work directories.
|
||||
let upper_dir_guest = Self::guest_shared_path(cid, &format!("{}/{}", SNAPSHOT_DIR, "fs"));
|
||||
let work_dir_guest = Self::guest_shared_path(cid, &format!("{}/{}", SNAPSHOT_DIR, "work"));
|
||||
|
||||
info!(
|
||||
sl!(),
|
||||
"mounting rafs (standalone mode): source={}, cid={}", rafs_meta, cid,
|
||||
);
|
||||
|
||||
// Go-runtime parity: mount plain RAFS via nydusd. The writable overlay
|
||||
// is assembled by kata-agent in the guest kernel using the Storage below.
|
||||
// If the rafs mount fails, undo the snapshot bind mount above so we don't
|
||||
// leak a mount that would block later cleanup/retries.
|
||||
let rafs_mnt = match nydus_fs
|
||||
.mount_rafs(cid, rafs_meta, &extra_options.config)
|
||||
.await
|
||||
{
|
||||
Ok(mnt) => mnt,
|
||||
Err(e) => {
|
||||
if let Err(umount_err) = nix::mount::umount(&snapshot_share_dir) {
|
||||
warn!(
|
||||
sl!(),
|
||||
"failed to umount snapshot dir {:?} after rafs mount failure: {}",
|
||||
snapshot_share_dir,
|
||||
umount_err
|
||||
);
|
||||
}
|
||||
return Err(e).context("failed to mount rafs in standalone mode");
|
||||
}
|
||||
};
|
||||
|
||||
let lowerdir_guest = Self::guest_nydus_image_path(cid);
|
||||
let options = vec![
|
||||
format!("upperdir={}", upper_dir_guest),
|
||||
format!("workdir={}", work_dir_guest),
|
||||
format!("lowerdir={}", lowerdir_guest),
|
||||
"index=off".to_string(),
|
||||
];
|
||||
|
||||
info!(
|
||||
sl!(),
|
||||
"nydus standalone overlay storage: mount_point={}, lowerdir={}, upperdir={}, workdir={}",
|
||||
rootfs_guest_path,
|
||||
lowerdir_guest,
|
||||
upper_dir_guest,
|
||||
work_dir_guest
|
||||
);
|
||||
|
||||
Ok((
|
||||
Storage {
|
||||
driver: KATA_OVERLAY_DEV_TYPE.to_string(),
|
||||
source: TYPE_OVERLAY_FS.to_string(),
|
||||
fs_type: TYPE_OVERLAY_FS.to_string(),
|
||||
options,
|
||||
mount_point: rootfs_guest_path.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
rootfs_guest_path,
|
||||
Some(rafs_mnt),
|
||||
))
|
||||
}
|
||||
|
||||
/// Create rootfs in inline mode (built-in nydusd).
|
||||
///
|
||||
/// In this mode:
|
||||
/// - nydus is built into Dragonball VMM
|
||||
/// - overlay is assembled by guest kernel
|
||||
/// - virtiofs is mounted at `/run/kata-containers/shared/containers/`
|
||||
/// - passthrough_fs uses PASSTHROUGH_FS_DIR subdirectory
|
||||
async fn create_inline_rootfs(
|
||||
device_manager: &RwLock<DeviceManager>,
|
||||
share_fs: &Arc<dyn ShareFs>,
|
||||
sid: &str,
|
||||
cid: &str,
|
||||
rafs_meta: &str,
|
||||
extra_options: &NydusExtraOptions,
|
||||
prefetch_list_path: Option<String>,
|
||||
) -> Result<(Option<Storage>, String, Option<String>)> {
|
||||
let share_fs_mount = share_fs.get_share_fs_mount();
|
||||
|
||||
// Mount rafs via DeviceManager (inline mode uses built-in nydusd).
|
||||
// This is different from standalone mode which uses nydusd API.
|
||||
let rafs_mnt = crate::share_fs::do_get_guest_share_path(HYBRID_ROOTFS_LOWER_DIR, cid, true);
|
||||
crate::share_fs::rafs_mount(
|
||||
device_manager,
|
||||
sid,
|
||||
rafs_meta.to_string(),
|
||||
rafs_mnt.clone(),
|
||||
extra_options.config.clone(),
|
||||
prefetch_list_path,
|
||||
)
|
||||
.await
|
||||
.context("failed to do rafs mount")?;
|
||||
|
||||
// Create rootfs directory on the host side.
|
||||
// In inline mode, we use PASSTHROUGH_FS_DIR subdirectory.
|
||||
let container_share_dir = get_host_rw_shared_path(sid)
|
||||
.join(PASSTHROUGH_FS_DIR)
|
||||
.join(cid);
|
||||
let rootfs_dir = container_share_dir.join(ROOTFS);
|
||||
fs::create_dir_all(rootfs_dir).context("failed to create directory")?;
|
||||
|
||||
// Guest mount point
|
||||
let rootfs_guest_path = do_get_guest_path(ROOTFS, cid, false, false);
|
||||
|
||||
// Bind mount the snapshot dir under the share directory
|
||||
share_fs_mount
|
||||
.share_rootfs(&ShareFsRootfsConfig {
|
||||
cid: cid.to_string(),
|
||||
source: extra_options.snapshot_dir.clone(),
|
||||
target: SNAPSHOT_DIR.to_string(),
|
||||
readonly: false,
|
||||
is_rafs: false,
|
||||
})
|
||||
.await
|
||||
.context("share nydus rootfs")?;
|
||||
|
||||
// Build overlay options for guest kernel overlay
|
||||
let options = vec![
|
||||
format!(
|
||||
"lowerdir={}",
|
||||
do_get_guest_path(HYBRID_ROOTFS_LOWER_DIR, cid, false, true)
|
||||
),
|
||||
format!(
|
||||
"workdir={}",
|
||||
do_get_guest_path(
|
||||
format!("{}/{}", SNAPSHOT_DIR, "work").as_str(),
|
||||
cid,
|
||||
false,
|
||||
false
|
||||
)
|
||||
),
|
||||
format!(
|
||||
"upperdir={}",
|
||||
do_get_guest_path(
|
||||
format!("{}/{}", SNAPSHOT_DIR, "fs").as_str(),
|
||||
cid,
|
||||
false,
|
||||
false
|
||||
)
|
||||
),
|
||||
"index=off".to_string(),
|
||||
];
|
||||
|
||||
info!(
|
||||
sl!(),
|
||||
"nydus inline overlay storage: mount_point={}, rafs_mnt={}",
|
||||
rootfs_guest_path,
|
||||
rafs_mnt
|
||||
);
|
||||
|
||||
Ok((
|
||||
Storage {
|
||||
driver: KATA_OVERLAY_DEV_TYPE.to_string(),
|
||||
source: TYPE_OVERLAY_FS.to_string(),
|
||||
fs_type: TYPE_OVERLAY_FS.to_string(),
|
||||
options,
|
||||
mount_point: rootfs_guest_path.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
rootfs_guest_path,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
/// Generate the nydus image guest path for lowerdir:
|
||||
/// `/run/kata-containers/shared/rafs/<cid>/lowerdir`
|
||||
fn guest_nydus_image_path(cid: &str) -> String {
|
||||
PathBuf::from(kata_guest_nydus_root_dir())
|
||||
.join(NYDUS_RAFS_DIR)
|
||||
.join(cid)
|
||||
.join(LOWER_DIR)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Generate the guest shared dir path for containers: `/run/kata-containers/shared/containers/<cid>/<suffix>`
|
||||
fn guest_shared_path(cid: &str, suffix: &str) -> String {
|
||||
let guest_shared_dir = kata_guest_share_dir();
|
||||
PathBuf::from(&guest_shared_dir)
|
||||
.join(cid)
|
||||
.join(suffix)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -150,7 +382,7 @@ impl Rootfs for NydusRootfs {
|
||||
}
|
||||
|
||||
async fn get_storage(&self) -> Option<Vec<Storage>> {
|
||||
Some(vec![self.rootfs.clone()])
|
||||
self.rootfs.clone().map(|rootfs| vec![rootfs])
|
||||
}
|
||||
|
||||
async fn get_device_id(&self) -> Result<Option<String>> {
|
||||
@@ -158,8 +390,27 @@ impl Rootfs for NydusRootfs {
|
||||
}
|
||||
|
||||
async fn cleanup(&self, _device_manager: &RwLock<DeviceManager>) -> Result<()> {
|
||||
// TODO: Clean up NydusRootfs after the container is killed
|
||||
warn!(sl!(), "Cleaning up NydusRootfs is still unimplemented.");
|
||||
if let (Some(nydus_fs), Some(rafs_mnt)) = (&self.nydus_share_fs, &self.rafs_mountpoint) {
|
||||
if let Err(e) = nydus_fs.umount_rafs(rafs_mnt).await {
|
||||
warn!(
|
||||
sl!(),
|
||||
"failed to umount rafs at {} with err {}", rafs_mnt, e
|
||||
);
|
||||
}
|
||||
|
||||
let sn_shared_path = get_host_rw_shared_path(&self.sid)
|
||||
.join(&self.cid)
|
||||
.join(SNAPSHOT_DIR);
|
||||
if sn_shared_path.exists() {
|
||||
if let Err(e) = nix::mount::umount(&sn_shared_path) {
|
||||
warn!(
|
||||
sl!(),
|
||||
"failed to umount snapshot mount at {:?} with err {}", sn_shared_path, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -205,6 +456,40 @@ mod tests {
|
||||
use std::{fs::File, path::PathBuf};
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_guest_shared_path() {
|
||||
// "/run/kata-containers/shared/containers/<cid>/<suffix>"
|
||||
let cid = "nydustester";
|
||||
let path = NydusRootfs::guest_shared_path(cid, "rootfs");
|
||||
assert_eq!(
|
||||
path,
|
||||
"/run/kata-containers/shared/containers/nydustester/rootfs"
|
||||
);
|
||||
|
||||
let upper_path = NydusRootfs::guest_shared_path(cid, &format!("{}/{}", SNAPSHOT_DIR, "fs"));
|
||||
assert_eq!(
|
||||
upper_path,
|
||||
"/run/kata-containers/shared/containers/nydustester/snapshotdir/fs"
|
||||
);
|
||||
|
||||
let work_path =
|
||||
NydusRootfs::guest_shared_path(cid, &format!("{}/{}", SNAPSHOT_DIR, "work"));
|
||||
assert_eq!(
|
||||
work_path,
|
||||
"/run/kata-containers/shared/containers/nydustester/snapshotdir/work"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guest_nydus_image_path() {
|
||||
let cid = "nydustester";
|
||||
let path = NydusRootfs::guest_nydus_image_path(cid);
|
||||
assert_eq!(
|
||||
path,
|
||||
"/run/kata-containers/shared/rafs/nydustester/lowerdir"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_nydus_prefetch_files() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
// Copyright (c) 2019-2026 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -13,6 +13,9 @@ mod share_virtio_fs_inline;
|
||||
use share_virtio_fs_inline::ShareVirtioFsInline;
|
||||
mod share_virtio_fs_standalone;
|
||||
use share_virtio_fs_standalone::ShareVirtioFsStandalone;
|
||||
mod share_virtio_fs_nydus;
|
||||
pub use nydus::nydus_client::NydusClient;
|
||||
pub use nydus::nydus_daemon::{Nydusd, NydusdConfig};
|
||||
mod utils;
|
||||
use tokio::sync::Mutex;
|
||||
pub use utils::{
|
||||
@@ -26,7 +29,7 @@ pub mod sandbox_bind_mounts;
|
||||
use std::{collections::HashMap, fmt::Debug, path::PathBuf, sync::Arc};
|
||||
|
||||
use agent::Storage;
|
||||
use anyhow::{anyhow, Context, Ok, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use kata_types::{build_path, config::hypervisor::SharedFsInfo};
|
||||
use oci_spec::runtime as oci;
|
||||
@@ -34,8 +37,10 @@ use tokio::sync::RwLock;
|
||||
|
||||
use hypervisor::{device::device_manager::DeviceManager, Hypervisor};
|
||||
|
||||
use crate::share_fs::share_virtio_fs_nydus::ShareVirtioFsNydus;
|
||||
|
||||
const VIRTIO_FS: &str = "virtio-fs";
|
||||
const _VIRTIO_FS_NYDUS: &str = "virtio-fs-nydus";
|
||||
pub const VIRTIO_FS_NYDUS: &str = "virtio-fs-nydus";
|
||||
const INLINE_VIRTIO_FS: &str = "inline-virtio-fs";
|
||||
|
||||
const DEFAULT_KATA_HOST_SHARED_DIR: &str = "/run/kata-containers/shared/sandboxes/";
|
||||
@@ -43,6 +48,10 @@ const DEFAULT_KATA_HOST_SHARED_DIR: &str = "/run/kata-containers/shared/sandboxe
|
||||
/// default share fs (for example virtio-fs) mount path in the guest
|
||||
const DEFAULT_KATA_GUEST_SHARE_DIR: &str = "/run/kata-containers/shared/containers/";
|
||||
|
||||
/// The virtiofs mount point in the guest for nydusd mode.
|
||||
/// In nydusd mode, virtiofs is mounted at `/run/kata-containers/shared/`
|
||||
const DEFAULT_KATA_GUEST_ROOT_DIR: &str = "/run/kata-containers/shared/";
|
||||
|
||||
pub const PASSTHROUGH_FS_DIR: &str = "passthrough";
|
||||
const RAFS_DIR: &str = "rafs";
|
||||
|
||||
@@ -54,6 +63,11 @@ pub fn kata_guest_share_dir() -> String {
|
||||
build_path(DEFAULT_KATA_GUEST_SHARE_DIR)
|
||||
}
|
||||
|
||||
/// The virtiofs mount point in the guest for nydusd mode.
|
||||
pub fn kata_guest_nydus_root_dir() -> String {
|
||||
build_path(DEFAULT_KATA_GUEST_ROOT_DIR)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ShareFs: Send + Sync {
|
||||
fn get_share_fs_mount(&self) -> Arc<dyn ShareFsMount>;
|
||||
@@ -83,15 +97,9 @@ pub trait ShareFs: Send + Sync {
|
||||
/// that are specific to the nydusd daemon's rafs mount capabilities.
|
||||
#[async_trait]
|
||||
pub trait NydusShareFs: Send + Sync {
|
||||
/// Mount rafs with nydusd native overlay support.
|
||||
/// Mount rafs through nydusd.
|
||||
/// Returns the mount point path within the nydusd namespace.
|
||||
async fn mount_rafs(
|
||||
&self,
|
||||
cid: &str,
|
||||
rafs_meta: &str,
|
||||
config: &str,
|
||||
overlay_config: &str,
|
||||
) -> Result<String>;
|
||||
async fn mount_rafs(&self, cid: &str, rafs_meta: &str, config: &str) -> Result<String>;
|
||||
|
||||
/// Umount rafs from nydusd.
|
||||
/// Called during container cleanup.
|
||||
@@ -185,16 +193,39 @@ pub trait ShareFsMount: Send + Sync {
|
||||
async fn cleanup(&self, sid: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
pub fn new(id: &str, config: &SharedFsInfo) -> Result<Arc<dyn ShareFs>> {
|
||||
/// Result of creating a new share fs instance.
|
||||
pub struct ShareFsInstance {
|
||||
/// The share fs trait object (always present).
|
||||
pub share_fs: Arc<dyn ShareFs>,
|
||||
/// The nydus-specific trait object (present only in standalone nydus mode).
|
||||
pub nydus_share_fs: Option<Arc<dyn NydusShareFs>>,
|
||||
}
|
||||
|
||||
pub fn new(id: &str, config: &SharedFsInfo) -> Result<ShareFsInstance> {
|
||||
let shared_fs = config.shared_fs.clone();
|
||||
let shared_fs = shared_fs.unwrap_or_default();
|
||||
match shared_fs.as_str() {
|
||||
INLINE_VIRTIO_FS => Ok(Arc::new(
|
||||
ShareVirtioFsInline::new(id, config).context("new inline virtio fs")?,
|
||||
)),
|
||||
VIRTIO_FS => Ok(Arc::new(
|
||||
ShareVirtioFsStandalone::new(id, config).context("new standalone virtio fs")?,
|
||||
)),
|
||||
INLINE_VIRTIO_FS => Ok(ShareFsInstance {
|
||||
share_fs: Arc::new(
|
||||
ShareVirtioFsInline::new(id, config).context("new inline virtiofs")?,
|
||||
),
|
||||
nydus_share_fs: None,
|
||||
}),
|
||||
VIRTIO_FS => Ok(ShareFsInstance {
|
||||
share_fs: Arc::new(
|
||||
ShareVirtioFsStandalone::new(id, config).context("new standalone virtiofs")?,
|
||||
),
|
||||
nydus_share_fs: None,
|
||||
}),
|
||||
VIRTIO_FS_NYDUS => {
|
||||
let nydus = Arc::new(
|
||||
ShareVirtioFsNydus::new(id, config).context("new standalone nydus virtiofs")?,
|
||||
);
|
||||
Ok(ShareFsInstance {
|
||||
share_fs: nydus.clone() as Arc<dyn ShareFs>,
|
||||
nydus_share_fs: Some(nydus as Arc<dyn NydusShareFs>),
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow!("unsupported shared fs {:?}", &shared_fs)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ pub struct MountRequest {
|
||||
pub fs_type: String,
|
||||
pub source: PathBuf,
|
||||
pub config: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub overlay: Option<String>,
|
||||
}
|
||||
|
||||
impl MountRequest {
|
||||
@@ -25,21 +23,6 @@ impl MountRequest {
|
||||
fs_type: fs_type.to_string(),
|
||||
source: source.to_path_buf(),
|
||||
config: config.to_string(),
|
||||
overlay: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_overlay(
|
||||
fs_type: &str,
|
||||
source: &Path,
|
||||
config: &str,
|
||||
overlay_config: &str,
|
||||
) -> Self {
|
||||
Self {
|
||||
fs_type: fs_type.to_string(),
|
||||
source: source.to_path_buf(),
|
||||
config: config.to_string(),
|
||||
overlay: Some(overlay_config.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ pub struct NydusdConfig {
|
||||
pub extra_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NydusdConfig {
|
||||
pub fn new(
|
||||
path: PathBuf,
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) 2026 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
|
||||
use agent::Storage;
|
||||
use hypervisor::{device::device_manager::DeviceManager, Hypervisor};
|
||||
use kata_types::config::hypervisor::SharedFsInfo;
|
||||
|
||||
use super::nydus::nydus_daemon::{Nydusd, NydusdConfig};
|
||||
use super::share_virtio_fs::{
|
||||
prepare_virtiofs, FS_TYPE_VIRTIO_FS, KATA_VIRTIO_FS_DEV_TYPE, MOUNT_GUEST_TAG,
|
||||
};
|
||||
use super::utils::get_host_rw_shared_path;
|
||||
use super::virtio_fs_share_mount::VirtiofsShareMount;
|
||||
use super::{kata_guest_nydus_root_dir, MountedInfo, NydusShareFs, ShareFs, ShareFsMount};
|
||||
|
||||
const NYDUSD_API_SOCK: &str = "nydusd-api.sock";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShareVirtioFsNydusConfig {
|
||||
id: String,
|
||||
pub virtio_fs_daemon: PathBuf,
|
||||
pub virtio_fs_extra_args: Vec<String>,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
pub struct ShareVirtioFsNydus {
|
||||
config: ShareVirtioFsNydusConfig,
|
||||
nydusd: Arc<RwLock<Option<Nydusd>>>,
|
||||
share_fs_mount: Arc<dyn ShareFsMount>,
|
||||
mounted_info_set: Arc<Mutex<HashMap<String, MountedInfo>>>,
|
||||
}
|
||||
|
||||
impl ShareVirtioFsNydus {
|
||||
pub fn new(id: &str, config: &SharedFsInfo) -> Result<Self> {
|
||||
Ok(Self {
|
||||
config: ShareVirtioFsNydusConfig {
|
||||
id: id.to_string(),
|
||||
virtio_fs_daemon: config.virtio_fs_daemon.clone().into(),
|
||||
virtio_fs_extra_args: config.virtio_fs_extra_args.clone(),
|
||||
debug: false,
|
||||
},
|
||||
nydusd: Arc::new(RwLock::new(None)),
|
||||
share_fs_mount: Arc::new(VirtiofsShareMount::new(id)),
|
||||
mounted_info_set: Arc::new(Mutex::new(HashMap::new())),
|
||||
})
|
||||
}
|
||||
|
||||
async fn setup_nydusd(&self, h: &dyn Hypervisor) -> Result<()> {
|
||||
let jailer_root = h.get_jailer_root().await?;
|
||||
let sock_path = Path::new(&jailer_root).join("virtiofsd.sock");
|
||||
let api_sock_path = Path::new(&jailer_root).join(NYDUSD_API_SOCK);
|
||||
|
||||
// new and validate nydusd config
|
||||
let nydusd_config = NydusdConfig::new(
|
||||
self.config.virtio_fs_daemon.clone(),
|
||||
sock_path,
|
||||
api_sock_path,
|
||||
get_host_rw_shared_path(&self.config.id),
|
||||
self.config.debug,
|
||||
self.config.virtio_fs_extra_args.clone(),
|
||||
)
|
||||
.validate()
|
||||
.context("validate nydusd config")?;
|
||||
|
||||
// start nydusd with the validated config
|
||||
let nydusd = Nydusd::new(nydusd_config);
|
||||
let pid = nydusd.start().await.context("failed to start nydusd")?;
|
||||
|
||||
info!(sl!(), "nydusd started with pid {}", pid);
|
||||
|
||||
{
|
||||
let mut n = self.nydusd.write().await;
|
||||
*n = Some(nydusd);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ShareFs for ShareVirtioFsNydus {
|
||||
fn get_share_fs_mount(&self) -> Arc<dyn ShareFsMount> {
|
||||
self.share_fs_mount.clone()
|
||||
}
|
||||
|
||||
async fn setup_device_before_start_vm(
|
||||
&self,
|
||||
h: &dyn Hypervisor,
|
||||
d: &RwLock<DeviceManager>,
|
||||
) -> Result<()> {
|
||||
let jailer_root = h.get_jailer_root().await?;
|
||||
|
||||
prepare_virtiofs(d, KATA_VIRTIO_FS_DEV_TYPE, &self.config.id, &jailer_root)
|
||||
.await
|
||||
.context("prepare virtiofs for nydus")?;
|
||||
|
||||
self.setup_nydusd(h).await.context("setup nydusd")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup_device_after_start_vm(
|
||||
&self,
|
||||
_h: &dyn Hypervisor,
|
||||
_d: &RwLock<DeviceManager>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_storages(&self) -> Result<Vec<Storage>> {
|
||||
let mut storages: Vec<Storage> = Vec::new();
|
||||
|
||||
// In nydusd mode, virtiofs is mounted at `/run/kata-containers/shared/`, because nydusd's
|
||||
// internal passthrough_fs is mounted at `/containers` within the virtiofs namespace, which
|
||||
// maps to `/run/kata-containers/shared/containers/` in the guest.
|
||||
let shared_volume = Storage {
|
||||
driver: String::from(KATA_VIRTIO_FS_DEV_TYPE),
|
||||
driver_options: Vec::new(),
|
||||
source: String::from(MOUNT_GUEST_TAG),
|
||||
fs_type: String::from(FS_TYPE_VIRTIO_FS),
|
||||
fs_group: None,
|
||||
options: vec![String::from("nodev")],
|
||||
mount_point: kata_guest_nydus_root_dir(),
|
||||
shared: false,
|
||||
};
|
||||
|
||||
storages.push(shared_volume);
|
||||
Ok(storages)
|
||||
}
|
||||
|
||||
fn mounted_info_set(&self) -> Arc<Mutex<HashMap<String, MountedInfo>>> {
|
||||
self.mounted_info_set.clone()
|
||||
}
|
||||
|
||||
async fn stop(&self) -> Result<()> {
|
||||
info!(sl!(), "stopping nydusd daemon");
|
||||
let nydusd = {
|
||||
let mut nydusd_guard = self.nydusd.write().await;
|
||||
nydusd_guard.take()
|
||||
};
|
||||
|
||||
if let Some(nydusd) = nydusd {
|
||||
nydusd.stop().await.context("failed to stop nydusd")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl NydusShareFs for ShareVirtioFsNydus {
|
||||
async fn mount_rafs(&self, cid: &str, rafs_meta: &str, config: &str) -> Result<String> {
|
||||
let mountpoint = format!("/rafs/{}/lowerdir", cid);
|
||||
let nydusd_guard = self.nydusd.read().await;
|
||||
let nydusd = nydusd_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("nydusd not initialized"))?;
|
||||
|
||||
nydusd
|
||||
.mount_rafs(&mountpoint, &PathBuf::from(rafs_meta), config)
|
||||
.await
|
||||
.context("failed to mount rafs via nydusd API")?;
|
||||
|
||||
Ok(mountpoint)
|
||||
}
|
||||
|
||||
async fn umount_rafs(&self, mountpoint: &str) -> Result<()> {
|
||||
let nydusd_guard = self.nydusd.read().await;
|
||||
let nydusd = nydusd_guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("nydusd not initialized"))?;
|
||||
nydusd
|
||||
.umount(mountpoint)
|
||||
.await
|
||||
.context("failed to umount rafs via nydusd API")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user