This is an automated email from the ASF dual-hosted git repository.

pearl11594 pushed a commit to branch bnr-resource-limits
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 9aea6335d11b07e644588ae2a30533cd568339b9
Author: Pearl Dsilva <pearl1...@gmail.com>
AuthorDate: Thu Oct 3 13:44:06 2024 -0400

    B&R Framework: Support account / domain backup limits
---
 .../java/com/cloud/configuration/Resource.java     |   3 +-
 .../org/apache/cloudstack/api/ApiConstants.java    |   2 +
 .../api/command/user/backup/CreateBackupCmd.java   |  19 +++-
 .../user/backup/CreateBackupScheduleCmd.java       |   7 ++
 .../user/backup/DeleteBackupScheduleCmd.java       |  13 ++-
 .../cloudstack/api/response/AccountResponse.java   |  27 +++++
 .../cloudstack/api/response/DomainResponse.java    |  24 ++++
 .../cloudstack/api/response/ProjectResponse.java   |  27 +++++
 .../response/ResourceLimitAndCountResponse.java    |   6 +
 .../java/org/apache/cloudstack/backup/Backup.java  |  22 ++++
 .../apache/cloudstack/backup/BackupManager.java    |   9 +-
 .../apache/cloudstack/backup/BackupProvider.java   |   2 +-
 .../apache/cloudstack/backup/BackupSchedule.java   |   1 +
 .../apache/cloudstack/backup/BackupScheduleVO.java |  11 ++
 .../org/apache/cloudstack/backup/BackupVO.java     |  11 ++
 .../apache/cloudstack/backup/dao/BackupDao.java    |   1 +
 .../cloudstack/backup/dao/BackupDaoImpl.java       |  17 +++
 .../resources/META-INF/db/schema-41910to42000.sql  |   2 +
 .../cloudstack/backup/DummyBackupProvider.java     |   4 +-
 .../cloudstack/backup/NASBackupProvider.java       |  12 +-
 .../cloudstack/backup/NetworkerBackupProvider.java |   6 +-
 .../com/cloud/api/query/ViewResponseHelper.java    |   7 ++
 .../cloud/api/query/dao/AccountJoinDaoImpl.java    |   8 ++
 .../com/cloud/api/query/dao/DomainJoinDaoImpl.java |   8 ++
 .../java/com/cloud/api/query/vo/AccountJoinVO.java |  14 +++
 .../java/com/cloud/api/query/vo/DomainJoinVO.java  |  19 ++++
 .../main/java/com/cloud/configuration/Config.java  |  17 +++
 .../resourcelimit/ResourceLimitManagerImpl.java    |   8 ++
 .../cloud/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 bf8fca9d905..e04a22e1204 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 @@ public interface Resource {
         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 bb16b0ff90d..cee5b578260 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 558f92e4006..1ed2584ca00 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 @@ package org.apache.cloudstack.api.command.user.backup;
 
 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.ApiErrorCode;
 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 class CreateBackupCmd extends BaseAsyncCreateCmd {
         return vmId;
     }
 
+    public Long getScheduleId() {
+        if (scheduleId != null) {
+            return scheduleId;
+        } else {
+        return Snapshot.MANUAL_POLICY_ID;
+        }
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -75,7 +92,7 @@ public class CreateBackupCmd extends BaseAsyncCreateCmd {
     @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 5dc06af2123..90b9b9912e9 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 org.apache.cloudstack.context.CallContext;
 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 class CreateBackupScheduleCmd extends BaseCmd {
         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 0245f228b89..0679821a051 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.ServerApiException;
 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 class DeleteBackupScheduleCmd  extends BaseCmd {
         return vmId;
     }
 
+    public Long getId() { return id; }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -73,7 +84,7 @@ public class DeleteBackupScheduleCmd  extends BaseCmd {
     @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 7a84e85a4a6..67a3c516598 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 class AccountResponse extends BaseResponse 
implements ResourceLimitAndCou
         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 7c6ad3a91c3..afd811d252d 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 class DomainResponse extends 
BaseResponseWithAnnotations implements Resou
         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 1c63697559b..37837d9608b 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 class ProjectResponse extends BaseResponse 
implements ResourceLimitAndCou
         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 f9e6df3a038..e679755c835 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 f21f20adb33..dffe8a03213 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 @@ public interface Backup extends ControlledEntity, 
InternalIdentity, Identity {
         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 8b45bb4ee5e..d8271fb8869 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 @@ package org.apache.cloudstack.backup;
 
 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 d36dfb7360f..aed0ac86117 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 d81dd731b1f..da90da508ff 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 ba31dc59d39..89a28be77d2 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 class BackupScheduleVO implements BackupSchedule {
     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 9b285e66cab..b9dd9809a24 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 class BackupVO implements Backup {
         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 89a13245b0a..ac211a745d4 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, Long> 
{
     BackupVO getBackupVO(Backup backup);
     List<Backup> 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 5a9cd062037..b67a5ad54a4 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 java.util.Objects;
 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<BackupVO, 
Long> implements Bac
     BackupOfferingDao backupOfferingDao;
 
     private SearchBuilder<BackupVO> backupSearch;
+    private GenericSearchBuilder<BackupVO, Long> CountBackupsByAccount;
 
     public BackupDaoImpl() {
     }
@@ -72,6 +74,13 @@ public class BackupDaoImpl extends GenericDaoBase<BackupVO, 
Long> implements Bac
         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 class BackupDaoImpl extends 
GenericDaoBase<BackupVO, Long> implements Bac
         return listByVmId(zoneId, vmId);
     }
 
+    @Override
+    public Long countBackupsForAccount(long accountId) {
+        SearchCriteria<Long> 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 c36b71c2f25..899866c9d5f 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 f162c51a703..9c325c4f528 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 class DummyBackupProvider extends AdapterBase 
implements BackupProvider {
     }
 
     @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 class DummyBackupProvider extends AdapterBase 
implements BackupProvider {
         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 f900125a462..93d1c61be15 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.cloudstack.storage.datastore.db.StoragePoolVO;
 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 @@ public class NASBackupProvider extends AdapterBase 
implements BackupProvider, Co
     }
 
     @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 class NASBackupProvider extends AdapterBase 
implements BackupProvider, Co
             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 0e87ad33887..dbd27af5521 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 class NetworkerBackupProvider extends AdapterBase 
implements BackupProvid
     }
 
     @Override
-    public boolean takeBackup(VirtualMachine vm) {
+    public BackupVO takeBackup(VirtualMachine vm) {
         String networkerServer;
         String clusterName;
 
@@ -514,11 +514,11 @@ public class NetworkerBackupProvider extends AdapterBase 
implements BackupProvid
         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 db650bf7c3e..171dafcb613 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 @@ public class ViewResponseHelper {
         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<Resource.ResourceType, 
Long> resourceLimitMap, DomainJoinVO domainJoinVO){
@@ -468,6 +469,7 @@ public class ViewResponseHelper {
         
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<Resource.ResourceType, Long> 
resourceLimitMap, DomainJoinVO domainJoinVO, List<DomainJoinVO> domainsCopy) {
@@ -486,6 +488,7 @@ public class ViewResponseHelper {
             Long primaryStorageLimit = 
resourceLimitMap.get(Resource.ResourceType.primary_storage);
             Long secondaryStorageLimit = 
resourceLimitMap.get(Resource.ResourceType.secondary_storage);
             Long projectLimit = 
resourceLimitMap.get(Resource.ResourceType.project);
+            Long backupLimit = 
resourceLimitMap.get(Resource.ResourceType.backup);
 
             if (vmLimit == null) {
                 vmLimit = parentDomainJoinVO.getVmLimit();
@@ -535,6 +538,10 @@ public class ViewResponseHelper {
                 projectLimit = parentDomainJoinVO.getProjectLimit();
                 resourceLimitMap.put(Resource.ResourceType.project, 
projectLimit);
             }
+            if (backupLimit == null) {
+                backupLimit = parentDomainJoinVO.getBackupLimit();
+                resourceLimitMap.put(Resource.ResourceType.backup, 
backupLimit);
+            }
             //-- try till parent present
             if (parentDomainJoinVO.getParent() != null && 
parentDomainJoinVO.getParent() != Domain.ROOT_DOMAIN) {
                 setParentResourceLimitIfNeeded(resourceLimitMap, 
parentDomainJoinVO, domainsCopy);
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
index 7ffd3ef319f..9af15cf7bc2 100644
--- a/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java
@@ -237,6 +237,14 @@ public class AccountJoinDaoImpl extends 
GenericDaoBase<AccountJoinVO, Long> impl
         response.setSecondaryStorageLimit(secondaryStorageLimitDisplay);
         response.setSecondaryStorageTotal(secondaryStorageTotal);
         response.setSecondaryStorageAvailable(secondaryStorageAvail);
+
+        long backupLimit = 
ApiDBUtils.findCorrectResourceLimit(account.getBackupLimit(), account.getId(), 
ResourceType.backup);
+        String backupLimitDisplay = (fullView || backupLimit == -1) ? 
Resource.UNLIMITED : String.valueOf(snapshotLimit);
+        long backupTotal = (account.getBackupTotal() == null) ? 0 : 
account.getBackupTotal();
+        String backupAvail = (fullView || backupLimit == -1) ? 
Resource.UNLIMITED : String.valueOf(backupLimit - backupTotal);
+        response.setBackupLimit(backupLimitDisplay);
+        response.setBackupTotal(backupTotal);
+        response.setBackupAvailable(backupAvail);
     }
 
     @Override
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
index 9ad05d216a9..b86664b5fec 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
@@ -212,6 +212,14 @@ public class DomainJoinDaoImpl extends 
GenericDaoBase<DomainJoinVO, Long> implem
         response.setSecondaryStorageLimit(secondaryStorageLimitDisplay);
         response.setSecondaryStorageTotal(secondaryStorageTotal);
         response.setSecondaryStorageAvailable(secondaryStorageAvail);
+
+        long backupLimit = 
ApiDBUtils.findCorrectResourceLimitForDomain(domain.getBackupLimit(), 
ResourceType.backup, domain.getId());
+        String backupLimitDisplay = (fullView || snapshotLimit == -1) ? 
Resource.UNLIMITED : String.valueOf(backupLimit);
+        long backupTotal = (domain.getBackupTotal() == null) ? 0 : 
domain.getBackupTotal();
+        String backupAvail = (fullView || snapshotLimit == -1) ? 
Resource.UNLIMITED : String.valueOf(backupLimit - backupTotal);
+        response.setBackupLimit(backupLimitDisplay);
+        response.setBackupTotal(backupTotal);
+        response.setBackupAvailable(backupAvail);
     }
 
     @Override
diff --git a/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java 
b/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java
index 19c49294c84..e1c99d48879 100644
--- a/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java
@@ -123,6 +123,12 @@ public class AccountJoinVO extends BaseViewVO implements 
InternalIdentity, Ident
     @Column(name = "snapshotTotal")
     private Long snapshotTotal;
 
+    @Column(name = "backupLimit")
+    private Long backupLimit;
+
+    @Column(name = "backupTotal")
+    private Long backupTotal;
+
     @Column(name = "templateLimit")
     private Long templateLimit;
 
@@ -290,6 +296,10 @@ public class AccountJoinVO extends BaseViewVO implements 
InternalIdentity, Ident
         return snapshotTotal;
     }
 
+    public Long getBackupTotal() {
+        return backupTotal;
+    }
+
     public Long getTemplateTotal() {
         return templateTotal;
     }
@@ -346,6 +356,10 @@ public class AccountJoinVO extends BaseViewVO implements 
InternalIdentity, Ident
         return snapshotLimit;
     }
 
+    public Long getBackupLimit() {
+        return backupLimit;
+    }
+
     public Long getTemplateLimit() {
         return templateLimit;
     }
diff --git a/server/src/main/java/com/cloud/api/query/vo/DomainJoinVO.java 
b/server/src/main/java/com/cloud/api/query/vo/DomainJoinVO.java
index e17eacd68fa..84279e226fe 100644
--- a/server/src/main/java/com/cloud/api/query/vo/DomainJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/DomainJoinVO.java
@@ -100,6 +100,12 @@ public class DomainJoinVO extends BaseViewVO implements 
InternalIdentity, Identi
     @Column(name="snapshotTotal")
     private Long snapshotTotal;
 
+    @Column(name="backupLimit")
+    private Long backupLimit;
+
+    @Column(name="backupTotal")
+    private Long backupTotal;
+
     @Column(name="templateLimit")
     private Long templateLimit;
 
@@ -311,8 +317,21 @@ public class DomainJoinVO extends BaseViewVO implements 
InternalIdentity, Identi
         this.snapshotTotal = snapshotTotal;
     }
 
+    public Long getBackupTotal() {
+        return backupTotal;
+    }
 
+    public void setBackupTotal(Long backupTotal) {
+        this.backupTotal = backupTotal;
+    }
 
+    public Long getBackupLimit() {
+        return backupLimit;
+    }
+
+    public void setBackupLimit(Long backupLimit) {
+        this.backupLimit = backupLimit;
+    }
 
     public Long getTemplateTotal() {
         return templateTotal;
diff --git a/server/src/main/java/com/cloud/configuration/Config.java 
b/server/src/main/java/com/cloud/configuration/Config.java
index ce3ac768468..d7cf3ea7907 100644
--- a/server/src/main/java/com/cloud/configuration/Config.java
+++ b/server/src/main/java/com/cloud/configuration/Config.java
@@ -1317,6 +1317,14 @@ public enum Config {
             "20",
             "The default maximum number of snapshots that can be created for 
an account",
             null),
+    DefaultMaxAccountBackups(
+            "Account Defaults",
+            ManagementServer.class,
+            Long.class,
+            "max.account.backups",
+            "20",
+            "The default maximum number of backups that can be created for an 
account",
+            null),
     DefaultMaxAccountVolumes(
             "Account Defaults",
             ManagementServer.class,
@@ -1415,6 +1423,7 @@ DefaultMaxAccountProjects(
     DefaultMaxDomainPublicIPs("Domain Defaults", ManagementServer.class, 
Long.class, "max.domain.public.ips", "40", "The default maximum number of 
public IPs that can be consumed by a domain", null),
     DefaultMaxDomainTemplates("Domain Defaults", ManagementServer.class, 
Long.class, "max.domain.templates", "40", "The default maximum number of 
templates that can be deployed for a domain", null),
     DefaultMaxDomainSnapshots("Domain Defaults", ManagementServer.class, 
Long.class, "max.domain.snapshots", "40", "The default maximum number of 
snapshots that can be created for a domain", null),
+    DefaultMaxDomainBackups("Domain Defaults", ManagementServer.class, 
Long.class, "max.domain.backups", "40", "The default maximum number of backups 
that can be created for a domain", null),
     DefaultMaxDomainVolumes("Domain Defaults", ManagementServer.class, 
Long.class, "max.domain.volumes", "40", "The default maximum number of volumes 
that can be created for a domain", null),
     DefaultMaxDomainNetworks("Domain Defaults", ManagementServer.class, 
Long.class, "max.domain.networks", "40", "The default maximum number of 
networks that can be created for a domain", null),
     DefaultMaxDomainVpcs("Domain Defaults", ManagementServer.class, 
Long.class, "max.domain.vpcs", "40", "The default maximum number of vpcs that 
can be created for a domain", null),
@@ -1456,6 +1465,14 @@ DefaultMaxAccountProjects(
             "20",
             "The default maximum number of snapshots that can be created for a 
project",
             null),
+    DefaultMaxProjectBackups(
+            "Project Defaults",
+            ManagementServer.class,
+            Long.class,
+            "max.project.backups",
+            "20",
+            "The default maximum number of backups that can be created for a 
project",
+            null),
     DefaultMaxProjectVolumes(
             "Project Defaults",
             ManagementServer.class,
diff --git 
a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java 
b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java
index b59ddc029ee..947a3ae8550 100644
--- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java
+++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java
@@ -42,6 +42,7 @@ import org.apache.cloudstack.api.response.AccountResponse;
 import org.apache.cloudstack.api.response.DomainResponse;
 import org.apache.cloudstack.api.response.ResourceLimitAndCountResponse;
 import org.apache.cloudstack.api.response.TaggedResourceLimitAndCountResponse;
+import org.apache.cloudstack.backup.dao.BackupDao;
 import org.apache.cloudstack.context.CallContext;
 import 
org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 import org.apache.cloudstack.framework.config.ConfigKey;
@@ -171,6 +172,8 @@ public class ResourceLimitManagerImpl extends ManagerBase 
implements ResourceLim
     @Inject
     protected SnapshotDao _snapshotDao;
     @Inject
+    protected BackupDao backupDao;
+    @Inject
     private SnapshotDataStoreDao _snapshotDataStoreDao;
     @Inject
     private TemplateDataStoreDao _vmTemplateStoreDao;
@@ -288,6 +291,7 @@ public class ResourceLimitManagerImpl extends ManagerBase 
implements ResourceLim
             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 class ResourceLimitManagerImpl extends ManagerBase 
implements ResourceLim
             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 class ResourceLimitManagerImpl extends ManagerBase 
implements ResourceLim
             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 @@ public class ResourceLimitManagerImpl extends ManagerBase 
implements ResourceLim
             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 dd63371b888..370a5c57509 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<Integer> SnapshotMonthlyMax = new 
ConfigKey<Integer>(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<Integer> BackupHourlyMax = new 
ConfigKey<Integer>(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<Integer> BackupDailyMax = new 
ConfigKey<Integer>(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<Integer> BackupWeeklyMax = new 
ConfigKey<Integer>(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<Integer> BackupMonthlyMax = new 
ConfigKey<Integer>(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<Boolean> usageSnapshotSelection = new 
ConfigKey<Boolean>("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<Integer> BackupRetryAttempts = new 
ConfigKey<Integer>(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 50c8ff8b83a..91f9d647748 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.ExtractSnapshotCmd;
 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 class SnapshotManagerImpl extends 
MutualExclusiveIdsManagerBase implement
 
     @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 class SnapshotManagerImpl extends 
MutualExclusiveIdsManagerBase implement
         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 8430a59f5ce..19da9fe2dc4 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.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.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 class BackupManagerImpl extends ManagerBase 
implements BackupManager {
                 
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<BackupScheduleVO> backupSchedules = 
backupScheduleDao.listByVM(vm.getId());
+                for(BackupSchedule backupSchedule: backupSchedules) {
                     backupScheduleDao.remove(backupSchedule.getId());
                 }
                 result = true;
@@ -413,6 +426,7 @@ public class BackupManagerImpl extends ManagerBase 
implements BackupManager {
         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 class BackupManagerImpl extends ManagerBase 
implements BackupManager {
         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 class BackupManagerImpl extends ManagerBase 
implements BackupManager {
         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 class BackupManagerImpl extends ManagerBase 
implements BackupManager {
 
     @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<BackupScheduleVO> 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 class BackupManagerImpl extends ManagerBase 
implements BackupManager {
             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 @@ public class BackupManagerImpl extends ManagerBase 
implements BackupManager {
         }
     }
 
+    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 class BackupManagerImpl extends ManagerBase 
implements BackupManager {
         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");

Reply via email to