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 947b4af8f69..20bacdf45b9 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 @@ -285,6 +285,7 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot } protected Boolean supportStoragePoolType(StoragePoolType storagePoolType) { - return super.supportStoragePoolType(storagePoolType, StoragePoolType.Filesystem); + return super.supportStoragePoolType(storagePoolType, StoragePoolType.Filesystem, + StoragePoolType.CLVM, StoragePoolType.CLVM_NG); } } 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 61d2caa0e06..6a1ddd06dcc 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 @@ -87,6 +87,7 @@ import com.cloud.agent.api.MigrateCommand; import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo; import com.cloud.agent.api.ModifyTargetsAnswer; import com.cloud.agent.api.ModifyTargetsCommand; +import com.cloud.agent.api.PreMigrationCommand; import com.cloud.agent.api.PrepareForMigrationCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; @@ -2096,7 +2097,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { MigrateCommand.MigrateDiskInfo migrateDiskInfo; - boolean isNonManagedToNfs = supportStoragePoolType(sourceStoragePool.getPoolType(), StoragePoolType.Filesystem) && destStoragePool.getPoolType() == StoragePoolType.NetworkFilesystem && !managedStorageDestination; + boolean isNonManagedToNfs = supportStoragePoolType(sourceStoragePool.getPoolType(), StoragePoolType.Filesystem, StoragePoolType.CLVM, StoragePoolType.CLVM_NG) && destStoragePool.getPoolType() == StoragePoolType.NetworkFilesystem && !managedStorageDestination; if (isNonManagedToNfs) { migrateDiskInfo = new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.FILE, @@ -2116,6 +2117,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, destVolumeInfo); } + prepareDisksForMigrationForClvm(vmTO, volumeDataStoreMap, srcHost); + PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO); Answer pfma; @@ -2211,10 +2214,42 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { } } + private void prepareDisksForMigrationForClvm(VirtualMachineTO vmTO, Map volumeDataStoreMap, Host srcHost) { + // For CLVM/CLVM_NG source pools, convert volumes from exclusive to shared mode + // on the source host BEFORE PrepareForMigrationCommand on the destination. + boolean hasClvmSource = volumeDataStoreMap.keySet().stream() + .map(v -> _storagePoolDao.findById(v.getPoolId())) + .anyMatch(p -> p != null && (p.getPoolType() == StoragePoolType.CLVM || p.getPoolType() == StoragePoolType.CLVM_NG)); + + if (hasClvmSource) { + logger.info("CLVM/CLVM_NG source pool detected for VM [{}], sending PreMigrationCommand to source host [{}] to convert volumes to shared mode.", vmTO.getName(), srcHost.getId()); + PreMigrationCommand preMigCmd = new PreMigrationCommand(vmTO, vmTO.getName()); + try { + Answer preMigAnswer = agentManager.send(srcHost.getId(), preMigCmd); + if (preMigAnswer == null || !preMigAnswer.getResult()) { + String details = preMigAnswer != null ? preMigAnswer.getDetails() : "null answer returned"; + logger.warn("PreMigrationCommand failed for CLVM/CLVM_NG VM [{}] on source host [{}]: {}. Migration will continue but may fail if volumes are exclusively locked.", vmTO.getName(), srcHost.getId(), details); + } else { + logger.info("Successfully converted CLVM/CLVM_NG volumes to shared mode on source host [{}] for VM [{}].", srcHost.getId(), vmTO.getName()); + } + } catch (Exception e) { + logger.warn("Failed to send PreMigrationCommand to source host [{}] for VM [{}]: {}. Migration will continue but may fail if volumes are exclusively locked.", srcHost.getId(), vmTO.getName(), e.getMessage()); + } + } + } + private MigrationOptions.Type decideMigrationTypeAndCopyTemplateIfNeeded(Host destHost, VMInstanceVO vmInstance, VolumeInfo srcVolumeInfo, StoragePoolVO sourceStoragePool, StoragePoolVO destStoragePool, DataStore destDataStore) { VMTemplateVO vmTemplate = _vmTemplateDao.findById(vmInstance.getTemplateId()); String srcVolumeBackingFile = getVolumeBackingFile(srcVolumeInfo); + + // Check if source is CLVM/CLVM_NG (block device storage) + // LinkedClone (VIR_MIGRATE_NON_SHARED_INC) only works for file → file migrations + // For block device sources, use FullClone (VIR_MIGRATE_NON_SHARED_DISK) + boolean sourceIsBlockDevice = sourceStoragePool.getPoolType() == StoragePoolType.CLVM || + sourceStoragePool.getPoolType() == StoragePoolType.CLVM_NG; + if (StringUtils.isNotBlank(srcVolumeBackingFile) && supportStoragePoolType(destStoragePool.getPoolType(), StoragePoolType.Filesystem) && + !sourceIsBlockDevice && srcVolumeInfo.getTemplateId() != null && Objects.nonNull(vmTemplate) && !Arrays.asList(KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME, VM_IMPORT_DEFAULT_TEMPLATE_NAME).contains(vmTemplate.getName())) { @@ -2222,8 +2257,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { copyTemplateToTargetFilesystemStorageIfNeeded(srcVolumeInfo, sourceStoragePool, destDataStore, destStoragePool, destHost); return MigrationOptions.Type.LinkedClone; } - logger.debug(String.format("Skipping copy template from source storage pool [%s] to target storage pool [%s] before migration due to volume [%s] does not have a " + - "template or we are doing full clone migration.", sourceStoragePool.getId(), destStoragePool.getId(), srcVolumeInfo.getId())); + + if (sourceIsBlockDevice) { + logger.debug(String.format("Source storage pool [%s] is block device (CLVM/CLVM_NG). Using FullClone migration for volume [%s] to target storage pool [%s]. Template copy skipped as entire volume will be copied.", sourceStoragePool.getId(), srcVolumeInfo.getId(), destStoragePool.getId())); + } else { + logger.debug(String.format("Skipping copy template from source storage pool [%s] to target storage pool [%s] before migration due to volume [%s] does not have a template or we are doing full clone migration.", sourceStoragePool.getId(), destStoragePool.getId(), srcVolumeInfo.getId())); + } return MigrationOptions.Type.FullClone; }