From a892f2a5a3e3cd8641760161bf5d214bf0b7c8b4 Mon Sep 17 00:00:00 2001 From: iximeow Date: Tue, 6 Jan 2026 09:11:20 +0000 Subject: [PATCH] Indicator should allow stopping instances at Init `lifecycle::Indicator` to date is only used for the virtio NIC, but `phd-tests` doesn't actually exercise that.. so, add an `Indicator` to `I440FxHostBrdige` as it is a component present at all times in all VMs, and glue it into Lifecycle as expected in any other application of Indicator. With this, the new test to stop a created-but-not-started VM hangs and eventually fails with a timeout, and fixing the expected state transition in Indicator makes it pass. Incidentally, this exercises the rest of the state transitions, with various tests pausing or restarting VMs, migrating them, etc. --- lib/propolis/src/hw/chipset/i440fx.rs | 16 ++++++++++++++++ lib/propolis/src/lifecycle.rs | 2 +- phd-tests/tests/src/server_state_machine.rs | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/propolis/src/hw/chipset/i440fx.rs b/lib/propolis/src/hw/chipset/i440fx.rs index a3566d96c..5f874c10c 100644 --- a/lib/propolis/src/hw/chipset/i440fx.rs +++ b/lib/propolis/src/hw/chipset/i440fx.rs @@ -21,6 +21,7 @@ use crate::hw::pci::{ self, Bdf, INTxPinID, LintrCfg, PcieCfgDecoder, PioCfgDecoder, }; use crate::intr_pins::{IntrPin, LegacyPIC, LegacyPin, NoOpPin}; +use crate::lifecycle; use crate::migrate::*; use crate::mmio::MmioFn; use crate::pio::{PioBus, PioFn}; @@ -155,6 +156,7 @@ pub struct Opts { pub struct I440FxHostBridge { pci_state: pci::DeviceState, + indicator: lifecycle::Indicator, pci_topology: Arc, pci_cfg: PioCfgDecoder, @@ -189,6 +191,7 @@ impl I440FxHostBridge { Arc::new(Self { pci_state, + indicator: Default::default(), pci_topology, pci_cfg, @@ -276,6 +279,19 @@ impl Lifecycle for I440FxHostBridge { fn reset(&self) { self.pci_state.reset(self); } + fn start(&self) -> anyhow::Result<()> { + self.indicator.start(); + Ok(()) + } + fn pause(&self) { + self.indicator.pause(); + } + fn resume(&self) { + self.indicator.resume(); + } + fn halt(&self) { + self.indicator.halt(); + } fn migrate(&self) -> Migrator<'_> { Migrator::Multi(self) } diff --git a/lib/propolis/src/lifecycle.rs b/lib/propolis/src/lifecycle.rs index 1d8e194c5..c138ae7c0 100644 --- a/lib/propolis/src/lifecycle.rs +++ b/lib/propolis/src/lifecycle.rs @@ -173,7 +173,7 @@ impl IndicatedState { const fn valid_transition(old: Self, new: Self) -> bool { use IndicatedState::*; match (old, new) { - (Init, Run) | (Init, Halt) => true, + (Init, Run) | (Init, Pause) => true, (Run, Pause) => true, (Pause, Run) | (Pause, Halt) => true, _ => false, diff --git a/phd-tests/tests/src/server_state_machine.rs b/phd-tests/tests/src/server_state_machine.rs index c15a59641..352019751 100644 --- a/phd-tests/tests/src/server_state_machine.rs +++ b/phd-tests/tests/src/server_state_machine.rs @@ -29,6 +29,22 @@ async fn instance_start_stop_test(ctx: &Framework) { .await?; } +#[phd_testcase] +async fn instance_stop_unstarted_test(ctx: &Framework) { + let mut vm = ctx.spawn_default_vm("instance_stop_unstarted_test").await?; + + vm.instance_ensure().await?; + let instance = vm.get().await?.instance; + assert_eq!(instance.state, InstanceState::Creating); + + // At this point the VM is created and its resources are held as + // appropriate. Stopping the VM will cause propolis-server to destroy the + // VM, releasing those resources and getting the server ready for shutdown. + vm.stop().await?; + vm.wait_for_state(InstanceState::Destroyed, Duration::from_secs(60)) + .await?; +} + #[phd_testcase] async fn instance_stop_causes_destroy_test(ctx: &Framework) { let mut vm =