Skip to content

Commit d7786a9

Browse files
Migrate guest time-related data
1 parent e335d21 commit d7786a9

File tree

20 files changed

+900
-29
lines changed

20 files changed

+900
-29
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ softnpu-lib = { git = "https://github.com/oxidecomputer/softnpu", branch = "main
118118
syn = "1.0"
119119
tempfile = "3.2"
120120
thiserror = "1.0"
121+
time = { path = "crates/time" }
121122
tokio = "1"
122123
tokio-tungstenite = "0.17"
123124
tokio-util = "0.7"

bin/propolis-server/src/lib/initializer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ pub fn build_instance(
6464
name: &str,
6565
spec: &InstanceSpec,
6666
use_reservoir: bool,
67-
_log: slog::Logger,
67+
log: slog::Logger,
6868
) -> Result<Instance> {
6969
let (lowmem, highmem) = get_spec_guest_ram_limits(spec);
7070
let create_opts = propolis::vmm::CreateOpts {
7171
force: true,
7272
use_reservoir,
7373
track_dirty: true,
7474
};
75-
let mut builder = Builder::new(name, create_opts)?
75+
let mut builder = Builder::new(name, log.clone(), create_opts)?
7676
.max_cpus(spec.devices.board.cpus)?
7777
.add_mem_region(0, lowmem, "lowmem")?
7878
.add_rom_region(0x1_0000_0000 - MAX_ROM_SIZE, MAX_ROM_SIZE, "bootrom")?

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,26 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> DestinationProtocol<T> {
313313
}
314314
}
315315
}
316+
self.send_msg(codec::Message::Okay).await?;
317+
318+
// Update VMM data
319+
let vmm_state: String = match self.read_msg().await? {
320+
codec::Message::Serialized(encoded) => encoded,
321+
msg => {
322+
error!(self.log(), "arch state: unexpected message: {msg:?}");
323+
return Err(MigrateError::UnexpectedMessage);
324+
}
325+
};
326+
info!(self.log(), "VMM State: {:?}", vmm_state);
327+
{
328+
let instance_guard = self.vm_controller.instance().lock();
329+
let vmm_hdl = &instance_guard.machine().hdl;
330+
let mut deserializer = ron::Deserializer::from_str(&vmm_state)
331+
.map_err(codec::ProtocolError::from)?;
332+
let deserializer =
333+
&mut <dyn erased_serde::Deserializer>::erase(&mut deserializer);
334+
vmm_hdl.import(deserializer)?;
335+
}
316336

317337
self.send_msg(codec::Message::Okay).await
318338
}

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

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ struct SourceProtocol<T: AsyncRead + AsyncWrite + Unpin + Send> {
6060

6161
/// Transport to the destination Instance.
6262
conn: WebSocketStream<T>,
63+
64+
// TODO: A hacky way for now to hang onto the VMM timing data between
65+
// migration phases.
66+
//
67+
// We want to read the VMM timing data as soon as we can after we pause the
68+
// source, and make adjustments on the destination as close as we can to
69+
// the end of the migration.
70+
//
71+
// This lets us hang on to the data between export in the pause phase,
72+
// the send the data in the device_state phase, after the bulk of the
73+
// migration time has passed.
74+
vmm_data: Option<propolis::vmm::migrate::BhyveVmV1>,
6375
}
6476

6577
impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
@@ -69,7 +81,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
6981
response_rx: tokio::sync::mpsc::Receiver<MigrateSourceResponse>,
7082
conn: WebSocketStream<T>,
7183
) -> Self {
72-
Self { vm_controller, command_tx, response_rx, conn }
84+
Self { vm_controller, command_tx, response_rx, conn, vmm_data: None }
7385
}
7486

7587
fn log(&self) -> &slog::Logger {
@@ -233,16 +245,28 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
233245
info!(self.log(), "Pausing devices");
234246
self.command_tx.send(MigrateSourceCommand::Pause).await.unwrap();
235247
let resp = self.response_rx.recv().await.unwrap();
236-
match resp {
237-
MigrateSourceResponse::Pause(Ok(())) => Ok(()),
238-
_ => {
239-
info!(
240-
self.log(),
241-
"Unexpected pause response from state worker: {:?}", resp
242-
);
243-
Err(MigrateError::SourcePause)
244-
}
248+
if !matches!(resp, MigrateSourceResponse::Pause(Ok(()))) {
249+
info!(
250+
self.log(),
251+
"Unexpected pause response from state worker: {:?}", resp
252+
);
253+
return Err(MigrateError::SourcePause);
245254
}
255+
256+
// Fetch the timing data values after we pause, but before lengthier
257+
// migration steps, so we can correctly account for migration time in
258+
// our adjustments.
259+
self.timing_data_snapshot().await
260+
}
261+
262+
async fn timing_data_snapshot(&mut self) -> Result<(), MigrateError> {
263+
let instance_guard = self.vm_controller.instance().lock();
264+
let vmm_hdl = &instance_guard.machine().hdl;
265+
let raw = vmm_hdl.export_vm()?;
266+
self.vmm_data = Some(raw);
267+
info!(self.log(), "VMM State: {:#?}", self.vmm_data);
268+
269+
Ok(())
246270
}
247271

248272
async fn device_state(&mut self) -> Result<(), MigrateError> {
@@ -286,6 +310,14 @@ impl<T: AsyncRead + AsyncWrite + Unpin + Send> SourceProtocol<T> {
286310
.await?;
287311

288312
self.send_msg(codec::Message::Okay).await?;
313+
self.read_ok().await?;
314+
315+
// Migrate VMM-wide data
316+
let vmm_data = self.vmm_data.as_mut().unwrap();
317+
let vmm_state = ron::ser::to_string(&vmm_data)
318+
.map_err(codec::ProtocolError::from)?;
319+
info!(self.log(), "VMM State: {:#?}", vmm_state);
320+
self.send_msg(codec::Message::Serialized(vmm_state)).await?;
289321
self.read_ok().await
290322
}
291323

bin/propolis-standalone/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,12 +600,14 @@ impl std::ops::DerefMut for InnerGuard<'_> {
600600

601601
fn build_instance(
602602
name: &str,
603+
log: slog::Logger,
603604
max_cpu: u8,
604605
lowmem: usize,
605606
highmem: usize,
606607
) -> Result<propolis::Instance> {
607608
let mut builder = Builder::new(
608609
name,
610+
log,
609611
propolis::vmm::CreateOpts { force: true, ..Default::default() },
610612
)?
611613
.max_cpus(max_cpu)?
@@ -687,7 +689,7 @@ fn setup_instance(
687689

688690
slog::info!(log, "Creating VM with {} vCPUs, {} lowmem, {} highmem",
689691
cpus, lowmem, highmem;);
690-
let pinst = build_instance(vm_name, cpus, lowmem, highmem)
692+
let pinst = build_instance(vm_name, log.clone(), cpus, lowmem, highmem)
691693
.context("Failed to create VM Instance")?;
692694
let inst = Instance::new(pinst, config.clone(), from_restore, log.clone());
693695
slog::info!(log, "VM created"; "name" => vm_name);

crates/bhyve-api/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ unsafe fn ioctl(
230230
#[repr(u32)]
231231
#[derive(IntoPrimitive)]
232232
pub enum ApiVersion {
233+
/// Adds support for modifying timing-related data of a guest.
234+
V10 = 10,
235+
233236
/// Revamps ioctls for administrating the VMM memory reservoir and adds
234237
/// kstat for tracking its capacity and utilization.
235238
V9 = 9,
@@ -250,7 +253,7 @@ pub enum ApiVersion {
250253
}
251254
impl ApiVersion {
252255
pub const fn current() -> Self {
253-
Self::V9
256+
Self::V10
254257
}
255258
}
256259

crates/bhyve-api/sys/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ pub const VM_MAXCPU: usize = 32;
1313
/// This is the VMM interface version which bhyve_api expects to operate
1414
/// against. All constants and structs defined by the crate are done so in
1515
/// terms of that specific version.
16-
pub const VMM_CURRENT_INTERFACE_VERSION: u32 = 9;
16+
pub const VMM_CURRENT_INTERFACE_VERSION: u32 = 10;

crates/bhyve-api/sys/src/vmm_data.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub const VDC_ATPIC: u16 = 9;
2121
pub const VDC_HPET: u16 = 10;
2222
pub const VDC_PM_TIMER: u16 = 11;
2323
pub const VDC_RTC: u16 = 12;
24+
pub const VDC_VMM_TIMING: u16 = 13;
2425

2526
#[repr(C)]
2627
#[derive(Copy, Clone, Default, Serialize, Deserialize)]
@@ -189,7 +190,19 @@ impl Default for vdi_rtc_v1 {
189190
}
190191
}
191192

193+
#[repr(C)]
194+
#[derive(Copy, Clone, Default, Serialize, Deserialize)]
195+
pub struct vdi_timing_info_v1 {
196+
pub vt_guest_freq: u64,
197+
pub vt_guest_tsc: u64,
198+
pub vt_boot_hrtime: i64,
199+
pub vt_hrtime: i64,
200+
pub vt_hres_sec: u64,
201+
pub vt_hres_ns: u64,
202+
}
203+
192204
// VDC_VMM_ARCH v1 data identifiers
205+
// TODO: fix this up when illumos 15143 lands
193206

194207
/// Offset of guest TSC from system at time of boot
195208
pub const VAI_TSC_BOOT_OFFSET: u32 = 1;

crates/time/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "time"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
libc.workspace = true

0 commit comments

Comments
 (0)