Skip to content

Commit 2817a7d

Browse files
committed
kvm: dedicated live migration network with TLS, parallel streams and cluster CPU baseline
Carry KVM live migration traffic on a dedicated network instead of the management network. A Migration traffic type is added to the zone's physical network with a per hypervisor label; each KVM host resolves its own IP on the labelled NIC and reports it, and the libvirt data stream is routed to it only when both the source and the destination host have one, otherwise migration falls back to the existing behaviour unchanged. listHosts returns each host's migration IP, shown on the host detail view, so an operator can confirm the dedicated network is in use. Migrations can be encrypted with libvirt native TLS and parallelised with multiple file-descriptor streams. Both are opt-in and gated on the host libvirt version, failing closed with a clear error when the host is too old rather than silently downgrading. Add a cluster-scoped CPU baseline model so a mixed cluster can present a uniform guest CPU, keeping a VM's CPU definition stable as it migrates across hosts. The baseline guest is pinned with fallback=forbid so it refuses a host that cannot provide the exact model rather than silently degrading the CPU the guest sees; the forbid is scoped to the baseline and leaves a VM with its own explicit CPU mode or model untouched. The baseline is validated against every host in the cluster before it is accepted, and hosts that cannot run the model are reported back instead of failing the migration later. Setting the baseline to "auto" computes the common-denominator model of the cluster's reachable hosts, by collecting each host's CPU and running cpu-baseline, and persists the computed model; it does not re-compute as hosts change, so the committed baseline stays stable. Unit tests cover the IP resolver (both-ends gating, non-KVM skip, blank values), the libvirt flag and typed-parameter construction, the version gate, the CPU baseline injection, the forbid fallback emission and scoping, the auto-compute and model parsing, and the host compatibility check including the fail-closed path on an unknown model. A Marvin smoke test exercises the migration traffic type API.
1 parent 602d9ec commit 2817a7d

50 files changed

Lines changed: 2709 additions & 43 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎agent/conf/agent.properties‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,41 @@ zone=default
6666
# If this is commented, the value of the private NIC device will be used.
6767
#guest.network.device=
6868

69+
# Policy for encrypting the live-migration data stream using QEMU-native TLS.
70+
# "Disabled" (default, plaintext) or "Required" (use TLS, and fail the migration if
71+
# the host libvirt is older than 3.2.0, rather than silently sending plaintext).
72+
# "Required" needs a migration x509 trust set up on every participating host first:
73+
# a CA plus server and client certs under migrate_tls_x509_cert_dir in qemu.conf
74+
# (default /etc/pki/qemu), with the cert SAN covering the host's migration address,
75+
# then restart libvirtd. Without that, libvirt rejects the TLS migration.
76+
#migrate.encryption.policy=Disabled
77+
78+
# Use multiple parallel TCP streams (multifd) for live migration to fill fast NICs.
79+
#migrate.parallel.enabled=false
80+
81+
# Number of parallel connections (multifd channels) for live migration, used only
82+
# when migrate.parallel.enabled is true. 0 lets libvirt/QEMU pick its default.
83+
# To use more than one physical NIC for migration bandwidth, bond the NICs under the
84+
# migration bridge; libvirt sends all channels to a single destination address and
85+
# cannot bind them to separate NICs itself. For the channels to spread across bond
86+
# members the bond must hash on the L4 ports (802.3ad or balance-xor with
87+
# xmit_hash_policy=layer3+4); the default layer2 hash pins all channels to one member
88+
# because they share a MAC and IP pair, giving no spread. The spread is statistical,
89+
# not guaranteed.
90+
#migrate.parallel.connections=0
91+
92+
# Allow migrating a VM whose disk cache mode libvirt considers unsafe (e.g. writeback).
93+
# Only enable on coherent shared storage such as Ceph RBD.
94+
#migrate.allow.unsafe=false
95+
96+
# Run a virsh cpu-compare precheck against the destination before migrating, and abort
97+
# early with a clear error if the destination CPU cannot run the guest.
98+
#migrate.cpu.precheck.enabled=false
99+
100+
# Memory-compression method for non-parallel migration, xbzrle or mt. Blank leaves libvirt's
101+
# default method. Ignored when migrate.parallel.enabled is true.
102+
#migrate.compression.method=
103+
69104
# Local storage path. Multiple values can be entered and separated by commas.
70105
#local.storage.path=/var/lib/libvirt/images/
71106

‎agent/src/main/java/com/cloud/agent/properties/AgentProperties.java‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,63 @@ public class AgentProperties{
146146
*/
147147
public static final Property<String> QEMU_SOCKETS_PATH = new Property<>("qemu.sockets.path", "/var/lib/libvirt/qemu");
148148

149+
/**
150+
* Policy for encrypting the live-migration data stream (guest RAM, and disk contents
151+
* during storage migration) using QEMU-native TLS (VIR_MIGRATE_TLS). Values: "Disabled"
152+
* (default, plaintext TCP) or "Required" (encrypt, and fail the migration if the host has no
153+
* TLS migration environment). Required needs migrate_tls_x509_cert_dir / default_tls_x509_cert_dir
154+
* configured in qemu.conf on every host.
155+
* Data type: String.<br>
156+
* Default value: "Disabled".
157+
*/
158+
public static final Property<String> MIGRATE_ENCRYPTION_POLICY = new Property<>("migrate.encryption.policy", "Disabled");
159+
160+
/**
161+
* Use multiple parallel TCP streams (multifd) for live migration to saturate fast NICs
162+
* (25/40/100GbE). Single-stream migration cannot fill such links. Requires libvirt 5.2.0+.
163+
* Data type: Boolean.<br>
164+
* Default value: false.
165+
*/
166+
public static final Property<Boolean> MIGRATE_PARALLEL_ENABLED = new Property<>("migrate.parallel.enabled", false);
167+
168+
/**
169+
* Number of parallel connections (multifd channels) to use for live migration when migrate.parallel.enabled
170+
* is set. 0 lets libvirt/QEMU choose its default. To spread migration across more than one physical NIC,
171+
* bond the NICs (for example LACP) under the migration bridge; libvirt sends all channels to a single
172+
* destination address, so it cannot bind them to separate NICs itself.<br>
173+
* Data type: Integer.<br>
174+
* Default value: 0.
175+
*/
176+
public static final Property<Integer> MIGRATE_PARALLEL_CONNECTIONS = new Property<>("migrate.parallel.connections", 0);
177+
178+
/**
179+
* Allow live migration of a VM whose disk cache mode libvirt considers unsafe (anything
180+
* other than none/directsync), by setting VIR_MIGRATE_UNSAFE. Safe only on coherent shared
181+
* storage such as Ceph RBD, where writeback caching does not risk data loss on migration.
182+
* Data type: Boolean.<br>
183+
* Default value: false.
184+
*/
185+
public static final Property<Boolean> MIGRATE_ALLOW_UNSAFE = new Property<>("migrate.allow.unsafe", false);
186+
187+
/**
188+
* Run a CPU-compatibility precheck (virsh cpu-compare against the destination host) before
189+
* a live migration, so an incompatible destination fails fast with a clear message instead of a
190+
* cryptic mid-migration libvirt error. Best-effort and fail-open: if the check cannot run it does
191+
* not block the migration. Requires the source host's virsh to reach the destination libvirt.
192+
* Data type: Boolean.<br>
193+
* Default value: false.
194+
*/
195+
public static final Property<Boolean> MIGRATE_CPU_PRECHECK_ENABLED = new Property<>("migrate.cpu.precheck.enabled", false);
196+
197+
/**
198+
* Migration memory-compression method, "xbzrle" or "mt". Empty (default) leaves libvirt's
199+
* own default method in effect. Only takes effect when compression is enabled (libvirt &gt;= 1.0.3,
200+
* which sets VIR_MIGRATE_COMPRESSED) and is skipped under multifd.
201+
* Data type: String.<br>
202+
* Default value: "" (empty).
203+
*/
204+
public static final Property<String> MIGRATE_COMPRESSION_METHOD = new Property<>("migrate.compression.method", "");
205+
149206
/**
150207
* MANDATORY: The UUID for the local storage pool.<br>
151208
* This property allows multiple values to be entered in a single String. The different values must be separated by commas.<br>

‎api/src/main/java/com/cloud/host/Host.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public static String[] toStrings(Host.Type... types) {
6666
String HOST_VIRTV2V_VERSION = "host.virtv2v.version";
6767
String HOST_SSH_PORT = "host.ssh.port";
6868
String HOST_CDROM_MAX_COUNT = "host.cdrom.max.count";
69+
String HOST_MIGRATION_IP = "host.migration.ip";
6970
String GUEST_OS_CATEGORY_ID = "guest.os.category.id";
7071
String GUEST_OS_RULE = "guest.os.rule";
7172

‎api/src/main/java/com/cloud/network/Networks.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ public static URI encodeStringIntoBroadcastUri(String candidate, BroadcastDomain
306306
* Different types of network traffic in the data center.
307307
*/
308308
public enum TrafficType {
309-
None, Public, Guest, Storage, Management, Control, Vpn;
309+
None, Public, Guest, Storage, Management, Control, Vpn, Migration;
310310

311311
public static boolean isSystemNetwork(TrafficType trafficType) {
312312
if (Storage.equals(trafficType) || Management.equals(trafficType) || Control.equals(trafficType)) {
@@ -322,6 +322,8 @@ public static TrafficType getTrafficType(String type) {
322322
return Guest;
323323
} else if ("Storage".equals(type)) {
324324
return Storage;
325+
} else if ("Migration".equals(type)) {
326+
return Migration;
325327
} else if ("Management".equals(type)) {
326328
return Management;
327329
} else if ("Control".equals(type)) {

‎api/src/main/java/com/cloud/network/PhysicalNetworkSetupInfo.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class PhysicalNetworkSetupInfo {
2828
String publicNetworkName;
2929
String guestNetworkName;
3030
String storageNetworkName;
31+
String migrationNetworkName;
3132
String mgmtVlan;
3233

3334
public PhysicalNetworkSetupInfo() {
@@ -49,6 +50,14 @@ public String getStorageNetworkName() {
4950
return storageNetworkName;
5051
}
5152

53+
public String getMigrationNetworkName() {
54+
return migrationNetworkName;
55+
}
56+
57+
public void setMigrationNetworkName(String migrationNetworkName) {
58+
this.migrationNetworkName = migrationNetworkName;
59+
}
60+
5261
public void setPrivateNetworkName(String privateNetworkName) {
5362
this.privateNetworkName = privateNetworkName;
5463
}

‎api/src/main/java/com/cloud/vm/VmDetailConstants.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ public interface VmDetailConstants {
118118
// CPU mode and model, ADMIN only
119119
String GUEST_CPU_MODE = "guest.cpu.mode";
120120
String GUEST_CPU_MODEL = "guest.cpu.model";
121+
// Fallback policy for a custom CPU model ("allow" or "forbid"). "forbid" refuses a host that
122+
// cannot provide the exact model instead of silently degrading it; set by the cluster CPU baseline.
123+
String GUEST_CPU_MODEL_FALLBACK = "guest.cpu.model.fallback";
121124

122125
// Lease related
123126
String INSTANCE_LEASE_EXPIRY_DATE = "leaseexpirydate";

‎api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ public class HostResponse extends BaseResponseWithAnnotations {
307307
@Param(description = "True if the host has capability to support UEFI boot")
308308
private Boolean uefiCapability;
309309

310+
@SerializedName("migrationip")
311+
@Param(description = "the IP address the host uses for live migration traffic, set when a dedicated migration network is configured on this host", since = "24.0.0")
312+
private String migrationIp;
313+
310314
@SerializedName(ApiConstants.ENCRYPTION_SUPPORTED)
311315
@Param(description = "True if the host supports encryption", since = "4.18")
312316
private Boolean encryptionSupported;
@@ -896,6 +900,14 @@ public void setUefiCapability(Boolean hostCapability) {
896900
this.uefiCapability = hostCapability;
897901
}
898902

903+
public void setMigrationIp(String migrationIp) {
904+
this.migrationIp = migrationIp;
905+
}
906+
907+
public String getMigrationIp() {
908+
return migrationIp;
909+
}
910+
899911
public void setEncryptionSupported(Boolean encryptionSupported) {
900912
this.encryptionSupported = encryptionSupported;
901913
}

‎api/src/main/java/org/apache/cloudstack/query/QueryService.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
*/
111111
public interface QueryService {
112112

113-
List<String> RootAdminOnlyVmSettings = Arrays.asList(VmDetailConstants.GUEST_CPU_MODE, VmDetailConstants.GUEST_CPU_MODEL);
113+
List<String> RootAdminOnlyVmSettings = Arrays.asList(VmDetailConstants.GUEST_CPU_MODE, VmDetailConstants.GUEST_CPU_MODEL, VmDetailConstants.GUEST_CPU_MODEL_FALLBACK);
114114

115115
// Config keys
116116
ConfigKey<Boolean> AllowUserViewDestroyedVM = new ConfigKey<>("Advanced", Boolean.class, "allow.user.view.destroyed.vm", "false",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.agent.api;
21+
22+
import java.util.List;
23+
24+
/**
25+
* Runs {@code virsh cpu-baseline} over the given per-host {@code <cpu>} elements and returns the
26+
* most feature-rich CPU compatible with all of them: the common-denominator cluster baseline.
27+
*/
28+
public class BaselineCpuCommand extends Command {
29+
30+
private List<String> hostCpuXmls;
31+
32+
protected BaselineCpuCommand() {
33+
}
34+
35+
public BaselineCpuCommand(List<String> hostCpuXmls) {
36+
this.hostCpuXmls = hostCpuXmls;
37+
}
38+
39+
public List<String> getHostCpuXmls() {
40+
return hostCpuXmls;
41+
}
42+
43+
@Override
44+
public boolean executeInSequence() {
45+
return false;
46+
}
47+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.agent.api;
21+
22+
/**
23+
* Checks on the agent, via {@code virsh cpu-compare}, that the host CPU can run the given {@code <cpu>} model.
24+
*/
25+
public class CheckCpuCompatibilityCommand extends Command {
26+
27+
private String cpuXml;
28+
private String vmName;
29+
30+
protected CheckCpuCompatibilityCommand() {
31+
}
32+
33+
public CheckCpuCompatibilityCommand(String vmName, String cpuXml) {
34+
this.vmName = vmName;
35+
this.cpuXml = cpuXml;
36+
}
37+
38+
public String getCpuXml() {
39+
return cpuXml;
40+
}
41+
42+
public String getVmName() {
43+
return vmName;
44+
}
45+
46+
@Override
47+
public boolean executeInSequence() {
48+
return false;
49+
}
50+
}

0 commit comments

Comments
 (0)