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 ac6acdf42516..fab10808e647 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1489,6 +1489,18 @@ public class ApiConstants { public static final String SCHEDULED = "scheduled"; public static final String SCHEDULED_DATE = "scheduleddate"; public static final String BACKUP_PROVIDER = "backupprovider"; + public static final String SCHEDULE_REPORT = "schedulereport"; + public static final String DOMAIN_REPORT = "domainreport"; + public static final String ACCOUNT_REPORT = "accountreport"; + public static final String FAILURE_REASON = "failurereason"; + public static final String SUCCESSFUL_BACKUP = "sucessfulbackup"; + public static final String FAILED_BACKUP = "failedbackup"; + public static final String DELETED_BACKUP = "deletedbackup"; + public static final String PROVIDER_INFO = "providerinfo"; + public static final String LOGID = "logid"; + public static final String COMPRESSION_REPORT = "compressionreport"; + public static final String VALIDATION_REPORT = "validationreport"; + public static final String JOB_TYPE = "jobtype"; /** * This enum specifies IO Drivers, each option controls specific policies on I/O. diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/GetBackupReportCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/GetBackupReportCmd.java new file mode 100644 index 000000000000..19ba2770cc9f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/GetBackupReportCmd.java @@ -0,0 +1,133 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.backup; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.BackupReportResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupReportService; + +import javax.inject.Inject; +import java.util.Date; + +@APICommand(name = "getBackupReport", + description = "Get the backup report for the given period", + responseObject = BackupReportResponse.class, since = "4.24.0.0", authorized = {RoleType.Admin}) +public class GetBackupReportCmd extends BaseCmd { + + @Inject + private BackupReportService backupReportService; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "Get backup report by zone ID.") + private Long zoneId; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "Get backup report by domain ID.") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT_ID, + type = CommandType.UUID, + entityType = AccountResponse.class, + description = "Get backup report by account ID.") + private Long accountId; + + @Parameter(name = ApiConstants.PROJECT_ID, + type = CommandType.UUID, + entityType = ProjectResponse.class, + description = "Get backup report by project ID.") + private Long projectId; + + @Parameter(name = ApiConstants.START_DATE, + type = CommandType.DATE, + description = "Start date of the report.", + required = true) + private Date startDate; + + @Parameter(name = ApiConstants.END_DATE, + type = CommandType.DATE, + description = "End date of the report.", + required = true) + private Date endDate; + + public Long getZoneId() { + return zoneId; + } + + public Long getDomainId() { + if (domainId != null && (accountId != null || projectId != null)) { + throw new InvalidParameterValueException("domainid and accountid or projectid cannot be informed at the same time."); + } + return domainId; + } + + public Long getAccountId() { + if (projectId != null && accountId != null) { + throw new InvalidParameterValueException("accountid and projectid cannot be informed at the same time."); + } + return accountId; + } + + public Long getProjectId() { + return projectId; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + if (startDate.after(endDate)) { + throw new InvalidParameterValueException("End date must be after start date."); + } + return endDate; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, + NetworkRuleConflictException { + BackupReportResponse response = backupReportService.getBackupReport(getStartDate(), getEndDate(), getZoneId(), getDomainId(), getAccountId(), getProjectId()); + + response.setResponseName(getCommandName()); + response.setObjectName("backupreport"); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return 0; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupReportAccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportAccountResponse.java new file mode 100644 index 000000000000..d97e0d53ff55 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportAccountResponse.java @@ -0,0 +1,132 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import java.util.ArrayList; +import java.util.List; + +public class BackupReportAccountResponse extends BaseResponse { + + @SerializedName(ApiConstants.SUCCESSFUL_BACKUP) + @Param(description = "Successful backup.") + private List successfulBackups; + + @SerializedName(ApiConstants.FAILED_BACKUP) + @Param(description = "Failed backup.") + private List failedBackups; + + @SerializedName(ApiConstants.DELETED_BACKUP) + @Param(description = "Deleted backup.") + private List deletedBackups; + + @SerializedName(ApiConstants.BACKUP_STORAGE_TOTAL) + @Param(description = "Backup storage usage increase for the Account during the period.") + private Double storageUsage; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "Account ID.") + private String accountId; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "Account name.") + private String accountName; + + @SerializedName(ApiConstants.PROJECT_ID) + @Param(description = "Project ID.") + private String projectId; + + @SerializedName(ApiConstants.PROJECT) + @Param(description = "Project name.") + private String projectName; + + public BackupReportAccountResponse() { + this.successfulBackups = new ArrayList<>(); + this.failedBackups = new ArrayList<>(); + this.deletedBackups = new ArrayList<>(); + this.storageUsage = 0D; + } + + public void addSuccessfulBackup(BackupResponse successfulBackup) { + this.successfulBackups.add(successfulBackup); + } + + public void addFailedBackup(BackupResponse failedBackup) { + this.failedBackups.add(failedBackup); + } + + public void addDeletedBackup(BackupResponse deletedBackup) { + this.deletedBackups.add(deletedBackup); + } + + public List getSuccessfulBackups() { + return successfulBackups; + } + + public List getFailedBackups() { + return failedBackups; + } + + public List getDeletedBackups() { + return deletedBackups; + } + + public Double getStorageUsage() { + return storageUsage; + } + + public void addStorageUsage(double storageUsage) { + this.storageUsage += storageUsage; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public String getProjectId() { + return projectId; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupReportCompressionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportCompressionResponse.java new file mode 100644 index 000000000000..9f8add4207d7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportCompressionResponse.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import java.util.ArrayList; +import java.util.List; + +@EntityReference(value = BackupReportCompressionResponse.class) +public class BackupReportCompressionResponse extends BaseResponse { + + @SerializedName(ApiConstants.COMPRESSION_REPORT) + @Param(description = "Compressed backups in the period.") + private List backupResponseList; + + public BackupReportCompressionResponse() { + this.backupResponseList = new ArrayList<>(); + } + + public List getBackupResponseList() { + return backupResponseList; + } + + public void addBackupResponse (BackupResponse backupResponse) { + this.backupResponseList.add(backupResponse); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupReportDomainResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportDomainResponse.java new file mode 100644 index 000000000000..0ae720b55d13 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportDomainResponse.java @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import java.util.ArrayList; +import java.util.List; + +public class BackupReportDomainResponse extends BaseResponse { + + @SerializedName(ApiConstants.ACCOUNT_REPORT) + @Param(description = "Account report") + private List backupReportAccountResponseList; + + @SerializedName(ApiConstants.BACKUP_STORAGE_TOTAL) + @Param(description = "Backup storage usage increase for the Domain during the period.") + private Double storageUsage; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "Domain ID.") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "Domain name.") + private String domainName; + + public BackupReportDomainResponse(String domainId, String domainName) { + this.backupReportAccountResponseList = new ArrayList<>(); + this.domainId = domainId; + this.domainName = domainName; + this.storageUsage = 0D; + } + + public List getBackupReportAccountResponseList() { + return backupReportAccountResponseList; + } + + public void addBackupReportAccountResponse(BackupReportAccountResponse backupReportDomainResponse) { + this.backupReportAccountResponseList.add(backupReportDomainResponse); + } + + public Double getStorageUsage() { + return storageUsage; + } + + public void addStorageUsage(double storageUsage) { + this.storageUsage += storageUsage; + } + + public String getDomainId() { + return domainId; + } + + public String getDomainName() { + return domainName; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupReportResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportResponse.java new file mode 100644 index 000000000000..ee9d5b6e2f93 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportResponse.java @@ -0,0 +1,112 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@EntityReference(value = BackupReportResponse.class) +public class BackupReportResponse extends BaseResponse { + @SerializedName(ApiConstants.DOMAIN_REPORT) + @Param(description = "List of backup reports per domain.") + private List backupReportDomainResponseList; + + @SerializedName(ApiConstants.SCHEDULE_REPORT) + @Param(description = "List of scheduled backups to be taken.") + private List backupScheduleResponseList; + + @SerializedName(ApiConstants.PROVIDER_INFO) + @Param(description = "Provider information.") + private List providerInfo; + + @SerializedName(ApiConstants.START_DATE) + @Param(description = "Start date of the report.") + private Date startDate; + + @SerializedName(ApiConstants.END_DATE) + @Param(description = "End date of the report.") + private Date endDate; + + @SerializedName(ApiConstants.BACKUP_STORAGE_TOTAL) + @Param(description = "Backup storage usage increase for the whole environment during the period.") + private Double storageUsage; + + public BackupReportResponse(Date startDate, Date endDate) { + this.backupReportDomainResponseList = new ArrayList<>(); + this.backupScheduleResponseList = new ArrayList<>(); + this.providerInfo = new ArrayList<>(); + this.startDate = startDate; + this.endDate = endDate; + this.storageUsage = 0D; + } + + public List getBackupReportDomainResponseList() { + return backupReportDomainResponseList; + } + + public void addBackupReportDomainResponse(BackupReportDomainResponse response) { + this.backupReportDomainResponseList.add(response); + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public Double getStorageUsage() { + return storageUsage; + } + + public void addStorageUsage(double storageUsage) { + this.storageUsage += storageUsage; + } + + public List getBackupScheduleResponseList() { + return backupScheduleResponseList; + } + + public void addBackupScheduleResponse(BackupScheduleResponse backupScheduleResponse) { + this.backupScheduleResponseList.add(backupScheduleResponse); + } + + public List getProviderInfo() { + return providerInfo; + } + + public void addProviderInfo(List providerInfo) { + this.providerInfo.addAll(providerInfo); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupReportValidationResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportValidationResponse.java new file mode 100644 index 000000000000..17ca01f39309 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupReportValidationResponse.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import java.util.ArrayList; +import java.util.List; + +@EntityReference(value = BackupReportValidationResponse.class) +public class BackupReportValidationResponse extends BaseResponse { + + @SerializedName(ApiConstants.VALIDATION_REPORT) + @Param(description = "Validated backups in the period.") + private List backupResponseList; + + public BackupReportValidationResponse() { + this.backupResponseList = new ArrayList<>(); + } + + public List getBackupResponseList() { + return backupResponseList; + } + + public void addBackupResponse (BackupResponse backupResponse) { + this.backupResponseList.add(backupResponse); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java index 70db01445edd..f7f6d9af4cc5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java @@ -63,6 +63,10 @@ public class BackupResponse extends BaseResponse { @Param(description = "Backup date") private Date date; + @SerializedName(ApiConstants.REMOVED) + @Param(description = "backup deletion date") + private Date removed; + @SerializedName(ApiConstants.SIZE) @Param(description = "Backup size in bytes") private Long size; @@ -107,6 +111,14 @@ public class BackupResponse extends BaseResponse { @Param(description = "Account name") private String account; + @SerializedName(ApiConstants.PROJECT_ID) + @Param(description = "project id") + private String projectId; + + @SerializedName(ApiConstants.PROJECT) + @Param(description = "project name") + private String projectName; + @SerializedName(ApiConstants.DOMAIN_ID) @Param(description = "Domain ID") private String domainId; @@ -143,6 +155,30 @@ public class BackupResponse extends BaseResponse { @Param(description = "Previous active checkpoint ID for incremental backups", since = "4.23.0") private String fromCheckpointId; + @SerializedName(ApiConstants.FAILURE_REASON) + @Param(description = "The reason for the backup failure, as reported by the provider.") + private String failureReason; + + @SerializedName(ApiConstants.LOGID) + @Param(description = "The logid of the backup creation process.") + private String logid; + + @SerializedName(ApiConstants.ATTEMPTS) + @Param(description = "Number of attempts on the backup service job.") + private String attempts; + + @SerializedName(ApiConstants.JOB_TYPE) + @Param(description = "Type of backup service job executed.") + private String jobType; + + @SerializedName(ApiConstants.START_DATE) + @Param(description = "Start date of the backup service job.") + private Date startDate; + + @SerializedName(ApiConstants.END_DATE) + @Param(description = "End date of the backup service job.") + private Date endDate; + @SerializedName(ApiConstants.TO_CHECKPOINT_ID) @Param(description = "Next checkpoint ID for incremental backups", since = "4.23.0") private String toCheckpointId; @@ -379,6 +415,82 @@ public String getToCheckpointId() { return this.toCheckpointId; } + public String getFailureReason() { + return failureReason; + } + + public void setFailureReason(String failureReason) { + this.failureReason = failureReason; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public String getLogid() { + return logid; + } + + public void setLogid(String logid) { + this.logid = logid; + } + + public String getBackupOfferingName() { + return backupOfferingName; + } + + public void setBackupOfferingName(String backupOfferingName) { + this.backupOfferingName = backupOfferingName; + } + + public String getAttempts() { + return attempts; + } + + public void setAttempts(String attempts) { + this.attempts = attempts; + } + + public String getJobType() { + return jobType; + } + + public void setJobType(String jobType) { + this.jobType = jobType; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + public void setHostId(String hostId) { this.hostId = hostId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java index 5da07864603a..072a0479c780 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java @@ -60,14 +60,38 @@ public class BackupScheduleResponse extends BaseResponse { @Param(description = ApiConstants.PARAMETER_DESCRIPTION_ISOLATED_BACKUPS) private boolean isolated; - public void setId(String id) { - this.id = id; - } - @SerializedName(ApiConstants.QUIESCE_VM) @Param(description = "quiesce the instance before checkpointing the disks for backup") private Boolean quiesceVM; + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "the account that the backup schedule is associated with") + private String account; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "the ID of the account that the backup schedule is associated with") + private String accountId; + + @SerializedName(ApiConstants.PROJECT) + @Param(description = "the project name of the backup schedule") + private String projectName; + + @SerializedName(ApiConstants.PROJECT_ID) + @Param(description = "the project ID of the backup schedule") + private String projectId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "the domain that the backup schedule is associated with") + private String domain; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "the domain ID that the backup schedule is associated with") + private String domainid; + + public void setId(String id) { + this.id = id; + } + public String getVmName() { return vmName; } @@ -119,4 +143,60 @@ public void setQuiesceVM(Boolean quiesceVM) { public void setIsolated(boolean isolated) { this.isolated = isolated; } + + public boolean isIsolated() { + return isolated; + } + + public String getId() { + return id; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getProjectId() { + return projectId; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String getDomainid() { + return domainid; + } + + public void setDomainid(String domainid) { + this.domainid = domainid; + } } 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 66e4501c460e..02d53f80f4ea 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -16,6 +16,7 @@ //under the License. package org.apache.cloudstack.backup; +import java.util.Date; import java.util.List; import com.cloud.utils.Pair; @@ -145,6 +146,10 @@ default boolean supportsMemoryVmSnapshot() { return true; } + default List getBackupReport(Date startDate, Date endDate, long zoneId, Long domainId, Long accountId) { + return List.of(); + } + /** * Returns the backup storage usage (Used, Total) for a backup provider * @param zoneId the zone for which to return metrics diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupReportService.java b/api/src/main/java/org/apache/cloudstack/backup/BackupReportService.java new file mode 100644 index 000000000000..7ac9ffd80e12 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupReportService.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import org.apache.cloudstack.api.response.BackupReportResponse; + +import java.util.Date; + +public interface BackupReportService { + + BackupReportResponse getBackupReport(Date startDate, Date endDate, Long zoneId, Long domainId, Long accountId, Long projectId); +} diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42020to42030.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42020to42030.java index e0aa38a717b2..1a8661ee4cf4 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42020to42030.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42020to42030.java @@ -18,11 +18,102 @@ import java.io.InputStream; import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import com.cloud.utils.exception.CloudRuntimeException; public class Upgrade42020to42030 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { + private static String SELECT_TEMPLATE = "SELECT `name` FROM `cloud`.`email_template` WHERE `name`=\"backup_report_template\";"; + private static String INSERT_TEMPLATE = "INSERT INTO `cloud`.`email_template` (name, template) VALUES (\"backup_report_template\", \'

Backup report

\n" + + "

Backup storage usage increase for the environment during the period is ${storageUsage} GiB.

\n" + + "\n" + + "<#list backupReportDomainResponseList as domain>\n" + + "
\n" + + "

Domain ${domain.domainName}

\n" + + "

Backup storage usage increase for the domain ${domain.domainName} is ${domain.storageUsage} GiB.

\n" + + "\n" + + " <#list domain.backupReportAccountResponseList as account>\n" + + "
\n" + + " <#if account.accountName??>\n" + + "

Account ${account.accountName}

\n" + + "

Backup storage usage increase for the account ${account.accountName} is ${account.storageUsage} GiB.

\n" + + " <#else>\n" + + "

Project ${account.projectName}

\n" + + "

Backup storage usage increase for the project ${account.projectName} is ${account.storageUsage} GiB.

\n" + + " \n" + + " \n" + + "\n" + + " <#list account.successfulBackups>\n" + + "

Successfully created backups for the ${(account.accountName??)?then(\"project\",\"account\")}:

\n" + + "
    \n" + + " <#items as backup>\n" + + "
  • Backup ${backup.name!\"without name\"} with ID ${backup.id} of VM ${backup.vmName} with ID ${backup.vmId} was created at ${backup.date?datetime};
  • \n" + + " \n" + + "
\n" + + " \n" + + "\n" + + " <#list account.failedBackups>\n" + + "

Failed backups for the ${(account.accountName??)?then(\"project\",\"account\")}:

\n" + + "
    \n" + + "\n" + + " <#items as backup>\n" + + "
  • Backup ${backup.name!\"without name\"} with ID ${backup.id} of VM ${backup.vmName} with ID ${backup.vmId} failed at ${(backup.date?datetime)!\"unable to get date\"};\n" + + "\n" + + " <#if backup.failureReason?? || backup.logid??>\n" + + "
      \n" + + " <#if backup.failureReason??>\n" + + "
    • Due to ${backup.failureReason}
    • \n" + + " \n" + + " <#if backup.logid??>\n" + + "
    • With logid:${backup.logid}
    • \n" + + " \n" + + "
    \n" + + " \n" + + " \n" + + "\n" + + "
\n" + + " \n" + + "\n" + + " <#list account.deletedBackups>\n" + + "

Removed backups for the ${(account.accountName??)?then(\"project\",\"account\")}:

\n" + + "
    \n" + + " <#items as backup>\n" + + "
  • Backup ${backup.name} with ID ${backup.id} of VM ${backup.vmName} with ID ${backup.vmId} was created at ${(backup.date?datetime)!\"unable to get date\"} and deleted at ${backup.removed?datetime};
  • \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + "\n" + + "\n" + + "<#list backupScheduleResponseList>\n" + + "
\n" + + "

Scheduled backups

\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " <#items as schedule>\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "
VMIDScheduled dateSchedule ID
${schedule.vmName}${schedule.vmId}${schedule.schedule}${schedule.id}
\n" + + "\n\')"; + @Override public String[] getUpgradableVersionRange() { return new String[]{"4.20.2.0", "4.20.3.0"}; @@ -51,6 +142,29 @@ public InputStream[] getPrepareScripts() { @Override public void performDataMigration(Connection conn) { + insertBackupReportEmailTemplate(conn); + } + + private void insertBackupReportEmailTemplate(Connection conn) { + try (PreparedStatement pstmt = conn.prepareStatement(SELECT_TEMPLATE)) { + ResultSet result = pstmt.executeQuery(); + if (result.next()) { + logger.debug("Email template for backup_report_template is already on the database."); + return; + } + } catch (SQLException e) { + String message = String.format("Unable to retrieve email templates due to [%s].", e.getMessage()); + logger.error(message, e); + throw new CloudRuntimeException(message, e); + } + + try (PreparedStatement pstmt = conn.prepareStatement(INSERT_TEMPLATE)) { + pstmt.executeUpdate(); + } catch (SQLException e) { + String message = String.format("Unable to insert email template for backup_report_template due to [%s].", e.getMessage()); + logger.error(message, e); + throw new CloudRuntimeException(message, e); + } } @Override diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java index b60a52bba88b..1e1643fa7e72 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java @@ -29,6 +29,96 @@ public class Upgrade42210to42300 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { + // This must be moved to the new upgrade class when 4.23 is released + private static String SELECT_TEMPLATE = "SELECT `name` FROM `cloud`.`email_template` WHERE `name`=\"backup_report_template\";"; + private static String INSERT_TEMPLATE = "INSERT INTO `cloud`.`email_template` (name, template) VALUES (\"backup_report_template\", \'

Backup report

\n" + + "

Backup storage usage increase for the environment during the period is ${storageUsage} GiB.

\n" + + "\n" + + "<#list backupReportDomainResponseList as domain>\n" + + "
\n" + + "

Domain ${domain.domainName}

\n" + + "

Backup storage usage increase for the domain ${domain.domainName} is ${domain.storageUsage} GiB.

\n" + + "\n" + + " <#list domain.backupReportAccountResponseList as account>\n" + + "
\n" + + " <#if account.accountName??>\n" + + "

Account ${account.accountName}

\n" + + "

Backup storage usage increase for the account ${account.accountName} is ${account.storageUsage} GiB.

\n" + + " <#else>\n" + + "

Project ${account.projectName}

\n" + + "

Backup storage usage increase for the project ${account.projectName} is ${account.storageUsage} GiB.

\n" + + " \n" + + " \n" + + "\n" + + " <#list account.successfulBackups>\n" + + "

Successfully created backups for the ${(account.accountName??)?then(\"project\",\"account\")}:

\n" + + "
    \n" + + " <#items as backup>\n" + + "
  • Backup ${backup.name!\"without name\"} with ID ${backup.id} of VM ${backup.vmName} with ID ${backup.vmId} was created at ${backup.date?datetime};
  • \n" + + " \n" + + "
\n" + + " \n" + + "\n" + + " <#list account.failedBackups>\n" + + "

Failed backups for the ${(account.accountName??)?then(\"project\",\"account\")}:

\n" + + "
    \n" + + "\n" + + " <#items as backup>\n" + + "
  • Backup ${backup.name!\"without name\"} with ID ${backup.id} of VM ${backup.vmName} with ID ${backup.vmId} failed at ${(backup.date?datetime)!\"unable to get date\"};\n" + + "\n" + + " <#if backup.failureReason?? || backup.logid??>\n" + + "
      \n" + + " <#if backup.failureReason??>\n" + + "
    • Due to ${backup.failureReason}
    • \n" + + " \n" + + " <#if backup.logid??>\n" + + "
    • With logid:${backup.logid}
    • \n" + + " \n" + + "
    \n" + + " \n" + + " \n" + + "\n" + + "
\n" + + " \n" + + "\n" + + " <#list account.deletedBackups>\n" + + "

Removed backups for the ${(account.accountName??)?then(\"project\",\"account\")}:

\n" + + "
    \n" + + " <#items as backup>\n" + + "
  • Backup ${backup.name} with ID ${backup.id} of VM ${backup.vmName} with ID ${backup.vmId} was created at ${(backup.date?datetime)!\"unable to get date\"} and deleted at ${backup.removed?datetime};
  • \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + "\n" + + "\n" + + "<#list backupScheduleResponseList>\n" + + "
\n" + + "

Scheduled backups

\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " <#items as schedule>\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "
VMIDScheduled dateSchedule ID
${schedule.vmName}${schedule.vmId}${schedule.schedule}${schedule.id}
\n" + + "\n\')"; + + @Override public String[] getUpgradableVersionRange() { return new String[]{"4.22.1.0", "4.23.0.0"}; @@ -54,6 +144,7 @@ public InputStream[] getPrepareScripts() { public void performDataMigration(Connection conn) { unhideJsInterpretationEnabled(conn); dropUsageVmInstanceIndex(conn); + insertBackupReportEmailTemplate(conn); } protected void unhideJsInterpretationEnabled(Connection conn) { @@ -99,4 +190,27 @@ private void dropUsageVmInstanceIndex(Connection conn) { indexList.add("vm_instance_id"); DbUpgradeUtils.dropKeysIfExist(conn, "cloud_usage.usage_vm_instance", indexList, false); } + + private void insertBackupReportEmailTemplate(Connection conn) { + try (PreparedStatement pstmt = conn.prepareStatement(SELECT_TEMPLATE)) { + ResultSet result = pstmt.executeQuery(); + if (result.next()) { + logger.debug("Email template for backup_report_template is already on the database."); + return; + } + } catch (SQLException e) { + String message = String.format("Unable to retrieve email templates due to [%s].", e.getMessage()); + logger.error(message, e); + throw new CloudRuntimeException(message, e); + } + + try (PreparedStatement pstmt = conn.prepareStatement(INSERT_TEMPLATE)) { + pstmt.executeUpdate(); + } catch (SQLException e) { + String message = String.format("Unable to insert email template for backup_report_template due to [%s].", e.getMessage()); + logger.error(message, e); + throw new CloudRuntimeException(message, e); + } + } + } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportJoinVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportJoinVO.java new file mode 100644 index 000000000000..41a9c8094bb6 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportJoinVO.java @@ -0,0 +1,207 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.utils.db.GenericDao; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +@Entity +@Table(name = "backup_report_view") +public class BackupReportJoinVO { + + @Column(name = "zone_id") + private long zoneId; + + @Column(name="zone_uuid") + private String zoneUuid; + + @Column(name = "zone_name") + private String zoneName; + + @Column(name = "domain_id") + private long domainId; + + @Column(name="domain_uuid") + private String domainUuid; + + @Column(name = "domain_name") + private String domainName; + + @Column(name = "account_id") + private long accountId; + + @Column(name="account_uuid") + private String accountUuid; + + @Column(name = "account_name") + private String accountName; + + @Column(name="project_uuid") + private String projectUuid; + + @Column(name = "project_name") + private String projectName; + + @Column(name = "vm_id") + private long vmId; + + @Column(name="vm_uuid") + private String vmUuid; + + @Column(name = "vm_name") + private String vmName; + + @Column(name = "backup_id") + private long backupId; + + @Column(name="backup_uuid") + private String backupUuid; + + @Column(name = "backup_name") + private String backupName; + + @Column(name = "offering_name") + private String offeringName; + + @Column(name = "size") + private long size; + + @Enumerated(value = EnumType.STRING) + @Column(name = "status") + private Backup.Status status; + + @Column(name = "failure_reason") + private String failureReason; + + @Column(name = "logid") + private String logid; + + @Column(name = "date") + @Temporal(value = TemporalType.DATE) + private Date date; + + @Column(name = GenericDao.REMOVED_COLUMN) + private Date removed; + + public BackupReportJoinVO() { + } + + public long getZoneId() { + return zoneId; + } + + public String getZoneUuid() { + return zoneUuid; + } + + public String getZoneName() { + return zoneName; + } + + public long getDomainId() { + return domainId; + } + + public String getDomainUuid() { + return domainUuid; + } + + public String getDomainName() { + return domainName; + } + + public long getAccountId() { + return accountId; + } + + public String getAccountUuid() { + return accountUuid; + } + + public String getAccountName() { + return accountName; + } + + public String getProjectUuid() { + return projectUuid; + } + + public String getProjectName() { + return projectName; + } + + public long getVmId() { + return vmId; + } + + public String getVmUuid() { + return vmUuid; + } + + public String getVmName() { + return vmName; + } + + public long getBackupId() { + return backupId; + } + + public String getBackupUuid() { + return backupUuid; + } + + public String getBackupName() { + return backupName; + } + + public String getOfferingName() { + return offeringName; + } + + public long getSize() { + return size; + } + + public Backup.Status getStatus() { + return status; + } + + public String getFailureReason() { + return failureReason; + } + + public Date getDate() { + return date; + } + + public Date getRemoved() { + return removed; + } + + public String getLogid() { + return logid; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportKbossJoinVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportKbossJoinVO.java new file mode 100644 index 000000000000..bfe245a80e05 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportKbossJoinVO.java @@ -0,0 +1,247 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.utils.db.GenericDao; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +@Entity +@Table(name = "backup_report_kboss_view") +public class BackupReportKbossJoinVO { + + @Column(name = "zone_id") + private long zoneId; + + @Column(name="zone_uuid") + private String zoneUuid; + + @Column(name = "zone_name") + private String zoneName; + + @Column(name = "domain_id") + private long domainId; + + @Column(name="domain_uuid") + private String domainUuid; + + @Column(name = "domain_name") + private String domainName; + + @Column(name = "account_id") + private long accountId; + + @Column(name="account_uuid") + private String accountUuid; + + @Column(name = "account_name") + private String accountName; + + @Column(name="project_uuid") + private String projectUuid; + + @Column(name = "project_name") + private String projectName; + + @Column(name = "vm_id") + private long vmId; + + @Column(name="vm_uuid") + private String vmUuid; + + @Column(name = "vm_name") + private String vmName; + + @Column(name = "backup_id") + private long backupId; + + @Column(name="backup_uuid") + private String backupUuid; + + @Column(name = "backup_name") + private String backupName; + + @Column(name = "offering_name") + private String offeringName; + + @Column(name = "size") + private long size; + + @Column(name = "uncompressed_size") + private long uncompressedSize; + + @Enumerated(value = EnumType.STRING) + @Column(name = "compression_status") + private Backup.CompressionStatus compressionStatus; + + @Enumerated(value = EnumType.STRING) + @Column(name = "validation_status") + private Backup.ValidationStatus validationStatus; + + @Column(name = "backup_date") + @Temporal(value = TemporalType.DATE) + private Date backupDate; + + @Column(name = "backup_removed") + @Temporal(value = TemporalType.DATE) + private Date backupRemoved; + + @Column(name = "job_id") + private Long jobId; + + @Column(name = "attempts") + private String attempts; + + @Enumerated(value = EnumType.STRING) + @Column(name = "type") + private InternalBackupServiceJobType type; + + @Column(name = "start_time") + @Temporal(value = TemporalType.TIMESTAMP) + private Date startTime; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + public BackupReportKbossJoinVO() { + } + + public long getZoneId() { + return zoneId; + } + + public String getZoneUuid() { + return zoneUuid; + } + + public String getZoneName() { + return zoneName; + } + + public long getDomainId() { + return domainId; + } + + public String getDomainUuid() { + return domainUuid; + } + + public String getDomainName() { + return domainName; + } + + public long getAccountId() { + return accountId; + } + + public String getAccountUuid() { + return accountUuid; + } + + public String getAccountName() { + return accountName; + } + + public String getProjectUuid() { + return projectUuid; + } + + public String getProjectName() { + return projectName; + } + + public long getVmId() { + return vmId; + } + + public String getVmUuid() { + return vmUuid; + } + + public String getVmName() { + return vmName; + } + + public long getBackupId() { + return backupId; + } + + public String getBackupUuid() { + return backupUuid; + } + + public String getBackupName() { + return backupName; + } + + public String getOfferingName() { + return offeringName; + } + + public long getSize() { + return size; + } + + public Backup.CompressionStatus getCompressionStatus() { + return compressionStatus; + } + + public Backup.ValidationStatus getValidationStatus() { + return validationStatus; + } + + public Date getBackupDate() { + return backupDate; + } + + public Date getBackupRemoved() { + return backupRemoved; + } + + public Long getJobId() { + return jobId; + } + + public String getAttempts() { + return attempts; + } + + public InternalBackupServiceJobType getType() { + return type; + } + + public Date getStartTime() { + return startTime; + } + + public Date getRemoved() { + return removed; + } + + public long getUncompressedSize() { + return uncompressedSize; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportVO.java new file mode 100644 index 000000000000..6a79132ca0ae --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupReportVO.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.api.InternalIdentity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + +@Entity +@Table(name = "backup_report") +public class BackupReportVO implements InternalIdentity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + private Date removed; + + @Column(name = "task_enabled") + private boolean taskEnabled; + + public BackupReportVO() { + } + + public BackupReportVO(Date created, boolean taskEnabled) { + this.created = created; + this.taskEnabled = taskEnabled; + } + + @Override + public long getId() { + return id; + } + + public Date getCreated() { + return created; + } + + public Date getRemoved() { + return removed; + } + + public boolean isTaskEnabled() { + return taskEnabled; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public void setTaskEnabled(boolean taskEnabled) { + this.taskEnabled = taskEnabled; + } +} 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 b4cad92d8770..7546d79d47ac 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 @@ -123,6 +123,12 @@ public class BackupVO implements Backup { @Column(name = "host_id") private Long hostId; + @Column(name = "failure_reason") + private String failureReason; + + @Column(name = "logid") + private String logid; + @Transient Map details; @@ -131,7 +137,7 @@ public BackupVO() { } public BackupVO(String name, long vmId, long backupOfferingId, long accountId, long domainId, long zoneId, long virtualSize, - Status status, Long backupScheduleId, CompressionStatus compressionStatus, ValidationStatus validationStatus) { + Status status, Long backupScheduleId, CompressionStatus compressionStatus, ValidationStatus validationStatus, String logid) { this.name = name; this.vmId = vmId; this.backupOfferingId = backupOfferingId; @@ -145,6 +151,7 @@ public BackupVO(String name, long vmId, long backupOfferingId, long accountId, l this.backupScheduleId = backupScheduleId; this.compressionStatus = compressionStatus; this.validationStatus = validationStatus; + this.logid = logid; } @Override @@ -353,6 +360,22 @@ public void setUncompressedSize(Long uncompressedSize) { this.uncompressedSize = uncompressedSize; } + public String getFailureReason() { + return failureReason; + } + + public void setFailureReason(String failureReason) { + this.failureReason = failureReason; + } + + public String getLogid() { + return logid; + } + + public void setLogid(String logid) { + this.logid = logid; + } + @Override public String getFromCheckpointId() { return fromCheckpointId; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportDao.java new file mode 100644 index 000000000000..8dddbadf9c15 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportDao.java @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.backup.BackupReportVO; + +public interface BackupReportDao extends GenericDao { + + BackupReportVO findLatest(); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportDaoImpl.java new file mode 100644 index 000000000000..77a94ec81638 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportDaoImpl.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import org.apache.cloudstack.backup.BackupReportVO; + +public class BackupReportDaoImpl extends GenericDaoBase implements BackupReportDao { + + public BackupReportDaoImpl () { + } + + @Override + public BackupReportVO findLatest() { + Filter filter = new Filter(BackupReportVO.class, "created", false); + return findOneIncludingRemovedBy(null, filter); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportJoinDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportJoinDao.java new file mode 100644 index 000000000000..50d47d65e833 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportJoinDao.java @@ -0,0 +1,29 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.backup.BackupReportJoinVO; + +import java.util.Date; +import java.util.List; + +public interface BackupReportJoinDao extends GenericDao { + + List listByZoneAndDomainAndAccountAndBetweenDates(Long zoneId, Long domainId, Long accountId, Date start, Date end); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportJoinDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportJoinDaoImpl.java new file mode 100644 index 000000000000..d50d77debddb --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportJoinDaoImpl.java @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.backup.BackupReportJoinVO; + +import java.util.Date; +import java.util.List; + + +public class BackupReportJoinDaoImpl extends GenericDaoBase implements BackupReportJoinDao { + + private static final String ZONE_ID = "zone_id"; + private static final String DOMAIN_ID = "domain_id"; + private static final String ACCOUNT_ID = "account_id"; + private static final String DATE = "date"; + private static final String REMOVED = "removed"; + + private SearchBuilder listByZoneAndDomainAndAccountAndDateBetween; + + public BackupReportJoinDaoImpl() { + listByZoneAndDomainAndAccountAndDateBetween = createSearchBuilder(); + listByZoneAndDomainAndAccountAndDateBetween.and(ZONE_ID, listByZoneAndDomainAndAccountAndDateBetween.entity().getZoneId(), SearchCriteria.Op.EQ); + listByZoneAndDomainAndAccountAndDateBetween.and(DOMAIN_ID, listByZoneAndDomainAndAccountAndDateBetween.entity().getDomainId(), SearchCriteria.Op.EQ); + listByZoneAndDomainAndAccountAndDateBetween.and(ACCOUNT_ID, listByZoneAndDomainAndAccountAndDateBetween.entity().getAccountId(), SearchCriteria.Op.EQ); + listByZoneAndDomainAndAccountAndDateBetween.and().op(DATE, listByZoneAndDomainAndAccountAndDateBetween.entity().getDate(), SearchCriteria.Op.BETWEEN); + listByZoneAndDomainAndAccountAndDateBetween.or(REMOVED, listByZoneAndDomainAndAccountAndDateBetween.entity().getRemoved(), SearchCriteria.Op.BETWEEN); + listByZoneAndDomainAndAccountAndDateBetween.cp(); + listByZoneAndDomainAndAccountAndDateBetween.done(); + } + + @Override + public List listByZoneAndDomainAndAccountAndBetweenDates(Long zoneId, Long domainId, Long accountId, Date start, Date end) { + SearchCriteria sc = listByZoneAndDomainAndAccountAndDateBetween.create(); + sc.setParametersIfNotNull(ZONE_ID, zoneId); + sc.setParametersIfNotNull(DOMAIN_ID, domainId); + sc.setParametersIfNotNull(ACCOUNT_ID, accountId); + sc.setParameters(DATE, start, end); + sc.setParameters(REMOVED, start, end); + Filter filter = new Filter(BackupReportJoinVO.class, "domainId", true); + filter.addOrderBy(BackupReportJoinVO.class, "accountId", true); + filter.addOrderBy(BackupReportJoinVO.class, "vmId", true); + filter.addOrderBy(BackupReportJoinVO.class, DATE, true); + + return searchIncludingRemoved(sc, filter, null, false); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportKbossJoinDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportKbossJoinDao.java new file mode 100644 index 000000000000..9f27707ee69f --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportKbossJoinDao.java @@ -0,0 +1,29 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.backup.BackupReportKbossJoinVO; + +import java.util.Date; +import java.util.List; + +public interface BackupReportKbossJoinDao extends GenericDao { + + List listByZoneAndDomainAndAccountAndBetweenDates(Long zoneId, Long domainId, Long accountId, Date start, Date end); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportKbossJoinDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportKbossJoinDaoImpl.java new file mode 100644 index 000000000000..16f404487140 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupReportKbossJoinDaoImpl.java @@ -0,0 +1,58 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.backup.BackupReportKbossJoinVO; + +import java.util.Date; +import java.util.List; + +public class BackupReportKbossJoinDaoImpl extends GenericDaoBase implements BackupReportKbossJoinDao { + private static final String ZONE_ID = "zone_id"; + private static final String DOMAIN_ID = "domain_id"; + private static final String ACCOUNT_ID = "account_id"; + private static final String REMOVED = "removed"; + + private SearchBuilder listByZoneAndDomainAndAccountAndDateBetween; + + public BackupReportKbossJoinDaoImpl() { + listByZoneAndDomainAndAccountAndDateBetween = createSearchBuilder(); + listByZoneAndDomainAndAccountAndDateBetween.and(ZONE_ID, listByZoneAndDomainAndAccountAndDateBetween.entity().getZoneId(), SearchCriteria.Op.EQ); + listByZoneAndDomainAndAccountAndDateBetween.and(DOMAIN_ID, listByZoneAndDomainAndAccountAndDateBetween.entity().getDomainId(), SearchCriteria.Op.EQ); + listByZoneAndDomainAndAccountAndDateBetween.and(ACCOUNT_ID, listByZoneAndDomainAndAccountAndDateBetween.entity().getAccountId(), SearchCriteria.Op.EQ); + listByZoneAndDomainAndAccountAndDateBetween.and(REMOVED, listByZoneAndDomainAndAccountAndDateBetween.entity().getRemoved(), SearchCriteria.Op.BETWEEN); + listByZoneAndDomainAndAccountAndDateBetween.done(); + } + + @Override + public List listByZoneAndDomainAndAccountAndBetweenDates(Long zoneId, Long domainId, Long accountId, Date start, Date end) { + SearchCriteria sc = listByZoneAndDomainAndAccountAndDateBetween.create(); + sc.setParametersIfNotNull(ZONE_ID, zoneId); + sc.setParametersIfNotNull(DOMAIN_ID, domainId); + sc.setParametersIfNotNull(ACCOUNT_ID, accountId); + sc.setParameters(REMOVED, start, end); + Filter filter = new Filter(BackupReportKbossJoinVO.class, "domainId", true); + filter.addOrderBy(BackupReportKbossJoinVO.class, "accountId", true); + filter.addOrderBy(BackupReportKbossJoinVO.class, "vmId", true); + + return searchIncludingRemoved(sc, filter, null, false); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java index 87b7dab1ff7b..72492d2feae1 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java @@ -31,4 +31,6 @@ public interface BackupScheduleDao extends GenericDao { BackupScheduleVO findByVMAndIntervalType(Long vmId, DateUtil.IntervalType intervalType); List getSchedulesToExecute(Date currentTimestamp); + + List getSchedulesToExecuteForDomainAndAccount(Date currentTimestamp, Long zoneId, Long domainId, Long accountId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java index 972af73391af..e24cf4789a5c 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java @@ -23,10 +23,14 @@ import java.util.List; import javax.annotation.PostConstruct; +import javax.inject.Inject; import com.cloud.utils.DateUtil; import com.cloud.utils.db.DB; +import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.TransactionLegacy; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.backup.BackupScheduleVO; import com.cloud.utils.db.GenericDaoBase; @@ -34,8 +38,13 @@ import com.cloud.utils.db.SearchCriteria; public class BackupScheduleDaoImpl extends GenericDaoBase implements BackupScheduleDao { + + @Inject + private VMInstanceDao vmInstanceDao; + private SearchBuilder backupScheduleSearch; private SearchBuilder executableSchedulesSearch; + private SearchBuilder listExecutableSchedulesByZoneAndDomainAndAccount; public BackupScheduleDaoImpl() { } @@ -52,6 +61,17 @@ protected void init() { executableSchedulesSearch.and("scheduledTimestamp", executableSchedulesSearch.entity().getScheduledTimestamp(), SearchCriteria.Op.LT); executableSchedulesSearch.and("asyncJobId", executableSchedulesSearch.entity().getAsyncJobId(), SearchCriteria.Op.NULL); executableSchedulesSearch.done(); + + listExecutableSchedulesByZoneAndDomainAndAccount = createSearchBuilder(); + listExecutableSchedulesByZoneAndDomainAndAccount.and("account_id", listExecutableSchedulesByZoneAndDomainAndAccount.entity().getAccountId(), SearchCriteria.Op.EQ); + listExecutableSchedulesByZoneAndDomainAndAccount.and("domain_id", listExecutableSchedulesByZoneAndDomainAndAccount.entity().getDomainId(), SearchCriteria.Op.EQ); + listExecutableSchedulesByZoneAndDomainAndAccount.and("scheduledTimestamp", listExecutableSchedulesByZoneAndDomainAndAccount.entity().getScheduledTimestamp(), SearchCriteria.Op.LT); + listExecutableSchedulesByZoneAndDomainAndAccount.and("asyncJobId", listExecutableSchedulesByZoneAndDomainAndAccount.entity().getAsyncJobId(), SearchCriteria.Op.NULL); + SearchBuilder join = vmInstanceDao.createSearchBuilder(); + join.and("zone_id", join.entity().getDataCenterId(), SearchCriteria.Op.EQ); + listExecutableSchedulesByZoneAndDomainAndAccount.join("vms", join, listExecutableSchedulesByZoneAndDomainAndAccount.entity().getVmId(), join.entity().getId(), + JoinBuilder.JoinType.INNER); + listExecutableSchedulesByZoneAndDomainAndAccount.done(); } @Override @@ -76,6 +96,16 @@ public List getSchedulesToExecute(Date currentTimestamp) { return listBy(sc); } + @Override + public List getSchedulesToExecuteForDomainAndAccount(Date currentTimestamp, Long zoneId, Long domainId, Long accountId) { + SearchCriteria sc = listExecutableSchedulesByZoneAndDomainAndAccount.create(); + sc.setParameters("scheduledTimestamp", currentTimestamp); + sc.setParametersIfNotNull("domain_id", domainId); + sc.setParametersIfNotNull("account_id", accountId); + sc.setJoinParametersIfNotNull("vms", "zone_id", zoneId); + return listBy(sc); + } + @DB @Override public boolean remove(Long id) { diff --git a/engine/schema/src/main/java/org/apache/cloudstack/email/template/EmailTemplateVO.java b/engine/schema/src/main/java/org/apache/cloudstack/email/template/EmailTemplateVO.java new file mode 100644 index 000000000000..a6ba33c69cce --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/email/template/EmailTemplateVO.java @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.email.template; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "email_template") +public class EmailTemplateVO { + @Id + @Column(name = "id") + private long id; + + @Column(name = "name") + private String name; + + @Column(name = "template", length = 65535) + private String template; + + public EmailTemplateVO() { + } + + public String getName() { + return name; + } + + public String getTemplate() { + return template; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/email/template/dao/EmailTemplateDao.java b/engine/schema/src/main/java/org/apache/cloudstack/email/template/dao/EmailTemplateDao.java new file mode 100644 index 000000000000..10b10129162a --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/email/template/dao/EmailTemplateDao.java @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.email.template.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.email.template.EmailTemplateVO; + +public interface EmailTemplateDao extends GenericDao { + + EmailTemplateVO findByName(String name); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/email/template/dao/EmailTemplateDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/email/template/dao/EmailTemplateDaoImpl.java new file mode 100644 index 000000000000..5497a76d8ab2 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/email/template/dao/EmailTemplateDaoImpl.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.email.template.dao; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.email.template.EmailTemplateVO; + +public class EmailTemplateDaoImpl extends GenericDaoBase implements EmailTemplateDao { + + private static final String NAME = "name"; + + private SearchBuilder findByName; + + public EmailTemplateDaoImpl() { + findByName = createSearchBuilder(); + findByName.and(NAME, findByName.entity().getName(), SearchCriteria.Op.EQ); + findByName.done(); + } + + @Override + public EmailTemplateVO findByName(String name) { + SearchCriteria sc = findByName.create(); + sc.setParameters(NAME, name); + return findOneBy(sc); + } +} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 932db538f30b..36b18d95f337 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -278,6 +278,10 @@ + + + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index 417d69a162eb..a1188385313f 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -651,3 +651,24 @@ WHERE `name`='user.vm.readonly.details' AND `value` IS NOT NULL; -- usage records introduced in 4.22.1 (cumulative and per-VM) can coexist. See #13399. CALL `cloud_usage`.`IDEMPOTENT_DROP_INDEX`('id', 'cloud_usage.usage_volume'); CALL `cloud_usage`.`IDEMPOTENT_ADD_UNIQUE_INDEX`('cloud_usage.usage_volume', 'id', '(volume_id ASC, created ASC, vm_id ASC)'); + +-- Backup report +-- CHANGE FILE LATER + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'failure_reason', 'varchar(255)'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'logid', 'varchar(14)'); + +CREATE TABLE IF NOT EXISTS `cloud`.`backup_report` ( + `id` bigint NOT NULL UNIQUE AUTO_INCREMENT, + `created` DATETIME NOT NULL, + `removed` DATETIME, + `task_enabled` tinyint(1) DEFAULT 0, + PRIMARY KEY (`id`) +); + +CREATE TABLE IF NOT EXISTS `cloud`.`email_template` ( + `id` bigint NOT NULL UNIQUE AUTO_INCREMENT, + `name` VARCHAR(55) NOT NULL, + `template` TEXT, + PRIMARY KEY (`id`) +); diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.backup_report_kboss_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.backup_report_kboss_view.sql new file mode 100644 index 000000000000..d34f15987141 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.backup_report_kboss_view.sql @@ -0,0 +1,58 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- VIEW `cloud`.`backup_report_kboss_view`; + +DROP VIEW IF EXISTS `cloud`.`backup_report_kboss_view`; +CREATE VIEW `cloud`.`backup_report_kboss_view` AS +SELECT dc.id AS zone_id, + dc.uuid AS zone_uuid, + dc.name AS zone_name, + d.id AS domain_id, + d.uuid AS domain_uuid, + d.name AS domain_name, + a.id AS account_id, + a.uuid AS account_uuid, + a.account_name, + p.uuid AS project_uuid, + p.name as project_name, + vi.id AS vm_id, + vi.uuid AS vm_uuid, + vi.name AS vm_name, + b.id AS backup_id, + b.uuid AS backup_uuid, + b.name AS backup_name, + bo.name AS offering_name, + b.size, + b.uncompressed_size, + b.compression_status, + b.validation_status, + b.date as backup_date, + b.removed as backup_removed, + bj.id as job_id, + bj.attempts, + bj.type, + bj.start_time, + bj.removed +FROM internal_backup_service_job bj + LEFT JOIN backups b on bj.backup_id = b.id + LEFT JOIN vm_instance vi ON bj.instance_id = vi.id + LEFT JOIN account a ON bj.account_id = a.id + LEFT JOIN projects p ON bj.account_id = p.project_account_id + LEFT JOIN domain d ON a.domain_id = d.id + LEFT JOIN data_center dc ON bj.zone_id = dc.id + LEFT JOIN backup_offering bo ON b.backup_offering_id = bo.id; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.backup_report_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.backup_report_view.sql new file mode 100644 index 000000000000..e2bce548b1cb --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.backup_report_view.sql @@ -0,0 +1,52 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- VIEW `cloud`.`backup_report_view`; + +DROP VIEW IF EXISTS `cloud`.`backup_report_view`; +CREATE VIEW `cloud`.`backup_report_view` AS +SELECT dc.id AS zone_id, + dc.uuid AS zone_uuid, + dc.name AS zone_name, + d.id AS domain_id, + d.uuid AS domain_uuid, + d.name AS domain_name, + a.id AS account_id, + a.uuid AS account_uuid, + a.account_name, + p.uuid AS project_uuid, + p.name as project_name, + vi.id AS vm_id, + vi.uuid AS vm_uuid, + vi.name AS vm_name, + b.id AS backup_id, + b.uuid AS backup_uuid, + b.name AS backup_name, + bo.name AS offering_name, + b.size, + b.status, + b.failure_reason, + b.logid, + b.date, + b.removed +FROM backups b + LEFT JOIN vm_instance vi ON b.vm_id = vi.id + LEFT JOIN account a ON b.account_id = a.id + LEFT JOIN projects p ON b.account_id = p.project_account_id + LEFT JOIN domain d ON b.domain_id = d.id + LEFT JOIN data_center dc ON b.zone_id = dc.id + LEFT JOIN backup_offering bo ON b.backup_offering_id = bo.id; diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java index dcd863465d1b..9871f78bced5 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java @@ -926,11 +926,15 @@ public Class getEntityBeanType() { } @DB() - protected T findOneIncludingRemovedBy(final SearchCriteria sc) { - Filter filter = new Filter(1, true); + protected T findOneIncludingRemovedBy(final SearchCriteria sc, Filter filter) { + filter.setLimit(1L); List results = searchIncludingRemoved(sc, filter, null, false); - assert results.size() <= 1 : "Didn't the limiting worked?"; - return results.size() == 0 ? null : results.get(0); + return results.isEmpty() ? null : results.get(0); + } + + @DB() + protected T findOneIncludingRemovedBy(final SearchCriteria sc) { + return findOneIncludingRemovedBy(sc, new Filter(1, true)); } @Override diff --git a/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java b/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java index 5edac430a673..d71000a4e6ff 100644 --- a/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java +++ b/plugins/backup/kboss/src/main/java/org/apache/cloudstack/backup/KbossBackupProvider.java @@ -47,10 +47,14 @@ import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; +import org.apache.cloudstack.api.response.BackupReportCompressionResponse; +import org.apache.cloudstack.api.response.BackupReportValidationResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; +import org.apache.cloudstack.backup.dao.BackupReportKbossJoinDao; import org.apache.cloudstack.backup.dao.InternalBackupDataStoreDao; import org.apache.cloudstack.backup.dao.InternalBackupJoinDao; import org.apache.cloudstack.backup.dao.InternalBackupServiceJobDao; @@ -115,6 +119,7 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.projects.dao.ProjectDao; import com.cloud.resource.ResourceState; import com.cloud.storage.DataStoreRole; import com.cloud.storage.DiskOfferingVO; @@ -162,6 +167,7 @@ import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; +import org.apache.logging.log4j.ThreadContext; public class KbossBackupProvider extends AdapterBase implements InternalBackupProvider, Configurable { protected ConfigKey backupChainSize = new ConfigKey<>("Advanced", Integer.class, "backup.chain.size", "8", "Determines the max size of a backup chain." + @@ -272,6 +278,12 @@ public class KbossBackupProvider extends AdapterBase implements InternalBackupPr @Inject private AlertManager alertManager; + @Inject + private BackupReportKbossJoinDao backupReportKbossJoinDao; + + @Inject + private ProjectDao projectDao; + protected final List validChildStatesToRemoveBackup = List.of(Backup.Status.Expunged, Backup.Status.Error, Backup.Status.Failed); private final List supportedStoragePoolTypes = List.of(Storage.StoragePoolType.Filesystem, Storage.StoragePoolType.NetworkFilesystem, @@ -382,12 +394,16 @@ public Pair orchestrateTakeBackup(Backup backup, boolean quiesceV VirtualMachine userVm = virtualMachineManager.findById(vmId); Long hostId = vmSnapshotHelper.pickRunningHost(vmId); HostVO hostVO = hostDao.findById(hostId); + backupVO.setDate(new Date()); if (hostVO.getStatus() != Status.Up || hostVO.getResourceState() != ResourceState.Enabled) { + String failureReason = String.format("There is no host available to create the backup [%s] of VM [%s]. Setting the backup as \"Failed\".", backupVO.getUuid(), + userVm.getUuid()); backupVO.setStatus(Backup.Status.Failed); + backupVO.setFailureReason(failureReason); backupDao.update(backupVO.getId(), backupVO); - logger.error("No available host found to create backup [{}] of VM [{}]. Setting the backup as Failed.", backupVO.getUuid(), userVm.getUuid()); + logger.error(failureReason); return new Pair<>(Boolean.FALSE, backup.getId()); } @@ -397,6 +413,7 @@ public Pair orchestrateTakeBackup(Backup backup, boolean quiesceV volumeTOs = vmSnapshotHelper.getVolumeTOList(userVm.getId()); validateStorages(volumeTOs, userVm.getUuid()); } catch (Exception e) { + backupVO.setFailureReason(e.getMessage()); backupVO.setStatus(Backup.Status.Failed); backupDao.update(backupVO.getId(), backupVO); throw e; @@ -406,7 +423,6 @@ public Pair orchestrateTakeBackup(Backup backup, boolean quiesceV BackupOfferingVO backupOfferingVO = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId()); - backupVO.setDate(new Date()); List backupChain = getBackupJoinParents(backupVO, true); InternalBackupJoinVO parentBackup = null; if (isolated) { @@ -1034,6 +1050,58 @@ public ConfigKey[] getConfigKeys() { backupCompressionCoroutines}; } + @Override + public List getBackupReport(Date startDate, Date endDate, long zoneId, Long domainId, Long accountId) { + List kbossReports = backupReportKbossJoinDao.listByZoneAndDomainAndAccountAndBetweenDates(zoneId, domainId, accountId, startDate, endDate); + + BackupReportCompressionResponse compressionResponse = new BackupReportCompressionResponse(); + BackupReportValidationResponse validationResponse = new BackupReportValidationResponse(); + for (BackupReportKbossJoinVO kbossReport : kbossReports) { + if (kbossReport.getValidationStatus() == Backup.ValidationStatus.NotValidated && kbossReport.getCompressionStatus() == Backup.CompressionStatus.Uncompressed) { + logger.trace("Not adding backup [{}] to KBOSS report as it is neither validated nor compressed.", kbossReport.getBackupUuid()); + continue; + } + logger.trace("Adding backup [{}] to KBOSS provider backup report.", kbossReport.getBackupUuid()); + BackupResponse backupResponse = createBaseResponse(kbossReport); + + if (kbossReport.getValidationStatus() != Backup.ValidationStatus.NotValidated) { + backupResponse.setValidationStatus(kbossReport.getValidationStatus()); + validationResponse.addBackupResponse(backupResponse); + } + if (kbossReport.getCompressionStatus() != Backup.CompressionStatus.Uncompressed) { + backupResponse.setSize(kbossReport.getSize()); + backupResponse.setUncompressedSize(kbossReport.getUncompressedSize()); + backupResponse.setCompressionStatus(kbossReport.getCompressionStatus()); + compressionResponse.addBackupResponse(backupResponse); + } + } + return List.of(compressionResponse, validationResponse); + } + + private BackupResponse createBaseResponse(BackupReportKbossJoinVO kbossReport) { + BackupResponse backupResponse = new BackupResponse(); + backupResponse.setBackupOffering(kbossReport.getOfferingName()); + backupResponse.setId(kbossReport.getBackupUuid()); + backupResponse.setName(kbossReport.getBackupName()); + backupResponse.setVmId(kbossReport.getVmUuid()); + backupResponse.setVmName(kbossReport.getVmName()); + backupResponse.setDate(kbossReport.getBackupDate()); + backupResponse.setRemoved(kbossReport.getBackupRemoved()); + backupResponse.setZone(kbossReport.getZoneName()); + backupResponse.setZoneId(kbossReport.getZoneUuid()); + backupResponse.setJobId(kbossReport.getJobId() != null ? kbossReport.getJobId().toString() : null); + backupResponse.setJobType(kbossReport.getType().name()); + backupResponse.setStartDate(kbossReport.getStartTime()); + backupResponse.setEndDate(kbossReport.getRemoved()); + backupResponse.setDomainId(kbossReport.getDomainUuid()); + + backupResponse.setAccountId(kbossReport.getAccountUuid()); + backupResponse.setAccount(kbossReport.getAccountName()); + backupResponse.setProjectId(kbossReport.getProjectUuid()); + backupResponse.setProjectName(kbossReport.getProjectName()); + return backupResponse; + } + protected Outcome createBackupThroughJobQueue(VirtualMachine vm, boolean quiesceVm, boolean isolated) { final CallContext context = CallContext.current(); long userId = context.getCallingUser().getId(); @@ -1042,7 +1110,7 @@ protected Outcome createBackupThroughJobQueue(VirtualMachine vm, boolean quie BackupVO backup = new BackupVO(String.format("%s-%s", vm.getHostName(), DateUtil.getDateInSystemTimeZone()), vmId, vm.getBackupOfferingId(), accountId, vm.getDomainId(), vm.getDataCenterId(), 0, Backup.Status.Queued, null, - Backup.CompressionStatus.Uncompressed, Backup.ValidationStatus.NotValidated); + Backup.CompressionStatus.Uncompressed, Backup.ValidationStatus.NotValidated, ThreadContext.get("logcontextid")); VmWorkJobVO workJob = new VmWorkJobVO(AsyncJobExecutionContext.getOriginJobId(), userId, accountId, VmWorkTakeBackup.class.getName(), vmId, VirtualMachine.Type.Instance, VmWorkJobVO.Step.Starting); @@ -2118,6 +2186,7 @@ protected void processBackupFailure(Answer answer, VirtualMachine vm, long hostI backupVO.setStatus(Backup.Status.Error); } + backupVO.setFailureReason(answer == null ? "No answer from host" : answer.getDetails()); backupDao.update(backupVO.getId(), backupVO); } 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 8e6d33c4e668..b7f65ed5b590 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 @@ -64,6 +64,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.ThreadContext; import javax.inject.Inject; import java.text.SimpleDateFormat; @@ -674,7 +675,7 @@ private BackupVO createBackupObject(VirtualMachine vm, String backupPath, String backup.setName(backupManager.getBackupNameFromVM(vm)); Map details = backupManager.getBackupDetailsFromVM(vm); backup.setDetails(details); - + backup.setLogid(ThreadContext.get("logcontextid")); return backupDao.persist(backup); } 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 1cf962edae51..7c3359aa0c00 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 @@ -51,6 +51,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.ThreadContext; import org.apache.xml.utils.URI; import org.apache.cloudstack.backup.networker.api.NetworkerBackup; @@ -546,6 +547,7 @@ public Pair takeBackup(VirtualMachine vm, Boolean quiesceVM, bo backup.setBackedUpVolumes(backupManager.createVolumeInfoFromVolumes(volumes)); Map details = backupManager.getBackupDetailsFromVM(vm); backup.setDetails(details); + backup.setLogid(ThreadContext.get("logcontextid")); backupDao.persist(backup); return new Pair<>(true, backup); } else { diff --git a/pom.xml b/pom.xml index 354ec01bb396..3a584c465756 100644 --- a/pom.xml +++ b/pom.xml @@ -198,6 +198,7 @@ 8.6.0 1.51.0 2.16.0 + 2.3.34 diff --git a/server/pom.xml b/server/pom.xml index af0d32725729..cc2fd01bb267 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -105,6 +105,11 @@ compiler ${cs.mustache.version} + + org.freemarker + freemarker + ${cs.freemaker.version} + org.apache.cloudstack cloud-utils 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 58e435b6406d..94f784e917df 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -65,6 +65,7 @@ import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.DownloadValidationScreenshotCmd; import org.apache.cloudstack.api.command.user.backup.FinishBackupChainCmd; +import org.apache.cloudstack.api.command.user.backup.GetBackupReportCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupServiceJobsCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; @@ -2069,6 +2070,7 @@ public List> getCommands() { cmdList.add(DownloadValidationScreenshotCmd.class); cmdList.add(ListBackupServiceJobsCmd.class); cmdList.add(FinishBackupChainCmd.class); + cmdList.add(GetBackupReportCmd.class); return cmdList; } diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupReportServiceImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupReportServiceImpl.java new file mode 100644 index 000000000000..dee79342bacd --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupReportServiceImpl.java @@ -0,0 +1,463 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup; + +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.projects.ProjectVO; +import com.cloud.projects.dao.ProjectDao; +import com.cloud.user.Account; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.DateUtil; +import com.cloud.utils.UuidUtils; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.UserVmDao; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import org.apache.cloudstack.api.response.BackupReportAccountResponse; +import org.apache.cloudstack.api.response.BackupReportDomainResponse; +import org.apache.cloudstack.api.response.BackupReportResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.backup.dao.BackupReportDao; +import org.apache.cloudstack.backup.dao.BackupReportJoinDao; +import org.apache.cloudstack.backup.dao.BackupScheduleDao; +import org.apache.cloudstack.email.template.EmailTemplateVO; +import org.apache.cloudstack.email.template.dao.EmailTemplateDao; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.utils.mailing.MailAddress; +import org.apache.cloudstack.utils.mailing.SMTPMailProperties; +import org.apache.cloudstack.utils.mailing.SMTPMailSender; +import org.apache.logging.log4j.ThreadContext; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.time.temporal.ChronoUnit; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class BackupReportServiceImpl extends ManagerBase implements Configurable, BackupReportService { + + protected ConfigKey backupReportTaskEnabled = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Boolean.class, + "backup.report.task.enabled", "false", "Whether the backup report task should run or not.", true, ConfigKey.Scope.Zone); + + protected ConfigKey backupReportPeriod = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, + "backup.report.period", "1440", "The period of time, in minutes, between two executions of the backup report task. The report will always contain all information" + + " regarding backups between the last execution and the current execution. If the task was disabled, the report will contain information up to the period configured.", + true, ConfigKey.Scope.Global); + + protected ConfigKey backupReportTimeout = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, "backup.report.timeout", "10", "Timeout, in minutes, of the " + + "backup report task.", true, ConfigKey.Scope.Global); + + private static final String LOCK = "backup_report_lock"; + private static final String TEMPLATE_NAME = "backup_report_template"; + protected static final String LOGCONTEXTID = "logcontextid"; + private static final double GIB = (1024 * 1024 * 1024); + + private final List aliveBackupStates = List.of(Backup.Status.BackedUp, Backup.Status.Restoring); + private final List errorBackupStates = List.of(Backup.Status.Error, Backup.Status.Failed); + + private ScheduledExecutorService scheduledExecutor; + + @Inject + private BackupReportDao backupReportDao; + + @Inject + private DomainDao domainDao; + + @Inject + private AccountDao accountDao; + + @Inject + private ProjectDao projectDao; + + @Inject + private UserVmDao userVmDao; + + @Inject + private DataCenterDao dataCenterDao; + + @Inject + private BackupReportJoinDao backupReportJoinDao; + + @Inject + private BackupScheduleDao backupScheduleDao; + + @Inject + private ConfigurationDao configurationDao; + + @Inject + private EmailTemplateDao emailTemplateDao; + + @Inject + private BackupManager backupManager; + + private SMTPMailSender mailSender; + private String senderAddress; + private String[] recipients; + private Configuration freemarkerConfig; + + public BackupReportServiceImpl () { + }; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + this.freemarkerConfig = new Configuration(Configuration.VERSION_2_3_34); + + Map configs = configurationDao.getConfiguration("management-server", params); + + senderAddress = configs.get("alert.email.sender"); + String emailAddressList = configs.get("alert.email.addresses"); + recipients = null; + if (emailAddressList != null) { + recipients = emailAddressList.split(","); + } else { + logger.warn("No recipients set in global setting 'alert.email.addresses', skipping running backup report task."); + return true; + } + + String namespace = "alert.smtp"; + + mailSender = new SMTPMailSender(configs, namespace); + + scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("BackupCompressionScheduler")); + scheduledExecutor.schedule(this::run, 60, TimeUnit.SECONDS); + return true; + } + + protected void run() { + ThreadContext.put(LOGCONTEXTID, UuidUtils.first(UUID.randomUUID().toString())); + logger.info("Starting backup report task."); + + try { + Transaction.execute(TransactionLegacy.CLOUD_DB, new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + buildAndSendReport(); + } + }); + } catch (Exception e) { + logger.error("Caught exception [{}] while executing backup report task.", e.getMessage(), e); + } + int nextExecutionIn = getPeriod(); + + if (logger.isDebugEnabled()) { + Calendar now = Calendar.getInstance(); + now.add(Calendar.MINUTE, nextExecutionIn); + logger.debug("Scheduling next backup report task to [{}]", now.toInstant().toString()); + } + + scheduledExecutor.schedule(this::run, nextExecutionIn, TimeUnit.MINUTES); + ThreadContext.pop(); + } + + @Override + public BackupReportResponse getBackupReport(Date startDate, Date endDate, Long zoneId, Long domainId, Long accountId, Long projectId) { + logger.info("Generating backup report from [{}] to [{}] for zone [{}], domain [{}], account [{}] or project [{}].", startDate, endDate, zoneId, domainId, accountId, projectId); + BackupReportResponse backupReportResponse = new BackupReportResponse(startDate, endDate); + if (projectId != null) { + accountId = projectDao.findById(projectId).getProjectAccountId(); + } + + addReportsOnExistingBackups(startDate, endDate, zoneId, domainId, accountId, backupReportResponse); + addReportsOnFutureBackups(startDate, endDate, zoneId, domainId, accountId, backupReportResponse); + + List zoneIds = zoneId == null ? dataCenterDao.listAllZones().stream().map(DataCenterVO::getId).collect(Collectors.toList()) : List.of(zoneId); + for (Long zone : zoneIds) { + BackupProvider provider = backupManager.getBackupProvider(zone); + logger.info("Asking provider [{}] for the backup report on zone [{}].", provider.getName(), zone); + List providerReports = provider.getBackupReport(startDate, endDate, zone, domainId, accountId); + backupReportResponse.addProviderInfo(providerReports); + } + + return backupReportResponse; + } + + private void addReportsOnFutureBackups(Date startDate, Date endDate, Long zoneId, Long domainId, Long accountId, BackupReportResponse backupReportResponse) { + Date now = new Date(); + if (endDate.before(now)) { + logger.debug("End date [{}] is before now [{}], not adding report on future backups.", endDate, now); + return; + } + logger.info("Adding reports on future backups."); + List backupSchedules = backupScheduleDao.getSchedulesToExecuteForDomainAndAccount(endDate, zoneId, domainId, accountId); + for (BackupScheduleVO schedule : backupSchedules) { + Date nextExecution = schedule.getScheduledTimestamp(); + if (nextExecution.before(startDate)) { + nextExecution = DateUtil.getNextRunTime(schedule.getScheduleType(), schedule.getSchedule(), TimeZone.getTimeZone(schedule.getTimezone()).getID(), startDate); + if (nextExecution.after(endDate)) { + continue; + } + } + addBackupScheduleResponse(schedule, nextExecution, backupReportResponse); + } + } + + private void addReportsOnExistingBackups(Date startDate, Date endDate, Long zoneId, Long domainId, Long accountId, BackupReportResponse backupReportResponse) { + List backupInfos = backupReportJoinDao.listByZoneAndDomainAndAccountAndBetweenDates(zoneId, domainId, accountId, startDate, + endDate); + long currentDomainId = -1; + BackupReportDomainResponse currentDomainResponse = null; + long currentAccountId = -1; + BackupReportAccountResponse currentAccountResponse = null; + logger.info("Adding reports on existing backups. A total of [{}] backups will be reported.", backupInfos.size()); + + for (BackupReportJoinVO backupInfo : backupInfos) { + if (backupInfo.getDomainId() != currentDomainId) { + currentDomainId = backupInfo.getDomainId(); + currentDomainResponse = new BackupReportDomainResponse(backupInfo.getDomainUuid(), backupInfo.getDomainName()); + backupReportResponse.addBackupReportDomainResponse(currentDomainResponse); + logger.debug("Adding reports for domain [{}].", backupInfo.getDomainUuid()); + } + + if (backupInfo.getAccountId() != currentAccountId) { + currentAccountId = backupInfo.getAccountId(); + currentAccountResponse = new BackupReportAccountResponse(); + if (backupInfo.getProjectUuid() != null) { + currentAccountResponse.setProjectName(backupInfo.getProjectName()); + currentAccountResponse.setProjectId(backupInfo.getProjectUuid()); + } else { + currentAccountResponse.setAccountName(backupInfo.getAccountName()); + currentAccountResponse.setAccountId(backupInfo.getAccountUuid()); + } + currentDomainResponse.addBackupReportAccountResponse(currentAccountResponse); + logger.debug("Adding reports for account [{}].", currentAccountResponse.getAccountId()); + } + + addBackupReport(backupReportResponse, backupInfo, currentDomainResponse, currentAccountResponse); + } + } + + private void addBackupReport(BackupReportResponse backupReportResponse, BackupReportJoinVO backupInfo, BackupReportDomainResponse currentDomainResponse, + BackupReportAccountResponse currentAccountResponse) { + if (backupInfo.getStatus() == Backup.Status.BackingUp) { + return; + } + logger.trace("Adding report for backup [{}].", backupInfo.getBackupUuid()); + BackupResponse backupResponse = new BackupResponse(); + backupResponse.setBackupOffering(backupInfo.getOfferingName()); + backupResponse.setId(backupInfo.getBackupUuid()); + backupResponse.setName(backupInfo.getBackupName()); + backupResponse.setVmId(backupInfo.getVmUuid()); + backupResponse.setVmName(backupInfo.getVmName()); + backupResponse.setDate(backupInfo.getDate()); + backupResponse.setZone(backupInfo.getZoneName()); + backupResponse.setZoneId(backupInfo.getZoneUuid()); + + if (aliveBackupStates.contains(backupInfo.getStatus()) && backupInfo.getRemoved() == null) { + double backupSizeInGib = backupInfo.getSize() / GIB; + backupReportResponse.addStorageUsage(backupSizeInGib); + currentDomainResponse.addStorageUsage(backupSizeInGib); + currentAccountResponse.addStorageUsage(backupSizeInGib); + currentAccountResponse.addSuccessfulBackup(backupResponse); + } else if (errorBackupStates.contains(backupInfo.getStatus()) && backupInfo.getRemoved() == null) { + backupResponse.setFailureReason(backupInfo.getFailureReason()); + backupResponse.setLogid(backupInfo.getLogid()); + currentAccountResponse.addFailedBackup(backupResponse); + } else { + backupResponse.setRemoved(backupInfo.getRemoved()); + currentAccountResponse.addDeletedBackup(backupResponse); + } + } + + private void addBackupScheduleResponse(BackupScheduleVO schedule, Date nextExecution, BackupReportResponse backupReportResponse) { + BackupScheduleResponse scheduleResponse = new BackupScheduleResponse(); + logger.trace("Adding report for future execution of backup schedule [{}].", schedule.getUuid()); + scheduleResponse.setId(schedule.getUuid()); + scheduleResponse.setSchedule(nextExecution.toInstant().toString()); + scheduleResponse.setQuiesceVM(schedule.getQuiesceVM()); + scheduleResponse.setIsolated(schedule.isIsolated()); + + VMInstanceVO vm = userVmDao.findById(schedule.getVmId()); + if (vm != null) { + scheduleResponse.setVmId(vm.getUuid()); + scheduleResponse.setVmName(vm.getHostName()); + } + + AccountVO backupAccount = accountDao.findById(schedule.getAccountId()); + scheduleResponse.setAccountId(backupAccount.getUuid()); + if (backupAccount.getType() == Account.Type.PROJECT) { + ProjectVO project = projectDao.findByProjectAccountId(backupAccount.getAccountId()); + scheduleResponse.setProjectId(project.getUuid()); + scheduleResponse.setProjectName(project.getName()); + } else { + scheduleResponse.setAccount(backupAccount.getAccountName()); + } + + DomainVO domain = domainDao.findById(backupAccount.getDomainId()); + scheduleResponse.setDomain(domain.getName()); + scheduleResponse.setDomainid(domain.getUuid()); + backupReportResponse.addBackupScheduleResponse(scheduleResponse); + } + + private int getPeriod() { + return backupReportPeriod.value() < 1 ? Integer.parseInt(backupReportPeriod.defaultValue()) : backupReportPeriod.value(); + } + + protected void buildAndSendReport() { + boolean lock = false; + try { + lock = backupReportDao.lockInLockTable(LOCK, 300); + if (!lock) { + logger.warn("Unable to get lock for backup report. Giving up."); + return; + } + + BackupReportVO latestReport = backupReportDao.findLatest(); + + if (isTaskDisabled(latestReport)) { + return; + } + + if (isLastTaskRunning(latestReport)) { + return; + } + + int period = getPeriod(); + Calendar start = getStart(latestReport, period); + if (start == null) { + return; + } + + Calendar end = Calendar.getInstance(); + + BackupReportVO thisReport = new BackupReportVO(end.getTime(), true); + thisReport = backupReportDao.persist(thisReport); + + end.add(Calendar.MINUTE, period); + BackupReportResponse response = getBackupReport(start.getTime(), end.getTime(), null, null, null, null); + + String subject = String.format("Backup report from %s to %s", start.toInstant().toString(), end.toInstant().toString()); + + EmailTemplateVO templateVO = emailTemplateDao.findByName(TEMPLATE_NAME); + Template template = new Template("template", new StringReader(templateVO.getTemplate()), freemarkerConfig); + StringWriter writer = new StringWriter(); + template.process(response, writer); + String result = writer.toString(); + + sendMail(subject, result); + backupReportDao.remove(thisReport.getId()); + } catch (TemplateException | IOException e) { + logger.error(e); + throw new CloudRuntimeException(e); + } finally { + if (lock) { + backupReportDao.unlockFromLockTable(LOCK); + } + } + } + + private boolean isLastTaskRunning(BackupReportVO latestReport) { + Calendar timeout = Calendar.getInstance(); + timeout.add(Calendar.MINUTE, -backupReportTimeout.value()); + if (latestReport != null && latestReport.getRemoved() == null) { + if (latestReport.getCreated().before(timeout.getTime())) { + logger.warn("Last backup report task has timed out. Will set it as removed and proceed with execution of new task."); + backupReportDao.remove(latestReport.getId()); + } else { + logger.debug("Last backup report task is still running. Skipping this task."); + return true; + } + } + return false; + } + + private Calendar getStart(BackupReportVO latestReport, int period) { + Calendar start = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + if (latestReport == null || !latestReport.isTaskEnabled()) { + logger.info("Last report not found or task was disabled, will get the report from the last [{}] minutes.", period); + start.add(Calendar.MINUTE, -period); + } else { + Calendar lastReportStart = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + lastReportStart.setTime(latestReport.getCreated()); + if (ChronoUnit.MINUTES.between(lastReportStart.toInstant(), start.toInstant()) < period) { + logger.debug("Last report was less then [{}] minutes ago. Skipping execution.", period); + return null; + } + start.setTime(lastReportStart.getTime()); + } + return start; + } + + private boolean isTaskDisabled(BackupReportVO latestReport) { + if (backupReportTaskEnabled.value()) { + return false; + } + + if (latestReport == null || latestReport.isTaskEnabled()) { + Date now = new Date(); + BackupReportVO disabledTask = new BackupReportVO(now, false); + disabledTask.setRemoved(now); + backupReportDao.persist(disabledTask); + } + logger.debug("Backup report task is disabled, skipping running."); + return true; + } + + private void sendMail(String subject, String body) { + SMTPMailProperties mailProps = new SMTPMailProperties(); + mailProps.setSender(new MailAddress(senderAddress)); + mailProps.setSubject(subject); + mailProps.setContent(body); + mailProps.setContentType("text/html; charset=utf-8"); + + Set addresses = new HashSet<>(); + for (String recipient : recipients) { + addresses.add(new MailAddress(recipient)); + } + + mailProps.setRecipients(addresses); + mailSender.sendMail(mailProps); + } + + @Override + public String getConfigComponentName() { + return BackupReportServiceImpl.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] {backupReportTaskEnabled, backupReportPeriod, backupReportTimeout}; + } +} diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index c0bcba44c642..8b0b4aafdd70 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -356,6 +356,8 @@ + +