Skip to content

Commit

Permalink
vmm: setup hosts, hostname and resolv.conf files
Browse files Browse the repository at this point in the history
If containerd has created these three files, it will append relevant cri mounts
to container. If these mounts are defined in Kubernetes Pod Yaml by the end user,
they will also exist in container mounts. So anyway, in the above cases, vmm-sandboxer
should not append any mounts.

If not, vmm-snadboxer could prepare sandbox files and covert them into oci mounts
before sandbox starts. The hostname should be set to either pod hostname or host
hostname, the hosts should be set as host, and the DNS should be set either from
pod spec or host DNS.

Moreover, it's good to set guest DNS if we want to mount some nfs in guest.

Signed-off-by: Zhang Tianyang <burning9699@gmail.com>
  • Loading branch information
Burning1020 committed Dec 14, 2023
1 parent 7b6c933 commit 77c226c
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 17 deletions.
8 changes: 8 additions & 0 deletions vmm/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
32 changes: 32 additions & 0 deletions vmm/sandbox/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions vmm/sandbox/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"

183 changes: 181 additions & 2 deletions vmm/sandbox/src/container/handler/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -46,6 +55,7 @@ where
&self,
sandbox: &mut KuasarSandbox<T>,
) -> 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
Expand All @@ -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);
Expand All @@ -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<Mount> = vec![];
let cri_mount_handler = |dst, filename, extra_mounts: &mut Vec<Mount>| {
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<Mount>) -> 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<Mount> {
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);
}
}
4 changes: 2 additions & 2 deletions vmm/sandbox/src/container/handler/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 77c226c

Please sign in to comment.