runtime-rs: support rootfs volume for resource

Fixes: #3785
Signed-off-by: Quanwei Zhou <quanweiZhou@linux.alibaba.com>
This commit is contained in:
Quanwei Zhou 2022-03-30 10:12:55 +08:00 committed by Fupan Li
parent 234d7bca04
commit 3ff0db05a7
18 changed files with 1500 additions and 6 deletions

View File

@ -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"

View File

@ -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" }

View File

@ -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),
}

View 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
}
}

View 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;
}
}

View 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
}

View 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!()
}
}

View 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)),
}
}

View File

@ -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(())
}

View File

@ -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))
}

View File

@ -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![])
}
}

View 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()
}

View File

@ -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 })
}
}

View 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
}

View 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!()
}
}

View 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
}

View 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)
}

View 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
}