Skip to content

Commit f5ac6ca

Browse files
committed
test: Add unit tests for hint/reporting
Add integration tests for free page hinting and reporting. Asserting the features are enabled correctly. Testing the config space updates triggered by hinting are being set as expected. Signed-off-by: Jack Thomson <[email protected]>
1 parent 6f09bdf commit f5ac6ca

File tree

3 files changed

+334
-30
lines changed

3 files changed

+334
-30
lines changed

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

Lines changed: 284 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,19 @@ fn default_ack_on_stop() -> bool {
8585
}
8686

8787
/// Command recieved from the API to start a hinting run
88-
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize)]
88+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize)]
8989
pub struct StartHintingCmd {
9090
/// If we should automatically acknowledge end of the run after stop.
9191
#[serde(default = "default_ack_on_stop")]
9292
pub acknowledge_on_stop: bool,
9393
}
9494

95+
impl Default for StartHintingCmd {
96+
fn default() -> Self {
97+
Self { acknowledge_on_stop: true }
98+
}
99+
}
100+
95101
/// Returned to the API for get hinting status
96102
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize)]
97103
pub struct HintingStatus {
@@ -936,18 +942,51 @@ impl VirtioDevice for Balloon {
936942

937943
#[cfg(test)]
938944
pub(crate) mod tests {
945+
use itertools::iproduct;
946+
939947
use super::super::BALLOON_CONFIG_SPACE_SIZE;
940948
use super::*;
949+
use crate::arch::host_page_size;
941950
use crate::check_metric_after_block;
942951
use crate::devices::virtio::balloon::report_balloon_event_fail;
943952
use crate::devices::virtio::balloon::test_utils::{
944953
check_request_completion, invoke_handler_for_queue_event, set_request,
945954
};
946955
use crate::devices::virtio::queue::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE};
947-
use crate::devices::virtio::test_utils::{VirtQueue, default_interrupt, default_mem};
956+
use crate::devices::virtio::test_utils::{
957+
VirtQueue, default_interrupt, default_mem
958+
};
959+
use crate::devices::virtio::test_utils::test::{
960+
VirtioTestDevice, VirtioTestHelper, create_virtio_mem,
961+
};
948962
use crate::test_utils::single_region_mem;
963+
use crate::utils::align_up;
949964
use crate::vstate::memory::GuestAddress;
950965

966+
impl VirtioTestDevice for Balloon {
967+
fn set_queues(&mut self, queues: Vec<Queue>) {
968+
self.queues = queues;
969+
}
970+
971+
fn num_queues(&self) -> usize {
972+
let mut idx = STATS_INDEX;
973+
974+
if self.stats_polling_interval_s > 0 {
975+
idx += 1;
976+
}
977+
978+
if self.free_page_hinting() {
979+
idx += 1;
980+
}
981+
982+
if self.free_page_reporting() {
983+
idx += 1;
984+
}
985+
986+
idx
987+
}
988+
}
989+
951990
impl Balloon {
952991
pub(crate) fn set_queue(&mut self, idx: usize, q: Queue) {
953992
self.queues[idx] = q;
@@ -1030,31 +1069,37 @@ pub(crate) mod tests {
10301069
#[test]
10311070
fn test_virtio_features() {
10321071
// Test all feature combinations.
1033-
for deflate_on_oom in [true, false].iter() {
1034-
for stats_interval in [0, 1].iter() {
1035-
let mut balloon =
1036-
Balloon::new(0, *deflate_on_oom, *stats_interval, false, false).unwrap();
1037-
assert_eq!(balloon.device_type(), VIRTIO_ID_BALLOON);
1038-
1039-
let features: u64 = (1u64 << VIRTIO_F_VERSION_1)
1040-
| (u64::from(*deflate_on_oom) << VIRTIO_BALLOON_F_DEFLATE_ON_OOM)
1041-
| ((u64::from(*stats_interval)) << VIRTIO_BALLOON_F_STATS_VQ);
1042-
1043-
assert_eq!(
1044-
balloon.avail_features_by_page(0),
1045-
(features & 0xFFFFFFFF) as u32
1046-
);
1047-
assert_eq!(balloon.avail_features_by_page(1), (features >> 32) as u32);
1048-
for i in 2..10 {
1049-
assert_eq!(balloon.avail_features_by_page(i), 0u32);
1050-
}
1072+
let combinations = iproduct!(
1073+
&[true, false], // Reporitng
1074+
&[true, false], // Hinting
1075+
&[true, false], // Deflate
1076+
&[0, 1] // Interval
1077+
);
10511078

1052-
for i in 0..10 {
1053-
balloon.ack_features_by_page(i, u32::MAX);
1054-
}
1055-
// Only present features should be acknowledged.
1056-
assert_eq!(balloon.acked_features, features);
1079+
for (reporting, hinting, deflate_on_oom, stats_interval) in combinations {
1080+
let mut balloon = Balloon::new(0, *deflate_on_oom, *stats_interval, *hinting, *reporting).unwrap();
1081+
assert_eq!(balloon.device_type(), VIRTIO_ID_BALLOON);
1082+
1083+
let features: u64 = (1u64 << VIRTIO_F_VERSION_1)
1084+
| (u64::from(*deflate_on_oom) << VIRTIO_BALLOON_F_DEFLATE_ON_OOM)
1085+
| ((u64::from(*reporting)) << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING)
1086+
| ((u64::from(*hinting)) << VIRTIO_BALLOON_F_FREE_PAGE_HINTING)
1087+
| ((u64::from(*stats_interval)) << VIRTIO_BALLOON_F_STATS_VQ);
1088+
1089+
assert_eq!(
1090+
balloon.avail_features_by_page(0),
1091+
(features & 0xFFFFFFFF) as u32
1092+
);
1093+
assert_eq!(balloon.avail_features_by_page(1), (features >> 32) as u32);
1094+
for i in 2..10 {
1095+
assert_eq!(balloon.avail_features_by_page(i), 0u32);
1096+
}
1097+
1098+
for i in 0..10 {
1099+
balloon.ack_features_by_page(i, u32::MAX);
10571100
}
1101+
// Only present features should be acknowledged.
1102+
assert_eq!(balloon.acked_features, features);
10581103
}
10591104
}
10601105

@@ -1125,6 +1170,58 @@ pub(crate) mod tests {
11251170
assert_eq!(actual_config_space, expected_config_space);
11261171
}
11271172

1173+
#[test]
1174+
fn test_free_page_hinting_config() {
1175+
let mut balloon = Balloon::new(0, true, 0, true, false).unwrap();
1176+
let mem = default_mem();
1177+
let interrupt = default_interrupt();
1178+
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
1179+
balloon.set_queue(INFLATE_INDEX, infq.create_queue());
1180+
balloon.set_queue(DEFLATE_INDEX, infq.create_queue());
1181+
balloon.set_queue(balloon.free_page_hinting_idx(), infq.create_queue());
1182+
balloon.activate(mem.clone(), interrupt).unwrap();
1183+
1184+
let expected_config_space: [u8; BALLOON_CONFIG_SPACE_SIZE] = [
1185+
0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1186+
];
1187+
balloon.write_config(0, &expected_config_space);
1188+
1189+
let mut actual_config_space = [0u8; BALLOON_CONFIG_SPACE_SIZE];
1190+
balloon.read_config(0, &mut actual_config_space);
1191+
assert_eq!(actual_config_space, expected_config_space);
1192+
1193+
// We expect the cmd_id to be set to 2 now
1194+
balloon.start_hinting(Default::default()).unwrap();
1195+
1196+
let expected_config_space: [u8; BALLOON_CONFIG_SPACE_SIZE] = [
1197+
0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
1198+
];
1199+
let mut actual_config_space = [0u8; BALLOON_CONFIG_SPACE_SIZE];
1200+
balloon.read_config(0, &mut actual_config_space);
1201+
assert_eq!(actual_config_space, expected_config_space);
1202+
1203+
// We expect the cmd_id to be set to 1
1204+
balloon.stop_hinting().unwrap();
1205+
1206+
let expected_config_space: [u8; BALLOON_CONFIG_SPACE_SIZE] = [
1207+
0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1208+
];
1209+
let mut actual_config_space = [0u8; BALLOON_CONFIG_SPACE_SIZE];
1210+
balloon.read_config(0, &mut actual_config_space);
1211+
assert_eq!(actual_config_space, expected_config_space);
1212+
1213+
// We expect the cmd_id to be bumped up to 3 now
1214+
balloon.start_hinting(Default::default()).unwrap();
1215+
1216+
let expected_config_space: [u8; BALLOON_CONFIG_SPACE_SIZE] = [
1217+
0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
1218+
];
1219+
let mut actual_config_space = [0u8; BALLOON_CONFIG_SPACE_SIZE];
1220+
balloon.read_config(0, &mut actual_config_space);
1221+
assert_eq!(actual_config_space, expected_config_space);
1222+
1223+
}
1224+
11281225
#[test]
11291226
fn test_invalid_request() {
11301227
let mut balloon = Balloon::new(0, true, 0, false, false).unwrap();
@@ -1400,16 +1497,174 @@ pub(crate) mod tests {
14001497
}
14011498
}
14021499

1500+
#[test]
1501+
fn test_process_reporting() {
1502+
let mem = create_virtio_mem();
1503+
let mut th = VirtioTestHelper::<Balloon>::new(&mem,
1504+
Balloon::new(0, true, 0, false, true).unwrap());
1505+
1506+
th.activate_device(&mem);
1507+
1508+
let page_size = host_page_size() as u64;
1509+
1510+
// This has to be u32 for the scatter gather
1511+
#[allow(clippy::cast_possible_truncation)]
1512+
let page_size_chain = page_size as u32;
1513+
let reporting_idx = th.device().free_page_reporting_idx();
1514+
1515+
let safe_addr = align_up(th.data_address(), page_size);
1516+
1517+
th.add_scatter_gather(reporting_idx, 0, &[(0, safe_addr, page_size_chain, 0)]);
1518+
check_metric_after_block!(
1519+
METRICS.free_page_report_freed,
1520+
page_size,
1521+
th.device().process_free_page_reporting_queue().unwrap()
1522+
);
1523+
1524+
// Test with multiple items
1525+
th.add_scatter_gather(reporting_idx, 0, &[
1526+
(0, safe_addr, page_size_chain, 0),
1527+
(1, safe_addr + page_size, page_size_chain, 0),
1528+
(2, safe_addr + (page_size * 2), page_size_chain, 0),
1529+
]);
1530+
1531+
check_metric_after_block!(
1532+
METRICS.free_page_report_freed,
1533+
page_size * 3,
1534+
th.device().process_free_page_reporting_queue().unwrap()
1535+
);
1536+
1537+
// Test with unaligned length
1538+
th.add_scatter_gather(reporting_idx, 0, &[
1539+
(1, safe_addr + 1, page_size_chain, 0),
1540+
]);
1541+
1542+
check_metric_after_block!(
1543+
METRICS.free_page_report_fails,
1544+
1,
1545+
th.device().process_free_page_reporting_queue().unwrap()
1546+
);
1547+
}
1548+
1549+
#[test]
1550+
fn test_process_hinting() {
1551+
let mem = create_virtio_mem();
1552+
let mut th = VirtioTestHelper::<Balloon>::new(&mem,
1553+
Balloon::new(0, true, 0, true, false).unwrap());
1554+
1555+
th.activate_device(&mem);
1556+
1557+
let page_size = host_page_size() as u64;
1558+
let hinting_idx = th.device().free_page_hinting_idx();
1559+
// This has to be u32 for the scatter gather
1560+
#[allow(clippy::cast_possible_truncation)]
1561+
let page_size_chain = page_size as u32;
1562+
let safe_addr = align_up(th.data_address(), page_size);
1563+
1564+
// Test the good case
1565+
th.device().start_hinting(Default::default()).unwrap();
1566+
1567+
let mut host_cmd = th.device().get_hinting_status().unwrap().host_cmd;
1568+
println!("We got host cmd {host_cmd}");
1569+
1570+
macro_rules! test_hinting {
1571+
($cmd:expr, $expected:expr) => {
1572+
mem.write_obj($cmd as u32, GuestAddress::new(safe_addr)).unwrap();
1573+
th.add_scatter_gather(hinting_idx, 0, &[
1574+
(0, safe_addr, 4, 0),
1575+
(1, safe_addr + page_size, page_size_chain, 0),
1576+
]);
1577+
1578+
check_metric_after_block!(
1579+
METRICS.free_page_hint_freed,
1580+
$expected,
1581+
th.device().process_free_page_hinting_queue().unwrap()
1582+
);
1583+
};
1584+
1585+
($expected:expr) => {
1586+
th.add_scatter_gather(hinting_idx, 0, &[
1587+
(0, safe_addr + page_size, page_size_chain, 0),
1588+
]);
1589+
1590+
check_metric_after_block!(
1591+
METRICS.free_page_hint_freed,
1592+
$expected,
1593+
th.device().process_free_page_hinting_queue().unwrap()
1594+
);
1595+
};
1596+
}
1597+
1598+
// Ack the start of the hinting run and send a single page
1599+
test_hinting!(host_cmd, page_size);
1600+
1601+
// Report pages for an invalid cmd
1602+
test_hinting!(host_cmd + 1, 0);
1603+
1604+
// If correct cmd is again used continue again
1605+
test_hinting!(host_cmd, page_size);
1606+
1607+
// Trigger another hinting run this will bump the cmd id
1608+
// so we should ignore any inflight requests
1609+
th.device().start_hinting(Default::default()).unwrap();
1610+
test_hinting!(0);
1611+
1612+
// Update to our new host cmd and check this now works
1613+
host_cmd = th.device().get_hinting_status().unwrap().host_cmd;
1614+
test_hinting!(host_cmd, page_size);
1615+
test_hinting!(page_size);
1616+
1617+
// Simulate the driver finishing a run. Any reported values after
1618+
// should be ignored
1619+
test_hinting!(FREE_PAGE_HINT_STOP, 0);
1620+
test_hinting!(0);
1621+
1622+
// As we had auto ack on finish the host cmd should be set to done
1623+
host_cmd = th.device().get_hinting_status().unwrap().host_cmd;
1624+
assert_eq!(host_cmd, FREE_PAGE_HINT_DONE);
1625+
1626+
// Test no ack on stop behaviour
1627+
th.device().start_hinting(StartHintingCmd { acknowledge_on_stop: false}).unwrap();
1628+
host_cmd = th.device().get_hinting_status().unwrap().host_cmd;
1629+
test_hinting!(host_cmd, page_size);
1630+
test_hinting!(page_size);
1631+
test_hinting!(FREE_PAGE_HINT_STOP, 0);
1632+
let new_host_cmd = th.device().get_hinting_status().unwrap().host_cmd;
1633+
assert_eq!(host_cmd, new_host_cmd);
1634+
1635+
// Test misaligned writes report as an error
1636+
th.device().start_hinting(Default::default()).unwrap();
1637+
host_cmd = th.device().get_hinting_status().unwrap().host_cmd;
1638+
test_hinting!(host_cmd, page_size);
1639+
test_hinting!(page_size);
1640+
1641+
th.add_scatter_gather(hinting_idx, 0, &[
1642+
(0, safe_addr + page_size + 1, page_size_chain, 0),
1643+
]);
1644+
1645+
check_metric_after_block!(
1646+
METRICS.free_page_hint_fails,
1647+
1,
1648+
th.device().process_free_page_hinting_queue().unwrap()
1649+
);
1650+
}
1651+
14031652
#[test]
14041653
fn test_process_balloon_queues() {
1405-
let mut balloon = Balloon::new(0x10, true, 0, false, false).unwrap();
1654+
let mut balloon = Balloon::new(0x10, true, 0, true, true).unwrap();
14061655
let mem = default_mem();
14071656
let interrupt = default_interrupt();
14081657
let infq = VirtQueue::new(GuestAddress(0), &mem, 16);
14091658
let defq = VirtQueue::new(GuestAddress(0), &mem, 16);
1659+
let hintq = VirtQueue::new(GuestAddress(0), &mem, 16);
1660+
let reportq = VirtQueue::new(GuestAddress(0), &mem, 16);
14101661

14111662
balloon.set_queue(INFLATE_INDEX, infq.create_queue());
14121663
balloon.set_queue(DEFLATE_INDEX, defq.create_queue());
1664+
balloon.set_queue(balloon.free_page_hinting_idx(),
1665+
hintq.create_queue());
1666+
balloon.set_queue(balloon.free_page_reporting_idx(),
1667+
reportq.create_queue());
14131668

14141669
balloon.activate(mem, interrupt).unwrap();
14151670
balloon.process_virtio_queues().unwrap();
@@ -1451,6 +1706,9 @@ pub(crate) mod tests {
14511706
let mut balloon = Balloon::new(0, true, 0, false, false).unwrap();
14521707
// Assert that we can't update an inactive device.
14531708
balloon.update_size(1).unwrap_err();
1709+
balloon.start_hinting(Default::default()).unwrap_err();
1710+
balloon.get_hinting_status().unwrap_err();
1711+
balloon.stop_hinting().unwrap_err();
14541712
}
14551713

14561714
#[test]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ mod tests {
339339
self.queues = queues;
340340
}
341341

342-
fn num_queues() -> usize {
342+
fn num_queues(&self) -> usize {
343343
RNG_NUM_QUEUES
344344
}
345345
}

0 commit comments

Comments
 (0)