mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm: Introduce MigrationMode migration option
Introducing a migration mode to both sides of the migration (send and receive), so that a user can desribe which way the memory should be migrated between the source and destination VMs. For now, we only introduce `precopy` and `postcopy` as viable options, but we can expect other modes (more optimized) to be added in the future. Signed-off-by: Sebastien Boeuf <sboeuf@meta.com> Assisted-by: Claude:claude-opus-4-8
This commit is contained in:
+108
-4
@@ -270,6 +270,31 @@ pub struct VmCoredumpData {
|
|||||||
pub destination_url: String,
|
pub destination_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Memory transfer mode for a migration.
|
||||||
|
#[derive(Copy, Clone, Default, Deserialize, Serialize, Debug, PartialEq, Eq)]
|
||||||
|
pub enum MigrationMode {
|
||||||
|
/// Transfer all guest memory before the destination resumes.
|
||||||
|
#[default]
|
||||||
|
Precopy,
|
||||||
|
/// Resume the destination first and fault guest pages in on demand.
|
||||||
|
/// This is an experimental mode. It uses a single connection even
|
||||||
|
/// when parallel connections are configured. Pages are served on
|
||||||
|
/// demand, but a background faulting mechanism also pulls in the
|
||||||
|
/// remaining pages to speed up completion.
|
||||||
|
Postcopy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for MigrationMode {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"precopy" => Ok(MigrationMode::Precopy),
|
||||||
|
"postcopy" => Ok(MigrationMode::Postcopy),
|
||||||
|
_ => Err(format!("Invalid migration mode: {s}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, Default, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Default, Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct VmReceiveMigrationData {
|
pub struct VmReceiveMigrationData {
|
||||||
@@ -278,6 +303,9 @@ pub struct VmReceiveMigrationData {
|
|||||||
/// Directory containing the TLS server certificate (server-cert.pem), the TLS server key (server-key.pem), and the client TLS root CA certificate (ca-cert.pem).
|
/// Directory containing the TLS server certificate (server-cert.pem), the TLS server key (server-key.pem), and the client TLS root CA certificate (ca-cert.pem).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tls_dir: Option<PathBuf>,
|
pub tls_dir: Option<PathBuf>,
|
||||||
|
/// Memory transfer mode.
|
||||||
|
#[serde(default)]
|
||||||
|
pub memory_mode: MigrationMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -291,11 +319,11 @@ pub enum VmReceiveMigrationConfigError {
|
|||||||
|
|
||||||
impl VmReceiveMigrationData {
|
impl VmReceiveMigrationData {
|
||||||
pub const SYNTAX: &'static str = "VM receive migration parameters \
|
pub const SYNTAX: &'static str = "VM receive migration parameters \
|
||||||
\"<receiver_url>\" or \"receiver_url=<url>[,tls_dir=<path>]\"";
|
\"<receiver_url>\" or \"receiver_url=<url>[,tls_dir=<path>][,memory_mode=precopy|postcopy]\"";
|
||||||
|
|
||||||
pub fn parse(migration: &str) -> Result<Self, VmReceiveMigrationConfigError> {
|
pub fn parse(migration: &str) -> Result<Self, VmReceiveMigrationConfigError> {
|
||||||
let mut parser = OptionParser::new();
|
let mut parser = OptionParser::new();
|
||||||
parser.add("receiver_url").add("tls_dir");
|
parser.add("receiver_url").add("tls_dir").add("memory_mode");
|
||||||
parser
|
parser
|
||||||
.parse(migration)
|
.parse(migration)
|
||||||
.map_err(VmReceiveMigrationConfigError::ParseError)?;
|
.map_err(VmReceiveMigrationConfigError::ParseError)?;
|
||||||
@@ -309,10 +337,15 @@ impl VmReceiveMigrationData {
|
|||||||
.convert::<String>("tls_dir")
|
.convert::<String>("tls_dir")
|
||||||
.map_err(VmReceiveMigrationConfigError::ParseError)?
|
.map_err(VmReceiveMigrationConfigError::ParseError)?
|
||||||
.map(|path| PathBuf::from(&path));
|
.map(|path| PathBuf::from(&path));
|
||||||
|
let memory_mode = parser
|
||||||
|
.convert::<MigrationMode>("memory_mode")
|
||||||
|
.map_err(VmReceiveMigrationConfigError::ParseError)?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let data = Self {
|
let data = Self {
|
||||||
receiver_url,
|
receiver_url,
|
||||||
tls_dir,
|
tls_dir,
|
||||||
|
memory_mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
data.validate()?;
|
data.validate()?;
|
||||||
@@ -422,6 +455,9 @@ pub struct VmSendMigrationData {
|
|||||||
/// Path to the directory containing the TLS root CA certificate (ca-cert.pem), the TLS client certificate (client-cert.pem), and TLS client key (client-key.pem).
|
/// Path to the directory containing the TLS root CA certificate (ca-cert.pem), the TLS client certificate (client-cert.pem), and TLS client key (client-key.pem).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tls_dir: Option<PathBuf>,
|
pub tls_dir: Option<PathBuf>,
|
||||||
|
/// Memory transfer mode.
|
||||||
|
#[serde(default)]
|
||||||
|
pub memory_mode: MigrationMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VmSendMigrationData {
|
impl VmSendMigrationData {
|
||||||
@@ -429,7 +465,7 @@ impl VmSendMigrationData {
|
|||||||
\"destination_url=<url>[,local=on|off,\
|
\"destination_url=<url>[,local=on|off,\
|
||||||
downtime_ms=<milliseconds>,timeout_s=<seconds>,\
|
downtime_ms=<milliseconds>,timeout_s=<seconds>,\
|
||||||
timeout_strategy=cancel|ignore,connections=<amount>,\
|
timeout_strategy=cancel|ignore,connections=<amount>,\
|
||||||
tls_dir=<path>]\"";
|
tls_dir=<path>,memory_mode=precopy|postcopy]\"";
|
||||||
|
|
||||||
// Same as QEMU.
|
// Same as QEMU.
|
||||||
pub const DEFAULT_DOWNTIME: Duration = Duration::from_millis(300);
|
pub const DEFAULT_DOWNTIME: Duration = Duration::from_millis(300);
|
||||||
@@ -458,7 +494,8 @@ impl VmSendMigrationData {
|
|||||||
.add("timeout_s")
|
.add("timeout_s")
|
||||||
.add("timeout_strategy")
|
.add("timeout_strategy")
|
||||||
.add("connections")
|
.add("connections")
|
||||||
.add("tls_dir");
|
.add("tls_dir")
|
||||||
|
.add("memory_mode");
|
||||||
parser
|
parser
|
||||||
.parse(migration)
|
.parse(migration)
|
||||||
.map_err(VmSendMigrationConfigError::ParseError)?;
|
.map_err(VmSendMigrationConfigError::ParseError)?;
|
||||||
@@ -514,6 +551,10 @@ impl VmSendMigrationData {
|
|||||||
.convert::<String>("tls_dir")
|
.convert::<String>("tls_dir")
|
||||||
.map_err(VmSendMigrationConfigError::ParseError)?
|
.map_err(VmSendMigrationConfigError::ParseError)?
|
||||||
.map(|path| PathBuf::from(&path));
|
.map(|path| PathBuf::from(&path));
|
||||||
|
let memory_mode = parser
|
||||||
|
.convert::<MigrationMode>("memory_mode")
|
||||||
|
.map_err(VmSendMigrationConfigError::ParseError)?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let data = Self {
|
let data = Self {
|
||||||
destination_url,
|
destination_url,
|
||||||
@@ -523,6 +564,7 @@ impl VmSendMigrationData {
|
|||||||
timeout_strategy,
|
timeout_strategy,
|
||||||
connections,
|
connections,
|
||||||
tls_dir,
|
tls_dir,
|
||||||
|
memory_mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
data.validate()?;
|
data.validate()?;
|
||||||
@@ -593,6 +635,21 @@ impl VmSendMigrationData {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches!(self.memory_mode, MigrationMode::Postcopy) {
|
||||||
|
if self.local {
|
||||||
|
return Err(VmSendMigrationConfigError::ValidationError(
|
||||||
|
"memory_mode=postcopy and local options are mutually exclusive.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.connections.get() > 1 {
|
||||||
|
return Err(VmSendMigrationConfigError::ValidationError(
|
||||||
|
"memory_mode=postcopy currently requires a single connection (connections=1)."
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1921,6 +1978,7 @@ mod unit_tests {
|
|||||||
VmReceiveMigrationData {
|
VmReceiveMigrationData {
|
||||||
receiver_url: "tcp:192.168.1.1:8080".to_string(),
|
receiver_url: "tcp:192.168.1.1:8080".to_string(),
|
||||||
tls_dir: None,
|
tls_dir: None,
|
||||||
|
memory_mode: MigrationMode::Precopy,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1947,6 +2005,7 @@ mod unit_tests {
|
|||||||
VmReceiveMigrationData {
|
VmReceiveMigrationData {
|
||||||
receiver_url: "tcp:192.168.1.1:8080".to_string(),
|
receiver_url: "tcp:192.168.1.1:8080".to_string(),
|
||||||
tls_dir: Some(tls_dir_path),
|
tls_dir: Some(tls_dir_path),
|
||||||
|
memory_mode: MigrationMode::Precopy,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1961,6 +2020,33 @@ mod unit_tests {
|
|||||||
VmReceiveMigrationData::parse("receiver_url=tcp:192.168.1.1").unwrap_err();
|
VmReceiveMigrationData::parse("receiver_url=tcp:192.168.1.1").unwrap_err();
|
||||||
VmReceiveMigrationData::parse("receiver_url=tcp:[2001:db8::1]").unwrap_err();
|
VmReceiveMigrationData::parse("receiver_url=tcp:[2001:db8::1]").unwrap_err();
|
||||||
VmReceiveMigrationData::parse("receiver_url=unix:/tmp/sock,tls_dir=/tmp").unwrap_err();
|
VmReceiveMigrationData::parse("receiver_url=unix:/tmp/sock,tls_dir=/tmp").unwrap_err();
|
||||||
|
|
||||||
|
// memory_mode defaults to precopy when not specified.
|
||||||
|
let data = VmReceiveMigrationData::parse("receiver_url=tcp:127.0.0.1:1234").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
data,
|
||||||
|
VmReceiveMigrationData {
|
||||||
|
receiver_url: "tcp:127.0.0.1:1234".to_string(),
|
||||||
|
tls_dir: None,
|
||||||
|
memory_mode: MigrationMode::Precopy,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Explicit receiver_url with memory_mode=postcopy.
|
||||||
|
let data =
|
||||||
|
VmReceiveMigrationData::parse("receiver_url=unix:/tmp/sock,memory_mode=postcopy")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
data,
|
||||||
|
VmReceiveMigrationData {
|
||||||
|
receiver_url: "unix:/tmp/sock".to_string(),
|
||||||
|
tls_dir: None,
|
||||||
|
memory_mode: MigrationMode::Postcopy,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Missing receiver_url in keyed form must fail.
|
||||||
|
VmReceiveMigrationData::parse("memory_mode=postcopy").unwrap_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2062,6 +2148,7 @@ mod unit_tests {
|
|||||||
timeout_strategy: Default::default(),
|
timeout_strategy: Default::default(),
|
||||||
connections: VmSendMigrationData::default_connections(),
|
connections: VmSendMigrationData::default_connections(),
|
||||||
tls_dir: None,
|
tls_dir: None,
|
||||||
|
memory_mode: MigrationMode::Precopy,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2082,7 +2169,24 @@ mod unit_tests {
|
|||||||
timeout_strategy: TimeoutStrategy::Ignore,
|
timeout_strategy: TimeoutStrategy::Ignore,
|
||||||
connections: NonZeroU32::new(4).unwrap(),
|
connections: NonZeroU32::new(4).unwrap(),
|
||||||
tls_dir: Some(tls_dir_path),
|
tls_dir: Some(tls_dir_path),
|
||||||
|
memory_mode: MigrationMode::Precopy,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Postcopy happy path (TCP only, single connection).
|
||||||
|
let data =
|
||||||
|
VmSendMigrationData::parse("destination_url=tcp:192.168.1.1:8080,memory_mode=postcopy")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(data.memory_mode, MigrationMode::Postcopy);
|
||||||
|
|
||||||
|
// memory_mode=postcopy + local must be rejected.
|
||||||
|
VmSendMigrationData::parse("destination_url=unix:/tmp/sock,local=on,memory_mode=postcopy")
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
// memory_mode=postcopy + multi-connection must be rejected.
|
||||||
|
VmSendMigrationData::parse(
|
||||||
|
"destination_url=tcp:192.168.1.1:8080,memory_mode=postcopy,connections=4",
|
||||||
|
)
|
||||||
|
.unwrap_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1515,6 +1515,17 @@ components:
|
|||||||
Directory containing the TLS server certificate (server-cert.pem), the TLS
|
Directory containing the TLS server certificate (server-cert.pem), the TLS
|
||||||
server key (server-key.pem), and the client TLS root CA certificate (ca-cert.pem).
|
server key (server-key.pem), and the client TLS root CA certificate (ca-cert.pem).
|
||||||
TLS is only supported with tcp:<host>:<port> receiver URLs.
|
TLS is only supported with tcp:<host>:<port> receiver URLs.
|
||||||
|
memory_mode:
|
||||||
|
$ref: "#/components/schemas/MigrationMode"
|
||||||
|
|
||||||
|
MigrationMode:
|
||||||
|
type: string
|
||||||
|
enum: ["Precopy", "Postcopy"]
|
||||||
|
default: "Precopy"
|
||||||
|
description: >
|
||||||
|
Memory transfer mode. Precopy transfers all guest memory before the
|
||||||
|
destination resumes. Postcopy resumes the destination first and faults
|
||||||
|
guest pages in on demand.
|
||||||
|
|
||||||
TimeoutStrategy:
|
TimeoutStrategy:
|
||||||
type: string
|
type: string
|
||||||
@@ -1568,6 +1579,8 @@ components:
|
|||||||
Directory containing the TLS root CA certificate (ca-cert.pem), the TLS client
|
Directory containing the TLS root CA certificate (ca-cert.pem), the TLS client
|
||||||
certificate (client-cert.pem), and TLS client key (client-key.pem).
|
certificate (client-cert.pem), and TLS client key (client-key.pem).
|
||||||
TLS is only supported with tcp:<host>:<port> destination URLs.
|
TLS is only supported with tcp:<host>:<port> destination URLs.
|
||||||
|
memory_mode:
|
||||||
|
$ref: "#/components/schemas/MigrationMode"
|
||||||
|
|
||||||
VmAddUserDevice:
|
VmAddUserDevice:
|
||||||
required:
|
required:
|
||||||
|
|||||||
Reference in New Issue
Block a user