diff --git a/vmm/common/src/lib.rs b/vmm/common/src/lib.rs index 2b5ed4be..c1d9403f 100644 --- a/vmm/common/src/lib.rs +++ b/vmm/common/src/lib.rs @@ -25,3 +25,11 @@ pub const KUASAR_STATE_DIR: &str = "/run/kuasar/state"; pub const IO_FILE_PREFIX: &str = "io"; pub const STORAGE_FILE_PREFIX: &str = "storage"; pub const SHARED_DIR_SUFFIX: &str = "shared"; + +pub const ETC_HOSTS: &str = "/etc/hosts"; +pub const ETC_HOSTNAME: &str = "/etc/hostname"; +pub const ETC_RESOLV: &str = "/etc/resolv.conf"; +pub const DEV_SHM: &str = "/dev/shm"; +pub const HOSTS_FILENAME: &str = "hosts"; +pub const HOSTNAME_FILENAME: &str = "hostname"; +pub const RESOLV_FILENAME: &str = "resolv.conf"; diff --git a/vmm/sandbox/Cargo.lock b/vmm/sandbox/Cargo.lock index 652fb584..edf4b012 100644 --- a/vmm/sandbox/Cargo.lock +++ b/vmm/sandbox/Cargo.lock @@ -753,6 +753,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.9" @@ -953,6 +964,12 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchit" version = "0.5.0" @@ -1254,6 +1271,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -1894,6 +1917,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "temp-dir" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af547b166dd1ea4b472165569fc456cfb6818116f854690b0ff205e636523dab" + [[package]] name = "tempfile" version = "3.7.0" @@ -2334,6 +2363,7 @@ dependencies = [ "containerd-shim", "env_logger", "futures-util", + "hostname", "lazy_static", "log", "netlink-packet-core", @@ -2341,6 +2371,7 @@ dependencies = [ "nix 0.26.2", "oci-spec", "os_pipe", + "path-clean", "proc-macro2", "procfs", "prost-types 0.10.1", @@ -2353,6 +2384,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "temp-dir", "time 0.3.25", "tokio", "toml", diff --git a/vmm/sandbox/Cargo.toml b/vmm/sandbox/Cargo.toml index ab0c208b..0dae26c5 100644 --- a/vmm/sandbox/Cargo.toml +++ b/vmm/sandbox/Cargo.toml @@ -43,6 +43,8 @@ ttrpc = { version = "0.7", features = ["async"] } protobuf = "3.2" cgroups-rs = "0.3.2" proc-macro2 = "1.0.66" +hostname = "0.3" +path-clean = "1.0.1" [[bin]] name = "qemu" @@ -55,3 +57,7 @@ path = "src/bin/cloud_hypervisor/main.rs" [[bin]] name = "stratovirt" path = "src/bin/stratovirt/main.rs" + +[dev-dependencies] +temp-dir = "0.1.11" + diff --git a/vmm/sandbox/src/container/handler/spec.rs b/vmm/sandbox/src/container/handler/spec.rs index 02e53355..d2cab729 100644 --- a/vmm/sandbox/src/container/handler/spec.rs +++ b/vmm/sandbox/src/container/handler/spec.rs @@ -14,10 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ +use std::path::Path; + use anyhow::anyhow; use async_trait::async_trait; -use containerd_sandbox::error::Error; -use vmm_common::SHARED_DIR_SUFFIX; +use containerd_sandbox::{ + error::Error, + spec::{JsonSpec, Mount}, +}; +use path_clean::clean; +use vmm_common::{ + ETC_HOSTNAME, ETC_HOSTS, ETC_RESOLV, HOSTNAME_FILENAME, HOSTS_FILENAME, KUASAR_STATE_DIR, + RESOLV_FILENAME, SHARED_DIR_SUFFIX, +}; use crate::{ container::handler::Handler, sandbox::KuasarSandbox, utils::write_file_atomic, vm::VM, @@ -46,6 +55,7 @@ where &self, sandbox: &mut KuasarSandbox, ) -> containerd_sandbox::error::Result<()> { + let shared_path = format!("{}/{}", sandbox.base_dir, SHARED_DIR_SUFFIX); let container = sandbox.container_mut(&self.container_id)?; let spec = container .data @@ -59,6 +69,8 @@ where if let Some(p) = spec.process.as_mut() { p.apparmor_profile = "".to_string(); } + // Update sandbox files mounts for container + container_mounts(&shared_path, spec); let spec_str = serde_json::to_string(spec) .map_err(|e| anyhow!("failed to parse spec in sandbox, {}", e))?; let config_path = format!("{}/{}", container.data.bundle, CONFIG_FILE_NAME); @@ -80,3 +92,170 @@ where Ok(()) } } + +// container_mounts sets up necessary container system file mounts +// including /etc/hostname, /etc/hosts and /etc/resolv.conf. +fn container_mounts(shared_path: &str, spec: &mut JsonSpec) { + let rw_option = if spec.root.as_ref().map(|r| r.readonly).unwrap_or_default() { + "ro" + } else { + "rw" + }; + + let mut extra_mounts: Vec = vec![]; + let cri_mount_handler = |dst, filename, extra_mounts: &mut Vec| { + if !is_in_cri_mounts(dst, &spec.mounts) { + let host_path = format!("{}/{}", shared_path, filename); + // If host path exist, should add it to container mount + if Path::exists(host_path.as_ref()) { + extra_mounts.push(Mount { + destination: dst.to_string(), + r#type: "bind".to_string(), + source: format!("{}/{}", KUASAR_STATE_DIR, filename), + options: vec!["rbind", "rprivate", rw_option] + .into_iter() + .map(String::from) + .collect(), + }); + } + } + }; + + cri_mount_handler(ETC_HOSTNAME, HOSTNAME_FILENAME, &mut extra_mounts); + cri_mount_handler(ETC_HOSTS, HOSTS_FILENAME, &mut extra_mounts); + cri_mount_handler(ETC_RESOLV, RESOLV_FILENAME, &mut extra_mounts); + spec.mounts.append(&mut extra_mounts); +} + +fn is_in_cri_mounts(dst: &str, mounts: &Vec) -> bool { + for mount in mounts { + if clean(&mount.destination) == clean(dst) { + return true; + } + } + false +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use containerd_sandbox::spec::{JsonSpec, Mount, Root}; + use containerd_shim::util::write_str_to_file; + use temp_dir::TempDir; + + use crate::container::handler::spec::{container_mounts, is_in_cri_mounts}; + + fn generate_cri_mounts() -> Vec { + vec![ + Mount { + destination: "/etc/hosts".to_string(), + r#type: "".to_string(), + source: "/run/kuasar-vmm/0d042b22dbaf083f704c5945488c7f63d10a664f743802a7901ac6fae6460d9b/shared/hosts".to_string(), + options: vec![], + }, + Mount { + destination: "/etc/hostname".to_string(), + r#type: "".to_string(), + source: "/run/kuasar-vmm/0d042b22dbaf083f704c5945488c7f63d10a664f743802a7901ac6fae6460d9b/shared/hostname".to_string(), + options: vec![], + }, + Mount { + destination: "/etc/resolv.conf".to_string(), + r#type: "".to_string(), + source: "/run/kuasar-vmm/0d042b22dbaf083f704c5945488c7f63d10a664f743802a7901ac6fae6460d9b/shared/resolv.conf".to_string(), + options: vec![], + }, + Mount { + destination: "/dev/shm".to_string(), + r#type: "".to_string(), + source: "/run/kuasar-vmm/0d042b22dbaf083f704c5945488c7f63d10a664f743802a7901ac6fae6460d9b/shared/shm".to_string(), + options: vec![], + }, + ] + } + + #[test] + fn test_is_in_cri_mounts() { + let cri_mounts = generate_cri_mounts(); + assert!(is_in_cri_mounts("/etc/hostname", &cri_mounts)); + assert!(is_in_cri_mounts("/etc/hosts", &cri_mounts)); + assert!(is_in_cri_mounts("/etc/resolv.conf", &cri_mounts)); + assert!(is_in_cri_mounts("/dev/shm", &cri_mounts)); + assert!(!is_in_cri_mounts("/var/lib/kuasar", &cri_mounts)); + } + + #[tokio::test] + // When no mount defined in spec and kuasar doesn't create these files, expect no mount added. + async fn test_container_mounts_with_target_file_not_exist() { + let mut spec = JsonSpec::default(); + spec.mounts = vec![]; + + let tmp_path = TempDir::new().unwrap(); + let shared_path = tmp_path.path().to_str().unwrap(); + container_mounts(shared_path, &mut spec); + assert_eq!(spec.mounts.len(), 0); + } + + #[tokio::test] + // When no mount defined in spec and kuasar created hostname file, expect hostname mount is added. + async fn test_container_mounts_with_hostname_file_exist() { + let mut spec = JsonSpec::default(); + spec.mounts = vec![]; + spec.root = Some(Root::default()); + assert!(!spec.root.clone().expect("root should not be None").readonly); + + let tmp_path = TempDir::new().unwrap(); + let shared_path = tmp_path.path().to_str().unwrap(); + write_str_to_file(tmp_path.child("hostname"), "kuasar-deno-001") + .await + .unwrap(); + container_mounts(shared_path, &mut spec); + assert_eq!(spec.mounts.len(), 1); + assert!(spec.mounts[0].options.contains(&"rw".to_string())); + let parent_path = Path::new(&spec.mounts[0].source) + .parent() + .unwrap() + .to_str() + .unwrap(); + assert_eq!(parent_path, "/run/kuasar/state"); + } + + #[tokio::test] + // When readonly rootfs defined in spec, expect hostname mount is readonly. + async fn test_container_mounts_with_mounts_and_ro() { + let mut spec = JsonSpec::default(); + spec.mounts = vec![]; + spec.root = Some(Root { + path: "".to_string(), + readonly: true, + }); + assert!(spec.root.clone().expect("root should not be None").readonly); + + let tmp_path = TempDir::new().unwrap(); + let shared_path = tmp_path.path().to_str().unwrap(); + write_str_to_file(tmp_path.child("hostname"), "kuasar-deno-001") + .await + .unwrap(); + container_mounts(shared_path, &mut spec); + assert_eq!(spec.mounts.len(), 1); + assert!(spec.mounts[0].options.contains(&"ro".to_string())); + } + + #[tokio::test] + // When hostname mount already defined, expect hostname mount is no changed. + async fn test_container_mounts_with_mount_predefined() { + let mut spec = JsonSpec::default(); + let cri_mount = generate_cri_mounts(); + spec.mounts = cri_mount.clone(); + + let tmp_path = TempDir::new().unwrap(); + let shared_path = tmp_path.path().to_str().unwrap(); + container_mounts(shared_path, &mut spec); + assert_eq!(spec.mounts.len(), 4); + assert_eq!(cri_mount[0].source, spec.mounts[0].source); + assert_eq!(cri_mount[0].r#type, spec.mounts[0].r#type); + assert_eq!(cri_mount[0].destination, spec.mounts[0].destination); + assert_eq!(cri_mount[0].options, spec.mounts[0].options); + } +} diff --git a/vmm/sandbox/src/container/handler/storage.rs b/vmm/sandbox/src/container/handler/storage.rs index cb9461cf..2a77d982 100644 --- a/vmm/sandbox/src/container/handler/storage.rs +++ b/vmm/sandbox/src/container/handler/storage.rs @@ -21,7 +21,7 @@ use containerd_sandbox::{ Sandbox, }; use log::debug; -use vmm_common::{storage::ANNOTATION_KEY_STORAGE, STORAGE_FILE_PREFIX}; +use vmm_common::{storage::ANNOTATION_KEY_STORAGE, DEV_SHM, STORAGE_FILE_PREFIX}; use crate::{ container::handler::Handler, sandbox::KuasarSandbox, storage::mount::is_bind_shm, @@ -68,7 +68,7 @@ where } // TODO if vmm-task mount shm when startup, then just use the same shm if is_bind_shm(&m) { - m.source = "/dev/shm".to_string(); + m.source = DEV_SHM.to_string(); m.options.push("rbind".to_string()); } handled_mounts.push(m); diff --git a/vmm/sandbox/src/sandbox.rs b/vmm/sandbox/src/sandbox.rs index e9b97f78..7905a248 100644 --- a/vmm/sandbox/src/sandbox.rs +++ b/vmm/sandbox/src/sandbox.rs @@ -25,14 +25,18 @@ use containerd_sandbox::{ utils::cleanup_mounts, ContainerOption, Sandbox, SandboxOption, SandboxStatus, Sandboxer, }; +use containerd_shim::util::write_str_to_file; use log::{debug, error, info, warn}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tokio::{ - fs::{remove_dir_all, OpenOptions}, + fs::{copy, create_dir_all, remove_dir_all, OpenOptions}, io::{AsyncReadExt, AsyncWriteExt}, sync::{Mutex, RwLock}, }; -use vmm_common::{api::sandbox_ttrpc::SandboxServiceClient, storage::Storage, SHARED_DIR_SUFFIX}; +use vmm_common::{ + api::sandbox_ttrpc::SandboxServiceClient, storage::Storage, ETC_HOSTS, ETC_RESOLV, + HOSTNAME_FILENAME, HOSTS_FILENAME, RESOLV_FILENAME, SHARED_DIR_SUFFIX, +}; use crate::{ cgroup::SandboxCgroup, @@ -42,7 +46,7 @@ use crate::{ }, container::KuasarContainer, network::{Network, NetworkConfig}, - utils::{get_resources, get_sandbox_cgroup_parent_path}, + utils::{get_dns_config, get_hostname, get_resources, get_sandbox_cgroup_parent_path}, vm::{Hooks, Recoverable, VMFactory, VM}, }; @@ -236,6 +240,9 @@ where let network = Network::new(network_config).await?; network.attach_to(&mut sandbox).await?; } + + // setup sandbox files: hosts, hostname and resolv.conf for guest + sandbox.setup_sandbox_files().await?; self.hooks.post_create(&mut sandbox).await?; sandbox.dump().await?; self.sandboxes @@ -363,7 +370,7 @@ where async fn remove_container(&mut self, id: &str) -> Result<()> { self.deference_container_storages(id).await?; - let bundle = format!("{}/{}/{}", self.base_dir, SHARED_DIR_SUFFIX, id); + let bundle = format!("{}/{}", self.get_sandbox_shared_path(), &id); if let Err(e) = tokio::fs::remove_dir_all(&*bundle).await { if e.kind() != ErrorKind::NotFound { return Err(anyhow!("failed to remove bundle {}, {}", bundle, e).into()); @@ -540,6 +547,83 @@ where client_sync_clock(client).await; } } + + async fn setup_sandbox_files(&self) -> Result<()> { + let shared_path = self.get_sandbox_shared_path(); + create_dir_all(&shared_path) + .await + .map_err(|e| anyhow!("create host sandbox path({}): {}", shared_path, e))?; + + // Handle hostname + let mut hostname = get_hostname(&self.data); + if hostname.is_empty() { + hostname = hostname::get() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_default(); + } + hostname.push('\n'); + let hostname_path = Path::new(&shared_path).join(HOSTNAME_FILENAME); + write_str_to_file(hostname_path, &hostname) + .await + .map_err(|e| anyhow!("write hostname: {:?}", e))?; + + // handle hosts + let hosts_path = Path::new(&shared_path).join(HOSTS_FILENAME); + copy(ETC_HOSTS, hosts_path) + .await + .map_err(|e| anyhow!("copy hosts: {}", e))?; + + // handle resolv.conf + let resolv_path = Path::new(&shared_path).join(RESOLV_FILENAME); + match get_dns_config(&self.data).map(|dns_config| { + parse_dnsoptions( + &dns_config.servers, + &dns_config.searches, + &dns_config.options, + ) + }) { + Some(resolv_content) if !resolv_content.is_empty() => { + write_str_to_file(resolv_path, &resolv_content) + .await + .map_err(|e| anyhow!("write reslov.conf: {:?}", e))?; + } + _ => { + copy(ETC_RESOLV, resolv_path) + .await + .map_err(|e| anyhow!("copy resolv.conf: {}", e))?; + } + } + + Ok(()) + } + + pub fn get_sandbox_shared_path(&self) -> String { + format!("{}/{}", self.base_dir, SHARED_DIR_SUFFIX) + } +} + +// parse_dnsoptions parse DNS options into resolv.conf format content, +// if none option is specified, will return empty with no error. +fn parse_dnsoptions( + servers: &Vec, + searches: &Vec, + options: &Vec, +) -> String { + let mut resolv_content = String::new(); + + if !searches.is_empty() { + resolv_content.push_str(&format!("search {}\n", searches.join(" "))); + } + + if !servers.is_empty() { + resolv_content.push_str(&format!("nameserver {}\n", servers.join("\nnameserver "))); + } + + if !options.is_empty() { + resolv_content.push_str(&format!("options {}\n", options.join(" "))); + } + + resolv_content } #[derive(Default, Debug, Deserialize)] @@ -595,3 +679,57 @@ fn monitor(sandbox_mutex: Arc>>) { } }); } + +#[cfg(test)] +mod tests { + mod dns { + use crate::sandbox::parse_dnsoptions; + + #[derive(Default)] + struct DnsConfig { + servers: Vec, + searches: Vec, + options: Vec, + } + + #[test] + fn test_parse_empty_dns_option() { + let mut dns_test = DnsConfig::default(); + let resolv_content = + parse_dnsoptions(&dns_test.servers, &dns_test.searches, &dns_test.options); + assert!(resolv_content.is_empty()) + } + + #[test] + fn test_parse_non_empty_dns_option() { + let dns_test = DnsConfig { + servers: vec!["8.8.8.8", "server.google.com"] + .into_iter() + .map(String::from) + .collect(), + searches: vec![ + "server0.google.com", + "server1.google.com", + "server2.google.com", + "server3.google.com", + "server4.google.com", + "server5.google.com", + "server6.google.com", + ] + .into_iter() + .map(String::from) + .collect(), + options: vec!["timeout:1"].into_iter().map(String::from).collect(), + }; + let expected_content = "search server0.google.com server1.google.com server2.google.com server3.google.com server4.google.com server5.google.com server6.google.com +nameserver 8.8.8.8 +nameserver server.google.com +options timeout:1 +".to_string(); + let resolv_content = + parse_dnsoptions(&dns_test.servers, &dns_test.searches, &dns_test.options); + assert!(!resolv_content.is_empty()); + assert_eq!(resolv_content, expected_content) + } + } +} diff --git a/vmm/sandbox/src/storage/mod.rs b/vmm/sandbox/src/storage/mod.rs index 107f64c6..43748681 100644 --- a/vmm/sandbox/src/storage/mod.rs +++ b/vmm/sandbox/src/storage/mod.rs @@ -28,7 +28,7 @@ pub use utils::*; use vmm_common::{ mount::{bind_mount, unmount, MNT_NOFOLLOW}, storage::{Storage, DRIVEREPHEMERALTYPE}, - KUASAR_STATE_DIR, SHARED_DIR_SUFFIX, + KUASAR_STATE_DIR, }; use crate::{ @@ -158,7 +158,7 @@ where } else { m.source.clone() }; - let host_dest = format!("{}/{}/{}", self.base_dir, SHARED_DIR_SUFFIX, &storage_id); + let host_dest = format!("{}/{}", self.get_sandbox_shared_path(), &storage_id); debug!("bind mount storage for mount {:?}, dest: {}", m, &host_dest); let source_path = Path::new(&*source); if source_path.is_dir() { @@ -206,7 +206,7 @@ where m ))); } - let host_dest = format!("{}/{}/{}", self.base_dir, SHARED_DIR_SUFFIX, &storage_id); + let host_dest = format!("{}/{}", self.get_sandbox_shared_path(), &storage_id); debug!("overlay mount storage for {:?}, dest: {}", m, &host_dest); tokio::fs::create_dir_all(&host_dest).await?; mount_rootfs(Some(&m.r#type), Some(&m.source), &m.options, &host_dest) @@ -288,7 +288,7 @@ where if device_id.is_some() { self.vm.hot_detach(&device_id.unwrap()).await?; } else if fs_type == "bind" { - let mount_point = format!("{}/{}/{}", self.base_dir, SHARED_DIR_SUFFIX, id); + let mount_point = format!("{}/{}", self.get_sandbox_shared_path(), &id); unmount(&mount_point, MNT_DETACH | MNT_NOFOLLOW)?; if Path::new(&mount_point).is_dir() { tokio::fs::remove_dir(&mount_point).await.map_err(|e| { diff --git a/vmm/sandbox/src/storage/mount.rs b/vmm/sandbox/src/storage/mount.rs index bc2360d7..ee0cf5fd 100644 --- a/vmm/sandbox/src/storage/mount.rs +++ b/vmm/sandbox/src/storage/mount.rs @@ -16,11 +16,12 @@ limitations under the License. use anyhow::anyhow; use containerd_sandbox::{error::Result, spec::Mount}; +use vmm_common::DEV_SHM; use crate::{storage::MountInfo, utils::read_file}; pub fn is_bind_shm(m: &Mount) -> bool { - is_bind(m) && m.destination == "/dev/shm" + is_bind(m) && m.destination == DEV_SHM } pub fn is_bind(m: &Mount) -> bool { diff --git a/vmm/sandbox/src/utils.rs b/vmm/sandbox/src/utils.rs index 08d561ac..b049e57c 100644 --- a/vmm/sandbox/src/utils.rs +++ b/vmm/sandbox/src/utils.rs @@ -26,7 +26,7 @@ use std::{ use anyhow::anyhow; use containerd_sandbox::{ - cri::api::v1::LinuxContainerResources, + cri::api::v1::{DnsConfig, LinuxContainerResources}, data::SandboxData, error::{Error, Result}, }; @@ -86,6 +86,16 @@ pub fn get_overhead_resources(data: &SandboxData) -> Option<&LinuxContainerResou .and_then(|c| c.linux.as_ref()) .and_then(|l| l.overhead.as_ref()) } +pub fn get_hostname(data: &SandboxData) -> String { + data.config + .as_ref() + .map(|c| c.hostname.clone()) + .unwrap_or_default() +} + +pub fn get_dns_config(data: &SandboxData) -> Option<&DnsConfig> { + data.config.as_ref().and_then(|c| c.dns_config.as_ref()) +} #[allow(dead_code)] pub fn get_total_resources(data: &SandboxData) -> Option { diff --git a/vmm/task/src/container.rs b/vmm/task/src/container.rs index 8ac89465..786b198b 100644 --- a/vmm/task/src/container.rs +++ b/vmm/task/src/container.rs @@ -41,8 +41,8 @@ use containerd_shim::{ util::read_spec, ExitSignal, }; -use log::{debug, error}; -use nix::{sys::signalfd::signal::kill, unistd::Pid}; +use log::{debug, error, warn}; +use nix::{mount, mount::MsFlags, sys::signalfd::signal::kill, unistd::Pid}; use oci_spec::runtime::{LinuxResources, Process, Spec}; use runc::{options::GlobalOpts, Runc, Spawner}; use serde::Deserialize; @@ -52,7 +52,9 @@ use tokio::{ process::Command, sync::Mutex, }; -use vmm_common::{mount::get_mount_type, storage::Storage, KUASAR_STATE_DIR}; +use vmm_common::{ + mount::get_mount_type, storage::Storage, ETC_RESOLV, KUASAR_STATE_DIR, RESOLV_FILENAME, +}; use crate::{ device::rescan_pci_bus, @@ -195,6 +197,26 @@ impl KuasarFactory { Self { sandbox } } + // create_sandbox will do some preparation to provide a livable environment for containers, + // such as adding guest hooks, setting kernel paras, preparing sandbox files and namespaces + pub fn create_sandbox(&self) -> Result<()> { + // Setup DNS, bind mount to /etc/resolv.conf + let dns_file = Path::new(KUASAR_STATE_DIR).join(RESOLV_FILENAME); + if dns_file.exists() { + mount::mount( + Some(&dns_file), + ETC_RESOLV, + Some("bind"), + MsFlags::MS_BIND, + None::<&str>, + )?; + } else { + warn!("unable to find DNS files in kuasar state dir"); + } + + Ok(()) + } + async fn do_create(&self, init: &mut InitProcess) -> Result<()> { let id = init.id.to_string(); let stdio = &init.stdio; diff --git a/vmm/task/src/main.rs b/vmm/task/src/main.rs index 713fe1c0..9600d753 100644 --- a/vmm/task/src/main.rs +++ b/vmm/task/src/main.rs @@ -291,7 +291,7 @@ async fn mount_static_mounts(mounts: Vec) -> containerd_shim::Resul // start_ttrpc_server will create all the ttrpc service and register them to a server that // bind to vsock 1024 port. async fn start_ttrpc_server() -> containerd_shim::Result { - let task = create_task_service().await; + let task = create_task_service().await?; let task_service = create_task(Arc::new(Box::new(task))); let sandbox = SandboxService::new()?; diff --git a/vmm/task/src/task.rs b/vmm/task/src/task.rs index da0c5854..a93eaa59 100644 --- a/vmm/task/src/task.rs +++ b/vmm/task/src/task.rs @@ -35,11 +35,14 @@ use crate::{ sandbox::SandboxResources, }; -pub(crate) async fn create_task_service() -> TaskService { +pub(crate) async fn create_task_service( +) -> containerd_shim::Result> { let (tx, mut rx) = channel(128); let sandbox = Arc::new(Mutex::new(SandboxResources::new().await)); + let factory = KuasarFactory::new(sandbox); + factory.create_sandbox()?; let task = TaskService { - factory: KuasarFactory::new(sandbox), + factory, containers: Arc::new(Default::default()), namespace: "k8s.io".to_string(), exit: Arc::new(Default::default()), @@ -54,7 +57,7 @@ pub(crate) async fn create_task_service() -> TaskService) {