Skip to content

Commit

Permalink
Migrate guest time-related data
Browse files Browse the repository at this point in the history
  • Loading branch information
jordanhendricks committed May 12, 2023
1 parent 6db58ae commit 7ed4808
Show file tree
Hide file tree
Showing 9 changed files with 1,111 additions and 3 deletions.
111 changes: 111 additions & 0 deletions bin/propolis-server/src/lib/migrate/destination.rs
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
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
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
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
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

0 comments on commit 7ed4808

Please sign in to comment.