mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vm-migration: context: move unit tests into sub module
This helps to better separate the unit tests from the new ones in the following commit. On-behalf-of: SAP philipp.schuster@sap.com Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This commit is contained in:
committed by
Rob Bradford
parent
c99ed77d1a
commit
b9c3cfb14d
@@ -238,169 +238,172 @@ impl Display for MemoryMigrationContext {
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::*;
|
||||
use crate::protocol::MemoryRange;
|
||||
mod memory_migration_ctx_tests {
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn make_table(bytes: u64) -> MemoryRangeTable {
|
||||
let mut table = MemoryRangeTable::default();
|
||||
if bytes > 0 {
|
||||
table.push(MemoryRange {
|
||||
gpa: 0,
|
||||
length: bytes,
|
||||
});
|
||||
use super::*;
|
||||
use crate::protocol::MemoryRange;
|
||||
|
||||
fn make_table(bytes: u64) -> MemoryRangeTable {
|
||||
let mut table = MemoryRangeTable::default();
|
||||
if bytes > 0 {
|
||||
table.push(MemoryRange {
|
||||
gpa: 0,
|
||||
length: bytes,
|
||||
});
|
||||
}
|
||||
table
|
||||
}
|
||||
table
|
||||
}
|
||||
|
||||
/// A controlled migration scenario with fixed timing offsets.
|
||||
///
|
||||
/// ```text
|
||||
/// migration_begin
|
||||
/// + 1.0s -> iteration_begin
|
||||
/// + 1.1s -> transfer_begin
|
||||
/// + 2.0s -> transfer ends (transfer_duration = 0.9s)
|
||||
/// + 2.1s -> iteration ends (iteration_duration = 1.1s, overhead = 0.2s)
|
||||
/// ```
|
||||
struct Scenario {
|
||||
migration_begin: Instant,
|
||||
iteration_begin: Instant,
|
||||
transfer_begin: Instant,
|
||||
transfer_duration: Duration,
|
||||
}
|
||||
/// A controlled migration scenario with fixed timing offsets.
|
||||
///
|
||||
/// ```text
|
||||
/// migration_begin
|
||||
/// + 1.0s -> iteration_begin
|
||||
/// + 1.1s -> transfer_begin
|
||||
/// + 2.0s -> transfer ends (transfer_duration = 0.9s)
|
||||
/// + 2.1s -> iteration ends (iteration_duration = 1.1s, overhead = 0.2s)
|
||||
/// ```
|
||||
struct Scenario {
|
||||
migration_begin: Instant,
|
||||
iteration_begin: Instant,
|
||||
transfer_begin: Instant,
|
||||
transfer_duration: Duration,
|
||||
}
|
||||
|
||||
impl Scenario {
|
||||
/// We use a fixed point in the past so all offsets are in the past too,
|
||||
/// meaning elapsed() calls in the code under test will be >= our durations.
|
||||
const FIXPOINT_PAST: Duration = Duration::from_secs(10);
|
||||
impl Scenario {
|
||||
/// We use a fixed point in the past so all offsets are in the past too,
|
||||
/// meaning elapsed() calls in the code under test will be >= our durations.
|
||||
const FIXPOINT_PAST: Duration = Duration::from_secs(10);
|
||||
|
||||
fn new() -> Self {
|
||||
// Use a fixed point in the past so all offsets are in the past too,
|
||||
// meaning elapsed() calls in the code under test will be >= our durations.
|
||||
let migration_begin = Instant::now() - Self::FIXPOINT_PAST;
|
||||
Self {
|
||||
migration_begin,
|
||||
iteration_begin: migration_begin + Duration::from_millis(1000),
|
||||
transfer_begin: migration_begin + Duration::from_millis(1100),
|
||||
transfer_duration: Duration::from_millis(900),
|
||||
fn new() -> Self {
|
||||
// Use a fixed point in the past so all offsets are in the past too,
|
||||
// meaning elapsed() calls in the code under test will be >= our durations.
|
||||
let migration_begin = Instant::now() - Self::FIXPOINT_PAST;
|
||||
Self {
|
||||
migration_begin,
|
||||
iteration_begin: migration_begin + Duration::from_millis(1000),
|
||||
transfer_begin: migration_begin + Duration::from_millis(1100),
|
||||
transfer_duration: Duration::from_millis(900),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_ctx(&self) -> MemoryMigrationContext {
|
||||
let mut ctx = MemoryMigrationContext::new();
|
||||
// Override migration_begin with our controlled value.
|
||||
ctx.migration_begin = self.migration_begin;
|
||||
ctx
|
||||
}
|
||||
}
|
||||
|
||||
fn make_ctx(&self) -> MemoryMigrationContext {
|
||||
let mut ctx = MemoryMigrationContext::new();
|
||||
// Override migration_begin with our controlled value.
|
||||
ctx.migration_begin = self.migration_begin;
|
||||
ctx
|
||||
#[test]
|
||||
fn before_transfer_updates_begin_and_bytes() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(4096));
|
||||
|
||||
assert_eq!(ctx.iteration_begin, s.iteration_begin);
|
||||
assert_eq!(ctx.current_iteration_total_bytes, 4096);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn before_transfer_estimated_downtime() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
// Empty table -> zero downtime regardless of bandwidth
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(0));
|
||||
assert_eq!(ctx.estimated_downtime, Some(Duration::ZERO));
|
||||
|
||||
// No bandwidth yet -> None
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
assert_eq!(ctx.estimated_downtime, None);
|
||||
|
||||
// 1024 B/s, 1024 bytes -> 1s
|
||||
ctx.bandwidth_bytes_per_second = 1024.0;
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
assert_eq!(ctx.estimated_downtime, Some(Duration::from_secs(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_transfer_updates_timing_and_bandwidth() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration);
|
||||
|
||||
assert_eq!(ctx.transfer_begin, s.transfer_begin);
|
||||
assert_eq!(ctx.transfer_duration, Some(s.transfer_duration));
|
||||
// 1024 bytes / 0.9s
|
||||
assert_eq!(ctx.bandwidth_bytes_per_second, 1024.0 / 0.9);
|
||||
// iteration_duration = time from iteration_begin until now (>= transfer_duration)
|
||||
assert!(ctx.iteration_duration.unwrap() >= s.transfer_duration);
|
||||
// Zero transfer_duration -> bandwidth is 0.0, no division by zero
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, Duration::ZERO);
|
||||
assert_eq!(ctx.bandwidth_bytes_per_second, 0.0);
|
||||
|
||||
// Check finalize() sets migration duration
|
||||
assert_eq!(ctx.migration_duration, None);
|
||||
ctx.finalize();
|
||||
assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_iterations_accumulate_bytes_and_feed_downtime_estimate() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
// Iteration 0: no bandwidth yet -> downtime is None
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
assert_eq!(ctx.estimated_downtime, None);
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration);
|
||||
assert_eq!(ctx.total_sent_bytes, 1024);
|
||||
|
||||
// Iteration 1: bandwidth now known -> downtime is Some
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(2048));
|
||||
assert!(ctx.estimated_downtime.is_some());
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration);
|
||||
assert_eq!(ctx.total_sent_bytes, 1024 + 2048);
|
||||
|
||||
// Check finalize() sets migration duration
|
||||
assert_eq!(ctx.migration_duration, None);
|
||||
ctx.finalize();
|
||||
assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// The display format is specifically crafted to be very insightful in logs.
|
||||
/// Therefore, we have a dedicated test for that format.
|
||||
fn display_format() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
// Iteration 0: 1 MiB in 1s
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024 * 1024));
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1));
|
||||
ctx.iteration += 1;
|
||||
|
||||
// Iteration 1: 512 KiB in 1s; fix migration_duration for deterministic elapsed/avg_bw
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(512 * 1024));
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1));
|
||||
|
||||
ctx.migration_duration = Some(Duration::from_secs(2));
|
||||
let out = ctx.to_string();
|
||||
|
||||
assert_eq!(
|
||||
out,
|
||||
"iter=1 curr=1MiB total=2MiB bw=0.50MiB/s transfer=1.00s overhead=8000ms est_downtime=500ms elapsed=2.00s avg_bw=0.15MiB/s"
|
||||
);
|
||||
|
||||
// Should change elapsed() time!
|
||||
// Since this is at least 10s, we never face timing issues in CI!
|
||||
ctx.finalize();
|
||||
let out2 = ctx.to_string();
|
||||
assert_ne!(out2, out, "elapsed time should have changed! is={out2}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn before_transfer_updates_begin_and_bytes() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(4096));
|
||||
|
||||
assert_eq!(ctx.iteration_begin, s.iteration_begin);
|
||||
assert_eq!(ctx.current_iteration_total_bytes, 4096);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn before_transfer_estimated_downtime() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
// Empty table -> zero downtime regardless of bandwidth
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(0));
|
||||
assert_eq!(ctx.estimated_downtime, Some(Duration::ZERO));
|
||||
|
||||
// No bandwidth yet -> None
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
assert_eq!(ctx.estimated_downtime, None);
|
||||
|
||||
// 1024 B/s, 1024 bytes -> 1s
|
||||
ctx.bandwidth_bytes_per_second = 1024.0;
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
assert_eq!(ctx.estimated_downtime, Some(Duration::from_secs(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_transfer_updates_timing_and_bandwidth() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration);
|
||||
|
||||
assert_eq!(ctx.transfer_begin, s.transfer_begin);
|
||||
assert_eq!(ctx.transfer_duration, Some(s.transfer_duration));
|
||||
// 1024 bytes / 0.9s
|
||||
assert_eq!(ctx.bandwidth_bytes_per_second, 1024.0 / 0.9);
|
||||
// iteration_duration = time from iteration_begin until now (>= transfer_duration)
|
||||
assert!(ctx.iteration_duration.unwrap() >= s.transfer_duration);
|
||||
// Zero transfer_duration -> bandwidth is 0.0, no division by zero
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, Duration::ZERO);
|
||||
assert_eq!(ctx.bandwidth_bytes_per_second, 0.0);
|
||||
|
||||
// Check finalize() sets migration duration
|
||||
assert_eq!(ctx.migration_duration, None);
|
||||
ctx.finalize();
|
||||
assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_iterations_accumulate_bytes_and_feed_downtime_estimate() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
// Iteration 0: no bandwidth yet -> downtime is None
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024));
|
||||
assert_eq!(ctx.estimated_downtime, None);
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration);
|
||||
assert_eq!(ctx.total_sent_bytes, 1024);
|
||||
|
||||
// Iteration 1: bandwidth now known -> downtime is Some
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(2048));
|
||||
assert!(ctx.estimated_downtime.is_some());
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, s.transfer_duration);
|
||||
assert_eq!(ctx.total_sent_bytes, 1024 + 2048);
|
||||
|
||||
// Check finalize() sets migration duration
|
||||
assert_eq!(ctx.migration_duration, None);
|
||||
ctx.finalize();
|
||||
assert!(matches!(ctx.migration_duration, Some(d) if d >= Scenario::FIXPOINT_PAST));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// The display format is specifically crafted to be very insightful in logs.
|
||||
/// Therefore, we have a dedicated test for that format.
|
||||
fn display_format() {
|
||||
let s = Scenario::new();
|
||||
let mut ctx = s.make_ctx();
|
||||
|
||||
// Iteration 0: 1 MiB in 1s
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(1024 * 1024));
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1));
|
||||
ctx.iteration += 1;
|
||||
|
||||
// Iteration 1: 512 KiB in 1s; fix migration_duration for deterministic elapsed/avg_bw
|
||||
ctx.update_metrics_before_transfer(s.iteration_begin, &make_table(512 * 1024));
|
||||
ctx.update_metrics_after_transfer(s.transfer_begin, Duration::from_secs(1));
|
||||
|
||||
ctx.migration_duration = Some(Duration::from_secs(2));
|
||||
let out = ctx.to_string();
|
||||
|
||||
assert_eq!(
|
||||
out,
|
||||
"iter=1 curr=1MiB total=2MiB bw=0.50MiB/s transfer=1.00s overhead=8000ms est_downtime=500ms elapsed=2.00s avg_bw=0.15MiB/s"
|
||||
);
|
||||
|
||||
// Should change elapsed() time!
|
||||
// Since this is at least 10s, we never face timing issues in CI!
|
||||
ctx.finalize();
|
||||
let out2 = ctx.to_string();
|
||||
assert_ne!(out2, out, "elapsed time should have changed! is={out2}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user