From aa7d4bc5905fa29bfbc0a0f5e1ec087d61039592 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 17 Feb 2026 13:36:13 +0530 Subject: [PATCH] changes for backup job fix Signed-off-by: Abhishek Kumar --- .../admin/backup/FinalizeBackupCmd.java | 8 ++ .../command/admin/backup/StartBackupCmd.java | 65 +++++++++- .../org/apache/cloudstack/backup/Backup.java | 2 +- .../backup/IncrementalBackupService.java | 8 +- .../apache/cloudstack/veeam/RouteHandler.java | 8 ++ .../veeam/adapter/ServerAdapter.java | 111 ++++++++++++++---- .../veeam/api/DisksRouteHandler.java | 2 +- .../veeam/api/ImageTransfersRouteHandler.java | 3 +- .../cloudstack/veeam/api/VmsRouteHandler.java | 27 ++--- .../converter/BackupVOToBackupConverter.java | 42 +++++-- .../VolumeJoinVOToDiskConverter.java | 17 +++ .../backup/IncrementalBackupServiceImpl.java | 66 +++++++---- 12 files changed, 275 insertions(+), 84 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java index 129c570f7ac..3ea69b66b5b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java @@ -63,6 +63,14 @@ public class FinalizeBackupCmd extends BaseCmd implements AdminCmd { return backupId; } + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public void setBackupId(Long backupId) { + this.backupId = backupId; + } + @Override public void execute() { boolean result = incrementalBackupService.finalizeBackup(this); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java index ea899580184..b3a87178d16 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java @@ -22,24 +22,33 @@ import javax.inject.Inject; 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.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.AdminCmd; 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.backup.IncrementalBackupService; import org.apache.cloudstack.context.CallContext; +import com.cloud.event.EventTypes; + @APICommand(name = "startBackup", description = "Start a VM backup session (oVirt-style incremental backup)", responseObject = BackupResponse.class, since = "4.22.0", authorized = {RoleType.Admin}) -public class StartBackupCmd extends BaseCmd implements AdminCmd { + public class StartBackupCmd extends BaseAsyncCreateCmd implements AdminCmd { @Inject private IncrementalBackupService incrementalBackupService; + @Inject + private BackupManager backupManager; + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, @@ -47,19 +56,65 @@ public class StartBackupCmd extends BaseCmd implements AdminCmd { description = "ID of the VM") private Long vmId; + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + description = "the name of the backup") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "the description for the backup") + private String description; + public Long getVmId() { return vmId; } + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + @Override public void execute() { - BackupResponse response = incrementalBackupService.startBackup(this); - response.setResponseName(getCommandName()); - setResponseObject(response); + try { + Backup backup = incrementalBackupService.startBackup(this); + BackupResponse response = backupManager.createBackupResponse(backup, null); + + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } } @Override public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + + @Override + public void create() { + Backup backup = incrementalBackupService.createBackup(this); + + if (backup != null) { + setEntityId(backup.getId()); + setEntityUuid(backup.getUuid()); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Backup"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_CREATE; + } + + @Override + public String getEventDescription() { + return "Starting backup for Instance " + vmId; + } } 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 014fc3c483b..bc464beeb6d 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -41,7 +41,7 @@ public interface Backup extends ControlledEntity, InternalIdentity, Identity { Integer getNbdPort(); enum Status { - Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged + Allocated, Queued, BackingUp, ReadyForTransfer, FinalizingTransfer, BackedUp, Error, Failed, Restoring, Removed, Expunged } class Metric { diff --git a/api/src/main/java/org/apache/cloudstack/backup/IncrementalBackupService.java b/api/src/main/java/org/apache/cloudstack/backup/IncrementalBackupService.java index 67ef7175c41..ed97f780db1 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/IncrementalBackupService.java +++ b/api/src/main/java/org/apache/cloudstack/backup/IncrementalBackupService.java @@ -26,7 +26,6 @@ import org.apache.cloudstack.api.command.admin.backup.FinalizeImageTransferCmd; import org.apache.cloudstack.api.command.admin.backup.ListImageTransfersCmd; import org.apache.cloudstack.api.command.admin.backup.ListVmCheckpointsCmd; import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.CheckpointResponse; import org.apache.cloudstack.api.response.ImageTransferResponse; import org.apache.cloudstack.framework.config.ConfigKey; @@ -44,11 +43,16 @@ public interface IncrementalBackupService extends Configurable, PluggableService "10", "The image transfer progress polling interval in seconds.", true, ConfigKey.Scope.Global); + /** + * Creates a backup session for a VM + */ + Backup createBackup(StartBackupCmd cmd); + /** * Start a backup session for a VM * Creates a new checkpoint and starts NBD server for pull-mode backup */ - BackupResponse startBackup(StartBackupCmd cmd); + Backup startBackup(StartBackupCmd cmd); /** * Finalize a backup session diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/RouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/RouteHandler.java index a955eeac020..4e0381be699 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/RouteHandler.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/RouteHandler.java @@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.cloudstack.veeam.utils.Negotiation; +import org.apache.logging.log4j.Logger; import com.cloud.utils.component.Adapter; @@ -43,6 +44,12 @@ public interface RouteHandler extends Adapter { return path; } + static String getRequestData(HttpServletRequest req, Logger logger) { + String data = RouteHandler.getRequestData(req); + logger.info("Received method: {} request. Request-data: {}", req.getMethod(), data); + return data; + } + static String getRequestData(HttpServletRequest req) { String contentType = req.getContentType(); if (contentType == null) { @@ -52,6 +59,7 @@ public interface RouteHandler extends Adapter { if (!"application/json".equals(mime) && !"application/x-www-form-urlencoded".equals(mime)) { return null; } + String result = null; try { StringBuilder data = new StringBuilder(); String line; diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java index 6ccb2c224ea..761abb3f0ab 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -37,8 +38,8 @@ import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiServerService; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.command.admin.backup.FinalizeBackupCmd; +import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; import org.apache.cloudstack.api.command.admin.vm.DeployVMCmdByAdmin; -import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; import org.apache.cloudstack.api.command.user.network.ListNetworksCmd; import org.apache.cloudstack.api.command.user.offering.ListServiceOfferingsCmd; @@ -66,6 +67,9 @@ import org.apache.cloudstack.backup.IncrementalBackupService; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.ImageTransferDao; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; @@ -102,6 +106,7 @@ import org.apache.cloudstack.veeam.api.dto.VnicProfile; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import com.cloud.api.query.dao.AsyncJobJoinDao; import com.cloud.api.query.dao.DataCenterJoinDao; @@ -246,6 +251,9 @@ public class ServerAdapter extends ManagerBase { @Inject ApiServerService apiServerService; + @Inject + AsyncJobDao asyncJobDao; + @Inject AsyncJobJoinDao asyncJobJoinDao; @@ -840,7 +848,7 @@ public class ServerAdapter extends ManagerBase { } public ImageTransfer getImageTransfer(String uuid) { - ImageTransferVO vo = imageTransferDao.findByUuid(uuid); + ImageTransferVO vo = imageTransferDao.findByUuidIncludingRemoved(uuid); if (vo == null) { throw new InvalidParameterValueException("Image transfer with ID " + uuid + " not found"); } @@ -863,7 +871,15 @@ public class ServerAdapter extends ManagerBase { throw new InvalidParameterValueException("Invalid or missing direction"); } Format format = EnumUtils.fromString(Format.class, request.getFormat()); - return createImageTransfer(null, volumeVO.getId(), direction, format); + Long backupId = null; + if (request.getBackup() != null && StringUtils.isNotBlank(request.getBackup().getId())) { + BackupVO backupVO = backupDao.findByUuid(request.getBackup().getId()); + if (backupVO == null) { + throw new InvalidParameterValueException("Backup with ID " + request.getBackup().getId() + " not found"); + } + backupId = backupVO.getId(); + } + return createImageTransfer(backupId, volumeVO.getId(), direction, format); } public boolean handleCancelImageTransfer(String uuid) { @@ -887,7 +903,7 @@ public class ServerAdapter extends ManagerBase { CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { org.apache.cloudstack.backup.ImageTransfer imageTransfer = - incrementalBackupService.createImageTransfer(volumeId, null, direction, format); + incrementalBackupService.createImageTransfer(volumeId, backupId, direction, format); ImageTransferVO imageTransferVO = imageTransferDao.findById(imageTransfer.getId()); return ImageTransferVOToImageTransferConverter.toImageTransfer(imageTransferVO, this::getHostById, this::getVolumeById); } finally { @@ -1054,7 +1070,7 @@ public class ServerAdapter extends ManagerBase { throw new InvalidParameterValueException("VM with ID " + uuid + " not found"); } List backups = backupDao.searchByVmIds(List.of(vo.getId())); - return BackupVOToBackupConverter.toBackupList(backups, id -> vo); + return BackupVOToBackupConverter.toBackupList(backups, id -> vo, this::getHostById); } public Backup createInstanceBackup(final String vmUuid, final Backup request) { @@ -1062,26 +1078,26 @@ public class ServerAdapter extends ManagerBase { if (vmVo == null) { throw new InvalidParameterValueException("VM with ID " + vmUuid + " not found"); } - Pair serviceUserAccount = createServiceAccountIfNeeded(); - CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); + // Register a context as resource owner + Account account = accountService.getAccount(vmVo.getAccountId()); + CallContext ctx = CallContext.register(vmVo.getUserId(), vmVo.getAccountId()); try { - CreateBackupCmd cmd = new CreateBackupCmd(); + StartBackupCmd cmd = new StartBackupCmd(); ComponentContext.inject(cmd); Map params = new HashMap<>(); params.put(ApiConstants.VIRTUAL_MACHINE_ID, vmVo.getUuid()); params.put(ApiConstants.NAME, request.getName()); params.put(ApiConstants.DESCRIPTION, request.getDescription()); ApiServerService.AsyncCmdResult result = - apiServerService.processAsyncCmd(cmd, params, ctx, serviceUserAccount.first().getId(), - serviceUserAccount.second()); + apiServerService.processAsyncCmd(cmd, params, ctx, vmVo.getUserId(), account); if (result.objectId == null) { - throw new CloudRuntimeException("No backup ID returned"); + throw new CloudRuntimeException("Unexpected backup ID returned"); } BackupVO vo = backupDao.findById(result.objectId); if (vo == null) { throw new CloudRuntimeException("Backup not found"); } - return BackupVOToBackupConverter.toBackup(vo, id -> vmVo); + return BackupVOToBackupConverter.toBackup(vo, id -> vmVo, this::getHostById, this::getBackupDisks); } catch (Exception e) { throw new CloudRuntimeException("Failed to create backup: " + e.getMessage(), e); } finally { @@ -1089,16 +1105,53 @@ public class ServerAdapter extends ManagerBase { } } + @Nullable + private BackupVO getBackupFromJob(ApiServerService.AsyncCmdResult result, UserVmVO vmVo) { + AsyncJobVO jobVo = null; + // wait for job to complete and get backup ID + long timeoutNanos = TimeUnit.MINUTES.toNanos(2); + final long deadline = System.nanoTime() + timeoutNanos; + long sleepMillis = 1000; + while (System.nanoTime() < deadline) { + jobVo = asyncJobDao.findByIdIncludingRemoved(result.jobId); + if (jobVo == null) { + throw new CloudRuntimeException("Failed to find job for backup creation"); + } + if (!JobInfo.Status.IN_PROGRESS.equals(jobVo.getStatus())) { + break; + } + try { + Thread.sleep(sleepMillis); + // back off gradually to reduce DB pressure + sleepMillis = Math.min(5000, sleepMillis + 500); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new CloudRuntimeException("Interrupted while waiting for backup creation job", ie); + } + } + // if still in progress after timeout, fail fast + if (jobVo != null && JobInfo.Status.IN_PROGRESS.equals(jobVo.getStatus())) { + throw new CloudRuntimeException("Timed out waiting for backup creation job"); + } + BackupVO vo = null; + List backups = backupDao.searchByVmIds(List.of(vmVo.getId())); + if (CollectionUtils.isNotEmpty(backups)) { + vo = backups.get(0); + } + return vo; + } + public Backup getBackup(String uuid) { BackupVO vo = backupDao.findByUuid(uuid); if (vo == null) { throw new InvalidParameterValueException("Backup with ID " + uuid + " not found"); } - return BackupVOToBackupConverter.toBackup(vo, id -> userVmDao.findById(id)); + return BackupVOToBackupConverter.toBackup(vo, id -> userVmDao.findById(id), this::getHostById, + this::getBackupDisks); } public List listDisksByBackupUuid(final String uuid) { - throw new InvalidParameterValueException("List Backup Disks with ID " + uuid + " not implmenented"); + throw new InvalidParameterValueException("List Backup Disks with ID " + uuid + " not implemented"); // BackupVO vo = backupDao.findByUuid(uuid); // if (vo == null) { // throw new InvalidParameterValueException("Backup with ID " + uuid + " not found"); @@ -1106,28 +1159,28 @@ public class ServerAdapter extends ManagerBase { // return VolumeJoinVOToDiskConverter.toDiskList(volumes); } - public void finalizeBackup(final String vmUuid, final String uuid, String data) { - ResourceAction action = null; - UserVmVO vmVo = userVmDao.findByUuid(vmUuid); - if (vmVo == null) { + public Backup finalizeBackup(final String vmUuid, final String backupUuid) { + UserVmVO vm = userVmDao.findByUuid(vmUuid); + if (vm == null) { throw new InvalidParameterValueException("Instance with ID " + vmUuid + " not found"); } - BackupVO vo = backupDao.findByUuid(uuid); - if (vo == null) { - throw new InvalidParameterValueException("Backup with ID " + uuid + " not found"); + BackupVO backup = backupDao.findByUuid(backupUuid); + if (backup == null) { + throw new InvalidParameterValueException("Backup with ID " + backupUuid + " not found"); } Pair serviceUserAccount = createServiceAccountIfNeeded(); - CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); + CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { FinalizeBackupCmd cmd = new FinalizeBackupCmd(); ComponentContext.inject(cmd); - Map params = new HashMap<>(); - params.put(ApiConstants.VIRTUAL_MACHINE_ID, vmVo.getUuid()); - params.put(ApiConstants.BACKUP_ID, vo.getUuid()); + cmd.setBackupId(backup.getId()); + cmd.setVmId(vm.getId()); boolean result = incrementalBackupService.finalizeBackup(cmd); if (!result) { throw new CloudRuntimeException("Failed to finalize backup"); } + backup = backupDao.findById(backup.getId()); + return BackupVOToBackupConverter.toBackup(backup, id -> vm, this::getHostById, this::getBackupDisks); } catch (Exception e) { throw new CloudRuntimeException("Failed to finalize backup: " + e.getMessage(), e); } finally { @@ -1135,6 +1188,14 @@ public class ServerAdapter extends ManagerBase { } } + protected List getBackupDisks(final BackupVO backup) { + List volumeInfos = backup.getBackedUpVolumes(); + if (CollectionUtils.isEmpty(volumeInfos)) { + return Collections.emptyList(); + } + return VolumeJoinVOToDiskConverter.toDiskListFromVolumeInfos(volumeInfos); + } + public List listCheckpointsByInstanceUuid(final String uuid) { throw new InvalidParameterValueException("Checkpoints for VM with ID " + uuid + " not implemented"); // UserVmVO vo = userVmDao.findByUuid(uuid); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/DisksRouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/DisksRouteHandler.java index c13bacdfba0..b69164d2d8d 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/DisksRouteHandler.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/DisksRouteHandler.java @@ -123,7 +123,7 @@ public class DisksRouteHandler extends ManagerBase implements RouteHandler { protected void handlePost(final HttpServletRequest req, final HttpServletResponse resp, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException { - String data = RouteHandler.getRequestData(req); + String data = RouteHandler.getRequestData(req, logger); logger.info("Received POST request on /api/disks endpoint. Request-data: {}", data); // ToDo: remove try { Disk request = io.getMapper().jsonMapper().readValue(data, Disk.class); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ImageTransfersRouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ImageTransfersRouteHandler.java index 9c77a28e426..bff16e00d82 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ImageTransfersRouteHandler.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ImageTransfersRouteHandler.java @@ -113,8 +113,7 @@ public class ImageTransfersRouteHandler extends ManagerBase implements RouteHand protected void handlePost(final HttpServletRequest req, final HttpServletResponse resp, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException { - String data = RouteHandler.getRequestData(req); - logger.info("Received POST request on /api/imagetransfers endpoint. Request-data: {}", data); + String data = RouteHandler.getRequestData(req, logger); try { ImageTransfer request = io.getMapper().jsonMapper().readValue(data, ImageTransfer.class); ImageTransfer response = serverAdapter.handleCreateImageTransfer(request); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java index 9eb12fdf396..4618aa2ae54 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java @@ -246,12 +246,6 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { io.notFound(resp, null, outFormat); } - protected String getRequestData(final HttpServletRequest req) { - String data = RouteHandler.getRequestData(req); - logger.info("Received method: {} request. Request-data: {}", req.getMethod(), data); - return data; - } - protected void handleGet(final HttpServletRequest req, final HttpServletResponse resp, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException { final VmListQuery q = fromRequest(req); @@ -310,7 +304,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { protected void handlePost(final HttpServletRequest req, final HttpServletResponse resp, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException { - String data = getRequestData(req); + String data = RouteHandler.getRequestData(req, logger); try { Vm request = io.getMapper().jsonMapper().readValue(data, Vm.class); Vm response = serverAdapter.createInstance(request); @@ -332,7 +326,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { protected void handleUpdateById(final String id, final HttpServletRequest req, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { - String data = RouteHandler.getRequestData(req); + String data = RouteHandler.getRequestData(req, logger); logger.info("Received PUT request. Request-data: {}", data); try { Vm request = io.getMapper().jsonMapper().readValue(data, Vm.class); @@ -397,7 +391,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { protected void handlePostDiskAttachmentForVmId(final String id, final HttpServletRequest req, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { - String data = getRequestData(req); + String data = RouteHandler.getRequestData(req, logger); try { DiskAttachment request = io.getMapper().jsonMapper().readValue(data, DiskAttachment.class); DiskAttachment response = serverAdapter.handleInstanceAttachDisk(id, request); @@ -421,7 +415,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { protected void handlePostNicForVmId(final String id, final HttpServletRequest req, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { - String data = getRequestData(req); + String data = RouteHandler.getRequestData(req, logger); try { Nic request = io.getMapper().jsonMapper().readValue(data, Nic.class); Nic response = serverAdapter.handleAttachInstanceNic(id, request); @@ -445,7 +439,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { protected void handlePostSnapshotForVmId(final String id, final HttpServletRequest req, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { - String data = getRequestData(req); + String data = RouteHandler.getRequestData(req, logger); try { Snapshot request = io.getMapper().jsonMapper().readValue(data, Snapshot.class); Snapshot response = serverAdapter.handleCreateInstanceSnapshot(id, request); @@ -486,7 +480,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { //ToDo: implement - String data = getRequestData(req); + String data = RouteHandler.getRequestData(req, logger); io.badRequest(resp, "Not implemented", outFormat); } @@ -504,11 +498,11 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { protected void handlePostBackupForVmId(final String id, final HttpServletRequest req, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { - String data = getRequestData(req); + String data = RouteHandler.getRequestData(req, logger); try { Backup request = io.getMapper().jsonMapper().readValue(data, Backup.class); Backup response = serverAdapter.createInstanceBackup(id, request); - io.getWriter().write(resp, HttpServletResponse.SC_ACCEPTED, response, outFormat); + io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat); } catch (JsonProcessingException | CloudRuntimeException e) { io.badRequest(resp, e.getMessage(), outFormat); } @@ -539,10 +533,9 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { protected void handleFinalizeBackupById(final String vmId, final String backupId, final HttpServletRequest req, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { - String data = getRequestData(req); try { - serverAdapter.finalizeBackup(vmId, backupId, data); - io.getWriter().write(resp, HttpServletResponse.SC_OK, null, outFormat); + Backup backup = serverAdapter.finalizeBackup(vmId, backupId); + io.getWriter().write(resp, HttpServletResponse.SC_OK, backup, outFormat); } catch (CloudRuntimeException e) { io.badRequest(resp, e.getMessage(), outFormat); } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/BackupVOToBackupConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/BackupVOToBackupConverter.java index 5d93524ef52..728d38e6c31 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/BackupVOToBackupConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/BackupVOToBackupConverter.java @@ -25,13 +25,16 @@ import org.apache.cloudstack.backup.BackupVO; import org.apache.cloudstack.veeam.VeeamControlService; import org.apache.cloudstack.veeam.api.VmsRouteHandler; import org.apache.cloudstack.veeam.api.dto.Backup; +import org.apache.cloudstack.veeam.api.dto.Disk; import org.apache.cloudstack.veeam.api.dto.Vm; +import com.cloud.api.query.vo.HostJoinVO; import com.cloud.vm.UserVmVO; public class BackupVOToBackupConverter { - public static Backup toBackup(final BackupVO backupVO, final Function vmResolver) { + public static Backup toBackup(final BackupVO backupVO, final Function vmResolver, + final Function hostResolver, final Function> disksResolver) { Backup backup = new Backup(); final String basePath = VeeamControlService.ContextPath.value(); backup.setHref(basePath + VmsRouteHandler.BASE_ROUTE + "/backups/" + backupVO.getUuid()); @@ -39,13 +42,13 @@ public class BackupVOToBackupConverter { backup.setName(backupVO.getName()); backup.setDescription(backupVO.getDescription()); backup.setCreationDate(backupVO.getDate().getTime()); -// backup.setPhase(backupVO.getPhase().name()); -// if (backupVO.getFromCheckpointId() != null) { -// backup.setFromCheckpointId(backupVO.getFromCheckpointId().toString()); -// } -// if (backupVO.getToCheckpointId() != null) { -// backup.setToCheckpointId(backupVO.getToCheckpointId().toString()); -// } + backup.setPhase(mapStatusToPhase(backupVO.getStatus())); + if (backupVO.getFromCheckpointId() != null) { + backup.setFromCheckpointId(backupVO.getFromCheckpointId()); + } + if (backupVO.getToCheckpointId() != null) { + backup.setToCheckpointId(backupVO.getToCheckpointId()); + } if (vmResolver != null) { final UserVmVO vmVO = vmResolver.apply(backupVO.getVmId()); if (vmVO != null) { @@ -55,10 +58,29 @@ public class BackupVOToBackupConverter { return backup; } - public static List toBackupList(final List backupVOs, final Function vmResolver) { + public static List toBackupList(final List backupVOs, final Function vmResolver, + final Function hostResolver) { return backupVOs .stream() - .map(backupVO -> toBackup(backupVO, vmResolver)) + .map(backupVO -> toBackup(backupVO, vmResolver, hostResolver, null)) .collect(Collectors.toList()); } + + private static String mapStatusToPhase(final BackupVO.Status status) { + switch (status) { + case Allocated: + case Queued: + return "initializing"; + case BackingUp: + return "starting"; + case ReadyForTransfer: + return "ready"; + case FinalizingTransfer: + return "finalizing"; + case Restoring: + case BackedUp: + return "succeeded"; + } + return "failed"; + } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java index 1214ccd172a..2808e20a188 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java @@ -17,10 +17,12 @@ package org.apache.cloudstack.veeam.api.converter; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.veeam.VeeamControlService; import org.apache.cloudstack.veeam.api.ApiService; import org.apache.cloudstack.veeam.api.DisksRouteHandler; @@ -133,6 +135,21 @@ public class VolumeJoinVOToDiskConverter { .collect(Collectors.toList()); } + public static List toDiskListFromVolumeInfos(final List volumeInfos) { + List disks = new ArrayList<>(); + for (Backup.VolumeInfo volumeInfo : volumeInfos) { + Disk disk = new Disk(); + disk.setId(volumeInfo.getUuid()); + disk.setName(volumeInfo.getUuid()); + disk.setProvisionedSize(String.valueOf(volumeInfo.getSize())); + disk.setActualSize(String.valueOf(volumeInfo.getSize())); + disk.setTotalSize(String.valueOf(volumeInfo.getSize())); + disk.setBootable(String.valueOf(Volume.Type.ROOT.equals(volumeInfo.getType()))); + disks.add(disk); + } + return disks; + } + public static DiskAttachment toDiskAttachment(final VolumeJoinVO vol) { final DiskAttachment da = new DiskAttachment(); final String basePath = VeeamControlService.ContextPath.value(); diff --git a/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java b/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java index b2e906aed4f..40c459782b6 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java @@ -38,19 +38,19 @@ import org.apache.cloudstack.api.command.admin.backup.FinalizeImageTransferCmd; import org.apache.cloudstack.api.command.admin.backup.ListImageTransfersCmd; import org.apache.cloudstack.api.command.admin.backup.ListVmCheckpointsCmd; import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.CheckpointResponse; import org.apache.cloudstack.api.response.ImageTransferResponse; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.ImageTransferDao; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.springframework.stereotype.Component; @@ -131,7 +131,8 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme } @Override - public BackupResponse startBackup(StartBackupCmd cmd) { + public Backup createBackup(StartBackupCmd cmd) { + //ToDo: add config check, access check, resource count check, etc. Long vmId = cmd.getVmId(); VMInstanceVO vm = vmInstanceDao.findById(vmId); @@ -148,11 +149,17 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme throw new CloudRuntimeException("Backup already in progress for VM: " + vmId); } - boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId()); - BackupVO backup = new BackupVO(); backup.setVmId(vmId); - backup.setName(vmId + "-" + DateTime.now()); + String name = cmd.getName(); + if (StringUtils.isEmpty(name)) { + name = vmId + "-" + DateTime.now(); + } + backup.setName(name); + final String description = cmd.getDescription(); + if (StringUtils.isNotEmpty(description)) { + backup.setDescription(description); + } backup.setAccountId(vm.getAccountId()); backup.setDomainId(vm.getDomainId()); backup.setZoneId(vm.getDataCenterId()); @@ -162,7 +169,6 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme String toCheckpointId = "ckp-" + UUID.randomUUID().toString().substring(0, 8); String fromCheckpointId = vm.getActiveCheckpointId(); - Long fromCheckpointCreateTime = vm.getActiveCheckpointCreateTime(); backup.setToCheckpointId(toCheckpointId); backup.setFromCheckpointId(fromCheckpointId); @@ -174,27 +180,39 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme // Will be changed later if incremental was done backup.setType("FULL"); - backup = backupDao.persist(backup); + return backupDao.persist(backup); + } + @Override + public Backup startBackup(StartBackupCmd cmd) { + BackupVO backup = backupDao.findById(cmd.getEntityId()); + Long vmId = cmd.getVmId(); + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + throw new CloudRuntimeException("VM not found: " + vmId); + } List volumes = volumeDao.findByInstance(vmId); Map diskPathUuidMap = new HashMap<>(); for (Volume vol : volumes) { String volumePath = getVolumePathForFileBasedBackend(vol); diskPathUuidMap.put(volumePath, vol.getUuid()); } + long hostId = backup.getHostId(); Host host = hostDao.findById(hostId); StartBackupCommand startCmd = new StartBackupCommand( vm.getInstanceName(), - toCheckpointId, - fromCheckpointId, - fromCheckpointCreateTime, - nbdPort, + backup.getToCheckpointId(), + backup.getFromCheckpointId(), + vm.getActiveCheckpointCreateTime(), + backup.getNbdPort(), diskPathUuidMap, host.getPrivateIpAddress(), vm.getState() == State.Stopped ); + boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId()); + StartBackupAnswer answer; try { if (dummyOffering) { @@ -218,13 +236,14 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme // todo: set it in the backend backup.setType("Incremental"); } + backup.setStatus(Backup.Status.ReadyForTransfer); backupDao.update(backup.getId(), backup); + return backup; + } - BackupResponse response = new BackupResponse(); - response.setId(backup.getUuid()); - response.setVmId(vm.getUuid()); - response.setStatus(backup.getStatus()); - return response; + protected void updateBackupState(BackupVO backup, Backup.Status newStatus) { + backup.setStatus(newStatus); + backupDao.update(backup.getId(), backup); } @Override @@ -249,9 +268,12 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme boolean dummyOffering = isDummyOffering(backup.getBackupOfferingId()); + updateBackupState(backup, Backup.Status.FinalizingTransfer); + List transfers = imageTransferDao.listByBackupId(backupId); for (ImageTransferVO transfer : transfers) { if (transfer.getPhase() != ImageTransferVO.Phase.finished) { + updateBackupState(backup, Backup.Status.Failed); throw new CloudRuntimeException(String.format("Image transfer %s not finalized for backup: %s", transfer.getUuid(), backup.getUuid())); } imageTransferDao.remove(transfer.getId()); @@ -269,10 +291,12 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme } } catch (AgentUnavailableException | OperationTimedoutException e) { + updateBackupState(backup, Backup.Status.Failed); throw new CloudRuntimeException("Failed to communicate with agent", e); } if (!answer.getResult()) { + updateBackupState(backup, Backup.Status.Failed); throw new CloudRuntimeException("Failed to stop backup: " + answer.getDetails()); } } @@ -290,7 +314,8 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme } // Delete backup session record - backupDao.remove(backup.getId()); + backup.setStatus(Backup.Status.BackedUp); + backupDao.update(backupId, backup); return true; @@ -616,8 +641,7 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme } imageTransfer.setPhase(ImageTransferVO.Phase.finished); imageTransferDao.update(imageTransfer.getId(), imageTransfer); -// ToDo: check this -// imageTransferDao.remove(imageTransfer.getId()); + imageTransferDao.remove(imageTransfer.getId()); return true; }