support live migration from clvm_ng to nfs and vice-versa

This commit is contained in:
Pearl Dsilva 2026-03-19 12:07:40 -04:00
parent 22f9d0e6f1
commit c0cf89584b
4 changed files with 174 additions and 4 deletions

View File

@ -26,6 +26,7 @@ import java.util.Map;
import com.cloud.agent.api.to.DpdkTO;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.storage.Storage;
public class MigrateCommand extends Command {
private String vmName;
@ -184,6 +185,7 @@ public class MigrateCommand extends Command {
private final String sourceText;
private final String backingStoreText;
private boolean isSourceDiskOnStorageFileSystem;
private Storage.StoragePoolType destPoolType;
public MigrateDiskInfo(final String serialNumber, final DiskType diskType, final DriverType driverType, final Source source, final String sourceText) {
this.serialNumber = serialNumber;
@ -232,6 +234,14 @@ public class MigrateCommand extends Command {
public void setSourceDiskOnStorageFileSystem(boolean isDiskOnFileSystemStorage) {
this.isSourceDiskOnStorageFileSystem = isDiskOnFileSystemStorage;
}
public Storage.StoragePoolType getDestPoolType() {
return destPoolType;
}
public void setDestPoolType(Storage.StoragePoolType destPoolType) {
this.destPoolType = destPoolType;
}
}
@Override

View File

@ -144,12 +144,16 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot
}
/**
* Configures a {@link MigrateDiskInfo} object configured for migrating a File System volume and calls rootImageProvisioning.
* Configures a {@link MigrateDiskInfo} object configured for migrating a File System volume.
*/
@Override
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath, String backingPath) {
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.FILE, MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
MigrateCommand.MigrateDiskInfo.Source.FILE, destPath, backingPath);
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
MigrateCommand.MigrateDiskInfo.Source.FILE,
destPath,
backingPath);
}
/**
@ -158,6 +162,17 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot
*/
@Override
protected String generateDestPath(Host destHost, StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
if (destStoragePool.getPoolType() == StoragePoolType.CLVM || destStoragePool.getPoolType() == StoragePoolType.CLVM_NG) {
String vgName = destStoragePool.getPath();
if (StringUtils.isBlank(vgName)) {
throw new CloudRuntimeException(String.format("CLVM/CLVM_NG destination pool [%s] has empty VG path", destStoragePool.getUuid()));
}
if (vgName.startsWith("/")) {
vgName = vgName.substring(1);
}
return String.format("/dev/%s/%s", vgName, destVolumeInfo.getUuid());
}
return new File(destStoragePool.getPath(), destVolumeInfo.getUuid()).getAbsolutePath();
}

View File

@ -36,6 +36,7 @@ import com.cloud.agent.api.CheckVirtualMachineAnswer;
import com.cloud.agent.api.CheckVirtualMachineCommand;
import com.cloud.agent.api.PrepareForMigrationAnswer;
import com.cloud.resource.ResourceManager;
import com.cloud.storage.ClvmLockManager;
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
@ -2107,9 +2108,11 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
} else {
String backingPath = generateBackingPath(destStoragePool, destVolumeInfo);
migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath, backingPath);
migrateDiskInfo = updateMigrateDiskInfoForBlockDevice(migrateDiskInfo, destStoragePool);
migrateDiskInfo.setSourceDiskOnStorageFileSystem(isStoragePoolTypeOfFile(sourceStoragePool));
migrateDiskInfoList.add(migrateDiskInfo);
}
migrateDiskInfo.setDestPoolType(destVolumeInfo.getStoragePoolType());
prepareDiskWithSecretConsumerDetail(vmTO, srcVolumeInfo, destVolumeInfo.getPath());
migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);
@ -2328,6 +2331,39 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
MigrateCommand.MigrateDiskInfo.Source.DEV, destPath, backingPath);
}
/**
* UpdatesMigrateDiskInfo for CLVM/CLVM_NG block devices by returning a new instance with corrected disk type, driver type, and source.
* For CLVM/CLVM_NG destinations, returns a new MigrateDiskInfo with BLOCK disk type, DEV source, and appropriate driver type (QCOW2 for CLVM_NG, RAW for CLVM).
* For other storage types, returns the original MigrateDiskInfo unchanged.
*
* @param migrateDiskInfo The original MigrateDiskInfo object
* @param destStoragePool The destination storage pool
* @return A new MigrateDiskInfo with updated values for CLVM/CLVM_NG, or the original for other storage types
*/
protected MigrateCommand.MigrateDiskInfo updateMigrateDiskInfoForBlockDevice(MigrateCommand.MigrateDiskInfo migrateDiskInfo,
StoragePoolVO destStoragePool) {
if (ClvmLockManager.isClvmPoolType(destStoragePool.getPoolType())) {
MigrateCommand.MigrateDiskInfo.DriverType driverType =
(destStoragePool.getPoolType() == StoragePoolType.CLVM_NG) ?
MigrateCommand.MigrateDiskInfo.DriverType.QCOW2 :
MigrateCommand.MigrateDiskInfo.DriverType.RAW;
logger.debug("Updating MigrateDiskInfo for {} destination: setting BLOCK disk type, DEV source, and {} driver type",
destStoragePool.getPoolType(), driverType);
return new MigrateCommand.MigrateDiskInfo(
migrateDiskInfo.getSerialNumber(),
MigrateCommand.MigrateDiskInfo.DiskType.BLOCK,
driverType,
MigrateCommand.MigrateDiskInfo.Source.DEV,
migrateDiskInfo.getSourceText(),
migrateDiskInfo.getBackingStoreText());
}
return migrateDiskInfo;
}
/**
* Sets the volume path as the iScsi name in case of a configured iScsi.
*/

View File

@ -46,6 +46,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtGpuDef;
import com.cloud.hypervisor.kvm.resource.LibvirtXMLParser;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.storage.Storage;
import com.cloud.utils.Ternary;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachine;
@ -243,6 +244,25 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
final ExecutorService executor = Executors.newFixedThreadPool(1);
boolean migrateNonSharedInc = command.isMigrateNonSharedInc() && !migrateStorageManaged;
// If any of the destination disks target CLVM/CLVM_NG pools, exclude them from
// the migration disk list. These disks are already created/activated on the
// destination storage before VM migration, so libvirt should not try to migrate them.
// Only non-CLVM disks will be migrated by libvirt.
boolean effectiveMigrateStorage = migrateStorage;
if (migrateStorage && migrateDiskLabels != null && hasClvmDestinationDisks(mapMigrateStorage)) {
logger.info("Filtering out CLVM/CLVM_NG disks from migration for VM {} as they are pre-created on destination", vmName);
migrateDiskLabels = filterOutClvmDisks(migrateDiskLabels, disks, mapMigrateStorage);
logger.debug("Remaining disks to migrate via libvirt: {}", migrateDiskLabels);
// If all disks were filtered out (only CLVM/CLVM_NG), disable storage migration entirely
// to prevent libvirt from trying to handle the block devices
if (migrateDiskLabels != null && migrateDiskLabels.isEmpty()) {
logger.info("All disks are CLVM/CLVM_NG and pre-created on destination. Disabling storage migration for VM {}", vmName);
effectiveMigrateStorage = false;
migrateDiskLabels = null;
}
}
// add cancel hook before we start. If migration fails to start and hook is called, it's non-fatal
cancelHook = new MigrationCancelHook(dm);
libvirtComputingResource.addDisconnectHook(cancelHook);
@ -250,7 +270,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING);
final Callable<Domain> worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc,
migrateStorage, migrateNonSharedInc,
effectiveMigrateStorage, migrateNonSharedInc,
command.isAutoConvergence(), vmName, command.getDestinationIp(), migrateDiskLabels);
final Future<Domain> migrateThread = executor.submit(worker);
executor.shutdown();
@ -1073,4 +1093,93 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
return xmlDesc.replaceAll("(graphics\\s+[^>]*type=['\"]vnc['\"][^>]*passwd=['\"])([^'\"]*)(['\"])",
"$1*****$3");
}
/**
* Checks if any of the destination disks in the migration target a CLVM or CLVM_NG storage pool.
* This is used to determine if incremental migration should be disabled to avoid libvirt
* precreate errors with QCOW2-on-LVM setups.
*
* @param mapMigrateStorage the map containing migration disk information with destination pool types
* @return true if any destination disk targets CLVM or CLVM_NG, false otherwise
*/
protected boolean hasClvmDestinationDisks(Map<String, MigrateCommand.MigrateDiskInfo> mapMigrateStorage) {
if (MapUtils.isEmpty(mapMigrateStorage)) {
return false;
}
try {
for (Map.Entry<String, MigrateCommand.MigrateDiskInfo> entry : mapMigrateStorage.entrySet()) {
MigrateCommand.MigrateDiskInfo diskInfo = entry.getValue();
if (isClvmBlockDevice(diskInfo)) {
logger.debug("Found disk targeting CLVM/CLVM_NG destination pool");
return true;
}
}
} catch (final Exception e) {
logger.debug("Failed to check for CLVM destination disks: {}. Assuming no CLVM disks.", e.getMessage());
}
return false;
}
/**
* Filters out disk labels that target CLVM or CLVM_NG destination pools from the migration disk labels set.
* CLVM/CLVM_NG disks are pre-created/activated on the destination before VM migration,
* so they should not be migrated by libvirt.
*
* @param migrateDiskLabels the original set of disk labels to migrate
* @param diskDefinitions the list of disk definitions to map labels to paths
* @param mapMigrateStorage the map containing migration disk information with destination pool types
* @return a new set with CLVM/CLVM_NG disk labels removed
*/
protected Set<String> filterOutClvmDisks(Set<String> migrateDiskLabels,
List<DiskDef> diskDefinitions,
Map<String, MigrateCommand.MigrateDiskInfo> mapMigrateStorage) {
if (migrateDiskLabels == null || migrateDiskLabels.isEmpty()) {
return migrateDiskLabels;
}
Set<String> filteredLabels = new HashSet<>(migrateDiskLabels);
try {
Set<String> clvmDiskPaths = new HashSet<>();
for (Map.Entry<String, MigrateCommand.MigrateDiskInfo> entry : mapMigrateStorage.entrySet()) {
MigrateCommand.MigrateDiskInfo diskInfo = entry.getValue();
if (isClvmBlockDevice(diskInfo)) {
clvmDiskPaths.add(entry.getKey());
logger.debug("Identified CLVM/CLVM_NG destination disk: {}", entry.getKey());
}
}
// Map disk paths to labels and remove CLVM disk labels from the migration set
if (!clvmDiskPaths.isEmpty()) {
for (String clvmDiskPath : clvmDiskPaths) {
for (DiskDef diskDef : diskDefinitions) {
String diskPath = diskDef.getDiskPath();
if (diskPath != null && diskPath.contains(clvmDiskPath)) {
String label = diskDef.getDiskLabel();
if (filteredLabels.remove(label)) {
logger.info("Excluded disk label {} (path: {}) from libvirt migration - CLVM/CLVM_NG destination",
label, clvmDiskPath);
}
break;
}
}
}
}
} catch (final Exception e) {
logger.warn("Failed to filter CLVM disks: {}. Proceeding with original disk list.", e.getMessage());
return migrateDiskLabels;
}
return filteredLabels;
}
private boolean isClvmBlockDevice(MigrateCommand.MigrateDiskInfo diskInfo) {
if (diskInfo == null ||diskInfo.getDestPoolType() == null) {
return false;
}
return (Storage.StoragePoolType.CLVM.equals(diskInfo.getDestPoolType()) || Storage.StoragePoolType.CLVM_NG.equals(diskInfo.getDestPoolType()));
}
}