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");