Skip to content

Commit 318ab04

Browse files
authored
vmm_tests: add test for using the shutdown IC across servicing (#927)
The shutdown IC is handled specially, so ensure it behaves as expected across OpenHCL servicing.
1 parent 40dce63 commit 318ab04

File tree

5 files changed

+81
-10
lines changed

5 files changed

+81
-10
lines changed

petri/src/vm/openvmm/runtime.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ impl PetriVmOpenVmm {
166166
/// method is best effort for them.
167167
pub async fn wait_for_successful_boot_event(&mut self) -> anyhow::Result<()>
168168
);
169+
petri_vm_fn!(
170+
/// Waits for the Hyper-V shutdown IC to be ready, returning a receiver
171+
/// that will be closed when it is no longer ready.
172+
pub async fn wait_for_enlightened_shutdown_ready(&mut self) -> anyhow::Result<mesh::OneshotReceiver<()>>
173+
);
169174
petri_vm_fn!(
170175
/// Instruct the guest to shutdown via the Hyper-V shutdown IC.
171176
pub async fn send_enlightened_shutdown(&mut self, kind: ShutdownKind) -> anyhow::Result<()>
@@ -305,13 +310,21 @@ impl PetriVmInner {
305310
Ok(())
306311
}
307312

308-
async fn send_enlightened_shutdown(&mut self, kind: ShutdownKind) -> anyhow::Result<()> {
313+
async fn wait_for_enlightened_shutdown_ready(
314+
&mut self,
315+
) -> anyhow::Result<mesh::OneshotReceiver<()>> {
309316
tracing::info!("Waiting for shutdown ic ready");
310-
self.resources
317+
let recv = self
318+
.resources
311319
.shutdown_ic_send
312320
.call(ShutdownRpc::WaitReady, ())
313321
.await?;
314322

323+
Ok(recv)
324+
}
325+
326+
async fn send_enlightened_shutdown(&mut self, kind: ShutdownKind) -> anyhow::Result<()> {
327+
self.wait_for_enlightened_shutdown_ready().await?;
315328
if let Some(duration) = self.quirks.hyperv_shutdown_ic_sleep {
316329
tracing::info!("QUIRK: Waiting for {:?}", duration);
317330
PolledTimer::new(&self.resources.driver)

support/mesh/mesh_channel_core/src/oneshot.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ impl<T> OneshotSender<T> {
101101
// SAFETY: the slot is of type `T`.
102102
unsafe { self.0.send(value) }
103103
}
104+
105+
/// Returns true if the matching receiver is closed.
106+
pub fn is_closed(&self) -> bool {
107+
self.0.is_closed()
108+
}
104109
}
105110

106111
impl<T: MeshField> DefaultEncoding for OneshotSender<T> {
@@ -172,6 +177,16 @@ impl OneshotSenderCore {
172177
}
173178
}
174179

180+
fn is_closed(&self) -> bool {
181+
match &*self.0 .0.lock() {
182+
SlotState::Done => true,
183+
SlotState::Sent(_) => true,
184+
SlotState::Waiting(_) => false,
185+
SlotState::ReceiverRemote(port, _) => port.is_closed().unwrap_or(false),
186+
SlotState::SenderRemote { .. } => unreachable!(),
187+
}
188+
}
189+
175190
/// # Safety
176191
/// The caller must ensure that the slot is of type `T`.
177192
unsafe fn send<T>(self, value: T) {

vm/devices/hyperv_ic/src/shutdown.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub struct ShutdownIc {
4242
#[inspect(skip)]
4343
recv: mesh::Receiver<ShutdownRpc>,
4444
#[inspect(skip)]
45-
wait_ready: Vec<Rpc<(), ()>>,
45+
wait_ready: Vec<Rpc<(), mesh::OneshotReceiver<()>>>,
4646
}
4747

4848
#[doc(hidden)]
@@ -66,6 +66,8 @@ enum ChannelState {
6666
#[inspect(display)]
6767
message_version: hyperv_ic_protocol::Version,
6868
state: ReadyState,
69+
#[inspect(with = "|x| x.len()")]
70+
clients: Vec<mesh::OneshotSender<()>>,
6971
},
7072
}
7173

@@ -140,11 +142,16 @@ impl ShutdownChannel {
140142
r?;
141143
}
142144
Event::Request(req) => match req {
143-
ShutdownRpc::WaitReady(rpc) => match self.state {
145+
ShutdownRpc::WaitReady(rpc) => match &mut self.state {
144146
ChannelState::SendVersion | ChannelState::WaitVersion => {
145147
ic.wait_ready.push(rpc)
146148
}
147-
ChannelState::Ready { .. } => rpc.complete(()),
149+
ChannelState::Ready { clients, .. } => {
150+
let (send, recv) = mesh::oneshot();
151+
clients.retain(|c| !c.is_closed());
152+
clients.push(send);
153+
rpc.complete(recv);
154+
}
148155
},
149156
ShutdownRpc::Shutdown(rpc) => match self.state {
150157
ChannelState::SendVersion | ChannelState::WaitVersion => {
@@ -168,7 +175,7 @@ impl ShutdownChannel {
168175

169176
async fn process_state_machine(
170177
&mut self,
171-
wait_ready: &mut Vec<Rpc<(), ()>>,
178+
wait_ready: &mut Vec<Rpc<(), mesh::OneshotReceiver<()>>>,
172179
) -> Result<(), Error> {
173180
match self.state {
174181
ChannelState::SendVersion => {
@@ -218,19 +225,27 @@ impl ShutdownChannel {
218225
.map_err(|_| Error::TruncatedMessage)?
219226
.0; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
220227

228+
let clients = wait_ready
229+
.drain(..)
230+
.map(|rpc| {
231+
let (send, recv) = mesh::oneshot();
232+
rpc.complete(recv);
233+
send
234+
})
235+
.collect();
236+
221237
self.state = ChannelState::Ready {
222238
framework_version,
223239
message_version,
224240
state: ReadyState::Ready,
241+
clients,
225242
};
226-
for rpc in wait_ready.drain(..) {
227-
rpc.complete(());
228-
}
229243
}
230244
ChannelState::Ready {
231245
ref mut state,
232246
framework_version,
233247
message_version,
248+
clients: _,
234249
} => match state {
235250
ReadyState::Ready => std::future::pending().await,
236251
ReadyState::SendShutdown(params) => {
@@ -473,6 +488,7 @@ mod save_restore {
473488
framework_version,
474489
message_version,
475490
state,
491+
clients: _,
476492
} = &runner.state
477493
{
478494
let request = if let ReadyState::SendShutdown(request) = state {
@@ -515,6 +531,7 @@ mod save_restore {
515531
framework_version: framework.into(),
516532
message_version: message.into(),
517533
state,
534+
clients: Vec::new(),
518535
}
519536
} else {
520537
if saved_state.waiting_on_version {

vm/devices/hyperv_ic_resources/src/shutdown.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ impl ResourceId<VmbusDeviceHandleKind> for ShutdownIcHandle {
2323
#[derive(MeshPayload)]
2424
pub enum ShutdownRpc {
2525
/// Wait for the shutdown IC to be ready.
26-
WaitReady(Rpc<(), ()>),
26+
///
27+
/// Returns a receiver that closes when the IC is no longer ready.
28+
///
29+
/// FUTURE: this should instead be a `Sender` that is used to send the
30+
/// shutdown request. Do this once `Sender` has a mechanism to wait on the
31+
/// receiver to be closed.
32+
WaitReady(Rpc<(), mesh::OneshotReceiver<()>>),
2733
/// Send a shutdown request to the guest.
2834
Shutdown(Rpc<ShutdownParams, ShutdownResult>),
2935
}

vmm_tests/vmm_tests/tests/tests/x86_64/openhcl_servicing.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,25 @@ async fn openhcl_servicing_keepalive(
6767
.await
6868
}
6969

70+
#[openvmm_test(openhcl_linux_direct_x64 [LATEST_LINUX_DIRECT_TEST_X64])]
71+
async fn openhcl_servicing_shutdown_ic(
72+
config: PetriVmConfigOpenVmm,
73+
(igvm_file,): (ResolvedArtifact<impl petri_artifacts_common::tags::IsOpenhclIgvm>,),
74+
) -> Result<(), anyhow::Error> {
75+
let (mut vm, agent) = config.with_vmbus_redirect().run().await?;
76+
agent.ping().await?;
77+
let shutdown_ic = vm.wait_for_enlightened_shutdown_ready().await?;
78+
vm.restart_openhcl(igvm_file, OpenHclServicingFlags::default())
79+
.await?;
80+
// VTL2 will disconnect and then reconnect the shutdown IC across a servicing event.
81+
tracing::info!("waiting for shutdown IC to close");
82+
shutdown_ic.await.unwrap_err();
83+
vm.wait_for_enlightened_shutdown_ready().await?;
84+
vm.send_enlightened_shutdown(petri::ShutdownKind::Shutdown)
85+
.await?;
86+
assert_eq!(vm.wait_for_teardown().await?, HaltReason::PowerOff);
87+
Ok(())
88+
}
89+
7090
// TODO: add tests with guest workloads while doing servicing.
7191
// TODO: add tests from previous release branch to current.

0 commit comments

Comments
 (0)