-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nvme_driver: test: hardening the save_restore testing for the nvme driver to verify keepalive functionality #815
base: main
Are you sure you want to change the base?
Changes from 18 commits
e117566
c640a00
d7ab79a
5c16819
63589d6
d803a55
c8b6b3d
cd92495
2d616b5
1396811
1da4fbc
ccb7c5f
5e4c82c
58cdec0
c5f7d49
cb72ac8
6e2bd10
76f1a72
47079d6
4c5974c
0d1423b
536ceed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,6 +106,17 @@ impl SubmissionQueue { | |
mem, | ||
}) | ||
} | ||
|
||
/// Given the saved state, checks the state of the submission queue. Does not verify memory. | ||
// TODO: Can this be an associated function instead? | ||
#[cfg(test)] | ||
pub(crate) fn verify_restore(&self, saved_state: &SubmissionQueueSavedState) { | ||
assert_eq!(saved_state.sqid, self.sqid); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two questions about these checks:
|
||
assert_eq!(saved_state.head, self.head); | ||
assert_eq!(saved_state.tail, self.tail); | ||
assert_eq!(saved_state.committed_tail, self.committed_tail); | ||
assert_eq!(saved_state.len, self.len); | ||
} | ||
} | ||
|
||
#[derive(Inspect)] | ||
|
@@ -201,6 +212,16 @@ impl CompletionQueue { | |
mem, | ||
}) | ||
} | ||
|
||
/// Given the saved state, checks the state of the completion queue. Does not verify memory. | ||
#[cfg(test)] | ||
pub(crate) fn verify_restore(&self, saved_state: &CompletionQueueSavedState) { | ||
assert_eq!(saved_state.cqid, self.cqid); | ||
assert_eq!(saved_state.head, self.head); | ||
assert_eq!(saved_state.committed_head, self.committed_head); | ||
assert_eq!(saved_state.len, self.len); | ||
assert_eq!(saved_state.phase, self.phase); | ||
} | ||
} | ||
|
||
fn advance(n: u32, l: u32) -> u32 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -224,6 +224,21 @@ impl DeviceSharedMemory { | |
state: self.state.clone(), | ||
}) | ||
} | ||
|
||
// TODO: [nvme-keepalive-testing] | ||
// This is only a stop-gap until we can swap out the back end of nvme tests to use real memory | ||
pub fn alloc_specific(&self, len: usize, base_pfn: u64) -> Option<DmaBuffer>{ | ||
assert!(len % PAGE_SIZE == 0); | ||
let count = len / PAGE_SIZE; | ||
let start_page = base_pfn as usize; | ||
|
||
let pages = (start_page..start_page + count).map(|p| p as u64).collect(); | ||
Some(DmaBuffer { | ||
mem: self.mem.clone(), | ||
pfns: pages, | ||
state: self.state.clone(), | ||
}) | ||
} | ||
} | ||
|
||
pub struct DmaBuffer { | ||
|
@@ -270,15 +285,24 @@ pub struct EmulatedDmaAllocator { | |
shared_mem: DeviceSharedMemory, | ||
} | ||
|
||
impl EmulatedDmaAllocator { | ||
pub fn new(shared_mem: DeviceSharedMemory) -> Self { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was a new function for this removed? It suddenly stopped working for me for some reason so I had to write this instead! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There were few changes in this area recently |
||
EmulatedDmaAllocator { | ||
shared_mem, | ||
} | ||
} | ||
} | ||
|
||
impl DmaClient for EmulatedDmaAllocator { | ||
fn allocate_dma_buffer(&self, len: usize) -> anyhow::Result<MemoryBlock> { | ||
let memory = MemoryBlock::new(self.shared_mem.alloc(len).context("out of memory")?); | ||
memory.as_slice().atomic_fill(0); | ||
Ok(memory) | ||
} | ||
|
||
fn attach_dma_buffer(&self, _len: usize, _base_pfn: u64) -> anyhow::Result<MemoryBlock> { | ||
anyhow::bail!("restore is not supported for emulated DMA") | ||
fn attach_dma_buffer(&self, len: usize, base_pfn: u64) -> anyhow::Result<MemoryBlock> { | ||
let memory = MemoryBlock::new( self.shared_mem.alloc_specific(len, base_pfn).context("could not alloc specific. out of memory")?); | ||
Ok(memory) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand what the purpose of this is. Is this to make sure that
restore
restores the thing? It just seems like it's duplicating the logic inrestore
. Is that valuable?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To put it differently, I don't see the value of this kind of "invasive" testing--we're just checking to see if
restore
puts the internal state of our object in the state that we expect. But if we screwed uprestore
, isn't it just as likely that we screwed upverify_restore
? Now we have to get it right in two places.A more effective test would be to validate that after
restore
, the driver is in the state we expect by observing the behavior of the driver.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point! Initially I was coming at this as something that would test but also prevent regression of restore in the future. Kind of a basic check to verify that values were set correctly. For example, if restore functionality was to ever regress to restore every submission queue with one entry less than intended. This theoretically would not be an invalid state for the driver (I think) but would definitely be incorrect since multiple restores would shrink the queue significantly. In my head, coming up with and testing for every such edge case by looking at behavior alone might prove to be hard.
What I am wondering is, does this type of testing that I built out provide any value at all? Do you think I should scrap this or should I be adding more tests to verify behavior on top of this? I am happy to go either way but interested to know your thoughts on how to proceed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a first step we just need to re-enable
restore()
in unit tests. The reason why it was disabled in first place is that it usedattach_dma_buffer
call in EmulatedDma, but the team's feedback was to remove it from EmulatedDma because it was mocking non-existing functionality.You re-added
attach_dma_buffer
so I expect that team's feedback will be same, but let's see...We need to redesign some interfaces to allow unit tests to mock pfn-based access. Or, maybe, hide pfn details in a separate module.
As a next step we would like to generate I/O traffic before/after servicing.