Skip to content

Commit

Permalink
device: fs: add vhost-user-fs e2e test
Browse files Browse the repository at this point in the history
Add unit tests for vhost-user-fs device mount, verifying successful
mounting of vhost-user fs device with sandbox abled. Test without
sandbox is not prepared, since the vhost-user-fs device need to run as
root.

A new test scenario(mount_rw) has been added to e2e test. This scenario
mounts virtiofs in the guest, then verifies data by reading from the
host's shared directory. It also creates a file in the guest, writes
data to it, and has the host verify the written data. This scenario is
tested using both in-process virtiofs and vhost-user-fs devices.

Additionally, the copy_file functionality has been refactored into a
separate scenario that runs on both virtio-fs and vhost-user-fs devices.

BUG=b:355159487
TEST=tools/dev_container tools/presubmit

Change-Id: Icc09355fee1aae464b98fa153a98cbdd4a9e117d
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5746575
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Commit-Queue: Yuan Yao <yuanyaogoog@chromium.org>
  • Loading branch information
youenn98 authored and crosvm LUCI committed Aug 5, 2024
1 parent 54e5b6b commit 34ed5bc
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 17 deletions.
8 changes: 8 additions & 0 deletions e2e_tests/fixture/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,14 @@ impl Config {
));
self
}

#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn with_vhost_user_fs(mut self, socket_path: &Path, tag: &str) -> Self {
self.extra_args.push("--vhost-user-fs".to_string());
self.extra_args
.push(format!("{},tag={}", socket_path.to_str().unwrap(), tag));
self
}
}

static PREP_ONCE: Once = Once::new();
Expand Down
146 changes: 129 additions & 17 deletions e2e_tests/tests/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,80 @@

//! Testing virtio-fs.

#![cfg(any(target_os = "android", target_os = "linux"))]

use std::path::Path;

use fixture::vhost_user::CmdType;
use fixture::vhost_user::Config as VuConfig;
use fixture::vhost_user::VhostUserBackend;
use fixture::vm::Config;
use fixture::vm::TestVm;
use tempfile::NamedTempFile;
use tempfile::TempDir;

/// Tests file copy on virtiofs
/// Tests file copy
///
/// 1. Create `original.txt` on a temporal directory.
/// 2. Start a VM with a virtiofs device for the temporal directory.
/// 3. Copy `original.txt` to `new.txt` in the guest.
/// 4. Check that `new.txt` is created in the host.
#[test]
fn copy_file() {
fn copy_file(mut vm: TestVm, tag: &str, dir: TempDir) {
const ORIGINAL_FILE_NAME: &str = "original.txt";
const NEW_FILE_NAME: &str = "new.txt";
const TEST_DATA: &str = "virtiofs works!";

let temp_dir = tempfile::tempdir().unwrap();
let orig_file = temp_dir.path().join(ORIGINAL_FILE_NAME);
let orig_file = dir.path().join(ORIGINAL_FILE_NAME);

std::fs::write(orig_file, TEST_DATA).unwrap();

// TODO(b/269137600): Split this into multiple lines instead of connecting commands with `&&`.
vm.exec_in_guest(&format!(
"mount -t virtiofs {tag} /mnt && cp /mnt/{} /mnt/{} && sync",
ORIGINAL_FILE_NAME, NEW_FILE_NAME,
))
.unwrap();

let new_file = dir.path().join(NEW_FILE_NAME);
let contents = std::fs::read(new_file).unwrap();
assert_eq!(TEST_DATA.as_bytes(), &contents);
}

/// Tests mount/read/create/write
/// 1. Create `read_file.txt` with test data in host's temporal directory.
/// 2. Start a VM with a virtiofs device for the temporal directory.
/// 3. Guest reads read_file.txt file & verify the content is test data
/// 4. Guest creates a write_file.txt file in shared directory
/// 5. Host reads file from host's temporal directory & verify content is test data
fn mount_rw(mut vm: TestVm, tag: &str, dir: TempDir) {
const READ_FILE_NAME: &str = "read_test.txt";
const WRITE_FILE_NAME: &str = "write_test.txt";
const TEST_DATA: &str = "hello world";

let read_test_file = dir.path().join(READ_FILE_NAME);
let write_test_file = dir.path().join(WRITE_FILE_NAME);
std::fs::write(read_test_file, TEST_DATA).unwrap();

assert_eq!(
vm.exec_in_guest(&format!(
"mount -t virtiofs {tag} /mnt && cat /mnt/read_test.txt"
))
.unwrap()
.stdout
.trim(),
TEST_DATA
);

const IN_FS_WRITE_FILE_PATH: &str = "/mnt/write_test.txt";
let _ = vm.exec_in_guest(&format!("echo -n {TEST_DATA} > {IN_FS_WRITE_FILE_PATH}"));
let read_contents = std::fs::read(write_test_file).unwrap();
assert_eq!(TEST_DATA.as_bytes(), &read_contents);
}

#[test]
fn fs_copy_file() {
let tag = "mtdtest";
let temp_dir = tempfile::tempdir().unwrap();

let config = Config::new().extra_args(vec![
"--shared-dir".to_string(),
Expand All @@ -34,17 +87,25 @@ fn copy_file() {
),
]);

let mut vm = TestVm::new(config).unwrap();
// TODO(b/269137600): Split this into multiple lines instead of connecting commands with `&&`.
vm.exec_in_guest(&format!(
"mount -t virtiofs {tag} /mnt && cp /mnt/{} /mnt/{} && sync",
ORIGINAL_FILE_NAME, NEW_FILE_NAME,
))
.unwrap();
let vm = TestVm::new(config).unwrap();
copy_file(vm, tag, temp_dir)
}

let new_file = temp_dir.path().join(NEW_FILE_NAME);
let contents = std::fs::read(new_file).unwrap();
assert_eq!(TEST_DATA.as_bytes(), &contents);
#[test]
fn fs_mount_rw() {
let tag = "mtdtest";
let temp_dir = tempfile::tempdir().unwrap();

let config = Config::new().extra_args(vec![
"--shared-dir".to_string(),
format!(
"{}:{tag}:type=fs:cache=auto",
temp_dir.path().to_str().unwrap()
),
]);

let vm = TestVm::new(config).unwrap();
mount_rw(vm, tag, temp_dir)
}

/// Tests file ownership seen by the VM.
Expand All @@ -54,15 +115,14 @@ fn copy_file() {
/// 3. Start a VM with a virtiofs device for the temporal directory.
/// 4. Check that `user_file.txt`'s uid is <mapped-uid> in the VM.
/// 5. Verify gid similarly.
#[cfg(any(target_os = "android", target_os = "linux"))]
#[test]
fn file_ugid() {
const FILE_NAME: &str = "user_file.txt";
let uid = base::geteuid();
let gid = base::getegid();
let mapped_uid: u32 = rand::random();
let mapped_gid: u32 = rand::random();
let uid_map = format!("{} {} 1", mapped_uid, uid);
let uid_map: String = format!("{} {} 1", mapped_uid, uid);
let gid_map = format!("{} {} 1", mapped_gid, gid);

let temp_dir = tempfile::tempdir().unwrap();
Expand Down Expand Up @@ -101,3 +161,55 @@ fn file_ugid() {
assert!(output.stdout.contains(&format!("Uid: ({}/", mapped_uid)));
assert!(output.stdout.contains(&format!("Gid: ({}/", mapped_gid)));
}

pub fn create_vu_fs_config(socket: &Path, shared_dir: &Path, tag: &str) -> VuConfig {
let uid = base::geteuid();
let gid = base::getegid();
let socket_path = socket.to_str().unwrap();
let shared_dir_path = shared_dir.to_str().unwrap();
println!("socket={socket_path}, tag={tag}, shared_dir={shared_dir_path}");
VuConfig::new(CmdType::Device, "vhost-user-fs").extra_args(vec![
"fs".to_string(),
format!("--socket={socket_path}"),
format!("--shared-dir={shared_dir_path}"),
format!("--tag={tag}"),
format!("--uid-map=0 {uid} 1"),
format!("--gid-map=0 {gid} 1"),
])
}

/// Tests vhost-user fs device copy file.
#[test]
fn vhost_user_fs_copy_file() {
let socket = NamedTempFile::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();

let config = Config::new();
let tag = "mtdtest";

let vu_config = create_vu_fs_config(socket.path(), temp_dir.path(), tag);
let _vu_device = VhostUserBackend::new(vu_config).unwrap();

let config = config.with_vhost_user_fs(socket.path(), tag);
let vm = TestVm::new(config).unwrap();

copy_file(vm, tag, temp_dir);
}

/// Tests vhost-user fs device mount and read write.
#[test]
fn vhost_user_fs_mount_rw() {
let socket = NamedTempFile::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();

let config = Config::new();
let tag = "mtdtest";

let vu_config = create_vu_fs_config(socket.path(), temp_dir.path(), tag);
let _vu_device = VhostUserBackend::new(vu_config).unwrap();

let config = config.with_vhost_user_fs(socket.path(), tag);
let vm = TestVm::new(config).unwrap();

mount_rw(vm, tag, temp_dir);
}

0 comments on commit 34ed5bc

Please sign in to comment.