Skip to content

Commit 7ed4808

Browse files
Migrate guest time-related data
1 parent 6db58ae commit 7ed4808

File tree

9 files changed

+1111
-3
lines changed

9 files changed

+1111
-3
lines changed

bin/propolis-server/src/lib/migrate/destination.rs

+111
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use propolis::inventory::Entity;
55
use propolis::migrate::{
66
MigrateCtx, MigrateStateError, Migrator, PayloadOffer, PayloadOffers,
77
};
8+
use propolis::vmm;
89
use slog::{error, info, trace, warn};
910
use std::convert::TryInto;
1011
use std::io;
@@ -108,6 +109,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> DestinationProtocol<T> {
108109
self.ram_push(&step).await
109110
}
110111
MigratePhase::DeviceState => self.device_state().await,
112+
MigratePhase::TimeData => self.time_data().await,
111113
MigratePhase::RamPull => self.ram_pull().await,
112114
MigratePhase::ServerState => self.server_state().await,
113115
MigratePhase::Finish => self.finish().await,
@@ -129,6 +131,11 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> DestinationProtocol<T> {
129131
// pre- and post-pause steps.
130132
self.run_phase(MigratePhase::RamPushPrePause).await?;
131133
self.run_phase(MigratePhase::RamPushPostPause).await?;
134+
135+
// Import of the time data *must* be done before we import device
136+
// state: the proper functioning of device timers depends on an adjusted
137+
// boot_hrtime.
138+
self.run_phase(MigratePhase::TimeData).await?;
132139
self.run_phase(MigratePhase::DeviceState).await?;
133140
self.run_phase(MigratePhase::RamPull).await?;
134141
self.run_phase(MigratePhase::ServerState).await?;
@@ -321,6 +328,110 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> DestinationProtocol<T> {
321328
self.import_device(&target, &device, &migrate_ctx)?;
322329
}
323330
}
331+
self.send_msg(codec::Message::Okay).await
332+
}
333+
334+
// Get the guest time data from the source, make updates to it based on the
335+
// new host, and write the data out to bhvye.
336+
async fn time_data(&mut self) -> Result<(), MigrateError> {
337+
// Read time data sent by the source and deserialize
338+
let raw: String = match self.read_msg().await? {
339+
codec::Message::Serialized(encoded) => encoded,
340+
msg => {
341+
error!(self.log(), "time data: unexpected message: {msg:?}");
342+
return Err(MigrateError::UnexpectedMessage);
343+
}
344+
};
345+
info!(self.log(), "VMM Time Data: {:?}", raw);
346+
let time_data_src: vmm::time::VmTimeData = ron::from_str(&raw)
347+
.map_err(|e| {
348+
MigrateError::TimeData(format!(
349+
"VMM Time Data deserialization error: {}",
350+
e
351+
))
352+
})?;
353+
probes::migrate_time_data_before!(|| {
354+
(
355+
time_data_src.guest_freq,
356+
time_data_src.guest_tsc,
357+
time_data_src.boot_hrtime,
358+
)
359+
});
360+
361+
// Take a snapshot of the host hrtime/wall clock time, then adjust
362+
// time data appropriately.
363+
let vmm_hdl = {
364+
let instance_guard = self.vm_controller.instance().lock();
365+
&instance_guard.machine().hdl.clone()
366+
};
367+
let (dst_hrt, dst_wc) = vmm::time::host_time_snapshot(vmm_hdl)
368+
.map_err(|e| {
369+
MigrateError::TimeData(format!(
370+
"could not read host time: {}",
371+
e
372+
))
373+
})?;
374+
let (time_data_dst, adjust) =
375+
vmm::time::adjust_time_data(time_data_src, dst_hrt, dst_wc)
376+
.map_err(|e| {
377+
MigrateError::TimeData(format!(
378+
"could not adjust VMM Time Data: {}",
379+
e
380+
))
381+
})?;
382+
383+
// In case import fails, log adjustments made to time data and fire
384+
// dtrace probe first
385+
if adjust.migrate_delta_negative {
386+
warn!(
387+
self.log(),
388+
"Found negative wall clock delta between target import \
389+
and source export:\n\
390+
- source wall clock time: {:?}\n\
391+
- target wall clock time: {:?}\n",
392+
time_data_src.wall_clock(),
393+
dst_wc
394+
);
395+
}
396+
info!(
397+
self.log(),
398+
"Time data adjustments:\n\
399+
- guest TSC freq: {} Hz = {} GHz\n\
400+
- guest uptime ns: {:?}\n\
401+
- migration time delta: {:?}\n\
402+
- guest_tsc adjustment = {} + {} = {}\n\
403+
- boot_hrtime adjustment = {} ---> {} - {} = {}\n\
404+
- dest highres clock time: {}\n\
405+
- dest wall clock time: {:?}",
406+
time_data_dst.guest_freq,
407+
time_data_dst.guest_freq as f64 / vmm::time::NS_PER_SEC as f64,
408+
adjust.guest_uptime_ns,
409+
adjust.migrate_delta,
410+
time_data_src.guest_tsc,
411+
adjust.guest_tsc_delta,
412+
time_data_dst.guest_tsc,
413+
time_data_src.boot_hrtime,
414+
dst_hrt,
415+
adjust.boot_hrtime_delta,
416+
time_data_dst.boot_hrtime,
417+
dst_hrt,
418+
dst_wc
419+
);
420+
probes::migrate_time_data_after!(|| {
421+
(
422+
time_data_dst.guest_freq,
423+
time_data_dst.guest_tsc,
424+
time_data_dst.boot_hrtime,
425+
adjust.guest_uptime_ns,
426+
adjust.migrate_delta.as_nanos() as u64,
427+
adjust.migrate_delta_negative,
428+
)
429+
});
430+
431+
// Import the adjusted time data
432+
vmm::time::import_time_data(vmm_hdl, time_data_dst).map_err(|e| {
433+
MigrateError::TimeData(format!("VMM Time Data import error: {}", e))
434+
})?;
324435

325436
self.send_msg(codec::Message::Okay).await
326437
}

bin/propolis-server/src/lib/migrate/mod.rs

+22
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ enum MigratePhase {
6464
Pause,
6565
RamPushPrePause,
6666
RamPushPostPause,
67+
TimeData,
6768
DeviceState,
6869
RamPull,
6970
ServerState,
@@ -77,6 +78,7 @@ impl std::fmt::Display for MigratePhase {
7778
MigratePhase::Pause => "Pause",
7879
MigratePhase::RamPushPrePause => "RamPushPrePause",
7980
MigratePhase::RamPushPostPause => "RamPushPostPause",
81+
MigratePhase::TimeData => "TimeData",
8082
MigratePhase::DeviceState => "DeviceState",
8183
MigratePhase::RamPull => "RamPull",
8284
MigratePhase::ServerState => "ServerState",
@@ -147,6 +149,10 @@ pub enum MigrateError {
147149
#[error("received out-of-phase message")]
148150
Phase,
149151

152+
/// Failed to export/import time data state
153+
#[error("failed to migrate VMM time data: {0}")]
154+
TimeData(String),
155+
150156
/// Failed to export/import device state for migration
151157
#[error("failed to migrate device state: {0}")]
152158
DeviceState(String),
@@ -207,6 +213,7 @@ impl From<MigrateError> for HttpError {
207213
| MigrateError::UnexpectedMessage
208214
| MigrateError::SourcePause
209215
| MigrateError::Phase
216+
| MigrateError::TimeData(_)
210217
| MigrateError::DeviceState(_)
211218
| MigrateError::RemoteError(_, _)
212219
| MigrateError::StateMachine(_) => {
@@ -422,4 +429,19 @@ mod probes {
422429
fn migrate_phase_end(step_desc: &str) {}
423430
fn migrate_xfer_ram_region(pages: u64, size: u64, paused: u8) {}
424431
fn migrate_xfer_ram_page(addr: u64, size: u64) {}
432+
fn migrate_time_data_before(
433+
src_guest_freq: u64,
434+
src_guest_tsc: u64,
435+
src_boot_hrtime: i64,
436+
) {
437+
}
438+
fn migrate_time_data_after(
439+
dst_guest_freq: u64,
440+
dst_guest_tsc: u64,
441+
dst_boot_hrtime: i64,
442+
guest_uptime: u64,
443+
migrate_delta_ns: u64,
444+
migrate_delta_negative: bool,
445+
) {
446+
}
425447
}

bin/propolis-server/src/lib/migrate/source.rs

+26
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use propolis::inventory::Order;
44
use propolis::migrate::{
55
MigrateCtx, MigrateStateError, Migrator, PayloadOutputs,
66
};
7+
use propolis::vmm;
78
use slog::{error, info, trace};
89
use std::convert::TryInto;
910
use std::io;
@@ -112,6 +113,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
112113
MigratePhase::RamPushPrePause | MigratePhase::RamPushPostPause => {
113114
self.ram_push(&step).await
114115
}
116+
MigratePhase::TimeData => self.time_data().await,
115117
MigratePhase::DeviceState => self.device_state().await,
116118
MigratePhase::RamPull => self.ram_pull().await,
117119
MigratePhase::ServerState => self.server_state().await,
@@ -130,6 +132,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
130132
self.run_phase(MigratePhase::RamPushPrePause).await?;
131133
self.run_phase(MigratePhase::Pause).await?;
132134
self.run_phase(MigratePhase::RamPushPostPause).await?;
135+
self.run_phase(MigratePhase::TimeData).await?;
133136
self.run_phase(MigratePhase::DeviceState).await?;
134137
self.run_phase(MigratePhase::RamPull).await?;
135138
self.run_phase(MigratePhase::ServerState).await?;
@@ -389,6 +392,29 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
389392
self.read_ok().await
390393
}
391394

395+
// Read and send over the time data
396+
async fn time_data(&mut self) -> Result<(), MigrateError> {
397+
let vmm_hdl = {
398+
let instance_guard = self.vm_controller.instance().lock();
399+
&instance_guard.machine().hdl.clone()
400+
};
401+
let vm_time_data =
402+
vmm::time::export_time_data(vmm_hdl).map_err(|e| {
403+
MigrateError::TimeData(format!(
404+
"VMM Time Data export error: {}",
405+
e
406+
))
407+
})?;
408+
info!(self.log(), "VMM Time Data: {:#?}", vm_time_data);
409+
410+
let time_data_serialized = ron::ser::to_string(&vm_time_data)
411+
.map_err(codec::ProtocolError::from)?;
412+
info!(self.log(), "VMM Time Data: {:#?}", time_data_serialized);
413+
self.send_msg(codec::Message::Serialized(time_data_serialized)).await?;
414+
415+
self.read_ok().await
416+
}
417+
392418
async fn ram_pull(&mut self) -> Result<(), MigrateError> {
393419
self.update_state(MigrationState::RamPush).await;
394420
let m = self.read_msg().await?;

lib/propolis/src/vmm/hdl.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use std::time::Duration;
1717
use crate::common::PAGE_SIZE;
1818
use crate::vmm::mem::Prot;
1919

20-
#[derive(Default, Copy, Clone)]
2120
/// Configurable options for VMM instance creation
2221
///
2322
/// # Options:
@@ -26,6 +25,7 @@ use crate::vmm::mem::Prot;
2625
/// - `use_reservoir`: Allocate guest memory (only) from the VMM reservoir. If
2726
/// this is enabled, and memory in excess of what is available from the
2827
/// reservoir is requested, creation of that guest memory resource will fail.
28+
#[derive(Default, Copy, Clone)]
2929
pub struct CreateOpts {
3030
pub force: bool,
3131
pub use_reservoir: bool,
@@ -418,7 +418,7 @@ impl VmmHdl {
418418
#[cfg(test)]
419419
impl VmmHdl {
420420
/// Build a VmmHdl instance suitable for unit tests, but nothing else, since
421-
/// it will not be backed by any real vmm reousrces.
421+
/// it will not be backed by any real vmm resources.
422422
pub(crate) fn new_test(mem_size: usize) -> Result<Self> {
423423
use tempfile::tempfile;
424424
let fp = tempfile()?;

lib/propolis/src/vmm/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod data;
44
pub mod hdl;
55
pub mod machine;
66
pub mod mem;
7+
pub mod time;
78

89
pub use hdl::*;
910
pub use machine::*;

0 commit comments

Comments
 (0)