diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 907b93eca10..a2baf7c7dd5 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -584,6 +584,15 @@ public class EventTypes { public static final String EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE = "TEMPLATE.DIRECT.DOWNLOAD.FAILURE"; public static final String EVENT_ISO_DIRECT_DOWNLOAD_FAILURE = "ISO.DIRECT.DOWNLOAD.FAILURE"; + // Backup and Recovery events + public static final String EVENT_ADD_VM_TO_BACKUP_POLICY = "ADD.VM.TO.BACKUP.POLICY"; + public static final String EVENT_REMOVE_VM_FROM_BACKUP_POLICY = "REMOVE.VM.FROM.BACKUP.POLICY"; + public static final String EVENT_IMPORT_BACKUP_POLICY = "IMPORT.BACKUP.POLICY"; + public static final String EVENT_CREATE_VM_BACKUP = "CREATE.VM.BACKUP"; + public static final String EVENT_DELETE_VM_BACKUP = "DELETE.VM.BACKUP"; + public static final String EVENT_RESTORE_VM_FROM_BACKUP = "RESTORE.VM.FROM.BACKUP"; + public static final String EVENT_RESTORE_VOLUME_FROM_BACKUP_AND_ATTACH_TO_VM = "RESTORE.VOLUME.FROM.BACKUP.AND.ATTACH.TO.VM"; + static { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java index 1dbe0337df6..c94b9e20c26 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupPolicyCmd.java @@ -18,10 +18,12 @@ package org.apache.cloudstack.api.command.admin.backup; import javax.inject.Inject; +import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; @@ -43,7 +45,7 @@ import com.cloud.utils.exception.CloudRuntimeException; description = "Imports a backup policy from the backup provider", responseObject = BackupPolicyResponse.class, since = "4.12.0", authorized = {RoleType.Admin}) -public class ImportBackupPolicyCmd extends BaseCmd { +public class ImportBackupPolicyCmd extends BaseAsyncCmd { public static final String APINAME = "importBackupPolicy"; @Inject @@ -122,4 +124,14 @@ public class ImportBackupPolicyCmd extends BaseCmd { public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + + @Override + public String getEventType() { + return EventTypes.EVENT_IMPORT_BACKUP_POLICY; + } + + @Override + public String getEventDescription() { + return "Importing backup policy: " + policyName + " (externalId=" + policyExternalId + ") on zone " + zoneId ; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.java index c6d12198a45..3074188314a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AddVMToBackupPolicyCmd.java @@ -18,11 +18,12 @@ package org.apache.cloudstack.api.command.user.backup; import javax.inject.Inject; +import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupPolicyResponse; @@ -41,7 +42,7 @@ import com.cloud.exception.ResourceUnavailableException; description = "Assigns a VM to an existing backup policy", responseObject = SuccessResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class AddVMToBackupPolicyCmd extends BaseCmd { +public class AddVMToBackupPolicyCmd extends BaseAsyncCmd { public static final String APINAME = "addVMToBackupPolicy"; @Inject @@ -106,4 +107,14 @@ public class AddVMToBackupPolicyCmd extends BaseCmd { public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + + @Override + public String getEventType() { + return EventTypes.EVENT_ADD_VM_TO_BACKUP_POLICY; + } + + @Override + public String getEventDescription() { + return "Adding VM " + vmId + " to backup policy " + policyId; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java index 6855c999a8f..94a821abded 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateVMBackupCmd.java @@ -19,15 +19,18 @@ package org.apache.cloudstack.api.command.user.backup; import javax.inject.Inject; +import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -40,9 +43,9 @@ import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = CreateVMBackupCmd.APINAME, description = "Create VM backup", - responseObject = SuccessResponse.class, since = "4.12.0", + responseObject = BackupResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class CreateVMBackupCmd extends BaseCmd { +public class CreateVMBackupCmd extends BaseAsyncCmd { public static final String APINAME = "createVMBackup"; @Inject @@ -76,10 +79,9 @@ public class CreateVMBackupCmd extends BaseCmd { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.createBackup(vmId); - // FIXME: the response type - if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); + Backup backup = backupManager.createBackup(vmId); + if (backup != null) { + BackupResponse response = _responseGenerator.createBackupResponse(backup); response.setResponseName(getCommandName()); setResponseObject(response); } else { @@ -99,4 +101,14 @@ public class CreateVMBackupCmd extends BaseCmd { public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + + @Override + public String getEventType() { + return EventTypes.EVENT_CREATE_VM_BACKUP; + } + + @Override + public String getEventDescription() { + return "Creating backup for VM " + vmId; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java index 1faa6286133..fde8a9f7e6a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteVMBackupCmd.java @@ -26,8 +26,8 @@ import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.SuccessResponse; -import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -52,19 +52,19 @@ public class DeleteVMBackupCmd extends BaseCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, - entityType = UserVmResponse.class, + entityType = BackupResponse.class, required = true, - description = "id of the VM") - private Long vmId; + description = "id of backup") + private Long backupId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getVmId() { - return vmId; + public Long getId() { + return backupId; } ///////////////////////////////////////////////////// @@ -74,7 +74,7 @@ public class DeleteVMBackupCmd extends BaseCmd { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.deleteBackup(vmId); + boolean result = backupManager.deleteBackup(backupId); // FIXME: the response type if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java index d4444803b3f..a9b183fd438 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListVMBackupsCmd.java @@ -31,7 +31,6 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -62,10 +61,6 @@ public class ListVMBackupsCmd extends BaseBackupListCmd { description = "id of the VM") private Long vmId; - @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, - description = "The zone ID") - private Long zoneId; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -74,10 +69,6 @@ public class ListVMBackupsCmd extends BaseBackupListCmd { return vmId; } - public Long getZoneId() { - return zoneId; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -85,7 +76,7 @@ public class ListVMBackupsCmd extends BaseBackupListCmd { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try{ - List backups = backupManager.listVMBackups(getZoneId(), getVmId()); + List backups = backupManager.listVMBackups(getVmId()); setupResponseBackupList(backups); } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java index c9c5518152a..e28e6708792 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVMFromBackupPolicyCmd.java @@ -19,11 +19,12 @@ package org.apache.cloudstack.api.command.user.backup; import javax.inject.Inject; +import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupPolicyResponse; @@ -42,7 +43,7 @@ import com.cloud.exception.ResourceUnavailableException; description = "Removes a VM from an existing backup policy", responseObject = SuccessResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class RemoveVMFromBackupPolicyCmd extends BaseCmd { +public class RemoveVMFromBackupPolicyCmd extends BaseAsyncCmd { public static final String APINAME = "removeVMFromBackupPolicy"; @Inject @@ -108,4 +109,13 @@ public class RemoveVMFromBackupPolicyCmd extends BaseCmd { return CallContext.current().getCallingAccount().getId(); } + @Override + public String getEventType() { + return EventTypes.EVENT_REMOVE_VM_FROM_BACKUP_POLICY; + } + + @Override + public String getEventDescription() { + return "Removing VM " + vmId + " from backup policy " + policyId; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java similarity index 78% rename from api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMBackupCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java index 7ffc8816473..019b535d4e8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVMFromBackupCmd.java @@ -19,17 +19,17 @@ package org.apache.cloudstack.api.command.user.backup; import javax.inject.Inject; +import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.SuccessResponse; -import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -40,12 +40,12 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.utils.exception.CloudRuntimeException; -@APICommand(name = RestoreVMBackupCmd.APINAME, - description = "Restore VM backup", +@APICommand(name = RestoreVMFromBackupCmd.APINAME, + description = "Restore VM from backup", responseObject = SuccessResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class RestoreVMBackupCmd extends BaseCmd { - public static final String APINAME = "restoreVMBackup"; +public class RestoreVMFromBackupCmd extends BaseAsyncCmd { + public static final String APINAME = "restoreVMFromBackup"; @Inject private BackupManager backupManager; @@ -61,34 +61,14 @@ public class RestoreVMBackupCmd extends BaseCmd { description = "id of the backup") private Long backupId; - //FIXME: is this necessary when backup id is known? unless we want to restore to a different VM? - @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, - type = CommandType.UUID, - entityType = UserVmResponse.class, - required = true, - description = "id of the VM") - private Long vmId; - - @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, - description = "The zone ID") - private Long zoneId; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public Long getVmId() { - return vmId; - } - public Long getBackupId() { return backupId; } - public Long getZoneId() { - return zoneId; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -96,7 +76,7 @@ public class RestoreVMBackupCmd extends BaseCmd { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreBackup(zoneId, vmId, backupId); + boolean result = backupManager.restoreVMFromBackup(backupId); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); @@ -119,4 +99,13 @@ public class RestoreVMBackupCmd extends BaseCmd { return CallContext.current().getCallingAccount().getId(); } + @Override + public String getEventType() { + return EventTypes.EVENT_RESTORE_VM_FROM_BACKUP; + } + + @Override + public String getEventDescription() { + return "Restoring VM from backup " + backupId; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java similarity index 88% rename from api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupVolumeCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java index f9cbe26b5ec..1567e7f9ae1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java @@ -19,10 +19,12 @@ package org.apache.cloudstack.api.command.user.backup; import javax.inject.Inject; +import com.cloud.event.EventTypes; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; @@ -30,7 +32,6 @@ import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VolumeResponse; -import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.context.CallContext; @@ -41,12 +42,12 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.utils.exception.CloudRuntimeException; -@APICommand(name = RestoreBackupVolumeCmd.APINAME, +@APICommand(name = RestoreVolumeFromBackupAndAttachToVMCmd.APINAME, description = "Restore and attach a backed up volume to VM", responseObject = SuccessResponse.class, since = "4.12.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class RestoreBackupVolumeCmd extends BaseCmd { - public static final String APINAME = "restoreBackupVolumeAndAttachToVM"; +public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { + public static final String APINAME = "restoreVolumeFromBackupAndAttachToVM"; @Inject private BackupManager backupManager; @@ -79,10 +80,6 @@ public class RestoreBackupVolumeCmd extends BaseCmd { description = "id of the VM where to attach the restored volume") private Long vmId; - @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, - description = "The zone ID") - private Long zoneId; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -99,10 +96,6 @@ public class RestoreBackupVolumeCmd extends BaseCmd { return backupId; } - public Long getZoneId() { - return zoneId; - } - @Override public String getCommandName() { return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; @@ -120,7 +113,7 @@ public class RestoreBackupVolumeCmd extends BaseCmd { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreBackupVolumeAndAttachToVM(zoneId, volumeId, vmId, backupId); + boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeId, vmId, backupId); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); @@ -132,4 +125,14 @@ public class RestoreBackupVolumeCmd extends BaseCmd { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } } + + @Override + public String getEventType() { + return EventTypes.EVENT_RESTORE_VOLUME_FROM_BACKUP_AND_ATTACH_TO_VM; + } + + @Override + public String getEventDescription() { + return "Restoring volume "+ volumeId + " from backup " + backupId + " and attaching it to VM " + vmId; + } } 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 77e916a8775..2cc58af3ba6 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 @@ -24,6 +24,7 @@ import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; import org.apache.cloudstack.backup.Backup; +import java.util.Date; import java.util.List; @EntityReference(value = Backup.class) @@ -69,6 +70,10 @@ public class BackupResponse extends BaseResponse { @Param(description = "backup volume ids") private Backup.Status status; + @SerializedName(ApiConstants.START_DATE) + @Param(description = "backup start date") + private Date startDate; + public String getId() { return id; } @@ -148,4 +153,12 @@ public class BackupResponse extends BaseResponse { public void setVolumeIds(List volumeIds) { this.volumeIds = volumeIds; } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } } diff --git a/api/src/main/java/org/apache/cloudstack/backup/Backup.java b/api/src/main/java/org/apache/cloudstack/backup/Backup.java index 9ffbf4a758c..79d462688e5 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -39,4 +39,5 @@ public interface Backup extends InternalIdentity, Identity { List getVolumeIds(); Status getStatus(); Date getStartTime(); + Date getRemoved(); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index 0a83ee0d60b..c25ef18cf36 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -67,7 +67,7 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer /** * List existing backups for a VM */ - List listVMBackups(Long zoneId, Long vmId); + List listVMBackups(Long vmId); /** * List backup policies @@ -82,24 +82,23 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer * @param vmId Virtual Machine ID * @return returns operation success */ - boolean createBackup(Long vmId); + Backup createBackup(Long vmId); /** - * Deletes backup of a VM - * @param vmId Virtual Machine ID + * Deletes a backup * @return returns operation success */ - boolean deleteBackup(Long vmId); + boolean deleteBackup(Long backupId); /** - * Restore a full backed up VM + * Restore a full VM from backup */ - boolean restoreBackup(Long zoneId, Long vmId, Long backupId); + boolean restoreVMFromBackup(Long backupId); /** * Restore a backed up volume and attach it to a VM */ - boolean restoreBackupVolumeAndAttachToVM(Long zoneId, Long volumeId, Long vmId, Long backupId); + boolean restoreBackupVolumeAndAttachToVM(Long volumeId, Long vmId, Long backupId); /** * Deletes a backup policy 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 0e3980b805b..4e857ae0c66 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -63,7 +63,7 @@ public interface BackupProvider { * @param vm * @return true if backup successfully starts */ - boolean startBackup(BackupPolicy policy, VirtualMachine vm); + Backup createVMBackup(BackupPolicy policy, VirtualMachine vm); /** * Restore VM from backup @@ -79,4 +79,9 @@ public interface BackupProvider { * List VM Backups */ List listVMBackups(Long zoneId, VirtualMachine vm); + + /** + * Remove a VM backup + */ + boolean removeVMBackup(VirtualMachine vm, String backupId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java index 61f0b90bcb2..6b3c24cf622 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupTO.java @@ -165,6 +165,11 @@ public class BackupTO implements Backup { return startTime; } + @Override + public Date getRemoved() { + return null; + } + public void setStartTime(Date startTime) { this.startTime = startTime; } 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 f7b3f781f0d..9a6f04cef38 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 @@ -71,7 +71,7 @@ public class BackupVO implements Backup { private Long parentId; @Column(name = "vm_id") - private long vmId; + private Long vmId; @Column(name = "volumes") private String volumes; @@ -83,6 +83,10 @@ public class BackupVO implements Backup { @Temporal(value = TemporalType.TIMESTAMP) private Date startTime; + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + @Transient private List volumeIds; @@ -173,7 +177,7 @@ public class BackupVO implements Backup { this.parentId = parentId; } - public void setVmId(long vmId) { + public void setVmId(Long vmId) { this.vmId = vmId; } @@ -224,6 +228,14 @@ public class BackupVO implements Backup { } } + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + protected String getVolumes() { return volumes; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java index fb6f01b0ef0..9bb8ea11772 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java @@ -30,4 +30,5 @@ public interface BackupDao extends GenericDao { List syncVMBackups(Long zoneId, Long vmId, List externalBackups); BackupResponse newBackupResponse(Backup backup); + BackupVO getBackupVO(Backup backup); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java index 0661ab8645d..f10d2f9278b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java @@ -82,7 +82,7 @@ public class BackupDaoImpl extends GenericDaoBase implements Bac return findOneBy(sc); } - private BackupVO getBackupVO(Backup backup) { + public BackupVO getBackupVO(Backup backup) { BackupVO backupVO = new BackupVO(); backupVO.setZoneId(backup.getZoneId()); backupVO.setAccountId(backup.getAccountId()); @@ -146,6 +146,7 @@ public class BackupDaoImpl extends GenericDaoBase implements Bac backupResponse.setVolumeIds(volIds); } backupResponse.setStatus(backup.getStatus()); + backupResponse.setStartDate(backup.getStartTime()); backupResponse.setObjectName("backup"); return backupResponse; } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql index c347f7ada76..4e696538781 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41110to41200.sql @@ -64,14 +64,15 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backup` ( `uuid` varchar(40) NOT NULL, `account_id` bigint(20) unsigned NOT NULL, `zone_id` bigint(20) unsigned NOT NULL, - `external_id` varchar(40) NOT NULL COMMENT 'backup ID on provider side', + `external_id` varchar(80) NOT NULL COMMENT 'backup ID on provider side', `name` varchar(255) NOT NULL COMMENT 'backup name', `description` varchar(255) COMMENT 'backup description', `parent_id` bigint(20) unsigned COMMENT 'backup parent id', `vm_id` bigint(20) unsigned NOT NULL, `volumes` varchar(100), `status` varchar(20) NOT NULL, - `start` timestamp, + `start` datetime DEFAULT NULL, + `removed` datetime DEFAULT NULL, PRIMARY KEY (`id`), CONSTRAINT `fk_backup__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_backup__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE, diff --git a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java index 89164519001..0aacf9104dc 100644 --- a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java +++ b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java @@ -17,9 +17,11 @@ package org.apache.cloudstack.backup; import java.util.Arrays; +import java.util.Comparator; import java.util.Date; import java.util.List; +import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.log4j.Logger; import com.cloud.agent.api.to.VolumeTO; @@ -28,10 +30,15 @@ import com.cloud.storage.Volume; import com.cloud.utils.component.AdapterBase; import com.cloud.vm.VirtualMachine; +import javax.inject.Inject; + public class DummyBackupProvider extends AdapterBase implements BackupProvider { private static final Logger s_logger = Logger.getLogger(DummyBackupProvider.class); + @Inject + private BackupDao backupDao; + @Override public String getName() { return "dummy"; @@ -69,8 +76,24 @@ public class DummyBackupProvider extends AdapterBase implements BackupProvider { } @Override - public boolean startBackup(BackupPolicy policy, VirtualMachine vm) { - return true; + public Backup createVMBackup(BackupPolicy policy, VirtualMachine vm) { + s_logger.debug("Creating VM backup for VM " + vm.getInstanceName() + " from backup policy " + policy.getName()); + + List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + String backupNumber = String.valueOf(backups.size() + 1); + Backup lastBackup = null; + if (backups.size() > 0) { + backups.sort(Comparator.comparing(Backup::getStartTime)); + lastBackup = backups.get(backups.size() - 1); + } + BackupTO newBackup = new BackupTO(vm.getDataCenterId(), vm.getAccountId(), + "xxxx-xxxx-" + vm.getUuid() + "-" + backupNumber, "Backup-" + vm.getUuid() + backupNumber, + "VM-" + vm.getInstanceName() + "-backup-" + backupNumber, + lastBackup != null ? lastBackup.getExternalId() : null, vm.getId(), null, + Backup.Status.BackedUp, new Date()); + backups.add(newBackup); + + return newBackup; } @Override @@ -89,15 +112,19 @@ public class DummyBackupProvider extends AdapterBase implements BackupProvider { @Override public List listVMBackups(Long zoneId, VirtualMachine vm) { s_logger.debug("Listing VM " + vm.getInstanceName() + "backups on the Dummy Backup Provider"); + return backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + } - BackupTO backup1 = new BackupTO(zoneId, vm.getAccountId(), - "xxxx-xxxx", "Backup-1", "VM-" + vm.getInstanceName() + "-backup-1", - null, vm.getId(), null, Backup.Status.BackedUp, new Date()); + @Override + public boolean removeVMBackup(VirtualMachine vm, String backupId) { + s_logger.debug("Removing VM backup " + backupId + " for VM " + vm.getInstanceName() + " on the Dummy Backup Provider"); - BackupTO backup2 = new BackupTO(zoneId, vm.getAccountId(), "yyyy-yyyy", - "Backup-2", "VM-" + vm.getInstanceName() + "-backup-2", - backup1.getExternalId(), vm.getId(), null, Backup.Status.BackedUp, new Date()); - - return Arrays.asList(backup1, backup2); + List backups = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); + for (Backup backup : backups) { + if (backup.getExternalId().equals(backupId)) { + return true; + } + } + return false; } } diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java index 9bed15b3d6d..84e442aa4cc 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -128,8 +128,10 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, } @Override - public boolean startBackup(BackupPolicy policy, VirtualMachine vm) { - return getClient(vm.getDataCenterId()).startBackupJob(policy.getExternalId()); + public Backup createVMBackup(BackupPolicy policy, VirtualMachine vm) { + //TODO: Return backup + getClient(vm.getDataCenterId()).startBackupJob(policy.getExternalId()); + return null; } @Override @@ -150,6 +152,12 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, return null; } + @Override + public boolean removeVMBackup(VirtualMachine vm, String backupId) { + //TODO: Implement + return false; + } + @Override public String getConfigComponentName() { return BackupService.class.getSimpleName(); diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java index a72e8d2f691..285542a01ba 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java @@ -82,6 +82,11 @@ public class VeeamBackup implements Backup { return null; } + @Override + public Date getRemoved() { + return null; + } + @Override public String getUuid() { return uid; 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 623bac55d85..b57ab16d010 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -25,6 +25,8 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; import org.apache.cloudstack.api.command.admin.backup.DeleteBackupPolicyCmd; import org.apache.cloudstack.api.command.admin.backup.ImportBackupPolicyCmd; import org.apache.cloudstack.api.command.admin.backup.ListBackupProvidersCmd; @@ -35,14 +37,13 @@ import org.apache.cloudstack.api.command.user.backup.ListBackupPoliciesCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupPolicyVMMappingsCmd; import org.apache.cloudstack.api.command.user.backup.ListVMBackupsCmd; import org.apache.cloudstack.api.command.user.backup.RemoveVMFromBackupPolicyCmd; -import org.apache.cloudstack.api.command.user.backup.RestoreBackupVolumeCmd; -import org.apache.cloudstack.api.command.user.backup.RestoreVMBackupCmd; +import org.apache.cloudstack.api.command.user.backup.RestoreVolumeFromBackupAndAttachToVMCmd; +import org.apache.cloudstack.api.command.user.backup.RestoreVMFromBackupCmd; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupPolicyDao; import org.apache.cloudstack.backup.dao.BackupPolicyVMMapDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.BooleanUtils; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -89,6 +90,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private List backupProviders; @Override + @ActionEvent(eventType = EventTypes.EVENT_IMPORT_BACKUP_POLICY, eventDescription = "importing backup policy", async = true) public BackupPolicy importBackupPolicy(Long zoneId, String policyExternalId, String policyName, String policyDescription) { final BackupProvider provider = getBackupProvider(zoneId); if (!provider.isBackupPolicy(zoneId, policyExternalId)) { @@ -105,6 +107,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { } @Override + @ActionEvent(eventType = EventTypes.EVENT_ADD_VM_TO_BACKUP_POLICY, eventDescription = "adding VM to backup policy", async = true) public boolean addVMToBackupPolicy(Long policyId, Long virtualMachineId) { VMInstanceVO vm = vmInstanceDao.findById(virtualMachineId); if (vm == null) { @@ -132,6 +135,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { } @Override + @ActionEvent(eventType = EventTypes.EVENT_REMOVE_VM_FROM_BACKUP_POLICY, eventDescription = "removing VM from backup policy", async = true) public boolean removeVMFromBackupPolicy(Long policyId, Long vmId) { BackupPolicyVO policy = backupPolicyDao.findById(policyId); if (policy == null) { @@ -171,19 +175,15 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { } @Override - public List listVMBackups(Long zoneId, Long vmId) { - BackupProvider backupProvider = getBackupProvider(zoneId); + //TODO: Add background job to sync VM backups from the provider + public List listVMBackups(Long vmId) { VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } - List existingBackups = backupDao.listByVmId(zoneId, vmId); - if (CollectionUtils.isNotEmpty(existingBackups)) { - return existingBackups; - } else { - List externalBackups = backupProvider.listVMBackups(zoneId, vm); - return backupDao.syncVMBackups(zoneId, vmId, externalBackups); - } + Long zoneId = vm.getDataCenterId(); + BackupProvider backupProvider = getBackupProvider(zoneId); + return backupDao.listByVmId(zoneId, vmId); } /** @@ -229,48 +229,76 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { } @Override - public boolean createBackup(Long vmId) { + @ActionEvent(eventType = EventTypes.EVENT_CREATE_VM_BACKUP, eventDescription = "creating VM backup", async = true) + public Backup createBackup(Long vmId) { VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("VM does not exist"); } BackupPolicyVMMap vmMap = backupPolicyVMMapDao.findByVMId(vmId); + if (vmMap == null) { + throw new CloudRuntimeException("VM " + vmId + " is not assigned to any backup policy"); + } BackupPolicyVO policy = backupPolicyDao.findById(vmMap.getPolicyId()); if (policy == null) { throw new CloudRuntimeException("Policy does not exist"); } BackupProvider backupProvider = getBackupProvider(vm.getDataCenterId()); - // FIXME: on successfully started, add an entry in DB? - return backupProvider.startBackup(policy, vm); + + Backup vmBackup = backupProvider.createVMBackup(policy, vm); + if (vmBackup == null) { + return null; + } + BackupVO backupVO = backupDao.getBackupVO(vmBackup); + return backupDao.persist(backupVO); } @Override - public boolean deleteBackup(Long vmId) { - // TODO: implement me - return false; - } - - @Override - public boolean restoreBackup(Long zoneId, Long vmId, Long backupId) { - BackupProvider backupProvider = getBackupProvider(zoneId); + @ActionEvent(eventType = EventTypes.EVENT_DELETE_VM_BACKUP, eventDescription = "deleting VM backup", async = true) + public boolean deleteBackup(Long backupId) { + BackupVO backup = backupDao.findById(backupId); + if (backup == null) { + throw new CloudRuntimeException("Backup " + backupId + " does not exist"); + } + Long zoneId = backup.getZoneId(); + Long vmId = backup.getVmId(); VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); } + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + boolean result = backupProvider.removeVMBackup(vm, backup.getExternalId()); + if (result) { + backupDao.remove(backupId); + } + return result; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_RESTORE_VM_FROM_BACKUP, eventDescription = "restoring VM from backup", async = true) + public boolean restoreVMFromBackup(Long backupId) { BackupVO backup = backupDao.findById(backupId); if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } + Long vmId = backup.getVmId(); + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM " + vmId + " does not exist"); + } return backupProvider.restoreVMFromBackup(vm.getUuid(), backup.getUuid()); } @Override - public boolean restoreBackupVolumeAndAttachToVM(Long zoneId, Long volumeId, Long vmId, Long backupId) { - BackupProvider backupProvider = getBackupProvider(zoneId); + @ActionEvent(eventType = EventTypes.EVENT_RESTORE_VM_FROM_BACKUP, eventDescription = "restoring VM from backup", async = true) + public boolean restoreBackupVolumeAndAttachToVM(Long volumeId, Long vmId, Long backupId) { BackupVO backup = backupDao.findById(backupId); if (backup == null) { throw new CloudRuntimeException("Backup " + backupId + " does not exist"); } + BackupProvider backupProvider = getBackupProvider(backup.getZoneId()); + VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { throw new CloudRuntimeException("VM " + vmId + " does not exist"); @@ -346,8 +374,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { cmdList.add(ListVMBackupsCmd.class); cmdList.add(CreateVMBackupCmd.class); cmdList.add(DeleteVMBackupCmd.class); - cmdList.add(RestoreVMBackupCmd.class); - cmdList.add(RestoreBackupVolumeCmd.class); + cmdList.add(RestoreVMFromBackupCmd.class); + cmdList.add(RestoreVolumeFromBackupAndAttachToVMCmd.class); return cmdList; } diff --git a/test/integration/smoke/test_backup_recovery.py b/test/integration/smoke/test_backup_recovery.py index b9764346c83..f9a00b95568 100644 --- a/test/integration/smoke/test_backup_recovery.py +++ b/test/integration/smoke/test_backup_recovery.py @@ -18,7 +18,7 @@ from marvin.cloudstackTestCase import cloudstackTestCase from marvin.lib.utils import (cleanup_resources) -from marvin.lib.base import (Account, ServiceOffering, VirtualMachine, BackupPolicy, Configurations) +from marvin.lib.base import (Account, ServiceOffering, VirtualMachine, BackupPolicy, Configurations, VMBackup) from marvin.lib.common import (get_domain, get_zone, get_template) from nose.plugins.attrib import attr from marvin.codes import FAILED @@ -50,8 +50,8 @@ class TestDummyBackupAndRecovery(cloudstackTestCase): # Check backup configuration values, set them to enable the dummy provider - backup_enabled_cfg = Configurations.list(cls.api_client, name='backup.framework.enabled') - backup_provider_cfg = Configurations.list(cls.api_client, name='backup.framework.provider.plugin') + backup_enabled_cfg = Configurations.list(cls.api_client, name='backup.framework.enabled', zoneid=cls.zone.id) + backup_provider_cfg = Configurations.list(cls.api_client, name='backup.framework.provider.plugin', zoneid=cls.zone.id) cls.backup_enabled = backup_enabled_cfg[0].value cls.backup_provider = backup_provider_cfg[0].value @@ -110,20 +110,24 @@ class TestDummyBackupAndRecovery(cloudstackTestCase): # 3. Delete backup policy # 4. List internal backup policies, policy id should not be listed + # Import backup policy ext_policy = self.external_policies[1] self.debug("Importing backup policy %s - %s" % (ext_policy.externalid, ext_policy.name)) policy = BackupPolicy.importExisting(self.apiclient, self.zone.id, ext_policy.externalid, ext_policy.name, ext_policy.description) + # Verify policy is listed imported_policies = BackupPolicy.listInternal(self.apiclient, self.zone.id) self.assertIsInstance(imported_policies, list, "List Backup Policies should return a valid response") self.assertNotEqual(len(imported_policies), 0, "Check if the list API returns a non-empty response") matching_policies = [x for x in imported_policies if x.id == policy.id] self.assertNotEqual(len(matching_policies), 0, "Check if there is a matching policy") + # Delete backup policy self.debug("Deleting backup policy %s" % policy.id) policy.delete(self.apiclient) + # Verify policy is not listed imported_policies = BackupPolicy.listInternal(self.apiclient, self.zone.id) self.assertIsInstance(imported_policies, list, "List Backup Policies should return a valid response") matching_policies = [x for x in imported_policies if x.id == policy.id] @@ -141,28 +145,58 @@ class TestDummyBackupAndRecovery(cloudstackTestCase): # 3. Remove VM from backup policy # 4. Verify there is no mapping between the VM and the backup policy + # Add VM to backup policy self.debug("Adding VM %s to backup policy %s" % (self.vm.id, self.policy.id)) - self.policy.addVM(self.apiclient, self.vm.id, self.zone.id) + self.policy.addVM(self.apiclient, self.vm.id) # Verify a mapping between backup policy and VM is created on DB - qresultset = self.dbclient.execute("select id from vm_instance where uuid='%s';" % self.vm.id) - vm_id = qresultset[0][0] - - qresultset = self.dbclient.execute("select id from backup_policy where uuid='%s';" % self.policy.id) - policy_id = qresultset[0][0] - - qresultset = self.dbclient.execute("select id from backup_policy_vm_map where policy_id='%d' and vm_id = '%d';" - % (policy_id, vm_id)) - - map = qresultset[0] - self.assertNotEqual(len(map), 0, "A mapping between VM and backup policy should exist on DB") - self.assertNotEqual(map[0], None, "A mapping between VM and backup policy should exist on DB") + mappings = BackupPolicy.listVMMappings(self.apiclient, self.policy.id, self.vm.id, self.zone.id) + self.assertNotEqual(len(mappings), 0, "A mapping between VM and backup policy should exist") + self.assertNotEqual(mappings[0], None, "A mapping between VM and backup policy should exist") + # Remove VM from backup policy self.debug("Removing VM %s from backup policy %s" % (self.vm.id, self.policy.id)) - self.policy.removeVM(self.apiclient, self.vm.id, self.zone.id) + self.policy.removeVM(self.apiclient, self.vm.id) - # Verify mapping is removed from DB - qresultset = self.dbclient.execute("select id from backup_policy_vm_map where policy_id='%d' and vm_id = '%d';" - % (policy_id, vm_id)) + # Verify mapping is removed + zone_mappings = BackupPolicy.listVMMappings(self.apiclient, zoneid=self.zone.id) + matching_mappings = [x for x in zone_mappings if x.policyid == self.policy.id and x.virtualmachineid == self.vm.id] + self.assertEqual(len(matching_mappings), 0, "The mapping between VM and backup policy should be removed") - self.assertEqual(len(qresultset), 0, "The mapping between VM and backup policy should be removed from DB") + @attr(tags=["advanced", "backup"], required_hardware="false") + def test_vm_backup_lifecycle(self): + """ + Test VM backup lifecycle + """ + + # Validate the following: + # 1. List VM backups, verify no backups are created + # 2. Add VM to policy + # 3. Create VM backup + # 4. List VM backups, verify backup is created + # 5. Delete VM backup + # 6. List VM backups, verify backup is deleted + # 7. Remove VM from policy + + # Verify there are no backups for the VM + backups = VMBackup.list(self.apiclient, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Create a VM backup + self.policy.addVM(self.apiclient, self.vm.id) + VMBackup.create(self.apiclient, self.vm.id) + + # Verify backup is created for the VM + backups = VMBackup.list(self.apiclient, self.vm.id) + self.assertEqual(len(backups), 1, "There should exist only one backup for the VM") + backup = backups[0] + + # Delete backup + VMBackup.delete(self.apiclient, backup.id) + + # Verify backup is deleted + backups = VMBackup.list(self.apiclient, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Remove VM from policy + self.policy.removeVM(self.apiclient, self.vm.id) \ No newline at end of file diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 6087753185d..56eaab2f10b 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -5426,20 +5426,76 @@ class BackupPolicy: cmd.id = self.id return (apiclient.deleteBackupPolicy(cmd)) - def addVM(self, apiclient, vmid, zoneid): + def addVM(self, apiclient, vmid): """Add a VM to a backup policy""" - cmd = addVirtualMachineToBackupPolicy.addVirtualMachineToBackupPolicyCmd() - cmd.backuppolicyid = self.id + cmd = addVMToBackupPolicy.addVMToBackupPolicyCmd() + cmd.policyid = self.id cmd.virtualmachineid = vmid - cmd.zoneid = zoneid - return (apiclient.addVirtualMachineToBackupPolicy(cmd)) + return (apiclient.addVMToBackupPolicy(cmd)) - def removeVM(self, apiclient, vmid, zoneid): + def removeVM(self, apiclient, vmid): """Remove a VM from a backup policy""" - cmd = removeVirtualMachineFromBackupPolicy.removeVirtualMachineFromBackupPolicyCmd() - cmd.backuppolicyid = self.id + cmd = removeVMFromBackupPolicy.removeVMFromBackupPolicyCmd() + cmd.policyid = self.id cmd.virtualmachineid = vmid - cmd.zoneid = zoneid - return (apiclient.removeVirtualMachineFromBackupPolicy(cmd)) \ No newline at end of file + return (apiclient.removeVMFromBackupPolicy(cmd)) + + @classmethod + def listVMMappings(self, apiclient, policyid=None, vmid=None, zoneid=None): + """List VM - Backup policies mappings""" + + cmd = listBackupPolicyVMMappings.listBackupPolicyVMMappingsCmd() + if vmid: + cmd.virtualmachineid = vmid + if zoneid: + cmd.zoneid = zoneid + if policyid: + cmd.policyid = policyid + return (apiclient.listBackupPolicyVMMappings(cmd)) + +class VMBackup: + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(self, apiclient, vmid): + """Create VM backup""" + + cmd = createVMBackup.createVMBackupCmd() + cmd.virtualmachineid = vmid + return (apiclient.createVMBackup(cmd)) + + @classmethod + def delete(self, apiclient, id): + """Delete VM backup""" + + cmd = deleteVMBackup.deleteVMBackupCmd() + cmd.id = id + return (apiclient.deleteVMBackup(cmd)) + + @classmethod + def list(self, apiclient, vmid): + """List VM backups""" + + cmd = listVMBackups.listVMBackupsCmd() + cmd.virtualmachineid = vmid + return (apiclient.listVMBackups(cmd)) + + def restoreVM(self, apiclient): + """Restore VM from backup""" + + cmd = restoreVMFromBackup.restoreVMFromBackupCmd() + cmd.id = self.id + return (apiclient.restoreVMFromBackup(cmd)) + + def restoreVolumeAndAttachToVM(self, apiclient, volumeid, vmid): + """Restore volume from backup and attach it to VM""" + + cmd = restoreVolumeFromBackupAndAttachToVM.restoreVolumeFromBackupAndAttachToVMCmd() + cmd.id = self.id + cmd.volumeid = volumeid + cmd.virtualmachineid = vmid + return (apiclient.restoreVolumeFromBackupAndAttachToVM(cmd)) \ No newline at end of file