package org.ovirt.engine.core.bll.storage.disk.cinder; import java.util.Date; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javax.inject.Inject; import org.ovirt.engine.core.bll.InternalCommandAttribute; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.storage.disk.image.BaseImagesCommand; import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler; import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil; import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback; import org.ovirt.engine.core.common.action.CreateCinderSnapshotParameters; import org.ovirt.engine.core.common.action.RemoveCinderDiskVolumeParameters; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.businessentities.Snapshot; import org.ovirt.engine.core.common.businessentities.storage.CinderDisk; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.businessentities.storage.ImageStatus; import org.ovirt.engine.core.common.businessentities.storage.VolumeClassification; import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat; import org.ovirt.engine.core.common.businessentities.storage.VolumeType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.DiskImageDao; import org.ovirt.engine.core.dao.ImageDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.di.Injector; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @InternalCommandAttribute @NonTransactiveCommandAttribute public class CreateCinderSnapshotCommand<T extends CreateCinderSnapshotParameters> extends BaseImagesCommand<T> { @Inject private ImageDao imageDao; @Inject private DiskImageDao diskImageDao; @Inject private VmDao vmDao; private CinderDisk disk; public CreateCinderSnapshotCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } private CinderDisk cloneDisk() { boolean isStateless = isStatelessSnapshot(); CinderDisk cinderDependentVolume = getDisk(); initCinderDependentVolume(cinderDependentVolume); cinderDependentVolume.setActive(isStateless); String volumeId = isStateless ? getCinderBroker().cloneDisk(cinderDependentVolume) : getCinderBroker().createSnapshot(cinderDependentVolume, getParameters().getDescription()); cinderDependentVolume.setVolumeClassification(isStateless ? VolumeClassification.Volume : VolumeClassification.Snapshot); Guid destinationImageId = Guid.createGuidFromString(volumeId); getParameters().setDestinationImageId(destinationImageId); cinderDependentVolume.setImageId(destinationImageId); return cinderDependentVolume; } private void initCinderDependentVolume(CinderDisk newCinderVolume) { // override volume type and volume format to Unassigned and unassigned for Cinder. newCinderVolume.setVolumeType(VolumeType.Unassigned); newCinderVolume.setVolumeFormat(VolumeFormat.Unassigned); newCinderVolume.setImageStatus(ImageStatus.LOCKED); newCinderVolume.setCreationDate(new Date()); newCinderVolume.setLastModifiedDate(new Date()); newCinderVolume.setQuotaId(getParameters().getQuotaId()); newCinderVolume.setDiskProfileId(getParameters().getDiskProfileId()); newCinderVolume.setQuotaId(getParameters().getQuotaId()); // Get the last snapshot to be the parent of the new volume. DiskImage leaf = ImagesHandler.getSnapshotLeaf(getDiskImage().getId()); newCinderVolume.setParentId(leaf.getImageId()); } /** * By default old image must be replaced by new one */ protected void processOldImageFromDb() { getCompensationContext().snapshotEntity(getDiskImage().getImage()); getParameters().setOldLastModifiedValue(getDiskImage().getLastModified()); getDiskImage().setLastModified(new Date()); getDiskImage().setVmSnapshotId(getParameters().getVmSnapshotId()); if (isStatelessSnapshot()) { getDiskImage().setActive(Boolean.FALSE); } imageDao.update(getDiskImage().getImage()); getCompensationContext().stateChanged(); } @Override protected void executeCommand() { final CinderDisk newCinderVolume; newCinderVolume = cloneDisk(); if (!isStatelessSnapshot()) { getDiskImage().setVmSnapshotId(getParameters().getVmSnapshotId()); } TransactionSupport.executeInNewTransaction(() -> { processOldImageFromDb(); addDiskImageToDb(newCinderVolume, getCompensationContext(), isStatelessSnapshot()); setActionReturnValue(newCinderVolume); setSucceeded(true); return null; }); getReturnValue().setActionReturnValue(newCinderVolume.getImageId()); persistCommand(getParameters().getParentCommand(), true); setSucceeded(true); } private boolean isStatelessSnapshot() { return getParameters().getSnapshotType() == Snapshot.SnapshotType.STATELESS; } @Override protected void endSuccessfully() { if (!getParameters().isLeaveLocked()) { getDestinationDiskImage().setImageStatus(ImageStatus.OK); imageDao.update(getDestinationDiskImage().getImage()); } setSucceeded(true); } @Override protected void endWithFailure() { revertCinderVolume((CinderDisk) getDestinationDiskImage()); if (isDestinationImageExists(getDestinationDiskImage().getId()) && isImageSnapshot(getDestinationDiskImage())) { updateLastModifiedInParent(getDestinationDiskImage().getParentId()); } super.endWithFailure(); if (getParameters().getSnapshotType().equals(Snapshot.SnapshotType.STATELESS)) { updateOldImageAsActive(Snapshot.SnapshotType.ACTIVE, true); } } private void revertCinderVolume(CinderDisk diskVolumeVolume) { RemoveCinderDiskVolumeParameters removeDiskVolumeParam = new RemoveCinderDiskVolumeParameters(diskVolumeVolume); Future<VdcReturnValueBase> future = CommandCoordinatorUtil.executeAsyncCommand( VdcActionType.RemoveCinderDiskVolume, removeDiskVolumeParam, null); try { future.get(); } catch (InterruptedException | ExecutionException e) { log.error("Fail to revert snapshot id '{}' for disk id '{}'. Exception: {}", diskVolumeVolume.getImageId(), diskVolumeVolume.getId(), e); } } protected CinderDisk getDisk() { if (disk == null) { disk = (CinderDisk) diskImageDao.get(getImageId()); } return disk; } @Override public Guid getStorageDomainId() { return getParameters().getStorageDomainId(); } @Override protected void lockImage() { ImagesHandler.updateImageStatus(getParameters().getImageId(), ImageStatus.LOCKED); } private void updateLastModifiedInParent(Guid parentId) { DiskImage previousSnapshot = diskImageDao.getSnapshotById(parentId); // If the old description of the snapshot got overriden, we should restore the previous description if (getParameters().getOldLastModifiedValue() != null) { previousSnapshot.setLastModified(getParameters().getOldLastModifiedValue()); } imageDao.update(previousSnapshot.getImage()); } private boolean isImageSnapshot(DiskImage destinationImage) { // Empty Guid, means new disk rather than snapshot, so no need to add a map to the db for new disk. return !destinationImage.getParentId().equals(Guid.Empty) && !destinationImage.getParentId().equals(destinationImage.getImageTemplateId()); } private boolean isDestinationImageExists(Guid destinationId) { return getDestinationDiskImage() != null && !vmDao.getVmsListForDisk(destinationId, false).isEmpty(); } @Override public CommandCallback getCallback() { return Injector.injectMembers(new CreateCinderSnapshotCommandCallback()); } }