package org.ovirt.engine.core.bll.storage.disk.image;
import java.util.List;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.ConcurrentChildCommandsExecutionCallback;
import org.ovirt.engine.core.bll.InternalCommandAttribute;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.storage.domain.PostDeleteActionHandler;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.validator.storage.DiskValidator;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.CopyImageGroupWithDataCommandParameters;
import org.ovirt.engine.core.common.action.MoveOrCopyImageGroupParameters;
import org.ovirt.engine.core.common.action.RemoveImageParameters;
import org.ovirt.engine.core.common.action.VdcActionParametersBase;
import org.ovirt.engine.core.common.action.VdcActionParametersBase.EndProcedure;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskCreationInfo;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskType;
import org.ovirt.engine.core.common.asynctasks.EntityInfo;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatic;
import org.ovirt.engine.core.common.businessentities.storage.Disk;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.DiskStorageType;
import org.ovirt.engine.core.common.businessentities.storage.ImageOperation;
import org.ovirt.engine.core.common.businessentities.storage.ImageStorageDomainMap;
import org.ovirt.engine.core.common.businessentities.storage.ImageStorageDomainMapId;
import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat;
import org.ovirt.engine.core.common.businessentities.storage.VolumeType;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.vdscommands.CopyImageVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.MoveImageGroupVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.DiskDao;
import org.ovirt.engine.core.dao.DiskImageDao;
import org.ovirt.engine.core.dao.ImageDao;
import org.ovirt.engine.core.dao.ImageStorageDomainMapDao;
import org.ovirt.engine.core.dao.StorageDomainDao;
import org.ovirt.engine.core.dao.StorageDomainStaticDao;
@InternalCommandAttribute
public class CopyImageGroupCommand<T extends MoveOrCopyImageGroupParameters> extends BaseImagesCommand<T> {
@Inject
private PostDeleteActionHandler postDeleteActionHandler;
@Inject
private DiskDao diskDao;
@Inject
private ImageStorageDomainMapDao imageStorageDomainMapDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private StorageDomainStaticDao storageDomainStaticDao;
@Inject
private ImageDao imageDao;
public CopyImageGroupCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
private DiskImage diskImage;
@Override
protected DiskImage getImage() {
switch (getActionState()) {
case END_SUCCESS:
case END_FAILURE:
if (diskImage == null) {
List<DiskImage> diskImages =
diskImageDao.getAllSnapshotsForImageGroup(getParameters().getImageGroupID());
diskImage = diskImages.isEmpty() ? null : diskImages.get(0);
}
return diskImage;
default:
return super.getImage();
}
}
@Override
protected boolean validate() {
// Not relevant for import VM/VMTemplate
if (getParameters().isImportEntity()) {
return true;
}
Guid imageGroupId = Guid.isNullOrEmpty(getParameters().getImageGroupID()) ?
getImageGroupId() : getParameters().getImageGroupID();
Disk disk = diskDao.get(imageGroupId);
if (disk != null) {
DiskValidator diskValidator = new DiskValidator(disk);
return validate(diskValidator.validateUnsupportedDiskStorageType(
DiskStorageType.LUN, DiskStorageType.CINDER));
}
return true;
}
@Override
protected void executeCommand() {
lockImage();
if (performStorageOperation()) {
// Add storage domain in db only if there is new entity in DB.
if (!shouldUpdateStorageDisk() && getParameters().getAddImageDomainMapping()) {
imageStorageDomainMapDao.save
(new ImageStorageDomainMap(getParameters().getImageId(),
getParameters().getStorageDomainId(),
getParameters().getQuotaId(),
getParameters().getDiskProfileId()));
}
setSucceeded(true);
}
}
private boolean isUsingSPDMFlow() {
return isDataOperationsByHSM() && !getParameters().getUseCopyCollapse()
&& getParameters().getParentCommand() == VdcActionType.MoveOrCopyDisk
&& getParameters().getOperation() == ImageOperation.Move;
}
private boolean performStorageOperation() {
Guid sourceDomainId = getParameters().getSourceDomainId() != null ? getParameters().getSourceDomainId()
: getDiskImage().getStorageIds().get(0);
if (isUsingSPDMFlow()) {
CopyImageGroupWithDataCommandParameters p = new CopyImageGroupWithDataCommandParameters(
getStorageDomain().getStoragePoolId(),
sourceDomainId,
getParameters().getStorageDomainId(),
getParameters().getImageGroupID(),
getParameters().getImageId(),
getParameters().getImageGroupID(),
getParameters().getImageId(),
getVolumeFormatForDomain(),
getParameters().getUseCopyCollapse());
p.setParentParameters(getParameters());
p.setParentCommand(getActionType());
p.setEndProcedure(EndProcedure.COMMAND_MANAGED);
p.setJobWeight(getParameters().getJobWeight());
runInternalAction(VdcActionType.CopyImageGroupWithData, p);
return true;
} else {
VDSReturnValue vdsReturnValue;
Guid taskId = persistAsyncTaskPlaceHolder(getParameters().getParentCommand());
if (getParameters().getUseCopyCollapse()) {
vdsReturnValue = runVdsCommand(
VDSCommandType.CopyImage,
postDeleteActionHandler.fixParameters(
new CopyImageVDSCommandParameters(getStorageDomain().getStoragePoolId(),
sourceDomainId,
getParameters().getContainerId(),
getParameters().getImageGroupID(),
getParameters().getImageId(),
getParameters().getDestImageGroupId(),
getParameters().getDestinationImageId(),
"",
getParameters().getStorageDomainId(),
getParameters().getCopyVolumeType(),
getVolumeFormatForDomain(),
getParameters().getVolumeType(),
isWipeAfterDelete(),
getStorageDomain().isDiscardAfterDelete(),
getParameters().getForceOverride())));
} else {
vdsReturnValue = runVdsCommand(
VDSCommandType.MoveImageGroup,
postDeleteActionHandler.fixParameters(
new MoveImageGroupVDSCommandParameters(
getDiskImage() != null ? getDiskImage().getStoragePoolId()
: getStorageDomain().getStoragePoolId(),
sourceDomainId,
getDiskImage() != null ?
getDiskImage().getId() : getParameters().getImageGroupID(),
getParameters().getStorageDomainId(),
getParameters().getContainerId(),
ImageOperation.Copy,
isWipeAfterDelete(),
storageDomainDao.get(sourceDomainId).isDiscardAfterDelete(),
getParameters().getForceOverride())));
}
if (vdsReturnValue.getSucceeded()) {
AsyncTaskCreationInfo taskCreationInfo = vdsReturnValue.getCreationInfo();
getTaskIdList().add(
createTask(taskId,
taskCreationInfo,
getParameters().getParentCommand(),
VdcObjectType.Storage,
sourceDomainId,
getParameters().getStorageDomainId()));
}
return vdsReturnValue.getSucceeded();
}
}
@Override
public CommandCallback getCallback() {
if (isUsingSPDMFlow()){
return new ConcurrentChildCommandsExecutionCallback();
}
return null;
}
private boolean isWipeAfterDelete() {
return getDestinationDiskImage() != null ? getDestinationDiskImage().isWipeAfterDelete()
: getParameters().getWipeAfterDelete();
}
/**
* Since we are supporting copy/move operations between different storage families (file/block) we have to
* predetermine the volume format according to the destination storage type, for block domains we cannot use sparse
* combined with raw so we will change the raw to cow in that case, file domains will have the original format
* retained
*/
private VolumeFormat getVolumeFormatForDomain() {
if (getParameters().getVolumeFormat() == VolumeFormat.COW) {
return VolumeFormat.COW;
}
StorageDomainStatic destDomain = storageDomainStaticDao.get(getParameters().getStorageDomainId());
if (destDomain.getStorageType().isBlockDomain() && getParameters().getVolumeType() == VolumeType.Sparse) {
return VolumeFormat.COW;
}
else {
return VolumeFormat.RAW;
}
}
/**
* Shareable disk which shared between more than one VM, will be returned more than once when fetching the images by image group
* since it has multiple VM devices (one for each VM it is attached to) and not because he has snapshots,
* so the shareable disk needs to be distinct when updating the storage domain.
* @param snapshots - All the images which related to the image group id
*/
private static void setSnapshotForShareableDisk(List<DiskImage> snapshots) {
if (!snapshots.isEmpty() && snapshots.get(0).isShareable()) {
DiskImage sharedDisk = snapshots.get(0);
snapshots.clear();
snapshots.add(sharedDisk);
}
}
@Override
protected AsyncTaskType getTaskType() {
if (!isUsingSPDMFlow()) {
return AsyncTaskType.moveImage;
}
return AsyncTaskType.notSupported;
}
@Override
protected void endSuccessfully() {
if (shouldUpdateStorageDisk()) {
List<DiskImage> snapshots = diskImageDao
.getAllSnapshotsForImageGroup(getParameters().getDestImageGroupId());
setSnapshotForShareableDisk(snapshots);
for (DiskImage snapshot : snapshots) {
imageStorageDomainMapDao.remove
(new ImageStorageDomainMapId(snapshot.getImageId(),
snapshot.getStorageIds().get(0)));
imageStorageDomainMapDao.save
(new ImageStorageDomainMap(snapshot.getImageId(),
getParameters().getStorageDomainId(),
getParameters().getQuotaId(),
getParameters().getDiskProfileId()));
setQcowCompatForSnapshot(snapshot, null);
}
}
super.endSuccessfully();
}
protected void setQcowCompatForSnapshot(DiskImage snapshot, DiskImage volInfo) {
DiskImage info = volInfo;
if (snapshot.getVolumeFormat().equals(VolumeFormat.COW)) {
try {
if (info == null) {
info = getVolumeInfo(snapshot.getStoragePoolId(),
getParameters().getStorageDomainId(),
snapshot.getId(),
snapshot.getImageId());
}
if (info != null) {
setQcowCompatByQemuImageInfo(snapshot.getStoragePoolId(),
snapshot.getId(),
snapshot.getImageId(),
getParameters().getStorageDomainId(),
snapshot);
}
imageDao.update(snapshot.getImage());
} catch (EngineException e) {
// Logging only
log.error("Unable to update the image info for image '{}' (image group: '{}') on domain '{}'",
snapshot.getImageId(),
snapshot.getId(),
getParameters().getStorageDomainId());
}
}
}
private boolean shouldUpdateStorageDisk() {
return getParameters().getOperation() == ImageOperation.Move ||
getParameters().getParentCommand() == VdcActionType.ImportVm;
}
@Override
protected void endWithFailure() {
if (!getParameters().isImportEntity()) {
unLockImage();
}
if (getParameters().getAddImageDomainMapping()) {
// remove image-storage mapping
imageStorageDomainMapDao.remove
(new ImageStorageDomainMapId(getParameters().getImageId(),
getParameters().getStorageDomainId()));
}
revertTasks();
setSucceeded(true);
}
@Override
protected void revertTasks() {
if (getParameters().getRevertDbOperationScope() != null) {
Guid destImageId = getParameters().getDestinationImageId();
RemoveImageParameters removeImageParams =
new RemoveImageParameters(destImageId);
if (getParameters().getParentCommand() == VdcActionType.AddVmFromSnapshot) {
removeImageParams.setParentParameters(getParameters());
removeImageParams.setParentCommand(VdcActionType.CopyImageGroup);
} else {
removeImageParams.setParentParameters(removeImageParams);
removeImageParams.setParentCommand(VdcActionType.RemoveImage);
removeImageParams.setStorageDomainId(getParameters().getStorageDomainId());
removeImageParams.setDbOperationScope(getParameters().getRevertDbOperationScope());
removeImageParams.setShouldLockImage(getParameters().isShouldLockImageOnRevert());
}
removeImageParams.setEntityInfo(new EntityInfo(VdcObjectType.Disk, getDestinationImageId()));
// Setting the image as the monitored entity, so there will not be dependency
VdcReturnValueBase returnValue =
checkAndPerformRollbackUsingCommand(VdcActionType.RemoveImage, removeImageParams, null);
if (returnValue.getSucceeded()) {
// Starting to monitor the the tasks - RemoveImage is an internal command
// which adds the taskId on the internal task ID list
startPollingAsyncTasks(returnValue.getInternalVdsmTaskIdList());
}
}
}
@Override
protected boolean canPerformRollbackUsingCommand(VdcActionType commandType, VdcActionParametersBase params) {
return diskImageDao.get(getParameters().getDestinationImageId()) != null;
}
}