vmm: add TLS API option to receive migration call

As we now have more than one parameter for the receive migration call,
this commit also adds parsing and validation for those parameters. We
maintain backwards compatibility by also correctly parsing the case
where the caller only provides a URL.

On-behalf-of: SAP sebastian.eydam@sap.com
Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
This commit is contained in:
Sebastian Eydam
2026-04-15 10:32:02 +02:00
committed by Rob Bradford
parent c23edda98b
commit 58baee16ac
5 changed files with 130 additions and 12 deletions

View File

@@ -270,9 +270,80 @@ pub struct VmCoredumpData {
}
#[derive(Clone, Deserialize, Serialize, Default, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct VmReceiveMigrationData {
/// URL for the reception of migration state
pub receiver_url: String,
/// 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)]
pub tls_dir: Option<PathBuf>,
}
#[derive(Debug, Error)]
pub enum VmReceiveMigrationConfigError {
#[error("Error parsing receive migration parameters")]
ParseError(#[source] OptionParserError),
#[error("Error validating receive migration parameters")]
ValidationError(String),
}
impl VmReceiveMigrationData {
pub const SYNTAX: &'static str = "VM receive migration parameters \
\"<receiver_url>\" or \"receiver_url=<url>[,tls_dir=<path>]\"";
pub fn parse(migration: &str) -> Result<Self, VmReceiveMigrationConfigError> {
let mut parser = OptionParser::new();
parser.add("receiver_url").add("tls_dir");
parser
.parse(migration)
.map_err(VmReceiveMigrationConfigError::ParseError)?;
let receiver_url = parser.get("receiver_url").ok_or_else(|| {
VmReceiveMigrationConfigError::ParseError(OptionParserError::InvalidSyntax(
"receiver_url is required".to_string(),
))
})?;
let tls_dir = parser
.convert::<String>("tls_dir")
.map_err(VmReceiveMigrationConfigError::ParseError)?
.map(|path| PathBuf::from(&path));
let data = Self {
receiver_url,
tls_dir,
};
data.validate()?;
Ok(data)
}
pub fn validate(&self) -> Result<(), VmReceiveMigrationConfigError> {
if let Some(addr) = self.receiver_url.strip_prefix("tcp:") {
tcp_address_to_server_name(addr).map_err(|e| {
VmReceiveMigrationConfigError::ValidationError(format!(
"receiver_url must use tcp:<host>:<port> or unix:<path>: {e}."
))
})?;
} else if self
.receiver_url
.strip_prefix("unix:")
.is_some_and(|path| !path.is_empty())
{
if self.tls_dir.is_some() {
return Err(VmReceiveMigrationConfigError::ValidationError(
"UNIX sockets and TLS encryption cannot be used at the same time.".to_string(),
));
}
} else {
return Err(VmReceiveMigrationConfigError::ValidationError(
"receiver_url must use tcp:<host>:<port> or unix:<path>.".to_string(),
));
}
Ok(())
}
}
#[derive(Copy, Clone, Default, Deserialize, Serialize, Debug, PartialEq, Eq)]
@@ -1780,6 +1851,47 @@ impl ApiAction for VmNmi {
mod unit_tests {
use super::*;
#[test]
fn test_vm_receive_migration_data_parse() {
let data = VmReceiveMigrationData::parse("receiver_url=tcp:192.168.1.1:8080").unwrap();
assert_eq!(
data,
VmReceiveMigrationData {
receiver_url: "tcp:192.168.1.1:8080".to_string(),
tls_dir: None,
}
);
let data = VmReceiveMigrationData::parse("receiver_url=tcp:[2001:db8::1]:8080").unwrap();
assert_eq!(data.receiver_url, "tcp:[2001:db8::1]:8080");
let data =
VmReceiveMigrationData::parse("receiver_url=tcp:destination.example:8080").unwrap();
assert_eq!(data.receiver_url, "tcp:destination.example:8080");
let data = VmReceiveMigrationData::parse("receiver_url=unix:/tmp/ch=migrate.sock").unwrap();
assert_eq!(data.receiver_url, "unix:/tmp/ch=migrate.sock");
let tls_dir = std::env::temp_dir();
let data = VmReceiveMigrationData::parse(&format!(
"receiver_url=tcp:192.168.1.1:8080,tls_dir={}",
tls_dir.display()
))
.unwrap();
assert_eq!(
data,
VmReceiveMigrationData {
receiver_url: "tcp:192.168.1.1:8080".to_string(),
tls_dir: Some(tls_dir),
}
);
VmReceiveMigrationData::parse("receiver_url=file:///tmp/migration").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=unix:/tmp/sock,tls_dir=/tmp").unwrap_err();
}
#[test]
fn test_vm_send_migration_data_parse() {
// Fully specified

View File

@@ -1506,6 +1506,12 @@ components:
properties:
receiver_url:
type: string
tls_dir:
type: string
description: >
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).
TLS is only supported with tcp:<host>:<port> receiver URLs.
TimeoutStrategy:
type: string