mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-28 19:54:35 +00:00
runtime-rs: support rootfs volume for resource
Fixes: #3785 Signed-off-by: Quanwei Zhou <quanweiZhou@linux.alibaba.com>
This commit is contained in:
parent
234d7bca04
commit
3ff0db05a7
75
src/runtime-rs/Cargo.lock
generated
75
src/runtime-rs/Cargo.lock
generated
@ -293,7 +293,7 @@ checksum = "ec3245a0ca564e7f3c797d20d833a6870f57a728ac967d5225b3ffdef4465011"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -321,6 +321,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.1.31"
|
||||
@ -1066,6 +1072,29 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand 0.4.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
dependencies = [
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.3.1",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@ -1074,7 +1103,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1084,9 +1113,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
@ -1096,6 +1140,15 @@ dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.11"
|
||||
@ -1135,6 +1188,7 @@ dependencies = [
|
||||
name = "resource"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"cgroups-rs",
|
||||
@ -1148,7 +1202,9 @@ dependencies = [
|
||||
"nix 0.16.1",
|
||||
"oci",
|
||||
"slog",
|
||||
"slog-scope",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1306,7 +1362,7 @@ dependencies = [
|
||||
"nix 0.23.1",
|
||||
"oci",
|
||||
"protobuf",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"serial_test",
|
||||
"service",
|
||||
"sha2",
|
||||
@ -1472,7 +1528,7 @@ dependencies = [
|
||||
name = "tests_utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1692,6 +1748,15 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cfec50b0842181ba6e713151b72f4ec84a6a7e2c9c8a8a3ffc37bb1cd16b231"
|
||||
dependencies = [
|
||||
"rand 0.3.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
@ -13,8 +13,11 @@ libc = ">=0.2.39"
|
||||
log = "^0.4.0"
|
||||
nix = "0.16.0"
|
||||
slog = "2.5.2"
|
||||
tokio = { version = "1.8.0", features = ["sync"] }
|
||||
slog-scope = "4.4.0"
|
||||
tokio = { version = "1.8.0", features = ["process"] }
|
||||
uuid = { version = "0.4", features = ["v4"] }
|
||||
|
||||
agent = { path = "../agent" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
kata-types = { path = "../../../libs/kata-types" }
|
||||
kata-sys-util = { path = "../../../libs/kata-sys-util" }
|
||||
|
@ -4,4 +4,25 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[macro_use]
|
||||
extern crate slog;
|
||||
|
||||
logging::logger_with_subsystem!(sl, "resource");
|
||||
|
||||
pub mod cgroups;
|
||||
pub mod manager;
|
||||
mod manager_inner;
|
||||
pub mod rootfs;
|
||||
pub mod share_fs;
|
||||
pub mod volume;
|
||||
pub use manager::ResourceManager;
|
||||
|
||||
use kata_types::config::hypervisor::SharedFsInfo;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ResourceConfig {
|
||||
ShareFs(SharedFsInfo),
|
||||
}
|
||||
|
92
src/runtime-rs/crates/resource/src/manager.rs
Normal file
92
src/runtime-rs/crates/resource/src/manager.rs
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::{Agent, Storage};
|
||||
use anyhow::Result;
|
||||
use hypervisor::Hypervisor;
|
||||
use kata_types::config::TomlConfig;
|
||||
use kata_types::mount::Mount;
|
||||
use oci::LinuxResources;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{manager_inner::ResourceManagerInner, rootfs::Rootfs, volume::Volume, ResourceConfig};
|
||||
|
||||
pub struct ResourceManager {
|
||||
inner: Arc<RwLock<ResourceManagerInner>>,
|
||||
}
|
||||
|
||||
impl ResourceManager {
|
||||
pub fn new(
|
||||
sid: &str,
|
||||
agent: Arc<dyn Agent>,
|
||||
hypervisor: Arc<dyn Hypervisor>,
|
||||
toml_config: &TomlConfig,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
inner: Arc::new(RwLock::new(ResourceManagerInner::new(
|
||||
sid,
|
||||
agent,
|
||||
hypervisor,
|
||||
toml_config,
|
||||
)?)),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn prepare_before_start_vm(&self, device_configs: Vec<ResourceConfig>) -> Result<()> {
|
||||
let mut inner = self.inner.write().await;
|
||||
inner.prepare_before_start_vm(device_configs).await
|
||||
}
|
||||
|
||||
pub async fn setup_after_start_vm(&self) -> Result<()> {
|
||||
let mut inner = self.inner.write().await;
|
||||
inner.setup_after_start_vm().await
|
||||
}
|
||||
|
||||
pub async fn get_storage_for_sandbox(&self) -> Result<Vec<Storage>> {
|
||||
let inner = self.inner.read().await;
|
||||
inner.get_storage_for_sandbox().await
|
||||
}
|
||||
|
||||
pub async fn handler_rootfs(
|
||||
&self,
|
||||
cid: &str,
|
||||
bundle_path: &str,
|
||||
rootfs_mounts: &[Mount],
|
||||
) -> Result<Arc<dyn Rootfs>> {
|
||||
let inner = self.inner.read().await;
|
||||
inner.handler_rootfs(cid, bundle_path, rootfs_mounts).await
|
||||
}
|
||||
|
||||
pub async fn handler_volumes(
|
||||
&self,
|
||||
cid: &str,
|
||||
oci_mounts: &[oci::Mount],
|
||||
) -> Result<Vec<Arc<dyn Volume>>> {
|
||||
let inner = self.inner.read().await;
|
||||
inner.handler_volumes(cid, oci_mounts).await
|
||||
}
|
||||
|
||||
pub async fn dump(&self) {
|
||||
let inner = self.inner.read().await;
|
||||
inner.dump().await
|
||||
}
|
||||
|
||||
pub async fn update_cgroups(
|
||||
&self,
|
||||
cid: &str,
|
||||
linux_resources: Option<&LinuxResources>,
|
||||
) -> Result<()> {
|
||||
let inner = self.inner.read().await;
|
||||
inner.update_cgroups(cid, linux_resources).await
|
||||
}
|
||||
|
||||
pub async fn delete_cgroups(&self) -> Result<()> {
|
||||
let inner = self.inner.read().await;
|
||||
inner.delete_cgroups().await
|
||||
}
|
||||
}
|
134
src/runtime-rs/crates/resource/src/manager_inner.rs
Normal file
134
src/runtime-rs/crates/resource/src/manager_inner.rs
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::{Agent, Storage};
|
||||
use anyhow::{Context, Result};
|
||||
use hypervisor::Hypervisor;
|
||||
use kata_types::config::TomlConfig;
|
||||
use kata_types::mount::Mount;
|
||||
use oci::LinuxResources;
|
||||
|
||||
use crate::{
|
||||
cgroups::CgroupsResource,
|
||||
rootfs::{RootFsResource, Rootfs},
|
||||
share_fs::{self, ShareFs},
|
||||
volume::{Volume, VolumeResource},
|
||||
ResourceConfig,
|
||||
};
|
||||
|
||||
pub(crate) struct ResourceManagerInner {
|
||||
sid: String,
|
||||
// TODO: remove
|
||||
#[allow(dead_code)]
|
||||
agent: Arc<dyn Agent>,
|
||||
hypervisor: Arc<dyn Hypervisor>,
|
||||
share_fs: Option<Arc<dyn ShareFs>>,
|
||||
|
||||
pub rootfs_resource: RootFsResource,
|
||||
pub volume_resource: VolumeResource,
|
||||
pub cgroups_resource: CgroupsResource,
|
||||
}
|
||||
|
||||
impl ResourceManagerInner {
|
||||
pub(crate) fn new(
|
||||
sid: &str,
|
||||
agent: Arc<dyn Agent>,
|
||||
hypervisor: Arc<dyn Hypervisor>,
|
||||
toml_config: &TomlConfig,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
sid: sid.to_string(),
|
||||
agent,
|
||||
hypervisor,
|
||||
share_fs: None,
|
||||
rootfs_resource: RootFsResource::new(),
|
||||
volume_resource: VolumeResource::new(),
|
||||
cgroups_resource: CgroupsResource::new(sid, toml_config)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn prepare_before_start_vm(
|
||||
&mut self,
|
||||
device_configs: Vec<ResourceConfig>,
|
||||
) -> Result<()> {
|
||||
for dc in device_configs {
|
||||
match dc {
|
||||
ResourceConfig::ShareFs(c) => {
|
||||
let share_fs = share_fs::new(&self.sid, &c).context("new share fs")?;
|
||||
share_fs
|
||||
.setup_device_before_start_vm(self.hypervisor.as_ref())
|
||||
.await
|
||||
.context("setup share fs device before start vm")?;
|
||||
self.share_fs = Some(share_fs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn setup_after_start_vm(&mut self) -> Result<()> {
|
||||
if let Some(share_fs) = self.share_fs.as_ref() {
|
||||
share_fs
|
||||
.setup_device_after_start_vm(self.hypervisor.as_ref())
|
||||
.await
|
||||
.context("setup share fs device after start vm")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_storage_for_sandbox(&self) -> Result<Vec<Storage>> {
|
||||
let mut storages = vec![];
|
||||
if let Some(d) = self.share_fs.as_ref() {
|
||||
let mut s = d.get_storages().await.context("get storage")?;
|
||||
storages.append(&mut s);
|
||||
}
|
||||
Ok(storages)
|
||||
}
|
||||
|
||||
pub async fn handler_rootfs(
|
||||
&self,
|
||||
cid: &str,
|
||||
bundle_path: &str,
|
||||
rootfs_mounts: &[Mount],
|
||||
) -> Result<Arc<dyn Rootfs>> {
|
||||
self.rootfs_resource
|
||||
.handler_rootfs(&self.share_fs, cid, bundle_path, rootfs_mounts)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn handler_volumes(
|
||||
&self,
|
||||
cid: &str,
|
||||
oci_mounts: &[oci::Mount],
|
||||
) -> Result<Vec<Arc<dyn Volume>>> {
|
||||
self.volume_resource
|
||||
.handler_volumes(&self.share_fs, cid, oci_mounts)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_cgroups(
|
||||
&self,
|
||||
cid: &str,
|
||||
linux_resources: Option<&LinuxResources>,
|
||||
) -> Result<()> {
|
||||
self.cgroups_resource
|
||||
.update_cgroups(cid, linux_resources, self.hypervisor.as_ref())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_cgroups(&self) -> Result<()> {
|
||||
self.cgroups_resource.delete().await
|
||||
}
|
||||
|
||||
pub async fn dump(&self) {
|
||||
self.rootfs_resource.dump().await;
|
||||
self.volume_resource.dump().await;
|
||||
}
|
||||
}
|
123
src/runtime-rs/crates/resource/src/rootfs/mod.rs
Normal file
123
src/runtime-rs/crates/resource/src/rootfs/mod.rs
Normal file
@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
mod share_fs_rootfs;
|
||||
|
||||
use std::{sync::Arc, vec::Vec};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use kata_types::mount::Mount;
|
||||
use log::{error, info};
|
||||
use nix::sys::stat::{self, SFlag};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::share_fs::ShareFs;
|
||||
|
||||
const ROOTFS: &str = "rootfs";
|
||||
|
||||
#[async_trait]
|
||||
pub trait Rootfs: Send + Sync {
|
||||
async fn get_guest_rootfs_path(&self) -> Result<String>;
|
||||
async fn get_rootfs_mount(&self) -> Result<Vec<oci::Mount>>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RootFsResourceInner {
|
||||
rootfs: Vec<Arc<dyn Rootfs>>,
|
||||
}
|
||||
|
||||
pub struct RootFsResource {
|
||||
inner: Arc<RwLock<RootFsResourceInner>>,
|
||||
}
|
||||
|
||||
impl Default for RootFsResource {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RootFsResource {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(RootFsResourceInner::default())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handler_rootfs(
|
||||
&self,
|
||||
share_fs: &Option<Arc<dyn ShareFs>>,
|
||||
cid: &str,
|
||||
bundle_path: &str,
|
||||
rootfs_mounts: &[Mount],
|
||||
) -> Result<Arc<dyn Rootfs>> {
|
||||
match rootfs_mounts {
|
||||
mounts_vec if is_single_layer_rootfs(mounts_vec) => {
|
||||
// Safe as single_layer_rootfs must have one layer
|
||||
let layer = &mounts_vec[0];
|
||||
|
||||
let rootfs = if let Some(_dev_id) = get_block_device(&layer.source) {
|
||||
// block rootfs
|
||||
unimplemented!()
|
||||
} else if let Some(share_fs) = share_fs {
|
||||
// share fs rootfs
|
||||
let share_fs_mount = share_fs.get_share_fs_mount();
|
||||
share_fs_rootfs::ShareFsRootfs::new(&share_fs_mount, cid, bundle_path, layer)
|
||||
.await
|
||||
.context("new share fs rootfs")?
|
||||
} else {
|
||||
return Err(anyhow!("unsupported rootfs {:?}", &layer));
|
||||
};
|
||||
|
||||
let mut inner = self.inner.write().await;
|
||||
let r = Arc::new(rootfs);
|
||||
inner.rootfs.push(r.clone());
|
||||
Ok(r)
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"unsupported rootfs mounts count {}",
|
||||
rootfs_mounts.len()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dump(&self) {
|
||||
let inner = self.inner.read().await;
|
||||
for r in &inner.rootfs {
|
||||
info!(
|
||||
"rootfs {:?}: count {}",
|
||||
r.get_guest_rootfs_path().await,
|
||||
Arc::strong_count(r)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_single_layer_rootfs(rootfs_mounts: &[Mount]) -> bool {
|
||||
rootfs_mounts.len() == 1
|
||||
}
|
||||
|
||||
fn get_block_device(file_path: &str) -> Option<u64> {
|
||||
if file_path.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match stat::stat(file_path) {
|
||||
Ok(fstat) => {
|
||||
if SFlag::from_bits_truncate(fstat.st_mode) == SFlag::S_IFBLK {
|
||||
return Some(fstat.st_rdev);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to stat for {} {:?}", file_path, err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
None
|
||||
}
|
59
src/runtime-rs/crates/resource/src/rootfs/share_fs_rootfs.rs
Normal file
59
src/runtime-rs/crates/resource/src/rootfs/share_fs_rootfs.rs
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use kata_sys_util::mount::Mounter;
|
||||
use kata_types::mount::Mount;
|
||||
|
||||
use super::{Rootfs, ROOTFS};
|
||||
use crate::share_fs::{ShareFsMount, ShareFsRootfsConfig};
|
||||
|
||||
pub(crate) struct ShareFsRootfs {
|
||||
guest_path: String,
|
||||
}
|
||||
|
||||
impl ShareFsRootfs {
|
||||
pub async fn new(
|
||||
share_fs_mount: &Arc<dyn ShareFsMount>,
|
||||
cid: &str,
|
||||
bundle_path: &str,
|
||||
rootfs: &Mount,
|
||||
) -> Result<Self> {
|
||||
let bundle_rootfs = format!("{}/{}", bundle_path, ROOTFS);
|
||||
rootfs.mount(&bundle_rootfs).context(format!(
|
||||
"mount rootfs from {:?} to {}",
|
||||
&rootfs, &bundle_rootfs
|
||||
))?;
|
||||
|
||||
let mount_result = share_fs_mount
|
||||
.share_rootfs(ShareFsRootfsConfig {
|
||||
cid: cid.to_string(),
|
||||
source: bundle_rootfs.to_string(),
|
||||
target: ROOTFS.to_string(),
|
||||
readonly: false,
|
||||
})
|
||||
.await
|
||||
.context("share rootfs")?;
|
||||
|
||||
Ok(ShareFsRootfs {
|
||||
guest_path: mount_result.guest_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Rootfs for ShareFsRootfs {
|
||||
async fn get_guest_rootfs_path(&self) -> Result<String> {
|
||||
Ok(self.guest_path.clone())
|
||||
}
|
||||
|
||||
async fn get_rootfs_mount(&self) -> Result<Vec<oci::Mount>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
78
src/runtime-rs/crates/resource/src/share_fs/mod.rs
Normal file
78
src/runtime-rs/crates/resource/src/share_fs/mod.rs
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
mod share_virtio_fs;
|
||||
mod share_virtio_fs_inline;
|
||||
use share_virtio_fs_inline::ShareVirtioFsInline;
|
||||
mod share_virtio_fs_standalone;
|
||||
use share_virtio_fs_standalone::ShareVirtioFsStandalone;
|
||||
mod utils;
|
||||
mod virtio_fs_share_mount;
|
||||
use virtio_fs_share_mount::VirtiofsShareMount;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::Storage;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use hypervisor::Hypervisor;
|
||||
use kata_types::config::hypervisor::SharedFsInfo;
|
||||
|
||||
const VIRTIO_FS: &str = "virtio-fs";
|
||||
const INLINE_VIRTIO_FS: &str = "inline-virtio-fs";
|
||||
|
||||
const KATA_HOST_SHARED_DIR: &str = "/run/kata-containers/shared/sandboxes/";
|
||||
const KATA_GUEST_SHARE_DIR: &str = "/run/kata-containers/shared/containers/";
|
||||
pub(crate) const DEFAULT_KATA_GUEST_SANDBOX_DIR: &str = "/run/kata-containers/sandbox/";
|
||||
|
||||
const PASSTHROUGH_FS_DIR: &str = "passthrough";
|
||||
|
||||
#[async_trait]
|
||||
pub trait ShareFs: Send + Sync {
|
||||
fn get_share_fs_mount(&self) -> Arc<dyn ShareFsMount>;
|
||||
async fn setup_device_before_start_vm(&self, h: &dyn Hypervisor) -> Result<()>;
|
||||
async fn setup_device_after_start_vm(&self, h: &dyn Hypervisor) -> Result<()>;
|
||||
async fn get_storages(&self) -> Result<Vec<Storage>>;
|
||||
}
|
||||
|
||||
pub struct ShareFsRootfsConfig {
|
||||
// TODO: for nydus v5/v6 need to update ShareFsMount
|
||||
pub cid: String,
|
||||
pub source: String,
|
||||
pub target: String,
|
||||
pub readonly: bool,
|
||||
}
|
||||
|
||||
pub struct ShareFsVolumeConfig {
|
||||
pub cid: String,
|
||||
pub source: String,
|
||||
pub target: String,
|
||||
pub readonly: bool,
|
||||
}
|
||||
|
||||
pub struct ShareFsMountResult {
|
||||
pub guest_path: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ShareFsMount: Send + Sync {
|
||||
async fn share_rootfs(&self, config: ShareFsRootfsConfig) -> Result<ShareFsMountResult>;
|
||||
async fn share_volume(&self, config: ShareFsVolumeConfig) -> Result<ShareFsMountResult>;
|
||||
}
|
||||
|
||||
pub fn new(id: &str, config: &SharedFsInfo) -> Result<Arc<dyn ShareFs>> {
|
||||
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")?,
|
||||
)),
|
||||
_ => Err(anyhow!("unsupported shred fs {:?}", &shared_fs)),
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use hypervisor::{device, Hypervisor};
|
||||
use kata_sys_util::mount;
|
||||
|
||||
use super::utils;
|
||||
|
||||
pub(crate) const MOUNT_GUEST_TAG: &str = "kataShared";
|
||||
pub(crate) const PASSTHROUGH_FS_DIR: &str = "passthrough";
|
||||
|
||||
pub(crate) const FS_TYPE_VIRTIO_FS: &str = "virtio_fs";
|
||||
pub(crate) const KATA_VIRTIO_FS_DEV_TYPE: &str = "virtio-fs";
|
||||
|
||||
const VIRTIO_FS_SOCKET: &str = "virtiofsd.sock";
|
||||
|
||||
pub(crate) fn generate_sock_path(root: &str) -> String {
|
||||
let socket_path = Path::new(root).join(VIRTIO_FS_SOCKET);
|
||||
socket_path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
pub(crate) async fn prepare_virtiofs(
|
||||
h: &dyn Hypervisor,
|
||||
fs_type: &str,
|
||||
id: &str,
|
||||
root: &str,
|
||||
) -> Result<()> {
|
||||
let host_ro_dest = utils::get_host_ro_shared_path(id);
|
||||
utils::ensure_dir_exist(&host_ro_dest)?;
|
||||
|
||||
let host_rw_dest = utils::get_host_rw_shared_path(id);
|
||||
utils::ensure_dir_exist(&host_rw_dest)?;
|
||||
|
||||
mount::bind_mount_unchecked(&host_rw_dest, &host_ro_dest, true)
|
||||
.context("bind mount shared_fs directory")?;
|
||||
|
||||
let share_fs_device = device::Device::ShareFsDevice(device::ShareFsDeviceConfig {
|
||||
sock_path: generate_sock_path(root),
|
||||
mount_tag: String::from(MOUNT_GUEST_TAG),
|
||||
host_path: String::from(host_ro_dest.to_str().unwrap()),
|
||||
fs_type: fs_type.to_string(),
|
||||
queue_size: 0,
|
||||
queue_num: 0,
|
||||
});
|
||||
h.add_device(share_fs_device).await.context("add device")?;
|
||||
Ok(())
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use agent::Storage;
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use hypervisor::{
|
||||
device::{Device as HypervisorDevice, ShareFsMountConfig, ShareFsMountType, ShareFsOperation},
|
||||
Hypervisor,
|
||||
};
|
||||
use kata_types::config::hypervisor::SharedFsInfo;
|
||||
|
||||
use super::{
|
||||
share_virtio_fs::{
|
||||
prepare_virtiofs, FS_TYPE_VIRTIO_FS, KATA_VIRTIO_FS_DEV_TYPE, MOUNT_GUEST_TAG,
|
||||
PASSTHROUGH_FS_DIR,
|
||||
},
|
||||
utils, ShareFs, *,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
pub(crate) static ref SHARED_DIR_VIRTIO_FS_OPTIONS: Vec::<String> = vec![
|
||||
String::from("default_permissions,allow_other,rootmode=040000,user_id=0,group_id=0"),
|
||||
String::from("nodev"),
|
||||
];
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShareVirtioFsInlineConfig {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
pub struct ShareVirtioFsInline {
|
||||
config: ShareVirtioFsInlineConfig,
|
||||
share_fs_mount: Arc<dyn ShareFsMount>,
|
||||
}
|
||||
|
||||
impl ShareVirtioFsInline {
|
||||
pub(crate) fn new(id: &str, _config: &SharedFsInfo) -> Result<Self> {
|
||||
Ok(Self {
|
||||
config: ShareVirtioFsInlineConfig { id: id.to_string() },
|
||||
share_fs_mount: Arc::new(VirtiofsShareMount::new(id)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ShareFs for ShareVirtioFsInline {
|
||||
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) -> Result<()> {
|
||||
prepare_virtiofs(h, INLINE_VIRTIO_FS, &self.config.id, "")
|
||||
.await
|
||||
.context("prepare virtiofs")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup_device_after_start_vm(&self, h: &dyn Hypervisor) -> Result<()> {
|
||||
setup_inline_virtiofs(&self.config.id, h)
|
||||
.await
|
||||
.context("setup inline virtiofs")?;
|
||||
Ok(())
|
||||
}
|
||||
async fn get_storages(&self) -> Result<Vec<Storage>> {
|
||||
// setup storage
|
||||
let mut storages: Vec<Storage> = Vec::new();
|
||||
|
||||
let mut shared_options = SHARED_DIR_VIRTIO_FS_OPTIONS.clone();
|
||||
shared_options.push(format!("tag={}", MOUNT_GUEST_TAG));
|
||||
|
||||
let shared_volume: Storage = 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),
|
||||
options: shared_options,
|
||||
mount_point: String::from(KATA_GUEST_SHARE_DIR),
|
||||
};
|
||||
|
||||
storages.push(shared_volume);
|
||||
Ok(storages)
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_inline_virtiofs(id: &str, h: &dyn Hypervisor) -> Result<()> {
|
||||
// - source is the absolute path of PASSTHROUGH_FS_DIR on host, e.g.
|
||||
// /run/kata-containers/shared/sandboxes/<sid>/passthrough
|
||||
// - mount point is the path relative to KATA_GUEST_SHARE_DIR in guest
|
||||
let mnt = format!("/{}", PASSTHROUGH_FS_DIR);
|
||||
|
||||
let rw_source = utils::get_host_rw_shared_path(id).join(PASSTHROUGH_FS_DIR);
|
||||
utils::ensure_dir_exist(&rw_source)?;
|
||||
|
||||
let ro_source = utils::get_host_ro_shared_path(id).join(PASSTHROUGH_FS_DIR);
|
||||
let source = String::from(ro_source.to_str().unwrap());
|
||||
|
||||
let virtio_fs = HypervisorDevice::ShareFsMount(ShareFsMountConfig {
|
||||
source: source.clone(),
|
||||
fstype: ShareFsMountType::PASSTHROUGH,
|
||||
mount_point: mnt,
|
||||
config: None,
|
||||
tag: String::from(MOUNT_GUEST_TAG),
|
||||
op: ShareFsOperation::Mount,
|
||||
prefetch_list_path: None,
|
||||
});
|
||||
h.add_device(virtio_fs)
|
||||
.await
|
||||
.context(format!("fail to attach passthrough fs {:?}", source))
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::{process::Stdio, sync::Arc};
|
||||
|
||||
use agent::Storage;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use hypervisor::Hypervisor;
|
||||
use kata_types::config::hypervisor::SharedFsInfo;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, BufReader},
|
||||
process::{Child, Command},
|
||||
sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
RwLock,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
share_virtio_fs::generate_sock_path, utils::get_host_ro_shared_path,
|
||||
virtio_fs_share_mount::VirtiofsShareMount, ShareFs, ShareFsMount,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShareVirtioFsStandaloneConfig {
|
||||
id: String,
|
||||
jail_root: String,
|
||||
|
||||
// virtio_fs_daemon is the virtio-fs vhost-user daemon path
|
||||
pub virtio_fs_daemon: String,
|
||||
// virtio_fs_cache cache mode for fs version cache or "none"
|
||||
pub virtio_fs_cache: String,
|
||||
// virtio_fs_extra_args passes options to virtiofsd daemon
|
||||
pub virtio_fs_extra_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ShareVirtioFsStandaloneInner {
|
||||
pid: Option<u32>,
|
||||
}
|
||||
pub(crate) struct ShareVirtioFsStandalone {
|
||||
inner: Arc<RwLock<ShareVirtioFsStandaloneInner>>,
|
||||
config: ShareVirtioFsStandaloneConfig,
|
||||
share_fs_mount: Arc<dyn ShareFsMount>,
|
||||
}
|
||||
|
||||
impl ShareVirtioFsStandalone {
|
||||
pub(crate) fn new(id: &str, _config: &SharedFsInfo) -> Result<Self> {
|
||||
Ok(Self {
|
||||
inner: Arc::new(RwLock::new(ShareVirtioFsStandaloneInner::default())),
|
||||
// TODO: update with config
|
||||
config: ShareVirtioFsStandaloneConfig {
|
||||
id: id.to_string(),
|
||||
jail_root: "".to_string(),
|
||||
virtio_fs_daemon: "".to_string(),
|
||||
virtio_fs_cache: "".to_string(),
|
||||
virtio_fs_extra_args: vec![],
|
||||
},
|
||||
share_fs_mount: Arc::new(VirtiofsShareMount::new(id)),
|
||||
})
|
||||
}
|
||||
|
||||
fn virtiofsd_args(&self, sock_path: &str) -> Result<Vec<String>> {
|
||||
let source_path = get_host_ro_shared_path(&self.config.id);
|
||||
if !source_path.exists() {
|
||||
return Err(anyhow!("The virtiofs shared path didn't exist"));
|
||||
}
|
||||
|
||||
let mut args: Vec<String> = vec![
|
||||
String::from("-f"),
|
||||
String::from("-o"),
|
||||
format!("vhost_user_socket={}", sock_path),
|
||||
String::from("-o"),
|
||||
format!("source={}", source_path.to_str().unwrap()),
|
||||
String::from("-o"),
|
||||
format!("cache={}", self.config.virtio_fs_cache),
|
||||
];
|
||||
|
||||
if !self.config.virtio_fs_extra_args.is_empty() {
|
||||
let mut extra_args: Vec<String> = self.config.virtio_fs_extra_args.clone();
|
||||
args.append(&mut extra_args);
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
async fn setup_virtiofsd(&self) -> Result<()> {
|
||||
let sock_path = generate_sock_path(&self.config.jail_root);
|
||||
let args = self.virtiofsd_args(&sock_path).context("virtiofsd args")?;
|
||||
|
||||
let mut cmd = Command::new(&self.config.virtio_fs_daemon);
|
||||
let child_cmd = cmd.args(&args).stderr(Stdio::piped());
|
||||
let child = child_cmd.spawn().context("spawn virtiofsd")?;
|
||||
|
||||
// update virtiofsd pid{
|
||||
{
|
||||
let mut inner = self.inner.write().await;
|
||||
inner.pid = child.id();
|
||||
}
|
||||
|
||||
let (tx, mut rx): (Sender<Result<()>>, Receiver<Result<()>>) = channel(100);
|
||||
tokio::spawn(run_virtiofsd(child, tx));
|
||||
|
||||
// TODO: support timeout
|
||||
match rx.recv().await.unwrap() {
|
||||
Ok(_) => {
|
||||
info!(sl!(), "start virtiofsd successfully");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!(sl!(), "failed to start virtiofsd {}", e);
|
||||
self.shutdown_virtiofsd()
|
||||
.await
|
||||
.context("shutdown_virtiofsd")?;
|
||||
Err(anyhow!("failed to start virtiofsd"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn shutdown_virtiofsd(&self) -> Result<()> {
|
||||
let mut inner = self.inner.write().await;
|
||||
|
||||
if let Some(pid) = inner.pid.take() {
|
||||
info!(sl!(), "shutdown virtiofsd pid {}", pid);
|
||||
let pid = ::nix::unistd::Pid::from_raw(pid as i32);
|
||||
if let Err(err) = ::nix::sys::signal::kill(pid, nix::sys::signal::SIGKILL) {
|
||||
if err != ::nix::Error::Sys(nix::errno::Errno::ESRCH) {
|
||||
return Err(anyhow!("failed to kill virtiofsd pid {} {}", pid, err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_virtiofsd(mut child: Child, tx: Sender<Result<()>>) -> Result<()> {
|
||||
let stderr = child.stderr.as_mut().unwrap();
|
||||
let stderr_reader = BufReader::new(stderr);
|
||||
let mut lines = stderr_reader.lines();
|
||||
|
||||
while let Some(buffer) = lines.next_line().await.context("read next line")? {
|
||||
let trim_buffer = buffer.trim_end();
|
||||
if !trim_buffer.is_empty() {
|
||||
info!(sl!(), "source: virtiofsd {}", trim_buffer);
|
||||
}
|
||||
if buffer.contains("Waiting for vhost-user socket connection") {
|
||||
tx.send(Ok(())).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
info!(sl!(), "wait virtiofsd {:?}", child.wait().await);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ShareFs for ShareVirtioFsStandalone {
|
||||
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) -> Result<()> {
|
||||
self.setup_virtiofsd().await.context("setup virtiofsd")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup_device_after_start_vm(&self, _h: &dyn Hypervisor) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_storages(&self) -> Result<Vec<Storage>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
94
src/runtime-rs/crates/resource/src/share_fs/utils.rs
Normal file
94
src/runtime-rs/crates/resource/src/share_fs/utils.rs
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use kata_sys_util::mount;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(crate) fn ensure_dir_exist(path: &Path) -> Result<()> {
|
||||
if !path.exists() {
|
||||
std::fs::create_dir_all(path).context(format!("failed to create directory {:?}", path))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn share_to_guest(
|
||||
// absolute path for source
|
||||
source: &str,
|
||||
// relative path for target
|
||||
target: &str,
|
||||
sid: &str,
|
||||
cid: &str,
|
||||
readonly: bool,
|
||||
is_volume: bool,
|
||||
) -> Result<String> {
|
||||
let host_dest = do_get_host_path(target, sid, cid, is_volume, false);
|
||||
mount::bind_mount_unchecked(source, &host_dest, readonly)
|
||||
.context(format!("failed to bind mount {} to {}", source, &host_dest))?;
|
||||
|
||||
// bind mount remount event is not propagated to mount subtrees, so we have
|
||||
// to remount the read only dir mount point directly.
|
||||
if readonly {
|
||||
let dst = do_get_host_path(target, sid, cid, is_volume, true);
|
||||
mount::bind_remount_read_only(&dst).context("bind remount readonly")?;
|
||||
}
|
||||
|
||||
Ok(do_get_guest_path(target, cid, is_volume))
|
||||
}
|
||||
|
||||
pub(crate) fn get_host_ro_shared_path(id: &str) -> PathBuf {
|
||||
Path::new(KATA_HOST_SHARED_DIR).join(id).join("ro")
|
||||
}
|
||||
|
||||
pub(crate) fn get_host_rw_shared_path(id: &str) -> PathBuf {
|
||||
Path::new(KATA_HOST_SHARED_DIR).join(id).join("rw")
|
||||
}
|
||||
|
||||
fn do_get_guest_any_path(target: &str, cid: &str, is_volume: bool, is_virtiofs: bool) -> String {
|
||||
let dir = PASSTHROUGH_FS_DIR;
|
||||
let guest_share_dir = if is_virtiofs {
|
||||
Path::new("/")
|
||||
} else {
|
||||
Path::new(KATA_GUEST_SHARE_DIR)
|
||||
};
|
||||
|
||||
let path = if is_volume && !is_virtiofs {
|
||||
guest_share_dir.join(dir).join(target)
|
||||
} else {
|
||||
guest_share_dir.join(dir).join(cid).join(target)
|
||||
};
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
fn do_get_guest_path(target: &str, cid: &str, is_volume: bool) -> String {
|
||||
do_get_guest_any_path(target, cid, is_volume, false)
|
||||
}
|
||||
|
||||
fn do_get_host_path(
|
||||
target: &str,
|
||||
sid: &str,
|
||||
cid: &str,
|
||||
is_volume: bool,
|
||||
read_only: bool,
|
||||
) -> String {
|
||||
let dir = PASSTHROUGH_FS_DIR;
|
||||
|
||||
let get_host_path = if read_only {
|
||||
get_host_ro_shared_path
|
||||
} else {
|
||||
get_host_rw_shared_path
|
||||
};
|
||||
|
||||
let path = if is_volume {
|
||||
get_host_path(sid).join(dir).join(target)
|
||||
} else {
|
||||
get_host_path(sid).join(dir).join(cid).join(target)
|
||||
};
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::{utils, ShareFsMount, ShareFsMountResult, ShareFsRootfsConfig, ShareFsVolumeConfig};
|
||||
|
||||
pub struct VirtiofsShareMount {
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl VirtiofsShareMount {
|
||||
pub fn new(id: &str) -> Self {
|
||||
Self { id: id.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ShareFsMount for VirtiofsShareMount {
|
||||
async fn share_rootfs(&self, config: ShareFsRootfsConfig) -> Result<ShareFsMountResult> {
|
||||
// TODO: select virtiofs or support nydus
|
||||
let guest_path = utils::share_to_guest(
|
||||
&config.source,
|
||||
&config.target,
|
||||
&self.id,
|
||||
&config.cid,
|
||||
config.readonly,
|
||||
false,
|
||||
)
|
||||
.context("share to guest")?;
|
||||
Ok(ShareFsMountResult { guest_path })
|
||||
}
|
||||
|
||||
async fn share_volume(&self, config: ShareFsVolumeConfig) -> Result<ShareFsMountResult> {
|
||||
let guest_path = utils::share_to_guest(
|
||||
&config.source,
|
||||
&config.target,
|
||||
&self.id,
|
||||
&config.cid,
|
||||
config.readonly,
|
||||
true,
|
||||
)
|
||||
.context("share to guest")?;
|
||||
Ok(ShareFsMountResult { guest_path })
|
||||
}
|
||||
}
|
37
src/runtime-rs/crates/resource/src/volume/block_volume.rs
Normal file
37
src/runtime-rs/crates/resource/src/volume/block_volume.rs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use super::Volume;
|
||||
|
||||
pub(crate) struct BlockVolume {}
|
||||
|
||||
/// BlockVolume: block device volume
|
||||
impl BlockVolume {
|
||||
pub(crate) fn new(_m: &oci::Mount) -> Result<Self> {
|
||||
Ok(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Volume for BlockVolume {
|
||||
fn get_volume_mount(&self) -> anyhow::Result<Vec<oci::Mount>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_storage(&self) -> Result<Vec<agent::Storage>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cleanup(&self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_block_volume(_m: &oci::Mount) -> bool {
|
||||
// attach block device
|
||||
false
|
||||
}
|
36
src/runtime-rs/crates/resource/src/volume/default_volume.rs
Normal file
36
src/runtime-rs/crates/resource/src/volume/default_volume.rs
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use super::Volume;
|
||||
|
||||
pub(crate) struct DefaultVolume {
|
||||
mount: oci::Mount,
|
||||
}
|
||||
|
||||
/// DefaultVolume: passthrough the mount to guest
|
||||
impl DefaultVolume {
|
||||
pub fn new(mount: &oci::Mount) -> Result<Self> {
|
||||
Ok(Self {
|
||||
mount: mount.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Volume for DefaultVolume {
|
||||
fn get_volume_mount(&self) -> anyhow::Result<Vec<oci::Mount>> {
|
||||
Ok(vec![self.mount.clone()])
|
||||
}
|
||||
|
||||
fn get_storage(&self) -> Result<Vec<agent::Storage>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn cleanup(&self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
99
src/runtime-rs/crates/resource/src/volume/mod.rs
Normal file
99
src/runtime-rs/crates/resource/src/volume/mod.rs
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
mod block_volume;
|
||||
mod default_volume;
|
||||
mod share_fs_volume;
|
||||
mod shm_volume;
|
||||
|
||||
use std::{sync::Arc, vec::Vec};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::share_fs::ShareFs;
|
||||
|
||||
pub trait Volume: Send + Sync {
|
||||
fn get_volume_mount(&self) -> Result<Vec<oci::Mount>>;
|
||||
fn get_storage(&self) -> Result<Vec<agent::Storage>>;
|
||||
fn cleanup(&self) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct VolumeResourceInner {
|
||||
volumes: Vec<Arc<dyn Volume>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct VolumeResource {
|
||||
inner: Arc<RwLock<VolumeResourceInner>>,
|
||||
}
|
||||
|
||||
impl VolumeResource {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn handler_volumes(
|
||||
&self,
|
||||
share_fs: &Option<Arc<dyn ShareFs>>,
|
||||
cid: &str,
|
||||
oci_mounts: &[oci::Mount],
|
||||
) -> Result<Vec<Arc<dyn Volume>>> {
|
||||
let mut volumes: Vec<Arc<dyn Volume>> = vec![];
|
||||
for m in oci_mounts {
|
||||
let volume: Arc<dyn Volume> = if shm_volume::is_shim_volume(m) {
|
||||
let shm_size = shm_volume::DEFAULT_SHM_SIZE;
|
||||
Arc::new(
|
||||
shm_volume::ShmVolume::new(m, shm_size)
|
||||
.context(format!("new shm volume {:?}", m))?,
|
||||
)
|
||||
} else if share_fs_volume::is_share_fs_volume(m) {
|
||||
Arc::new(
|
||||
share_fs_volume::ShareFsVolume::new(share_fs, m, cid)
|
||||
.await
|
||||
.context(format!("new share fs volume {:?}", m))?,
|
||||
)
|
||||
} else if block_volume::is_block_volume(m) {
|
||||
Arc::new(
|
||||
block_volume::BlockVolume::new(m)
|
||||
.context(format!("new block volume {:?}", m))?,
|
||||
)
|
||||
} else if is_skip_volume(m) {
|
||||
info!(sl!(), "skip volume {:?}", m);
|
||||
continue;
|
||||
} else {
|
||||
Arc::new(
|
||||
default_volume::DefaultVolume::new(m)
|
||||
.context(format!("new default volume {:?}", m))?,
|
||||
)
|
||||
};
|
||||
|
||||
volumes.push(volume.clone());
|
||||
let mut inner = self.inner.write().await;
|
||||
inner.volumes.push(volume);
|
||||
}
|
||||
|
||||
Ok(volumes)
|
||||
}
|
||||
|
||||
pub async fn dump(&self) {
|
||||
let inner = self.inner.read().await;
|
||||
for v in &inner.volumes {
|
||||
info!(
|
||||
sl!(),
|
||||
"volume mount {:?}: count {}",
|
||||
v.get_volume_mount(),
|
||||
Arc::strong_count(v)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_skip_volume(_m: &oci::Mount) -> bool {
|
||||
// TODO: support volume check
|
||||
false
|
||||
}
|
153
src/runtime-rs/crates/resource/src/volume/share_fs_volume.rs
Normal file
153
src/runtime-rs/crates/resource/src/volume/share_fs_volume.rs
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use log::debug;
|
||||
use nix::sys::stat::{stat, SFlag};
|
||||
|
||||
use super::Volume;
|
||||
use crate::share_fs::{ShareFs, ShareFsVolumeConfig};
|
||||
|
||||
// copy file to container's rootfs if filesystem sharing is not supported, otherwise
|
||||
// bind mount it in the shared directory.
|
||||
// Ignore /dev, directories and all other device files. We handle
|
||||
// only regular files in /dev. It does not make sense to pass the host
|
||||
// device nodes to the guest.
|
||||
// skip the volumes whose source had already set to guest share dir.
|
||||
pub(crate) struct ShareFsVolume {
|
||||
mounts: Vec<oci::Mount>,
|
||||
}
|
||||
|
||||
impl ShareFsVolume {
|
||||
pub(crate) async fn new(
|
||||
share_fs: &Option<Arc<dyn ShareFs>>,
|
||||
m: &oci::Mount,
|
||||
cid: &str,
|
||||
) -> Result<Self> {
|
||||
let file_name = Path::new(&m.source).file_name().unwrap().to_str().unwrap();
|
||||
let file_name = generate_mount_path(cid, file_name);
|
||||
|
||||
let mut volume = Self { mounts: vec![] };
|
||||
match share_fs {
|
||||
None => {
|
||||
let mut need_copy = false;
|
||||
match stat(Path::new(&m.source)) {
|
||||
Ok(stat) => {
|
||||
// Ignore the mount if this is not a regular file (excludes
|
||||
// directory, socket, device, ...) as it cannot be handled by
|
||||
// a simple copy. But this should not be treated as an error,
|
||||
// only as a limitation.
|
||||
// golang implement:
|
||||
// ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket |
|
||||
// ModeDevice | ModeCharDevice | ModeIrregular
|
||||
let file_type = SFlag::S_IFDIR
|
||||
| SFlag::S_IFLNK
|
||||
| SFlag::S_IFIFO
|
||||
| SFlag::S_IFSOCK
|
||||
| SFlag::S_IFCHR
|
||||
| SFlag::S_IFREG;
|
||||
if !file_type.contains(SFlag::from_bits_truncate(stat.st_mode)) {
|
||||
debug!(
|
||||
"Ignoring non-regular file as FS sharing not supported. mount: {:?}",
|
||||
m
|
||||
);
|
||||
return Ok(volume);
|
||||
}
|
||||
if SFlag::from_bits_truncate(stat.st_mode) != SFlag::S_IFDIR {
|
||||
need_copy = true;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(anyhow!(format!(
|
||||
"failed to stat file {} {:?}",
|
||||
&m.source, err
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if need_copy {
|
||||
// TODO: copy file
|
||||
}
|
||||
}
|
||||
Some(share_fs) => {
|
||||
let share_fs_mount = share_fs.get_share_fs_mount();
|
||||
let mount_result = share_fs_mount
|
||||
.share_volume(ShareFsVolumeConfig {
|
||||
cid: cid.to_string(),
|
||||
source: m.source.clone(),
|
||||
target: file_name,
|
||||
readonly: false,
|
||||
})
|
||||
.await
|
||||
.context("share fs volume")?;
|
||||
|
||||
volume.mounts.push(oci::Mount {
|
||||
destination: m.destination.clone(),
|
||||
r#type: "bind".to_string(),
|
||||
source: mount_result.guest_path,
|
||||
options: m.options.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(volume)
|
||||
}
|
||||
}
|
||||
|
||||
impl Volume for ShareFsVolume {
|
||||
fn get_volume_mount(&self) -> anyhow::Result<Vec<oci::Mount>> {
|
||||
Ok(self.mounts.clone())
|
||||
}
|
||||
|
||||
fn get_storage(&self) -> Result<Vec<agent::Storage>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn cleanup(&self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_share_fs_volume(m: &oci::Mount) -> bool {
|
||||
m.r#type == "bind" && !is_host_device(&m.destination)
|
||||
}
|
||||
|
||||
fn is_host_device(dest: &str) -> bool {
|
||||
if dest == "/dev" {
|
||||
return true;
|
||||
}
|
||||
|
||||
if dest.starts_with("/dev") {
|
||||
let src = match std::fs::canonicalize(dest) {
|
||||
Err(_) => return false,
|
||||
Ok(src) => src,
|
||||
};
|
||||
|
||||
if src.is_file() {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// Note, don't generate random name, attaching rafs depends on the predictable name.
|
||||
// If template_mnt is passed, just use existed name in it
|
||||
pub fn generate_mount_path(id: &str, file_name: &str) -> String {
|
||||
let mut nid = String::from(id);
|
||||
if nid.len() > 10 {
|
||||
nid = nid.chars().take(10).collect();
|
||||
}
|
||||
|
||||
let mut uid = uuid::Uuid::new_v4().to_string();
|
||||
let uid_vec: Vec<&str> = uid.splitn(2, '-').collect();
|
||||
uid = String::from(uid_vec[0]);
|
||||
|
||||
format!("{}-{}-{}", nid, uid, file_name)
|
||||
}
|
105
src/runtime-rs/crates/resource/src/volume/shm_volume.rs
Normal file
105
src/runtime-rs/crates/resource/src/volume/shm_volume.rs
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2019-2022 Alibaba Cloud
|
||||
// Copyright (c) 2019-2022 Ant Group
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use super::Volume;
|
||||
use crate::share_fs::DEFAULT_KATA_GUEST_SANDBOX_DIR;
|
||||
|
||||
pub const SHM_DIR: &str = "shm";
|
||||
// DEFAULT_SHM_SIZE is the default shm size to be used in case host
|
||||
// IPC is used.
|
||||
pub const DEFAULT_SHM_SIZE: u64 = 65536 * 1024;
|
||||
|
||||
// KATA_EPHEMERAL_DEV_TYPE creates a tmpfs backed volume for sharing files between containers.
|
||||
pub const KATA_EPHEMERAL_DEV_TYPE: &str = "ephemeral";
|
||||
|
||||
pub(crate) struct ShmVolume {
|
||||
mount: oci::Mount,
|
||||
storage: Option<agent::Storage>,
|
||||
}
|
||||
|
||||
impl ShmVolume {
|
||||
pub(crate) fn new(m: &oci::Mount, shm_size: u64) -> Result<Self> {
|
||||
let (storage, mount) = if shm_size > 0 {
|
||||
// storage
|
||||
let mount_path = Path::new(DEFAULT_KATA_GUEST_SANDBOX_DIR).join(SHM_DIR);
|
||||
let mount_path = mount_path.to_str().unwrap();
|
||||
let option = format!("size={}", shm_size);
|
||||
|
||||
let options = vec![
|
||||
String::from("noexec"),
|
||||
String::from("nosuid"),
|
||||
String::from("nodev"),
|
||||
String::from("mode=1777"),
|
||||
option,
|
||||
];
|
||||
|
||||
let storage = agent::Storage {
|
||||
driver: String::from(KATA_EPHEMERAL_DEV_TYPE),
|
||||
driver_options: Vec::new(),
|
||||
source: String::from("shm"),
|
||||
fs_type: String::from("tmpfs"),
|
||||
options,
|
||||
mount_point: mount_path.to_string(),
|
||||
};
|
||||
|
||||
// mount
|
||||
let mount = oci::Mount {
|
||||
r#type: "bind".to_string(),
|
||||
destination: m.destination.clone(),
|
||||
source: mount_path.to_string(),
|
||||
options: vec!["rbind".to_string()],
|
||||
};
|
||||
|
||||
(Some(storage), mount)
|
||||
} else {
|
||||
let mount = oci::Mount {
|
||||
r#type: "tmpfs".to_string(),
|
||||
destination: m.destination.clone(),
|
||||
source: "shm".to_string(),
|
||||
options: vec![
|
||||
"noexec",
|
||||
"nosuid",
|
||||
"nodev",
|
||||
"mode=1777",
|
||||
&format!("size={}", DEFAULT_SHM_SIZE),
|
||||
]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
};
|
||||
(None, mount)
|
||||
};
|
||||
|
||||
Ok(Self { storage, mount })
|
||||
}
|
||||
}
|
||||
|
||||
impl Volume for ShmVolume {
|
||||
fn get_volume_mount(&self) -> anyhow::Result<Vec<oci::Mount>> {
|
||||
Ok(vec![self.mount.clone()])
|
||||
}
|
||||
|
||||
fn get_storage(&self) -> Result<Vec<agent::Storage>> {
|
||||
let s = if let Some(s) = self.storage.as_ref() {
|
||||
vec![s.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn cleanup(&self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_shim_volume(m: &oci::Mount) -> bool {
|
||||
m.destination == "/dev/shm" && m.r#type != KATA_EPHEMERAL_DEV_TYPE
|
||||
}
|
Loading…
Reference in New Issue
Block a user