From 64db486b0e32fdd76481fae4d7f7b9c13a0c1a85 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 10 Sep 2024 13:26:00 -0400 Subject: [PATCH 01/16] NAS B&R Plugin enhancements --- .../cluster/KubernetesClusterManagerImpl.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index 39e4bbf935fc..139b5c79b802 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -1469,6 +1469,9 @@ public boolean deleteKubernetesCluster(DeleteKubernetesClusterCmd cmd) throws Cl } List vmMapList = kubernetesClusterVmMapDao.listByClusterId(kubernetesClusterId); + if (checkIfVmsAssociatedWithBackupOffering(vmMapList)) { + throw new CloudRuntimeException("Unable to delete Kubernetes cluster, as node(s) are associated to a backup offering"); + } for (KubernetesClusterVmMapVO vmMap : vmMapList) { try { userVmService.destroyVm(vmMap.getVmId(), expunge); @@ -1491,6 +1494,16 @@ public Boolean doInTransaction(TransactionStatus status) { } } + private boolean checkIfVmsAssociatedWithBackupOffering(List vmMapList) { + for(KubernetesClusterVmMapVO vmMap : vmMapList) { + VMInstanceVO vm = vmInstanceDao.findById(vmMap.getVmId()); + if (Objects.nonNull(vm.getBackupOfferingId())) { + return true; + } + } + return false; + } + @Override public ListResponse listKubernetesClusters(ListKubernetesClustersCmd cmd) { if (!KubernetesServiceEnabled.value()) { From d5a259da0a8205f65ebe82eaa832ee2aba2771f4 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 20 Sep 2024 10:44:04 -0400 Subject: [PATCH 02/16] Prevent printing mount opts which may include password by removing from response --- .../response/BackupRepositoryResponse.java | 12 ------ scripts/vm/hypervisor/kvm/nasbackup.sh | 6 +++ .../java/com/cloud/api/ApiResponseHelper.java | 1 - .../cloudstack/backup/BackupManagerImpl.java | 42 +++++++++++++++++++ tools/marvin/setup.py | 2 +- ui/src/config/section/config.js | 2 +- 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java index 3847176608c0..0db51f040349 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java @@ -57,10 +57,6 @@ public class BackupRepositoryResponse extends BaseResponse { @Param(description = "backup type") private String type; - @SerializedName(ApiConstants.MOUNT_OPTIONS) - @Param(description = "mount options for the backup repository") - private String mountOptions; - @SerializedName(ApiConstants.CAPACITY_BYTES) @Param(description = "capacity of the backup repository") private Long capacityBytes; @@ -112,14 +108,6 @@ public void setAddress(String address) { this.address = address; } - public String getMountOptions() { - return mountOptions; - } - - public void setMountOptions(String mountOptions) { - this.mountOptions = mountOptions; - } - public String getProviderName() { return providerName; } diff --git a/scripts/vm/hypervisor/kvm/nasbackup.sh b/scripts/vm/hypervisor/kvm/nasbackup.sh index 5b264321bd8d..8b6c0b8c52d7 100755 --- a/scripts/vm/hypervisor/kvm/nasbackup.sh +++ b/scripts/vm/hypervisor/kvm/nasbackup.sh @@ -100,6 +100,12 @@ mount_operation() { mount_point=$(mktemp -d -t csbackup.XXXXX) dest="$mount_point/${BACKUP_DIR}" mount -t ${NAS_TYPE} ${NAS_ADDRESS} ${mount_point} $([[ ! -z "${MOUNT_OPTS}" ]] && echo -o ${MOUNT_OPTS}) + if [ $? -eq 0 ]; then + echo "Successfully mounted ${NAS_TYPE} store" + else + echo "Failed to mount ${NAS_TYPE} store" + exit 1 + fi } function usage { diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 810f0abd7e00..76110dc58f76 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -5439,7 +5439,6 @@ public BackupRepositoryResponse createBackupRepositoryResponse(BackupRepository response.setAddress(backupRepository.getAddress()); response.setProviderName(backupRepository.getProvider()); response.setType(backupRepository.getType()); - response.setMountOptions(backupRepository.getMountOptions()); response.setCapacityBytes(backupRepository.getCapacityBytes()); response.setObjectName("backuprepository"); DataCenter zone = ApiDBUtils.findZoneById(backupRepository.getZoneId()); 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 b86b65bd465d..7509a4183b3f 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -19,24 +19,33 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.Date; 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; import java.util.stream.Collectors; import com.amazonaws.util.CollectionUtils; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VolumeApiService; +import com.cloud.storage.dao.VMTemplateDao; import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.NicVO; import com.cloud.vm.UserVmManager; +import com.cloud.vm.UserVmService; import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachineManager; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.vm.dao.NicDao; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.backup.DeleteBackupOfferingCmd; @@ -166,6 +175,14 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private VolumeApiService volumeApiService; @Inject private VolumeOrchestrationService volumeOrchestrationService; + @Inject + private VMTemplateDao templateDao; + @Inject + private NicDao nicDao; + @Inject + private NetworkDao networkDao; + @Inject + protected UserVmService userVmService; private AsyncJobDispatcher asyncJobDispatcher; private Timer backupTimer; @@ -645,6 +662,9 @@ public boolean restoreBackup(final Long backupId) { !vm.getState().equals(VirtualMachine.State.Destroyed)) { throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); } + if (VirtualMachine.State.Expunging.equals(vm.getState()) && vm.getRemoved() != null) { + restoreExpungedVm(vm); + } // This is done to handle historic backups if any with Veeam / Networker plugins List backupVolumes = CollectionUtils.isNullOrEmpty(backup.getBackedUpVolumes()) ? vm.getBackupVolumeList() : backup.getBackedUpVolumes(); @@ -705,6 +725,28 @@ protected void tryRestoreVM(BackupVO backup, VMInstanceVO vm, BackupOffering off } } + private void restoreExpungedVm(VMInstanceVO vm) { + VMTemplateVO template = templateDao.findById(vm.getTemplateId()); + if (Objects.isNull(template)) { + throw new CloudRuntimeException("Failed to find VM template to restore the VM"); + } + List networkIds = nicDao.listByVmId(vm.getId()).stream().sorted(Comparator.comparing(NicVO::getDeviceId)).map(NicVO::getNetworkId).collect(Collectors.toList()); + for (Long networkId : networkIds) { + NetworkVO networkVO = networkDao.findById(networkId); + if (Objects.isNull(networkVO)) { + throw new CloudRuntimeException("Failed to find network all networks the VM was previously attached to"); + } + } + + CallContext ctx = CallContext.current(); + final Account owner = ctx.getCallingAccount(); +// userVmService.createAdvancedVirtualMachine(vm.getDataCenterId(), vm.getServiceOfferingId(), template.getId(), networkIds, owner, +// vm.getHostName(), vm.getHostName(), null, null, null, +// Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, null, null, null, keypairs, +// requestedIps, addrs, null, null, null, customParameterMap, null, null, null, null, true, null, null); + + } + /** * Tries to update the state of given VM, given specified event * @param vm The VM to update its state diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py index 0618d84370a4..679b1d5920d8 100644 --- a/tools/marvin/setup.py +++ b/tools/marvin/setup.py @@ -27,7 +27,7 @@ raise RuntimeError("python setuptools is required to build Marvin") -VERSION = "4.20.0.0-SNAPSHOT" +VERSION = "4.20.0.0" setup(name="Marvin", version=VERSION, diff --git a/ui/src/config/section/config.js b/ui/src/config/section/config.js index 1736adf79c4f..2a4dbb84a6ec 100644 --- a/ui/src/config/section/config.js +++ b/ui/src/config/section/config.js @@ -151,7 +151,7 @@ export default { ], mapping: { type: { - options: ['nfs'] + options: ['nfs', 'cifs'] }, provider: { value: (record) => { return 'nas' } From 6dfb0ea2ceda4cc5be84771f892f7ffd04a96abf Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 20 Sep 2024 12:08:40 -0400 Subject: [PATCH 03/16] revert marvin change --- tools/marvin/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py index 679b1d5920d8..0618d84370a4 100644 --- a/tools/marvin/setup.py +++ b/tools/marvin/setup.py @@ -27,7 +27,7 @@ raise RuntimeError("python setuptools is required to build Marvin") -VERSION = "4.20.0.0" +VERSION = "4.20.0.0-SNAPSHOT" setup(name="Marvin", version=VERSION, From b1ccf9d44328239807fd8a1905523423a3c902e3 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 23 Sep 2024 10:11:56 -0400 Subject: [PATCH 04/16] add sanity checks to validate minimum qemu and libvirt versions --- scripts/vm/hypervisor/kvm/nasbackup.sh | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/scripts/vm/hypervisor/kvm/nasbackup.sh b/scripts/vm/hypervisor/kvm/nasbackup.sh index 8b6c0b8c52d7..ed98db53db78 100755 --- a/scripts/vm/hypervisor/kvm/nasbackup.sh +++ b/scripts/vm/hypervisor/kvm/nasbackup.sh @@ -32,6 +32,46 @@ MOUNT_OPTS="" BACKUP_DIR="" DISK_PATHS="" +vercomp() { + local IFS=. + local i ver1=($1) ver2=($3) + + # Compare each segment of the version numbers + for ((i=0; i<${#ver1[@]}; i++)); do + if [[ -z ${ver2[i]} ]]; then + ver2[i]=0 + fi + + if ((10#${ver1[i]} > 10#${ver2[i]})); then + return 1 # Version 1 is greater + elif ((10#${ver1[i]} < 10#${ver2[i]})); then + return 2 # Version 2 is greater + fi + done + return 0 # Versions are equal +} + +sanity_checks() { + hvVersion=$(virsh version | grep hypervisor | awk '{print $(NF)}') + libvVersion=$(virsh version | grep libvirt | awk '{print $(NF)}' | tail -n 1) + apiVersion=$(virsh version | grep API | awk '{print $(NF)}') + + # Compare qemu version (hvVersion >= 4.2.0) + vercomp "$hvVersion" ">=" "4.2.0" + hvStatus=$? + + # Compare libvirt version (libvVersion >= 7.2.0) + vercomp "$libvVersion" ">=" "7.2.0" + libvStatus=$? + + if [[ ($hvStatus -eq 0 || $hvStatus -eq 1) && ($libvStatus -eq 0 || $libvStatus -eq 1) ]]; then + echo "Success \t\t [ QEMU: $hvVersion Libvirt: $libvVersion apiVersion: $apiVersion ]" + else + echo "Failure... \tYour QEMU version $hvVersion or libvirt version $libvVersion is unsupported. Consider upgrading to the required minimum version of QEMU: 4.2.0 and Libvirt: 7.2.0" + exit 1 + fi +} + ### Operation methods ### backup_running_vm() { @@ -163,6 +203,9 @@ while [[ $# -gt 0 ]]; do esac done +# Perform Initial sanity checks +sanity_checks + if [ "$OP" = "backup" ]; then STATE=$(virsh -c qemu:///system list | grep $VM | awk '{print $3}') if [ "$STATE" = "running" ]; then From 343e7dca2740f22ce8415276b90ace1195204737 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 23 Sep 2024 10:17:02 -0400 Subject: [PATCH 05/16] check is user running script is part of libvirt group --- scripts/vm/hypervisor/kvm/nasbackup.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/vm/hypervisor/kvm/nasbackup.sh b/scripts/vm/hypervisor/kvm/nasbackup.sh index ed98db53db78..b55ec4d616df 100755 --- a/scripts/vm/hypervisor/kvm/nasbackup.sh +++ b/scripts/vm/hypervisor/kvm/nasbackup.sh @@ -65,11 +65,20 @@ sanity_checks() { libvStatus=$? if [[ ($hvStatus -eq 0 || $hvStatus -eq 1) && ($libvStatus -eq 0 || $libvStatus -eq 1) ]]; then - echo "Success \t\t [ QEMU: $hvVersion Libvirt: $libvVersion apiVersion: $apiVersion ]" + echo "Success... [ QEMU: $hvVersion Libvirt: $libvVersion apiVersion: $apiVersion ]" else - echo "Failure... \tYour QEMU version $hvVersion or libvirt version $libvVersion is unsupported. Consider upgrading to the required minimum version of QEMU: 4.2.0 and Libvirt: 7.2.0" + echo "Failure... Your QEMU version $hvVersion or libvirt version $libvVersion is unsupported. Consider upgrading to the required minimum version of QEMU: 4.2.0 and Libvirt: 7.2.0" exit 1 fi + + echo "Checking Permissions" + if groups $USER | grep -q '\blibvirt\b'; then + echo "Success... User $USER is part of libvirt group" + else + echo "Failure - User $USER is not part of libvirt group" + exit 1 + fi + echo "Environment Sanity Checks successfully passed" } ### Operation methods ### From f2f81c82f4d998594fbe28ba9b3a083328c0b244 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 23 Sep 2024 10:40:21 -0400 Subject: [PATCH 06/16] revert changes of retore expunged VM --- .../cloudstack/backup/BackupManagerImpl.java | 48 +------------------ 1 file changed, 1 insertion(+), 47 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 b39230f21ad4..8430a59f5cef 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -19,33 +19,22 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.Date; 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; import java.util.stream.Collectors; import com.amazonaws.util.CollectionUtils; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VolumeApiService; -import com.cloud.storage.dao.VMTemplateDao; import com.cloud.utils.fsm.NoTransitionException; -import com.cloud.vm.NicVO; -import com.cloud.vm.UserVmManager; -import com.cloud.vm.UserVmService; -import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachineManager; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.vm.dao.NicDao; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.backup.DeleteBackupOfferingCmd; @@ -72,7 +61,6 @@ import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; 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.jobs.AsyncJobDispatcher; import org.apache.cloudstack.framework.jobs.AsyncJobManager; @@ -173,16 +161,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private VirtualMachineManager virtualMachineManager; @Inject private VolumeApiService volumeApiService; - @Inject - private VolumeOrchestrationService volumeOrchestrationService; - @Inject - private VMTemplateDao templateDao; - @Inject - private NicDao nicDao; - @Inject - private NetworkDao networkDao; - @Inject - protected UserVmService userVmService; private AsyncJobDispatcher asyncJobDispatcher; private Timer backupTimer; @@ -642,9 +620,7 @@ public boolean restoreBackup(final Long backupId) { !vm.getState().equals(VirtualMachine.State.Destroyed)) { throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); } - if (VirtualMachine.State.Expunging.equals(vm.getState()) && vm.getRemoved() != null) { - restoreExpungedVm(vm); - } + // This is done to handle historic backups if any with Veeam / Networker plugins List backupVolumes = CollectionUtils.isNullOrEmpty(backup.getBackedUpVolumes()) ? vm.getBackupVolumeList() : backup.getBackedUpVolumes(); @@ -705,28 +681,6 @@ protected void tryRestoreVM(BackupVO backup, VMInstanceVO vm, BackupOffering off } } - private void restoreExpungedVm(VMInstanceVO vm) { - VMTemplateVO template = templateDao.findById(vm.getTemplateId()); - if (Objects.isNull(template)) { - throw new CloudRuntimeException("Failed to find VM template to restore the VM"); - } - List networkIds = nicDao.listByVmId(vm.getId()).stream().sorted(Comparator.comparing(NicVO::getDeviceId)).map(NicVO::getNetworkId).collect(Collectors.toList()); - for (Long networkId : networkIds) { - NetworkVO networkVO = networkDao.findById(networkId); - if (Objects.isNull(networkVO)) { - throw new CloudRuntimeException("Failed to find network all networks the VM was previously attached to"); - } - } - - CallContext ctx = CallContext.current(); - final Account owner = ctx.getCallingAccount(); -// userVmService.createAdvancedVirtualMachine(vm.getDataCenterId(), vm.getServiceOfferingId(), template.getId(), networkIds, owner, -// vm.getHostName(), vm.getHostName(), null, null, null, -// Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, null, null, null, keypairs, -// requestedIps, addrs, null, null, null, customParameterMap, null, null, null, null, true, null, null); - - } - /** * Tries to update the state of given VM, given specified event * @param vm The VM to update its state From be9eba39c9037c89116ab2f41ae0786e69aa9920 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 30 Sep 2024 07:55:46 -0400 Subject: [PATCH 07/16] add code coverage ignore file --- codecov.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000000..a9c6aca16918 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,3 @@ +ignore: + - api/src/main/java/org/apache/cloudstack/api/response/* + - plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/* From 585126b4b1cfed668e5b73b1fa4df643a9d2c6ad Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 30 Sep 2024 09:50:58 -0400 Subject: [PATCH 08/16] remove check --- scripts/vm/hypervisor/kvm/nasbackup.sh | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scripts/vm/hypervisor/kvm/nasbackup.sh b/scripts/vm/hypervisor/kvm/nasbackup.sh index b55ec4d616df..dad94f33bc52 100755 --- a/scripts/vm/hypervisor/kvm/nasbackup.sh +++ b/scripts/vm/hypervisor/kvm/nasbackup.sh @@ -71,14 +71,7 @@ sanity_checks() { exit 1 fi - echo "Checking Permissions" - if groups $USER | grep -q '\blibvirt\b'; then - echo "Success... User $USER is part of libvirt group" - else - echo "Failure - User $USER is not part of libvirt group" - exit 1 - fi - echo "Environment Sanity Checks successfully passed" + echo "Environment Sanity Checks successfully passed" } ### Operation methods ### From d024a964c41d4196c845f87f27dc09ae324bc4ab Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 30 Sep 2024 10:57:30 -0400 Subject: [PATCH 09/16] issue with listing schedules and add defensive checks --- .../api/command/user/backup/ListBackupScheduleCmd.java | 2 +- .../org/apache/cloudstack/backup/NASBackupProvider.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java index a76107174358..fa6e3ea5d455 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java @@ -82,7 +82,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE List schedules = backupManager.listBackupSchedule(getVmId()); ListResponse response = new ListResponse<>(); List scheduleResponses = new ArrayList<>(); - if (CollectionUtils.isNullOrEmpty(schedules)) { + if (!CollectionUtils.isNullOrEmpty(schedules)) { for (BackupSchedule schedule : schedules) { scheduleResponses.add(_responseGenerator.createBackupScheduleResponse(schedule)); } 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 4a6725abdca5..f900125a462f 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 @@ -373,8 +373,12 @@ public Map getBackupMetrics(Long zoneId, List Date: Mon, 30 Sep 2024 23:10:07 -0400 Subject: [PATCH 10/16] redirect logs to agent log file --- scripts/vm/hypervisor/kvm/nasbackup.sh | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts/vm/hypervisor/kvm/nasbackup.sh b/scripts/vm/hypervisor/kvm/nasbackup.sh index dad94f33bc52..91b3e5bada5e 100755 --- a/scripts/vm/hypervisor/kvm/nasbackup.sh +++ b/scripts/vm/hypervisor/kvm/nasbackup.sh @@ -31,6 +31,16 @@ NAS_ADDRESS="" MOUNT_OPTS="" BACKUP_DIR="" DISK_PATHS="" +logFile="/var/log/cloudstack/agent/agent.log" + +log() { + [[ "$verb" -eq 1 ]] && builtin echo "$@" + if [[ "$1" == "-ne" || "$1" == "-e" || "$1" == "-n" ]]; then + builtin echo -e "$(date '+%Y-%m-%d %H-%M-%S>')" "${@: 2}" >> "$logFile" + else + builtin echo "$(date '+%Y-%m-%d %H-%M-%S>')" "$@" >> "$logFile" + fi +} vercomp() { local IFS=. @@ -43,7 +53,7 @@ vercomp() { fi if ((10#${ver1[i]} > 10#${ver2[i]})); then - return 1 # Version 1 is greater + return 0 # Version 1 is greater elif ((10#${ver1[i]} < 10#${ver2[i]})); then return 2 # Version 2 is greater fi @@ -64,14 +74,14 @@ sanity_checks() { vercomp "$libvVersion" ">=" "7.2.0" libvStatus=$? - if [[ ($hvStatus -eq 0 || $hvStatus -eq 1) && ($libvStatus -eq 0 || $libvStatus -eq 1) ]]; then - echo "Success... [ QEMU: $hvVersion Libvirt: $libvVersion apiVersion: $apiVersion ]" + if [[ $hvStatus -eq 0 && $libvStatus -eq 0 ]]; then + log -ne "Success... [ QEMU: $hvVersion Libvirt: $libvVersion apiVersion: $apiVersion ]" else echo "Failure... Your QEMU version $hvVersion or libvirt version $libvVersion is unsupported. Consider upgrading to the required minimum version of QEMU: 4.2.0 and Libvirt: 7.2.0" exit 1 fi - echo "Environment Sanity Checks successfully passed" + log -ne "Environment Sanity Checks successfully passed" } ### Operation methods ### @@ -143,7 +153,7 @@ mount_operation() { dest="$mount_point/${BACKUP_DIR}" mount -t ${NAS_TYPE} ${NAS_ADDRESS} ${mount_point} $([[ ! -z "${MOUNT_OPTS}" ]] && echo -o ${MOUNT_OPTS}) if [ $? -eq 0 ]; then - echo "Successfully mounted ${NAS_TYPE} store" + log -ne "Successfully mounted ${NAS_TYPE} store" else echo "Failed to mount ${NAS_TYPE} store" exit 1 From 0760ef596873b465dbea5af9fbf0b9f562336ff9 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 1 Oct 2024 00:04:39 -0400 Subject: [PATCH 11/16] add some more debugging --- scripts/vm/hypervisor/kvm/nasbackup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vm/hypervisor/kvm/nasbackup.sh b/scripts/vm/hypervisor/kvm/nasbackup.sh index 91b3e5bada5e..a128915e2c24 100755 --- a/scripts/vm/hypervisor/kvm/nasbackup.sh +++ b/scripts/vm/hypervisor/kvm/nasbackup.sh @@ -131,7 +131,7 @@ backup_stopped_vm() { name="root" for disk in $DISK_PATHS; do volUuid="${disk##*/}" - qemu-img convert -O qcow2 $disk $dest/$name.$volUuid.qcow2 + qemu-img convert -O qcow2 $disk $dest/$name.$volUuid.qcow2 | tee -a "$logFile" name="datadisk" done sync @@ -151,7 +151,7 @@ delete_backup() { mount_operation() { mount_point=$(mktemp -d -t csbackup.XXXXX) dest="$mount_point/${BACKUP_DIR}" - mount -t ${NAS_TYPE} ${NAS_ADDRESS} ${mount_point} $([[ ! -z "${MOUNT_OPTS}" ]] && echo -o ${MOUNT_OPTS}) + mount -t ${NAS_TYPE} ${NAS_ADDRESS} ${mount_point} $([[ ! -z "${MOUNT_OPTS}" ]] && echo -o ${MOUNT_OPTS}) | tee -a "$logFile" if [ $? -eq 0 ]; then log -ne "Successfully mounted ${NAS_TYPE} store" else From bf19dea4920894090b035f329a739e0702f26e18 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 1 Oct 2024 00:07:20 -0400 Subject: [PATCH 12/16] remove test file --- codecov.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index a9c6aca16918..000000000000 --- a/codecov.yml +++ /dev/null @@ -1,3 +0,0 @@ -ignore: - - api/src/main/java/org/apache/cloudstack/api/response/* - - plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/* From 5bfed063f6d9fa35a9c130dddcc336775ebeeafe Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 1 Oct 2024 09:58:59 -0400 Subject: [PATCH 13/16] prevent deletion of cks cluster when vms associated to a backup offering --- .../kubernetes/cluster/KubernetesClusterManagerImpl.java | 9 +++++---- .../actionworkers/KubernetesClusterDestroyWorker.java | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index 7655de5afa58..77d90c14b3e7 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -36,6 +36,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -1468,7 +1469,8 @@ public boolean deleteKubernetesCluster(DeleteKubernetesClusterCmd cmd) throws Cl } List vmMapList = kubernetesClusterVmMapDao.listByClusterId(kubernetesClusterId); - if (checkIfVmsAssociatedWithBackupOffering(vmMapList)) { + List vms = vmMapList.stream().map(vmMap -> vmInstanceDao.findById(vmMap.getVmId())).collect(Collectors.toList()); + if (checkIfVmsAssociatedWithBackupOffering(vms)) { throw new CloudRuntimeException("Unable to delete Kubernetes cluster, as node(s) are associated to a backup offering"); } for (KubernetesClusterVmMapVO vmMap : vmMapList) { @@ -1493,9 +1495,8 @@ public Boolean doInTransaction(TransactionStatus status) { } } - private boolean checkIfVmsAssociatedWithBackupOffering(List vmMapList) { - for(KubernetesClusterVmMapVO vmMap : vmMapList) { - VMInstanceVO vm = vmInstanceDao.findById(vmMap.getVmId()); + public static boolean checkIfVmsAssociatedWithBackupOffering(List vms) { + for(VMInstanceVO vm : vms) { if (Objects.nonNull(vm.getBackupOfferingId())) { return true; } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java index 50d7fb14085a..cef2e91042ac 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java @@ -245,6 +245,10 @@ public boolean destroy() throws CloudRuntimeException { init(); validateClusterSate(); this.clusterVMs = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId()); + List vms = this.clusterVMs.stream().map(vmMap -> vmInstanceDao.findById(vmMap.getVmId())).collect(Collectors.toList()); + if (KubernetesClusterManagerImpl.checkIfVmsAssociatedWithBackupOffering(vms)) { + throw new CloudRuntimeException("Unable to delete Kubernetes cluster, as node(s) are associated to a backup offering"); + } boolean cleanupNetwork = true; final KubernetesClusterDetailsVO clusterDetails = kubernetesClusterDetailsDao.findDetail(kubernetesCluster.getId(), "networkCleanup"); if (clusterDetails != null) { From 9aea6335d11b07e644588ae2a30533cd568339b9 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 3 Oct 2024 13:44:06 -0400 Subject: [PATCH 14/16] B&R Framework: Support account / domain backup limits --- .../com/cloud/configuration/Resource.java | 3 +- .../apache/cloudstack/api/ApiConstants.java | 2 + .../command/user/backup/CreateBackupCmd.java | 19 ++- .../user/backup/CreateBackupScheduleCmd.java | 7 + .../user/backup/DeleteBackupScheduleCmd.java | 13 +- .../api/response/AccountResponse.java | 27 ++++ .../api/response/DomainResponse.java | 24 ++++ .../api/response/ProjectResponse.java | 27 ++++ .../ResourceLimitAndCountResponse.java | 6 + .../org/apache/cloudstack/backup/Backup.java | 22 ++++ .../cloudstack/backup/BackupManager.java | 9 +- .../cloudstack/backup/BackupProvider.java | 2 +- .../cloudstack/backup/BackupSchedule.java | 1 + .../cloudstack/backup/BackupScheduleVO.java | 11 ++ .../apache/cloudstack/backup/BackupVO.java | 11 ++ .../cloudstack/backup/dao/BackupDao.java | 1 + .../cloudstack/backup/dao/BackupDaoImpl.java | 17 +++ .../META-INF/db/schema-41910to42000.sql | 2 + .../backup/DummyBackupProvider.java | 4 +- .../cloudstack/backup/NASBackupProvider.java | 12 +- .../backup/NetworkerBackupProvider.java | 6 +- .../cloud/api/query/ViewResponseHelper.java | 7 + .../api/query/dao/AccountJoinDaoImpl.java | 8 ++ .../api/query/dao/DomainJoinDaoImpl.java | 8 ++ .../com/cloud/api/query/vo/AccountJoinVO.java | 14 ++ .../com/cloud/api/query/vo/DomainJoinVO.java | 19 +++ .../java/com/cloud/configuration/Config.java | 17 +++ .../ResourceLimitManagerImpl.java | 8 ++ .../storage/snapshot/SnapshotManager.java | 10 ++ .../storage/snapshot/SnapshotManagerImpl.java | 9 +- .../cloudstack/backup/BackupManagerImpl.java | 124 ++++++++++++++++-- 31 files changed, 421 insertions(+), 29 deletions(-) diff --git a/api/src/main/java/com/cloud/configuration/Resource.java b/api/src/main/java/com/cloud/configuration/Resource.java index bf8fca9d9051..e04a22e12041 100644 --- a/api/src/main/java/com/cloud/configuration/Resource.java +++ b/api/src/main/java/com/cloud/configuration/Resource.java @@ -33,7 +33,8 @@ enum ResourceType { // Primary and Secondary storage are allocated_storage and n cpu("cpu", 8), memory("memory", 9), primary_storage("primary_storage", 10), - secondary_storage("secondary_storage", 11); + secondary_storage("secondary_storage", 11), + backup("backup", 12); private String name; private int ordinal; diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index bb16b0ff90de..cee5b5782607 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -320,6 +320,7 @@ public class ApiConstants { public static final String MAC_ADDRESS = "macaddress"; public static final String MAX = "max"; public static final String MAX_SNAPS = "maxsnaps"; + public static final String MAX_BACKUPS = "maxbackups"; public static final String MAX_CPU_NUMBER = "maxcpunumber"; public static final String MAX_MEMORY = "maxmemory"; public static final String MIN_CPU_NUMBER = "mincpunumber"; @@ -426,6 +427,7 @@ public class ApiConstants { public static final String QUALIFIERS = "qualifiers"; public static final String QUERY_FILTER = "queryfilter"; public static final String SCHEDULE = "schedule"; + public static final String SCHEDULE_ID = "scheduleid"; public static final String SCOPE = "scope"; public static final String SEARCH_BASE = "searchbase"; public static final String SECONDARY_IP = "secondaryip"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java index 558f92e4006d..1ed2584ca00e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java @@ -19,6 +19,7 @@ import javax.inject.Inject; +import com.cloud.storage.Snapshot; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -27,6 +28,7 @@ import org.apache.cloudstack.api.BaseAsyncCreateCmd; 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; @@ -60,6 +62,13 @@ public class CreateBackupCmd extends BaseAsyncCreateCmd { description = "ID of the VM") private Long vmId; + @Parameter(name = ApiConstants.SCHEDULE_ID, + type = CommandType.UUID, + entityType = BackupScheduleResponse.class, + description = "backup schedule ID of the VM, if this is null, it indicates that it is a manual backup.", + since = "4.20.1") + private Long scheduleId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -68,6 +77,14 @@ public Long getVmId() { return vmId; } + public Long getScheduleId() { + if (scheduleId != null) { + return scheduleId; + } else { + return Snapshot.MANUAL_POLICY_ID; + } + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -75,7 +92,7 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.createBackup(getVmId()); + boolean result = backupManager.createBackup(getVmId(), getScheduleId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java index 5dc06af21231..90b9b9912e95 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -36,6 +36,8 @@ import com.cloud.utils.DateUtil; import com.cloud.utils.exception.CloudRuntimeException; +import java.util.Objects; + @APICommand(name = "createBackupSchedule", description = "Creates a user-defined VM backup schedule", responseObject = BackupResponse.class, since = "4.14.0", @@ -75,6 +77,9 @@ public class CreateBackupScheduleCmd extends BaseCmd { description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.") private String timezone; + @Parameter(name = ApiConstants.MAX_BACKUPS, type = CommandType.INTEGER, required = true, description = "maximum number of backups to retain", since = "4.20.1") + private Integer maxBackups; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -95,6 +100,8 @@ public String getTimezone() { return timezone; } + public Integer getMaxBackups() { return Objects.nonNull(maxBackups) ? maxBackups : 0; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// 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..0679821a051d 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 @@ -29,6 +29,7 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.context.CallContext; import com.cloud.exception.ConcurrentOperationException; @@ -58,6 +59,14 @@ public class DeleteBackupScheduleCmd extends BaseCmd { description = "ID of the VM") private Long vmId; + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupSchedule.class, + required = true, + description = "ID of the schedule", + since = "4.20.1") + private Long id; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -66,6 +75,8 @@ public Long getVmId() { return vmId; } + public Long getId() { return id; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -73,7 +84,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/api/response/AccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java index 7a84e85a4a6f..67a3c5165983 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java @@ -127,6 +127,18 @@ public class AccountResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "the total number of snapshots available for this account") private String snapshotAvailable; + @SerializedName("backuplimit") + @Param(description = "the total number of backups which can be stored by this account") + private String backupLimit; + + @SerializedName("backuptotal") + @Param(description = "the total number of backups stored by this account") + private Long backupTotal; + + @SerializedName("backupvailable") + @Param(description = "the total number of backups available for this account") + private String backupAvailable; + @SerializedName("templatelimit") @Param(description = "the total number of templates which can be created by this account") private String templateLimit; @@ -382,6 +394,21 @@ public void setSnapshotAvailable(String snapshotAvailable) { this.snapshotAvailable = snapshotAvailable; } + @Override + public void setBackupLimit(String backupLimit) { + this.backupLimit = backupLimit; + } + + @Override + public void setBackupTotal(Long backupTotal) { + this.backupTotal = backupTotal; + } + + @Override + public void setBackupAvailable(String backupAvailable) { + this.backupAvailable = backupAvailable; + } + @Override public void setTemplateLimit(String templateLimit) { this.templateLimit = templateLimit; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java index 7c6ad3a91c38..afd811d252d4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java @@ -105,6 +105,15 @@ public class DomainResponse extends BaseResponseWithAnnotations implements Resou @SerializedName("snapshotavailable") @Param(description="the total number of snapshots available for this domain") private String snapshotAvailable; + @SerializedName("backuplimit") @Param(description="the total number of backups which can be stored by this domain") + private String backupLimit; + + @SerializedName("backuptotal") @Param(description="the total number of backups stored by this domain") + private Long backupTotal; + + @SerializedName("backupavailable") @Param(description="the total number of backups available for this domain") + private String backupAvailable; + @SerializedName("templatelimit") @Param(description="the total number of templates which can be created by this domain") private String templateLimit; @@ -313,6 +322,21 @@ public void setSnapshotAvailable(String snapshotAvailable) { this.snapshotAvailable = snapshotAvailable; } + @Override + public void setBackupLimit(String backupLimit) { + this.backupLimit = backupLimit; + } + + @Override + public void setBackupTotal(Long backupTotal) { + this.backupTotal = backupTotal; + } + + @Override + public void setBackupAvailable(String backupAvailable) { + this.backupAvailable = backupAvailable; + } + @Override public void setTemplateLimit(String templateLimit) { this.templateLimit = templateLimit; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java index 1c63697559b5..37837d9608b4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java @@ -188,6 +188,18 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "the total number of snapshots available for this project", since = "4.2.0") private String snapshotAvailable; + @SerializedName("backuplimit") + @Param(description = "the total number of backups which can be stored by this project", since = "4.20.1") + private String backupLimit; + + @SerializedName("backuptotal") + @Param(description = "the total number of backups stored by this project", since = "4.20.1") + private Long backupTotal; + + @SerializedName("backupavailable") + @Param(description = "the total number of backups available for this project", since = "4.20.1") + private String backupAvailable; + @SerializedName("templatelimit") @Param(description = "the total number of templates which can be created by this project", since = "4.2.0") private String templateLimit; @@ -320,6 +332,21 @@ public void setSnapshotAvailable(String snapshotAvailable) { this.snapshotAvailable = snapshotAvailable; } + @Override + public void setBackupLimit(String backupLimit) { + this.backupLimit = backupLimit; + } + + @Override + public void setBackupTotal(Long backupTotal) { + this.backupTotal = backupTotal; + } + + @Override + public void setBackupAvailable(String backupAvailable) { + this.backupAvailable = backupAvailable; + } + @Override public void setTemplateLimit(String templateLimit) { this.templateLimit = templateLimit; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java index f9e6df3a0386..e679755c8354 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java @@ -84,6 +84,12 @@ public interface ResourceLimitAndCountResponse { public void setSnapshotAvailable(String snapshotAvailable); + public void setBackupLimit(String backupLimit); + + public void setBackupTotal(Long backupTotal); + + public void setBackupAvailable(String backupAvailable); + public void setTemplateLimit(String templateLimit); public void setTemplateTotal(Long templateTotal); diff --git a/api/src/main/java/org/apache/cloudstack/backup/Backup.java b/api/src/main/java/org/apache/cloudstack/backup/Backup.java index f21f20adb33e..dffe8a032134 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -33,6 +33,28 @@ enum Status { Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged } + public enum Type { + MANUAL, HOURLY, DAILY, WEEKLY, MONTHLY; + private int max = 8; + + public void setMax(int max) { + this.max = max; + } + + public int getMax() { + return max; + } + + @Override + public String toString() { + return this.name(); + } + + public boolean equals(String snapshotType) { + return this.toString().equalsIgnoreCase(snapshotType); + } + } + class Metric { private Long backupSize = 0L; private Long dataSize = 0L; 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 8b45bb4ee5ef..d8271fb88699 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -19,9 +19,11 @@ import java.util.List; +import com.cloud.exception.ResourceAllocationException; 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; @@ -111,17 +113,18 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer /** * 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 * @param vmId Virtual Machine ID + * @param scheduleId Virtual Machine Backup Schedule ID * @return returns operation success */ - boolean createBackup(final Long vmId); + boolean createBackup(final Long vmId, final Long scheduleId) throws ResourceAllocationException; /** * List existing backups for a VM diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java index d36dfb7360f6..aed0ac861174 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -75,7 +75,7 @@ public interface BackupProvider { * @param backup * @return */ - boolean takeBackup(VirtualMachine vm); + Backup takeBackup(VirtualMachine vm); /** * Delete an existing backup 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 d81dd731b1ff..da90da508fff 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java @@ -30,4 +30,5 @@ public interface BackupSchedule extends InternalIdentity { String getTimezone(); Date getScheduledTimestamp(); Long getAsyncJobId(); + int getMaxBackups(); } 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 ba31dc59d390..89a28be77d2c 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 @@ -57,6 +57,9 @@ public class BackupScheduleVO implements BackupSchedule { @Column(name = "async_job_id") Long asyncJobId; + @Column(name = "max_backups") + int maxBackups = 0; + public BackupScheduleVO() { } @@ -121,4 +124,12 @@ public Long getAsyncJobId() { public void setAsyncJobId(Long asyncJobId) { this.asyncJobId = asyncJobId; } + + public int getMaxBackups() { + return maxBackups; + } + + public void setMaxBackups(int maxBackups) { + this.maxBackups = maxBackups; + } } 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 9b285e66cab9..b9dd9809a24f 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 @@ -87,6 +87,9 @@ public class BackupVO implements Backup { @Column(name = "zone_id") private long zoneId; + @Column(name = "backup_interval_type") + private short backupIntervalType; + @Column(name = "backed_volumes", length = 65535) protected String backedUpVolumes; @@ -201,6 +204,14 @@ public void setZoneId(long zoneId) { this.zoneId = zoneId; } + public short getBackupIntervalType() { + return backupIntervalType; + } + + public void setBackupIntervalType(short backupIntervalType) { + this.backupIntervalType = backupIntervalType; + } + @Override public Class getEntityType() { return Backup.class; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java index 89a13245b0a0..ac211a745d48 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java @@ -36,4 +36,5 @@ public interface BackupDao extends GenericDao { BackupVO getBackupVO(Backup backup); List listByOfferingId(Long backupOfferingId); BackupResponse newBackupResponse(Backup backup); + public Long countBackupsForAccount(long accountId); } 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 5a9cd0620374..b67a5ad54a4f 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 @@ -24,6 +24,7 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; +import com.cloud.utils.db.GenericSearchBuilder; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupOffering; @@ -60,6 +61,7 @@ public class BackupDaoImpl extends GenericDaoBase implements Bac BackupOfferingDao backupOfferingDao; private SearchBuilder backupSearch; + private GenericSearchBuilder CountBackupsByAccount; public BackupDaoImpl() { } @@ -72,6 +74,13 @@ protected void init() { backupSearch.and("backup_offering_id", backupSearch.entity().getBackupOfferingId(), SearchCriteria.Op.EQ); backupSearch.and("zone_id", backupSearch.entity().getZoneId(), SearchCriteria.Op.EQ); backupSearch.done(); + + CountBackupsByAccount = createSearchBuilder(Long.class); + CountBackupsByAccount.select(null, SearchCriteria.Func.COUNT, null); + CountBackupsByAccount.and("account", CountBackupsByAccount.entity().getAccountId(), SearchCriteria.Op.EQ); + CountBackupsByAccount.and("status", CountBackupsByAccount.entity().getStatus(), SearchCriteria.Op.NIN); + CountBackupsByAccount.and("removed", CountBackupsByAccount.entity().getRemoved(), SearchCriteria.Op.NULL); + CountBackupsByAccount.done(); } @Override @@ -142,6 +151,14 @@ public List syncBackups(Long zoneId, Long vmId, List externalBac return listByVmId(zoneId, vmId); } + @Override + public Long countBackupsForAccount(long accountId) { + SearchCriteria sc = CountBackupsByAccount.create(); + sc.setParameters("account", accountId); + sc.setParameters("status", Backup.Status.Error, Backup.Status.Failed, Backup.Status.Removed, Backup.Status.Expunged); + return customSearch(sc, null).get(0); + } + @Override public BackupResponse newBackupResponse(Backup backup) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql index c36b71c2f250..899866c9d5ff 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql @@ -412,6 +412,8 @@ CREATE TABLE `cloud`.`backup_repository` ( ALTER TABLE `cloud`.`backup_schedule` DROP FOREIGN KEY fk_backup_schedule__vm_id; ALTER TABLE `cloud`.`backup_schedule` DROP INDEX vm_id; ALTER TABLE `cloud`.`backup_schedule` ADD CONSTRAINT fk_backup_schedule__vm_id FOREIGN KEY (vm_id) REFERENCES vm_instance(id) ON DELETE CASCADE; +ALTER TABLE `cloud`.`backup_schedule` ADD COLUMN `max_backups` int(8) NOT NULL default 0 COMMENT 'maximum number of backups to maintain'; +ALTER TABLE `cloud`.`backup_schedule` ADD COLUMN `backup_interval_type` int(4) COMMENT 'type of backup, e.g. manual, recurring - hourly, daily, weekly or monthly'; -- Add volume details to the backups table to keep track of the volumes being backed up CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'backed_volumes', 'text DEFAULT NULL COMMENT "details of backed-up volumes" '); diff --git a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java index f162c51a703d..9c325c4f5281 100644 --- a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java +++ b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java @@ -111,7 +111,7 @@ public boolean willDeleteBackupsOnOfferingRemoval() { } @Override - public boolean takeBackup(VirtualMachine vm) { + public BackupVO takeBackup(VirtualMachine vm) { logger.debug("Starting backup for VM ID " + vm.getUuid() + " on Dummy provider"); BackupVO backup = new BackupVO(); @@ -127,7 +127,7 @@ public boolean takeBackup(VirtualMachine vm) { backup.setDomainId(vm.getDomainId()); backup.setZoneId(vm.getDataCenterId()); backup.setBackedUpVolumes(BackupManagerImpl.createVolumeInfoFromVolumes(volumeDao.findByInstance(vm.getId()))); - return backupDao.persist(backup) != null; + return backupDao.persist(backup); } @Override 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 f900125a462f..93d1c61be154 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 @@ -46,6 +46,8 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.opensaml.xml.signature.P; + import javax.inject.Inject; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -141,7 +143,7 @@ protected Host getVMHypervisorHost(VirtualMachine vm) { } @Override - public boolean takeBackup(final VirtualMachine vm) { + public BackupVO takeBackup(final VirtualMachine vm) { final Host host = getVMHypervisorHost(vm); final BackupRepository backupRepository = backupRepositoryDao.findByBackupOfferingId(vm.getBackupOfferingId()); @@ -179,12 +181,16 @@ public boolean takeBackup(final VirtualMachine vm) { backupVO.setSize(answer.getSize()); backupVO.setStatus(Backup.Status.BackedUp); backupVO.setBackedUpVolumes(BackupManagerImpl.createVolumeInfoFromVolumes(volumeDao.findByInstance(vm.getId()))); - return backupDao.update(backupVO.getId(), backupVO); + if (backupDao.update(backupVO.getId(), backupVO)) { + return backupVO; + } else { + throw new CloudRuntimeException("Failed to update backup"); + } } else { backupVO.setStatus(Backup.Status.Failed); backupDao.remove(backupVO.getId()); + return null; } - return Objects.nonNull(answer) && answer.getResult(); } private BackupVO createBackupObject(VirtualMachine vm, String backupPath) { 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 0e87ad338871..dbd27af55213 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 @@ -462,7 +462,7 @@ public Pair restoreBackedUpVolume(Backup backup, String volumeU } @Override - public boolean takeBackup(VirtualMachine vm) { + public BackupVO takeBackup(VirtualMachine vm) { String networkerServer; String clusterName; @@ -514,11 +514,11 @@ public boolean takeBackup(VirtualMachine vm) { if (backup != null) { backup.setBackedUpVolumes(BackupManagerImpl.createVolumeInfoFromVolumes(volumeDao.findByInstance(vm.getId()))); backupDao.persist(backup); - return true; + return backup; } else { LOG.error("Could not register backup for vm " + vm.getName() + " with saveset Time: " + saveTime); // We need to handle this rare situation where backup is successful but can't be registered properly. - return false; + return null; } } diff --git a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java index db650bf7c3ef..171dafcb613e 100644 --- a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java +++ b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java @@ -453,6 +453,7 @@ private static void copyResourceLimitsIntoMap(Map r resourceLimitMap.put(Resource.ResourceType.primary_storage, domainJoinVO.getPrimaryStorageLimit()); resourceLimitMap.put(Resource.ResourceType.secondary_storage, domainJoinVO.getSecondaryStorageLimit()); resourceLimitMap.put(Resource.ResourceType.project, domainJoinVO.getProjectLimit()); + resourceLimitMap.put(Resource.ResourceType.backup, domainJoinVO.getBackupLimit()); } private static void copyResourceLimitsFromMap(Map resourceLimitMap, DomainJoinVO domainJoinVO){ @@ -468,6 +469,7 @@ private static void copyResourceLimitsFromMap(Map r domainJoinVO.setPrimaryStorageLimit(resourceLimitMap.get(Resource.ResourceType.primary_storage)); domainJoinVO.setSecondaryStorageLimit(resourceLimitMap.get(Resource.ResourceType.secondary_storage)); domainJoinVO.setProjectLimit(resourceLimitMap.get(Resource.ResourceType.project)); + domainJoinVO.setBackupLimit(resourceLimitMap.get(Resource.ResourceType.backup)); } private static void setParentResourceLimitIfNeeded(Map resourceLimitMap, DomainJoinVO domainJoinVO, List domainsCopy) { @@ -486,6 +488,7 @@ private static void setParentResourceLimitIfNeeded(Map params) th projectResourceLimitMap.put(Resource.ResourceType.cpu.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectCpus.key()))); projectResourceLimitMap.put(Resource.ResourceType.memory.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectMemory.key()))); projectResourceLimitMap.put(Resource.ResourceType.primary_storage.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectPrimaryStorage.key()))); + projectResourceLimitMap.put(Resource.ResourceType.backup.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxProjectBackups.key()))); projectResourceLimitMap.put(Resource.ResourceType.secondary_storage.name(), MaxProjectSecondaryStorage.value()); accountResourceLimitMap.put(Resource.ResourceType.public_ip.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountPublicIPs.key()))); @@ -300,6 +304,7 @@ public boolean configure(final String name, final Map params) th accountResourceLimitMap.put(Resource.ResourceType.cpu.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountCpus.key()))); accountResourceLimitMap.put(Resource.ResourceType.memory.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountMemory.key()))); accountResourceLimitMap.put(Resource.ResourceType.primary_storage.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountPrimaryStorage.key()))); + accountResourceLimitMap.put(Resource.ResourceType.backup.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxAccountBackups.key()))); accountResourceLimitMap.put(Resource.ResourceType.secondary_storage.name(), MaxAccountSecondaryStorage.value()); accountResourceLimitMap.put(Resource.ResourceType.project.name(), DefaultMaxAccountProjects.value()); @@ -314,6 +319,7 @@ public boolean configure(final String name, final Map params) th domainResourceLimitMap.put(Resource.ResourceType.memory.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainMemory.key()))); domainResourceLimitMap.put(Resource.ResourceType.primary_storage.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainPrimaryStorage.key()))); domainResourceLimitMap.put(Resource.ResourceType.secondary_storage.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainSecondaryStorage.key()))); + domainResourceLimitMap.put(Resource.ResourceType.backup.name(), Long.parseLong(_configDao.getValue(Config.DefaultMaxDomainBackups.key()))); domainResourceLimitMap.put(Resource.ResourceType.project.name(), DefaultMaxDomainProjects.value()); } catch (NumberFormatException e) { logger.error("NumberFormatException during configuration", e); @@ -1238,6 +1244,8 @@ protected long recalculateAccountResourceCount(final long accountId, final Resou newCount = calculateVolumeCountForAccount(accountId, tag); } else if (type == Resource.ResourceType.snapshot) { newCount = _snapshotDao.countSnapshotsForAccount(accountId); + } else if (type == Resource.ResourceType.backup) { + newCount = backupDao.countBackupsForAccount(accountId); } else if (type == Resource.ResourceType.public_ip) { newCount = calculatePublicIpForAccount(accountId); } else if (type == Resource.ResourceType.template) { diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java index dd63371b888d..370a5c57509d 100644 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java @@ -48,6 +48,16 @@ public interface SnapshotManager extends Configurable { "Maximum recurring weekly snapshots to be retained for a volume. If the limit is reached, snapshots from the beginning of the week are deleted so that newer ones can be saved. This limit does not apply to manual snapshots. If set to 0, recurring weekly snapshots can not be scheduled.", false, ConfigKey.Scope.Global, null); static final ConfigKey SnapshotMonthlyMax = new ConfigKey(Integer.class, "snapshot.max.monthly", "Snapshots", "8", "Maximum recurring monthly snapshots to be retained for a volume. If the limit is reached, snapshots from the beginning of the month are deleted so that newer ones can be saved. This limit does not apply to manual snapshots. If set to 0, recurring monthly snapshots can not be scheduled.", false, ConfigKey.Scope.Global, null); + + static final ConfigKey BackupHourlyMax = new ConfigKey(Integer.class, "backup.max.hourly", "Advanced", "8", + "Maximum recurring hourly backups to be retained for an instance. If the limit is reached, early backups from the start of the hour are deleted so that newer ones can be saved. This limit does not apply to manual backups. If set to 0, recurring hourly backups can not be scheduled.", false, ConfigKey.Scope.Global, null); + static final ConfigKey BackupDailyMax = new ConfigKey(Integer.class, "backup.max.daily", "Advanced", "8", + "Maximum recurring daily backups to be retained for an instance. If the limit is reached, backups from the start of the day are deleted so that newer ones can be saved. This limit does not apply to manual backups. If set to 0, recurring daily backups can not be scheduled.", false, ConfigKey.Scope.Global, null); + static final ConfigKey BackupWeeklyMax = new ConfigKey(Integer.class, "backup.max.weekly", "Advanced", "8", + "Maximum recurring weekly backups to be retained for an instance. If the limit is reached, backups from the beginning of the week are deleted so that newer ones can be saved. This limit does not apply to manual backups. If set to 0, recurring weekly backups can not be scheduled.", false, ConfigKey.Scope.Global, null); + static final ConfigKey BackupMonthlyMax = new ConfigKey(Integer.class, "backup.max.monthly", "Advanced", "8", + "Maximum recurring monthly backups to be retained for an instance. If the limit is reached, backups from the beginning of the month are deleted so that newer ones can be saved. This limit does not apply to manual backups. If set to 0, recurring monthly backups can not be scheduled.", false, ConfigKey.Scope.Global, null); + static final ConfigKey usageSnapshotSelection = new ConfigKey("Usage", Boolean.class, "usage.snapshot.virtualsize.select", "false", "Set the value to true if snapshot usage need to consider virtual size, else physical size is considered ", false); public static final ConfigKey BackupRetryAttempts = new ConfigKey(Integer.class, "backup.retry", "Advanced", "3", diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index 50c8ff8b83a7..91f9d6477486 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -45,6 +45,7 @@ import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -283,7 +284,8 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {BackupRetryAttempts, BackupRetryInterval, SnapshotHourlyMax, SnapshotDailyMax, SnapshotMonthlyMax, SnapshotWeeklyMax, usageSnapshotSelection, + return new ConfigKey[] {BackupRetryAttempts, BackupRetryInterval, SnapshotHourlyMax, SnapshotDailyMax, SnapshotMonthlyMax, SnapshotWeeklyMax, + BackupHourlyMax, BackupDailyMax, BackupWeeklyMax, BackupMonthlyMax, usageSnapshotSelection, SnapshotInfo.BackupSnapshotAfterTakingSnapshot, VmStorageSnapshotKvm}; } @@ -1573,6 +1575,11 @@ public boolean configure(String name, Map params) throws Configu Type.DAILY.setMax(SnapshotDailyMax.value()); Type.WEEKLY.setMax(SnapshotWeeklyMax.value()); Type.MONTHLY.setMax(SnapshotMonthlyMax.value()); + Backup.Type.HOURLY.setMax(BackupHourlyMax.value()); + Backup.Type.DAILY.setMax(BackupDailyMax.value()); + Backup.Type.WEEKLY.setMax(BackupWeeklyMax.value()); + Backup.Type.MONTHLY.setMax(BackupMonthlyMax.value()); + _totalRetries = NumbersUtil.parseInt(_configDao.getValue("total.retries"), 4); _pauseInterval = 2 * NumbersUtil.parseInt(_configDao.getValue("ping.interval"), 60); 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 8430a59f5cef..19da9fe2dc47 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -23,13 +23,20 @@ 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; import java.util.stream.Collectors; import com.amazonaws.util.CollectionUtils; +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; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.vm.VirtualMachineManager; import javax.inject.Inject; @@ -138,6 +145,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { @Inject private AccountManager accountManager; @Inject + private DomainManager domainManager; + @Inject private VolumeDao volumeDao; @Inject private DataCenterDao dataCenterDao; @@ -161,6 +170,10 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private VirtualMachineManager virtualMachineManager; @Inject private VolumeApiService volumeApiService; + @Inject + private ResourceLimitService resourceLimitMgr; + @Inject + private AlertManager alertManager; private AsyncJobDispatcher asyncJobDispatcher; private Timer backupTimer; @@ -393,8 +406,8 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), "Backup-" + vm.getHostName() + "-" + vm.getUuid(), vm.getBackupOfferingId(), null, null, Backup.class.getSimpleName(), vm.getUuid()); - final BackupSchedule backupSchedule = backupScheduleDao.findByVM(vm.getId()); - if (backupSchedule != null) { + final List backupSchedules = backupScheduleDao.listByVM(vm.getId()); + for(BackupSchedule backupSchedule: backupSchedules) { backupScheduleDao.remove(backupSchedule.getId()); } result = true; @@ -413,6 +426,7 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { final DateUtil.IntervalType intervalType = cmd.getIntervalType(); final String scheduleString = cmd.getSchedule(); final TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone()); + final Integer maxBackups = cmd.getMaxBackups(); if (intervalType == null) { throw new CloudRuntimeException("Invalid interval type provided"); @@ -425,6 +439,28 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { if (vm.getBackupOfferingId() == null) { throw new CloudRuntimeException("Cannot configure backup schedule for the VM without having any backup offering"); } + if (maxBackups <= 0) { + throw new InvalidParameterValueException(String.format("maxBackups [%s] for instance %s should be greater than 0.", maxBackups, vm.getName())); + } + + Backup.Type backupType = Backup.Type.valueOf(intervalType.name()); + int intervalMaxBackups = backupType.getMax(); + if (maxBackups > intervalMaxBackups) { + throw new InvalidParameterValueException(String.format("maxBackups [%s] for instance %s exceeds limit [%s] for interval type [%s].", maxBackups, vm.getName(), + intervalMaxBackups, intervalType)); + } + + Account owner = accountManager.getAccount(vm.getAccountId()); + + long accountLimit = resourceLimitMgr.findCorrectResourceLimitForAccount(owner, Resource.ResourceType.backup, null); + long domainLimit = resourceLimitMgr.findCorrectResourceLimitForDomain(domainManager.getDomain(owner.getDomainId()), Resource.ResourceType.backup, null); + if (!accountManager.isRootAdmin(owner.getId()) && ((accountLimit != -1 && maxBackups > accountLimit) || (domainLimit != -1 && maxBackups > domainLimit))) { + String message = "domain/account"; + if (owner.getType() == Account.Type.PROJECT) { + message = "domain/project"; + } + throw new InvalidParameterValueException("Max number of backups shouldn't exceed the " + message + " level snapshot limit"); + } final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId()); if (offering == null || !offering.isUserDrivenBackupAllowed()) { @@ -452,8 +488,9 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { schedule.setSchedule(scheduleString); schedule.setTimezone(timezoneId); schedule.setScheduledTimestamp(nextDateTime); + schedule.setMaxBackups(maxBackups); backupScheduleDao.update(schedule.getId(), schedule); - return backupScheduleDao.findByVM(vmId); + return backupScheduleDao.findById(schedule.getId()); } @Override @@ -467,21 +504,35 @@ public List listBackupSchedule(final Long vmId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule") - public boolean deleteBackupSchedule(final Long vmId) { - final VMInstanceVO vm = findVmById(vmId); - validateForZone(vm.getDataCenterId()); - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + public boolean deleteBackupSchedule(DeleteBackupScheduleCmd cmd) { + Long vmId = cmd.getVmId(); + Long id = cmd.getId(); + if (Objects.nonNull(vmId)) { + final VMInstanceVO vm = findVmById(vmId); + validateForZone(vm.getDataCenterId()); + accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + return deleteAllVMBackupSchedules(vm.getId()); + } else { + final BackupSchedule schedule = backupScheduleDao.findById(id); + if (schedule == null) { + throw new CloudRuntimeException("Could not find the requested backup schedule."); + } + return backupScheduleDao.remove(schedule.getId()); + } + } - final BackupSchedule schedule = backupScheduleDao.findByVM(vmId); - if (schedule == null) { - throw new CloudRuntimeException("VM has no backup schedule defined, no need to delete anything."); + private 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 @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) - public boolean createBackup(final Long vmId) { + public boolean createBackup(final Long vmId, final Long scheduleId) throws ResourceAllocationException { final VMInstanceVO vm = findVmById(vmId); validateForZone(vm.getDataCenterId()); accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); @@ -499,13 +550,36 @@ public boolean createBackup(final Long vmId) { throw new CloudRuntimeException("The assigned backup offering does not allow ad-hoc user backup"); } + Backup.Type type = getBackupType(scheduleId); + Account owner = accountManager.getAccount(vm.getAccountId()); + try { + resourceLimitMgr.checkResourceLimit(owner, Resource.ResourceType.backup); + } catch (ResourceAllocationException e) { + if (type != Backup.Type.MANUAL) { + String msg = "Backup resource limit exceeded for account id : " + owner.getId() + ". Failed to create backup"; + logger.warn(msg); + alertManager.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPDATE_RESOURCE_COUNT, 0L, 0L, msg, "Backup resource limit exceeded for account id : " + owner.getId() + + ". Failed to create backups; please use updateResourceLimit to increase the limit"); + } + throw e; + } + ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(), vmId, ApiCommandResourceType.VirtualMachine.toString(), true, 0); + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); - if (backupProvider != null && backupProvider.takeBackup(vm)) { + if (backupProvider != null) { + Backup backup = backupProvider.takeBackup(vm); + if (backup == null) { + throw new CloudRuntimeException("Failed to create VM backup"); + } + BackupVO vmBackup = backupDao.findById(backup.getId()); + vmBackup.setBackupIntervalType((short)type.ordinal()); + backupDao.update(vmBackup.getId(), vmBackup); + resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup); return true; } throw new CloudRuntimeException("Failed to create VM backup"); @@ -681,6 +755,29 @@ protected void tryRestoreVM(BackupVO backup, VMInstanceVO vm, BackupOffering off } } + private Backup.Type getBackupType(Long scheduleId) { + if (scheduleId.equals(Snapshot.MANUAL_POLICY_ID)) { + return Backup.Type.MANUAL; + } else { + BackupScheduleVO scheduleVO = backupScheduleDao.findById(scheduleId); + DateUtil.IntervalType intvType = scheduleVO.getScheduleType(); + return getBackupType(intvType); + } + } + + private Backup.Type getBackupType(DateUtil.IntervalType intvType) { + if (intvType.equals(DateUtil.IntervalType.HOURLY)) { + return Backup.Type.HOURLY; + } else if (intvType.equals(DateUtil.IntervalType.DAILY)) { + return Backup.Type.DAILY; + } else if (intvType.equals(DateUtil.IntervalType.WEEKLY)) { + return Backup.Type.WEEKLY; + } else if (intvType.equals(DateUtil.IntervalType.MONTHLY)) { + return Backup.Type.MONTHLY; + } + return null; + } + /** * Tries to update the state of given VM, given specified event * @param vm The VM to update its state @@ -857,6 +954,7 @@ public boolean deleteBackup(final Long backupId, final Boolean forced) { final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); boolean result = backupProvider.deleteBackup(backup, forced); if (result) { + resourceLimitMgr.decrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup); return backupDao.remove(backup.getId()); } throw new CloudRuntimeException("Failed to delete the backup"); From 106e4c2bfb005acaf8ae762305463af84d80420e Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 3 Oct 2024 23:20:31 -0400 Subject: [PATCH 15/16] update veeam provider --- .../org/apache/cloudstack/backup/VeeamBackupProvider.java | 8 ++++++-- .../org/apache/cloudstack/backup/BackupManagerImpl.java | 8 +++++--- 2 files changed, 11 insertions(+), 5 deletions(-) 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 4750e3264aac..48d76591df5a 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 @@ -220,9 +220,13 @@ public boolean willDeleteBackupsOnOfferingRemoval() { } @Override - public boolean takeBackup(final VirtualMachine vm) { + public BackupVO takeBackup(final VirtualMachine vm) { final VeeamClient client = getClient(vm.getDataCenterId()); - return client.startBackupJob(vm.getBackupExternalId()); + boolean bkpJobSuccess = client.startBackupJob(vm.getBackupExternalId()); + if (bkpJobSuccess) { + return new BackupVO(); + } + return null; } @Override 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 19da9fe2dc47..3367f912faee 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -576,9 +576,11 @@ public boolean createBackup(final Long vmId, final Long scheduleId) throws Resou if (backup == null) { throw new CloudRuntimeException("Failed to create VM backup"); } - BackupVO vmBackup = backupDao.findById(backup.getId()); - vmBackup.setBackupIntervalType((short)type.ordinal()); - backupDao.update(vmBackup.getId(), vmBackup); + if (!"veeam".equals(backupProvider.getName())) { + BackupVO vmBackup = backupDao.findById(backup.getId()); + vmBackup.setBackupIntervalType((short) type.ordinal()); + backupDao.update(vmBackup.getId(), vmBackup); + } resourceLimitMgr.incrementResourceCount(vm.getAccountId(), Resource.ResourceType.backup); return true; } From 6646d6347ef4f28ee0d58832e080123f2067f120 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 3 Oct 2024 23:43:38 -0400 Subject: [PATCH 16/16] remove unused import --- .../java/org/apache/cloudstack/backup/NASBackupProvider.java | 1 - 1 file changed, 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 93d1c61be154..bbaf00735fde 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 @@ -46,7 +46,6 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -import org.opensaml.xml.signature.P; import javax.inject.Inject; import java.text.SimpleDateFormat;