Skip to content
Merged
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
111 changes: 111 additions & 0 deletions bin/propolis-server/src/lib/migrate/destination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use propolis::inventory::Entity;
use propolis::migrate::{
MigrateCtx, MigrateStateError, Migrator, PayloadOffer, PayloadOffers,
};
use propolis::vmm;
use slog::{error, info, trace, warn};
use std::convert::TryInto;
use std::io;
Expand Down Expand Up @@ -108,6 +109,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> DestinationProtocol<T> {
self.ram_push(&step).await
}
MigratePhase::DeviceState => self.device_state().await,
MigratePhase::TimeData => self.time_data().await,
MigratePhase::RamPull => self.ram_pull().await,
MigratePhase::ServerState => self.server_state().await,
MigratePhase::Finish => self.finish().await,
Expand All @@ -129,6 +131,11 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> DestinationProtocol<T> {
// pre- and post-pause steps.
self.run_phase(MigratePhase::RamPushPrePause).await?;
self.run_phase(MigratePhase::RamPushPostPause).await?;

// Import of the time data *must* be done before we import device
// state: the proper functioning of device timers depends on an adjusted
// boot_hrtime.
self.run_phase(MigratePhase::TimeData).await?;
self.run_phase(MigratePhase::DeviceState).await?;
self.run_phase(MigratePhase::RamPull).await?;
self.run_phase(MigratePhase::ServerState).await?;
Expand Down Expand Up @@ -321,6 +328,110 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> DestinationProtocol<T> {
self.import_device(&target, &device, &migrate_ctx)?;
}
}
self.send_msg(codec::Message::Okay).await
}

// Get the guest time data from the source, make updates to it based on the
// new host, and write the data out to bhvye.
async fn time_data(&mut self) -> Result<(), MigrateError> {
// Read time data sent by the source and deserialize
let raw: String = match self.read_msg().await? {
codec::Message::Serialized(encoded) => encoded,
msg => {
error!(self.log(), "time data: unexpected message: {msg:?}");
return Err(MigrateError::UnexpectedMessage);
}
};
info!(self.log(), "VMM Time Data: {:?}", raw);
let time_data_src: vmm::time::VmTimeData = ron::from_str(&raw)
.map_err(|e| {
MigrateError::TimeData(format!(
"VMM Time Data deserialization error: {}",
e
))
})?;
probes::migrate_time_data_before!(|| {
(
time_data_src.guest_freq,
time_data_src.guest_tsc,
time_data_src.boot_hrtime,
)
});

// Take a snapshot of the host hrtime/wall clock time, then adjust
// time data appropriately.
let vmm_hdl = {
let instance_guard = self.vm_controller.instance().lock();
&instance_guard.machine().hdl.clone()
};
let (dst_hrt, dst_wc) = vmm::time::host_time_snapshot(vmm_hdl)
.map_err(|e| {
MigrateError::TimeData(format!(
"could not read host time: {}",
e
))
})?;
let (time_data_dst, adjust) =
vmm::time::adjust_time_data(time_data_src, dst_hrt, dst_wc)
.map_err(|e| {
MigrateError::TimeData(format!(
"could not adjust VMM Time Data: {}",
e
))
})?;

// In case import fails, log adjustments made to time data and fire
// dtrace probe first
if adjust.migrate_delta_negative {
warn!(
self.log(),
"Found negative wall clock delta between target import \
and source export:\n\
- source wall clock time: {:?}\n\
- target wall clock time: {:?}\n",
time_data_src.wall_clock(),
dst_wc
);
}
info!(
self.log(),
"Time data adjustments:\n\
- guest TSC freq: {} Hz = {} GHz\n\
- guest uptime ns: {:?}\n\
- migration time delta: {:?}\n\
- guest_tsc adjustment = {} + {} = {}\n\
- boot_hrtime adjustment = {} ---> {} - {} = {}\n\
- dest highres clock time: {}\n\
- dest wall clock time: {:?}",
time_data_dst.guest_freq,
time_data_dst.guest_freq as f64 / vmm::time::NS_PER_SEC as f64,
adjust.guest_uptime_ns,
adjust.migrate_delta,
time_data_src.guest_tsc,
adjust.guest_tsc_delta,
time_data_dst.guest_tsc,
time_data_src.boot_hrtime,
dst_hrt,
adjust.boot_hrtime_delta,
time_data_dst.boot_hrtime,
dst_hrt,
dst_wc
);
probes::migrate_time_data_after!(|| {
(
time_data_dst.guest_freq,
time_data_dst.guest_tsc,
time_data_dst.boot_hrtime,
adjust.guest_uptime_ns,
adjust.migrate_delta.as_nanos() as u64,
adjust.migrate_delta_negative,
)
});

// Import the adjusted time data
vmm::time::import_time_data(vmm_hdl, time_data_dst).map_err(|e| {
MigrateError::TimeData(format!("VMM Time Data import error: {}", e))
})?;

self.send_msg(codec::Message::Okay).await
}
Expand Down
22 changes: 22 additions & 0 deletions bin/propolis-server/src/lib/migrate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ enum MigratePhase {
Pause,
RamPushPrePause,
RamPushPostPause,
TimeData,
DeviceState,
RamPull,
ServerState,
Expand All @@ -77,6 +78,7 @@ impl std::fmt::Display for MigratePhase {
MigratePhase::Pause => "Pause",
MigratePhase::RamPushPrePause => "RamPushPrePause",
MigratePhase::RamPushPostPause => "RamPushPostPause",
MigratePhase::TimeData => "TimeData",
MigratePhase::DeviceState => "DeviceState",
MigratePhase::RamPull => "RamPull",
MigratePhase::ServerState => "ServerState",
Expand Down Expand Up @@ -147,6 +149,10 @@ pub enum MigrateError {
#[error("received out-of-phase message")]
Phase,

/// Failed to export/import time data state
#[error("failed to migrate VMM time data: {0}")]
TimeData(String),

/// Failed to export/import device state for migration
#[error("failed to migrate device state: {0}")]
DeviceState(String),
Expand Down Expand Up @@ -207,6 +213,7 @@ impl From<MigrateError> for HttpError {
| MigrateError::UnexpectedMessage
| MigrateError::SourcePause
| MigrateError::Phase
| MigrateError::TimeData(_)
| MigrateError::DeviceState(_)
| MigrateError::RemoteError(_, _)
| MigrateError::StateMachine(_) => {
Expand Down Expand Up @@ -422,4 +429,19 @@ mod probes {
fn migrate_phase_end(step_desc: &str) {}
fn migrate_xfer_ram_region(pages: u64, size: u64, paused: u8) {}
fn migrate_xfer_ram_page(addr: u64, size: u64) {}
fn migrate_time_data_before(
src_guest_freq: u64,
src_guest_tsc: u64,
src_boot_hrtime: i64,
) {
}
fn migrate_time_data_after(
dst_guest_freq: u64,
dst_guest_tsc: u64,
dst_boot_hrtime: i64,
guest_uptime: u64,
migrate_delta_ns: u64,
migrate_delta_negative: bool,
) {
}
}
26 changes: 26 additions & 0 deletions bin/propolis-server/src/lib/migrate/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use propolis::inventory::Order;
use propolis::migrate::{
MigrateCtx, MigrateStateError, Migrator, PayloadOutputs,
};
use propolis::vmm;
use slog::{error, info, trace};
use std::convert::TryInto;
use std::io;
Expand Down Expand Up @@ -112,6 +113,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
MigratePhase::RamPushPrePause | MigratePhase::RamPushPostPause => {
self.ram_push(&step).await
}
MigratePhase::TimeData => self.time_data().await,
MigratePhase::DeviceState => self.device_state().await,
MigratePhase::RamPull => self.ram_pull().await,
MigratePhase::ServerState => self.server_state().await,
Expand All @@ -130,6 +132,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
self.run_phase(MigratePhase::RamPushPrePause).await?;
self.run_phase(MigratePhase::Pause).await?;
self.run_phase(MigratePhase::RamPushPostPause).await?;
self.run_phase(MigratePhase::TimeData).await?;
self.run_phase(MigratePhase::DeviceState).await?;
self.run_phase(MigratePhase::RamPull).await?;
self.run_phase(MigratePhase::ServerState).await?;
Expand Down Expand Up @@ -389,6 +392,29 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
self.read_ok().await
}

// Read and send over the time data
async fn time_data(&mut self) -> Result<(), MigrateError> {
let vmm_hdl = {
let instance_guard = self.vm_controller.instance().lock();
&instance_guard.machine().hdl.clone()
};
let vm_time_data =
vmm::time::export_time_data(vmm_hdl).map_err(|e| {
MigrateError::TimeData(format!(
"VMM Time Data export error: {}",
e
))
})?;
info!(self.log(), "VMM Time Data: {:#?}", vm_time_data);

let time_data_serialized = ron::ser::to_string(&vm_time_data)
.map_err(codec::ProtocolError::from)?;
info!(self.log(), "VMM Time Data: {:#?}", time_data_serialized);
self.send_msg(codec::Message::Serialized(time_data_serialized)).await?;

self.read_ok().await
}

async fn ram_pull(&mut self) -> Result<(), MigrateError> {
self.update_state(MigrationState::RamPush).await;
let m = self.read_msg().await?;
Expand Down
4 changes: 2 additions & 2 deletions lib/propolis/src/vmm/hdl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use std::time::Duration;
use crate::common::PAGE_SIZE;
use crate::vmm::mem::Prot;

#[derive(Default, Copy, Clone)]
/// Configurable options for VMM instance creation
///
/// # Options:
Expand All @@ -26,6 +25,7 @@ use crate::vmm::mem::Prot;
/// - `use_reservoir`: Allocate guest memory (only) from the VMM reservoir. If
/// this is enabled, and memory in excess of what is available from the
/// reservoir is requested, creation of that guest memory resource will fail.
#[derive(Default, Copy, Clone)]
pub struct CreateOpts {
pub force: bool,
pub use_reservoir: bool,
Expand Down Expand Up @@ -418,7 +418,7 @@ impl VmmHdl {
#[cfg(test)]
impl VmmHdl {
/// Build a VmmHdl instance suitable for unit tests, but nothing else, since
/// it will not be backed by any real vmm reousrces.
/// it will not be backed by any real vmm resources.
pub(crate) fn new_test(mem_size: usize) -> Result<Self> {
use tempfile::tempfile;
let fp = tempfile()?;
Expand Down
1 change: 1 addition & 0 deletions lib/propolis/src/vmm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod data;
pub mod hdl;
pub mod machine;
pub mod mem;
pub mod time;

pub use hdl::*;
pub use machine::*;
Expand Down
Loading