diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java index 1a445080b5c..5c96e4b7057 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -212,6 +212,7 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); throw new CloudRuntimeException("Failed to delete Veeam B&R job and backup, an operation may be in progress. Please try again after some time."); } + client.syncBackupRepository(); return true; } @@ -245,6 +246,8 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, return false; } + client.syncBackupRepository(); + List allBackups = backupDao.listByVmId(backup.getZoneId(), backup.getVmId()); for (Backup b : allBackups) { if (b.getId() != backup.getId()) { diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java index 7d3bfc50d18..bdc5723f79d 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java @@ -604,6 +604,7 @@ public class VeeamClient { joiner.add("PowerShell Add-PSSnapin VeeamPSSnapin"); } else { joiner.add("PowerShell Import-Module Veeam.Backup.PowerShell -WarningAction SilentlyContinue"); + joiner.add("$ProgressPreference='SilentlyContinue'"); } for (String cmd : cmds) { joiner.add(cmd); @@ -646,9 +647,7 @@ public class VeeamClient { String.format("$job = Get-VBRJob -Name '%s'", jobName), "if ($job) { Remove-VBRJob -Job $job -Confirm:$false }", String.format("$backup = Get-VBRBackup -Name '%s'", jobName), - "if ($backup) { Remove-VBRBackup -Backup $backup -FromDisk -Confirm:$false }", - "$repo = Get-VBRBackupRepository", - "Sync-VBRBackupRepository -Repository $repo" + "if ($backup) { Remove-VBRBackup -Backup $backup -FromDisk -Confirm:$false }" )); return result != null && result.first() && !result.second().contains(FAILED_TO_DELETE); } @@ -658,8 +657,6 @@ public class VeeamClient { Pair result = executePowerShellCommands(Arrays.asList( String.format("$restorePoint = Get-VBRRestorePoint ^| Where-Object { $_.Id -eq '%s' }", restorePointId), "if ($restorePoint) { Remove-VBRRestorePoint -Oib $restorePoint -Confirm:$false", - "$repo = Get-VBRBackupRepository", - "Sync-VBRBackupRepository -Repository $repo", "} else { ", " Write-Output 'Failed to delete'", " Exit 1", @@ -668,6 +665,17 @@ public class VeeamClient { return result != null && result.first() && !result.second().contains(FAILED_TO_DELETE); } + public boolean syncBackupRepository() { + LOG.debug("Trying to sync backup repository."); + Pair result = executePowerShellCommands(Arrays.asList( + "$repo = Get-VBRBackupRepository", + "$Syncs = Sync-VBRBackupRepository -Repository $repo", + "while ((Get-VBRSession -ID $Syncs.ID).Result -ne 'Success') { Start-Sleep -Seconds 10 }" + )); + LOG.debug("Done syncing backup repository."); + return result != null && result.first(); + } + public Map getBackupMetrics() { if (isLegacyServer()) { return getBackupMetricsLegacy(); diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index aa3b314fb3f..410fc7c2a7c 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -1022,12 +1022,12 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co /** * Get dest volume full path */ - private String getDestVolumeFullPath(VirtualDisk restoredDisk, VirtualMachineMO restoredVm, VirtualMachineMO vmMo) throws Exception { + private String getDestVolumeFullPath(VirtualMachineMO vmMo) throws Exception { VirtualDisk vmDisk = vmMo.getVirtualDisks().get(0); String vmDiskPath = vmMo.getVmdkFileBaseName(vmDisk); String vmDiskFullPath = getVolumeFullPath(vmMo.getVirtualDisks().get(0)); - String restoredVolumePath = restoredVm.getVmdkFileBaseName(restoredDisk); - return vmDiskFullPath.replace(vmDiskPath, restoredVolumePath); + String uuid = UUID.randomUUID().toString().replace("-", ""); + return vmDiskFullPath.replace(vmDiskPath, uuid); } /** @@ -1079,17 +1079,18 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co VirtualDisk restoredDisk = findRestoredVolume(volumeInfo, vmRestored); String diskPath = vmRestored.getVmdkFileBaseName(restoredDisk); - s_logger.debug("Restored disk size=" + toHumanReadableSize(restoredDisk.getCapacityInKB()) + " path=" + diskPath); + s_logger.debug("Restored disk size=" + toHumanReadableSize(restoredDisk.getCapacityInKB() * Resource.ResourceType.bytesToKiB) + " path=" + diskPath); // Detach restored VM disks - vmRestored.detachAllDisks(); + vmRestored.detachDisk(String.format("%s/%s.vmdk", location, diskPath), false); String srcPath = getVolumeFullPath(restoredDisk); - String destPath = getDestVolumeFullPath(restoredDisk, vmRestored, vmMo); + String destPath = getDestVolumeFullPath(vmMo); VirtualDiskManagerMO virtualDiskManagerMO = new VirtualDiskManagerMO(dcMo.getContext()); // Copy volume to the VM folder + s_logger.debug(String.format("Moving volume from %s to %s", srcPath, destPath)); virtualDiskManagerMO.moveVirtualDisk(srcPath, dcMo.getMor(), destPath, dcMo.getMor(), true); try { @@ -1103,11 +1104,13 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co vmRestored.destroy(); } - VirtualDisk attachedDisk = getAttachedDisk(vmMo, diskPath); + s_logger.debug(String.format("Attaching disk %s to vm %s", destPath, vm.getId())); + VirtualDisk attachedDisk = getAttachedDisk(vmMo, destPath); if (attachedDisk == null) { - s_logger.error("Failed to get the attached the (restored) volume " + diskPath); + s_logger.error("Failed to get the attached the (restored) volume " + destPath); return false; } + s_logger.debug(String.format("Creating volume entry for disk %s attached to vm %s", destPath, vm.getId())); createVolume(attachedDisk, vmMo, vm.getDomainId(), vm.getDataCenterId(), vm.getAccountId(), vm.getId(), poolId, vm.getTemplateId(), backup, false); if (vm.getBackupOfferingId() == null) { @@ -1119,9 +1122,9 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co return true; } - private VirtualDisk getAttachedDisk(VirtualMachineMO vmMo, String diskPath) throws Exception { + private VirtualDisk getAttachedDisk(VirtualMachineMO vmMo, String diskFullPath) throws Exception { for (VirtualDisk disk : vmMo.getVirtualDisks()) { - if (vmMo.getVmdkFileBaseName(disk).equals(diskPath)) { + if (getVolumeFullPath(disk).equals(diskFullPath)) { return disk; } } diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageLayoutHelper.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageLayoutHelper.java index 1cb57c37813..b6b92f67ec5 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageLayoutHelper.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageLayoutHelper.java @@ -218,7 +218,10 @@ public class VmwareStorageLayoutHelper implements Configurable { } public static void syncVolumeToRootFolder(DatacenterMO dcMo, DatastoreMO ds, String vmdkName, String vmName, String excludeFolders) throws Exception { - String fileDsFullPath = ds.searchFileInSubFolders(vmdkName + ".vmdk", false, excludeFolders); + String fileDsFullPath = ds.searchFileInSubFolders(String.format("%s/%s.vmdk", vmName, vmdkName), false, excludeFolders); + if (fileDsFullPath == null) { + fileDsFullPath = ds.searchFileInSubFolders(vmdkName + ".vmdk", false, excludeFolders); + } if (fileDsFullPath == null) return; @@ -409,6 +412,22 @@ public class VmwareStorageLayoutHelper implements Configurable { return String.format("[%s] %s/%s", dsMo.getName(), vmName, vmdkFileName); } + public static String getDatastoreVolumePath(DatastoreMO dsMo, String vmName, String volumePath) throws Exception { + String datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, vmName, volumePath + ".vmdk"); + if (dsMo.folderExists(String.format("[%s]", dsMo.getName()), vmName) && dsMo.fileExists(datastoreVolumePath)) { + return datastoreVolumePath; + } + datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, volumePath, volumePath + ".vmdk"); + if (dsMo.folderExists(String.format("[%s]", dsMo.getName()), volumePath) && dsMo.fileExists(datastoreVolumePath)) { + return datastoreVolumePath; + } + datastoreVolumePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo, volumePath + ".vmdk"); + if (dsMo.fileExists(datastoreVolumePath)) { + return datastoreVolumePath; + } + return dsMo.searchFileInSubFolders(volumePath + ".vmdk", false, null); + } + @Override public String getConfigComponentName() { return VmwareStorageLayoutHelper.class.getSimpleName(); diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java index ba9d16f8186..7f4f517a293 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java @@ -2062,17 +2062,7 @@ public class VmwareStorageProcessor implements StorageProcessor { datastoreVolumePath = dsMo.getDatastorePath((vmdkPath != null ? vmdkPath : dsMo.getName()) + ".vmdk"); } else { if (dsMo.getDatastoreType().equalsIgnoreCase("VVOL")) { - datastoreVolumePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo, volumePath + ".vmdk"); - if (!dsMo.fileExists(datastoreVolumePath)) { - datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, vmName, volumePath + ".vmdk"); - } - if (!dsMo.folderExists(String.format("[%s]", dsMo.getName()), vmName) || !dsMo.fileExists(datastoreVolumePath)) { - datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, volumePath, volumePath + ".vmdk"); - } - if (!dsMo.folderExists(String.format("[%s]", dsMo.getName()), volumePath) || !dsMo.fileExists(datastoreVolumePath)) { - datastoreVolumePath = dsMo.searchFileInSubFolders(volumePath + ".vmdk", true, null); - } - + datastoreVolumePath = VmwareStorageLayoutHelper.getDatastoreVolumePath(dsMo, vmName, volumePath); } else { datastoreVolumePath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dsMo.getOwnerDatacenter().first(), vmName, dsMo, volumePath, VmwareManager.s_vmwareSearchExcludeFolder.value()); } @@ -2101,16 +2091,7 @@ public class VmwareStorageProcessor implements StorageProcessor { } dsMo = new DatastoreMO(context, morDs); - datastoreVolumePath = VmwareStorageLayoutHelper.getLegacyDatastorePathFromVmdkFileName(dsMo, volumePath + ".vmdk"); - if (!dsMo.fileExists(datastoreVolumePath)) { - datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, vmName, volumePath + ".vmdk"); - } - if (!dsMo.folderExists(String.format("[%s]", dsMo.getName()), vmName) || !dsMo.fileExists(datastoreVolumePath)) { - datastoreVolumePath = VmwareStorageLayoutHelper.getVmwareDatastorePathFromVmdkFileName(dsMo, volumePath, volumePath + ".vmdk"); - } - if (!dsMo.folderExists(String.format("[%s]", dsMo.getName()), volumePath) || !dsMo.fileExists(datastoreVolumePath)) { - datastoreVolumePath = dsMo.searchFileInSubFolders(volumePath + ".vmdk", true, null); - } + datastoreVolumePath = VmwareStorageLayoutHelper.getDatastoreVolumePath(dsMo, vmName, volumePath); } } diff --git a/test/integration/smoke/test_backup_recovery_veeam.py b/test/integration/smoke/test_backup_recovery_veeam.py index d0da66fa7c2..f99b3db17d4 100644 --- a/test/integration/smoke/test_backup_recovery_veeam.py +++ b/test/integration/smoke/test_backup_recovery_veeam.py @@ -235,13 +235,13 @@ class TestVeeamBackupAndRecovery(cloudstackTestCase): if self.offering: self.cleanup.insert(0, self.offering) - self.vm = VirtualMachine.create(self.user_apiclient, self.services["small"], accountid=self.account.name, - domainid=self.account.domainid, serviceofferingid=self.service_offering.id) - self.vm_with_datadisk = VirtualMachine.create(self.user_apiclient, self.services["small"], accountid=self.account.name, domainid=self.account.domainid, serviceofferingid=self.service_offering.id, diskofferingid=self.disk_offering.id) + self.vm = VirtualMachine.create(self.user_apiclient, self.services["small"], accountid=self.account.name, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id) + # Assign VM to offering and create ad-hoc backup self.offering.assignOffering(self.user_apiclient, self.vm_with_datadisk.id) @@ -296,7 +296,13 @@ class TestVeeamBackupAndRecovery(cloudstackTestCase): listall=True ) self.assertTrue(isinstance(vm_volumes, list), "List volumes should return a valid list") - self.assertEqual(3, len(vm_volumes), "The number of volumes should be 2") + self.assertEqual(3, len(vm_volumes), "The number of volumes should be 3") finally: # Delete backup Backup.delete(self.user_apiclient, backup.id, forced=True) + # Remove VM from offering + self.offering.removeOffering(self.user_apiclient, self.vm_with_datadisk.id) + # Delete vm + self.vm.delete(self.apiclient) + # Delete vm with datadisk + self.vm_with_datadisk.delete(self.apiclient)