Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions bin/propolis-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ use uuid::Uuid;

use propolis_client::{
support::{InstanceSerialConsoleHelper, WSClientOffset},
types::{InstanceStateRequested, InstanceVcrReplace, MigrationState},
types::{
InstanceStateChange, InstanceStateRequested, InstanceVcrReplace,
MigrationState,
},
Client,
};

Expand Down Expand Up @@ -118,6 +121,9 @@ enum Command {
/// The requested state
#[clap(value_parser = parse_state)]
state: InstanceStateRequested,
/// The number of seconds to wait after sending ACPI PWRBTN_STS before
/// forcing stop/reset. (If omitted, stop/reset are forced immediately)
acpi_timeout_secs: Option<u64>,
},

/// Drop to a Serial console connected to the instance
Expand Down Expand Up @@ -572,10 +578,11 @@ async fn get_instance(client: &Client) -> anyhow::Result<()> {
async fn put_instance(
client: &Client,
state: InstanceStateRequested,
acpi_timeout_secs: Option<u64>,
) -> anyhow::Result<()> {
client
.instance_state_put()
.body(state)
.body(InstanceStateChange { state, acpi_timeout_secs })
.send()
.await
.with_context(|| anyhow!("failed to set instance state"))?;
Expand Down Expand Up @@ -958,7 +965,9 @@ async fn main() -> anyhow::Result<()> {
.await?
}
Command::Get => get_instance(&client).await?,
Command::State { state } => put_instance(&client, state).await?,
Command::State { state, acpi_timeout_secs } => {
put_instance(&client, state, acpi_timeout_secs).await?
}
Command::Serial { byte_offset } => {
serial(addr, byte_offset, log).await?
}
Expand Down
13 changes: 11 additions & 2 deletions bin/propolis-server/src/lib/initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ pub fn build_instance(
pub struct RegisteredChipset {
chipset: Arc<dyn Chipset>,
isa: Arc<i440fx::Piix3Lpc>,
pm: Arc<i440fx::Piix3PM>,
}
impl RegisteredChipset {
pub fn pci_attach(&self, bdf: pci::Bdf, dev: Arc<dyn pci::Endpoint>) {
Expand All @@ -197,6 +198,9 @@ impl RegisteredChipset {
pub fn irq_pin(&self, irq: u8) -> Option<Box<dyn intr_pins::IntrPin>> {
self.isa.irq_pin(irq)
}
pub fn acpi_shutdown(&self) {
self.pm.acpi_shutdown();
}
fn reset_pin(&self) -> Arc<dyn intr_pins::IntrPin> {
self.chipset.reset_pin()
}
Expand Down Expand Up @@ -356,6 +360,7 @@ impl MachineInitializer<'_> {
let chipset_pm = i440fx::Piix3PM::create(
self.machine.hdl.clone(),
chipset_hb.power_pin(),
chipset_lpc.sci_pin(),
self.log.new(slog::o!("device" => "piix3pm")),
);

Expand Down Expand Up @@ -387,7 +392,7 @@ impl MachineInitializer<'_> {
);
self.devices.insert(
SpecKey::Name(chipset_pm.type_name().into()),
chipset_pm,
chipset_pm.clone(),
);

// Record attachment for any bridges in PCI topology too
Expand All @@ -404,7 +409,11 @@ impl MachineInitializer<'_> {
self.devices.insert(spec_element.0.clone(), bridge);
}

Ok(RegisteredChipset { chipset: chipset_hb, isa: chipset_lpc })
Ok(RegisteredChipset {
chipset: chipset_hb,
isa: chipset_lpc,
pm: chipset_pm,
})
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions bin/propolis-server/src/lib/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use propolis_api_types::disk::{
};
use propolis_api_types::instance::{
ErrorCode, Instance, InstanceEnsureRequest, InstanceEnsureResponse,
InstanceGetResponse, InstanceInitializationMethod,
InstanceGetResponse, InstanceInitializationMethod, InstanceStateChange,
InstanceStateMonitorRequest, InstanceStateMonitorResponse,
InstanceStateRequested,
};
Expand Down Expand Up @@ -364,13 +364,13 @@ impl PropolisServerApi for PropolisServerImpl {

async fn instance_state_put(
rqctx: RequestContext<Self::Context>,
request: TypedBody<InstanceStateRequested>,
request: TypedBody<InstanceStateChange>,
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
let ctx = rqctx.context();
let requested_state = request.into_inner();
let requested_change = request.into_inner();
let vm = ctx.vm.active_vm().await.ok_or_else(not_created_error)?;
let result = vm
.put_state(requested_state)
.put_state(requested_change)
.map(|_| HttpResponseUpdatedNoContent {})
.map_err(|e| match e {
VmError::WaitingToInitialize => HttpError::for_unavail(
Expand All @@ -391,7 +391,7 @@ impl PropolisServerApi for PropolisServerImpl {
});

if result.is_ok() {
if let InstanceStateRequested::Reboot = requested_state {
if let InstanceStateRequested::Reboot = requested_change.state {
let stats = MutexGuard::map(
vm.services().oximeter.lock().await,
|state| &mut state.stats,
Expand Down
32 changes: 24 additions & 8 deletions bin/propolis-server/src/lib/vm/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
//! Implements a wrapper around an active VM.

use std::sync::Arc;
use std::time::Duration;

use propolis_api_types::instance::{
InstanceProperties, InstanceStateRequested,
InstanceProperties, InstanceStateChange, InstanceStateRequested,
};
use propolis_api_types::instance_spec::SpecKey;
use slog::info;
use uuid::Uuid;

use crate::vm::request_queue::ExternalRequest;
use crate::vm::request_queue::{ExternalRequest, RequestDeniedReason};
use crate::vm::SoftShutdownFate;

use super::{
objects::VmObjects, services::VmServices, CrucibleReplaceResultTx,
Expand Down Expand Up @@ -57,17 +59,31 @@ impl ActiveVm {
/// Pushes a state change request to the VM's state change queue.
pub(crate) fn put_state(
&self,
requested: InstanceStateRequested,
requested: InstanceStateChange,
) -> Result<(), VmError> {
info!(self.log, "requested state via API";
"state" => ?requested);

self.state_driver_queue
.queue_external_request(match requested {
"state" => ?requested.state,
"timeout" => ?requested.acpi_timeout_secs);

let ext_req = if let Some(secs) = requested.acpi_timeout_secs {
let timeout = Duration::from_secs(secs);
let fate = match requested.state {
InstanceStateRequested::Run => {
return Err(RequestDeniedReason::TimeoutOnRun.into());
}
InstanceStateRequested::Stop => SoftShutdownFate::Stop,
InstanceStateRequested::Reboot => SoftShutdownFate::Reboot,
};
ExternalRequest::acpi_shutdown(fate, timeout)
} else {
match requested.state {
InstanceStateRequested::Run => ExternalRequest::start(),
InstanceStateRequested::Stop => ExternalRequest::stop(),
InstanceStateRequested::Reboot => ExternalRequest::reboot(),
})
}
};
self.state_driver_queue
.queue_external_request(ext_req)
.map_err(Into::into)
}

Expand Down
1 change: 1 addition & 0 deletions bin/propolis-server/src/lib/vm/ensure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ async fn initialize_vm_objects(
framebuffer: Some(ramfb),
ps2ctrl,
attest_handle,
chipset: chipset.into(),
};

// Another really terrible hack. As we've found in Propolis#1008, brk()
Expand Down
7 changes: 7 additions & 0 deletions bin/propolis-server/src/lib/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ const VMM_MIN_RT_THREADS: usize = 8;
/// is greater.
const VMM_BASE_RT_THREADS: usize = 4;

/// The action to take after an ACPI shutdown either completes or times out.
#[derive(Clone, Copy, Debug)]
pub enum SoftShutdownFate {
Stop,
Reboot,
}

/// Errors generated by the VM controller and its subcomponents.
#[derive(Debug, thiserror::Error)]
pub(crate) enum VmError {
Expand Down
15 changes: 14 additions & 1 deletion bin/propolis-server/src/lib/vm/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ use propolis_api_types::instance_spec::SpecKey;
use slog::info;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};

use crate::{serial::Serial, spec::Spec, vcpu_tasks::VcpuTaskController};
use crate::{
initializer::RegisteredChipset, serial::Serial, spec::Spec,
vcpu_tasks::VcpuTaskController,
};

use super::{BlockBackendMap, CrucibleBackendMap, DeviceMap};

Expand Down Expand Up @@ -53,6 +56,7 @@ pub(super) struct InputVmObjects {
pub framebuffer: Option<Arc<RamFb>>,
pub ps2ctrl: Arc<PS2Ctrl>,
pub attest_handle: Option<attestation::server::AttestationSock>,
pub chipset: Arc<RegisteredChipset>,
}

/// The collection of objects and state that make up a Propolis instance.
Expand Down Expand Up @@ -91,6 +95,9 @@ pub(crate) struct VmObjectsLocked {

/// A handle to the VM's attestation server.
attest_handle: Option<attestation::server::AttestationSock>,

/// A handle to the VM's mainboard chipset.
chipset: Arc<RegisteredChipset>,
}

impl VmObjects {
Expand Down Expand Up @@ -132,6 +139,7 @@ impl VmObjectsLocked {
framebuffer: input.framebuffer,
ps2ctrl: input.ps2ctrl,
attest_handle: input.attest_handle,
chipset: input.chipset,
}
}

Expand Down Expand Up @@ -194,6 +202,11 @@ impl VmObjectsLocked {
&self.ps2ctrl
}

/// Yields a clonable reference to this VM's mainboard chipset.
pub(crate) fn chipset(&self) -> &Arc<RegisteredChipset> {
&self.chipset
}

pub(crate) fn device_map(&self) -> &DeviceMap {
&self.devices
}
Expand Down
Loading
Loading