changes for backup job fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-02-17 13:36:13 +05:30
parent 894eef1210
commit aa7d4bc590
12 changed files with 275 additions and 84 deletions

View File

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

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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;

View File

@ -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<BackupVO> 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<User, Account> 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<String, String> 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<BackupVO> 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<Disk> 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<User, Account> 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<String, String> 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<Disk> getBackupDisks(final BackupVO backup) {
List<org.apache.cloudstack.backup.Backup.VolumeInfo> volumeInfos = backup.getBackedUpVolumes();
if (CollectionUtils.isEmpty(volumeInfos)) {
return Collections.emptyList();
}
return VolumeJoinVOToDiskConverter.toDiskListFromVolumeInfos(volumeInfos);
}
public List<Checkpoint> listCheckpointsByInstanceUuid(final String uuid) {
throw new InvalidParameterValueException("Checkpoints for VM with ID " + uuid + " not implemented");
// UserVmVO vo = userVmDao.findByUuid(uuid);

View File

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

View File

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

View File

@ -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);
}

View File

@ -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<Long, UserVmVO> vmResolver) {
public static Backup toBackup(final BackupVO backupVO, final Function<Long, UserVmVO> vmResolver,
final Function<Long, HostJoinVO> hostResolver, final Function<BackupVO, List<Disk>> 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<Backup> toBackupList(final List<BackupVO> backupVOs, final Function<Long, UserVmVO> vmResolver) {
public static List<Backup> toBackupList(final List<BackupVO> backupVOs, final Function<Long, UserVmVO> vmResolver,
final Function<Long, HostJoinVO> 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";
}
}

View File

@ -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<Disk> toDiskListFromVolumeInfos(final List<Backup.VolumeInfo> volumeInfos) {
List<Disk> 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();

View File

@ -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<VolumeVO> volumes = volumeDao.findByInstance(vmId);
Map<String, String> 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<ImageTransferVO> 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;
}