mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vm-migration: add protocol versioning
Add protocol-side support for migration protocol versioning. Use the existing 6-byte Start command header, which was previously zero padding, to carry the sender's migration protocol version without changing the wire layout. Store the version as a little-endian u16 in the first two bytes and ignore the remaining four bytes. A zeroed command header continues to mean a legacy v0 sender. This keeps the message flow unchanged for rollout: Start is still followed by plain OK or Error (Aborted), and no new command is needed. On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
This commit is contained in:
committed by
Rob Bradford
parent
181d29ee90
commit
b27faaaa45
@@ -73,8 +73,22 @@
|
|||||||
//! Source->>Destination: Complete
|
//! Source->>Destination: Complete
|
||||||
//! Destination-->>Source: OK
|
//! Destination-->>Source: OK
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Protocol Versioning
|
||||||
|
//!
|
||||||
|
//! `Start` carries the sender's migration protocol version.
|
||||||
|
//! A zeroed version field is treated as legacy protocol `v0`.
|
||||||
|
//!
|
||||||
|
//! The destination validates that version and replies with a plain `OK` or
|
||||||
|
//! `Error`.
|
||||||
|
//!
|
||||||
|
//! Only the current and immediately previous protocol versions are
|
||||||
|
//! supported. Compatibility is one-way, from older protocol versions
|
||||||
|
//! to newer ones.
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::mem::size_of;
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
use std::{mem, slice};
|
use std::{mem, slice};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@@ -127,18 +141,33 @@ pub enum Command {
|
|||||||
CompletePaused = 8,
|
CompletePaused = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Newest migration protocol version sent by this implementation.
|
||||||
|
pub const CURRENT_PROTOCOL_VERSION: u16 = 0;
|
||||||
|
|
||||||
|
/// Returns the current migration protocol version and the previous version, if any.
|
||||||
|
pub fn supported_protocol_versions() -> RangeInclusive<u16> {
|
||||||
|
CURRENT_PROTOCOL_VERSION.saturating_sub(1)..=CURRENT_PROTOCOL_VERSION
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Default, Copy, Clone)]
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
command: Command,
|
command: Command,
|
||||||
padding: [u8; 6],
|
command_headers: [u8; 6],
|
||||||
length: u64, // Length of payload for command excluding the Request struct
|
/// Length of payload for command excluding the Request struct
|
||||||
|
length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: Request contains a series of integers with no implicit padding
|
// SAFETY: Request contains a series of integers with no implicit padding
|
||||||
unsafe impl ByteValued for Request {}
|
unsafe impl ByteValued for Request {}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
|
fn encode_sender_version(version: u16) -> [u8; 6] {
|
||||||
|
let mut command_headers = [0; 6];
|
||||||
|
command_headers[..size_of::<u16>()].copy_from_slice(&version.to_le_bytes());
|
||||||
|
command_headers
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(command: Command, length: u64) -> Self {
|
pub fn new(command: Command, length: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
command,
|
command,
|
||||||
@@ -148,7 +177,11 @@ impl Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn start() -> Self {
|
pub fn start() -> Self {
|
||||||
Self::new(Command::Start, 0)
|
Self {
|
||||||
|
command: Command::Start,
|
||||||
|
command_headers: Self::encode_sender_version(CURRENT_PROTOCOL_VERSION),
|
||||||
|
length: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(length: u64) -> Self {
|
pub fn state(length: u64) -> Self {
|
||||||
@@ -189,6 +222,10 @@ impl Request {
|
|||||||
self.length
|
self.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn command_headers(&self) -> &[u8; 6] {
|
||||||
|
&self.command_headers
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_from(fd: &mut dyn Read) -> Result<Request, MigratableError> {
|
pub fn read_from(fd: &mut dyn Read) -> Result<Request, MigratableError> {
|
||||||
let mut request = Request::default();
|
let mut request = Request::default();
|
||||||
fd.read_exact(Self::as_mut_slice(&mut request))
|
fd.read_exact(Self::as_mut_slice(&mut request))
|
||||||
@@ -485,7 +522,21 @@ impl MemoryRangeTable {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod unit_tests {
|
||||||
use crate::protocol::{MemoryRange, MemoryRangeTable};
|
use crate::protocol::{Command, MemoryRange, MemoryRangeTable, Request};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_start_request_ignores_residual_command_headers_bytes() {
|
||||||
|
let request = Request {
|
||||||
|
command: Command::Start,
|
||||||
|
command_headers: [1, 0, 0xaa, 0xbb, 0xcc, 0xdd],
|
||||||
|
length: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
u16::from_le_bytes([request.command_headers()[0], request.command_headers()[1]]),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_memory_range_table_from_dirty_ranges_iter() {
|
fn test_memory_range_table_from_dirty_ranges_iter() {
|
||||||
|
|||||||
Reference in New Issue
Block a user