Skip to content

Commit

Permalink
linstor: Support VM-Instance Disk snapshots
Browse files Browse the repository at this point in the history
This adds VM-Instance disk snapshot support for
Linstor primary storage. Instance snapshots are stored on
the used Linstor storage pool backend and can be converted
into regular volume snapshots and also reverted.

Instance VM snapshots are not fully atomic but with the
create multi snapshot feature as good as it gets.
Snapshots are done over multiple volumes in the same devicemanager run.
  • Loading branch information
rp- committed Mar 20, 2024
1 parent 80922d4 commit 94932ad
Show file tree
Hide file tree
Showing 3 changed files with 384 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager;
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
import org.apache.cloudstack.storage.snapshot.SnapshotObject;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.volume.VolumeObject;
import org.apache.log4j.Logger;
Expand Down Expand Up @@ -970,25 +971,29 @@ private Answer copyTemplate(DataObject srcData, DataObject dstData) {
* @param api Linstor Developer api object
* @param pool StoragePool this resource resides on
* @param rscName rscName of the snapshotted resource
* @param snapshotInfo snapshot info of the snapshot
* @param snapshotName Name of the snapshot to copy from
* @param snapshotObject snapshot object of the origCmd, so the path can be modified
* @param origCmd original LinstorBackupSnapshotCommand that needs to have a patched path
* @return answer from agent operation
* @throws ApiException if any Linstor api operation fails
*/
private Answer copyFromTemporaryResource(
DevelopersApi api, StoragePoolVO pool, String rscName, SnapshotInfo snapshotInfo, CopyCommand origCmd)
DevelopersApi api,
StoragePoolVO pool,
String rscName,
String snapshotName,
SnapshotObject snapshotObject,
CopyCommand origCmd)
throws ApiException {
Answer answer;
String restoreName = rscName + "-rst";
String snapshotName = LinstorUtil.RSC_PREFIX + snapshotInfo.getUuid();
String devName = restoreResourceFromSnapshot(api, pool, rscName, snapshotName, restoreName);

Optional<RemoteHostEndPoint> optEPAny = getLinstorEP(api, restoreName);
if (optEPAny.isPresent()) {
// patch the src device path to the temporary linstor resource
SnapshotObjectTO soTO = (SnapshotObjectTO)snapshotInfo.getTO();
soTO.setPath(devName);
origCmd.setSrcTO(soTO);
snapshotObject.setPath(devName);
origCmd.setSrcTO(snapshotObject.getTO());
answer = optEPAny.get().sendMessage(origCmd);
} else{
answer = new Answer(origCmd, false, "Unable to get matching Linstor endpoint.");
Expand All @@ -1003,8 +1008,8 @@ protected Answer copySnapshot(DataObject srcData, DataObject destData) {
int _backupsnapshotwait = NumbersUtil.parseInt(
value, Integer.parseInt(Config.BackupSnapshotWait.getDefaultValue()));

SnapshotInfo snapshotInfo = (SnapshotInfo)srcData;
Boolean snapshotFullBackup = snapshotInfo.getFullBackup();
SnapshotObject snapshotObject = (SnapshotObject)srcData;
Boolean snapshotFullBackup = snapshotObject.getFullBackup();
final StoragePoolVO pool = _storagePoolDao.findById(srcData.getDataStore().getId());
final DevelopersApi api = LinstorUtil.getLinstorAPI(pool.getHostAddress());
boolean fullSnapshot = true;
Expand All @@ -1015,29 +1020,46 @@ protected Answer copySnapshot(DataObject srcData, DataObject destData) {
options.put("fullSnapshot", fullSnapshot + "");
options.put(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.key(),
String.valueOf(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value()));
options.put("volumeSize", snapshotInfo.getBaseVolume().getSize() + "");
options.put("volumeSize", snapshotObject.getBaseVolume().getSize() + "");

try {
final String rscName = LinstorUtil.RSC_PREFIX + snapshotObject.getBaseVolume().getUuid();
String snapshotName = LinstorUtil.RSC_PREFIX + snapshotObject.getUuid();

// vmsnapshots don't have our typical snapshot path set
// instead the path is the internal snapshot name e.g.: {vm}_VS_{datestr}
// we have to find out and modify the path here before
if (!(snapshotObject.getPath().startsWith("/dev/mapper/") ||
snapshotObject.getPath().startsWith("zfs://"))) {
snapshotName = snapshotObject.getPath();
com.linbit.linstor.api.model.StoragePool linStoragePool =
LinstorUtil.getDiskfulStoragePool(api, rscName);
if (linStoragePool == null) {
throw new CloudRuntimeException("Linstor: Unable to find storage pool for resource " + rscName);
}
final String path = LinstorUtil.getSnapshotPath(linStoragePool, rscName, snapshotObject.getPath());
snapshotObject.setPath(path);
}

CopyCommand cmd = new LinstorBackupSnapshotCommand(
srcData.getTO(),
snapshotObject.getTO(),
destData.getTO(),
_backupsnapshotwait,
VirtualMachineManager.ExecuteInSequence.value());
cmd.setOptions(options);

String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getUuid();
Optional<RemoteHostEndPoint> optEP = getDiskfullEP(api, rscName);
Answer answer;
if (optEP.isPresent()) {
answer = optEP.get().sendMessage(cmd);
} else {
s_logger.debug("No diskfull endpoint found to copy image, creating diskless endpoint");
answer = copyFromTemporaryResource(api, pool, rscName, snapshotInfo, cmd);
answer = copyFromTemporaryResource(api, pool, rscName, snapshotName, snapshotObject, cmd);
}
return answer;
} catch (Exception e) {
s_logger.debug("copy snapshot failed: ", e);
throw new CloudRuntimeException(e.toString());
throw new CloudRuntimeException("Copy snapshot failed, please cleanup snapshot manually: " + e);
}

}
Expand Down
Loading

0 comments on commit 94932ad

Please sign in to comment.