|
46 | 46 | import com.cloud.hypervisor.kvm.resource.LibvirtXMLParser; |
47 | 47 | import com.cloud.resource.CommandWrapper; |
48 | 48 | import com.cloud.resource.ResourceWrapper; |
| 49 | +import com.cloud.storage.Storage; |
49 | 50 | import com.cloud.utils.Ternary; |
50 | 51 | import com.cloud.utils.exception.CloudRuntimeException; |
51 | 52 | import com.cloud.vm.VirtualMachine; |
@@ -243,14 +244,33 @@ Use VIR_DOMAIN_XML_SECURE (value = 1) prior to v1.0.0. |
243 | 244 | final ExecutorService executor = Executors.newFixedThreadPool(1); |
244 | 245 | boolean migrateNonSharedInc = command.isMigrateNonSharedInc() && !migrateStorageManaged; |
245 | 246 |
|
| 247 | + // If any of the destination disks target CLVM/CLVM_NG pools, exclude them from |
| 248 | + // the migration disk list. These disks are already created/activated on the |
| 249 | + // destination storage before VM migration, so libvirt should not try to migrate them. |
| 250 | + // Only non-CLVM disks will be migrated by libvirt. |
| 251 | + boolean effectiveMigrateStorage = migrateStorage; |
| 252 | + if (migrateStorage && migrateDiskLabels != null && hasClvmDestinationDisks(mapMigrateStorage)) { |
| 253 | + logger.info("Filtering out CLVM/CLVM_NG disks from migration for VM {} as they are pre-created on destination", vmName); |
| 254 | + migrateDiskLabels = filterOutClvmDisks(migrateDiskLabels, disks, mapMigrateStorage); |
| 255 | + logger.debug("Remaining disks to migrate via libvirt: {}", migrateDiskLabels); |
| 256 | + |
| 257 | + // If all disks were filtered out (only CLVM/CLVM_NG), disable storage migration entirely |
| 258 | + // to prevent libvirt from trying to handle the block devices |
| 259 | + if (migrateDiskLabels != null && migrateDiskLabels.isEmpty()) { |
| 260 | + logger.info("All disks are CLVM/CLVM_NG and pre-created on destination. Disabling storage migration for VM {}", vmName); |
| 261 | + effectiveMigrateStorage = false; |
| 262 | + migrateDiskLabels = null; |
| 263 | + } |
| 264 | + } |
| 265 | + |
246 | 266 | // add cancel hook before we start. If migration fails to start and hook is called, it's non-fatal |
247 | 267 | cancelHook = new MigrationCancelHook(dm); |
248 | 268 | libvirtComputingResource.addDisconnectHook(cancelHook); |
249 | 269 |
|
250 | 270 | libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING); |
251 | 271 |
|
252 | 272 | final Callable<Domain> worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc, |
253 | | - migrateStorage, migrateNonSharedInc, |
| 273 | + effectiveMigrateStorage, migrateNonSharedInc, |
254 | 274 | command.isAutoConvergence(), vmName, command.getDestinationIp(), migrateDiskLabels); |
255 | 275 | final Future<Domain> migrateThread = executor.submit(worker); |
256 | 276 | executor.shutdown(); |
@@ -1073,4 +1093,93 @@ public static String maskSensitiveInfoInXML(String xmlDesc) { |
1073 | 1093 | return xmlDesc.replaceAll("(graphics\\s+[^>]*type=['\"]vnc['\"][^>]*passwd=['\"])([^'\"]*)(['\"])", |
1074 | 1094 | "$1*****$3"); |
1075 | 1095 | } |
| 1096 | + |
| 1097 | + /** |
| 1098 | + * Checks if any of the destination disks in the migration target a CLVM or CLVM_NG storage pool. |
| 1099 | + * This is used to determine if incremental migration should be disabled to avoid libvirt |
| 1100 | + * precreate errors with QCOW2-on-LVM setups. |
| 1101 | + * |
| 1102 | + * @param mapMigrateStorage the map containing migration disk information with destination pool types |
| 1103 | + * @return true if any destination disk targets CLVM or CLVM_NG, false otherwise |
| 1104 | + */ |
| 1105 | + protected boolean hasClvmDestinationDisks(Map<String, MigrateCommand.MigrateDiskInfo> mapMigrateStorage) { |
| 1106 | + if (MapUtils.isEmpty(mapMigrateStorage)) { |
| 1107 | + return false; |
| 1108 | + } |
| 1109 | + |
| 1110 | + try { |
| 1111 | + for (Map.Entry<String, MigrateCommand.MigrateDiskInfo> entry : mapMigrateStorage.entrySet()) { |
| 1112 | + MigrateCommand.MigrateDiskInfo diskInfo = entry.getValue(); |
| 1113 | + if (isClvmBlockDevice(diskInfo)) { |
| 1114 | + logger.debug("Found disk targeting CLVM/CLVM_NG destination pool"); |
| 1115 | + return true; |
| 1116 | + } |
| 1117 | + } |
| 1118 | + } catch (final Exception e) { |
| 1119 | + logger.debug("Failed to check for CLVM destination disks: {}. Assuming no CLVM disks.", e.getMessage()); |
| 1120 | + } |
| 1121 | + |
| 1122 | + return false; |
| 1123 | + } |
| 1124 | + |
| 1125 | + /** |
| 1126 | + * Filters out disk labels that target CLVM or CLVM_NG destination pools from the migration disk labels set. |
| 1127 | + * CLVM/CLVM_NG disks are pre-created/activated on the destination before VM migration, |
| 1128 | + * so they should not be migrated by libvirt. |
| 1129 | + * |
| 1130 | + * @param migrateDiskLabels the original set of disk labels to migrate |
| 1131 | + * @param diskDefinitions the list of disk definitions to map labels to paths |
| 1132 | + * @param mapMigrateStorage the map containing migration disk information with destination pool types |
| 1133 | + * @return a new set with CLVM/CLVM_NG disk labels removed |
| 1134 | + */ |
| 1135 | + protected Set<String> filterOutClvmDisks(Set<String> migrateDiskLabels, |
| 1136 | + List<DiskDef> diskDefinitions, |
| 1137 | + Map<String, MigrateCommand.MigrateDiskInfo> mapMigrateStorage) { |
| 1138 | + if (migrateDiskLabels == null || migrateDiskLabels.isEmpty()) { |
| 1139 | + return migrateDiskLabels; |
| 1140 | + } |
| 1141 | + |
| 1142 | + Set<String> filteredLabels = new HashSet<>(migrateDiskLabels); |
| 1143 | + |
| 1144 | + try { |
| 1145 | + Set<String> clvmDiskPaths = new HashSet<>(); |
| 1146 | + for (Map.Entry<String, MigrateCommand.MigrateDiskInfo> entry : mapMigrateStorage.entrySet()) { |
| 1147 | + MigrateCommand.MigrateDiskInfo diskInfo = entry.getValue(); |
| 1148 | + if (isClvmBlockDevice(diskInfo)) { |
| 1149 | + clvmDiskPaths.add(entry.getKey()); |
| 1150 | + logger.debug("Identified CLVM/CLVM_NG destination disk: {}", entry.getKey()); |
| 1151 | + } |
| 1152 | + } |
| 1153 | + |
| 1154 | + // Map disk paths to labels and remove CLVM disk labels from the migration set |
| 1155 | + if (!clvmDiskPaths.isEmpty()) { |
| 1156 | + for (String clvmDiskPath : clvmDiskPaths) { |
| 1157 | + for (DiskDef diskDef : diskDefinitions) { |
| 1158 | + String diskPath = diskDef.getDiskPath(); |
| 1159 | + if (diskPath != null && diskPath.contains(clvmDiskPath)) { |
| 1160 | + String label = diskDef.getDiskLabel(); |
| 1161 | + if (filteredLabels.remove(label)) { |
| 1162 | + logger.info("Excluded disk label {} (path: {}) from libvirt migration - CLVM/CLVM_NG destination", |
| 1163 | + label, clvmDiskPath); |
| 1164 | + } |
| 1165 | + break; |
| 1166 | + } |
| 1167 | + } |
| 1168 | + } |
| 1169 | + } |
| 1170 | + |
| 1171 | + } catch (final Exception e) { |
| 1172 | + logger.warn("Failed to filter CLVM disks: {}. Proceeding with original disk list.", e.getMessage()); |
| 1173 | + return migrateDiskLabels; |
| 1174 | + } |
| 1175 | + |
| 1176 | + return filteredLabels; |
| 1177 | + } |
| 1178 | + |
| 1179 | + private boolean isClvmBlockDevice(MigrateCommand.MigrateDiskInfo diskInfo) { |
| 1180 | + if (diskInfo == null ||diskInfo.getDestPoolType() == null) { |
| 1181 | + return false; |
| 1182 | + } |
| 1183 | + return (Storage.StoragePoolType.CLVM.equals(diskInfo.getDestPoolType()) || Storage.StoragePoolType.CLVM_NG.equals(diskInfo.getDestPoolType())); |
| 1184 | + } |
1076 | 1185 | } |
0 commit comments