diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index 0d23442bab73..b3d2ab09a958 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -57,7 +57,7 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer ConfigKey BackupProviderPlugin = new ConfigKey<>("Advanced", String.class, "backup.framework.provider.plugin", "dummy", - "The backup and recovery provider plugin.", true, ConfigKey.Scope.Zone, BackupFrameworkEnabled.key()); + "The backup and recovery provider plugin (comma-separated). Example: dummy, veeam, networker, nas, commvault", true, ConfigKey.Scope.Zone, BackupFrameworkEnabled.key()); ConfigKey BackupSyncPollingInterval = new ConfigKey<>("Advanced", Long.class, "backup.framework.sync.interval", diff --git a/plugins/backup/ablestack-nas/src/main/java/org/apache/cloudstack/backup/AblestackNasBackupProvider.java b/plugins/backup/ablestack-nas/src/main/java/org/apache/cloudstack/backup/AblestackNasBackupProvider.java index ab13de00c9c7..0fd82a32e2ca 100644 --- a/plugins/backup/ablestack-nas/src/main/java/org/apache/cloudstack/backup/AblestackNasBackupProvider.java +++ b/plugins/backup/ablestack-nas/src/main/java/org/apache/cloudstack/backup/AblestackNasBackupProvider.java @@ -784,7 +784,7 @@ private List getBackupFileChain(String volumeUuid, Backup backup) { loadBackupDetailsIfNeeded(backup); if (isLegacyBackup(backup)) { Backup.VolumeInfo volumeInfo = getBackedUpVolumeInfo(backup.getBackedUpVolumes(), volumeUuid); - return volumeInfo != null ? getLegacyBackupFileCandidates(volumeInfo) : List.of(); + return volumeInfo != null ? List.of(getLegacyBackupFileName(volumeInfo)) : List.of(); } String backupEngine = getBackupDetail(backup, DETAIL_BACKUP_ENGINE); @@ -871,43 +871,8 @@ private boolean isLegacyBackup(Backup backup) { } private String getLegacyBackupFileName(Backup.VolumeInfo volumeInfo) { - String volumePath = volumeInfo.getPath(); - if (StringUtils.isNotBlank(volumePath) && - (volumePath.endsWith(".qcow2") || volumePath.endsWith(".raw") || volumePath.endsWith(".rbdiff"))) { - return volumePath; - } String diskPrefix = Volume.Type.ROOT.equals(volumeInfo.getType()) ? "root" : "datadisk"; - return String.format("%s.%s.qcow2", diskPrefix, volumeInfo.getPath()); - } - - private List getLegacyBackupFileCandidates(Backup.VolumeInfo volumeInfo) { - List candidates = new ArrayList<>(); - String volumePath = volumeInfo.getPath(); - if (StringUtils.isNotBlank(volumePath)) { - candidates.add(volumePath); - if (volumePath.contains("/")) { - String baseName = volumePath.substring(volumePath.lastIndexOf('/') + 1); - if (!Objects.equals(volumePath, baseName)) { - candidates.add(baseName); - } - } - } - - String legacyFileName = getLegacyBackupFileName(volumeInfo); - if (!candidates.contains(legacyFileName)) { - candidates.add(legacyFileName); - } - - if (volumePath != null && volumePath.contains("/")) { - String baseName = volumePath.substring(volumePath.lastIndexOf('/') + 1); - String diskPrefix = Volume.Type.ROOT.equals(volumeInfo.getType()) ? "root" : "datadisk"; - String baseNameLegacyFile = String.format("%s.%s.qcow2", diskPrefix, baseName); - if (!candidates.contains(baseNameLegacyFile)) { - candidates.add(baseNameLegacyFile); - } - } - - return candidates; + return String.format("%s.%s.qcow2", diskPrefix, volumeInfo.getUuid()); } private List getVolumePaths(List volumes) { diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index cc7b471313b7..3fd26bd210a8 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -2489,6 +2489,20 @@ private void syncOutOfBandBackups(final BackupProvider backupProvider, DataCente } for (final VMInstanceVO vm : vms) { try { + Long backupOfferingId = vm.getBackupOfferingId(); + if (backupOfferingId == null) { + logger.debug("Skipping VM [{}] because backup offering is not assigned.", vm); + continue; + } + BackupOfferingVO offering = backupOfferingDao.findById(vm.getBackupOfferingId()); + if (offering == null) { + logger.debug("Skipping VM [{}] because backup offering [{}] was not found.", vm, backupOfferingId); + continue; + } + if (!backupProvider.getName().equalsIgnoreCase(offering.getProvider())) { + logger.debug("Skipping VM [{}] because backup offering provider [{}] does not match current provider [{}].", vm, offering.getProvider(), backupProvider.getName()); + continue; + } logger.debug(String.format("Trying to sync backups of VM [%s] using backup provider [%s].", vm, backupProvider.getName())); // Sync out-of-band backups syncBackups(backupProvider, vm); diff --git a/ui/tests/unit/components/view/ActionButton.spec.js b/ui/tests/unit/components/view/ActionButton.spec.js index 3c1844e6ddec..b7d80f799c39 100644 --- a/ui/tests/unit/components/view/ActionButton.spec.js +++ b/ui/tests/unit/components/view/ActionButton.spec.js @@ -131,11 +131,11 @@ describe('Components > View > ActionButton.vue', () => { it('API should be called and return not empty', async (done) => { mockAxios.mockResolvedValue({ testapinameresponse: { count: 2 } }) const wrapper = factory({ - props: { - actions: [ - { - command: 'test-api-case-3', - response: 'json', + props: { + actions: [ + { + command: 'test-api-case-3', + response: 'json', label: 'label.action', api: 'test-api-case-3', showBadge: true, @@ -169,10 +169,10 @@ describe('Components > View > ActionButton.vue', () => { it('API should be called and return empty', async (done) => { mockAxios.mockResolvedValue({ data: [] }) const wrapper = factory({ - props: { - actions: [ - { - command: 'test-api-case-4', + props: { + actions: [ + { + command: 'test-api-case-4', response: 'json', label: 'label.action', api: 'test-api-case-4', @@ -207,10 +207,10 @@ describe('Components > View > ActionButton.vue', () => { it('API should be called and throw eror', async (done) => { mockAxios.mockRejectedValue('errMethodMessage') const wrapper = factory({ - props: { - actions: [ - { - command: 'test-api-case-5', + props: { + actions: [ + { + command: 'test-api-case-5', response: 'json', label: 'label.action', api: 'test-api-case-5',