Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
import com.cloud.agent.api.RemoveBitmapCommand;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.Storage;
import com.cloud.storage.Volume;
import com.cloud.storage.snapshot.SnapshotManager;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
Expand Down Expand Up @@ -116,6 +119,8 @@ public class SnapshotServiceImpl implements SnapshotService {
ConfigurationDao _configDao;
@Inject
HostDao hostDao;
@Inject
private PrimaryDataStoreDao storagePoolDao;

@Inject
private HeuristicRuleHelper heuristicRuleHelper;
Expand Down Expand Up @@ -624,7 +629,10 @@ public boolean deleteSnapshot(SnapshotInfo snapInfo) {
if (kvmCheckpointPath != null) {
snapInfo.setCheckpointPath(kvmCheckpointPath);
snapInfo.setKvmIncrementalSnapshot(true);
deleteBitmap(snapInfo);
StoragePoolVO snapPool = storagePoolDao.findById(snapInfo.getBaseVolume().getPoolId());
if (snapPool == null || snapPool.getPoolType() != Storage.StoragePoolType.CLVM_NG) {
deleteBitmap(snapInfo);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5371,6 +5371,15 @@ public boolean recreateCheckpointsOnVm(List<VolumeObjectTO> volumes, String vmNa
if (CollectionUtils.isEmpty(volume.getCheckpointPaths())) {
continue;
}

if (volume.getDataStore() instanceof PrimaryDataStoreTO) {
StoragePoolType poolType = ((PrimaryDataStoreTO) volume.getDataStore()).getPoolType();
if (StoragePoolType.CLVM_NG == poolType || StoragePoolType.CLVM == poolType) {
logger.debug("Skipping checkpoint recreation for CLVM/CLVM_NG volume [{}]: " +
"these pool types use QCOW2 backing chains instead of libvirt checkpoints.", volume);
continue;
}
}
Set<KVMStoragePool> storagePoolSet = connectToAllVolumeSnapshotSecondaryStorages(volume);
recreateCheckpointsOfDisk(vmName, volume, mapDiskToDiskDef);
disconnectAllVolumeSnapshotSecondaryStorages(storagePoolSet);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public class LibvirtRevertSnapshotCommandWrapper extends CommandWrapper<RevertSn
private static final String RADOS_CONNECTION_TIMEOUT = "30";

protected Set<StoragePoolType> storagePoolTypesThatSupportRevertSnapshot = new HashSet<>(Arrays.asList(StoragePoolType.RBD, StoragePoolType.Filesystem,
StoragePoolType.NetworkFilesystem, StoragePoolType.SharedMountPoint));
StoragePoolType.NetworkFilesystem, StoragePoolType.SharedMountPoint, StoragePoolType.CLVM_NG));

@Override
public Answer execute(final RevertSnapshotCommand command, final LibvirtComputingResource libvirtComputingResource) {
Expand Down Expand Up @@ -117,16 +117,27 @@ public Answer execute(final RevertSnapshotCommand command, final LibvirtComputin
secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(snapshotImageStore.getUrl());
}

if (primaryPool.getType() == StoragePoolType.CLVM || primaryPool.getType() == StoragePoolType.CLVM_NG) {
if (primaryPool.getType() == StoragePoolType.CLVM ||
(primaryPool.getType() == StoragePoolType.CLVM_NG && !snapshot.isKvmIncrementalSnapshot())) {
Script cmd = new Script(libvirtComputingResource.manageSnapshotPath(), libvirtComputingResource.getCmdsTimeout(), logger);
cmd.add("-v", getFullPathAccordingToStorage(secondaryStoragePool, snapshotRelPath));
cmd.add("-n", snapshotDisk.getName());
cmd.add("-p", snapshotDisk.getPath());
String result = cmd.execute();
if (result != null) {
logger.debug("Failed to revert snaptshot: " + result);
logger.debug("Failed to revert snapshot: " + result);
return new Answer(command, false, result);
}
} else if (primaryPool.getType() == StoragePoolType.CLVM_NG) {
String nfsSnapshotPath = getFullPathAccordingToStorage(secondaryStoragePool, snapshotRelPath);
Set<KVMStoragePool> storagePoolSet = libvirtComputingResource.connectToAllVolumeSnapshotSecondaryStorages(volume);
try {
replaceVolumeWithSnapshot(snapshotDisk.getPath(), nfsSnapshotPath);
} catch (LibvirtException | QemuImgException ex) {
throw new CloudRuntimeException(String.format("Unable to revert volume [%s] to snapshot [%s] due to [%s].", volume, snapshot, ex.getMessage()), ex);
} finally {
libvirtComputingResource.disconnectAllVolumeSnapshotSecondaryStorages(storagePoolSet);
}
} else {
revertVolumeToSnapshot(secondaryStoragePool, snapshotOnPrimaryStorage, snapshot, primaryPool, libvirtComputingResource);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,111 @@ private KVMPhysicalDisk createClvmNgDiskWithBacking(String volumeUuid, int timeo
return disk;
}

public KVMPhysicalDisk createSnapshotStagingLv(String stagingName, String backingLvPath,
long virtualSize, KVMStoragePool pool, int timeout) {
String vgName = getVgName(pool.getLocalPath());
long lvSize = calculateClvmNgLvSize(virtualSize, vgName);
String stagingPath = "/dev/" + vgName + "/" + stagingName;

Script lvcreate = new Script("lvcreate", Duration.millis(timeout), logger);
lvcreate.add("-n", stagingName);
lvcreate.add("-L", lvSize + "B");
lvcreate.add("--yes");
lvcreate.add(vgName);
String result = lvcreate.execute();
if (result != null) {
throw new CloudRuntimeException("Failed to create staging LV for CLVM_NG snapshot: " + result);
}

Script qemuImg = new Script("qemu-img", Duration.millis(timeout), logger);
qemuImg.add("create");
qemuImg.add("-f", "qcow2");
qemuImg.add("-o", String.format("backing_file=%s,backing_fmt=qcow2,cluster_size=64k,extended_l2=on", backingLvPath));
qemuImg.add(stagingPath);
qemuImg.add(String.valueOf(virtualSize));
result = qemuImg.execute();
if (result != null) {
removeLvOnFailure(stagingPath, timeout);
throw new CloudRuntimeException("Failed to create QCOW2 overlay on CLVM_NG staging LV: " + result);
}

long actualSize = getClvmVolumeSize(stagingPath);
KVMPhysicalDisk disk = new KVMPhysicalDisk(stagingPath, stagingName, pool);
disk.setFormat(PhysicalDiskFormat.QCOW2);
disk.setSize(actualSize);
disk.setVirtualSize(virtualSize);
return disk;
}

/**
* Creates a transient LVM snapshot of {@code sourceLvPath} for use as an export source.
*
* <p>The snapshot is sized at 20&nbsp;% of the source LV's actual size (minimum 1&nbsp;GiB,
* rounded up to the VG physical-extent boundary). This CoW space accommodates writes by the
* running VM during the export window. The caller is responsible for deleting the snapshot
* via {@link #removeLvIfExists} or the pool's {@code deletePhysicalDisk} once the export
* finishes.</p>
*
* @param snapName name for the new snapshot LV
* @param sourceLvPath absolute path of the LV to snapshot (e.g. {@code /dev/vg/vol-uuid})
* @param timeout script execution timeout in milliseconds
*/
public void createLvmSnapshotForExport(String snapName, String sourceLvPath, int timeout) {
long sourceBytes = getClvmVolumeSize(sourceLvPath);
long snapBytes = Math.max(sourceBytes / 5, 1024L * 1024 * 1024); // 20 %, min 1 GiB
String vgName = sourceLvPath.split("/")[2]; // /dev/<vg>/<lv>
long peSize = getVgPhysicalExtentSize(vgName);
snapBytes = ((snapBytes + peSize - 1) / peSize) * peSize;

Script lvcreate = new Script("lvcreate", Duration.millis(timeout), logger);
lvcreate.add("--snapshot");
lvcreate.add("-n", snapName);
lvcreate.add("-L", snapBytes + "B");
lvcreate.add("--yes");
lvcreate.add(sourceLvPath);
String result = lvcreate.execute();
if (result != null) {
throw new CloudRuntimeException(String.format(
"Failed to create LVM snapshot [%s] of [%s]: %s", snapName, sourceLvPath, result));
}
logger.debug("Created LVM snapshot [{}] of [{}] with {} bytes CoW space.",
snapName, sourceLvPath, snapBytes);
}

/**
* Renames {@code oldLvName} to {@code newLvName} within {@code vgName}.
* Throws {@link CloudRuntimeException} on failure.
*/
public void renameLv(String vgName, String oldLvName, String newLvName, int timeout) {
Script lvrename = new Script("lvrename", Duration.millis(timeout), logger);
lvrename.add(vgName);
lvrename.add(oldLvName);
lvrename.add(newLvName);
String result = lvrename.execute();
if (result != null) {
throw new CloudRuntimeException(String.format(
"Failed to rename LV [%s] to [%s] in VG [%s]: %s", oldLvName, newLvName, vgName, result));
}
logger.debug("Renamed LV [{}] to [{}] in VG [{}].", oldLvName, newLvName, vgName);
}

/**
* Removes the LV at {@code lvPath} if it exists; no-op (and no exception) if it does not.
* Used for best-effort cleanup on failure paths.
*
* @param lvPath absolute path of the LV (e.g. {@code /dev/vg/snap-tmp-...})
* @param timeout script execution timeout in milliseconds
*/
public void removeLvIfExists(String lvPath, int timeout) {
if (lvExists(lvPath)) {
Script lvremove = new Script("lvremove", Duration.millis(timeout), logger);
lvremove.add("-f");
lvremove.add(lvPath);
lvremove.execute();
logger.debug("Removed LV [{}] (best-effort cleanup).", lvPath);
}
}

private boolean lvExists(String lvPath) {
Script checkLv = new Script("lvs", Duration.millis(10000), logger);
checkLv.add("--noheadings");
Expand Down
Loading