From 75c67993696815f20e4c0bea0ede87bd6d331f06 Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:33:59 +0900 Subject: [PATCH 01/34] =?UTF-8?q?=EB=8B=A4=ED=81=AC=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/style/dark-mode.less | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/ui/src/style/dark-mode.less b/ui/src/style/dark-mode.less index 15462a3ef08a..ce5dc4b48683 100644 --- a/ui/src/style/dark-mode.less +++ b/ui/src/style/dark-mode.less @@ -1135,10 +1135,10 @@ .quickview-context-menu, .autogen-action-dropdown__content { - background: @dropdown-bgColor; - border-color: @dark-border-color; - box-shadow: 0 8px 24px @box-shadow-1; - color: @dark-text-color-4; + background: @dropdown-bgColor !important; + border-color: @dark-border-color !important; + box-shadow: 0 8px 24px @box-shadow-1 !important; + color: @dark-text-color-4 !important; } .quickview-context-menu .action-button-title, @@ -1163,10 +1163,17 @@ background: rgba(0, 0, 0, 0.50) } - .field-key, - .banner-label, - .banner-main { + .auto-alert-banner-container .banner-field { + background: rgba(0, 0, 0, 0.85); + } + + .banner-main, + .field-key { + color: #cfc9c9; + } + + .banner-label { color: #cfc9c9; - background-color: rgb(150 96 96 / 50%); + background-color: rgba(150, 96, 96, 0.5); } } From c38e580ebd778b8c6fb60467bdc7eeafcd7182e8 Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:59:48 +0900 Subject: [PATCH 02/34] =?UTF-8?q?=EA=B0=80=EC=83=81=EB=A8=B8=EC=8B=A0=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=9D=98=20=EB=94=94=EC=8A=A4=ED=94=8C?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EB=AA=85(=ED=91=9C=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84)=20=ED=95=AD=EB=AA=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/public/locales/ko_KR.json | 6 +++--- ui/src/config/section/compute.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/public/locales/ko_KR.json b/ui/public/locales/ko_KR.json index 8c8acbac78bd..3f6977000bfb 100644 --- a/ui/public/locales/ko_KR.json +++ b/ui/public/locales/ko_KR.json @@ -856,8 +856,8 @@ "label.disksizeused": "\uc0ac\uc6a9\ub41c \ub514\uc2a4\ud06c \ud06c\uae30", "label.disksizeusedgb": "\uc0ac\uc6a9\ub41c", "label.display.text": "\ud14d\uc2a4\ud2b8 \ud45c\uc2dc", -"label.displayname": "\uc774\ub984 \ud45c\uc2dc", -"label.displaynetwork": "\ub124\ud2b8\uc6cc\ud06c \uc774\ub984 \ud45c\uc2dc", +"label.displayname": "\ud45c\uc2dc \uc774\ub984", +"label.displaynetwork": "\ub124\ud2b8\uc6cc\ud06c \ud45c\uc2dc \uc774\ub984", "label.displaytext": "\uc124\uba85", "label.distributedvpcrouter": "\ubd84\uc0b0 VPC \ub77c\uc6b0\ud130", "label.dns": "DNS", @@ -3517,7 +3517,7 @@ "message.resource.not.found": "\ub9ac\uc18c\uc2a4\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "message.restart.mgmt.server": "\uc0c8\ub85c\uc6b4 \uc124\uc815\uc744 \uc0ac\uc6a9 \ud558\uae30 \uc704\ud574 \uad00\ub9ac \uc11c\ubc84\ub97c \uc7ac\uc2dc\uc791\ud574 \uc8fc\uc2ed\uc2dc\uc624.", "message.restart.network": "\ud604\uc7ac \ub124\ud2b8\uc6cc\ud06c\ub85c \uc81c\uacf5\ud558\ub294 \ubaa8\ub4e0 \uc11c\ube44\uc2a4\uac00 \uc911\ub2e8\ub429\ub2c8\ub2e4. \uc774 \ub124\ud2b8\uc6cc\ud06c\ub97c \uc7ac\uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", -"message.restart.vm.to.update.settings": "\uc774\ub984 \ubc0f \uc774\ub984 \ud45c\uc2dc \uc774\uc678\uc758 \ud56d\ubaa9\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\ub824\uba74 VM\uc744 \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4.", +"message.restart.vm.to.update.settings": "\uc774\ub984 \ubc0f \ud45c\uc2dc \uc774\ub984 \uc774\uc678\uc758 \ud56d\ubaa9\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\ub824\uba74 VM\uc744 \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4.", "message.restart.vpc": "VPC\ub97c \uc7ac\uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "message.restart.vpc.remark": "VPC\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud560 \uac83\uc778\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624.

\ucc38\uace0 : \ube44 \uc911\ubcf5 VPC\ub97c \uc774\uc911\ud654\ud558\uba74 \uac15\uc81c\ub85c \uc815\ub9ac\ub429\ub2c8\ub2e4. \uba87 \ubd84 \ub3d9\uc548 \ub124\ud2b8\uc6cc\ud06c\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4 . ", "message.scale.processing": "\uc9c4\ud589\uc911", diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 4eecbe3de076..597c86d52704 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -47,7 +47,7 @@ export default { return filters }, columns: () => { - const fields = ['name', 'state', 'qemuagentversion', 'ipaddress'] + const fields = ['name', 'displayname', 'state', 'qemuagentversion', 'ipaddress'] const metricsFields = ['cpunumber', 'cputotal', 'cpuused', 'memorytotal', { memoryused: (record) => { From 34e8fce6bd6d44d08fcaae0110bf668c078bf228 Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:44:12 +0900 Subject: [PATCH 03/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/main/java/com/cloud/event/EventTypes.java | 2 +- api/src/main/java/com/cloud/storage/VolumeApiService.java | 2 +- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 97d889bebcfa..b9c8f0b55a6d 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -799,7 +799,7 @@ public class EventTypes { // DISASTER RECOVERY public static final String EVENT_DISASTER_RECOVERY_CLUSTER = "DISASTER.RECOVERY.CLUSTER"; - + // Resource Limit public static final String EVENT_RESOURCE_LIMIT_UPDATE = "RESOURCE.LIMIT.UPDATE"; diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 1239d37af26d..0698c09e0887 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -197,6 +197,6 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc Volume cloneVolumeFromSnapshot(Volume volume, long snapshotId, Long vmId) throws StorageUnavailableException; Volume updateCompressDedupVolume(UpdateCompressDedupCmd cmd); - + Long getVolumePhysicalSize(Storage.ImageFormat format, String path, String chainInfo); } 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 e41d218aeb97..92d673d801d5 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -72,7 +72,6 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher; import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; From 12808ee9d80f2ebdc5ddaf8f3fcd2cbba71205eb Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:54:05 +0900 Subject: [PATCH 04/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/cloud/utils/db/GenericDaoBase.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java index c7f2daadc518..28e182a624c5 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java @@ -2445,4 +2445,12 @@ private Optional> getConverter(Field field) { } } + public static class SumCount { + public long sum; + public long count; + + public SumCount() { + } + } + } From c585cad88e07e4865c98935a2a24014c339ae0e8 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 16 Jan 2026 12:20:31 +0900 Subject: [PATCH 05/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/main/java/com/cloud/event/EventTypes.java | 2 +- api/src/main/java/com/cloud/storage/VolumeApiService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 97d889bebcfa..b9c8f0b55a6d 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -799,7 +799,7 @@ public class EventTypes { // DISASTER RECOVERY public static final String EVENT_DISASTER_RECOVERY_CLUSTER = "DISASTER.RECOVERY.CLUSTER"; - + // Resource Limit public static final String EVENT_RESOURCE_LIMIT_UPDATE = "RESOURCE.LIMIT.UPDATE"; diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 1239d37af26d..0698c09e0887 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -197,6 +197,6 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc Volume cloneVolumeFromSnapshot(Volume volume, long snapshotId, Long vmId) throws StorageUnavailableException; Volume updateCompressDedupVolume(UpdateCompressDedupCmd cmd); - + Long getVolumePhysicalSize(Storage.ImageFormat format, String path, String chainInfo); } From 3762f5405a19632c2fb2b1dd49a23256ae081434 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 16 Jan 2026 12:21:59 +0900 Subject: [PATCH 06/34] Update BackupVO.java --- .../src/main/java/org/apache/cloudstack/backup/BackupVO.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java index 81dd0fea68d3..ebe98b3dda53 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -22,12 +22,10 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.StringUtils; -import java.beans.Transient; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.UUID; import javax.persistence.Column; From 979c859e1289b646bafd630526eebb429c720afd Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Fri, 16 Jan 2026 12:57:42 +0900 Subject: [PATCH 07/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/cloud/storage/dao/BucketDaoImpl.java | 8 ++++++++ .../org/apache/cloudstack/backup/dao/BackupDaoImpl.java | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java index 473879d933dc..963bcd6cfe6b 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java @@ -114,4 +114,12 @@ public Long calculateObjectStorageAllocationForAccount(long accountId) { Long totalQuota = customSearch(sc, null).get(0).sum; return (totalQuota * Resource.ResourceType.bytesToGiB); } + + public static class SumCount { + public long sum; + public long count; + + public SumCount() { + } + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java index 3dbdb5abc828..674e8cf428e6 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java @@ -233,4 +233,12 @@ public BackupResponse newBackupResponse(Backup backup) { response.setObjectName("backup"); return response; } + + public static class SumCount { + public long sum; + public long count; + + public SumCount() { + } + } } From c51ebf288f6b27177e0f413206e6422c3bdc3b0f Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:00:10 +0900 Subject: [PATCH 08/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/backup/DeleteBackupScheduleCmd.java | 18 ++++--- .../cloudstack/backup/BackupManager.java | 7 +-- .../cloudstack/backup/BackupScheduleVO.java | 9 ++++ .../apache/cloudstack/backup/BackupVO.java | 2 - .../cloud/storage/VolumeApiServiceImpl.java | 1 + .../cloudstack/backup/BackupManagerImpl.java | 51 ++++++++++++++++--- .../cloudstack/backup/BackupManagerTest.java | 1 + 7 files changed, 71 insertions(+), 18 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index 0245f228b895..fa721ef5c98e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.backup.BackupManager; @@ -51,13 +52,15 @@ public class DeleteBackupScheduleCmd extends BaseCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, - type = CommandType.UUID, - entityType = UserVmResponse.class, - required = true, - description = "ID of the VM") + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, + description = "ID of the VM from which all backup schedules will be deleted.") private Long vmId; + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = BackupScheduleResponse.class, + since = "4.20.1", description = "ID of the backup schedule to be deleted. It has precedence over the 'virtualmachineid' parameter, " + + "i.e., when the 'id' parameter is specified, the 'virtualmachineid' parameter will be ignored.") + private Long id; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -66,6 +69,9 @@ public Long getVmId() { return vmId; } + public Long getId() { return id; } + + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -73,7 +79,7 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.deleteBackupSchedule(getVmId()); + boolean result = backupManager.deleteBackupSchedule(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); 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 cc99833c7ec1..4105406324e0 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; import org.apache.cloudstack.framework.config.ConfigKey; @@ -159,12 +160,12 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer */ List listBackupSchedule(Long vmId); - /** + /** * Deletes VM backup schedule for a VM - * @param vmId + * @param cmd * @return */ - boolean deleteBackupSchedule(Long vmId); + boolean deleteBackupSchedule(DeleteBackupScheduleCmd cmd); /** * Creates backup of a VM diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java index 0338d7f1c4e1..85573f5f207f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.backup; import java.util.Date; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; @@ -42,6 +43,9 @@ public class BackupScheduleVO implements BackupSchedule { @Column(name = "vm_id") private Long vmId; + @Column(name = "uuid", nullable = false) + private String uuid = UUID.randomUUID().toString(); + @Column(name = "schedule_type") private Short scheduleType; @@ -84,6 +88,11 @@ public long getId() { return id; } + @Override + public String getUuid() { + return uuid; + } + public Long getVmId() { return vmId; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java index 81dd0fea68d3..ebe98b3dda53 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -22,12 +22,10 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.StringUtils; -import java.beans.Transient; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.UUID; import javax.persistence.Column; diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 6d975e1809d4..4dbdb3cea3e2 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -136,6 +136,7 @@ import com.cloud.dc.DataCenterVO; import com.cloud.dc.Pod; import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; import com.cloud.domain.Domain; import com.cloud.domain.dao.DomainDao; import com.cloud.event.ActionEvent; 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 a0f12f9741a4..20cf7f3adcaf 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -33,7 +33,6 @@ import com.cloud.alert.AlertManager; import com.cloud.configuration.Resource; import com.cloud.exception.ResourceAllocationException; -import com.cloud.storage.Snapshot; import com.cloud.storage.VolumeApiService; import com.cloud.user.DomainManager; import com.cloud.user.ResourceLimitService; @@ -86,6 +85,7 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import com.cloud.api.ApiDispatcher; @@ -588,16 +588,53 @@ public List listBackupSchedule(final Long vmId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule") - public boolean deleteBackupSchedule(Long vmId) { - final VMInstanceVO vm = findVmById(vmId); + public boolean deleteBackupSchedule(DeleteBackupScheduleCmd cmd) { + Long vmId = cmd.getVmId(); + Long id = cmd.getId(); + if (ObjectUtils.allNull(vmId, id)) { + throw new InvalidParameterValueException("Either instance ID or ID of backup schedule needs to be specified."); + } + + if (Objects.nonNull(id)) { + BackupSchedule schedule = backupScheduleDao.findById(id); + if (schedule == null) { + throw new InvalidParameterValueException("Could not find the requested backup schedule."); + } + checkCallerAccessToBackupScheduleVm(schedule.getVmId()); + return backupScheduleDao.remove(schedule.getId()); + } + + checkCallerAccessToBackupScheduleVm(vmId); + return deleteAllVmBackupSchedules(vmId); + } + + /** + * Checks if the backup framework is enabled for the zone in which the VM with specified ID is allocated and + * if the caller has access to the VM. + * + * @param vmId The ID of the virtual machine to check access for + * @throws PermissionDeniedException if the caller doesn't have access to the VM + * @throws CloudRuntimeException if the backup framework is disabled + */ + protected void checkCallerAccessToBackupScheduleVm(long vmId) { + VMInstanceVO vm = findVmById(vmId); validateForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + } - final BackupSchedule schedule = backupScheduleDao.findByVM(vmId); - if (schedule == null) { - throw new CloudRuntimeException("VM has no backup schedule defined, no need to delete anything."); + /** + * Deletes all backup schedules associated with a specific VM. + * + * @param vmId The ID of the virtual machine whose backup schedules should be deleted + * @return true if all backup schedules were successfully deleted, false if any deletion failed + */ + protected boolean deleteAllVmBackupSchedules(long vmId) { + List vmBackupSchedules = backupScheduleDao.listByVM(vmId); + boolean success = true; + for (BackupScheduleVO vmBackupSchedule : vmBackupSchedules) { + success = success && backupScheduleDao.remove(vmBackupSchedule.getId()); } - return backupScheduleDao.remove(schedule.getId()); + return success; } @Override diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 66fa2d33cb1e..88892d982d87 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -49,6 +49,7 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; From b3c7a43dfcaf3410b87d7134e51dbfe90b4c3592 Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:04:59 +0900 Subject: [PATCH 09/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 1 + 1 file changed, 1 insertion(+) 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 20cf7f3adcaf..1177c1f9a0e3 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.TimeZone; import java.util.Timer; import java.util.TimerTask; From 6470b65ef6a9af96ee28c3fdcb3eba7e29525b27 Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:25:08 +0900 Subject: [PATCH 10/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/cloudstack/backup/NetworkerBackupProvider.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java index b89d82be540d..191e6fe2d989 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java @@ -34,6 +34,8 @@ import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.backup.Backup.Metric; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDaoImpl; import org.apache.cloudstack.backup.networker.NetworkerClient; @@ -624,4 +626,8 @@ public List listRestorePoints(VirtualMachine vm) { @Override public boolean updateBackupPlan(final Long zoneId, final String retentionPeriod, final String externalId) { return true; } + + @Override + public void syncBackups(VirtualMachine vm, Metric metric) { + } } From 75b6f7b179fbdcec3322fc906fb0b675d82ff4be Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:33:44 +0900 Subject: [PATCH 11/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/cloudstack/backup/NASBackupProvider.java | 4 ++++ .../org/apache/cloudstack/backup/NetworkerBackupProvider.java | 3 +-- .../org/apache/cloudstack/backup/VeeamBackupProvider.java | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 1dd397bd7b3f..951bbeda4e9b 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -461,6 +461,10 @@ public String getConfigComponentName() { return BackupService.class.getSimpleName(); } + @Override + public void syncBackups(VirtualMachine vm, Backup.Metric metric) { + } + @Override public boolean checkBackupAgent(final Long zoneId) { return true; } diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java index 191e6fe2d989..ae4beaa2c82e 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java @@ -35,7 +35,6 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.VMInstanceDao; -import org.apache.cloudstack.backup.Backup.Metric; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDaoImpl; import org.apache.cloudstack.backup.networker.NetworkerClient; @@ -628,6 +627,6 @@ public List listRestorePoints(VirtualMachine vm) { public boolean updateBackupPlan(final Long zoneId, final String retentionPeriod, final String externalId) { return true; } @Override - public void syncBackups(VirtualMachine vm, Metric metric) { + public void syncBackups(VirtualMachine vm, Backup.Metric metric) { } } 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 ed9aaa4c638f..e6aa5872aa63 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 @@ -370,6 +370,10 @@ public String getDescription() { return "Veeam Backup Plugin"; } + @Override + public void syncBackups(VirtualMachine vm, Backup.Metric metric) { + } + @Override public boolean checkBackupAgent(final Long zoneId) { return true; } From 9d7c9b22bc76463115bcec1a22223519709bb823 Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:47:23 +0900 Subject: [PATCH 12/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backup/CommvaultBackupProvider.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index c5878f575de5..85437c1fa8ae 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -763,7 +763,7 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU } @Override - public boolean takeBackup(VirtualMachine vm) { + public Pair takeBackup(VirtualMachine vm) { String hostName = null; try { String commvaultServer = getUrlDomain(CommvaultUrl.value()); @@ -821,7 +821,7 @@ public boolean takeBackup(VirtualMachine vm) { } } LOG.error("Failed to request createSnapshot Mold-API."); - return false; + return new Pair<>(false, null); } else { JSONObject jsonObject = new JSONObject(createSnapResult); String jobId = jsonObject.get("jobid").toString(); @@ -843,7 +843,7 @@ public boolean takeBackup(VirtualMachine vm) { } } LOG.error("createSnapshot Mold-API async job resulted in failure."); - return false; + return new Pair<>(false, null); } checkResult.put(vol.getId(), snapId); SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findLatestSnapshotForVolume(vol.getId(), DataStoreRole.Primary); @@ -1001,7 +1001,7 @@ public boolean takeBackup(VirtualMachine vm) { command = String.format(RM_COMMAND, storagePath + "/" + vm.getInstanceName()); executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); } - return true; + return new Pair<>(true, backup); } else { // 백업 실패 if (!checkResult.isEmpty()) { @@ -1018,7 +1018,7 @@ public boolean takeBackup(VirtualMachine vm) { executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); } LOG.error("createBackup commvault api resulted in " + jobStatus); - return false; + return new Pair<>(false, null); } } else { // 백업 실패 @@ -1036,7 +1036,7 @@ public boolean takeBackup(VirtualMachine vm) { executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); } LOG.error("createBackup commvault api resulted in " + jobStatus); - return false; + return new Pair<>(false, null); } } else { // 백업 실패 @@ -1054,7 +1054,7 @@ public boolean takeBackup(VirtualMachine vm) { executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); } LOG.error("failed request createBackup commvault api"); - return false; + return new Pair<>(false, null); } } else { // 백업 경로 업데이트 실패 @@ -1072,7 +1072,7 @@ public boolean takeBackup(VirtualMachine vm) { executeDeleteXmlCommand(hostVO, credentials.first(), credentials.second(), sshPort, command); } LOG.error("updateBackupSet commvault api resulted in failure."); - return false; + return new Pair<>(false, null); } } From 674f697eae8253e4c32b0bb739eb15cbbf6c8235 Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:16:16 +0900 Subject: [PATCH 13/34] =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cloudstack/backup/CommvaultBackupProvider.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java index 85437c1fa8ae..802d52d5861f 100644 --- a/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java +++ b/plugins/backup/commvault/src/main/java/org/apache/cloudstack/backup/CommvaultBackupProvider.java @@ -1559,4 +1559,16 @@ public static boolean versionCheck(String csVersionInfo) { } return true; } + + @Override + public List listRestorePoints(VirtualMachine vm) { + return null; + } + + + @Override + public Backup createNewBackupEntryForRestorePoint(Backup.RestorePoint restorePoint, VirtualMachine vm, Backup.Metric metric) { + return null; + } + } \ No newline at end of file From 8fcc034526b84c16fc9fa11d9864e8dae38e2e30 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 09:03:58 +0900 Subject: [PATCH 14/34] Update BackupSchedule.java --- .../main/java/org/apache/cloudstack/backup/BackupSchedule.java | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java index 26adc80db37a..da90da508fff 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java @@ -31,5 +31,4 @@ public interface BackupSchedule extends InternalIdentity { Date getScheduledTimestamp(); Long getAsyncJobId(); int getMaxBackups(); - String getUuid(); } From 78dd41e780f9def5a8900edea65eb41405ce5e76 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 09:06:35 +0900 Subject: [PATCH 15/34] Update BackupManagerImpl.java --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 2 -- 1 file changed, 2 deletions(-) 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 5c1c7f9d955f..c2deb6650ca1 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -33,7 +33,6 @@ import com.cloud.alert.AlertManager; import com.cloud.configuration.Resource; import com.cloud.exception.ResourceAllocationException; -import com.cloud.storage.Snapshot; import com.cloud.storage.VolumeApiService; import com.cloud.user.DomainManager; import com.cloud.user.ResourceLimitService; @@ -74,7 +73,6 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher; import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; From 7a02284ac6009aa80e1d2e8b5e5b389c7ac72551 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 09:57:58 +0900 Subject: [PATCH 16/34] Update VolumeApiServiceImpl.java --- .../src/main/java/com/cloud/storage/VolumeApiServiceImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 6d975e1809d4..c20ca58d9416 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -353,8 +353,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic private BackupDao backupDao; @Inject private StatsCollector statsCollector; - @Inject - HostPodDao podDao; protected Gson _gson; From 46349c21d7d4f6d7ded8a8679db16420aa480bbf Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:01:20 +0900 Subject: [PATCH 17/34] Update BackupManagerTest.java --- .../java/org/apache/cloudstack/backup/BackupManagerTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 66fa2d33cb1e..88892d982d87 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -49,6 +49,7 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; From c90bc9f9dfba80706e16317495d7bb2e5e00bbc9 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:24:03 +0900 Subject: [PATCH 18/34] Update BackupManagerTest.java --- .../cloudstack/backup/BackupManagerTest.java | 1335 ++++++++++++++++- 1 file changed, 1260 insertions(+), 75 deletions(-) diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 88892d982d87..489ac2c8213a 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -16,18 +16,41 @@ // under the License. package org.apache.cloudstack.backup; +import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.alert.AlertManager; +import com.cloud.capacity.CapacityVO; import com.cloud.configuration.Resource; +import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.domain.Domain; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import com.cloud.event.ActionEventUtils; +import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.Network; +import com.cloud.network.NetworkService; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offering.DiskOffering; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; import com.cloud.exception.ResourceAllocationException; +import com.cloud.storage.Storage; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; @@ -35,27 +58,39 @@ import com.cloud.user.DomainManager; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; -import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VmDiskInfo; import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; +import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.junit.After; import org.junit.Assert; @@ -76,6 +111,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.TimeZone; import java.util.UUID; @@ -85,10 +121,14 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.atLeastOnce; @RunWith(MockitoJUnitRunner.class) public class BackupManagerTest { @@ -99,6 +139,9 @@ public class BackupManagerTest { @Mock BackupOfferingDao backupOfferingDao; + @Mock + BackupDetailsDao backupDetailsDao; + @Mock BackupProvider backupProvider; @@ -138,8 +181,6 @@ public class BackupManagerTest { @Mock private Domain domainMock; - private AccountVO account; - @Mock private VMInstanceVO vmInstanceVOMock; @@ -164,7 +205,41 @@ public class BackupManagerTest { @Mock private DeleteBackupScheduleCmd deleteBackupScheduleCmdMock; - private UserVO user; + @Mock + DiskOfferingDao diskOfferingDao; + + @Mock + ServiceOfferingDao serviceOfferingDao; + + @Mock + VMTemplateDao vmTemplateDao; + + @Mock + UserVmJoinDao userVmJoinDao; + + @Mock + PrimaryDataStoreDao primaryDataStoreDao; + + @Mock + HostDao hostDao; + + @Mock + private NetworkDao networkDao; + + @Mock + private NetworkService networkService; + + @Mock + private VMInstanceDetailsDao vmInstanceDetailsDao; + + @Mock + AccountDao accountDao; + + @Mock + DomainDao domainDao; + + @Mock + private GuestOSDao _guestOSDao; private Gson gson; @@ -198,6 +273,12 @@ public void setup() throws Exception { return true; }); + backupProvider = mock(BackupProvider.class); + when(backupProvider.getName()).thenReturn("testbackupprovider"); + Map backupProvidersMap = new HashMap<>(); + backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); + ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap); + Account account = mock(Account.class); User user = mock(User.class); CallContext.register(user, account); @@ -212,6 +293,20 @@ public void tearDown() throws Exception { CallContext.unregister(); } + private void overrideBackupFrameworkConfigValue() { + ConfigKey configKey = BackupManager.BackupFrameworkEnabled; + this.configDepotImpl = (ConfigDepotImpl) ReflectionTestUtils.getField(configKey, "s_depot"); + ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), + Mockito.eq(ConfigKey.Scope.Global), Mockito.isNull())).thenReturn("true"); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), + Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("true"); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupProviderPlugin.key()), + Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("testbackupprovider"); + ReflectionTestUtils.setField(configKey, "s_depot", configDepot); + updatedConfigKeyDepot = true; + } + @Test public void testExceptionWhenUpdateWithNullId() { try { @@ -226,7 +321,7 @@ public void testExceptionWhenUpdateWithNullId() { } } - @Test (expected = InvalidParameterValueException.class) + @Test(expected = InvalidParameterValueException.class) public void testExceptionWhenUpdateWithNonExistentId() { Long id = 123l; @@ -236,7 +331,7 @@ public void testExceptionWhenUpdateWithNonExistentId() { backupManager.updateBackupOffering(cmd); } - @Test (expected = ServerApiException.class) + @Test(expected = ServerApiException.class) public void testExceptionWhenUpdateWithoutChanges() { UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class); when(cmd.getName()).thenReturn(null); @@ -267,96 +362,117 @@ public void testUpdateBackupOfferingSuccess() { @Test public void restoreBackedUpVolumeTestHostIpAndDatastoreUuid() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; Mockito.when(vm.getName()).thenReturn(vmName); Mockito.when(vm.getState()).thenReturn(vmState); - Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); + Pair vmNameAndState = new Pair<>(vmName, vmState); + + Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); + when(volumeInfo.getUuid()).thenReturn(volumeUuid); - Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), - Mockito.eq("127.0.0.1"), Mockito.eq("e9804933-8609-4de3-bccc-6278072a496c"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success")); - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + doReturn(new Pair(Boolean.TRUE, "Success")) + .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), + any(String.class), any(String.class), any(Pair.class)); + + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success", restoreBackedUpVolume.second()); - Mockito.verify(backupProvider, times(1)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyString(), any(Pair.class)); + verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), + any(String.class), any(String.class), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostIpAndDatastoreName() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; Mockito.when(vm.getName()).thenReturn(vmName); Mockito.when(vm.getState()).thenReturn(vmState); - Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); - Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), - Mockito.eq("127.0.0.1"), Mockito.eq("datastore-name"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success2")); - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Pair vmNameAndState = new Pair<>(vmName, vmState); + + Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); + when(volumeInfo.getUuid()).thenReturn(volumeUuid); + + doReturn(new Pair(Boolean.TRUE, "Success2")) + .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), + any(String.class), any(String.class), any(Pair.class)); + + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success2", restoreBackedUpVolume.second()); - Mockito.verify(backupProvider, times(2)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyString(), any(Pair.class)); + verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), + any(String.class), any(String.class), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostNameAndDatastoreUuid() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; Mockito.when(vm.getName()).thenReturn(vmName); Mockito.when(vm.getState()).thenReturn(vmState); - Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); + Pair vmNameAndState = new Pair<>(vmName, vmState); - Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), - Mockito.eq("hostname"), Mockito.eq("e9804933-8609-4de3-bccc-6278072a496c"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success3")); - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); + when(volumeInfo.getUuid()).thenReturn(volumeUuid); + + doReturn(new Pair(Boolean.TRUE, "Success3")) + .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), + any(String.class), any(String.class), any(Pair.class)); + + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success3", restoreBackedUpVolume.second()); - Mockito.verify(backupProvider, times(3)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyString(), any(Pair.class)); + verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), + any(String.class), any(String.class), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostAndDatastoreName() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; Mockito.when(vm.getName()).thenReturn(vmName); Mockito.when(vm.getState()).thenReturn(vmState); - Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); + Pair vmNameAndState = new Pair<>(vmName, vmState); - Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), - Mockito.eq("hostname"), Mockito.eq("datastore-name"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success4")); - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); + when(volumeInfo.getUuid()).thenReturn(volumeUuid); + + doReturn(new Pair(Boolean.TRUE, "Success4")) + .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), + any(String.class), any(String.class), any(Pair.class)); + + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success4", restoreBackedUpVolume.second()); - Mockito.verify(backupProvider, times(4)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyString(), any(Pair.class)); + verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), + any(String.class), any(String.class), any(Pair.class)); } @Test public void tryRestoreVMTestRestoreSucceeded() throws NoTransitionException { - BackupOffering offering = Mockito.mock(BackupOffering.class); - VolumeVO volumeVO = Mockito.mock(VolumeVO.class); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - BackupVO backup = Mockito.mock(BackupVO.class); + BackupOffering offering = mock(BackupOffering.class); + VolumeVO volumeVO = mock(VolumeVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + BackupVO backup = mock(BackupVO.class); try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), @@ -381,10 +497,10 @@ public void tryRestoreVMTestRestoreSucceeded() throws NoTransitionException { @Test public void tryRestoreVMTestRestoreFails() throws NoTransitionException { - BackupOffering offering = Mockito.mock(BackupOffering.class); - VolumeVO volumeVO = Mockito.mock(VolumeVO.class); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - BackupVO backup = Mockito.mock(BackupVO.class); + BackupOffering offering = mock(BackupOffering.class); + VolumeVO volumeVO = mock(VolumeVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + BackupVO backup = mock(BackupVO.class); try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), @@ -413,20 +529,6 @@ public void tryRestoreVMTestRestoreFails() throws NoTransitionException { } } - private void overrideBackupFrameworkConfigValue() { - ConfigKey configKey = BackupManager.BackupFrameworkEnabled; - this.configDepotImpl = (ConfigDepotImpl) ReflectionTestUtils.getField(configKey, "s_depot"); - ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); - Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), - Mockito.eq(ConfigKey.Scope.Global), Mockito.isNull())).thenReturn("true"); - Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), - Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("true"); - Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupProviderPlugin.key()), - Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("testbackupprovider"); - ReflectionTestUtils.setField(configKey, "s_depot", configDepot); - updatedConfigKeyDepot = true; - } - @Test public void testConfigureBackupSchedule() { Long vmId = 1L; @@ -441,6 +543,7 @@ public void testConfigureBackupSchedule() { when(cmd.getIntervalType()).thenReturn(DateUtil.IntervalType.DAILY); when(cmd.getMaxBackups()).thenReturn(8); when(cmd.getSchedule()).thenReturn("00:00:00"); + when(cmd.getQuiesceVM()).thenReturn(null); VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); when(vmInstanceDao.findById(vmId)).thenReturn(vm); @@ -516,7 +619,9 @@ public void createBackupTestCreateScheduledBackup() throws ResourceAllocationExc Long backupOfferingId = 4L; Long accountId = 5L; Long backupId = 6L; + Long oldestBackupId = 7L; Long newBackupSize = 1000000000L; + Long oldBackupSize = 400000000L; when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock); when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId); @@ -526,7 +631,7 @@ public void createBackupTestCreateScheduledBackup() throws ResourceAllocationExc overrideBackupFrameworkConfigValue(); when(backupOfferingDao.findById(backupOfferingId)).thenReturn(backupOfferingVOMock); when(backupOfferingVOMock.isUserDrivenBackupAllowed()).thenReturn(true); - when(backupOfferingVOMock.getProvider()).thenReturn("test"); + when(backupOfferingVOMock.getProvider()).thenReturn("testbackupprovider"); Mockito.doReturn(scheduleId).when(backupManager).getBackupScheduleId(asyncJobVOMock); @@ -536,31 +641,45 @@ public void createBackupTestCreateScheduledBackup() throws ResourceAllocationExc when(backupScheduleDao.findById(scheduleId)).thenReturn(schedule); when(schedule.getMaxBackups()).thenReturn(2); + VolumeVO volume = mock(VolumeVO.class); + when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume)); + when(volume.getState()).thenReturn(Volume.State.Ready); + when(volumeApiService.getVolumePhysicalSize(null, null, null)).thenReturn(newBackupSize); + BackupProvider backupProvider = mock(BackupProvider.class); Backup backup = mock(Backup.class); when(backup.getId()).thenReturn(backupId); when(backup.getSize()).thenReturn(newBackupSize); - when(backupProvider.getName()).thenReturn("test"); - when(backupProvider.takeBackup(vmInstanceVOMock)).thenReturn(new Pair<>(true, backup)); + when(backupProvider.getName()).thenReturn("testbackupprovider"); + when(backupProvider.takeBackup(vmInstanceVOMock, null)).thenReturn(new Pair<>(true, backup)); Map backupProvidersMap = new HashMap<>(); backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap); BackupVO backupVO = mock(BackupVO.class); when(backupVO.getId()).thenReturn(backupId); - BackupVO oldestBackupVO = mock(BackupVO.class);; + BackupVO oldestBackupVO = mock(BackupVO.class); when(backupDao.findById(backupId)).thenReturn(backupVO); List backups = new ArrayList<>(List.of(oldestBackupVO)); when(backupDao.listBySchedule(scheduleId)).thenReturn(backups); + CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); + when(cmd.getVmId()).thenReturn(vmId); + when(cmd.getName()).thenReturn("new-backup1"); + when(cmd.getQuiesceVM()).thenReturn(null); + try (MockedStatic ignored = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString())).thenReturn(1L); - assertTrue(backupManager.createBackup(vmId, asyncJobVOMock)); + assertTrue(backupManager.createBackup(cmd, asyncJobVOMock)); + + Mockito.verify(resourceLimitMgr, times(1)).checkResourceLimit(accountVOMock, Resource.ResourceType.backup); + Mockito.verify(resourceLimitMgr, times(1)).checkResourceLimit(accountVOMock, Resource.ResourceType.backup_storage, newBackupSize); + Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup); Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, newBackupSize); Mockito.verify(backupDao, times(1)).update(backupVO.getId(), backupVO); @@ -585,6 +704,46 @@ public void createBackupTestResourceLimitReached() throws ResourceAllocationExce BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering); when(offering.isUserDrivenBackupAllowed()).thenReturn(true); + when(offering.getProvider()).thenReturn("testbackupprovider"); + + Account account = Mockito.mock(Account.class); + when(accountManager.getAccount(accountId)).thenReturn(account); + Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup)).when(resourceLimitMgr).checkResourceLimit(account, Resource.ResourceType.backup); + + CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); + when(cmd.getVmId()).thenReturn(vmId); + when(cmd.getQuiesceVM()).thenReturn(null); + + String jobParams = "{}"; + when(asyncJobVOMock.getCmdInfo()).thenReturn(jobParams); + when(asyncJobVOMock.getId()).thenReturn(1L); + + backupManager.createBackup(cmd, asyncJobVOMock); + + String msg = "Backup storage space resource limit exceeded for account id : " + accountId + ". Failed to create backup"; + Mockito.verify(alertManagerMock, times(1)).sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Backup resource limit exceeded for account id : " + accountId + + ". Failed to create backups; please use updateResourceLimit to increase the limit"); + } + + @Test (expected = ResourceAllocationException.class) + public void testCreateBackupStorageLimitReached() throws ResourceAllocationException { + Long vmId = 1L; + Long zoneId = 2L; + Long scheduleId = 3L; + Long backupOfferingId = 4L; + Long accountId = 5L; + + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(vm.getDataCenterId()).thenReturn(zoneId); + when(vm.getBackupOfferingId()).thenReturn(backupOfferingId); + when(vm.getAccountId()).thenReturn(accountId); + + overrideBackupFrameworkConfigValue(); + BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering); + when(offering.isUserDrivenBackupAllowed()).thenReturn(true); + when(offering.getProvider()).thenReturn("testbackupprovider"); Mockito.doReturn(scheduleId).when(backupManager).getBackupScheduleId(asyncJobVOMock); @@ -592,7 +751,15 @@ public void createBackupTestResourceLimitReached() throws ResourceAllocationExce when(accountManager.getAccount(accountId)).thenReturn(account); Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup_storage)).when(resourceLimitMgr).checkResourceLimit(account, Resource.ResourceType.backup_storage, 0L); - backupManager.createBackup(vmId, asyncJobVOMock); + CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); + when(cmd.getVmId()).thenReturn(vmId); + when(cmd.getQuiesceVM()).thenReturn(null); + + backupManager.createBackup(cmd, asyncJobVOMock); + + String msg = "Backup storage space resource limit exceeded for account id : " + accountId + ". Failed to create backup"; + Mockito.verify(alertManagerMock, times(1)).sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Backup storage space resource limit exceeded for account id : " + accountId + + ". Failed to create backups; please use updateResourceLimit to increase the limit"); } @Test @@ -605,7 +772,7 @@ public void testBackupSyncTask() { Long backup1Size = 1 * Resource.ResourceType.bytesToGiB; Long backup2Size = 2 * Resource.ResourceType.bytesToGiB; Long newBackupSize = 3 * Resource.ResourceType.bytesToGiB; - Long metricSize = 4 * Resource.ResourceType.bytesToGiB; + Long restorePointSize = 4 * Resource.ResourceType.bytesToGiB; overrideBackupFrameworkConfigValue(); @@ -621,23 +788,23 @@ public void testBackupSyncTask() { VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); when(vm.getId()).thenReturn(vmId); when(vm.getAccountId()).thenReturn(accountId); - when(vmInstanceDao.listByZoneWithBackups(dataCenterId, null)).thenReturn(List.of(vm)); - Backup.Metric metric = new Backup.Metric(metricSize, null); - Map metricMap = new HashMap<>(); - metricMap.put(vm, metric); - when(backupProvider.getBackupMetrics(Mockito.anyLong(), Mockito.anyList())).thenReturn(metricMap); - - Backup.RestorePoint restorePoint1 = new Backup.RestorePoint(restorePoint1ExternalId, DateUtil.now(), "Root"); - Backup.RestorePoint restorePoint2 = new Backup.RestorePoint("12345", DateUtil.now(), "Root"); + List vmIds = List.of(vmId); + when(backupDao.listVmIdsWithBackupsInZone(dataCenterId)).thenReturn(vmIds); + when(vmInstanceDao.listByZoneAndBackupOffering(dataCenterId, null)).thenReturn(List.of(vm)); + + Backup.RestorePoint restorePoint1 = new Backup.RestorePoint(restorePoint1ExternalId, DateUtil.now(), "Full", restorePointSize, 0L); + Backup.RestorePoint restorePoint2 = new Backup.RestorePoint("12345", DateUtil.now(), "Full", restorePointSize, 0L); List restorePoints = new ArrayList<>(List.of(restorePoint1, restorePoint2)); when(backupProvider.listRestorePoints(vm)).thenReturn(restorePoints); BackupVO backupInDb1 = new BackupVO(); backupInDb1.setSize(backup1Size); + backupInDb1.setAccountId(accountId); backupInDb1.setExternalId(restorePoint1ExternalId); BackupVO backupInDb2 = new BackupVO(); backupInDb2.setSize(backup2Size); + backupInDb2.setAccountId(accountId); backupInDb2.setExternalId(null); ReflectionTestUtils.setField(backupInDb2, "id", backup2Id); when(backupDao.findById(backup2Id)).thenReturn(backupInDb2); @@ -646,7 +813,7 @@ public void testBackupSyncTask() { BackupVO newBackupEntry = new BackupVO(); newBackupEntry.setSize(newBackupSize); - when(backupProvider.createNewBackupEntryForRestorePoint(restorePoint2, vm, metric)).thenReturn(newBackupEntry); + when(backupProvider.createNewBackupEntryForRestorePoint(restorePoint2, vm)).thenReturn(newBackupEntry); try (MockedStatic ignored = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(), @@ -660,8 +827,8 @@ public void testBackupSyncTask() { backupSyncTask.runInContext(); verify(resourceLimitMgr, times(1)).decrementResourceCount(accountId, Resource.ResourceType.backup_storage, backup1Size); - verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, metricSize); - Assert.assertEquals(backupInDb1.getSize(), metricSize); + verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, restorePointSize); + Assert.assertEquals(backupInDb1.getSize(), restorePointSize); verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup); verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, newBackupSize); @@ -764,6 +931,687 @@ public void deleteBackupScheduleTestDeleteSpecificScheduleWhenItsIdIsSpecified() assertTrue(success); } + @Test + public void testGetBackupDetailsFromVM() { + Long vmId = 1L; + VirtualMachine vm = mock(VirtualMachine.class); + when(vm.getServiceOfferingId()).thenReturn(1L); + when(vm.getTemplateId()).thenReturn(2L); + when(vm.getId()).thenReturn(vmId); + + ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); + when(serviceOffering.getUuid()).thenReturn("service-offering-uuid"); + when(serviceOfferingDao.findById(1L)).thenReturn(serviceOffering); + VMTemplateVO template = mock(VMTemplateVO.class); + when(template.getUuid()).thenReturn("template-uuid"); + when(vmTemplateDao.findById(2L)).thenReturn(template); + + VMInstanceDetailVO vmInstanceDetail = mock(VMInstanceDetailVO.class); + when(vmInstanceDetail.getName()).thenReturn("mocked-detail-name"); + when(vmInstanceDetail.getValue()).thenReturn("mocked-detail-value"); + List vmDetails = Collections.singletonList(vmInstanceDetail); + when(vmInstanceDetailsDao.listDetails(vmId)).thenReturn(vmDetails); + + UserVmJoinVO userVmJoinVO = mock(UserVmJoinVO.class); + when(userVmJoinVO.getNetworkUuid()).thenReturn("mocked-network-uuid"); + List userVmJoinVOs = Collections.singletonList(userVmJoinVO); + when(userVmJoinDao.searchByIds(vmId)).thenReturn(userVmJoinVOs); + + Map details = backupManager.getBackupDetailsFromVM(vm); + + assertEquals("service-offering-uuid", details.get(ApiConstants.SERVICE_OFFERING_ID)); + assertEquals("[{\"networkid\":\"mocked-network-uuid\"}]", details.get(ApiConstants.NICS)); + assertEquals("{\"mocked-detail-name\":\"mocked-detail-value\"}", details.get(ApiConstants.VM_SETTINGS)); + } + + @Test + public void getDataDiskInfoListFromBackup() { + Long size1 = 5L * 1024 * 1024 * 1024; + Long size2 = 10L * 1024 * 1024 * 1024; + Backup backup = mock(Backup.class); + + Backup.VolumeInfo volumeInfo0 = mock(Backup.VolumeInfo.class); + when(volumeInfo0.getType()).thenReturn(Volume.Type.ROOT); + Backup.VolumeInfo volumeInfo1 = mock(Backup.VolumeInfo.class); + when(volumeInfo1.getDiskOfferingId()).thenReturn("disk-offering-uuid-1"); + when(volumeInfo1.getSize()).thenReturn(size1); + when(volumeInfo1.getMinIops()).thenReturn(100L); + when(volumeInfo1.getMaxIops()).thenReturn(300L); + when(volumeInfo1.getType()).thenReturn(Volume.Type.DATADISK); + when(volumeInfo1.getDeviceId()).thenReturn(1L); + Backup.VolumeInfo volumeInfo2 = mock(Backup.VolumeInfo.class); + when(volumeInfo2.getDiskOfferingId()).thenReturn("disk-offering-uuid-2"); + when(volumeInfo2.getSize()).thenReturn(size2); + when(volumeInfo2.getMinIops()).thenReturn(200L); + when(volumeInfo2.getMaxIops()).thenReturn(400L); + when(volumeInfo2.getType()).thenReturn(Volume.Type.DATADISK); + when(volumeInfo2.getDeviceId()).thenReturn(2L); + when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo0, volumeInfo1, volumeInfo2)); + + DiskOfferingVO diskOffering1 = mock(DiskOfferingVO.class); + when(diskOffering1.getUuid()).thenReturn("disk-offering-uuid-1"); + when(diskOffering1.getState()).thenReturn(DiskOffering.State.Active); + + DiskOfferingVO diskOffering2 = mock(DiskOfferingVO.class); + when(diskOffering2.getUuid()).thenReturn("disk-offering-uuid-2"); + when(diskOffering2.getState()).thenReturn(DiskOffering.State.Active); + + when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering1); + when(diskOfferingDao.findByUuid("disk-offering-uuid-2")).thenReturn(diskOffering2); + + List vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup); + + assertEquals(2, vmDiskInfoList.size()); + assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize()); + assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId()); + assertEquals(Long.valueOf(100), vmDiskInfoList.get(0).getMinIops()); + assertEquals(Long.valueOf(300), vmDiskInfoList.get(0).getMaxIops()); + + assertEquals("disk-offering-uuid-2", vmDiskInfoList.get(1).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(10), vmDiskInfoList.get(1).getSize()); + assertEquals(Long.valueOf(2), vmDiskInfoList.get(1).getDeviceId()); + assertEquals(Long.valueOf(200), vmDiskInfoList.get(1).getMinIops()); + assertEquals(Long.valueOf(400), vmDiskInfoList.get(1).getMaxIops()); + } + + @Test + public void getDataDiskInfoListFromBackupNullIops() { + Long size = 5L * 1024 * 1024 * 1024; + Backup backup = mock(Backup.class); + Backup.VolumeInfo volumeInfo1 = mock(Backup.VolumeInfo.class); + when(volumeInfo1.getDiskOfferingId()).thenReturn("disk-offering-uuid-1"); + when(volumeInfo1.getSize()).thenReturn(size); + when(volumeInfo1.getMinIops()).thenReturn(null); + when(volumeInfo1.getMaxIops()).thenReturn(null); + when(volumeInfo1.getType()).thenReturn(Volume.Type.DATADISK); + when(volumeInfo1.getDeviceId()).thenReturn(1L); + when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo1)); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(diskOffering.getUuid()).thenReturn("disk-offering-uuid-1"); + when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); + + when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); + + List vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup); + + assertEquals(1, vmDiskInfoList.size()); + assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize()); + assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId()); + assertNull(vmDiskInfoList.get(0).getMinIops()); + assertNull(vmDiskInfoList.get(0).getMaxIops()); + } + + @Test (expected = InvalidParameterValueException.class) + public void testCheckVmDisksSizeAgainstBackup() { + Long sizeInBackup = 5L * 1024 * 1024 * 1024; + Long sizeInCmd = 2L; + Backup backup = mock(Backup.class); + Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); + when(volumeInfo.getDiskOfferingId()).thenReturn("disk-offering-uuid-1"); + when(volumeInfo.getSize()).thenReturn(sizeInBackup); + when(volumeInfo.getType()).thenReturn(Volume.Type.DATADISK); + when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo)); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); + when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); + List vmDiskInfoList = List.of(new VmDiskInfo(diskOffering, sizeInCmd, 1L, null, null)); + + backupManager.checkVmDisksSizeAgainstBackup(vmDiskInfoList, backup); + } + + @Test + public void testGetRootDiskInfoFromBackup() { + Long size = 5L * 1024 * 1024 * 1024; + Backup backup = mock(Backup.class); + Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); + when(volumeInfo.getDiskOfferingId()).thenReturn("root-disk-offering-uuid"); + when(volumeInfo.getSize()).thenReturn(size); + when(volumeInfo.getType()).thenReturn(Volume.Type.ROOT); + when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo)); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(diskOffering.getUuid()).thenReturn("root-disk-offering-uuid"); + when(diskOfferingDao.findByUuid("root-disk-offering-uuid")).thenReturn(diskOffering); + + VmDiskInfo VmDiskInfo = backupManager.getRootDiskInfoFromBackup(backup); + + assertEquals("root-disk-offering-uuid", VmDiskInfo.getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), VmDiskInfo.getSize()); + assertEquals(null, VmDiskInfo.getDeviceId()); + } + + @Test + public void testImportBackupOffering() { + ImportBackupOfferingCmd cmd = Mockito.mock(ImportBackupOfferingCmd.class); + when(cmd.getZoneId()).thenReturn(1L); + when(cmd.getExternalId()).thenReturn("external-id"); + when(cmd.getName()).thenReturn("Test Offering"); + when(cmd.getDescription()).thenReturn("Test Description"); + when(cmd.getUserDrivenBackups()).thenReturn(true); + + overrideBackupFrameworkConfigValue(); + + when(backupOfferingDao.findByExternalId("external-id", 1L)).thenReturn(null); + when(backupOfferingDao.findByName("Test Offering", 1L)).thenReturn(null); + + BackupOfferingVO offering = new BackupOfferingVO(1L, "external-id", "testbackupprovider", "Test Offering", "Test Description", true); + when(backupOfferingDao.persist(any(BackupOfferingVO.class))).thenReturn(offering); + when(backupProvider.isValidProviderOffering(cmd.getZoneId(), cmd.getExternalId())).thenReturn(true); + + BackupOffering result = backupManager.importBackupOffering(cmd); + + assertEquals("Test Offering", result.getName()); + assertEquals("Test Description", result.getDescription()); + assertEquals(true, result.isUserDrivenBackupAllowed()); + assertEquals("external-id", result.getExternalId()); + assertEquals("testbackupprovider", result.getProvider()); + } + + @Test + public void testCreateVolumeInfoFromVolumes() { + Long diskOfferingId = 5L; + DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class); + Mockito.when(diskOffering.getUuid()).thenReturn("disk-offering-uuid"); + Mockito.when(diskOfferingDao.findById(diskOfferingId)).thenReturn(diskOffering); + + List volumes = new ArrayList<>(); + VolumeVO volume1 = new VolumeVO(Volume.Type.ROOT, "vol1", 1L, 2L, 3L, + diskOfferingId, null, 1024L, null, null, null); + volume1.setUuid("uuid1"); + volume1.setPath("path1"); + volume1.setDeviceId(0L); + volume1.setVolumeType(Volume.Type.ROOT); + volumes.add(volume1); + + VolumeVO volume2 = new VolumeVO(Volume.Type.ROOT, "vol2", 1L, 2L, 3L, + diskOfferingId, null, 2048L, 1000L, 2000L, null); + volume2.setUuid("uuid2"); + volume2.setPath("path2"); + volume2.setDeviceId(1L); + volume2.setVolumeType(Volume.Type.DATADISK); + volumes.add(volume2); + + String expectedJson = "[{\"uuid\":\"uuid1\",\"type\":\"ROOT\",\"size\":1024,\"path\":\"path1\",\"deviceId\":0,\"diskOfferingId\":\"disk-offering-uuid\"},{\"uuid\":\"uuid2\",\"type\":\"DATADISK\",\"size\":2048,\"path\":\"path2\",\"deviceId\":1,\"diskOfferingId\":\"disk-offering-uuid\",\"minIops\":1000,\"maxIops\":2000}]"; + String actualJson = backupManager.createVolumeInfoFromVolumes(new ArrayList<>(volumes)); + + assertEquals(expectedJson, actualJson); + } + + @Test + public void testAssignVMToBackupOffering() { + Long vmId = 1L; + Long offeringId = 2L; + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + BackupOfferingVO offering = mock(BackupOfferingVO.class); + + overrideBackupFrameworkConfigValue(); + + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(backupOfferingDao.findById(offeringId)).thenReturn(offering); + when(vm.getState()).thenReturn(VirtualMachine.State.Running); + when(vm.getDataCenterId()).thenReturn(1L); + when(vm.getBackupOfferingId()).thenReturn(null); + when(offering.getProvider()).thenReturn("testbackupprovider"); + when(backupProvider.assignVMToBackupOffering(vm, offering)).thenReturn(true); + when(vmInstanceDao.update(1L, vm)).thenReturn(true); + + try (MockedStatic ignored2 = Mockito.mockStatic(UsageEventUtils.class)) { + boolean result = backupManager.assignVMToBackupOffering(vmId, offeringId); + + assertTrue(result); + verify(vmInstanceDao, times(1)).findById(vmId); + verify(backupOfferingDao, times(1)).findById(offeringId); + verify(backupManager, times(1)).getBackupProvider("testbackupprovider"); + } + } + + @Test + public void testRemoveVMFromBackupOffering() { + Long vmId = 1L; + Long accountId = 2L; + Long zoneId = 3L; + Long offeringId = 4L; + Long backupScheduleId = 5L; + String vmHostName = "vm1"; + String vmUuid = "uuid1"; + String resourceName = "Backup-" + vmHostName + "-" + vmUuid; + + boolean forced = true; + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getDataCenterId()).thenReturn(1L); + when(vm.getBackupOfferingId()).thenReturn(offeringId); + when(vm.getAccountId()).thenReturn(accountId); + when(vm.getDataCenterId()).thenReturn(zoneId); + when(vm.getHostName()).thenReturn(vmHostName); + when(vm.getUuid()).thenReturn(vmUuid); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(vmInstanceDao.update(vmId, vm)).thenReturn(true); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + when(backupOfferingDao.findById(vm.getBackupOfferingId())).thenReturn(offering); + when(offering.getProvider()).thenReturn("testbackupprovider"); + when(backupProvider.removeVMFromBackupOffering(vm)).thenReturn(true); + when(backupProvider.willDeleteBackupsOnOfferingRemoval()).thenReturn(true); + when(backupDao.listByVmId(null, vmId)).thenReturn(new ArrayList<>()); + + BackupScheduleVO backupSchedule = new BackupScheduleVO(); + ReflectionTestUtils.setField(backupSchedule, "id", backupScheduleId); + when(backupScheduleDao.listByVM(vmId)).thenReturn(List.of(backupSchedule)); + + overrideBackupFrameworkConfigValue(); + + try (MockedStatic usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) { + boolean result = backupManager.removeVMFromBackupOffering(vmId, forced); + + assertTrue(result); + verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); + verify(backupOfferingDao, times(1)).findById(vm.getBackupOfferingId()); + verify(backupManager, times(1)).getBackupProvider("testbackupprovider"); + verify(backupScheduleDao, times(1)).remove(backupScheduleId); + usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVED_AND_BACKUPS_DELETED, accountId, zoneId, vmId, resourceName, + offeringId, null, null, Backup.class.getSimpleName(), vmUuid)); + } + } + + @Test + public void testDeleteBackupScheduleByVmId() { + Long vmId = 1L; + Long scheduleId = 2L; + DeleteBackupScheduleCmd cmd = new DeleteBackupScheduleCmd(); + ReflectionTestUtils.setField(cmd, "vmId", vmId); + + overrideBackupFrameworkConfigValue(); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + BackupScheduleVO schedule = mock(BackupScheduleVO.class); + when(schedule.getId()).thenReturn(scheduleId); + when(backupScheduleDao.listByVM(vmId)).thenReturn(List.of(schedule)); + when(backupScheduleDao.remove(scheduleId)).thenReturn(true); + + boolean result = backupManager.deleteBackupSchedule(cmd); + assertTrue(result); + } + + @Test + public void testRestoreBackupToVM() throws NoTransitionException { + Long backupId = 1L; + Long vmId = 2L; + Long hostId = 3L; + Long offeringId = 4L; + Long poolId = 5L; + + BackupVO backup = mock(BackupVO.class); + when(backup.getBackupOfferingId()).thenReturn(offeringId); + when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(vm.getId()).thenReturn(vmId); + when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); + when(vm.getHostId()).thenReturn(hostId); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + BackupProvider backupProvider = mock(BackupProvider.class); + when(backupProvider.supportsInstanceFromBackup()).thenReturn(true); + + overrideBackupFrameworkConfigValue(); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering); + when(offering.getProvider()).thenReturn("testbackupprovider"); + when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider); + when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId)).thenReturn(true); + when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringSuccess, hostId)).thenReturn(true); + + VolumeVO rootVolume = mock(VolumeVO.class); + when(rootVolume.getPoolId()).thenReturn(poolId); + HostVO host = mock(HostVO.class); + when(hostDao.findById(hostId)).thenReturn(host); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); + when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); + when(rootVolume.getPoolId()).thenReturn(poolId); + when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); + when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); + when(backupProvider.restoreBackupToVM(vm, backup, null, null)).thenReturn(new Pair<>(true, null)); + + try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { + boolean result = backupManager.restoreBackupToVM(backupId, vmId); + + assertTrue(result); + verify(backupProvider, times(1)).restoreBackupToVM(vm, backup, null, null); + verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId); + verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringSuccess, hostId); + } catch (CloudRuntimeException e) { + fail("Test failed due to exception" + e); + } + } + + @Test + public void testRestoreBackupToVMException() throws NoTransitionException { + Long backupId = 1L; + Long vmId = 2L; + Long hostId = 3L; + Long offeringId = 4L; + Long poolId = 5L; + + BackupVO backup = mock(BackupVO.class); + when(backup.getBackupOfferingId()).thenReturn(offeringId); + when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(vm.getId()).thenReturn(vmId); + when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); + when(vm.getHostId()).thenReturn(hostId); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + BackupProvider backupProvider = mock(BackupProvider.class); + when(backupProvider.supportsInstanceFromBackup()).thenReturn(true); + + overrideBackupFrameworkConfigValue(); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering); + when(offering.getProvider()).thenReturn("testbackupprovider"); + when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider); + when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId)).thenReturn(true); + when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringFailed, hostId)).thenReturn(true); + + VolumeVO rootVolume = mock(VolumeVO.class); + when(rootVolume.getPoolId()).thenReturn(poolId); + HostVO host = mock(HostVO.class); + when(hostDao.findById(hostId)).thenReturn(host); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); + when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); + when(rootVolume.getPoolId()).thenReturn(poolId); + when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); + when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); + when(backupProvider.restoreBackupToVM(vm, backup, null, null)).thenReturn(new Pair<>(false, null)); + + try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { + CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, + () -> backupManager.restoreBackupToVM(backupId, vmId)); + + verify(backupProvider, times(1)).restoreBackupToVM(vm, backup, null, null); + verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId); + verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringFailed, hostId); + } + } + + @Test + public void testGetBackupStorageUsedStats() { + Long zoneId = 1L; + overrideBackupFrameworkConfigValue(); + when(backupManager.getBackupProvider(zoneId)).thenReturn(backupProvider); + when(backupProvider.getBackupStorageStats(zoneId)).thenReturn(new Pair<>(100L, 200L)); + + CapacityVO capacity = backupManager.getBackupStorageUsedStats(zoneId); + + Assert.assertNotNull(capacity); + Assert.assertEquals(Optional.ofNullable(Long.valueOf(100)), Optional.ofNullable(capacity.getUsedCapacity())); + Assert.assertEquals(Optional.ofNullable(Long.valueOf(200)), Optional.ofNullable(capacity.getTotalCapacity())); + Assert.assertEquals(CapacityVO.CAPACITY_TYPE_BACKUP_STORAGE, capacity.getCapacityType()); + } + + @Test + public void testCheckAndRemoveBackupOfferingBeforeExpunge() { + Long vmId = 1L; + Long zoneId = 2L; + Long offeringId = 3L; + String vmUuid = "uuid1"; + String instanceName = "i-2-1-VM"; + String backupExternalId = "backup-external-id"; + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getUuid()).thenReturn(vmUuid); + when(vm.getBackupOfferingId()).thenReturn(offeringId); + when(vm.getInstanceName()).thenReturn(instanceName); + when(vm.getBackupExternalId()).thenReturn(backupExternalId); + when(vm.getDataCenterId()).thenReturn(zoneId); + Backup backup = mock(Backup.class); + when(backupDao.listByVmIdAndOffering(zoneId, vmId, offeringId)).thenReturn(List.of(backup)); + + CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, + () -> backupManager.checkAndRemoveBackupOfferingBeforeExpunge(vm)); + Assert.assertEquals("This Instance [uuid: uuid1, name: i-2-1-VM] has a " + + "Backup Offering [id: 3, external id: backup-external-id] with 1 backups. Please, remove the backup offering " + + "before proceeding to VM exclusion!", exception.getMessage()); + } + + @Test + public void testGetIpToNetworkMapFromBackup() { + Long networkId1 = 1L; + Long networkId2 = 2L; + String networkUuid1 = "network-uuid-1"; + String networkUuid2 = "network-uuid-2"; + String ip1 = "10.1.1.1"; + String ip2 = "10.1.1.2"; + String ipv61 = "2001:db8::1"; + String ipv62 = "2001:db8::2"; + String mac1 = "00:11:22:33:44:55"; + String mac2 = "00:11:22:33:44:56"; + + // Test case 1: Missing network information + Backup backup1 = mock(Backup.class); + List networkIds1 = new ArrayList<>(); + try { + backupManager.getIpToNetworkMapFromBackup(backup1, true, networkIds1); + fail("Expected CloudRuntimeException for missing network information"); + } catch (CloudRuntimeException e) { + assertEquals("Backup doesn't contain network information. Please specify at least one valid network while creating instance", e.getMessage()); + } + + // Test case 2: IP preservation enabled with IP information + Backup backup2 = mock(Backup.class); + String nicsJson = String.format("[{\"networkid\":\"%s\",\"ipaddress\":\"%s\",\"ip6address\":\"%s\",\"macaddress\":\"%s\"}," + + "{\"networkid\":\"%s\",\"ipaddress\":\"%s\",\"ip6address\":\"%s\",\"macaddress\":\"%s\"}]", + networkUuid1, ip1, ipv61, mac1, networkUuid2, ip2, ipv62, mac2); + when(backup2.getDetail(ApiConstants.NICS)).thenReturn(nicsJson); + + NetworkVO network1 = mock(NetworkVO.class); + NetworkVO network2 = mock(NetworkVO.class); + when(networkDao.findByUuid(networkUuid1)).thenReturn(network1); + when(networkDao.findByUuid(networkUuid2)).thenReturn(network2); + when(network1.getId()).thenReturn(networkId1); + when(network2.getId()).thenReturn(networkId2); + + Network.IpAddresses ipAddresses1 = mock(Network.IpAddresses.class); + Network.IpAddresses ipAddresses2 = mock(Network.IpAddresses.class); + when(networkService.getIpAddressesFromIps(ip1, ipv61, mac1)).thenReturn(ipAddresses1); + when(networkService.getIpAddressesFromIps(ip2, ipv62, mac2)).thenReturn(ipAddresses2); + + List networkIds2 = new ArrayList<>(); + Map result2 = backupManager.getIpToNetworkMapFromBackup(backup2, true, networkIds2); + + assertEquals(2, result2.size()); + assertEquals(ipAddresses1, result2.get(networkId1)); + assertEquals(ipAddresses2, result2.get(networkId2)); + assertEquals(2, networkIds2.size()); + assertTrue(networkIds2.contains(networkId1)); + assertTrue(networkIds2.contains(networkId2)); + + // Test case 3: IP preservation enabled but missing IP information + Backup backup3 = mock(Backup.class); + nicsJson = String.format("[{\"networkid\":\"%s\"}]", networkUuid1); + when(backup3.getDetail(ApiConstants.NICS)).thenReturn(nicsJson); + + List networkIds3 = new ArrayList<>(); + Map result3 = backupManager.getIpToNetworkMapFromBackup(backup3, true, networkIds3); + + assertEquals(1, result3.size()); + assertNull(result3.get(networkId1)); + assertEquals(1, networkIds3.size()); + assertTrue(networkIds3.contains(networkId1)); + + // Test case 4: IP preservation disabled + Backup backup4 = mock(Backup.class); + nicsJson = String.format("[{\"networkid\":\"%s\"}]", networkUuid1); + when(backup4.getDetail(ApiConstants.NICS)).thenReturn(nicsJson); + + List networkIds4 = new ArrayList<>(); + Map result4 = backupManager.getIpToNetworkMapFromBackup(backup4, false, networkIds4); + + assertEquals(1, result4.size()); + assertNull(result4.get(networkId1)); + assertEquals(1, networkIds4.size()); + assertTrue(networkIds4.contains(networkId1)); + } + + @Test + public void testDeleteBackupVmNotFound() { + Long backupId = 1L; + Long vmId = 2L; + Long zoneId = 3L; + Long accountId = 4L; + Long backupOfferingId = 5L; + String vmHostName = "vm1"; + String vmUuid = "uuid1"; + String resourceName = "Backup-" + vmHostName + "-" + vmUuid; + + BackupVO backup = mock(BackupVO.class); + when(backup.getId()).thenReturn(backupId); + when(backup.getVmId()).thenReturn(vmId); + when(backup.getZoneId()).thenReturn(zoneId); + when(backup.getAccountId()).thenReturn(accountId); + when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); + when(backup.getSize()).thenReturn(100L); + + overrideBackupFrameworkConfigValue(); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getAccountId()).thenReturn(accountId); + when(vm.getBackupOfferingId()).thenReturn(10L); + when(vm.getDataCenterId()).thenReturn(zoneId); + when(vm.getHostName()).thenReturn(vmHostName); + when(vm.getUuid()).thenReturn(vmUuid); + when(backupDao.findByIdIncludingRemoved(backupId)).thenReturn(backup); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(backupDao.listByVmIdAndOffering(zoneId, vmId, backupOfferingId)).thenReturn(new ArrayList<>()); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); + + when(backupProvider.deleteBackup(backup, false)).thenReturn(true); + + when(backupDao.remove(backupId)).thenReturn(true); + + try (MockedStatic usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) { + boolean result = backupManager.deleteBackup(backupId, false); + + assertTrue(result); + verify(backupProvider).deleteBackup(backup, false); + verify(resourceLimitMgr).decrementResourceCount(accountId, Resource.ResourceType.backup); + verify(resourceLimitMgr).decrementResourceCount(accountId, Resource.ResourceType.backup_storage, backup.getSize()); + verify(backupDao).remove(backupId); + usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVED_AND_BACKUPS_DELETED, accountId, zoneId, vmId, resourceName, + backupOfferingId, null, null, Backup.class.getSimpleName(), vmUuid)); + } + } + + @Test + public void testNewBackupResponse() { + Long vmId = 1L; + Long accountId = 2L; + Long domainId = 3L; + Long zoneId = 4L; + Long vmOfferingId = 5L; + Long backupOfferingId = 6L; + Long backupId = 7L; + Long templateId = 8L; + String templateUuid = "template-uuid1"; + String serviceOfferingUuid = "service-offering-uuid1"; + + BackupVO backup = new BackupVO(); + ReflectionTestUtils.setField(backup, "id", backupId); + ReflectionTestUtils.setField(backup, "uuid", "backup-uuid"); + backup.setVmId(vmId); + backup.setAccountId(accountId); + backup.setDomainId(domainId); + backup.setZoneId(zoneId); + backup.setBackupOfferingId(backupOfferingId); + backup.setType("Full"); + backup.setBackupScheduleId(null); + + VMInstanceVO vm = new VMInstanceVO(vmId, 0L, "test-vm", "test-vm", VirtualMachine.Type.User, + 0L, Hypervisor.HypervisorType.Simulator, 0L, domainId, accountId, 0L, false); + vm.setDataCenterId(zoneId); + vm.setBackupOfferingId(vmOfferingId); + vm.setTemplateId(templateId); + + AccountVO account = new AccountVO(); + account.setUuid("account-uuid"); + account.setAccountName("test-account"); + + DomainVO domain = new DomainVO(); + domain.setUuid("domain-uuid"); + domain.setName("test-domain"); + + DataCenterVO zone = new DataCenterVO(1L, "test-zone", null, null, null, null, null, null, null, null, DataCenter.NetworkType.Advanced, null, null); + zone.setUuid("zone-uuid"); + + BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); + Mockito.when(offering.getUuid()).thenReturn("offering-uuid"); + Mockito.when(offering.getName()).thenReturn("test-offering"); + + Mockito.when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + Mockito.when(accountDao.findByIdIncludingRemoved(accountId)).thenReturn(account); + Mockito.when(domainDao.findByIdIncludingRemoved(domainId)).thenReturn(domain); + Mockito.when(dataCenterDao.findByIdIncludingRemoved(zoneId)).thenReturn(zone); + Mockito.when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); + + VMTemplateVO template = mock(VMTemplateVO.class); + when(template.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); + when(template.getName()).thenReturn("template1"); + when(vmTemplateDao.findByUuid(templateUuid)).thenReturn(template); + Map details = new HashMap<>(); + details.put(ApiConstants.TEMPLATE_ID, templateUuid); + + ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); + when(serviceOffering.getName()).thenReturn("service-offering1"); + when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering); + details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid); + + NetworkVO network = mock(NetworkVO.class); + when(network.getName()).thenReturn("network1"); + when(networkDao.findByUuid("network-uuid1")).thenReturn(network); + details.put(ApiConstants.NICS, "[{\"networkid\":\"network-uuid1\"}]"); + + Mockito.when(backupDetailsDao.listDetailsKeyPairs(backup.getId(), true)).thenReturn(details); + + BackupResponse response = backupManager.createBackupResponse(backup, true); + + Assert.assertEquals("backup-uuid", response.getId()); + Assert.assertEquals("test-vm", response.getVmName()); + Assert.assertEquals("account-uuid", response.getAccountId()); + Assert.assertEquals("test-account", response.getAccount()); + Assert.assertEquals("domain-uuid", response.getDomainId()); + Assert.assertEquals("test-domain", response.getDomain()); + Assert.assertEquals("zone-uuid", response.getZoneId()); + Assert.assertEquals("test-zone", response.getZone()); + Assert.assertEquals("offering-uuid", response.getBackupOfferingId()); + Assert.assertEquals("test-offering", response.getBackupOffering()); + Assert.assertEquals("MANUAL", response.getIntervalType()); + Assert.assertEquals("{serviceofferingid=service-offering-uuid1, isiso=false, hypervisor=Simulator, " + + "nics=[{\"networkid\":\"network-uuid1\",\"networkname\":\"network1\"}], serviceofferingname=service-offering1, " + + "templatename=template1, templateid=template-uuid1}", response.getVmDetails().toString()); + Assert.assertEquals(true, response.getVmOfferingRemoved()); + } + @Test public void validateAndGetDefaultBackupRetentionIfRequiredTestReturnZeroAsDefaultValue() { int retention = backupManager.validateAndGetDefaultBackupRetentionIfRequired(null, backupOfferingVOMock, null); @@ -971,4 +1819,341 @@ public void sendExceededBackupLimitAlertTestSendAlertForBackupStorageResourceTyp expectedAlertDetails ); } -} + + @Test + public void testListBackupSchedulesAsRootAdmin() { + long vmId = 1L; + ListBackupScheduleCmd cmd = Mockito.mock(ListBackupScheduleCmd.class); + Mockito.when(cmd.getVmId()).thenReturn(vmId); + Mockito.when(cmd.getId()).thenReturn(1L); + + // Mock VM for validation + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vmInstanceDao.findById(vmId)).thenReturn(vm); + Mockito.when(vm.getDataCenterId()).thenReturn(1L); + overrideBackupFrameworkConfigValue(); + Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any()); + + BackupScheduleVO schedule1 = Mockito.mock(BackupScheduleVO.class); + BackupScheduleVO schedule2 = Mockito.mock(BackupScheduleVO.class); + List schedules = List.of(schedule1, schedule2); + + SearchBuilder searchBuilder = Mockito.mock(SearchBuilder.class); + SearchCriteria searchCriteria = Mockito.mock(SearchCriteria.class); + BackupScheduleVO entity = Mockito.mock(BackupScheduleVO.class); + + Mockito.when(backupScheduleDao.createSearchBuilder()).thenReturn(searchBuilder); + Mockito.when(searchBuilder.entity()).thenReturn(entity); + Mockito.when(searchBuilder.and(Mockito.anyString(), (Object) any(), Mockito.any())).thenReturn(searchBuilder); + Mockito.when(searchBuilder.create()).thenReturn(searchCriteria); + + Mockito.when(backupScheduleDao.searchAndCount(Mockito.any(), Mockito.any())).thenReturn(new Pair<>(schedules, schedules.size())); + List result = backupManager.listBackupSchedules(cmd); + + assertEquals(2, result.size()); + assertTrue(result.contains(schedule1)); + assertTrue(result.contains(schedule2)); + } + + @Test + public void testListBackupSchedulesAsNonAdmin() { + long vmId = 1L; + ListBackupScheduleCmd cmd = Mockito.mock(ListBackupScheduleCmd.class); + Mockito.when(cmd.getVmId()).thenReturn(vmId); + Mockito.when(cmd.getId()).thenReturn(1L); + + // Mock VM for validation + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vmInstanceDao.findById(vmId)).thenReturn(vm); + Mockito.when(vm.getDataCenterId()).thenReturn(1L); + overrideBackupFrameworkConfigValue(); + Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any()); + + BackupScheduleVO schedule = Mockito.mock(BackupScheduleVO.class); + List schedules = List.of(schedule); + + SearchBuilder searchBuilder = Mockito.mock(SearchBuilder.class); + SearchCriteria searchCriteria = Mockito.mock(SearchCriteria.class); + BackupScheduleVO entity = Mockito.mock(BackupScheduleVO.class); + + Mockito.when(backupScheduleDao.createSearchBuilder()).thenReturn(searchBuilder); + Mockito.when(searchBuilder.create()).thenReturn(searchCriteria); + Mockito.when(searchBuilder.entity()).thenReturn(entity); + Mockito.when(searchBuilder.and(Mockito.anyString(), (Object) any(), Mockito.any())).thenReturn(searchBuilder); + Mockito.lenient().when(backupScheduleDao.search(Mockito.eq(searchCriteria), Mockito.any())).thenReturn(schedules); + + Mockito.doNothing().when(accountManager).buildACLSearchBuilder(Mockito.any(), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any()); + Mockito.doNothing().when(accountManager).buildACLSearchCriteria(Mockito.any(), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any()); + + Mockito.when(backupScheduleDao.searchAndCount(Mockito.any(), Mockito.any())).thenReturn(new Pair<>(schedules, schedules.size())); + List result = backupManager.listBackupSchedules(cmd); + + assertEquals(1, result.size()); + assertTrue(result.contains(schedule)); + } + + @Test + public void testCanCreateInstanceFromBackupAcrossZonesSuccess() { + Long backupId = 1L; + Long backupOfferingId = 2L; + String providerName = "testbackupprovider"; + + BackupVO backup = mock(BackupVO.class); + when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + when(offering.getProvider()).thenReturn(providerName); + + BackupProvider backupProvider = mock(BackupProvider.class); + when(backupProvider.crossZoneInstanceCreationEnabled(offering)).thenReturn(true); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); + when(backupManager.getBackupProvider(providerName)).thenReturn(backupProvider); + + Boolean result = backupManager.canCreateInstanceFromBackupAcrossZones(backupId); + + assertTrue(result); + verify(backupDao, times(1)).findById(backupId); + verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId); + verify(backupManager, times(1)).getBackupProvider(providerName); + verify(backupProvider, times(1)).crossZoneInstanceCreationEnabled(offering); + } + + @Test + public void testCanCreateInstanceFromBackupAcrossZonesFalse() { + Long backupId = 1L; + Long backupOfferingId = 2L; + String providerName = "testbackupprovider"; + + BackupVO backup = mock(BackupVO.class); + when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + when(offering.getProvider()).thenReturn(providerName); + + BackupProvider backupProvider = mock(BackupProvider.class); + when(backupProvider.crossZoneInstanceCreationEnabled(offering)).thenReturn(false); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); + when(backupManager.getBackupProvider(providerName)).thenReturn(backupProvider); + + Boolean result = backupManager.canCreateInstanceFromBackupAcrossZones(backupId); + + assertFalse(result); + verify(backupDao, times(1)).findById(backupId); + verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId); + verify(backupManager, times(1)).getBackupProvider(providerName); + verify(backupProvider, times(1)).crossZoneInstanceCreationEnabled(offering); + } + + @Test + public void testCanCreateInstanceFromBackupAcrossZonesOfferingNotFound() { + Long backupId = 1L; + Long backupOfferingId = 2L; + + BackupVO backup = mock(BackupVO.class); + when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(null); + + CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, + () -> backupManager.canCreateInstanceFromBackupAcrossZones(backupId)); + + assertEquals("Failed to find backup offering", exception.getMessage()); + verify(backupDao, times(1)).findById(backupId); + verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId); + verify(backupManager, never()).getBackupProvider(any(String.class)); + } + + @Test + public void testRestoreBackupSuccess() throws NoTransitionException { + Long backupId = 1L; + Long vmId = 2L; + Long zoneId = 3L; + Long accountId = 4L; + Long domainId = 5L; + Long userId = 6L; + Long offeringId = 7L; + String vmInstanceName = "test-vm"; + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + + BackupVO backup = mock(BackupVO.class); + when(backup.getVmId()).thenReturn(vmId); + when(backup.getZoneId()).thenReturn(zoneId); + when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); + when(backup.getBackupOfferingId()).thenReturn(offeringId); + Backup.VolumeInfo volumeInfo = new Backup.VolumeInfo("uuid", "path", Volume.Type.ROOT, 1024L, 0L, "disk-offering-uuid", 1000L, 2000L); + when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo)); + when(backup.getUuid()).thenReturn("backup-uuid"); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getDataCenterId()).thenReturn(zoneId); + when(vm.getDomainId()).thenReturn(domainId); + when(vm.getAccountId()).thenReturn(accountId); + when(vm.getUserId()).thenReturn(userId); + when(vm.getInstanceName()).thenReturn(vmInstanceName); + when(vm.getHypervisorType()).thenReturn(hypervisorType); + when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); + when(vm.getRemoved()).thenReturn(null); + when(vm.getBackupOfferingId()).thenReturn(offeringId); + + BackupOfferingVO offering = mock(BackupOfferingVO.class); + when(offering.getProvider()).thenReturn("testbackupprovider"); + + VolumeVO volume = mock(VolumeVO.class); + when(volumeDao.findByInstance(vmId)).thenReturn(Collections.singletonList(volume)); + + BackupProvider backupProvider = mock(BackupProvider.class); + when(backupProvider.restoreVMFromBackup(vm, backup)).thenReturn(true); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering); + when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider); + doReturn(true).when(backupManager).importRestoredVM(zoneId, domainId, accountId, userId, vmInstanceName, hypervisorType, backup); + doNothing().when(backupManager).validateBackupForZone(any()); + when(virtualMachineManager.stateTransitTo(any(), any(), any())).thenReturn(true); + + try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { + Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), + Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), + Mockito.eq(true), Mockito.eq(0))).thenReturn(1L); + + boolean result = backupManager.restoreBackup(backupId); + + assertTrue(result); + verify(backupDao, times(1)).findById(backupId); + verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); + verify(backupOfferingDao, times(2)).findByIdIncludingRemoved(offeringId); + verify(backupProvider, times(1)).restoreVMFromBackup(vm, backup); + verify(backupManager, times(1)).importRestoredVM(zoneId, domainId, accountId, userId, vmInstanceName, hypervisorType, backup); + } + } + + @Test + public void testRestoreBackupBackupNotFound() { + Long backupId = 1L; + + when(backupDao.findById(backupId)).thenReturn(null); + + CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, + () -> backupManager.restoreBackup(backupId)); + + assertEquals("Backup " + backupId + " does not exist", exception.getMessage()); + verify(backupDao, times(1)).findById(backupId); + verify(vmInstanceDao, never()).findByIdIncludingRemoved(any()); + } + + @Test + public void testRestoreBackupBackupNotBackedUp() { + Long backupId = 1L; + + BackupVO backup = mock(BackupVO.class); + when(backup.getStatus()).thenReturn(Backup.Status.BackingUp); + + when(backupDao.findById(backupId)).thenReturn(backup); + + CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, + () -> backupManager.restoreBackup(backupId)); + + assertEquals("Backup should be in BackedUp state", exception.getMessage()); + verify(backupDao, times(1)).findById(backupId); + verify(vmInstanceDao, never()).findByIdIncludingRemoved(any()); + } + + @Test + public void testRestoreBackupVmExpunging() { + Long backupId = 1L; + Long vmId = 2L; + Long zoneId = 3L; + + BackupVO backup = mock(BackupVO.class); + when(backup.getVmId()).thenReturn(vmId); + when(backup.getZoneId()).thenReturn(zoneId); + when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getState()).thenReturn(VirtualMachine.State.Expunging); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + doNothing().when(backupManager).validateBackupForZone(any()); + + CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, + () -> backupManager.restoreBackup(backupId)); + + assertEquals("The Instance from which the backup was taken could not be found.", exception.getMessage()); + verify(backupDao, times(1)).findById(backupId); + verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); + } + + @Test + public void testRestoreBackupVmNotStopped() { + Long backupId = 1L; + Long vmId = 2L; + Long zoneId = 3L; + + BackupVO backup = mock(BackupVO.class); + when(backup.getVmId()).thenReturn(vmId); + when(backup.getZoneId()).thenReturn(zoneId); + when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getState()).thenReturn(VirtualMachine.State.Running); + when(vm.getRemoved()).thenReturn(null); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + doNothing().when(backupManager).validateBackupForZone(any()); + + CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, + () -> backupManager.restoreBackup(backupId)); + + assertEquals("Existing VM should be stopped before being restored from backup", exception.getMessage()); + verify(backupDao, times(1)).findById(backupId); + verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); + } + + @Test + public void testRestoreBackupVolumeMismatch() { + Long backupId = 1L; + Long vmId = 2L; + Long zoneId = 3L; + + BackupVO backup = mock(BackupVO.class); + when(backup.getVmId()).thenReturn(vmId); + when(backup.getZoneId()).thenReturn(zoneId); + when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); + when(backup.getBackedUpVolumes()).thenReturn(Collections.emptyList()); + + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getState()).thenReturn(VirtualMachine.State.Destroyed); + when(vm.getRemoved()).thenReturn(null); + when(vm.getBackupVolumeList()).thenReturn(Collections.emptyList()); + + VolumeVO volume = mock(VolumeVO.class); + when(volumeDao.findByInstance(vmId)).thenReturn(Collections.singletonList(volume)); + + when(backupDao.findById(backupId)).thenReturn(backup); + when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); + doNothing().when(backupManager).validateBackupForZone(any()); + + try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { + Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), + Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), + Mockito.eq(true), Mockito.eq(0))).thenReturn(1L); + CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, + () -> backupManager.restoreBackup(backupId)); + + assertEquals("Unable to restore VM with the current backup as the backup has different number of disks as the VM", exception.getMessage()); + } + verify(backupDao, times(1)).findById(backupId); + verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); + verify(volumeDao, times(1)).findByInstance(vmId); + } +} \ No newline at end of file From 4b86daa8b89009080d48e5a4a8811efcd473e301 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:29:04 +0900 Subject: [PATCH 19/34] Update BackupManagerTest.java --- .../cloudstack/backup/BackupManagerTest.java | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 489ac2c8213a..7f3bc99548fa 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -70,7 +70,6 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmDiskInfo; import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; import org.apache.cloudstack.api.ApiConstants; @@ -83,7 +82,6 @@ import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.dao.BackupDao; -import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; @@ -139,9 +137,6 @@ public class BackupManagerTest { @Mock BackupOfferingDao backupOfferingDao; - @Mock - BackupDetailsDao backupDetailsDao; - @Mock BackupProvider backupProvider; @@ -229,9 +224,6 @@ public class BackupManagerTest { @Mock private NetworkService networkService; - @Mock - private VMInstanceDetailsDao vmInstanceDetailsDao; - @Mock AccountDao accountDao; @@ -949,8 +941,6 @@ public void testGetBackupDetailsFromVM() { VMInstanceDetailVO vmInstanceDetail = mock(VMInstanceDetailVO.class); when(vmInstanceDetail.getName()).thenReturn("mocked-detail-name"); when(vmInstanceDetail.getValue()).thenReturn("mocked-detail-value"); - List vmDetails = Collections.singletonList(vmInstanceDetail); - when(vmInstanceDetailsDao.listDetails(vmId)).thenReturn(vmDetails); UserVmJoinVO userVmJoinVO = mock(UserVmJoinVO.class); when(userVmJoinVO.getNetworkUuid()).thenReturn("mocked-network-uuid"); @@ -998,21 +988,6 @@ public void getDataDiskInfoListFromBackup() { when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering1); when(diskOfferingDao.findByUuid("disk-offering-uuid-2")).thenReturn(diskOffering2); - - List vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup); - - assertEquals(2, vmDiskInfoList.size()); - assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid()); - assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize()); - assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId()); - assertEquals(Long.valueOf(100), vmDiskInfoList.get(0).getMinIops()); - assertEquals(Long.valueOf(300), vmDiskInfoList.get(0).getMaxIops()); - - assertEquals("disk-offering-uuid-2", vmDiskInfoList.get(1).getDiskOffering().getUuid()); - assertEquals(Long.valueOf(10), vmDiskInfoList.get(1).getSize()); - assertEquals(Long.valueOf(2), vmDiskInfoList.get(1).getDeviceId()); - assertEquals(Long.valueOf(200), vmDiskInfoList.get(1).getMinIops()); - assertEquals(Long.valueOf(400), vmDiskInfoList.get(1).getMaxIops()); } @Test @@ -1033,34 +1008,6 @@ public void getDataDiskInfoListFromBackupNullIops() { when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); - - List vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup); - - assertEquals(1, vmDiskInfoList.size()); - assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid()); - assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize()); - assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId()); - assertNull(vmDiskInfoList.get(0).getMinIops()); - assertNull(vmDiskInfoList.get(0).getMaxIops()); - } - - @Test (expected = InvalidParameterValueException.class) - public void testCheckVmDisksSizeAgainstBackup() { - Long sizeInBackup = 5L * 1024 * 1024 * 1024; - Long sizeInCmd = 2L; - Backup backup = mock(Backup.class); - Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); - when(volumeInfo.getDiskOfferingId()).thenReturn("disk-offering-uuid-1"); - when(volumeInfo.getSize()).thenReturn(sizeInBackup); - when(volumeInfo.getType()).thenReturn(Volume.Type.DATADISK); - when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo)); - - DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); - when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); - when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); - List vmDiskInfoList = List.of(new VmDiskInfo(diskOffering, sizeInCmd, 1L, null, null)); - - backupManager.checkVmDisksSizeAgainstBackup(vmDiskInfoList, backup); } @Test @@ -1586,13 +1533,6 @@ public void testNewBackupResponse() { when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering); details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid); - NetworkVO network = mock(NetworkVO.class); - when(network.getName()).thenReturn("network1"); - when(networkDao.findByUuid("network-uuid1")).thenReturn(network); - details.put(ApiConstants.NICS, "[{\"networkid\":\"network-uuid1\"}]"); - - Mockito.when(backupDetailsDao.listDetailsKeyPairs(backup.getId(), true)).thenReturn(details); - BackupResponse response = backupManager.createBackupResponse(backup, true); Assert.assertEquals("backup-uuid", response.getId()); From ad2cbc7ab7c605ececbe7315290ca41b2d8902df Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:31:48 +0900 Subject: [PATCH 20/34] Update BackupManagerTest.java --- .../apache/cloudstack/backup/BackupManagerTest.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 7f3bc99548fa..6c226e75dd56 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -65,10 +65,8 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; -import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VmDiskInfo; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; @@ -938,10 +936,6 @@ public void testGetBackupDetailsFromVM() { when(template.getUuid()).thenReturn("template-uuid"); when(vmTemplateDao.findById(2L)).thenReturn(template); - VMInstanceDetailVO vmInstanceDetail = mock(VMInstanceDetailVO.class); - when(vmInstanceDetail.getName()).thenReturn("mocked-detail-name"); - when(vmInstanceDetail.getValue()).thenReturn("mocked-detail-value"); - UserVmJoinVO userVmJoinVO = mock(UserVmJoinVO.class); when(userVmJoinVO.getNetworkUuid()).thenReturn("mocked-network-uuid"); List userVmJoinVOs = Collections.singletonList(userVmJoinVO); @@ -1023,12 +1017,6 @@ public void testGetRootDiskInfoFromBackup() { DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); when(diskOffering.getUuid()).thenReturn("root-disk-offering-uuid"); when(diskOfferingDao.findByUuid("root-disk-offering-uuid")).thenReturn(diskOffering); - - VmDiskInfo VmDiskInfo = backupManager.getRootDiskInfoFromBackup(backup); - - assertEquals("root-disk-offering-uuid", VmDiskInfo.getDiskOffering().getUuid()); - assertEquals(Long.valueOf(5), VmDiskInfo.getSize()); - assertEquals(null, VmDiskInfo.getDeviceId()); } @Test From 1642b48eea7cbbfdbdf1ad7f813fd0157256a0a4 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:34:24 +0900 Subject: [PATCH 21/34] Revert "Update BackupManagerTest.java" This reverts commit ad2cbc7ab7c605ececbe7315290ca41b2d8902df. --- .../apache/cloudstack/backup/BackupManagerTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 6c226e75dd56..7f3bc99548fa 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -65,8 +65,10 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VmDiskInfo; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; @@ -936,6 +938,10 @@ public void testGetBackupDetailsFromVM() { when(template.getUuid()).thenReturn("template-uuid"); when(vmTemplateDao.findById(2L)).thenReturn(template); + VMInstanceDetailVO vmInstanceDetail = mock(VMInstanceDetailVO.class); + when(vmInstanceDetail.getName()).thenReturn("mocked-detail-name"); + when(vmInstanceDetail.getValue()).thenReturn("mocked-detail-value"); + UserVmJoinVO userVmJoinVO = mock(UserVmJoinVO.class); when(userVmJoinVO.getNetworkUuid()).thenReturn("mocked-network-uuid"); List userVmJoinVOs = Collections.singletonList(userVmJoinVO); @@ -1017,6 +1023,12 @@ public void testGetRootDiskInfoFromBackup() { DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); when(diskOffering.getUuid()).thenReturn("root-disk-offering-uuid"); when(diskOfferingDao.findByUuid("root-disk-offering-uuid")).thenReturn(diskOffering); + + VmDiskInfo VmDiskInfo = backupManager.getRootDiskInfoFromBackup(backup); + + assertEquals("root-disk-offering-uuid", VmDiskInfo.getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), VmDiskInfo.getSize()); + assertEquals(null, VmDiskInfo.getDeviceId()); } @Test From 62e972b64771a6d0edbb64775c7814f399dac7ca Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:34:31 +0900 Subject: [PATCH 22/34] Revert "Update BackupManagerTest.java" This reverts commit 4b86daa8b89009080d48e5a4a8811efcd473e301. --- .../cloudstack/backup/BackupManagerTest.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 7f3bc99548fa..489ac2c8213a 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -70,6 +70,7 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmDiskInfo; import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; import org.apache.cloudstack.api.ApiConstants; @@ -82,6 +83,7 @@ import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; @@ -137,6 +139,9 @@ public class BackupManagerTest { @Mock BackupOfferingDao backupOfferingDao; + @Mock + BackupDetailsDao backupDetailsDao; + @Mock BackupProvider backupProvider; @@ -224,6 +229,9 @@ public class BackupManagerTest { @Mock private NetworkService networkService; + @Mock + private VMInstanceDetailsDao vmInstanceDetailsDao; + @Mock AccountDao accountDao; @@ -941,6 +949,8 @@ public void testGetBackupDetailsFromVM() { VMInstanceDetailVO vmInstanceDetail = mock(VMInstanceDetailVO.class); when(vmInstanceDetail.getName()).thenReturn("mocked-detail-name"); when(vmInstanceDetail.getValue()).thenReturn("mocked-detail-value"); + List vmDetails = Collections.singletonList(vmInstanceDetail); + when(vmInstanceDetailsDao.listDetails(vmId)).thenReturn(vmDetails); UserVmJoinVO userVmJoinVO = mock(UserVmJoinVO.class); when(userVmJoinVO.getNetworkUuid()).thenReturn("mocked-network-uuid"); @@ -988,6 +998,21 @@ public void getDataDiskInfoListFromBackup() { when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering1); when(diskOfferingDao.findByUuid("disk-offering-uuid-2")).thenReturn(diskOffering2); + + List vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup); + + assertEquals(2, vmDiskInfoList.size()); + assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize()); + assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId()); + assertEquals(Long.valueOf(100), vmDiskInfoList.get(0).getMinIops()); + assertEquals(Long.valueOf(300), vmDiskInfoList.get(0).getMaxIops()); + + assertEquals("disk-offering-uuid-2", vmDiskInfoList.get(1).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(10), vmDiskInfoList.get(1).getSize()); + assertEquals(Long.valueOf(2), vmDiskInfoList.get(1).getDeviceId()); + assertEquals(Long.valueOf(200), vmDiskInfoList.get(1).getMinIops()); + assertEquals(Long.valueOf(400), vmDiskInfoList.get(1).getMaxIops()); } @Test @@ -1008,6 +1033,34 @@ public void getDataDiskInfoListFromBackupNullIops() { when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); + + List vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup); + + assertEquals(1, vmDiskInfoList.size()); + assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid()); + assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize()); + assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId()); + assertNull(vmDiskInfoList.get(0).getMinIops()); + assertNull(vmDiskInfoList.get(0).getMaxIops()); + } + + @Test (expected = InvalidParameterValueException.class) + public void testCheckVmDisksSizeAgainstBackup() { + Long sizeInBackup = 5L * 1024 * 1024 * 1024; + Long sizeInCmd = 2L; + Backup backup = mock(Backup.class); + Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); + when(volumeInfo.getDiskOfferingId()).thenReturn("disk-offering-uuid-1"); + when(volumeInfo.getSize()).thenReturn(sizeInBackup); + when(volumeInfo.getType()).thenReturn(Volume.Type.DATADISK); + when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo)); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); + when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); + List vmDiskInfoList = List.of(new VmDiskInfo(diskOffering, sizeInCmd, 1L, null, null)); + + backupManager.checkVmDisksSizeAgainstBackup(vmDiskInfoList, backup); } @Test @@ -1533,6 +1586,13 @@ public void testNewBackupResponse() { when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering); details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid); + NetworkVO network = mock(NetworkVO.class); + when(network.getName()).thenReturn("network1"); + when(networkDao.findByUuid("network-uuid1")).thenReturn(network); + details.put(ApiConstants.NICS, "[{\"networkid\":\"network-uuid1\"}]"); + + Mockito.when(backupDetailsDao.listDetailsKeyPairs(backup.getId(), true)).thenReturn(details); + BackupResponse response = backupManager.createBackupResponse(backup, true); Assert.assertEquals("backup-uuid", response.getId()); From b27cb6d28ab93840da6544de5314475e4d5523a8 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:34:36 +0900 Subject: [PATCH 23/34] Revert "Update BackupManagerTest.java" This reverts commit c90bc9f9dfba80706e16317495d7bb2e5e00bbc9. --- .../cloudstack/backup/BackupManagerTest.java | 1335 +---------------- 1 file changed, 75 insertions(+), 1260 deletions(-) diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 489ac2c8213a..88892d982d87 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -16,41 +16,18 @@ // under the License. package org.apache.cloudstack.backup; -import com.cloud.api.query.dao.UserVmJoinDao; -import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.alert.AlertManager; -import com.cloud.capacity.CapacityVO; import com.cloud.configuration.Resource; -import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.domain.Domain; -import com.cloud.domain.DomainVO; -import com.cloud.domain.dao.DomainDao; import com.cloud.event.ActionEventUtils; -import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InvalidParameterValueException; -import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; -import com.cloud.hypervisor.Hypervisor; -import com.cloud.network.Network; -import com.cloud.network.NetworkService; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.offering.DiskOffering; -import com.cloud.service.ServiceOfferingVO; -import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.storage.DiskOfferingVO; import com.cloud.exception.ResourceAllocationException; -import com.cloud.storage.Storage; -import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.DiskOfferingDao; -import com.cloud.storage.dao.GuestOSDao; -import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; @@ -58,39 +35,27 @@ import com.cloud.user.DomainManager; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; -import com.cloud.user.dao.AccountDao; +import com.cloud.user.UserVO; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; -import com.cloud.utils.db.SearchBuilder; -import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; -import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VmDiskInfo; import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; -import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; -import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.dao.BackupDao; -import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.junit.After; import org.junit.Assert; @@ -111,7 +76,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.TimeZone; import java.util.UUID; @@ -121,14 +85,10 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.atLeastOnce; @RunWith(MockitoJUnitRunner.class) public class BackupManagerTest { @@ -139,9 +99,6 @@ public class BackupManagerTest { @Mock BackupOfferingDao backupOfferingDao; - @Mock - BackupDetailsDao backupDetailsDao; - @Mock BackupProvider backupProvider; @@ -181,6 +138,8 @@ public class BackupManagerTest { @Mock private Domain domainMock; + private AccountVO account; + @Mock private VMInstanceVO vmInstanceVOMock; @@ -205,41 +164,7 @@ public class BackupManagerTest { @Mock private DeleteBackupScheduleCmd deleteBackupScheduleCmdMock; - @Mock - DiskOfferingDao diskOfferingDao; - - @Mock - ServiceOfferingDao serviceOfferingDao; - - @Mock - VMTemplateDao vmTemplateDao; - - @Mock - UserVmJoinDao userVmJoinDao; - - @Mock - PrimaryDataStoreDao primaryDataStoreDao; - - @Mock - HostDao hostDao; - - @Mock - private NetworkDao networkDao; - - @Mock - private NetworkService networkService; - - @Mock - private VMInstanceDetailsDao vmInstanceDetailsDao; - - @Mock - AccountDao accountDao; - - @Mock - DomainDao domainDao; - - @Mock - private GuestOSDao _guestOSDao; + private UserVO user; private Gson gson; @@ -273,12 +198,6 @@ public void setup() throws Exception { return true; }); - backupProvider = mock(BackupProvider.class); - when(backupProvider.getName()).thenReturn("testbackupprovider"); - Map backupProvidersMap = new HashMap<>(); - backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); - ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap); - Account account = mock(Account.class); User user = mock(User.class); CallContext.register(user, account); @@ -293,20 +212,6 @@ public void tearDown() throws Exception { CallContext.unregister(); } - private void overrideBackupFrameworkConfigValue() { - ConfigKey configKey = BackupManager.BackupFrameworkEnabled; - this.configDepotImpl = (ConfigDepotImpl) ReflectionTestUtils.getField(configKey, "s_depot"); - ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); - Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), - Mockito.eq(ConfigKey.Scope.Global), Mockito.isNull())).thenReturn("true"); - Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), - Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("true"); - Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupProviderPlugin.key()), - Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("testbackupprovider"); - ReflectionTestUtils.setField(configKey, "s_depot", configDepot); - updatedConfigKeyDepot = true; - } - @Test public void testExceptionWhenUpdateWithNullId() { try { @@ -321,7 +226,7 @@ public void testExceptionWhenUpdateWithNullId() { } } - @Test(expected = InvalidParameterValueException.class) + @Test (expected = InvalidParameterValueException.class) public void testExceptionWhenUpdateWithNonExistentId() { Long id = 123l; @@ -331,7 +236,7 @@ public void testExceptionWhenUpdateWithNonExistentId() { backupManager.updateBackupOffering(cmd); } - @Test(expected = ServerApiException.class) + @Test (expected = ServerApiException.class) public void testExceptionWhenUpdateWithoutChanges() { UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class); when(cmd.getName()).thenReturn(null); @@ -362,117 +267,96 @@ public void testUpdateBackupOfferingSuccess() { @Test public void restoreBackedUpVolumeTestHostIpAndDatastoreUuid() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = mock(VMInstanceVO.class); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; Mockito.when(vm.getName()).thenReturn(vmName); Mockito.when(vm.getState()).thenReturn(vmState); - Pair vmNameAndState = new Pair<>(vmName, vmState); - - Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); - when(volumeInfo.getUuid()).thenReturn(volumeUuid); + Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); - doReturn(new Pair(Boolean.TRUE, "Success")) - .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), - any(String.class), any(String.class), any(Pair.class)); - - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), + Mockito.eq("127.0.0.1"), Mockito.eq("e9804933-8609-4de3-bccc-6278072a496c"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success")); + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success", restoreBackedUpVolume.second()); - verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), - any(String.class), any(String.class), any(Pair.class)); + Mockito.verify(backupProvider, times(1)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), + Mockito.anyString(), Mockito.anyString(), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostIpAndDatastoreName() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = mock(VMInstanceVO.class); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; Mockito.when(vm.getName()).thenReturn(vmName); Mockito.when(vm.getState()).thenReturn(vmState); - Pair vmNameAndState = new Pair<>(vmName, vmState); - - Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); - when(volumeInfo.getUuid()).thenReturn(volumeUuid); - - doReturn(new Pair(Boolean.TRUE, "Success2")) - .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), - any(String.class), any(String.class), any(Pair.class)); - - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); + Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), + Mockito.eq("127.0.0.1"), Mockito.eq("datastore-name"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success2")); + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success2", restoreBackedUpVolume.second()); - verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), - any(String.class), any(String.class), any(Pair.class)); + Mockito.verify(backupProvider, times(2)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), + Mockito.anyString(), Mockito.anyString(), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostNameAndDatastoreUuid() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = mock(VMInstanceVO.class); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; Mockito.when(vm.getName()).thenReturn(vmName); Mockito.when(vm.getState()).thenReturn(vmState); - Pair vmNameAndState = new Pair<>(vmName, vmState); + Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); - Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); - when(volumeInfo.getUuid()).thenReturn(volumeUuid); - - doReturn(new Pair(Boolean.TRUE, "Success3")) - .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), - any(String.class), any(String.class), any(Pair.class)); - - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), + Mockito.eq("hostname"), Mockito.eq("e9804933-8609-4de3-bccc-6278072a496c"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success3")); + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success3", restoreBackedUpVolume.second()); - verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), - any(String.class), any(String.class), any(Pair.class)); + Mockito.verify(backupProvider, times(3)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), + Mockito.anyString(), Mockito.anyString(), any(Pair.class)); } @Test public void restoreBackedUpVolumeTestHostAndDatastoreName() { BackupVO backupVO = new BackupVO(); - VMInstanceVO vm = mock(VMInstanceVO.class); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); String volumeUuid = "5f4ed903-ac23-4f8a-b595-69c73c40593f"; String vmName = "i-2-3-VM"; VirtualMachine.State vmState = VirtualMachine.State.Running; Mockito.when(vm.getName()).thenReturn(vmName); Mockito.when(vm.getState()).thenReturn(vmState); - Pair vmNameAndState = new Pair<>(vmName, vmState); + Pair vmNameAndState = new Pair<>("i-2-3-VM", VirtualMachine.State.Running); - Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); - when(volumeInfo.getUuid()).thenReturn(volumeUuid); - - doReturn(new Pair(Boolean.TRUE, "Success4")) - .when(backupProvider).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), - any(String.class), any(String.class), any(Pair.class)); - - Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeInfo, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); + Mockito.when(backupProvider.restoreBackedUpVolume(Mockito.any(), Mockito.eq(volumeUuid), + Mockito.eq("hostname"), Mockito.eq("datastore-name"), Mockito.eq(vmNameAndState))).thenReturn(new Pair(Boolean.TRUE, "Success4")); + Pair restoreBackedUpVolume = backupManager.restoreBackedUpVolume(volumeUuid, backupVO, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); assertEquals(Boolean.TRUE, restoreBackedUpVolume.first()); assertEquals("Success4", restoreBackedUpVolume.second()); - verify(backupProvider, atLeastOnce()).restoreBackedUpVolume(any(Backup.class), any(Backup.VolumeInfo.class), - any(String.class), any(String.class), any(Pair.class)); + Mockito.verify(backupProvider, times(4)).restoreBackedUpVolume(Mockito.any(), Mockito.anyString(), + Mockito.anyString(), Mockito.anyString(), any(Pair.class)); } @Test public void tryRestoreVMTestRestoreSucceeded() throws NoTransitionException { - BackupOffering offering = mock(BackupOffering.class); - VolumeVO volumeVO = mock(VolumeVO.class); - VMInstanceVO vm = mock(VMInstanceVO.class); - BackupVO backup = mock(BackupVO.class); + BackupOffering offering = Mockito.mock(BackupOffering.class); + VolumeVO volumeVO = Mockito.mock(VolumeVO.class); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + BackupVO backup = Mockito.mock(BackupVO.class); try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), @@ -497,10 +381,10 @@ public void tryRestoreVMTestRestoreSucceeded() throws NoTransitionException { @Test public void tryRestoreVMTestRestoreFails() throws NoTransitionException { - BackupOffering offering = mock(BackupOffering.class); - VolumeVO volumeVO = mock(VolumeVO.class); - VMInstanceVO vm = mock(VMInstanceVO.class); - BackupVO backup = mock(BackupVO.class); + BackupOffering offering = Mockito.mock(BackupOffering.class); + VolumeVO volumeVO = Mockito.mock(VolumeVO.class); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + BackupVO backup = Mockito.mock(BackupVO.class); try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), @@ -529,6 +413,20 @@ public void tryRestoreVMTestRestoreFails() throws NoTransitionException { } } + private void overrideBackupFrameworkConfigValue() { + ConfigKey configKey = BackupManager.BackupFrameworkEnabled; + this.configDepotImpl = (ConfigDepotImpl) ReflectionTestUtils.getField(configKey, "s_depot"); + ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), + Mockito.eq(ConfigKey.Scope.Global), Mockito.isNull())).thenReturn("true"); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupFrameworkEnabled.key()), + Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("true"); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(BackupManager.BackupProviderPlugin.key()), + Mockito.eq(ConfigKey.Scope.Zone), Mockito.anyLong())).thenReturn("testbackupprovider"); + ReflectionTestUtils.setField(configKey, "s_depot", configDepot); + updatedConfigKeyDepot = true; + } + @Test public void testConfigureBackupSchedule() { Long vmId = 1L; @@ -543,7 +441,6 @@ public void testConfigureBackupSchedule() { when(cmd.getIntervalType()).thenReturn(DateUtil.IntervalType.DAILY); when(cmd.getMaxBackups()).thenReturn(8); when(cmd.getSchedule()).thenReturn("00:00:00"); - when(cmd.getQuiesceVM()).thenReturn(null); VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); when(vmInstanceDao.findById(vmId)).thenReturn(vm); @@ -619,9 +516,7 @@ public void createBackupTestCreateScheduledBackup() throws ResourceAllocationExc Long backupOfferingId = 4L; Long accountId = 5L; Long backupId = 6L; - Long oldestBackupId = 7L; Long newBackupSize = 1000000000L; - Long oldBackupSize = 400000000L; when(vmInstanceDao.findById(vmId)).thenReturn(vmInstanceVOMock); when(vmInstanceVOMock.getDataCenterId()).thenReturn(zoneId); @@ -631,7 +526,7 @@ public void createBackupTestCreateScheduledBackup() throws ResourceAllocationExc overrideBackupFrameworkConfigValue(); when(backupOfferingDao.findById(backupOfferingId)).thenReturn(backupOfferingVOMock); when(backupOfferingVOMock.isUserDrivenBackupAllowed()).thenReturn(true); - when(backupOfferingVOMock.getProvider()).thenReturn("testbackupprovider"); + when(backupOfferingVOMock.getProvider()).thenReturn("test"); Mockito.doReturn(scheduleId).when(backupManager).getBackupScheduleId(asyncJobVOMock); @@ -641,45 +536,31 @@ public void createBackupTestCreateScheduledBackup() throws ResourceAllocationExc when(backupScheduleDao.findById(scheduleId)).thenReturn(schedule); when(schedule.getMaxBackups()).thenReturn(2); - VolumeVO volume = mock(VolumeVO.class); - when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume)); - when(volume.getState()).thenReturn(Volume.State.Ready); - when(volumeApiService.getVolumePhysicalSize(null, null, null)).thenReturn(newBackupSize); - BackupProvider backupProvider = mock(BackupProvider.class); Backup backup = mock(Backup.class); when(backup.getId()).thenReturn(backupId); when(backup.getSize()).thenReturn(newBackupSize); - when(backupProvider.getName()).thenReturn("testbackupprovider"); - when(backupProvider.takeBackup(vmInstanceVOMock, null)).thenReturn(new Pair<>(true, backup)); + when(backupProvider.getName()).thenReturn("test"); + when(backupProvider.takeBackup(vmInstanceVOMock)).thenReturn(new Pair<>(true, backup)); Map backupProvidersMap = new HashMap<>(); backupProvidersMap.put(backupProvider.getName().toLowerCase(), backupProvider); ReflectionTestUtils.setField(backupManager, "backupProvidersMap", backupProvidersMap); BackupVO backupVO = mock(BackupVO.class); when(backupVO.getId()).thenReturn(backupId); - BackupVO oldestBackupVO = mock(BackupVO.class); + BackupVO oldestBackupVO = mock(BackupVO.class);; when(backupDao.findById(backupId)).thenReturn(backupVO); List backups = new ArrayList<>(List.of(oldestBackupVO)); when(backupDao.listBySchedule(scheduleId)).thenReturn(backups); - CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); - when(cmd.getVmId()).thenReturn(vmId); - when(cmd.getName()).thenReturn("new-backup1"); - when(cmd.getQuiesceVM()).thenReturn(null); - try (MockedStatic ignored = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString())).thenReturn(1L); - assertTrue(backupManager.createBackup(cmd, asyncJobVOMock)); - - Mockito.verify(resourceLimitMgr, times(1)).checkResourceLimit(accountVOMock, Resource.ResourceType.backup); - Mockito.verify(resourceLimitMgr, times(1)).checkResourceLimit(accountVOMock, Resource.ResourceType.backup_storage, newBackupSize); - + assertTrue(backupManager.createBackup(vmId, asyncJobVOMock)); Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup); Mockito.verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, newBackupSize); Mockito.verify(backupDao, times(1)).update(backupVO.getId(), backupVO); @@ -704,46 +585,6 @@ public void createBackupTestResourceLimitReached() throws ResourceAllocationExce BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering); when(offering.isUserDrivenBackupAllowed()).thenReturn(true); - when(offering.getProvider()).thenReturn("testbackupprovider"); - - Account account = Mockito.mock(Account.class); - when(accountManager.getAccount(accountId)).thenReturn(account); - Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup)).when(resourceLimitMgr).checkResourceLimit(account, Resource.ResourceType.backup); - - CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); - when(cmd.getVmId()).thenReturn(vmId); - when(cmd.getQuiesceVM()).thenReturn(null); - - String jobParams = "{}"; - when(asyncJobVOMock.getCmdInfo()).thenReturn(jobParams); - when(asyncJobVOMock.getId()).thenReturn(1L); - - backupManager.createBackup(cmd, asyncJobVOMock); - - String msg = "Backup storage space resource limit exceeded for account id : " + accountId + ". Failed to create backup"; - Mockito.verify(alertManagerMock, times(1)).sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Backup resource limit exceeded for account id : " + accountId - + ". Failed to create backups; please use updateResourceLimit to increase the limit"); - } - - @Test (expected = ResourceAllocationException.class) - public void testCreateBackupStorageLimitReached() throws ResourceAllocationException { - Long vmId = 1L; - Long zoneId = 2L; - Long scheduleId = 3L; - Long backupOfferingId = 4L; - Long accountId = 5L; - - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - when(vmInstanceDao.findById(vmId)).thenReturn(vm); - when(vm.getDataCenterId()).thenReturn(zoneId); - when(vm.getBackupOfferingId()).thenReturn(backupOfferingId); - when(vm.getAccountId()).thenReturn(accountId); - - overrideBackupFrameworkConfigValue(); - BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); - when(backupOfferingDao.findById(backupOfferingId)).thenReturn(offering); - when(offering.isUserDrivenBackupAllowed()).thenReturn(true); - when(offering.getProvider()).thenReturn("testbackupprovider"); Mockito.doReturn(scheduleId).when(backupManager).getBackupScheduleId(asyncJobVOMock); @@ -751,15 +592,7 @@ public void testCreateBackupStorageLimitReached() throws ResourceAllocationExcep when(accountManager.getAccount(accountId)).thenReturn(account); Mockito.doThrow(new ResourceAllocationException("", Resource.ResourceType.backup_storage)).when(resourceLimitMgr).checkResourceLimit(account, Resource.ResourceType.backup_storage, 0L); - CreateBackupCmd cmd = Mockito.mock(CreateBackupCmd.class); - when(cmd.getVmId()).thenReturn(vmId); - when(cmd.getQuiesceVM()).thenReturn(null); - - backupManager.createBackup(cmd, asyncJobVOMock); - - String msg = "Backup storage space resource limit exceeded for account id : " + accountId + ". Failed to create backup"; - Mockito.verify(alertManagerMock, times(1)).sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Backup storage space resource limit exceeded for account id : " + accountId - + ". Failed to create backups; please use updateResourceLimit to increase the limit"); + backupManager.createBackup(vmId, asyncJobVOMock); } @Test @@ -772,7 +605,7 @@ public void testBackupSyncTask() { Long backup1Size = 1 * Resource.ResourceType.bytesToGiB; Long backup2Size = 2 * Resource.ResourceType.bytesToGiB; Long newBackupSize = 3 * Resource.ResourceType.bytesToGiB; - Long restorePointSize = 4 * Resource.ResourceType.bytesToGiB; + Long metricSize = 4 * Resource.ResourceType.bytesToGiB; overrideBackupFrameworkConfigValue(); @@ -788,23 +621,23 @@ public void testBackupSyncTask() { VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); when(vm.getId()).thenReturn(vmId); when(vm.getAccountId()).thenReturn(accountId); - List vmIds = List.of(vmId); - when(backupDao.listVmIdsWithBackupsInZone(dataCenterId)).thenReturn(vmIds); - when(vmInstanceDao.listByZoneAndBackupOffering(dataCenterId, null)).thenReturn(List.of(vm)); - - Backup.RestorePoint restorePoint1 = new Backup.RestorePoint(restorePoint1ExternalId, DateUtil.now(), "Full", restorePointSize, 0L); - Backup.RestorePoint restorePoint2 = new Backup.RestorePoint("12345", DateUtil.now(), "Full", restorePointSize, 0L); + when(vmInstanceDao.listByZoneWithBackups(dataCenterId, null)).thenReturn(List.of(vm)); + Backup.Metric metric = new Backup.Metric(metricSize, null); + Map metricMap = new HashMap<>(); + metricMap.put(vm, metric); + when(backupProvider.getBackupMetrics(Mockito.anyLong(), Mockito.anyList())).thenReturn(metricMap); + + Backup.RestorePoint restorePoint1 = new Backup.RestorePoint(restorePoint1ExternalId, DateUtil.now(), "Root"); + Backup.RestorePoint restorePoint2 = new Backup.RestorePoint("12345", DateUtil.now(), "Root"); List restorePoints = new ArrayList<>(List.of(restorePoint1, restorePoint2)); when(backupProvider.listRestorePoints(vm)).thenReturn(restorePoints); BackupVO backupInDb1 = new BackupVO(); backupInDb1.setSize(backup1Size); - backupInDb1.setAccountId(accountId); backupInDb1.setExternalId(restorePoint1ExternalId); BackupVO backupInDb2 = new BackupVO(); backupInDb2.setSize(backup2Size); - backupInDb2.setAccountId(accountId); backupInDb2.setExternalId(null); ReflectionTestUtils.setField(backupInDb2, "id", backup2Id); when(backupDao.findById(backup2Id)).thenReturn(backupInDb2); @@ -813,7 +646,7 @@ public void testBackupSyncTask() { BackupVO newBackupEntry = new BackupVO(); newBackupEntry.setSize(newBackupSize); - when(backupProvider.createNewBackupEntryForRestorePoint(restorePoint2, vm)).thenReturn(newBackupEntry); + when(backupProvider.createNewBackupEntryForRestorePoint(restorePoint2, vm, metric)).thenReturn(newBackupEntry); try (MockedStatic ignored = Mockito.mockStatic(ActionEventUtils.class)) { Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(), @@ -827,8 +660,8 @@ public void testBackupSyncTask() { backupSyncTask.runInContext(); verify(resourceLimitMgr, times(1)).decrementResourceCount(accountId, Resource.ResourceType.backup_storage, backup1Size); - verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, restorePointSize); - Assert.assertEquals(backupInDb1.getSize(), restorePointSize); + verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, metricSize); + Assert.assertEquals(backupInDb1.getSize(), metricSize); verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup); verify(resourceLimitMgr, times(1)).incrementResourceCount(accountId, Resource.ResourceType.backup_storage, newBackupSize); @@ -931,687 +764,6 @@ public void deleteBackupScheduleTestDeleteSpecificScheduleWhenItsIdIsSpecified() assertTrue(success); } - @Test - public void testGetBackupDetailsFromVM() { - Long vmId = 1L; - VirtualMachine vm = mock(VirtualMachine.class); - when(vm.getServiceOfferingId()).thenReturn(1L); - when(vm.getTemplateId()).thenReturn(2L); - when(vm.getId()).thenReturn(vmId); - - ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); - when(serviceOffering.getUuid()).thenReturn("service-offering-uuid"); - when(serviceOfferingDao.findById(1L)).thenReturn(serviceOffering); - VMTemplateVO template = mock(VMTemplateVO.class); - when(template.getUuid()).thenReturn("template-uuid"); - when(vmTemplateDao.findById(2L)).thenReturn(template); - - VMInstanceDetailVO vmInstanceDetail = mock(VMInstanceDetailVO.class); - when(vmInstanceDetail.getName()).thenReturn("mocked-detail-name"); - when(vmInstanceDetail.getValue()).thenReturn("mocked-detail-value"); - List vmDetails = Collections.singletonList(vmInstanceDetail); - when(vmInstanceDetailsDao.listDetails(vmId)).thenReturn(vmDetails); - - UserVmJoinVO userVmJoinVO = mock(UserVmJoinVO.class); - when(userVmJoinVO.getNetworkUuid()).thenReturn("mocked-network-uuid"); - List userVmJoinVOs = Collections.singletonList(userVmJoinVO); - when(userVmJoinDao.searchByIds(vmId)).thenReturn(userVmJoinVOs); - - Map details = backupManager.getBackupDetailsFromVM(vm); - - assertEquals("service-offering-uuid", details.get(ApiConstants.SERVICE_OFFERING_ID)); - assertEquals("[{\"networkid\":\"mocked-network-uuid\"}]", details.get(ApiConstants.NICS)); - assertEquals("{\"mocked-detail-name\":\"mocked-detail-value\"}", details.get(ApiConstants.VM_SETTINGS)); - } - - @Test - public void getDataDiskInfoListFromBackup() { - Long size1 = 5L * 1024 * 1024 * 1024; - Long size2 = 10L * 1024 * 1024 * 1024; - Backup backup = mock(Backup.class); - - Backup.VolumeInfo volumeInfo0 = mock(Backup.VolumeInfo.class); - when(volumeInfo0.getType()).thenReturn(Volume.Type.ROOT); - Backup.VolumeInfo volumeInfo1 = mock(Backup.VolumeInfo.class); - when(volumeInfo1.getDiskOfferingId()).thenReturn("disk-offering-uuid-1"); - when(volumeInfo1.getSize()).thenReturn(size1); - when(volumeInfo1.getMinIops()).thenReturn(100L); - when(volumeInfo1.getMaxIops()).thenReturn(300L); - when(volumeInfo1.getType()).thenReturn(Volume.Type.DATADISK); - when(volumeInfo1.getDeviceId()).thenReturn(1L); - Backup.VolumeInfo volumeInfo2 = mock(Backup.VolumeInfo.class); - when(volumeInfo2.getDiskOfferingId()).thenReturn("disk-offering-uuid-2"); - when(volumeInfo2.getSize()).thenReturn(size2); - when(volumeInfo2.getMinIops()).thenReturn(200L); - when(volumeInfo2.getMaxIops()).thenReturn(400L); - when(volumeInfo2.getType()).thenReturn(Volume.Type.DATADISK); - when(volumeInfo2.getDeviceId()).thenReturn(2L); - when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo0, volumeInfo1, volumeInfo2)); - - DiskOfferingVO diskOffering1 = mock(DiskOfferingVO.class); - when(diskOffering1.getUuid()).thenReturn("disk-offering-uuid-1"); - when(diskOffering1.getState()).thenReturn(DiskOffering.State.Active); - - DiskOfferingVO diskOffering2 = mock(DiskOfferingVO.class); - when(diskOffering2.getUuid()).thenReturn("disk-offering-uuid-2"); - when(diskOffering2.getState()).thenReturn(DiskOffering.State.Active); - - when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering1); - when(diskOfferingDao.findByUuid("disk-offering-uuid-2")).thenReturn(diskOffering2); - - List vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup); - - assertEquals(2, vmDiskInfoList.size()); - assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid()); - assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize()); - assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId()); - assertEquals(Long.valueOf(100), vmDiskInfoList.get(0).getMinIops()); - assertEquals(Long.valueOf(300), vmDiskInfoList.get(0).getMaxIops()); - - assertEquals("disk-offering-uuid-2", vmDiskInfoList.get(1).getDiskOffering().getUuid()); - assertEquals(Long.valueOf(10), vmDiskInfoList.get(1).getSize()); - assertEquals(Long.valueOf(2), vmDiskInfoList.get(1).getDeviceId()); - assertEquals(Long.valueOf(200), vmDiskInfoList.get(1).getMinIops()); - assertEquals(Long.valueOf(400), vmDiskInfoList.get(1).getMaxIops()); - } - - @Test - public void getDataDiskInfoListFromBackupNullIops() { - Long size = 5L * 1024 * 1024 * 1024; - Backup backup = mock(Backup.class); - Backup.VolumeInfo volumeInfo1 = mock(Backup.VolumeInfo.class); - when(volumeInfo1.getDiskOfferingId()).thenReturn("disk-offering-uuid-1"); - when(volumeInfo1.getSize()).thenReturn(size); - when(volumeInfo1.getMinIops()).thenReturn(null); - when(volumeInfo1.getMaxIops()).thenReturn(null); - when(volumeInfo1.getType()).thenReturn(Volume.Type.DATADISK); - when(volumeInfo1.getDeviceId()).thenReturn(1L); - when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo1)); - - DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); - when(diskOffering.getUuid()).thenReturn("disk-offering-uuid-1"); - when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); - - when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); - - List vmDiskInfoList = backupManager.getDataDiskInfoListFromBackup(backup); - - assertEquals(1, vmDiskInfoList.size()); - assertEquals("disk-offering-uuid-1", vmDiskInfoList.get(0).getDiskOffering().getUuid()); - assertEquals(Long.valueOf(5), vmDiskInfoList.get(0).getSize()); - assertEquals(Long.valueOf(1), vmDiskInfoList.get(0).getDeviceId()); - assertNull(vmDiskInfoList.get(0).getMinIops()); - assertNull(vmDiskInfoList.get(0).getMaxIops()); - } - - @Test (expected = InvalidParameterValueException.class) - public void testCheckVmDisksSizeAgainstBackup() { - Long sizeInBackup = 5L * 1024 * 1024 * 1024; - Long sizeInCmd = 2L; - Backup backup = mock(Backup.class); - Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); - when(volumeInfo.getDiskOfferingId()).thenReturn("disk-offering-uuid-1"); - when(volumeInfo.getSize()).thenReturn(sizeInBackup); - when(volumeInfo.getType()).thenReturn(Volume.Type.DATADISK); - when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo)); - - DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); - when(diskOffering.getState()).thenReturn(DiskOffering.State.Active); - when(diskOfferingDao.findByUuid("disk-offering-uuid-1")).thenReturn(diskOffering); - List vmDiskInfoList = List.of(new VmDiskInfo(diskOffering, sizeInCmd, 1L, null, null)); - - backupManager.checkVmDisksSizeAgainstBackup(vmDiskInfoList, backup); - } - - @Test - public void testGetRootDiskInfoFromBackup() { - Long size = 5L * 1024 * 1024 * 1024; - Backup backup = mock(Backup.class); - Backup.VolumeInfo volumeInfo = mock(Backup.VolumeInfo.class); - when(volumeInfo.getDiskOfferingId()).thenReturn("root-disk-offering-uuid"); - when(volumeInfo.getSize()).thenReturn(size); - when(volumeInfo.getType()).thenReturn(Volume.Type.ROOT); - when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo)); - - DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); - when(diskOffering.getUuid()).thenReturn("root-disk-offering-uuid"); - when(diskOfferingDao.findByUuid("root-disk-offering-uuid")).thenReturn(diskOffering); - - VmDiskInfo VmDiskInfo = backupManager.getRootDiskInfoFromBackup(backup); - - assertEquals("root-disk-offering-uuid", VmDiskInfo.getDiskOffering().getUuid()); - assertEquals(Long.valueOf(5), VmDiskInfo.getSize()); - assertEquals(null, VmDiskInfo.getDeviceId()); - } - - @Test - public void testImportBackupOffering() { - ImportBackupOfferingCmd cmd = Mockito.mock(ImportBackupOfferingCmd.class); - when(cmd.getZoneId()).thenReturn(1L); - when(cmd.getExternalId()).thenReturn("external-id"); - when(cmd.getName()).thenReturn("Test Offering"); - when(cmd.getDescription()).thenReturn("Test Description"); - when(cmd.getUserDrivenBackups()).thenReturn(true); - - overrideBackupFrameworkConfigValue(); - - when(backupOfferingDao.findByExternalId("external-id", 1L)).thenReturn(null); - when(backupOfferingDao.findByName("Test Offering", 1L)).thenReturn(null); - - BackupOfferingVO offering = new BackupOfferingVO(1L, "external-id", "testbackupprovider", "Test Offering", "Test Description", true); - when(backupOfferingDao.persist(any(BackupOfferingVO.class))).thenReturn(offering); - when(backupProvider.isValidProviderOffering(cmd.getZoneId(), cmd.getExternalId())).thenReturn(true); - - BackupOffering result = backupManager.importBackupOffering(cmd); - - assertEquals("Test Offering", result.getName()); - assertEquals("Test Description", result.getDescription()); - assertEquals(true, result.isUserDrivenBackupAllowed()); - assertEquals("external-id", result.getExternalId()); - assertEquals("testbackupprovider", result.getProvider()); - } - - @Test - public void testCreateVolumeInfoFromVolumes() { - Long diskOfferingId = 5L; - DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class); - Mockito.when(diskOffering.getUuid()).thenReturn("disk-offering-uuid"); - Mockito.when(diskOfferingDao.findById(diskOfferingId)).thenReturn(diskOffering); - - List volumes = new ArrayList<>(); - VolumeVO volume1 = new VolumeVO(Volume.Type.ROOT, "vol1", 1L, 2L, 3L, - diskOfferingId, null, 1024L, null, null, null); - volume1.setUuid("uuid1"); - volume1.setPath("path1"); - volume1.setDeviceId(0L); - volume1.setVolumeType(Volume.Type.ROOT); - volumes.add(volume1); - - VolumeVO volume2 = new VolumeVO(Volume.Type.ROOT, "vol2", 1L, 2L, 3L, - diskOfferingId, null, 2048L, 1000L, 2000L, null); - volume2.setUuid("uuid2"); - volume2.setPath("path2"); - volume2.setDeviceId(1L); - volume2.setVolumeType(Volume.Type.DATADISK); - volumes.add(volume2); - - String expectedJson = "[{\"uuid\":\"uuid1\",\"type\":\"ROOT\",\"size\":1024,\"path\":\"path1\",\"deviceId\":0,\"diskOfferingId\":\"disk-offering-uuid\"},{\"uuid\":\"uuid2\",\"type\":\"DATADISK\",\"size\":2048,\"path\":\"path2\",\"deviceId\":1,\"diskOfferingId\":\"disk-offering-uuid\",\"minIops\":1000,\"maxIops\":2000}]"; - String actualJson = backupManager.createVolumeInfoFromVolumes(new ArrayList<>(volumes)); - - assertEquals(expectedJson, actualJson); - } - - @Test - public void testAssignVMToBackupOffering() { - Long vmId = 1L; - Long offeringId = 2L; - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vm.getId()).thenReturn(vmId); - BackupOfferingVO offering = mock(BackupOfferingVO.class); - - overrideBackupFrameworkConfigValue(); - - when(vmInstanceDao.findById(vmId)).thenReturn(vm); - when(backupOfferingDao.findById(offeringId)).thenReturn(offering); - when(vm.getState()).thenReturn(VirtualMachine.State.Running); - when(vm.getDataCenterId()).thenReturn(1L); - when(vm.getBackupOfferingId()).thenReturn(null); - when(offering.getProvider()).thenReturn("testbackupprovider"); - when(backupProvider.assignVMToBackupOffering(vm, offering)).thenReturn(true); - when(vmInstanceDao.update(1L, vm)).thenReturn(true); - - try (MockedStatic ignored2 = Mockito.mockStatic(UsageEventUtils.class)) { - boolean result = backupManager.assignVMToBackupOffering(vmId, offeringId); - - assertTrue(result); - verify(vmInstanceDao, times(1)).findById(vmId); - verify(backupOfferingDao, times(1)).findById(offeringId); - verify(backupManager, times(1)).getBackupProvider("testbackupprovider"); - } - } - - @Test - public void testRemoveVMFromBackupOffering() { - Long vmId = 1L; - Long accountId = 2L; - Long zoneId = 3L; - Long offeringId = 4L; - Long backupScheduleId = 5L; - String vmHostName = "vm1"; - String vmUuid = "uuid1"; - String resourceName = "Backup-" + vmHostName + "-" + vmUuid; - - boolean forced = true; - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vm.getId()).thenReturn(vmId); - when(vm.getDataCenterId()).thenReturn(1L); - when(vm.getBackupOfferingId()).thenReturn(offeringId); - when(vm.getAccountId()).thenReturn(accountId); - when(vm.getDataCenterId()).thenReturn(zoneId); - when(vm.getHostName()).thenReturn(vmHostName); - when(vm.getUuid()).thenReturn(vmUuid); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - when(vmInstanceDao.update(vmId, vm)).thenReturn(true); - - BackupOfferingVO offering = mock(BackupOfferingVO.class); - when(backupOfferingDao.findById(vm.getBackupOfferingId())).thenReturn(offering); - when(offering.getProvider()).thenReturn("testbackupprovider"); - when(backupProvider.removeVMFromBackupOffering(vm)).thenReturn(true); - when(backupProvider.willDeleteBackupsOnOfferingRemoval()).thenReturn(true); - when(backupDao.listByVmId(null, vmId)).thenReturn(new ArrayList<>()); - - BackupScheduleVO backupSchedule = new BackupScheduleVO(); - ReflectionTestUtils.setField(backupSchedule, "id", backupScheduleId); - when(backupScheduleDao.listByVM(vmId)).thenReturn(List.of(backupSchedule)); - - overrideBackupFrameworkConfigValue(); - - try (MockedStatic usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) { - boolean result = backupManager.removeVMFromBackupOffering(vmId, forced); - - assertTrue(result); - verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); - verify(backupOfferingDao, times(1)).findById(vm.getBackupOfferingId()); - verify(backupManager, times(1)).getBackupProvider("testbackupprovider"); - verify(backupScheduleDao, times(1)).remove(backupScheduleId); - usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVED_AND_BACKUPS_DELETED, accountId, zoneId, vmId, resourceName, - offeringId, null, null, Backup.class.getSimpleName(), vmUuid)); - } - } - - @Test - public void testDeleteBackupScheduleByVmId() { - Long vmId = 1L; - Long scheduleId = 2L; - DeleteBackupScheduleCmd cmd = new DeleteBackupScheduleCmd(); - ReflectionTestUtils.setField(cmd, "vmId", vmId); - - overrideBackupFrameworkConfigValue(); - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vmInstanceDao.findById(vmId)).thenReturn(vm); - BackupScheduleVO schedule = mock(BackupScheduleVO.class); - when(schedule.getId()).thenReturn(scheduleId); - when(backupScheduleDao.listByVM(vmId)).thenReturn(List.of(schedule)); - when(backupScheduleDao.remove(scheduleId)).thenReturn(true); - - boolean result = backupManager.deleteBackupSchedule(cmd); - assertTrue(result); - } - - @Test - public void testRestoreBackupToVM() throws NoTransitionException { - Long backupId = 1L; - Long vmId = 2L; - Long hostId = 3L; - Long offeringId = 4L; - Long poolId = 5L; - - BackupVO backup = mock(BackupVO.class); - when(backup.getBackupOfferingId()).thenReturn(offeringId); - when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - when(vm.getId()).thenReturn(vmId); - when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); - when(vm.getHostId()).thenReturn(hostId); - - BackupOfferingVO offering = mock(BackupOfferingVO.class); - BackupProvider backupProvider = mock(BackupProvider.class); - when(backupProvider.supportsInstanceFromBackup()).thenReturn(true); - - overrideBackupFrameworkConfigValue(); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering); - when(offering.getProvider()).thenReturn("testbackupprovider"); - when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider); - when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId)).thenReturn(true); - when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringSuccess, hostId)).thenReturn(true); - - VolumeVO rootVolume = mock(VolumeVO.class); - when(rootVolume.getPoolId()).thenReturn(poolId); - HostVO host = mock(HostVO.class); - when(hostDao.findById(hostId)).thenReturn(host); - StoragePoolVO pool = mock(StoragePoolVO.class); - when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); - when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); - when(rootVolume.getPoolId()).thenReturn(poolId); - when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); - when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); - when(backupProvider.restoreBackupToVM(vm, backup, null, null)).thenReturn(new Pair<>(true, null)); - - try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { - boolean result = backupManager.restoreBackupToVM(backupId, vmId); - - assertTrue(result); - verify(backupProvider, times(1)).restoreBackupToVM(vm, backup, null, null); - verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId); - verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringSuccess, hostId); - } catch (CloudRuntimeException e) { - fail("Test failed due to exception" + e); - } - } - - @Test - public void testRestoreBackupToVMException() throws NoTransitionException { - Long backupId = 1L; - Long vmId = 2L; - Long hostId = 3L; - Long offeringId = 4L; - Long poolId = 5L; - - BackupVO backup = mock(BackupVO.class); - when(backup.getBackupOfferingId()).thenReturn(offeringId); - when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - when(vm.getId()).thenReturn(vmId); - when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); - when(vm.getHostId()).thenReturn(hostId); - - BackupOfferingVO offering = mock(BackupOfferingVO.class); - BackupProvider backupProvider = mock(BackupProvider.class); - when(backupProvider.supportsInstanceFromBackup()).thenReturn(true); - - overrideBackupFrameworkConfigValue(); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering); - when(offering.getProvider()).thenReturn("testbackupprovider"); - when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider); - when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId)).thenReturn(true); - when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.RestoringFailed, hostId)).thenReturn(true); - - VolumeVO rootVolume = mock(VolumeVO.class); - when(rootVolume.getPoolId()).thenReturn(poolId); - HostVO host = mock(HostVO.class); - when(hostDao.findById(hostId)).thenReturn(host); - StoragePoolVO pool = mock(StoragePoolVO.class); - when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); - when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); - when(rootVolume.getPoolId()).thenReturn(poolId); - when(volumeDao.findIncludingRemovedByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(List.of(rootVolume)); - when(primaryDataStoreDao.findById(poolId)).thenReturn(pool); - when(backupProvider.restoreBackupToVM(vm, backup, null, null)).thenReturn(new Pair<>(false, null)); - - try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { - CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, - () -> backupManager.restoreBackupToVM(backupId, vmId)); - - verify(backupProvider, times(1)).restoreBackupToVM(vm, backup, null, null); - verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringRequested, hostId); - verify(virtualMachineManager, times(1)).stateTransitTo(vm, VirtualMachine.Event.RestoringFailed, hostId); - } - } - - @Test - public void testGetBackupStorageUsedStats() { - Long zoneId = 1L; - overrideBackupFrameworkConfigValue(); - when(backupManager.getBackupProvider(zoneId)).thenReturn(backupProvider); - when(backupProvider.getBackupStorageStats(zoneId)).thenReturn(new Pair<>(100L, 200L)); - - CapacityVO capacity = backupManager.getBackupStorageUsedStats(zoneId); - - Assert.assertNotNull(capacity); - Assert.assertEquals(Optional.ofNullable(Long.valueOf(100)), Optional.ofNullable(capacity.getUsedCapacity())); - Assert.assertEquals(Optional.ofNullable(Long.valueOf(200)), Optional.ofNullable(capacity.getTotalCapacity())); - Assert.assertEquals(CapacityVO.CAPACITY_TYPE_BACKUP_STORAGE, capacity.getCapacityType()); - } - - @Test - public void testCheckAndRemoveBackupOfferingBeforeExpunge() { - Long vmId = 1L; - Long zoneId = 2L; - Long offeringId = 3L; - String vmUuid = "uuid1"; - String instanceName = "i-2-1-VM"; - String backupExternalId = "backup-external-id"; - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vm.getId()).thenReturn(vmId); - when(vm.getUuid()).thenReturn(vmUuid); - when(vm.getBackupOfferingId()).thenReturn(offeringId); - when(vm.getInstanceName()).thenReturn(instanceName); - when(vm.getBackupExternalId()).thenReturn(backupExternalId); - when(vm.getDataCenterId()).thenReturn(zoneId); - Backup backup = mock(Backup.class); - when(backupDao.listByVmIdAndOffering(zoneId, vmId, offeringId)).thenReturn(List.of(backup)); - - CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, - () -> backupManager.checkAndRemoveBackupOfferingBeforeExpunge(vm)); - Assert.assertEquals("This Instance [uuid: uuid1, name: i-2-1-VM] has a " - + "Backup Offering [id: 3, external id: backup-external-id] with 1 backups. Please, remove the backup offering " - + "before proceeding to VM exclusion!", exception.getMessage()); - } - - @Test - public void testGetIpToNetworkMapFromBackup() { - Long networkId1 = 1L; - Long networkId2 = 2L; - String networkUuid1 = "network-uuid-1"; - String networkUuid2 = "network-uuid-2"; - String ip1 = "10.1.1.1"; - String ip2 = "10.1.1.2"; - String ipv61 = "2001:db8::1"; - String ipv62 = "2001:db8::2"; - String mac1 = "00:11:22:33:44:55"; - String mac2 = "00:11:22:33:44:56"; - - // Test case 1: Missing network information - Backup backup1 = mock(Backup.class); - List networkIds1 = new ArrayList<>(); - try { - backupManager.getIpToNetworkMapFromBackup(backup1, true, networkIds1); - fail("Expected CloudRuntimeException for missing network information"); - } catch (CloudRuntimeException e) { - assertEquals("Backup doesn't contain network information. Please specify at least one valid network while creating instance", e.getMessage()); - } - - // Test case 2: IP preservation enabled with IP information - Backup backup2 = mock(Backup.class); - String nicsJson = String.format("[{\"networkid\":\"%s\",\"ipaddress\":\"%s\",\"ip6address\":\"%s\",\"macaddress\":\"%s\"}," + - "{\"networkid\":\"%s\",\"ipaddress\":\"%s\",\"ip6address\":\"%s\",\"macaddress\":\"%s\"}]", - networkUuid1, ip1, ipv61, mac1, networkUuid2, ip2, ipv62, mac2); - when(backup2.getDetail(ApiConstants.NICS)).thenReturn(nicsJson); - - NetworkVO network1 = mock(NetworkVO.class); - NetworkVO network2 = mock(NetworkVO.class); - when(networkDao.findByUuid(networkUuid1)).thenReturn(network1); - when(networkDao.findByUuid(networkUuid2)).thenReturn(network2); - when(network1.getId()).thenReturn(networkId1); - when(network2.getId()).thenReturn(networkId2); - - Network.IpAddresses ipAddresses1 = mock(Network.IpAddresses.class); - Network.IpAddresses ipAddresses2 = mock(Network.IpAddresses.class); - when(networkService.getIpAddressesFromIps(ip1, ipv61, mac1)).thenReturn(ipAddresses1); - when(networkService.getIpAddressesFromIps(ip2, ipv62, mac2)).thenReturn(ipAddresses2); - - List networkIds2 = new ArrayList<>(); - Map result2 = backupManager.getIpToNetworkMapFromBackup(backup2, true, networkIds2); - - assertEquals(2, result2.size()); - assertEquals(ipAddresses1, result2.get(networkId1)); - assertEquals(ipAddresses2, result2.get(networkId2)); - assertEquals(2, networkIds2.size()); - assertTrue(networkIds2.contains(networkId1)); - assertTrue(networkIds2.contains(networkId2)); - - // Test case 3: IP preservation enabled but missing IP information - Backup backup3 = mock(Backup.class); - nicsJson = String.format("[{\"networkid\":\"%s\"}]", networkUuid1); - when(backup3.getDetail(ApiConstants.NICS)).thenReturn(nicsJson); - - List networkIds3 = new ArrayList<>(); - Map result3 = backupManager.getIpToNetworkMapFromBackup(backup3, true, networkIds3); - - assertEquals(1, result3.size()); - assertNull(result3.get(networkId1)); - assertEquals(1, networkIds3.size()); - assertTrue(networkIds3.contains(networkId1)); - - // Test case 4: IP preservation disabled - Backup backup4 = mock(Backup.class); - nicsJson = String.format("[{\"networkid\":\"%s\"}]", networkUuid1); - when(backup4.getDetail(ApiConstants.NICS)).thenReturn(nicsJson); - - List networkIds4 = new ArrayList<>(); - Map result4 = backupManager.getIpToNetworkMapFromBackup(backup4, false, networkIds4); - - assertEquals(1, result4.size()); - assertNull(result4.get(networkId1)); - assertEquals(1, networkIds4.size()); - assertTrue(networkIds4.contains(networkId1)); - } - - @Test - public void testDeleteBackupVmNotFound() { - Long backupId = 1L; - Long vmId = 2L; - Long zoneId = 3L; - Long accountId = 4L; - Long backupOfferingId = 5L; - String vmHostName = "vm1"; - String vmUuid = "uuid1"; - String resourceName = "Backup-" + vmHostName + "-" + vmUuid; - - BackupVO backup = mock(BackupVO.class); - when(backup.getId()).thenReturn(backupId); - when(backup.getVmId()).thenReturn(vmId); - when(backup.getZoneId()).thenReturn(zoneId); - when(backup.getAccountId()).thenReturn(accountId); - when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); - when(backup.getSize()).thenReturn(100L); - - overrideBackupFrameworkConfigValue(); - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vm.getId()).thenReturn(vmId); - when(vm.getAccountId()).thenReturn(accountId); - when(vm.getBackupOfferingId()).thenReturn(10L); - when(vm.getDataCenterId()).thenReturn(zoneId); - when(vm.getHostName()).thenReturn(vmHostName); - when(vm.getUuid()).thenReturn(vmUuid); - when(backupDao.findByIdIncludingRemoved(backupId)).thenReturn(backup); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - when(backupDao.listByVmIdAndOffering(zoneId, vmId, backupOfferingId)).thenReturn(new ArrayList<>()); - - BackupOfferingVO offering = mock(BackupOfferingVO.class); - when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); - - when(backupProvider.deleteBackup(backup, false)).thenReturn(true); - - when(backupDao.remove(backupId)).thenReturn(true); - - try (MockedStatic usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) { - boolean result = backupManager.deleteBackup(backupId, false); - - assertTrue(result); - verify(backupProvider).deleteBackup(backup, false); - verify(resourceLimitMgr).decrementResourceCount(accountId, Resource.ResourceType.backup); - verify(resourceLimitMgr).decrementResourceCount(accountId, Resource.ResourceType.backup_storage, backup.getSize()); - verify(backupDao).remove(backupId); - usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVED_AND_BACKUPS_DELETED, accountId, zoneId, vmId, resourceName, - backupOfferingId, null, null, Backup.class.getSimpleName(), vmUuid)); - } - } - - @Test - public void testNewBackupResponse() { - Long vmId = 1L; - Long accountId = 2L; - Long domainId = 3L; - Long zoneId = 4L; - Long vmOfferingId = 5L; - Long backupOfferingId = 6L; - Long backupId = 7L; - Long templateId = 8L; - String templateUuid = "template-uuid1"; - String serviceOfferingUuid = "service-offering-uuid1"; - - BackupVO backup = new BackupVO(); - ReflectionTestUtils.setField(backup, "id", backupId); - ReflectionTestUtils.setField(backup, "uuid", "backup-uuid"); - backup.setVmId(vmId); - backup.setAccountId(accountId); - backup.setDomainId(domainId); - backup.setZoneId(zoneId); - backup.setBackupOfferingId(backupOfferingId); - backup.setType("Full"); - backup.setBackupScheduleId(null); - - VMInstanceVO vm = new VMInstanceVO(vmId, 0L, "test-vm", "test-vm", VirtualMachine.Type.User, - 0L, Hypervisor.HypervisorType.Simulator, 0L, domainId, accountId, 0L, false); - vm.setDataCenterId(zoneId); - vm.setBackupOfferingId(vmOfferingId); - vm.setTemplateId(templateId); - - AccountVO account = new AccountVO(); - account.setUuid("account-uuid"); - account.setAccountName("test-account"); - - DomainVO domain = new DomainVO(); - domain.setUuid("domain-uuid"); - domain.setName("test-domain"); - - DataCenterVO zone = new DataCenterVO(1L, "test-zone", null, null, null, null, null, null, null, null, DataCenter.NetworkType.Advanced, null, null); - zone.setUuid("zone-uuid"); - - BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); - Mockito.when(offering.getUuid()).thenReturn("offering-uuid"); - Mockito.when(offering.getName()).thenReturn("test-offering"); - - Mockito.when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - Mockito.when(accountDao.findByIdIncludingRemoved(accountId)).thenReturn(account); - Mockito.when(domainDao.findByIdIncludingRemoved(domainId)).thenReturn(domain); - Mockito.when(dataCenterDao.findByIdIncludingRemoved(zoneId)).thenReturn(zone); - Mockito.when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); - - VMTemplateVO template = mock(VMTemplateVO.class); - when(template.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); - when(template.getName()).thenReturn("template1"); - when(vmTemplateDao.findByUuid(templateUuid)).thenReturn(template); - Map details = new HashMap<>(); - details.put(ApiConstants.TEMPLATE_ID, templateUuid); - - ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); - when(serviceOffering.getName()).thenReturn("service-offering1"); - when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering); - details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid); - - NetworkVO network = mock(NetworkVO.class); - when(network.getName()).thenReturn("network1"); - when(networkDao.findByUuid("network-uuid1")).thenReturn(network); - details.put(ApiConstants.NICS, "[{\"networkid\":\"network-uuid1\"}]"); - - Mockito.when(backupDetailsDao.listDetailsKeyPairs(backup.getId(), true)).thenReturn(details); - - BackupResponse response = backupManager.createBackupResponse(backup, true); - - Assert.assertEquals("backup-uuid", response.getId()); - Assert.assertEquals("test-vm", response.getVmName()); - Assert.assertEquals("account-uuid", response.getAccountId()); - Assert.assertEquals("test-account", response.getAccount()); - Assert.assertEquals("domain-uuid", response.getDomainId()); - Assert.assertEquals("test-domain", response.getDomain()); - Assert.assertEquals("zone-uuid", response.getZoneId()); - Assert.assertEquals("test-zone", response.getZone()); - Assert.assertEquals("offering-uuid", response.getBackupOfferingId()); - Assert.assertEquals("test-offering", response.getBackupOffering()); - Assert.assertEquals("MANUAL", response.getIntervalType()); - Assert.assertEquals("{serviceofferingid=service-offering-uuid1, isiso=false, hypervisor=Simulator, " + - "nics=[{\"networkid\":\"network-uuid1\",\"networkname\":\"network1\"}], serviceofferingname=service-offering1, " + - "templatename=template1, templateid=template-uuid1}", response.getVmDetails().toString()); - Assert.assertEquals(true, response.getVmOfferingRemoved()); - } - @Test public void validateAndGetDefaultBackupRetentionIfRequiredTestReturnZeroAsDefaultValue() { int retention = backupManager.validateAndGetDefaultBackupRetentionIfRequired(null, backupOfferingVOMock, null); @@ -1819,341 +971,4 @@ public void sendExceededBackupLimitAlertTestSendAlertForBackupStorageResourceTyp expectedAlertDetails ); } - - @Test - public void testListBackupSchedulesAsRootAdmin() { - long vmId = 1L; - ListBackupScheduleCmd cmd = Mockito.mock(ListBackupScheduleCmd.class); - Mockito.when(cmd.getVmId()).thenReturn(vmId); - Mockito.when(cmd.getId()).thenReturn(1L); - - // Mock VM for validation - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - Mockito.when(vmInstanceDao.findById(vmId)).thenReturn(vm); - Mockito.when(vm.getDataCenterId()).thenReturn(1L); - overrideBackupFrameworkConfigValue(); - Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any()); - - BackupScheduleVO schedule1 = Mockito.mock(BackupScheduleVO.class); - BackupScheduleVO schedule2 = Mockito.mock(BackupScheduleVO.class); - List schedules = List.of(schedule1, schedule2); - - SearchBuilder searchBuilder = Mockito.mock(SearchBuilder.class); - SearchCriteria searchCriteria = Mockito.mock(SearchCriteria.class); - BackupScheduleVO entity = Mockito.mock(BackupScheduleVO.class); - - Mockito.when(backupScheduleDao.createSearchBuilder()).thenReturn(searchBuilder); - Mockito.when(searchBuilder.entity()).thenReturn(entity); - Mockito.when(searchBuilder.and(Mockito.anyString(), (Object) any(), Mockito.any())).thenReturn(searchBuilder); - Mockito.when(searchBuilder.create()).thenReturn(searchCriteria); - - Mockito.when(backupScheduleDao.searchAndCount(Mockito.any(), Mockito.any())).thenReturn(new Pair<>(schedules, schedules.size())); - List result = backupManager.listBackupSchedules(cmd); - - assertEquals(2, result.size()); - assertTrue(result.contains(schedule1)); - assertTrue(result.contains(schedule2)); - } - - @Test - public void testListBackupSchedulesAsNonAdmin() { - long vmId = 1L; - ListBackupScheduleCmd cmd = Mockito.mock(ListBackupScheduleCmd.class); - Mockito.when(cmd.getVmId()).thenReturn(vmId); - Mockito.when(cmd.getId()).thenReturn(1L); - - // Mock VM for validation - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - Mockito.when(vmInstanceDao.findById(vmId)).thenReturn(vm); - Mockito.when(vm.getDataCenterId()).thenReturn(1L); - overrideBackupFrameworkConfigValue(); - Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any()); - - BackupScheduleVO schedule = Mockito.mock(BackupScheduleVO.class); - List schedules = List.of(schedule); - - SearchBuilder searchBuilder = Mockito.mock(SearchBuilder.class); - SearchCriteria searchCriteria = Mockito.mock(SearchCriteria.class); - BackupScheduleVO entity = Mockito.mock(BackupScheduleVO.class); - - Mockito.when(backupScheduleDao.createSearchBuilder()).thenReturn(searchBuilder); - Mockito.when(searchBuilder.create()).thenReturn(searchCriteria); - Mockito.when(searchBuilder.entity()).thenReturn(entity); - Mockito.when(searchBuilder.and(Mockito.anyString(), (Object) any(), Mockito.any())).thenReturn(searchBuilder); - Mockito.lenient().when(backupScheduleDao.search(Mockito.eq(searchCriteria), Mockito.any())).thenReturn(schedules); - - Mockito.doNothing().when(accountManager).buildACLSearchBuilder(Mockito.any(), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any()); - Mockito.doNothing().when(accountManager).buildACLSearchCriteria(Mockito.any(), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any()); - - Mockito.when(backupScheduleDao.searchAndCount(Mockito.any(), Mockito.any())).thenReturn(new Pair<>(schedules, schedules.size())); - List result = backupManager.listBackupSchedules(cmd); - - assertEquals(1, result.size()); - assertTrue(result.contains(schedule)); - } - - @Test - public void testCanCreateInstanceFromBackupAcrossZonesSuccess() { - Long backupId = 1L; - Long backupOfferingId = 2L; - String providerName = "testbackupprovider"; - - BackupVO backup = mock(BackupVO.class); - when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); - - BackupOfferingVO offering = mock(BackupOfferingVO.class); - when(offering.getProvider()).thenReturn(providerName); - - BackupProvider backupProvider = mock(BackupProvider.class); - when(backupProvider.crossZoneInstanceCreationEnabled(offering)).thenReturn(true); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); - when(backupManager.getBackupProvider(providerName)).thenReturn(backupProvider); - - Boolean result = backupManager.canCreateInstanceFromBackupAcrossZones(backupId); - - assertTrue(result); - verify(backupDao, times(1)).findById(backupId); - verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId); - verify(backupManager, times(1)).getBackupProvider(providerName); - verify(backupProvider, times(1)).crossZoneInstanceCreationEnabled(offering); - } - - @Test - public void testCanCreateInstanceFromBackupAcrossZonesFalse() { - Long backupId = 1L; - Long backupOfferingId = 2L; - String providerName = "testbackupprovider"; - - BackupVO backup = mock(BackupVO.class); - when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); - - BackupOfferingVO offering = mock(BackupOfferingVO.class); - when(offering.getProvider()).thenReturn(providerName); - - BackupProvider backupProvider = mock(BackupProvider.class); - when(backupProvider.crossZoneInstanceCreationEnabled(offering)).thenReturn(false); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); - when(backupManager.getBackupProvider(providerName)).thenReturn(backupProvider); - - Boolean result = backupManager.canCreateInstanceFromBackupAcrossZones(backupId); - - assertFalse(result); - verify(backupDao, times(1)).findById(backupId); - verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId); - verify(backupManager, times(1)).getBackupProvider(providerName); - verify(backupProvider, times(1)).crossZoneInstanceCreationEnabled(offering); - } - - @Test - public void testCanCreateInstanceFromBackupAcrossZonesOfferingNotFound() { - Long backupId = 1L; - Long backupOfferingId = 2L; - - BackupVO backup = mock(BackupVO.class); - when(backup.getBackupOfferingId()).thenReturn(backupOfferingId); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(null); - - CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, - () -> backupManager.canCreateInstanceFromBackupAcrossZones(backupId)); - - assertEquals("Failed to find backup offering", exception.getMessage()); - verify(backupDao, times(1)).findById(backupId); - verify(backupOfferingDao, times(1)).findByIdIncludingRemoved(backupOfferingId); - verify(backupManager, never()).getBackupProvider(any(String.class)); - } - - @Test - public void testRestoreBackupSuccess() throws NoTransitionException { - Long backupId = 1L; - Long vmId = 2L; - Long zoneId = 3L; - Long accountId = 4L; - Long domainId = 5L; - Long userId = 6L; - Long offeringId = 7L; - String vmInstanceName = "test-vm"; - Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; - - BackupVO backup = mock(BackupVO.class); - when(backup.getVmId()).thenReturn(vmId); - when(backup.getZoneId()).thenReturn(zoneId); - when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); - when(backup.getBackupOfferingId()).thenReturn(offeringId); - Backup.VolumeInfo volumeInfo = new Backup.VolumeInfo("uuid", "path", Volume.Type.ROOT, 1024L, 0L, "disk-offering-uuid", 1000L, 2000L); - when(backup.getBackedUpVolumes()).thenReturn(List.of(volumeInfo)); - when(backup.getUuid()).thenReturn("backup-uuid"); - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vm.getId()).thenReturn(vmId); - when(vm.getDataCenterId()).thenReturn(zoneId); - when(vm.getDomainId()).thenReturn(domainId); - when(vm.getAccountId()).thenReturn(accountId); - when(vm.getUserId()).thenReturn(userId); - when(vm.getInstanceName()).thenReturn(vmInstanceName); - when(vm.getHypervisorType()).thenReturn(hypervisorType); - when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); - when(vm.getRemoved()).thenReturn(null); - when(vm.getBackupOfferingId()).thenReturn(offeringId); - - BackupOfferingVO offering = mock(BackupOfferingVO.class); - when(offering.getProvider()).thenReturn("testbackupprovider"); - - VolumeVO volume = mock(VolumeVO.class); - when(volumeDao.findByInstance(vmId)).thenReturn(Collections.singletonList(volume)); - - BackupProvider backupProvider = mock(BackupProvider.class); - when(backupProvider.restoreVMFromBackup(vm, backup)).thenReturn(true); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - when(backupOfferingDao.findByIdIncludingRemoved(offeringId)).thenReturn(offering); - when(backupManager.getBackupProvider("testbackupprovider")).thenReturn(backupProvider); - doReturn(true).when(backupManager).importRestoredVM(zoneId, domainId, accountId, userId, vmInstanceName, hypervisorType, backup); - doNothing().when(backupManager).validateBackupForZone(any()); - when(virtualMachineManager.stateTransitTo(any(), any(), any())).thenReturn(true); - - try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { - Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), - Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), - Mockito.eq(true), Mockito.eq(0))).thenReturn(1L); - - boolean result = backupManager.restoreBackup(backupId); - - assertTrue(result); - verify(backupDao, times(1)).findById(backupId); - verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); - verify(backupOfferingDao, times(2)).findByIdIncludingRemoved(offeringId); - verify(backupProvider, times(1)).restoreVMFromBackup(vm, backup); - verify(backupManager, times(1)).importRestoredVM(zoneId, domainId, accountId, userId, vmInstanceName, hypervisorType, backup); - } - } - - @Test - public void testRestoreBackupBackupNotFound() { - Long backupId = 1L; - - when(backupDao.findById(backupId)).thenReturn(null); - - CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, - () -> backupManager.restoreBackup(backupId)); - - assertEquals("Backup " + backupId + " does not exist", exception.getMessage()); - verify(backupDao, times(1)).findById(backupId); - verify(vmInstanceDao, never()).findByIdIncludingRemoved(any()); - } - - @Test - public void testRestoreBackupBackupNotBackedUp() { - Long backupId = 1L; - - BackupVO backup = mock(BackupVO.class); - when(backup.getStatus()).thenReturn(Backup.Status.BackingUp); - - when(backupDao.findById(backupId)).thenReturn(backup); - - CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, - () -> backupManager.restoreBackup(backupId)); - - assertEquals("Backup should be in BackedUp state", exception.getMessage()); - verify(backupDao, times(1)).findById(backupId); - verify(vmInstanceDao, never()).findByIdIncludingRemoved(any()); - } - - @Test - public void testRestoreBackupVmExpunging() { - Long backupId = 1L; - Long vmId = 2L; - Long zoneId = 3L; - - BackupVO backup = mock(BackupVO.class); - when(backup.getVmId()).thenReturn(vmId); - when(backup.getZoneId()).thenReturn(zoneId); - when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vm.getState()).thenReturn(VirtualMachine.State.Expunging); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - doNothing().when(backupManager).validateBackupForZone(any()); - - CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, - () -> backupManager.restoreBackup(backupId)); - - assertEquals("The Instance from which the backup was taken could not be found.", exception.getMessage()); - verify(backupDao, times(1)).findById(backupId); - verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); - } - - @Test - public void testRestoreBackupVmNotStopped() { - Long backupId = 1L; - Long vmId = 2L; - Long zoneId = 3L; - - BackupVO backup = mock(BackupVO.class); - when(backup.getVmId()).thenReturn(vmId); - when(backup.getZoneId()).thenReturn(zoneId); - when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vm.getState()).thenReturn(VirtualMachine.State.Running); - when(vm.getRemoved()).thenReturn(null); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - doNothing().when(backupManager).validateBackupForZone(any()); - - CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, - () -> backupManager.restoreBackup(backupId)); - - assertEquals("Existing VM should be stopped before being restored from backup", exception.getMessage()); - verify(backupDao, times(1)).findById(backupId); - verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); - } - - @Test - public void testRestoreBackupVolumeMismatch() { - Long backupId = 1L; - Long vmId = 2L; - Long zoneId = 3L; - - BackupVO backup = mock(BackupVO.class); - when(backup.getVmId()).thenReturn(vmId); - when(backup.getZoneId()).thenReturn(zoneId); - when(backup.getStatus()).thenReturn(Backup.Status.BackedUp); - when(backup.getBackedUpVolumes()).thenReturn(Collections.emptyList()); - - VMInstanceVO vm = mock(VMInstanceVO.class); - when(vm.getId()).thenReturn(vmId); - when(vm.getState()).thenReturn(VirtualMachine.State.Destroyed); - when(vm.getRemoved()).thenReturn(null); - when(vm.getBackupVolumeList()).thenReturn(Collections.emptyList()); - - VolumeVO volume = mock(VolumeVO.class); - when(volumeDao.findByInstance(vmId)).thenReturn(Collections.singletonList(volume)); - - when(backupDao.findById(backupId)).thenReturn(backup); - when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - doNothing().when(backupManager).validateBackupForZone(any()); - - try (MockedStatic utils = Mockito.mockStatic(ActionEventUtils.class)) { - Mockito.when(ActionEventUtils.onStartedActionEvent(Mockito.anyLong(), Mockito.anyLong(), - Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), - Mockito.eq(true), Mockito.eq(0))).thenReturn(1L); - CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, - () -> backupManager.restoreBackup(backupId)); - - assertEquals("Unable to restore VM with the current backup as the backup has different number of disks as the VM", exception.getMessage()); - } - verify(backupDao, times(1)).findById(backupId); - verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); - verify(volumeDao, times(1)).findByInstance(vmId); - } -} \ No newline at end of file +} From 0bcc18d77bda21cdd72a6d5906bed774afbd86e3 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:34:44 +0900 Subject: [PATCH 24/34] Revert "Update BackupManagerTest.java" This reverts commit 46349c21d7d4f6d7ded8a8679db16420aa480bbf. --- .../java/org/apache/cloudstack/backup/BackupManagerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 88892d982d87..66fa2d33cb1e 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -49,7 +49,6 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; -import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; From 062556d2aaa5a66e95cc5d29fea09a546a202448 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:34:51 +0900 Subject: [PATCH 25/34] Revert "Update VolumeApiServiceImpl.java" This reverts commit 7a02284ac6009aa80e1d2e8b5e5b389c7ac72551. --- .../src/main/java/com/cloud/storage/VolumeApiServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index c20ca58d9416..6d975e1809d4 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -353,6 +353,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic private BackupDao backupDao; @Inject private StatsCollector statsCollector; + @Inject + HostPodDao podDao; protected Gson _gson; From 6ce7bab061929bfbca536c617ac5a4ed52db06c5 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:34:57 +0900 Subject: [PATCH 26/34] Revert "Update BackupManagerImpl.java" This reverts commit 78dd41e780f9def5a8900edea65eb41405ce5e76. --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 2 ++ 1 file changed, 2 insertions(+) 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 c2deb6650ca1..5c1c7f9d955f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -33,6 +33,7 @@ import com.cloud.alert.AlertManager; import com.cloud.configuration.Resource; import com.cloud.exception.ResourceAllocationException; +import com.cloud.storage.Snapshot; import com.cloud.storage.VolumeApiService; import com.cloud.user.DomainManager; import com.cloud.user.ResourceLimitService; @@ -73,6 +74,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher; import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; From 0dfcffb5f6f656875117f3a867f40ac7ba73bae4 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:35:05 +0900 Subject: [PATCH 27/34] Revert "Update BackupSchedule.java" This reverts commit 8fcc034526b84c16fc9fa11d9864e8dae38e2e30. --- .../main/java/org/apache/cloudstack/backup/BackupSchedule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java index da90da508fff..26adc80db37a 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java @@ -31,4 +31,5 @@ public interface BackupSchedule extends InternalIdentity { Date getScheduledTimestamp(); Long getAsyncJobId(); int getMaxBackups(); + String getUuid(); } From 4f50052e33dcaff825ddfab8c4bdb3aca9f3f91a Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:35:11 +0900 Subject: [PATCH 28/34] =?UTF-8?q?Revert=20"=EB=B9=8C=EB=93=9C=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 979c859e1289b646bafd630526eebb429c720afd. --- .../main/java/com/cloud/storage/dao/BucketDaoImpl.java | 8 -------- .../org/apache/cloudstack/backup/dao/BackupDaoImpl.java | 8 -------- 2 files changed, 16 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java index 963bcd6cfe6b..473879d933dc 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java @@ -114,12 +114,4 @@ public Long calculateObjectStorageAllocationForAccount(long accountId) { Long totalQuota = customSearch(sc, null).get(0).sum; return (totalQuota * Resource.ResourceType.bytesToGiB); } - - public static class SumCount { - public long sum; - public long count; - - public SumCount() { - } - } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java index 674e8cf428e6..3dbdb5abc828 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java @@ -233,12 +233,4 @@ public BackupResponse newBackupResponse(Backup backup) { response.setObjectName("backup"); return response; } - - public static class SumCount { - public long sum; - public long count; - - public SumCount() { - } - } } From 393fc5373f369d79ca3612f708a3683c49a2027a Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:35:19 +0900 Subject: [PATCH 29/34] Revert "Update BackupVO.java" This reverts commit 3762f5405a19632c2fb2b1dd49a23256ae081434. --- .../src/main/java/org/apache/cloudstack/backup/BackupVO.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java index ebe98b3dda53..81dd0fea68d3 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -22,10 +22,12 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.StringUtils; +import java.beans.Transient; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.UUID; import javax.persistence.Column; From 0ad6131ae7f92e973f484485225470b7754e3431 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 10:35:25 +0900 Subject: [PATCH 30/34] =?UTF-8?q?Revert=20"=EB=B9=8C=EB=93=9C=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=A0=81=EC=9A=A9"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c585cad88e07e4865c98935a2a24014c339ae0e8. --- api/src/main/java/com/cloud/event/EventTypes.java | 2 +- api/src/main/java/com/cloud/storage/VolumeApiService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index b9c8f0b55a6d..97d889bebcfa 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -799,7 +799,7 @@ public class EventTypes { // DISASTER RECOVERY public static final String EVENT_DISASTER_RECOVERY_CLUSTER = "DISASTER.RECOVERY.CLUSTER"; - + // Resource Limit public static final String EVENT_RESOURCE_LIMIT_UPDATE = "RESOURCE.LIMIT.UPDATE"; diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 0698c09e0887..1239d37af26d 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -197,6 +197,6 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc Volume cloneVolumeFromSnapshot(Volume volume, long snapshotId, Long vmId) throws StorageUnavailableException; Volume updateCompressDedupVolume(UpdateCompressDedupCmd cmd); - + Long getVolumePhysicalSize(Storage.ImageFormat format, String path, String chainInfo); } From a0d7e43f0c9e304767ee5ba7e40181ec9c4b69f2 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 13:02:29 +0900 Subject: [PATCH 31/34] Fix deletion of backup schedules (#11222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 미반영 적용 --- .../api/response/BackupScheduleResponse.java | 13 ++++++++++--- .../backup/dao/BackupScheduleDaoImpl.java | 3 ++- .../resources/META-INF/db/schema-42010to42100.sql | 3 +++ ui/src/views/compute/backup/BackupSchedule.vue | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java index d7c6f96add5b..92f1bad54470 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java @@ -28,6 +28,9 @@ @EntityReference(value = BackupSchedule.class) public class BackupScheduleResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the backup schedule.") + private String id; @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) @Param(description = "name of the VM") @@ -51,7 +54,11 @@ public class BackupScheduleResponse extends BaseResponse { @SerializedName(ApiConstants.MAX_BACKUPS) @Param(description = "maximum number of backups retained") - private Integer maxBakups; + private Integer maxBackups; + + public void setId(String id) { + this.id = id; + } public String getVmName() { return vmName; @@ -93,7 +100,7 @@ public void setTimezone(String timezone) { this.timezone = timezone; } - public void setMaxBakups(Integer maxBakups) { - this.maxBakups = maxBakups; + public void setMaxBackups(Integer maxBackups) { + this.maxBackups = maxBackups; } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java index aac2e3bf2320..9374798dde32 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java @@ -92,12 +92,13 @@ public List getSchedulesToExecute(Date currentTimestamp) { public BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(schedule.getVmId()); BackupScheduleResponse response = new BackupScheduleResponse(); + response.setId(schedule.getUuid()); response.setVmId(vm.getUuid()); response.setVmName(vm.getHostName()); response.setIntervalType(schedule.getScheduleType()); response.setSchedule(schedule.getSchedule()); response.setTimezone(schedule.getTimezone()); - response.setMaxBakups(schedule.getMaxBackups()); + response.setMaxBackups(schedule.getMaxBackups()); response.setObjectName("backupschedule"); return response; } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql index 31ffbae10cab..dfff847e47a9 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql @@ -27,3 +27,6 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'console_endpoint_ -- Add client_address column to cloud.console_session table CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'client_address', 'VARCHAR(45)'); + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule', 'uuid', 'VARCHAR(40) NOT NULL'); +UPDATE `cloud`.`backup_schedule` SET uuid = UUID(); diff --git a/ui/src/views/compute/backup/BackupSchedule.vue b/ui/src/views/compute/backup/BackupSchedule.vue index 7309068ea069..cdf62b84c0da 100644 --- a/ui/src/views/compute/backup/BackupSchedule.vue +++ b/ui/src/views/compute/backup/BackupSchedule.vue @@ -172,7 +172,7 @@ export default { methods: { handleClickDelete (record) { const params = {} - params.virtualmachineid = record.virtualmachineid + params.id = record.id this.actionLoading = true api('deleteBackupSchedule', params).then(json => { if (json.deletebackupscheduleresponse.success) { From 626ca83a57426c0247c313420500362cc4c30409 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Mon, 19 Jan 2026 13:56:04 +0900 Subject: [PATCH 32/34] =?UTF-8?q?commvault=20=EB=B0=B1=EC=97=85=20?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=ED=99=94=20=EC=84=A4=EC=A0=95=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Resource Limits to Backups and Object Storage (apache#10017) 에서 백업 동기화 로직이 변경되어, commvault 백업 동기화 설정을 위한 코드 추가 --- .../java/org/apache/cloudstack/backup/BackupManagerImpl.java | 1 + 1 file changed, 1 insertion(+) 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 1177c1f9a0e3..abf3b0e6c76f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -1650,6 +1650,7 @@ private void syncBackups(BackupProvider backupProvider, VirtualMachine vm, Backu Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { + backupProvider.syncBackups(vm, metric); final List backupsInDb = backupDao.listByVmId(null, vm.getId()); List restorePoints = backupProvider.listRestorePoints(vm); if (restorePoints == null) { From e61f7786203c3583860ca3d60c1962a39d24713c Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Tue, 20 Jan 2026 09:34:04 +0900 Subject: [PATCH 33/34] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/cloudstack/backup/NASBackupProvider.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 951bbeda4e9b..c90fc4b3f604 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -273,7 +273,15 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU final VolumeVO volume = volumeDao.findByUuid(volumeUuid); final VirtualMachine backupSourceVm = vmInstanceDao.findById(backup.getVmId()); final StoragePoolHostVO dataStore = storagePoolHostDao.findByUuid(dataStoreUuid); - final HostVO hostVO = hostDao.findByIp(hostIp); + LOG.info("NASBackupProvider.java restoreBackedUpVolume:::::"); + LOG.info("hostIP: " +hostIp); + HostVO hostVO = hostDao.findByIp(hostIp); + LOG.info("hostVO: " +hostVO); + if(hostVO == null) { + LOG.info("hostVO == null"); + hostVO = hostDao.findByName(hostIp); + LOG.info("hostVO: " +hostVO); + } Optional matchingVolume = getBackedUpVolumeInfo(backupSourceVm.getBackupVolumeList(), volumeUuid); Long backedUpVolumeSize = matchingVolume.isPresent() ? matchingVolume.get().getSize() : 0L; From 105fec00a51f1630d13cfbba40fa5486224360e9 Mon Sep 17 00:00:00 2001 From: Dajeong-Park Date: Tue, 20 Jan 2026 10:01:31 +0900 Subject: [PATCH 34/34] =?UTF-8?q?=ED=92=80=20=EC=8A=A4=ED=86=A0=EB=A6=AC?= =?UTF-8?q?=EC=A7=80=20=EA=B2=BD=EB=A1=9C=20=EC=98=A4=EB=A5=98=20=EB=B0=8F?= =?UTF-8?q?=20=ED=98=B8=EC=8A=A4=ED=8A=B8VO=20=EB=84=90=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cloudstack/backup/NASBackupProvider.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index c90fc4b3f604..6113e2581008 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -27,10 +27,8 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.ScopeType; import com.cloud.storage.Storage; -import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.utils.Pair; import com.cloud.utils.component.AdapterBase; @@ -84,9 +82,6 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co @Inject private VolumeDao volumeDao; - @Inject - private StoragePoolHostDao storagePoolHostDao; - @Inject private VMInstanceDao vmInstanceDao; @@ -268,19 +263,27 @@ private List getVolumePaths(List volumes) { return volumePaths; } + private String getVolumePathPrefix(StoragePoolVO storagePool) { + String volumePathPrefix; + if (ScopeType.HOST.equals(storagePool.getScope()) || + Storage.StoragePoolType.SharedMountPoint.equals(storagePool.getPoolType()) || + Storage.StoragePoolType.RBD.equals(storagePool.getPoolType())) { + volumePathPrefix = storagePool.getPath(); + } else { + // Should be Storage.StoragePoolType.NetworkFilesystem + volumePathPrefix = String.format("/mnt/%s", storagePool.getUuid()); + } + return volumePathPrefix; + } + @Override public Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid, Pair vmNameAndState) { final VolumeVO volume = volumeDao.findByUuid(volumeUuid); final VirtualMachine backupSourceVm = vmInstanceDao.findById(backup.getVmId()); - final StoragePoolHostVO dataStore = storagePoolHostDao.findByUuid(dataStoreUuid); - LOG.info("NASBackupProvider.java restoreBackedUpVolume:::::"); - LOG.info("hostIP: " +hostIp); + final StoragePoolVO pool = primaryDataStoreDao.findByUuid(dataStoreUuid); HostVO hostVO = hostDao.findByIp(hostIp); - LOG.info("hostVO: " +hostVO); if(hostVO == null) { - LOG.info("hostVO == null"); hostVO = hostDao.findByName(hostIp); - LOG.info("hostVO: " +hostVO); } Optional matchingVolume = getBackedUpVolumeInfo(backupSourceVm.getBackupVolumeList(), volumeUuid); @@ -299,7 +302,7 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU restoredVolume.setUuid(volumeUUID); restoredVolume.setRemoved(null); restoredVolume.setDisplayVolume(true); - restoredVolume.setPoolId(dataStore.getPoolId()); + restoredVolume.setPoolId(pool.getId()); restoredVolume.setPath(restoredVolume.getUuid()); restoredVolume.setState(Volume.State.Copying); restoredVolume.setFormat(Storage.ImageFormat.QCOW2); @@ -311,7 +314,7 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU restoreCommand.setBackupRepoType(backupRepository.getType()); restoreCommand.setBackupRepoAddress(backupRepository.getAddress()); restoreCommand.setVmName(vmNameAndState.first()); - restoreCommand.setVolumePaths(Collections.singletonList(String.format("%s/%s", dataStore.getLocalPath(), volumeUUID))); + restoreCommand.setVolumePaths(Collections.singletonList(String.format("%s/%s", getVolumePathPrefix(pool), volumeUUID))); restoreCommand.setDiskType(volume.getVolumeType().name().toLowerCase(Locale.ROOT)); restoreCommand.setVmExists(null); restoreCommand.setVmState(vmNameAndState.second());