diff --git a/src/vmm/src/devices/virtio/block/device.rs b/src/vmm/src/devices/virtio/block/device.rs index 5da689b2ac4..3f205c14ef6 100644 --- a/src/vmm/src/devices/virtio/block/device.rs +++ b/src/vmm/src/devices/virtio/block/device.rs @@ -315,3 +315,26 @@ impl Persist<'_> for Block { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::devices::virtio::block::virtio::device::FileEngineType; + use crate::devices::virtio::block::virtio::test_utils::default_block; + + #[test] + fn test_spawn_worker_filter() { + let mut inline = Block::Virtio(default_block(FileEngineType::Sync)); + inline.spawn_worker(None).unwrap(); + + let mut threaded = default_block(FileEngineType::Sync); + threaded.config.threaded = true; + let mut threaded = Block::Virtio(threaded); + + assert!(matches!( + threaded.spawn_worker(None), + Err(BlockError::MissingSeccompFilter) + )); + threaded.spawn_worker(Some(Arc::new(vec![]))).unwrap(); + } +} diff --git a/src/vmm/src/devices/virtio/block/virtio/device.rs b/src/vmm/src/devices/virtio/block/virtio/device.rs index 6bd0a65ff3e..64d0849bb5c 100644 --- a/src/vmm/src/devices/virtio/block/virtio/device.rs +++ b/src/vmm/src/devices/virtio/block/virtio/device.rs @@ -819,13 +819,14 @@ mod tests { use crate::devices::virtio::block::virtio::IO_URING_NUM_ENTRIES; use crate::devices::virtio::block::virtio::request::*; use crate::devices::virtio::block::virtio::test_utils::{ - default_block, read_blk_req_descriptors, set_queue, set_rate_limiter, - simulate_async_completion_event, simulate_queue_and_async_completion_events, - simulate_queue_event, + default_block, default_threaded_block, read_blk_req_descriptors, set_queue, + set_rate_limiter, simulate_async_completion_event, + simulate_queue_and_async_completion_events, simulate_queue_event, }; use crate::devices::virtio::queue::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE}; use crate::devices::virtio::test_utils::{VirtQueue, default_interrupt, default_mem}; - use crate::rate_limiter::TokenType; + use crate::rate_limiter::{TokenBucket, TokenType}; + use crate::seccomp::BPF_MAX_LEN; use crate::vstate::memory::{Address, Bytes, GuestAddress}; #[test] @@ -1972,6 +1973,185 @@ mod tests { } } + #[test] + fn test_threaded_device() { + let mut block = default_threaded_block(FileEngineType::Sync); + let mem = default_mem(); + let interrupt = default_interrupt(); + let vq = VirtQueue::new(GuestAddress(0), &mem, 16); + set_queue(&mut block, 0, vq.create_queue()); + + block.activate(mem.clone(), interrupt).unwrap(); + + assert!(block.is_threaded_active()); + assert_eq!(block.num_queues(), BLOCK_QUEUE_SIZES.len()); + assert_eq!(block.queue_config(0).unwrap().size, vq.size()); + assert!(block.queue_config_mut(0).is_some()); + assert!(block.queue_event(0).is_some()); + } + + #[test] + fn test_threaded_update_disk() { + for engine in [FileEngineType::Sync, FileEngineType::Async] { + let mut block = default_threaded_block(engine); + let mem = default_mem(); + let interrupt = default_interrupt(); + let vq = VirtQueue::new(GuestAddress(0), &mem, 16); + set_queue(&mut block, 0, vq.create_queue()); + block.activate(mem.clone(), interrupt.clone()).unwrap(); + + let disk = TempFile::new().unwrap(); + disk.as_file().set_len(0x2000).unwrap(); + let disk_path = disk.as_path().to_str().unwrap().to_string(); + + block.update_disk_image(disk_path.clone()).unwrap(); + + assert_eq!(block.config().path_on_host, disk_path); + assert_eq!(u64::from_le(block.config_space.capacity), 16); + assert!( + interrupt.has_pending_interrupt(VirtioInterruptType::Config), + "updating an active disk must notify the guest" + ); + } + } + + #[test] + fn test_threaded_update_disk_error() { + for engine in [FileEngineType::Sync, FileEngineType::Async] { + let mut block = default_threaded_block(engine); + let original_config = block.config(); + let original_capacity = block.config_space.capacity; + + let mem = default_mem(); + let interrupt = default_interrupt(); + let vq = VirtQueue::new(GuestAddress(0), &mem, 16); + set_queue(&mut block, 0, vq.create_queue()); + block.activate(mem.clone(), interrupt.clone()).unwrap(); + + let mut missing_disk = TempFile::new().unwrap(); + let missing_path = missing_disk.as_path().to_str().unwrap().to_string(); + missing_disk.remove().unwrap(); + + assert!(block.update_disk_image(missing_path).is_err()); + assert_eq!(block.config(), original_config); + assert_eq!(block.config_space.capacity, original_capacity); + assert!(!interrupt.has_pending_interrupt(VirtioInterruptType::Config)); + } + } + + #[test] + fn test_threaded_rate_limiter() { + let mut block = default_threaded_block(FileEngineType::Sync); + let mem = default_mem(); + let vq = VirtQueue::new(GuestAddress(0), &mem, 16); + set_queue(&mut block, 0, vq.create_queue()); + block.activate(mem.clone(), default_interrupt()).unwrap(); + + let bandwidth = TokenBucket::new(1000, 1001, 1002).unwrap(); + let ops = TokenBucket::new(1003, 1004, 1005).unwrap(); + block.update_rate_limiter(BucketUpdate::Update(bandwidth), BucketUpdate::Update(ops)); + + // Reset the device before reactivation. + assert!(block.reset()); + assert_eq!( + block.config().rate_limiter, + RateLimiterConfig::from(&*block.rate_limiter()).into_option() + ); + + set_queue(&mut block, 0, vq.create_queue()); + block.activate(mem, default_interrupt()).unwrap(); + block.update_rate_limiter(BucketUpdate::Disabled, BucketUpdate::Disabled); + + assert!(block.reset()); + assert_eq!(block.config().rate_limiter, None); + assert!(block.rate_limiter().bandwidth().is_none()); + assert!(block.rate_limiter().ops().is_none()); + } + + #[test] + fn test_threaded_queue_dirty() { + let mut block = default_threaded_block(FileEngineType::Sync); + let mem = default_mem(); + let vq = VirtQueue::new(GuestAddress(0), &mem, 16); + set_queue(&mut block, 0, vq.create_queue()); + block.activate(mem.clone(), default_interrupt()).unwrap(); + + assert!(matches!( + block.mark_queue_memory_dirty(&mem), + Err(QueueError::NotReady) + )); + + block.prepare_save(); + + block.mark_queue_memory_dirty(&mem).unwrap(); + } + + #[test] + fn test_threaded_resume() { + let mut block = default_threaded_block(FileEngineType::Sync); + let mem = default_mem(); + let vq = VirtQueue::new(GuestAddress(0), &mem, 16); + set_queue(&mut block, 0, vq.create_queue()); + block.activate(mem.clone(), default_interrupt()).unwrap(); + + block.prepare_save(); + read_blk_req_descriptors(&vq); + let request_type_addr = GuestAddress(vq.dtable[0].addr.get()); + let status_addr = GuestAddress(vq.dtable[2].addr.get()); + vq.dtable[1].len.set(VIRTIO_BLK_ID_BYTES); + mem.write_obj::(VIRTIO_BLK_T_GET_ID, request_type_addr) + .unwrap(); + block.mark_queue_memory_dirty(&mem).unwrap(); + + block.kick(); + // A second pause waits until the resumed worker has processed its queue. + block.prepare_save(); + + assert_eq!(vq.used.idx.get(), 1); + assert_eq!(vq.used.ring[0].get().id, 0); + assert_eq!(vq.used.ring[0].get().len, VIRTIO_BLK_ID_BYTES + 1); + assert_eq!(mem.read_obj::(status_addr).unwrap(), VIRTIO_BLK_S_OK); + } + + #[test] + fn test_threaded_reset_paused() { + for engine in [FileEngineType::Sync, FileEngineType::Async] { + let mut block = default_threaded_block(engine); + let mem = default_mem(); + let vq = VirtQueue::new(GuestAddress(0), &mem, 16); + set_queue(&mut block, 0, vq.create_queue()); + block.set_acked_features(1); + block.activate(mem, default_interrupt()).unwrap(); + block.prepare_save(); + + assert!(block.reset()); + assert!(!block.is_activated()); + assert_eq!(block.acked_features(), 0); + assert!(!block.queue_config(0).unwrap().ready); + assert!(matches!(&block.state, BlockState::Configuring(_, Some(_)))); + } + } + + #[test] + fn test_threaded_reset_error() { + let mut block = default_block(FileEngineType::Sync); + block.config.threaded = true; + block + .spawn_worker(Arc::new(vec![0; BPF_MAX_LEN + 1])) + .unwrap(); + + let mem = default_mem(); + let vq = VirtQueue::new(GuestAddress(0), &mem, 16); + set_queue(&mut block, 0, vq.create_queue()); + block.set_acked_features(1); + block.activate(mem, default_interrupt()).unwrap(); + + assert!(!block.reset()); + assert!(block.is_threaded_active()); + assert_eq!(block.acked_features(), 1); + assert!(block.queue_config(0).unwrap().ready); + } + #[test] fn test_reset_and_reactivation() { for engine in [FileEngineType::Sync, FileEngineType::Async] { diff --git a/src/vmm/src/devices/virtio/block/virtio/persist.rs b/src/vmm/src/devices/virtio/block/virtio/persist.rs index 68435b8f1ce..48d092c8816 100644 --- a/src/vmm/src/devices/virtio/block/virtio/persist.rs +++ b/src/vmm/src/devices/virtio/block/virtio/persist.rs @@ -164,11 +164,14 @@ impl Persist<'_> for VirtioBlock { #[cfg(test)] mod tests { + use std::sync::Arc; use vmm_sys_util::tempfile::TempFile; use super::*; + use crate::devices::virtio::block::virtio::test_utils::{default_block_with_path, set_queue}; use crate::devices::virtio::device::VirtioDevice; - use crate::devices::virtio::test_utils::{default_interrupt, default_mem}; + use crate::devices::virtio::test_utils::{VirtQueue, default_interrupt, default_mem}; + use crate::vstate::memory::GuestAddress; #[test] fn test_cache_semantic_ser() { @@ -253,4 +256,37 @@ mod tests { // Test that block specific fields are the same. assert_eq!(restored_block.disk().file_path, block.disk().file_path); } + + #[test] + fn test_threaded_persistence() { + for engine in [FileEngineType::Sync, FileEngineType::Async] { + let disk = TempFile::new().unwrap(); + disk.as_file().set_len(0x1000).unwrap(); + let mut block = + default_block_with_path(disk.as_path().to_str().unwrap().to_string(), engine); + block.config.threaded = true; + block.spawn_worker(Arc::new(vec![])).unwrap(); + let mem = default_mem(); + let vq = VirtQueue::new(GuestAddress(0), &mem, BLOCK_QUEUE_SIZES[0]); + set_queue(&mut block, 0, vq.create_queue()); + block.set_acked_features(block.avail_features()); + block.activate(mem.clone(), default_interrupt()).unwrap(); + // Pause the worker for snapshotting + block.prepare_save(); + + let state = block.save(); + let serialized = bitcode::serialize(&state).unwrap(); + let restored_state = bitcode::deserialize(&serialized).unwrap(); + let restored = + VirtioBlock::restore(BlockConstructorArgs { mem }, &restored_state).unwrap(); + + assert!(state.threaded); + assert!(state.virtio_state.activated); + assert!(restored.config().threaded); + assert!(!restored.is_activated()); + assert_eq!(restored.acked_features(), block.acked_features()); + assert_eq!(restored.queue_config(0), block.queue_config(0)); + assert_eq!(restored.file_engine_type(), engine); + } + } } diff --git a/src/vmm/src/devices/virtio/block/virtio/test_utils.rs b/src/vmm/src/devices/virtio/block/virtio/test_utils.rs index 5a77eec69e2..71f9361b81d 100644 --- a/src/vmm/src/devices/virtio/block/virtio/test_utils.rs +++ b/src/vmm/src/devices/virtio/block/virtio/test_utils.rs @@ -3,6 +3,8 @@ #![doc(hidden)] +#[cfg(test)] +use std::sync::Arc; #[cfg(test)] use std::thread; #[cfg(test)] @@ -35,6 +37,14 @@ pub fn default_block(file_engine_type: FileEngineType) -> VirtioBlock { default_block_with_path(f.as_path().to_str().unwrap().to_string(), file_engine_type) } +#[cfg(test)] +pub fn default_threaded_block(file_engine_type: FileEngineType) -> VirtioBlock { + let mut block = default_block(file_engine_type); + block.config.threaded = true; + block.spawn_worker(Arc::new(vec![])).unwrap(); + block +} + /// Create a default Block instance using file at the specified path to be used in tests. pub fn default_block_with_path(path: String, file_engine_type: FileEngineType) -> VirtioBlock { let config = VirtioBlockConfig { diff --git a/src/vmm/src/devices/virtio/block/virtio/worker.rs b/src/vmm/src/devices/virtio/block/virtio/worker.rs index 507782602d4..c1b73cbb549 100644 --- a/src/vmm/src/devices/virtio/block/virtio/worker.rs +++ b/src/vmm/src/devices/virtio/block/virtio/worker.rs @@ -917,3 +917,104 @@ impl MutEventSubscriber for ThreadedWorker { self.register_control_event(ops); } } + +#[cfg(test)] +mod tests { + use super::*; + + fn disconnected_handle() -> WorkerHandle { + let (to_worker, from_vmm) = channel::(); + drop(from_vmm); + let (to_vmm, from_worker) = channel::(); + drop(to_vmm); + + WorkerHandle { + to_worker, + from_worker, + control_evt: EventFd::new(libc::EFD_NONBLOCK).unwrap(), + join: thread::spawn(|| {}), + queue_evts: Vec::new(), + } + } + + fn response_disconnected_handle() -> WorkerHandle { + let (to_worker, from_vmm) = channel::(); + let (to_vmm, from_worker) = channel::(); + drop(to_vmm); + + WorkerHandle { + to_worker, + from_worker, + control_evt: EventFd::new(libc::EFD_NONBLOCK).unwrap(), + join: thread::spawn(move || { + from_vmm.recv().unwrap(); + }), + queue_evts: Vec::new(), + } + } + + #[test] + fn test_parked_disk_update() { + let worker = WorkerHandle::spawn(Arc::new(vec![]), Vec::new(), "fc_test".into()).unwrap(); + + assert!(matches!( + worker.update_disk_image(String::new(), false), + Err(VirtioBlockError::WorkerControl(err)) if err.contains("worker is parked") + )); + + worker.finish(FlushMode::Drain); + } + + #[test] + fn test_parked_reset() { + let worker = WorkerHandle::spawn(Arc::new(vec![]), Vec::new(), "fc_test".into()).unwrap(); + + assert!(worker.reset().is_none()); + + worker.finish(FlushMode::Drain); + } + + #[test] + fn test_reset_disconnected() { + let worker = disconnected_handle(); + + assert!(worker.reset().is_none()); + + worker.finish(FlushMode::Drain); + } + + #[test] + fn test_disk_update_disconnected() { + let worker = disconnected_handle(); + + assert!(matches!( + worker.update_disk_image(String::new(), false), + Err(VirtioBlockError::WorkerControl(err)) + if err.contains("failed to send disk update") + )); + + worker.finish(FlushMode::Drain); + } + + #[test] + fn test_reset_no_response() { + let worker = response_disconnected_handle(); + + assert!(worker.reset().is_none()); + + worker.finish(FlushMode::Drain); + } + + #[test] + fn test_disk_update_no_response() { + let worker = response_disconnected_handle(); + + assert!(matches!( + worker.update_disk_image(String::new(), false), + Err(VirtioBlockError::WorkerControl(err)) + if err.contains("failed to receive disk update response") + )); + + worker.finish(FlushMode::Drain); + } +} diff --git a/tests/conftest.py b/tests/conftest.py index 200a8ac4f5d..7982414ecea 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -484,6 +484,12 @@ def io_engine(request): return request.param +@pytest.fixture(params=[False, True], ids=["inline", "threaded"]) +def threaded(request): + """Whether the block device uses a dedicated worker thread.""" + return request.param + + @pytest.fixture( params=[SnapshotType.DIFF, SnapshotType.DIFF_MINCORE, SnapshotType.FULL] ) diff --git a/tests/integration_tests/functional/test_drive_virtio.py b/tests/integration_tests/functional/test_drive_virtio.py index bc792ffba9e..45471c4649a 100644 --- a/tests/integration_tests/functional/test_drive_virtio.py +++ b/tests/integration_tests/functional/test_drive_virtio.py @@ -24,7 +24,7 @@ def partuuid_and_disk_path_tmpfs(rootfs, tmp_path): disk_path.unlink() -def test_rescan_file(uvm, io_engine): +def test_rescan_file(uvm, io_engine, threaded): """ Verify that rescan works with a file-backed virtio device. """ @@ -40,7 +40,7 @@ def test_rescan_file(uvm, io_engine): fs = drive_tools.FilesystemFile( os.path.join(test_microvm.fsfiles, "scratch"), size=block_size ) - test_microvm.add_drive("scratch", fs.path, io_engine=io_engine) + test_microvm.add_drive("scratch", fs.path, io_engine=io_engine, threaded=threaded) test_microvm.start() @@ -65,7 +65,7 @@ def test_rescan_file(uvm, io_engine): _check_block_size(test_microvm.ssh, "/dev/vdb", fs.size()) -def test_device_ordering(uvm, io_engine): +def test_device_ordering(uvm, io_engine, threaded): """ Verify device ordering. @@ -79,7 +79,7 @@ def test_device_ordering(uvm, io_engine): fs1 = drive_tools.FilesystemFile( os.path.join(test_microvm.fsfiles, "scratch1"), size=128 ) - test_microvm.add_drive("scratch1", fs1.path, io_engine=io_engine) + test_microvm.add_drive("scratch1", fs1.path, io_engine=io_engine, threaded=threaded) # Set up the microVM with 1 vCPUs, 256 MiB of RAM and a root file system # (this is the second block device added). @@ -90,7 +90,7 @@ def test_device_ordering(uvm, io_engine): fs2 = drive_tools.FilesystemFile( os.path.join(test_microvm.fsfiles, "scratch2"), size=512 ) - test_microvm.add_drive("scratch2", fs2.path, io_engine=io_engine) + test_microvm.add_drive("scratch2", fs2.path, io_engine=io_engine, threaded=threaded) test_microvm.start() @@ -112,7 +112,7 @@ def test_device_ordering(uvm, io_engine): _check_block_size(ssh_connection, "/dev/vdc", fs2.size()) -def test_rescan_dev(uvm, io_engine): +def test_rescan_dev(uvm, io_engine, threaded): """ Verify that rescan works with a device-backed virtio device. """ @@ -125,7 +125,7 @@ def test_rescan_dev(uvm, io_engine): # Add a scratch block device. fs1 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, "fs1")) - test_microvm.add_drive("scratch", fs1.path, io_engine=io_engine) + test_microvm.add_drive("scratch", fs1.path, io_engine=io_engine, threaded=threaded) test_microvm.start() @@ -152,7 +152,7 @@ def test_rescan_dev(uvm, io_engine): utils.check_output(["losetup", "--detach", loopback_device]) -def test_non_partuuid_boot(uvm, io_engine): +def test_non_partuuid_boot(uvm, io_engine, threaded): """ Test the output reported by blockdev when booting from /dev/vda. """ @@ -165,7 +165,9 @@ def test_non_partuuid_boot(uvm, io_engine): # Add another read-only block device. fs = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, "readonly")) - test_microvm.add_drive("scratch", fs.path, is_read_only=True, io_engine=io_engine) + test_microvm.add_drive( + "scratch", fs.path, is_read_only=True, io_engine=io_engine, threaded=threaded + ) test_microvm.start() @@ -252,7 +254,7 @@ def test_partuuid_update(uvm, io_engine): _check_drives(test_microvm, assert_dict, assert_dict.keys()) -def test_patch_drive(uvm, io_engine): +def test_patch_drive(uvm, io_engine, threaded): """ Test replacing the backing filesystem after guest boot works. """ @@ -264,7 +266,7 @@ def test_patch_drive(uvm, io_engine): test_microvm.add_net_iface() fs1 = drive_tools.FilesystemFile(os.path.join(test_microvm.fsfiles, "scratch")) - test_microvm.add_drive("scratch", fs1.path, io_engine=io_engine) + test_microvm.add_drive("scratch", fs1.path, io_engine=io_engine, threaded=threaded) test_microvm.start() @@ -290,7 +292,7 @@ def test_patch_drive(uvm, io_engine): assert lines[1].strip() == size_bytes_str -def test_no_flush(uvm, io_engine): +def test_no_flush(uvm, io_engine, threaded): """ Verify default block ignores flush. """ @@ -306,6 +308,7 @@ def test_no_flush(uvm, io_engine): test_microvm.rootfs_file, is_root_device=True, io_engine=io_engine, + threaded=threaded, ) test_microvm.start() @@ -326,7 +329,7 @@ def test_no_flush(uvm, io_engine): @pin_guest_kernel(GUEST_KERNEL_DEFAULT) @pin_rootfs_mode("rw") -def test_flush(uvm, io_engine): +def test_flush(uvm, io_engine, threaded): """ Verify block with flush actually flushes. """ @@ -342,6 +345,7 @@ def test_flush(uvm, io_engine): is_root_device=True, cache_type="Writeback", io_engine=io_engine, + threaded=threaded, ) test_microvm.start() @@ -388,7 +392,7 @@ def _check_mount(ssh_connection, dev_path): assert stderr == "" -def test_device_reset(uvm, io_engine): +def test_device_reset(uvm, io_engine, threaded): """ Test that virtio-block device reset works. """ @@ -398,7 +402,7 @@ def test_device_reset(uvm, io_engine): vm.add_net_iface() fs = drive_tools.FilesystemFile(os.path.join(vm.fsfiles, "scratch"), size=2) - vm.add_drive("scratch", fs.path, io_engine=io_engine) + vm.add_drive("scratch", fs.path, io_engine=io_engine, threaded=threaded) vm.start() # Verify the scratch drive is accessible. diff --git a/tests/integration_tests/functional/test_hotplug.py b/tests/integration_tests/functional/test_hotplug.py index 34c129d4276..38a66db9491 100644 --- a/tests/integration_tests/functional/test_hotplug.py +++ b/tests/integration_tests/functional/test_hotplug.py @@ -42,6 +42,7 @@ def test_hotplug_block(uvm_any): rate_limiter={ "ops": {"size": 100, "refill_time": 100}, }, + threaded=True, ) # Rescan PCI bus since no hotplug notification mechanism exists yet @@ -422,6 +423,7 @@ def test_hotplug_preserved_after_snapshot(uvm_any, microvm_factory): path_on_host=vm.create_jailed_resource(host_file.path), is_root_device=False, is_read_only=False, + threaded=True, ) vm.disks["block0"] = host_file.path diff --git a/tests/integration_tests/functional/test_snapshot_basic.py b/tests/integration_tests/functional/test_snapshot_basic.py index dd5efe61d68..1e43c37fbf0 100644 --- a/tests/integration_tests/functional/test_snapshot_basic.py +++ b/tests/integration_tests/functional/test_snapshot_basic.py @@ -192,7 +192,7 @@ def test_cycled_snapshot_restore( @pin_guest_kernel(GUEST_KERNEL_DEFAULT) -def test_patch_drive_snapshot(uvm_configured, microvm_factory): +def test_patch_drive_snapshot(uvm_configured, microvm_factory, io_engine): """ Test that a patched drive is correctly used by guests loaded from snapshot. """ @@ -206,7 +206,12 @@ def test_patch_drive_snapshot(uvm_configured, microvm_factory): root = Path(basevm.path) scratch_path1 = str(root / "scratch1") scratch_disk1 = drive_tools.FilesystemFile(scratch_path1, size=128) - basevm.add_drive("scratch", scratch_disk1.path) + basevm.add_drive( + "scratch", + scratch_disk1.path, + io_engine=io_engine, + threaded=True, + ) basevm.start() # Update drive to have another backing file, double in size. diff --git a/tests/integration_tests/performance/test_drive_rate_limiter.py b/tests/integration_tests/performance/test_drive_rate_limiter.py index e2aae8c4342..a70032d9b93 100644 --- a/tests/integration_tests/performance/test_drive_rate_limiter.py +++ b/tests/integration_tests/performance/test_drive_rate_limiter.py @@ -56,6 +56,7 @@ def test_patch_drive_limiter(uvm): "bandwidth": {"size": 10 * MB, "refill_time": 100}, "ops": {"size": 100, "refill_time": 100}, }, + threaded=True, ) test_microvm.start() diff --git a/tests/integration_tests/security/test_custom_seccomp.py b/tests/integration_tests/security/test_custom_seccomp.py index 9e068a46d6f..eb08789a4bf 100644 --- a/tests/integration_tests/security/test_custom_seccomp.py +++ b/tests/integration_tests/security/test_custom_seccomp.py @@ -6,6 +6,8 @@ import time from pathlib import Path +import pytest + from framework import utils from framework.artifacts import GUEST_KERNEL_DEFAULT, pin_guest_kernel @@ -33,6 +35,31 @@ def test_allow_all(uvm, seccompiler): utils.assert_seccomp_level(test_microvm.firecracker_pid, "2") +@pin_guest_kernel(GUEST_KERNEL_DEFAULT) +def test_missing_block_worker_filter(uvm, seccompiler): + """Reject a threaded block device when its seccomp filter is missing.""" + seccomp_filter = { + thread: {"default_action": "allow", "filter_action": "trap", "filter": []} + for thread in ["vmm", "api", "vcpu"] + } + + bpf_path = seccompiler.compile(seccomp_filter) + test_microvm = uvm + install_filter(test_microvm, bpf_path) + test_microvm.spawn() + test_microvm.basic_config(add_root_device=False) + test_microvm.add_drive( + drive_id="rootfs", + path_on_host=test_microvm.rootfs_file, + is_root_device=True, + is_read_only=test_microvm.rootfs_file.suffix == ".squashfs", + threaded=True, + ) + + with pytest.raises(RuntimeError, match="Missing block worker seccomp filter"): + test_microvm.start() + + @pin_guest_kernel(GUEST_KERNEL_DEFAULT) def test_working_filter(uvm, seccompiler): """Test --seccomp-filter, rejecting some dangerous syscalls.""" @@ -43,7 +70,7 @@ def test_working_filter(uvm, seccompiler): "filter_action": "kill_process", "filter": [{"syscall": "clone"}, {"syscall": "execve"}], } - for thread in ["vmm", "api", "vcpu"] + for thread in ["vmm", "api", "vcpu", "blk_worker"] } bpf_path = seccompiler.compile(seccomp_filter) @@ -69,6 +96,11 @@ def test_failing_filter(uvm, seccompiler): "filter_action": "trap", "filter": [{"syscall": "ioctl"}], }, + "blk_worker": { + "default_action": "allow", + "filter_action": "trap", + "filter": [], + }, } bpf_path = seccompiler.compile(seccomp_filter)