Skip to content

Commit d7f610f

Browse files
committed
test(vhost-user-blok): added unit tests
Made `VhostUserBlock` generic on `VhostUserHandleBackend`. Added unit tests for `VhostUserBlock` creation and feature negotiation. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent dc5c11c commit d7f610f

File tree

1 file changed

+316
-15
lines changed
  • src/vmm/src/devices/virtio/vhost_user_block

1 file changed

+316
-15
lines changed

src/vmm/src/devices/virtio/vhost_user_block/device.rs

Lines changed: 316 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use log::error;
1313
use utils::eventfd::EventFd;
1414
use utils::u64_to_usize;
1515
use vhost::vhost_user::message::*;
16-
use vhost::vhost_user::VhostUserMaster;
16+
use vhost::vhost_user::Master;
1717

1818
use super::{VhostUserBlockError, NUM_QUEUES, QUEUE_SIZE};
1919
use crate::devices::virtio::block_common::CacheType;
@@ -23,7 +23,8 @@ use crate::devices::virtio::gen::virtio_blk::{
2323
};
2424
use crate::devices::virtio::gen::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
2525
use crate::devices::virtio::queue::Queue;
26-
use crate::devices::virtio::vhost_user::VhostUserHandle;
26+
use crate::devices::virtio::vhost_user::VhostUserHandleBackend;
27+
use crate::devices::virtio::vhost_user::VhostUserHandleImpl;
2728
use crate::devices::virtio::{ActivateError, TYPE_BLOCK};
2829
use crate::logger::{IncMetric, METRICS};
2930
use crate::vmm_config::drive::BlockDeviceConfig;
@@ -32,6 +33,15 @@ use crate::vstate::memory::GuestMemoryMmap;
3233
/// Block device config space size in bytes.
3334
const BLOCK_CONFIG_SPACE_SIZE: u32 = 60;
3435

36+
const AVAILABLE_FEATURES: u64 = (1 << VIRTIO_F_VERSION_1)
37+
| (1 << VIRTIO_RING_F_EVENT_IDX)
38+
// vhost-user specific bit. Not defined in standart virtio spec.
39+
// Specifies ability of frontend to negotiate protocol features.
40+
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
41+
// We always try to negotiate readonly with the backend.
42+
// If the backend is configured as readonly, we will accept it.
43+
| (1 << VIRTIO_BLK_F_RO);
44+
3545
/// Use this structure to set up the Block Device before booting the kernel.
3646
#[derive(Debug, PartialEq, Eq)]
3747
pub struct VhostUserBlockConfig {
@@ -93,9 +103,10 @@ impl From<VhostUserBlockConfig> for BlockDeviceConfig {
93103
}
94104
}
95105

106+
pub type VhostUserBlock = VhostUserBlockImpl<Master>;
107+
96108
/// vhost-user block device.
97-
#[derive(Debug)]
98-
pub struct VhostUserBlock {
109+
pub struct VhostUserBlockImpl<T: VhostUserHandleBackend> {
99110
// Virtio fields.
100111
pub avail_features: u64,
101112
pub acked_features: u64,
@@ -116,28 +127,47 @@ pub struct VhostUserBlock {
116127
pub read_only: bool,
117128

118129
// Vhost user protocol handle
119-
pub vu_handle: VhostUserHandle,
130+
pub vu_handle: VhostUserHandleImpl<T>,
120131
pub vu_acked_protocol_features: u64,
121132
}
122133

123-
impl VhostUserBlock {
134+
// Need custom implementation because otherwise `Debug` is required for `vhost::Master`
135+
impl<T: VhostUserHandleBackend> std::fmt::Debug for VhostUserBlockImpl<T> {
136+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137+
f.debug_struct("VhostUserBlockImpl")
138+
.field("avail_features", &self.avail_features)
139+
.field("acked_features", &self.acked_features)
140+
.field("config_space", &self.config_space)
141+
.field("activate_evt", &self.activate_evt)
142+
.field("queues", &self.queues)
143+
.field("queue_evts", &self.queue_evts)
144+
.field("device_state", &self.device_state)
145+
.field("irq_trigger", &self.irq_trigger)
146+
.field("id", &self.id)
147+
.field("partuuid", &self.partuuid)
148+
.field("cache_type", &self.cache_type)
149+
.field("root_device", &self.root_device)
150+
.field("read_only", &self.read_only)
151+
.field("vu_handle", &self.vu_handle)
152+
.field(
153+
"vu_acked_protocol_features",
154+
&self.vu_acked_protocol_features,
155+
)
156+
.finish()
157+
}
158+
}
159+
160+
impl<T: VhostUserHandleBackend> VhostUserBlockImpl<T> {
124161
pub fn new(config: VhostUserBlockConfig) -> Result<Self, VhostUserBlockError> {
125-
let mut requested_features = (1 << VIRTIO_F_VERSION_1)
126-
| (1 << VIRTIO_RING_F_EVENT_IDX)
127-
// vhost-user specific bit. Not defined in standart virtio spec.
128-
// Specifies ability of frontend to negotiate protocol features.
129-
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
130-
// We always try to negotiate readonly with the backend.
131-
// If the backend is configured as readonly, we will accept it.
132-
| (1 << VIRTIO_BLK_F_RO);
162+
let mut requested_features = AVAILABLE_FEATURES;
133163

134164
if config.cache_type == CacheType::Writeback {
135165
requested_features |= 1 << VIRTIO_BLK_F_FLUSH;
136166
}
137167

138168
let requested_protocol_features = VhostUserProtocolFeatures::CONFIG;
139169

140-
let mut vu_handle = VhostUserHandle::new(&config.socket, NUM_QUEUES)
170+
let mut vu_handle = VhostUserHandleImpl::<T>::new(&config.socket, NUM_QUEUES)
141171
.map_err(VhostUserBlockError::VhostUser)?;
142172
let (acked_features, acked_protocol_features) = vu_handle
143173
.negotiate_features(requested_features, requested_protocol_features)
@@ -303,3 +333,274 @@ impl VirtioDevice for VhostUserBlock {
303333
false
304334
}
305335
}
336+
337+
#[cfg(test)]
338+
mod tests {
339+
#![allow(clippy::undocumented_unsafe_blocks)]
340+
#![allow(clippy::cast_possible_truncation)]
341+
342+
use super::*;
343+
344+
use std::os::unix::net::UnixStream;
345+
346+
#[test]
347+
fn test_new_no_features() {
348+
struct MockMaster {
349+
sock: UnixStream,
350+
max_queue_num: u64,
351+
is_owner: std::cell::UnsafeCell<bool>,
352+
features: u64,
353+
protocol_features: VhostUserProtocolFeatures,
354+
hdr_flags: std::cell::UnsafeCell<VhostUserHeaderFlag>,
355+
}
356+
357+
impl VhostUserHandleBackend for MockMaster {
358+
fn from_stream(sock: UnixStream, max_queue_num: u64) -> Self {
359+
Self {
360+
sock,
361+
max_queue_num,
362+
is_owner: std::cell::UnsafeCell::new(false),
363+
features: 0,
364+
protocol_features: VhostUserProtocolFeatures::empty(),
365+
hdr_flags: std::cell::UnsafeCell::new(VhostUserHeaderFlag::empty()),
366+
}
367+
}
368+
369+
fn set_owner(&self) -> Result<(), vhost::Error> {
370+
unsafe { *self.is_owner.get() = true };
371+
Ok(())
372+
}
373+
374+
fn set_hdr_flags(&self, flags: VhostUserHeaderFlag) {
375+
unsafe { *self.hdr_flags.get() = flags };
376+
}
377+
378+
fn get_features(&self) -> Result<u64, vhost::Error> {
379+
Ok(self.features)
380+
}
381+
382+
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures, vhost::Error> {
383+
Ok(self.protocol_features)
384+
}
385+
386+
fn set_protocol_features(
387+
&mut self,
388+
features: VhostUserProtocolFeatures,
389+
) -> Result<(), vhost::Error> {
390+
self.protocol_features = features;
391+
Ok(())
392+
}
393+
}
394+
395+
let tmp_dir = utils::tempdir::TempDir::new().unwrap();
396+
let tmp_dir_path_str = tmp_dir.as_path().to_str().unwrap();
397+
let tmp_socket_path = format!("{tmp_dir_path_str}/tmp_socket");
398+
399+
unsafe {
400+
let socketfd = libc::socket(libc::AF_UNIX, libc::SOCK_STREAM, 0);
401+
if socketfd < 0 {
402+
panic!("Cannot create socket");
403+
}
404+
let mut socket_addr = libc::sockaddr_un {
405+
sun_family: libc::AF_UNIX as u16,
406+
sun_path: [0; 108],
407+
};
408+
409+
std::ptr::copy::<i8>(
410+
tmp_socket_path.as_ptr().cast(),
411+
socket_addr.sun_path.as_mut_ptr(),
412+
tmp_socket_path.as_bytes().len(),
413+
);
414+
415+
let bind = libc::bind(
416+
socketfd,
417+
(&socket_addr as *const libc::sockaddr_un).cast(),
418+
std::mem::size_of::<libc::sockaddr_un>() as u32,
419+
);
420+
if bind < 0 {
421+
panic!("Cannot bind socket");
422+
}
423+
424+
let listen = libc::listen(socketfd, 1);
425+
if listen < 0 {
426+
panic!("Cannot listen on socket");
427+
}
428+
}
429+
430+
let vhost_block_config = VhostUserBlockConfig {
431+
drive_id: "test_drive".to_string(),
432+
partuuid: None,
433+
is_root_device: false,
434+
cache_type: CacheType::Unsafe,
435+
socket: tmp_socket_path.clone(),
436+
};
437+
let vhost_block = VhostUserBlockImpl::<MockMaster>::new(vhost_block_config).unwrap();
438+
439+
assert_eq!(
440+
vhost_block
441+
.vu_handle
442+
.vu
443+
.sock
444+
.peer_addr()
445+
.unwrap()
446+
.as_pathname()
447+
.unwrap()
448+
.to_str()
449+
.unwrap(),
450+
&tmp_socket_path,
451+
);
452+
assert_eq!(vhost_block.vu_handle.vu.max_queue_num, NUM_QUEUES);
453+
assert!(unsafe { *vhost_block.vu_handle.vu.is_owner.get() });
454+
assert_eq!(vhost_block.avail_features, 0);
455+
assert_eq!(vhost_block.acked_features, 0);
456+
assert_eq!(vhost_block.vu_acked_protocol_features, 0);
457+
assert_eq!(
458+
unsafe { *vhost_block.vu_handle.vu.hdr_flags.get() },
459+
VhostUserHeaderFlag::empty()
460+
);
461+
assert!(!vhost_block.root_device);
462+
assert!(!vhost_block.read_only);
463+
assert_eq!(vhost_block.config_space, Vec::<u8>::new());
464+
}
465+
466+
#[test]
467+
fn test_new_all_features() {
468+
struct MockMaster {
469+
sock: UnixStream,
470+
max_queue_num: u64,
471+
is_owner: std::cell::UnsafeCell<bool>,
472+
features: u64,
473+
protocol_features: VhostUserProtocolFeatures,
474+
hdr_flags: std::cell::UnsafeCell<VhostUserHeaderFlag>,
475+
}
476+
477+
impl VhostUserHandleBackend for MockMaster {
478+
fn from_stream(sock: UnixStream, max_queue_num: u64) -> Self {
479+
Self {
480+
sock,
481+
max_queue_num,
482+
is_owner: std::cell::UnsafeCell::new(false),
483+
features: AVAILABLE_FEATURES | (1 << VIRTIO_BLK_F_FLUSH),
484+
485+
protocol_features: VhostUserProtocolFeatures::all(),
486+
hdr_flags: std::cell::UnsafeCell::new(VhostUserHeaderFlag::empty()),
487+
}
488+
}
489+
490+
fn set_owner(&self) -> Result<(), vhost::Error> {
491+
unsafe { *self.is_owner.get() = true };
492+
Ok(())
493+
}
494+
495+
fn set_hdr_flags(&self, flags: VhostUserHeaderFlag) {
496+
unsafe { *self.hdr_flags.get() = flags };
497+
}
498+
499+
fn get_features(&self) -> Result<u64, vhost::Error> {
500+
Ok(self.features)
501+
}
502+
503+
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures, vhost::Error> {
504+
Ok(self.protocol_features)
505+
}
506+
507+
fn set_protocol_features(
508+
&mut self,
509+
features: VhostUserProtocolFeatures,
510+
) -> Result<(), vhost::Error> {
511+
self.protocol_features = features;
512+
Ok(())
513+
}
514+
515+
fn get_config(
516+
&mut self,
517+
_offset: u32,
518+
_size: u32,
519+
_flags: VhostUserConfigFlags,
520+
_buf: &[u8],
521+
) -> Result<(VhostUserConfig, VhostUserConfigPayload), vhost::Error> {
522+
Ok((VhostUserConfig::default(), vec![0x69, 0x69, 0x69]))
523+
}
524+
}
525+
526+
let tmp_dir = utils::tempdir::TempDir::new().unwrap();
527+
let tmp_dir_path_str = tmp_dir.as_path().to_str().unwrap();
528+
let tmp_socket_path = format!("{tmp_dir_path_str}/tmp_socket");
529+
530+
unsafe {
531+
let socketfd = libc::socket(libc::AF_UNIX, libc::SOCK_STREAM, 0);
532+
if socketfd < 0 {
533+
panic!("Cannot create socket");
534+
}
535+
let mut socket_addr = libc::sockaddr_un {
536+
sun_family: libc::AF_UNIX as u16,
537+
sun_path: [0; 108],
538+
};
539+
540+
std::ptr::copy::<i8>(
541+
tmp_socket_path.as_ptr().cast(),
542+
socket_addr.sun_path.as_mut_ptr(),
543+
tmp_socket_path.as_bytes().len(),
544+
);
545+
546+
let bind = libc::bind(
547+
socketfd,
548+
(&socket_addr as *const libc::sockaddr_un).cast(),
549+
std::mem::size_of::<libc::sockaddr_un>() as u32,
550+
);
551+
if bind < 0 {
552+
panic!("Cannot bind socket");
553+
}
554+
555+
let listen = libc::listen(socketfd, 1);
556+
if listen < 0 {
557+
panic!("Cannot listen on socket");
558+
}
559+
}
560+
561+
let vhost_block_config = VhostUserBlockConfig {
562+
drive_id: "test_drive".to_string(),
563+
partuuid: None,
564+
is_root_device: false,
565+
cache_type: CacheType::Writeback,
566+
socket: tmp_socket_path.clone(),
567+
};
568+
let vhost_block = VhostUserBlockImpl::<MockMaster>::new(vhost_block_config).unwrap();
569+
570+
assert_eq!(
571+
vhost_block
572+
.vu_handle
573+
.vu
574+
.sock
575+
.peer_addr()
576+
.unwrap()
577+
.as_pathname()
578+
.unwrap()
579+
.to_str()
580+
.unwrap(),
581+
&tmp_socket_path,
582+
);
583+
assert_eq!(vhost_block.vu_handle.vu.max_queue_num, NUM_QUEUES);
584+
assert!(unsafe { *vhost_block.vu_handle.vu.is_owner.get() });
585+
586+
assert_eq!(
587+
vhost_block.avail_features,
588+
AVAILABLE_FEATURES | (1 << VIRTIO_BLK_F_FLUSH)
589+
);
590+
assert_eq!(
591+
vhost_block.acked_features,
592+
VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
593+
);
594+
assert_eq!(
595+
vhost_block.vu_acked_protocol_features,
596+
VhostUserProtocolFeatures::CONFIG.bits()
597+
);
598+
assert_eq!(
599+
unsafe { *vhost_block.vu_handle.vu.hdr_flags.get() },
600+
VhostUserHeaderFlag::empty()
601+
);
602+
assert!(!vhost_block.root_device);
603+
assert!(!vhost_block.read_only);
604+
assert_eq!(vhost_block.config_space, vec![0x69, 0x69, 0x69]);
605+
}
606+
}

0 commit comments

Comments
 (0)