diff --git a/core/src/main/java/com/cloud/agent/api/MigrateCommand.java b/core/src/main/java/com/cloud/agent/api/MigrateCommand.java index 5ac4e9ae445..d9fb447d655 100644 --- a/core/src/main/java/com/cloud/agent/api/MigrateCommand.java +++ b/core/src/main/java/com/cloud/agent/api/MigrateCommand.java @@ -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 diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java index 20bacdf45b9..867470dac04 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java @@ -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(); } diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java index 6b21f2c2895..cc6ceca5d60 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java @@ -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. */ diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java index 8fef202ddbc..922a13807d0 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java @@ -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 worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc, - migrateStorage, migrateNonSharedInc, + effectiveMigrateStorage, migrateNonSharedInc, command.isAutoConvergence(), vmName, command.getDestinationIp(), migrateDiskLabels); final Future migrateThread = executor.submit(worker); executor.shutdown(); @@ -1073,4 +1093,93 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper]*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 mapMigrateStorage) { + if (MapUtils.isEmpty(mapMigrateStorage)) { + return false; + } + + try { + for (Map.Entry 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 filterOutClvmDisks(Set migrateDiskLabels, + List diskDefinitions, + Map mapMigrateStorage) { + if (migrateDiskLabels == null || migrateDiskLabels.isEmpty()) { + return migrateDiskLabels; + } + + Set filteredLabels = new HashSet<>(migrateDiskLabels); + + try { + Set clvmDiskPaths = new HashSet<>(); + for (Map.Entry 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())); + } }