Basic working version-1

This commit is contained in:
Abhisar Sinha 2026-01-15 10:25:07 +05:30 committed by Abhishek Kumar
parent 81c3b5ba0b
commit f396c5cc74
33 changed files with 2431 additions and 0 deletions

View File

@ -0,0 +1,87 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
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.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.ImageTransferResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.backup.IncrementalBackupService;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = "createImageTransfer",
description = "Create image transfer for a disk in backup",
responseObject = ImageTransferResponse.class,
since = "4.22.0",
authorized = {RoleType.Admin})
public class CreateImageTransferCmd extends BaseCmd implements AdminCmd {
@Inject
private IncrementalBackupService incrementalBackupService;
@Parameter(name = ApiConstants.BACKUP_ID,
type = CommandType.UUID,
entityType = BackupResponse.class,
required = true,
description = "ID of the backup")
private Long backupId;
@Parameter(name = ApiConstants.VOLUME_ID,
type = CommandType.UUID,
entityType = VolumeResponse.class,
required = true,
description = "ID of the disk/volume")
private Long volumeId;
@Parameter(name = ApiConstants.DIRECTION,
type = CommandType.STRING,
required = true,
description = "Direction of the transfer: upload, download")
private String direction;
public Long getBackupId() {
return backupId;
}
public Long getVolumeId() {
return volumeId;
}
public String getDirection() {
return direction;
}
@Override
public void execute() {
ImageTransferResponse response = incrementalBackupService.createImageTransfer(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,77 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
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.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.backup.IncrementalBackupService;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = "deleteVmCheckpoint",
description = "Delete a VM checkpoint",
responseObject = SuccessResponse.class,
since = "4.22.0",
authorized = {RoleType.Admin})
public class DeleteVmCheckpointCmd extends BaseCmd implements AdminCmd {
@Inject
private IncrementalBackupService incrementalBackupService;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
entityType = UserVmResponse.class,
required = true,
description = "ID of the VM")
private Long vmId;
@Parameter(name = "checkpointid",
type = CommandType.STRING,
required = true,
description = "Checkpoint ID")
private String checkpointId;
public Long getVmId() {
return vmId;
}
public String getCheckpointId() {
return checkpointId;
}
@Override
public void execute() {
boolean result = incrementalBackupService.deleteVmCheckpoint(this);
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess(result);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,79 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
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.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
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.IncrementalBackupService;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = "finalizeBackup",
description = "Finalize a VM backup session",
responseObject = SuccessResponse.class,
since = "4.22.0",
authorized = {RoleType.Admin})
public class FinalizeBackupCmd extends BaseCmd implements AdminCmd {
@Inject
private IncrementalBackupService incrementalBackupService;
@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.ID,
type = CommandType.UUID,
entityType = BackupResponse.class,
required = true,
description = "ID of the backup")
private Long backupId;
public Long getVmId() {
return vmId;
}
public Long getBackupId() {
return backupId;
}
@Override
public void execute() {
boolean result = incrementalBackupService.finalizeBackup(this);
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess(result);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,67 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
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.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.ImageTransferResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.backup.IncrementalBackupService;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = "finalizeImageTransfer",
description = "Finalize an image transfer",
responseObject = SuccessResponse.class,
since = "4.22.0",
authorized = {RoleType.Admin})
public class FinalizeImageTransferCmd extends BaseCmd implements AdminCmd {
@Inject
private IncrementalBackupService incrementalBackupService;
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = ImageTransferResponse.class,
required = true,
description = "ID of the image transfer")
private Long imageTransferId;
public Long getImageTransferId() {
return imageTransferId;
}
@Override
public void execute() {
boolean result = incrementalBackupService.finalizeImageTransfer(this);
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess(result);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,79 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import java.util.List;
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.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.ImageTransferResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.backup.IncrementalBackupService;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = "listImageTransfers",
description = "List image transfers for a backup",
responseObject = ImageTransferResponse.class,
since = "4.22.0",
authorized = {RoleType.Admin})
public class ListImageTransfersCmd extends BaseListCmd implements AdminCmd {
@Inject
private IncrementalBackupService incrementalBackupService;
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = ImageTransferResponse.class,
description = "ID of the Image Transfer")
private Long id;
@Parameter(name = ApiConstants.BACKUP_ID,
type = CommandType.UUID,
entityType = BackupResponse.class,
description = "ID of the backup")
private Long backupId;
public Long getId() {
return id;
}
public Long getBackupId() {
return backupId;
}
@Override
public void execute() {
List<ImageTransferResponse> responses = incrementalBackupService.listImageTransfers(this);
ListResponse<ImageTransferResponse> response = new ListResponse<>();
response.setResponses(responses);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,69 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
import java.util.List;
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.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.response.CheckpointResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.backup.IncrementalBackupService;
@APICommand(name = "listVmCheckpoints",
description = "List checkpoints for a VM",
responseObject = CheckpointResponse.class,
since = "4.22.0",
authorized = {RoleType.Admin})
public class ListVmCheckpointsCmd extends BaseListCmd implements AdminCmd {
@Inject
private IncrementalBackupService incrementalBackupService;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
entityType = UserVmResponse.class,
required = true,
description = "ID of the VM")
private Long vmId;
public Long getVmId() {
return vmId;
}
@Override
public void execute() {
List<CheckpointResponse> responses = incrementalBackupService.listVmCheckpoints(this);
ListResponse<CheckpointResponse> response = new ListResponse<>();
response.setResponses(responses);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return 0;
}
}

View File

@ -0,0 +1,65 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.command.admin.backup;
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.Parameter;
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.IncrementalBackupService;
import org.apache.cloudstack.context.CallContext;
@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 {
@Inject
private IncrementalBackupService incrementalBackupService;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
type = CommandType.UUID,
entityType = UserVmResponse.class,
required = true,
description = "ID of the VM")
private Long vmId;
public Long getVmId() {
return vmId;
}
@Override
public void execute() {
BackupResponse response = incrementalBackupService.startBackup(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -127,6 +127,18 @@ public class BackupResponse extends BaseResponse {
@Param(description = "Indicates whether the VM from which the backup was taken is expunged or not", since = "4.22.0")
private Boolean isVmExpunged;
@SerializedName("from_checkpoint_id")
@Param(description = "Previous active checkpoint id for incremental backups", since = "4.22.0")
private String fromCheckpointId;
@SerializedName("to_checkpoint_id")
@Param(description = "Next checkpoint id for incremental backups", since = "4.22.0")
private String toCheckpointId;
@SerializedName(ApiConstants.HOST_ID)
@Param(description = "Host ID where the backup is running", since = "4.22.0")
private String hostId;
public String getId() {
return id;
}
@ -314,4 +326,28 @@ public class BackupResponse extends BaseResponse {
public void setVmExpunged(Boolean isVmExpunged) {
this.isVmExpunged = isVmExpunged;
}
public void setFromCheckpointId(String fromCheckpointId) {
this.fromCheckpointId = fromCheckpointId;
}
public String getFromCheckpointId() {
return this.fromCheckpointId;
}
public void setToCheckpointId(String toCheckpointId) {
this.toCheckpointId = toCheckpointId;
}
public String getToCheckpointId() {
return this.toCheckpointId;
}
public void setHostId(String hostId) {
this.hostId = hostId;
}
public String getHostId() {
return this.hostId;
}
}

View File

@ -0,0 +1,50 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.response;
import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
public class CheckpointResponse extends BaseResponse {
@SerializedName("checkpointid")
@Param(description = "the checkpoint ID")
private String checkpointId;
@SerializedName("createtime")
@Param(description = "the checkpoint creation time")
private Long createTime;
@SerializedName("isactive")
@Param(description = "whether this is the active checkpoint")
private Boolean isActive;
public void setCheckpointId(String checkpointId) {
this.checkpointId = checkpointId;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
}

View File

@ -0,0 +1,104 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.api.response;
import java.util.Date;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.backup.ImageTransfer;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
@EntityReference(value = ImageTransfer.class)
public class ImageTransferResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "the ID of the image transfer")
private String id;
@SerializedName("backupid")
@Param(description = "the backup ID")
private String backupId;
@SerializedName("vmid")
@Param(description = "the VM ID")
private String vmId;
@SerializedName(ApiConstants.VOLUME_ID)
@Param(description = "the disk/volume ID")
private String diskId;
@SerializedName("devicename")
@Param(description = "the device name (vda, vdb, etc)")
private String deviceName;
@SerializedName("transferurl")
@Param(description = "the transfer URL")
private String transferUrl;
@SerializedName("phase")
@Param(description = "the transfer phase")
private String phase;
@SerializedName("direction")
@Param(description = "the image transfer direction: upload / download")
private String direction;
@SerializedName(ApiConstants.CREATED)
@Param(description = "the date created")
private Date created;
public void setId(String id) {
this.id = id;
}
public void setBackupId(String backupId) {
this.backupId = backupId;
}
public void setVmId(String vmId) {
this.vmId = vmId;
}
public void setDiskId(String diskId) {
this.diskId = diskId;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setTransferUrl(String transferUrl) {
this.transferUrl = transferUrl;
}
public void setPhase(String phase) {
this.phase = phase;
}
public void setDirection(String direction) {
this.direction = direction;
}
public void setCreated(Date created) {
this.created = created;
}
}

View File

@ -30,6 +30,16 @@ import com.cloud.storage.Volume;
public interface Backup extends ControlledEntity, InternalIdentity, Identity {
String getFromCheckpointId();
String getToCheckpointId();
Long getCheckpointCreateTime();
Long getHostId();
Integer getNbdPort();
enum Status {
Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged
}

View File

@ -0,0 +1,53 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.backup;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.InternalIdentity;
public interface ImageTransfer extends ControlledEntity, InternalIdentity {
public enum Direction {
upload, download
}
public enum Phase {
initializing, transferring, finished, failed
}
String getUuid();
long getBackupId();
long getVmId();
long getDiskId();
String getDeviceName();
long getHostId();
int getNbdPort();
String getTransferUrl();
Phase getPhase();
Direction getDirection();
String getSignedTicketId();
}

View File

@ -0,0 +1,78 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.List;
import org.apache.cloudstack.api.command.admin.backup.CreateImageTransferCmd;
import org.apache.cloudstack.api.command.admin.backup.DeleteVmCheckpointCmd;
import org.apache.cloudstack.api.command.admin.backup.FinalizeBackupCmd;
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 com.cloud.utils.component.PluggableService;
/**
* Service for managing oVirt-style incremental backups using libvirt checkpoints
*/
public interface IncrementalBackupService extends PluggableService {
/**
* Start a backup session for a VM
* Creates a new checkpoint and starts NBD server for pull-mode backup
*/
BackupResponse startBackup(StartBackupCmd cmd);
/**
* Finalize a backup session
* Stops NBD server, updates checkpoint tracking, deletes old checkpoints
*/
boolean finalizeBackup(FinalizeBackupCmd cmd);
/**
* Create an image transfer object for a disk
* Registers NBD endpoint with ImageIO (stubbed for POC)
*/
ImageTransferResponse createImageTransfer(CreateImageTransferCmd cmd);
/**
* Finalize an image transfer
* Marks transfer as complete (NBD is closed globally in finalize backup)
*/
boolean finalizeImageTransfer(FinalizeImageTransferCmd cmd);
/**
* List image transfers for a backup
*/
List<ImageTransferResponse> listImageTransfers(ListImageTransfersCmd cmd);
/**
* List checkpoints for a VM
*/
List<CheckpointResponse> listVmCheckpoints(ListVmCheckpointsCmd cmd);
/**
* Delete a VM checkpoint (no-op for normal flow, kept for API parity)
*/
boolean deleteVmCheckpoint(DeleteVmCheckpointCmd cmd);
}

View File

@ -0,0 +1,65 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Answer;
public class CreateImageTransferAnswer extends Answer {
private String imageTransferId;
private String transferUrl;
private String phase;
public CreateImageTransferAnswer() {
}
public CreateImageTransferAnswer(CreateImageTransferCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
public CreateImageTransferAnswer(CreateImageTransferCommand cmd, boolean success, String details,
String imageTransferId, String transferUrl, String phase) {
super(cmd, success, details);
this.imageTransferId = imageTransferId;
this.transferUrl = transferUrl;
this.phase = phase;
}
public String getImageTransferId() {
return imageTransferId;
}
public void setImageTransferId(String imageTransferId) {
this.imageTransferId = imageTransferId;
}
public String getTransferUrl() {
return transferUrl;
}
public void setTransferUrl(String transferUrl) {
this.transferUrl = transferUrl;
}
public String getPhase() {
return phase;
}
public void setPhase(String phase) {
this.phase = phase;
}
}

View File

@ -0,0 +1,64 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
public class CreateImageTransferCommand extends Command {
private Long vmId;
private Long backupId;
private Long diskId;
private String deviceName;
private int nbdPort;
public CreateImageTransferCommand() {
}
public CreateImageTransferCommand(Long vmId, Long backupId, Long diskId, String deviceName, int nbdPort) {
this.vmId = vmId;
this.backupId = backupId;
this.diskId = diskId;
this.deviceName = deviceName;
this.nbdPort = nbdPort;
}
public Long getVmId() {
return vmId;
}
public Long getBackupId() {
return backupId;
}
public Long getDiskId() {
return diskId;
}
public String getDeviceName() {
return deviceName;
}
public int getNbdPort() {
return nbdPort;
}
@Override
public boolean executeInSequence() {
return true;
}
}

View File

@ -0,0 +1,57 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.Map;
import com.cloud.agent.api.Answer;
public class StartBackupAnswer extends Answer {
private Long checkpointCreateTime;
private Map<Long, String> deviceMappings; // volumeId -> device name (vda, vdb, etc.)
public StartBackupAnswer() {
}
public StartBackupAnswer(StartBackupCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
public StartBackupAnswer(StartBackupCommand cmd, boolean success, String details,
Long checkpointCreateTime, Map<Long, String> deviceMappings) {
super(cmd, success, details);
this.checkpointCreateTime = checkpointCreateTime;
this.deviceMappings = deviceMappings;
}
public Long getCheckpointCreateTime() {
return checkpointCreateTime;
}
public void setCheckpointCreateTime(Long checkpointCreateTime) {
this.checkpointCreateTime = checkpointCreateTime;
}
public Map<Long, String> getDeviceMappings() {
return deviceMappings;
}
public void setDeviceMappings(Map<Long, String> deviceMappings) {
this.deviceMappings = deviceMappings;
}
}

View File

@ -0,0 +1,77 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.Map;
import com.cloud.agent.api.Command;
public class StartBackupCommand extends Command {
private String vmName;
private Long vmId;
private String toCheckpointId;
private String fromCheckpointId;
private int nbdPort;
private Map<Long, String> diskVolumePaths; // volumeId -> path mapping
public StartBackupCommand() {
}
public StartBackupCommand(String vmName, Long vmId, String toCheckpointId, String fromCheckpointId,
int nbdPort, Map<Long, String> diskVolumePaths) {
this.vmName = vmName;
this.vmId = vmId;
this.toCheckpointId = toCheckpointId;
this.fromCheckpointId = fromCheckpointId;
this.nbdPort = nbdPort;
this.diskVolumePaths = diskVolumePaths;
}
public String getVmName() {
return vmName;
}
public Long getVmId() {
return vmId;
}
public String getToCheckpointId() {
return toCheckpointId;
}
public String getFromCheckpointId() {
return fromCheckpointId;
}
public int getNbdPort() {
return nbdPort;
}
public Map<Long, String> getDiskVolumePaths() {
return diskVolumePaths;
}
public boolean isIncremental() {
return fromCheckpointId != null && !fromCheckpointId.isEmpty();
}
@Override
public boolean executeInSequence() {
return true;
}
}

View File

@ -0,0 +1,30 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Answer;
public class StopBackupAnswer extends Answer {
public StopBackupAnswer() {
}
public StopBackupAnswer(StopBackupCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
}

View File

@ -0,0 +1,52 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.api.Command;
public class StopBackupCommand extends Command {
private String vmName;
private Long vmId;
private Long backupId;
public StopBackupCommand() {
}
public StopBackupCommand(String vmName, Long vmId, Long backupId) {
this.vmName = vmName;
this.vmId = vmId;
this.backupId = backupId;
}
public String getVmName() {
return vmName;
}
public Long getVmId() {
return vmId;
}
public Long getBackupId() {
return backupId;
}
@Override
public boolean executeInSequence() {
return true;
}
}

View File

@ -202,6 +202,12 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
@Column(name = "backup_volumes", length = 65535)
protected String backupVolumes;
@Column(name = "active_checkpoint_id")
protected String activeCheckpointId;
@Column(name = "active_checkpoint_create_time")
protected Long activeCheckpointCreateTime;
public VMInstanceVO(long id, long serviceOfferingId, String name, String instanceName, Type type, Long vmTemplateId, HypervisorType hypervisorType, long guestOSId,
long domainId, long accountId, long userId, boolean haEnabled) {
this.id = id;
@ -628,4 +634,20 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
public void setBackupVolumes(String backupVolumes) {
this.backupVolumes = backupVolumes;
}
public String getActiveCheckpointId() {
return activeCheckpointId;
}
public void setActiveCheckpointId(String activeCheckpointId) {
this.activeCheckpointId = activeCheckpointId;
}
public Long getActiveCheckpointCreateTime() {
return activeCheckpointCreateTime;
}
public void setActiveCheckpointCreateTime(Long activeCheckpointCreateTime) {
this.activeCheckpointCreateTime = activeCheckpointCreateTime;
}
}

View File

@ -103,6 +103,21 @@ public class BackupVO implements Backup {
@Column(name = "backup_schedule_id")
private Long backupScheduleId;
@Column(name = "from_checkpoint_id")
private String fromCheckpointId;
@Column(name = "to_checkpoint_id")
private String toCheckpointId;
@Column(name = "checkpoint_create_time")
private Long checkpointCreateTime;
@Column(name = "host_id")
private Long hostId;
@Column(name = "nbd_port")
private Integer nbdPort;
@Transient
Map<String, String> details;
@ -288,4 +303,49 @@ public class BackupVO implements Backup {
public void setBackupScheduleId(Long backupScheduleId) {
this.backupScheduleId = backupScheduleId;
}
@Override
public String getFromCheckpointId() {
return fromCheckpointId;
}
public void setFromCheckpointId(String fromCheckpointId) {
this.fromCheckpointId = fromCheckpointId;
}
@Override
public String getToCheckpointId() {
return toCheckpointId;
}
public void setToCheckpointId(String toCheckpointId) {
this.toCheckpointId = toCheckpointId;
}
@Override
public Long getCheckpointCreateTime() {
return checkpointCreateTime;
}
public void setCheckpointCreateTime(Long checkpointCreateTime) {
this.checkpointCreateTime = checkpointCreateTime;
}
@Override
public Long getHostId() {
return hostId;
}
public void setHostId(Long hostId) {
this.hostId = hostId;
}
@Override
public Integer getNbdPort() {
return nbdPort;
}
public void setNbdPort(Integer nbdPort) {
this.nbdPort = nbdPort;
}
}

View File

@ -0,0 +1,243 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.Date;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name = "image_transfer")
public class ImageTransferVO implements ImageTransfer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "backup_id")
private long backupId;
@Column(name = "vm_id")
private long vmId;
@Column(name = "disk_id")
private long diskId;
@Column(name = "device_name")
private String deviceName;
@Column(name = "host_id")
private long hostId;
@Column(name = "nbd_port")
private int nbdPort;
@Column(name = "transfer_url")
private String transferUrl;
@Enumerated(value = EnumType.STRING)
@Column(name = "phase")
private Phase phase;
@Enumerated(value = EnumType.STRING)
@Column(name = "direction")
private Direction direction;
@Column(name = "signed_ticket_id")
private String signedTicketId;
@Column(name = "account_id")
Long accountId;
@Column(name = "domain_id")
Long domainId;
@Column(name = "created")
@Temporal(value = TemporalType.TIMESTAMP)
private Date created;
@Column(name = "updated")
@Temporal(value = TemporalType.TIMESTAMP)
private Date updated;
@Column(name = "removed")
@Temporal(value = TemporalType.TIMESTAMP)
private Date removed;
public ImageTransferVO() {
this.uuid = UUID.randomUUID().toString();
}
public ImageTransferVO(long backupId, long vmId, long diskId, String deviceName, long hostId, int nbdPort, Phase phase, Direction direction, Long accountId, Long domainId) {
this();
this.backupId = backupId;
this.vmId = vmId;
this.diskId = diskId;
this.deviceName = deviceName;
this.hostId = hostId;
this.nbdPort = nbdPort;
this.phase = phase;
this.direction = direction;
this.accountId = accountId;
this.domainId = domainId;
this.created = new Date();
}
@Override
public long getId() {
return id;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public long getBackupId() {
return backupId;
}
public void setBackupId(long backupId) {
this.backupId = backupId;
}
@Override
public long getVmId() {
return vmId;
}
public void setVmId(long vmId) {
this.vmId = vmId;
}
@Override
public long getDiskId() {
return diskId;
}
public void setDiskId(long diskId) {
this.diskId = diskId;
}
@Override
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
@Override
public long getHostId() {
return hostId;
}
public void setHostId(long hostId) {
this.hostId = hostId;
}
@Override
public int getNbdPort() {
return nbdPort;
}
public void setNbdPort(int nbdPort) {
this.nbdPort = nbdPort;
}
@Override
public String getTransferUrl() {
return transferUrl;
}
public void setTransferUrl(String transferUrl) {
this.transferUrl = transferUrl;
}
@Override
public Phase getPhase() {
return phase;
}
public void setPhase(Phase phase) {
this.phase = phase;
this.updated = new Date();
}
@Override
public Direction getDirection() {
return direction;
}
public void setDirection(Direction direction) {
this.direction = direction;
}
@Override
public String getSignedTicketId() {
return signedTicketId;
}
public void setSignedTicketId(String signedTicketId) {
this.signedTicketId = signedTicketId;
}
@Override
public Class<?> getEntityType() {
return ImageTransfer.class;
}
@Override
public String getName() {
return null;
}
@Override
public long getDomainId() {
return domainId;
}
@Override
public long getAccountId() {
return accountId;
}
public Date getCreated() {
return created;
}
public Date getUpdated() {
return updated;
}
}

View File

@ -0,0 +1,30 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup.dao;
import java.util.List;
import org.apache.cloudstack.backup.ImageTransferVO;
import com.cloud.utils.db.GenericDao;
public interface ImageTransferDao extends GenericDao<ImageTransferVO, Long> {
List<ImageTransferVO> listByBackupId(Long backupId);
List<ImageTransferVO> listByVmId(Long vmId);
ImageTransferVO findByUuid(String uuid);
}

View File

@ -0,0 +1,76 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup.dao;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.cloudstack.backup.ImageTransferVO;
import org.springframework.stereotype.Component;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@Component
public class ImageTransferDaoImpl extends GenericDaoBase<ImageTransferVO, Long> implements ImageTransferDao {
private SearchBuilder<ImageTransferVO> backupIdSearch;
private SearchBuilder<ImageTransferVO> vmIdSearch;
private SearchBuilder<ImageTransferVO> uuidSearch;
public ImageTransferDaoImpl() {
}
@PostConstruct
protected void init() {
backupIdSearch = createSearchBuilder();
backupIdSearch.and("backupId", backupIdSearch.entity().getBackupId(), SearchCriteria.Op.EQ);
backupIdSearch.done();
vmIdSearch = createSearchBuilder();
vmIdSearch.and("vmId", vmIdSearch.entity().getVmId(), SearchCriteria.Op.EQ);
vmIdSearch.done();
uuidSearch = createSearchBuilder();
uuidSearch.and("uuid", uuidSearch.entity().getUuid(), SearchCriteria.Op.EQ);
uuidSearch.done();
}
@Override
public List<ImageTransferVO> listByBackupId(Long backupId) {
SearchCriteria<ImageTransferVO> sc = backupIdSearch.create();
sc.setParameters("backupId", backupId);
return listBy(sc);
}
@Override
public List<ImageTransferVO> listByVmId(Long vmId) {
SearchCriteria<ImageTransferVO> sc = vmIdSearch.create();
sc.setParameters("vmId", vmId);
return listBy(sc);
}
@Override
public ImageTransferVO findByUuid(String uuid) {
SearchCriteria<ImageTransferVO> sc = uuidSearch.create();
sc.setParameters("uuid", uuid);
return findOneBy(sc);
}
}

View File

@ -273,6 +273,7 @@
<bean id="backupDaoImpl" class="org.apache.cloudstack.backup.dao.BackupDaoImpl" />
<bean id="backupDetailsDaoImpl" class="org.apache.cloudstack.backup.dao.BackupDetailsDaoImpl" />
<bean id="backupRepositoryDaoImpl" class="org.apache.cloudstack.backup.dao.BackupRepositoryDaoImpl" />
<bean id="imageTransferDaoImpl" class="org.apache.cloudstack.backup.dao.ImageTransferDaoImpl" />
<bean id="directDownloadCertificateDaoImpl" class="org.apache.cloudstack.direct.download.DirectDownloadCertificateDaoImpl" />
<bean id="directDownloadCertificateHostMapDaoImpl" class="org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMapDaoImpl" />
<bean id="routerHealthCheckResultsDaoImpl" class="com.cloud.network.dao.RouterHealthCheckResultDaoImpl" />

View File

@ -117,3 +117,43 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tin
--- Disable/enable NICs
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''Indicates whether the NIC is enabled or not'' ');
-- Add checkpoint tracking fields to backups table for incremental backup support
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'from_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "Previous active checkpoint id for incremental backups"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'to_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "New checkpoint id created for this backup session"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'checkpoint_create_time', 'BIGINT DEFAULT NULL COMMENT "Checkpoint creation timestamp from libvirt"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'host_id', 'BIGINT UNSIGNED DEFAULT NULL COMMENT "Host where backup is running"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'nbd_port', 'INT DEFAULT NULL COMMENT "NBD server port for backup"');
-- Add checkpoint tracking fields to vm_instance table for domain recreation
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_instance', 'active_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "Active checkpoint id tracked for incremental backups"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_instance', 'active_checkpoint_create_time', 'BIGINT DEFAULT NULL COMMENT "Active checkpoint creation time"');
-- Create image_transfer table for per-disk image transfers
CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`(
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
`uuid` varchar(40) NOT NULL COMMENT 'uuid',
`account_id` bigint unsigned NOT NULL COMMENT 'Account ID',
`domain_id` bigint unsigned NOT NULL COMMENT 'Domain ID',
`backup_id` bigint unsigned NOT NULL COMMENT 'Backup ID',
`vm_id` bigint unsigned NOT NULL COMMENT 'VM ID',
`disk_id` bigint unsigned NOT NULL COMMENT 'Disk/Volume ID',
`device_name` varchar(10) NOT NULL COMMENT 'Device name (vda, vdb, etc)',
`host_id` bigint unsigned NOT NULL COMMENT 'Host ID',
`nbd_port` int NOT NULL COMMENT 'NBD port',
`transfer_url` varchar(255) COMMENT 'ImageIO transfer URL',
`phase` varchar(20) NOT NULL COMMENT 'Transfer phase: initializing, transferring, finished, failed',
`direction` varchar(20) NOT NULL COMMENT 'Direction: upload, download',
`signed_ticket_id` varchar(255) COMMENT 'Signed ticket ID from ImageIO',
`created` datetime NOT NULL COMMENT 'date created',
`updated` datetime COMMENT 'date updated if not null',
`removed` datetime COMMENT 'date removed if not null',
PRIMARY KEY (`id`),
UNIQUE KEY `uuid` (`uuid`),
CONSTRAINT `fk_image_transfer__backup_id` FOREIGN KEY (`backup_id`) REFERENCES `backups`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_image_transfer__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_image_transfer__disk_id` FOREIGN KEY (`disk_id`) REFERENCES `volumes`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_image_transfer__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE,
INDEX `i_image_transfer__backup_id`(`backup_id`),
INDEX `i_image_transfer__vm_id`(`vm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

@ -0,0 +1,58 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import org.apache.cloudstack.backup.CreateImageTransferAnswer;
import org.apache.cloudstack.backup.CreateImageTransferCommand;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
@ResourceWrapper(handles = CreateImageTransferCommand.class)
public class LibvirtCreateImageTransferCommandWrapper extends CommandWrapper<CreateImageTransferCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
@Override
public Answer execute(CreateImageTransferCommand cmd, LibvirtComputingResource resource) {
String deviceName = cmd.getDeviceName();
int nbdPort = cmd.getNbdPort();
try {
// POC: ImageIO interaction is stubbed out
// In production, this would:
// 1. Register NBD endpoint nbd://127.0.0.1:{nbdPort}/{deviceName} with ImageIO
// 2. Create transfer object in ImageIO
// 3. Get signed ticket and transfer URL
// For POC, return stub data
String imageTransferId = "transfer-" + cmd.getDiskId();
String transferUrl = String.format("nbd://127.0.0.1:%d/%s", nbdPort, deviceName);
String phase = "initializing";
return new CreateImageTransferAnswer(cmd, true, "Image transfer created (stub)",
imageTransferId, transferUrl, phase);
} catch (Exception e) {
return new CreateImageTransferAnswer(cmd, false, "Error creating image transfer: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,159 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.io.File;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Map;
import org.apache.cloudstack.backup.StartBackupAnswer;
import org.apache.cloudstack.backup.StartBackupCommand;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainInfo;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = StartBackupCommand.class)
public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackupCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
@Override
public Answer execute(StartBackupCommand cmd, LibvirtComputingResource resource) {
String vmName = cmd.getVmName();
String toCheckpointId = cmd.getToCheckpointId();
String fromCheckpointId = cmd.getFromCheckpointId();
int nbdPort = cmd.getNbdPort();
try {
Connect conn = LibvirtConnection.getConnection();
Domain dm = conn.domainLookupByName(vmName);
if (dm == null) {
return new StartBackupAnswer(cmd, false, "Domain not found: " + vmName);
}
DomainInfo info = dm.getInfo();
if (info.state != DomainInfo.DomainState.VIR_DOMAIN_RUNNING) {
return new StartBackupAnswer(cmd, false, "VM is not running");
}
// Create backup XML
String backupXml = createBackupXml(cmd, fromCheckpointId, nbdPort);
String checkpointXml = createCheckpointXml(toCheckpointId);
// Write XMLs to temp files
File backupXmlFile = File.createTempFile("backup-", ".xml");
File checkpointXmlFile = File.createTempFile("checkpoint-", ".xml");
try (FileWriter writer = new FileWriter(backupXmlFile)) {
writer.write(backupXml);
}
try (FileWriter writer = new FileWriter(checkpointXmlFile)) {
writer.write(checkpointXml);
}
// Execute virsh backup-begin
String backupCmd = String.format("virsh backup-begin %s %s --checkpointxml %s",
vmName, backupXmlFile.getAbsolutePath(), checkpointXmlFile.getAbsolutePath());
Script script = new Script("/bin/bash");
script.add("-c");
script.add(backupCmd);
String result = script.execute();
backupXmlFile.delete();
checkpointXmlFile.delete();
if (result != null) {
return new StartBackupAnswer(cmd, false, "Backup begin failed: " + result);
}
// Get checkpoint creation time - using current time for POC
long checkpointCreateTime = System.currentTimeMillis();
// Build device mappings from domblklist
Map<Long, String> deviceMappings = getDeviceMappings(vmName, cmd.getDiskVolumePaths(), resource);
return new StartBackupAnswer(cmd, true, "Backup started successfully",
checkpointCreateTime, deviceMappings);
} catch (Exception e) {
return new StartBackupAnswer(cmd, false, "Error starting backup: " + e.getMessage());
}
}
private String createBackupXml(StartBackupCommand cmd, String fromCheckpointId, int nbdPort) {
StringBuilder xml = new StringBuilder();
xml.append("<domainbackup mode=\"pull\">\n");
if (fromCheckpointId != null && !fromCheckpointId.isEmpty()) {
xml.append(" <incremental>").append(fromCheckpointId).append("</incremental>\n");
}
xml.append(" <server transport=\"tcp\" name=\"127.0.0.1\" port=\"").append(nbdPort).append("\"/>\n");
xml.append(" <disks>\n");
// Add disk entries - simplified for POC
Map<Long, String> diskPaths = cmd.getDiskVolumePaths();
int diskIndex = 0;
for (Map.Entry<Long, String> entry : diskPaths.entrySet()) {
String deviceName = "vd" + (char)('a' + diskIndex);
String scratchFile = "/var/tmp/scratch-" + entry.getKey() + ".qcow2";
xml.append(" <disk name=\"").append(deviceName).append("\" type=\"file\" exportname=\"")
.append(deviceName).append("\">\n");
xml.append(" <scratch file=\"").append(scratchFile).append("\"/>\n");
xml.append(" </disk>\n");
diskIndex++;
}
xml.append(" </disks>\n");
xml.append("</domainbackup>");
return xml.toString();
}
private String createCheckpointXml(String checkpointId) {
return "<domaincheckpoint>\n" +
" <name>" + checkpointId + "</name>\n" +
"</domaincheckpoint>";
}
private Map<Long, String> getDeviceMappings(String vmName, Map<Long, String> diskPaths,
LibvirtComputingResource resource) {
Map<Long, String> mappings = new HashMap<>();
// Simplified for POC - map volumeIds to device names in order
int diskIndex = 0;
for (Long volumeId : diskPaths.keySet()) {
String deviceName = "vd" + (char)('a' + diskIndex);
mappings.put(volumeId, deviceName);
diskIndex++;
}
return mappings;
}
}

View File

@ -0,0 +1,69 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package com.cloud.hypervisor.kvm.resource.wrapper;
import org.apache.cloudstack.backup.StopBackupAnswer;
import org.apache.cloudstack.backup.StopBackupCommand;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.libvirt.Connect;
import org.libvirt.Domain;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = StopBackupCommand.class)
public class LibvirtStopBackupCommandWrapper extends CommandWrapper<StopBackupCommand, Answer, LibvirtComputingResource> {
protected Logger logger = LogManager.getLogger(getClass());
@Override
public Answer execute(StopBackupCommand cmd, LibvirtComputingResource resource) {
String vmName = cmd.getVmName();
try {
Connect conn = LibvirtConnection.getConnection();
Domain dm = conn.domainLookupByName(vmName);
if (dm == null) {
return new StopBackupAnswer(cmd, false, "Domain not found: " + vmName);
}
// Execute virsh domjobabort
String abortCmd = String.format("virsh domjobabort %s", vmName);
Script script = new Script("/bin/bash");
script.add("-c");
script.add(abortCmd);
String result = script.execute();
if (result != null && !result.isEmpty()) {
// Job abort may fail if no job is running, which is acceptable
logger.debug("domjobabort result: " + result);
}
return new StopBackupAnswer(cmd, true, "Backup stopped successfully");
} catch (Exception e) {
return new StopBackupAnswer(cmd, false, "Error stopping backup: " + e.getMessage());
}
}
}

View File

@ -2430,6 +2430,13 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
response.setVmDetails(vmDetails);
}
if (backup.getFromCheckpointId() != null) {
response.setFromCheckpointId(backup.getFromCheckpointId());
}
if (backup.getToCheckpointId() != null) {
response.setToCheckpointId(backup.getToCheckpointId());
}
response.setObjectName("backup");
return response;
}

View File

@ -0,0 +1,456 @@
//Licensed to the Apache Software Foundation (ASF) under one
//or more contributor license agreements. See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership. The ASF licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//the License. You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied. See the License for the
//specific language governing permissions and limitations
//under the License.
package org.apache.cloudstack.backup;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.cloudstack.api.command.admin.backup.CreateImageTransferCmd;
import org.apache.cloudstack.api.command.admin.backup.DeleteVmCheckpointCmd;
import org.apache.cloudstack.api.command.admin.backup.FinalizeBackupCmd;
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.commons.collections.CollectionUtils;
import org.joda.time.DateTime;
import org.springframework.stereotype.Component;
import com.cloud.agent.AgentManager;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.storage.Volume;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.dao.VMInstanceDao;
@Component
public class IncrementalBackupServiceImpl extends ManagerBase implements IncrementalBackupService {
@Inject
private VMInstanceDao vmInstanceDao;
@Inject
private BackupDao backupDao;
@Inject
private ImageTransferDao imageTransferDao;
@Inject
private VolumeDao volumeDao;
@Inject
private AgentManager agentManager;
@Inject
private BackupOfferingDao backupOfferingDao;
private static final int NBD_PORT_RANGE_START = 10809;
private static final int NBD_PORT_RANGE_END = 10909;
private boolean isDummyOffering(Long backupOfferingId) {
if (backupOfferingId == null) {
throw new CloudRuntimeException("VM not assigned a backup offering");
}
BackupOfferingVO offering = backupOfferingDao.findById(backupOfferingId);
if (offering == null) {
throw new CloudRuntimeException("Backup offering not found: " + backupOfferingId);
}
if ("dummy".equalsIgnoreCase(offering.getName())) {
return true;
}
return false;
}
@Override
public BackupResponse startBackup(StartBackupCmd cmd) {
Long vmId = cmd.getVmId();
// Get VM
VMInstanceVO vm = vmInstanceDao.findById(vmId);
if (vm == null) {
throw new CloudRuntimeException("VM not found: " + vmId);
}
if (vm.getState() != State.Running) {
throw new CloudRuntimeException("VM must be running to start backup");
}
// Check if backup already in progress
Backup existingBackup = backupDao.findByVmId(vmId);
if (existingBackup != null && existingBackup.getStatus() == Backup.Status.BackingUp) {
throw new CloudRuntimeException("Backup already in progress for VM: " + vmId);
}
boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId());
// Create backup record
BackupVO backup = new BackupVO();
backup.setVmId(vmId);
backup.setName(vmId + "-" + DateTime.now());
backup.setAccountId(vm.getAccountId());
backup.setDomainId(vm.getDomainId());
// todo: set to Increment if it is incremental backup
backup.setType("FULL");
backup.setZoneId(vm.getDataCenterId());
backup.setStatus(Backup.Status.BackingUp);
backup.setBackupOfferingId(vm.getBackupOfferingId());
backup.setDate(new Date());
// Generate checkpoint IDs
String toCheckpointId = "ckp-" + UUID.randomUUID().toString().substring(0, 8);
String fromCheckpointId = vm.getActiveCheckpointId(); // null for first full backup
backup.setToCheckpointId(toCheckpointId);
backup.setFromCheckpointId(fromCheckpointId);
// Allocate NBD port
int nbdPort = allocateNbdPort();
backup.setNbdPort(nbdPort);
backup.setHostId(vm.getHostId());
// Persist backup record
backup = backupDao.persist(backup);
// Get disk volume paths
List<? extends Volume> volumes = volumeDao.findByInstance(vmId);
Map<Long, String> diskVolumePaths = new HashMap<>();
for (Volume vol : volumes) {
diskVolumePaths.put(vol.getId(), vol.getPath());
}
// Send StartBackupCommand to agent
StartBackupCommand startCmd = new StartBackupCommand(
vm.getInstanceName(),
vmId,
toCheckpointId,
fromCheckpointId,
nbdPort,
diskVolumePaths
);
try {
StartBackupAnswer answer;
if (dummyOffering) {
answer = new StartBackupAnswer(startCmd, true, "Dummy answer", System.currentTimeMillis(), diskVolumePaths);
} else {
answer = (StartBackupAnswer) agentManager.send(vm.getHostId(), startCmd);
}
if (!answer.getResult()) {
backupDao.remove(backup.getId());
throw new CloudRuntimeException("Failed to start backup: " + answer.getDetails());
}
// Update backup with checkpoint creation time
backup.setCheckpointCreateTime(answer.getCheckpointCreateTime());
backupDao.update(backup.getId(), backup);
// Return response
BackupResponse response = new BackupResponse();
response.setId(backup.getUuid());
response.setVmId(vm.getUuid());
response.setStatus(backup.getStatus());
return response;
} catch (AgentUnavailableException | OperationTimedoutException e) {
backupDao.remove(backup.getId());
throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e);
}
}
@Override
public boolean finalizeBackup(FinalizeBackupCmd cmd) {
Long vmId = cmd.getVmId();
Long backupId = cmd.getBackupId();
// Get backup
BackupVO backup = backupDao.findById(backupId);
if (backup == null) {
throw new CloudRuntimeException("Backup not found: " + backupId);
}
if (!backup.getVmId().equals(vmId)) {
throw new CloudRuntimeException("Backup does not belong to VM: " + vmId);
}
// Get VM
VMInstanceVO vm = vmInstanceDao.findById(vmId);
if (vm == null) {
throw new CloudRuntimeException("VM not found: " + vmId);
}
boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId());
List<ImageTransferVO> transfers = imageTransferDao.listByBackupId(backupId);
if (CollectionUtils.isNotEmpty(transfers)) {
throw new CloudRuntimeException("Image transfers not finalized for backup: " + backupId);
}
// Send StopBackupCommand to agent
StopBackupCommand stopCmd = new StopBackupCommand(vm.getInstanceName(), vmId, backupId);
try {
StopBackupAnswer answer;
if (dummyOffering) {
answer = new StopBackupAnswer(stopCmd, true, "Dummy answer");
} else {
answer = (StopBackupAnswer) agentManager.send(vm.getHostId(), stopCmd);
}
if (!answer.getResult()) {
throw new CloudRuntimeException("Failed to stop backup: " + answer.getDetails());
}
// Update VM checkpoint tracking
String oldCheckpointId = vm.getActiveCheckpointId();
vm.setActiveCheckpointId(backup.getToCheckpointId());
vm.setActiveCheckpointCreateTime(backup.getCheckpointCreateTime());
vmInstanceDao.update(vmId, vm);
// Delete old checkpoint if exists (POC: skip actual libvirt call)
if (oldCheckpointId != null) {
// In production: send command to delete oldCheckpointId via virsh checkpoint-delete
logger.debug("Would delete old checkpoint: " + oldCheckpointId);
}
// Delete backup session record
backupDao.remove(backup.getId());
return true;
} catch (AgentUnavailableException | OperationTimedoutException e) {
throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e);
}
}
@Override
public ImageTransferResponse createImageTransfer(CreateImageTransferCmd cmd) {
Long backupId = cmd.getBackupId();
Long volumeId = cmd.getVolumeId();
BackupVO backup = backupDao.findById(backupId);
if (backup == null) {
throw new CloudRuntimeException("Backup not found: " + backupId);
}
Volume volume = volumeDao.findById(volumeId);
if (volume == null) {
throw new CloudRuntimeException("Volume not found: " + volumeId);
}
VMInstanceVO vm = vmInstanceDao.findById(backup.getVmId());
if (vm == null) {
throw new CloudRuntimeException("VM not found: " + backup.getVmId());
}
boolean dummyOffering = isDummyOffering(vm.getBackupOfferingId());
// Resolve device name (simplified for POC)
List<? extends Volume> volumes = volumeDao.findByInstance(backup.getVmId());
String deviceName = resolveDeviceName(volumes, volumeId);
// Create CreateImageTransferCommand
CreateImageTransferCommand transferCmd = new CreateImageTransferCommand(
backup.getVmId(),
backupId,
volumeId,
deviceName,
backup.getNbdPort()
);
try {
CreateImageTransferAnswer answer;
if (dummyOffering) {
answer = new CreateImageTransferAnswer(transferCmd, true, "Dummy answer", "image-transfer-id", "nbd://127.0.0.1:10809/vda", "initializing");
} else {
answer = (CreateImageTransferAnswer) agentManager.send(backup.getHostId(), transferCmd);
}
if (!answer.getResult()) {
throw new CloudRuntimeException("Failed to create image transfer: " + answer.getDetails());
}
// Create ImageTransfer record
ImageTransferVO imageTransfer = new ImageTransferVO(
backupId,
backup.getVmId(),
volumeId,
deviceName,
backup.getHostId(),
backup.getNbdPort(),
ImageTransferVO.Phase.initializing,
ImageTransfer.Direction.valueOf(cmd.getDirection()),
backup.getAccountId(),
backup.getDomainId()
);
imageTransfer.setTransferUrl(answer.getTransferUrl());
imageTransfer.setSignedTicketId(answer.getImageTransferId());
imageTransfer = imageTransferDao.persist(imageTransfer);
// Return response
ImageTransferResponse response = new ImageTransferResponse();
response.setId(imageTransfer.getUuid());
response.setBackupId(backup.getUuid());
response.setVmId(vm.getUuid());
response.setDiskId(volume.getUuid());
response.setDeviceName(deviceName);
response.setTransferUrl(answer.getTransferUrl());
response.setPhase(ImageTransferVO.Phase.initializing.toString());
response.setDirection(imageTransfer.getDirection().toString());
response.setCreated(imageTransfer.getCreated());
return response;
} catch (AgentUnavailableException | OperationTimedoutException e) {
throw new CloudRuntimeException("Failed to communicate with agent: " + e.getMessage(), e);
}
}
@Override
public boolean finalizeImageTransfer(FinalizeImageTransferCmd cmd) {
Long imageTransferId = cmd.getImageTransferId();
ImageTransferVO imageTransfer = imageTransferDao.findById(imageTransferId);
if (imageTransfer == null) {
throw new CloudRuntimeException("Image transfer not found: " + imageTransferId);
}
// Mark as finished (NBD is closed in backup finalize, not here)
imageTransfer.setPhase(ImageTransferVO.Phase.finished);
imageTransferDao.update(imageTransferId, imageTransfer);
imageTransferDao.remove(imageTransferId);
return true;
}
@Override
public List<ImageTransferResponse> listImageTransfers(ListImageTransfersCmd cmd) {
Long id = cmd.getId();
Long backupId = cmd.getBackupId();
List<ImageTransferVO> transfers;
if (id != null) {
transfers = List.of(imageTransferDao.findById(id));
} else if (backupId != null) {
transfers = imageTransferDao.listByBackupId(backupId);
} else {
transfers = imageTransferDao.listAll();
}
return transfers.stream().map(this::toImageTransferResponse).collect(Collectors.toList());
}
@Override
public List<CheckpointResponse> listVmCheckpoints(ListVmCheckpointsCmd cmd) {
Long vmId = cmd.getVmId();
VMInstanceVO vm = vmInstanceDao.findById(vmId);
if (vm == null) {
throw new CloudRuntimeException("VM not found: " + vmId);
}
// Return active checkpoint (POC: simplified, no libvirt query)
List<CheckpointResponse> responses = new ArrayList<>();
if (vm.getActiveCheckpointId() != null) {
CheckpointResponse response = new CheckpointResponse();
response.setCheckpointId(vm.getActiveCheckpointId());
response.setCreateTime(vm.getActiveCheckpointCreateTime());
response.setIsActive(true);
responses.add(response);
}
return responses;
}
@Override
public boolean deleteVmCheckpoint(DeleteVmCheckpointCmd cmd) {
// No-op for normal flow as per spec
// Kept for API parity with oVirt
return true;
}
@Override
public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<>();
cmdList.add(StartBackupCmd.class);
cmdList.add(FinalizeBackupCmd.class);
cmdList.add(CreateImageTransferCmd.class);
cmdList.add(FinalizeImageTransferCmd.class);
cmdList.add(ListImageTransfersCmd.class);
cmdList.add(ListVmCheckpointsCmd.class);
cmdList.add(DeleteVmCheckpointCmd.class);
return cmdList;
}
// Helper methods
private int allocateNbdPort() {
// Simplified port allocation for POC
Random random = new Random();
return NBD_PORT_RANGE_START + random.nextInt(NBD_PORT_RANGE_END - NBD_PORT_RANGE_START);
}
private String resolveDeviceName(List<? extends Volume> volumes, Long targetDiskId) {
// Simplified device name resolution for POC
int index = 0;
for (Volume vol : volumes) {
if (Long.valueOf(vol.getId()).equals(targetDiskId)) {
return "vd" + (char)('a' + index);
}
index++;
}
return "vda"; // fallback
}
private ImageTransferResponse toImageTransferResponse(ImageTransferVO imageTransfer) {
ImageTransferResponse response = new ImageTransferResponse();
response.setId(imageTransfer.getUuid());
BackupVO backup = backupDao.findById(imageTransfer.getBackupId());
VMInstanceVO vm = vmInstanceDao.findById(imageTransfer.getVmId());
Volume volume = volumeDao.findById(imageTransfer.getDiskId());
if (backup != null) response.setBackupId(backup.getUuid());
if (vm != null) response.setVmId(vm.getUuid());
if (volume != null) response.setDiskId(volume.getUuid());
response.setDeviceName(imageTransfer.getDeviceName());
response.setTransferUrl(imageTransfer.getTransferUrl());
response.setPhase(imageTransfer.getPhase().toString());
response.setCreated(imageTransfer.getCreated());
return response;
}
}

View File

@ -347,6 +347,8 @@
<bean id="backupRepositoryService" class="org.apache.cloudstack.backup.BackupRepositoryServiceImpl" />
<bean id="incrementalBackupService" class="org.apache.cloudstack.backup.IncrementalBackupServiceImpl" />
<bean id="storageLayer" class="com.cloud.storage.JavaStorageLayer" />
<bean id="nfsMountManager" class="org.apache.cloudstack.storage.NfsMountManagerImpl" >

View File

@ -223,6 +223,15 @@ known_categories = {
'Management': 'Management',
'Backup' : 'Backup and Recovery',
'Restore' : 'Backup and Recovery',
'startBackup' : 'Backup and Recovery',
'finalizeBackup' : 'Backup and Recovery',
'createImageTransfer' : 'Backup and Recovery',
'finalizeImageTransfer' : 'Backup and Recovery',
'listImageTransfers' : 'Backup and Recovery',
'listVmCheckpoints' : 'Backup and Recovery',
'deleteVmCheckpoint' : 'Backup and Recovery',
'ImageTransfer' : 'Backup and Recovery',
'VmCheckpoint' : 'Backup and Recovery',
'UnmanagedInstance': 'Virtual Machine',
'KubernetesSupportedVersion': 'Kubernetes Service',
'KubernetesCluster': 'Kubernetes Service',