Skip to content

Commit 4101b11

Browse files
committed
XXX migration progress and statistics endpoint
1 parent dc905d9 commit 4101b11

File tree

6 files changed

+543
-10
lines changed

6 files changed

+543
-10
lines changed

vm-migration/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
//
55

66
use anyhow::anyhow;
7+
pub use progress::MigrationPhase;
78
use serde::{Deserialize, Serialize};
89
use thiserror::Error;
910

1011
use crate::protocol::MemoryRangeTable;
1112

1213
mod bitpos_iterator;
14+
pub mod progress;
1315
pub mod protocol;
1416

1517
#[derive(Error, Debug)]

vm-migration/src/progress.rs

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// Copyright © 2025 Cyberus Technology GmbH
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
//! Module for reporting of the live-migration progress.
6+
7+
use std::num::NonZeroU64;
8+
9+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
10+
pub enum TransportationMode {
11+
Local,
12+
Tcp { connections: NonZeroU64, tls: bool },
13+
}
14+
15+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
16+
pub struct MemoryTransmissionInfo {
17+
/// The memory iteration (only in precopy mode).
18+
pub memory_iteration: u64,
19+
pub memory_bytes_total: u64,
20+
pub memory_bytes_transmitted: u64,
21+
pub memory_pages_4k_transmitted: u64,
22+
pub memory_bytes_remaining_iteration: u64,
23+
pub memory_bytes_remaining_total: u64,
24+
}
25+
26+
/// The different phases of the migration. Helper for [`MigrationProgressAndStatus`].
27+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
28+
pub enum MigrationPhase {
29+
/// The migration starts. Handshake and transfer of VM config.
30+
Starting {
31+
/// The timestamp where this phase began.
32+
timestamp_phase_begin: u64,
33+
},
34+
/// Transfer of VM memory in precopy mode.
35+
///
36+
/// Not used for local migrations.
37+
MemoryPrecopy {
38+
/// The timestamp where this phase began.
39+
timestamp_phase_begin: u64,
40+
/// The current memory info.
41+
memory_info: MemoryTransmissionInfo,
42+
},
43+
/// Transfer of VM memory in postcopy mode.
44+
///
45+
/// This follows after a precopy phase.
46+
///
47+
/// Not used for local migrations.
48+
MemoryPostcopy {},
49+
/// The VM migration is completing. This means the last chunks of memory
50+
/// are transmitted as well as the final VM state (vCPUs, devices).
51+
Completing {
52+
/// The timestamp where this phase began.
53+
timestamp_phase_begin: u64,
54+
/// The final memory transmission info.
55+
memory_info: MemoryTransmissionInfo,
56+
},
57+
Completed {
58+
/// The timestamp where this phase began.
59+
timestamp_phase_begin: u64,
60+
/// The final memory transmission info.
61+
memory_info: MemoryTransmissionInfo,
62+
},
63+
Failed {
64+
/// The timestamp where this phase began.
65+
timestamp_phase_begin: u64,
66+
/// Stringifies version of the underlying error.
67+
error: String,
68+
/// The final memory transmission info.
69+
memory_info: Option<MemoryTransmissionInfo>,
70+
},
71+
Cancelled {
72+
/// The timestamp where this phase began.
73+
timestamp_phase_begin: u64,
74+
/// The final memory transmission info.
75+
memory_info: Option<MemoryTransmissionInfo>,
76+
},
77+
}
78+
79+
/// Type holding a current snapshot about the progress and status information
80+
/// of an ongoing live migration.
81+
///
82+
/// The states correspond to the [live-migration protocol].
83+
///
84+
/// [live-migration protocol]: super::protocol
85+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
86+
pub struct MigrationProgressAndStatus {
87+
/// UNIX timestamp of the start of the live-migration process.
88+
pub timestamp_begin: u64,
89+
/// UNIX timestamp of the snapshot.
90+
pub timestamp_snapshot: u64,
91+
/// Configured target downtime.
92+
pub downtime_ms_target: u64,
93+
/// The currently expected downtime.
94+
pub downtime_ms_expected: Option<u64>,
95+
/// Snapshot of the current phase.
96+
pub phase: MigrationPhase,
97+
/// The used transportation mode.
98+
pub transportation_mode: TransportationMode,
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
use super::*;
104+
105+
// Helpful to see what the API will look like.
106+
#[test]
107+
fn print_json() {
108+
let starting = MigrationProgressAndStatus {
109+
timestamp_begin: 0,
110+
timestamp_snapshot: 0,
111+
downtime_ms_target: 0,
112+
downtime_ms_expected: None,
113+
phase: MigrationPhase::Starting {
114+
timestamp_phase_begin: 0,
115+
memory_bytes_total: 0,
116+
},
117+
transportation_mode: TransportationMode::Tcp {
118+
connections: NonZeroU64::new(3).unwrap(),
119+
tls: false,
120+
},
121+
};
122+
let memory_precopy = MigrationProgressAndStatus {
123+
timestamp_begin: 0,
124+
timestamp_snapshot: 0,
125+
downtime_ms_target: 0,
126+
downtime_ms_expected: None,
127+
phase: MigrationPhase::MemoryPrecopy {
128+
timestamp_phase_begin: 0,
129+
memory_info: MemoryTransmissionInfo {
130+
memory_iteration: 0,
131+
memory_bytes_total: 0,
132+
memory_bytes_transmitted: 0,
133+
memory_pages_4k_transmitted: 0,
134+
memory_bytes_remaining_iteration: 0,
135+
memory_bytes_remaining_total: 0,
136+
},
137+
},
138+
transportation_mode: TransportationMode::Tcp {
139+
connections: NonZeroU64::new(3).unwrap(),
140+
tls: false,
141+
},
142+
};
143+
let memory_postcopy = MigrationProgressAndStatus {
144+
timestamp_begin: 0,
145+
timestamp_snapshot: 0,
146+
downtime_ms_target: 0,
147+
downtime_ms_expected: None,
148+
phase: MigrationPhase::MemoryPostcopy {},
149+
transportation_mode: TransportationMode::Tcp {
150+
connections: NonZeroU64::new(3).unwrap(),
151+
tls: false,
152+
},
153+
};
154+
let completing = MigrationProgressAndStatus {
155+
timestamp_begin: 0,
156+
timestamp_snapshot: 0,
157+
downtime_ms_target: 0,
158+
downtime_ms_expected: None,
159+
phase: MigrationPhase::Completing {
160+
timestamp_phase_begin: 0,
161+
memory_info: MemoryTransmissionInfo {
162+
memory_iteration: 0,
163+
memory_bytes_total: 0,
164+
memory_bytes_transmitted: 0,
165+
memory_pages_4k_transmitted: 0,
166+
memory_bytes_remaining_iteration: 0,
167+
memory_bytes_remaining_total: 0,
168+
},
169+
},
170+
transportation_mode: TransportationMode::Tcp {
171+
connections: NonZeroU64::new(3).unwrap(),
172+
tls: false,
173+
},
174+
};
175+
let completed = MigrationProgressAndStatus {
176+
timestamp_begin: 0,
177+
timestamp_snapshot: 0,
178+
downtime_ms_target: 0,
179+
downtime_ms_expected: None,
180+
phase: MigrationPhase::Completed {
181+
timestamp_phase_begin: 0,
182+
memory_info: MemoryTransmissionInfo {
183+
memory_iteration: 0,
184+
memory_bytes_total: 0,
185+
memory_bytes_transmitted: 0,
186+
memory_pages_4k_transmitted: 0,
187+
memory_bytes_remaining_iteration: 0,
188+
memory_bytes_remaining_total: 0,
189+
},
190+
},
191+
transportation_mode: TransportationMode::Tcp {
192+
connections: NonZeroU64::new(3).unwrap(),
193+
tls: false,
194+
},
195+
};
196+
let failed = MigrationProgressAndStatus {
197+
timestamp_begin: 0,
198+
timestamp_snapshot: 0,
199+
downtime_ms_target: 0,
200+
downtime_ms_expected: None,
201+
phase: MigrationPhase::Failed {
202+
timestamp_phase_begin: 0,
203+
error: "some-error-lol".to_string(),
204+
memory_info: Some(MemoryTransmissionInfo {
205+
memory_iteration: 0,
206+
memory_bytes_total: 0,
207+
memory_bytes_transmitted: 0,
208+
memory_pages_4k_transmitted: 0,
209+
memory_bytes_remaining_iteration: 0,
210+
memory_bytes_remaining_total: 0,
211+
}),
212+
},
213+
transportation_mode: TransportationMode::Tcp {
214+
connections: NonZeroU64::new(3).unwrap(),
215+
tls: false,
216+
},
217+
};
218+
let cancelled = MigrationProgressAndStatus {
219+
timestamp_begin: 0,
220+
timestamp_snapshot: 0,
221+
downtime_ms_target: 0,
222+
downtime_ms_expected: None,
223+
phase: MigrationPhase::Cancelled {
224+
timestamp_phase_begin: 0,
225+
memory_info: Some(MemoryTransmissionInfo {
226+
memory_iteration: 0,
227+
memory_bytes_total: 0,
228+
memory_bytes_transmitted: 0,
229+
memory_pages_4k_transmitted: 0,
230+
memory_bytes_remaining_iteration: 0,
231+
memory_bytes_remaining_total: 0,
232+
}),
233+
},
234+
transportation_mode: TransportationMode::Tcp {
235+
connections: NonZeroU64::new(3).unwrap(),
236+
tls: false,
237+
},
238+
};
239+
240+
let vals = [
241+
starting,
242+
memory_precopy,
243+
memory_postcopy,
244+
completing,
245+
completed,
246+
failed,
247+
cancelled,
248+
];
249+
for val in vals {
250+
println!(
251+
"{:?}:\n{}\n\n",
252+
val,
253+
serde_json::to_string_pretty(&val).unwrap()
254+
);
255+
}
256+
}
257+
}

vmm/src/api/http/http_endpoint.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ use crate::api::VmCoredump;
5353
use crate::api::http::{EndpointHandler, HttpError, error_response};
5454
use crate::api::{
5555
AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem,
56-
VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete, VmNmi, VmPause,
57-
VmPowerButton, VmReboot, VmReceiveMigration, VmReceiveMigrationData, VmRemoveDevice, VmResize,
58-
VmResizeDisk, VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot,
56+
VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot, VmConfig, VmCounters, VmDelete,
57+
VmMigrationProgress, VmNmi, VmPause, VmPowerButton, VmReboot, VmReceiveMigration,
58+
VmReceiveMigrationData, VmRemoveDevice, VmResize, VmResizeDisk, VmResizeZone, VmRestore,
59+
VmResume, VmSendMigration, VmShutdown, VmSnapshot,
5960
};
6061
use crate::config::{RestoreConfig, RestoredNetConfig};
6162
use crate::cpu::Error as CpuError;
@@ -241,6 +242,7 @@ vm_action_put_handler_body!(VmRemoveDevice);
241242
vm_action_put_handler_body!(VmResizeDisk);
242243
vm_action_put_handler_body!(VmResizeZone);
243244
vm_action_put_handler_body!(VmSnapshot);
245+
vm_action_put_handler_body!(VmMigrationProgress);
244246

245247
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
246248
vm_action_put_handler_body!(VmCoredump);

vmm/src/api/http/mod.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ use self::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdow
2828
use crate::api::VmCoredump;
2929
use crate::api::{
3030
AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice,
31-
VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmNmi, VmPause, VmPowerButton, VmReboot,
32-
VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk, VmResizeZone, VmRestore, VmResume,
33-
VmSendMigration, VmShutdown, VmSnapshot,
31+
VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmMigrationProgress, VmNmi, VmPause,
32+
VmPowerButton, VmReboot, VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeDisk,
33+
VmResizeZone, VmRestore, VmResume, VmSendMigration, VmShutdown, VmSnapshot,
3434
};
3535
use crate::landlock::Landlock;
3636
use crate::seccomp_filters::{Thread, get_seccomp_filter};
@@ -273,6 +273,10 @@ pub static HTTP_ROUTES: LazyLock<HttpRoutes> = LazyLock::new(|| {
273273
endpoint!("/vm.shutdown"),
274274
Box::new(VmActionHandler::new(&VmShutdown)),
275275
);
276+
r.routes.insert(
277+
endpoint!("/vm.migration-progress"),
278+
Box::new(VmActionHandler::new(&VmMigrationProgress)),
279+
);
276280
r.routes.insert(
277281
endpoint!("/vm.snapshot"),
278282
Box::new(VmActionHandler::new(&VmSnapshot)),

0 commit comments

Comments
 (0)