package org.ovirt.engine.core.bll;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_ACTIVE;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_PLUGGED;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_SNAPABLE;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.bll.validator.VmValidator;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.AddVmFromSnapshotParameters;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotStatus;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.StorageType;
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.EngineMessage;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.queries.IdQueryParameters;
import org.ovirt.engine.core.common.queries.VdcQueryReturnValue;
import org.ovirt.engine.core.common.queries.VdcQueryType;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.DiskImageDao;
import org.ovirt.engine.core.dao.SnapshotDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.dao.VmDynamicDao;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
/**
* This class adds a cloned VM from a snapshot (Deep disk copy).
*/
@DisableInPrepareMode
@NonTransactiveCommandAttribute(forceCompensation = true)
public class AddVmFromSnapshotCommand<T extends AddVmFromSnapshotParameters> extends AddVmAndCloneImageCommand<T> {
@Inject
private DiskImageDao diskImageDao;
@Inject
private VmDynamicDao vmDynamicDao;
@Inject
private SnapshotDao snapshotDao;
@Inject
private VmDao vmDao;
private Guid sourceSnapshotId;
private Snapshot snapshot;
private VM sourceVmFromDb;
private VM vmFromConfiguration;
private Collection<DiskImage> diskImagesFromConfiguration;
private Guid storageDomainId;
protected AddVmFromSnapshotCommand(Guid commandId) {
super(commandId);
}
protected AddVmFromSnapshotCommand(T params, CommandContext commandContext) {
super(params, commandContext);
sourceSnapshotId = params.getSourceSnapshotId();
}
@Override
protected void init() {
super.init();
VM vm = vmDao.get(getVmIdFromSnapshot());
vmHandler.updateDisksFromDb(vm);
boolean isCinderDisksExist = !DisksFilter.filterCinderDisks(vm.getDiskList()).isEmpty();
getParameters().setUseCinderCommandCallback(isCinderDisksExist);
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Execution);
}
@Override
public Guid getStoragePoolId() {
return (getSourceVmFromDb() != null) ? getSourceVmFromDb().getStoragePoolId() : null;
}
@Override
public Guid getStorageDomainId() {
if (storageDomainId == null) {
// This is needed for logging the command using CommandBase.logCommand
List<DiskImage> images = diskImageDao.getAllSnapshotsForVmSnapshot(sourceSnapshotId);
storageDomainId = !images.isEmpty() ? images.get(0).getStorageIds().get(0) : Guid.Empty;
}
return storageDomainId;
}
@Override
protected Guid getStoragePoolIdFromSourceImageContainer() {
return sourceVmFromDb.getStoragePoolId();
}
protected Guid getVmIdFromSnapshot() {
return (getSnapshot() != null) ? getSnapshot().getVmId() : Guid.Empty;
}
@Override
protected Collection<DiskImage> getAdjustedDiskImagesFromConfiguration() {
if (diskImagesFromConfiguration == null) {
diskImagesFromConfiguration =
DisksFilter.filterImageDisks(vmFromConfiguration.getDiskMap().values(),
ONLY_SNAPABLE, ONLY_ACTIVE);
diskImagesFromConfiguration.addAll(
DisksFilter.filterCinderDisks(vmFromConfiguration.getDiskMap().values(), ONLY_PLUGGED));
adjustDisksImageConfiguration(diskImagesFromConfiguration);
}
return diskImagesFromConfiguration;
}
private void adjustDisksImageConfiguration(Collection<DiskImage> diskImages) {
for (DiskImage diskImage : diskImages) {
// Adjust disk image configuration if needed.
if ( diskImage.getVolumeType().equals(VolumeType.Sparse) && diskImage.getVolumeFormat().equals(VolumeFormat.RAW) &&
getDestintationDomainTypeFromDisk(diskImage).isBlockDomain()) {
diskImage.setVolumeFormat(VolumeFormat.COW);
}
}
}
private StorageType getDestintationDomainTypeFromDisk(DiskImage diskImage) {
return destStorages.get(diskInfoDestinationMap.get(diskImage.getId()).getStorageIds().get(0)).getStorageStaticData().getStorageType();
}
@Override
protected void logErrorOneOrMoreActiveDomainsAreMissing() {
log.error("Can not found any default active domain for one of the disks of snapshot with id '{}'",
sourceSnapshotId);
}
protected Snapshot getSnapshot() {
if (snapshot == null) {
snapshot = snapshotDao.get(sourceSnapshotId);
if (snapshot != null) {
setSnapshotName(snapshot.getDescription());
}
}
return snapshot;
}
@Override
protected boolean validate() {
// If snapshot does not exist, there is not point in checking any of the VM related checks
if (!validate(snapshotsValidator.snapshotExists(getSnapshot()))
|| !validate(snapshotsValidator.vmNotDuringSnapshot(getSnapshot().getVmId()))
|| !validate(snapshotsValidator.snapshotVmConfigurationBroken(getSnapshot(), getVmName()))) {
return false;
}
vmFromConfiguration = getVmFromConfiguration();
if (vmFromConfiguration == null) {
addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_VM_SNAPSHOT_HAS_NO_CONFIGURATION);
addValidationMessageVariable("VmName", getVmName());
addValidationMessageVariable("SnapshotName", getSnapshotName());
return false;
}
if (!super.validate()) {
return false;
}
if (!checkCanDisableVirtIoScsi()) {
return false;
}
return true;
}
protected boolean checkCanDisableVirtIoScsi() {
VmValidator vmValidator = createVmValidator(getVmFromConfiguration());
if (Boolean.FALSE.equals(getParameters().isVirtioScsiEnabled()) &&
!validate(vmValidator.canDisableVirtioScsi(getAdjustedDiskImagesFromConfiguration()))) {
return false;
} else {
return true;
}
}
@Override
protected VM getVmFromConfiguration() {
if (vmFromConfiguration == null) {
VdcQueryReturnValue queryReturnValue =
runInternalQuery(VdcQueryType.GetVmConfigurationBySnapshot,
new IdQueryParameters(snapshot.getId()));
if (queryReturnValue.getSucceeded()) {
vmFromConfiguration = queryReturnValue.getReturnValue();
}
}
return vmFromConfiguration;
}
@Override
/**
* Assumption - a snapshot can be locked only if in status OK, so if validate passed
* this is the status of the snapshot. In addition the newly added VM is in down status
*/
protected void lockEntities() {
TransactionSupport.executeInNewTransaction(() -> {
getCompensationContext().snapshotEntityStatus(getSnapshot());
snapshotDao.updateStatus(sourceSnapshotId, SnapshotStatus.LOCKED);
lockVmWithCompensationIfNeeded();
getCompensationContext().stateChanged();
return null;
});
freeLock();
}
@Override
protected VM getSourceVmFromDb() {
if (sourceVmFromDb == null) {
sourceVmFromDb = vmDao.get(getVmIdFromSnapshot());
}
return sourceVmFromDb;
}
@Override
protected void unlockEntities() {
// Assumption - this is last DB change of command, no need for compensation here
snapshotDao.updateStatus(sourceSnapshotId, SnapshotStatus.OK);
vmDynamicDao.updateStatus(getVmId(), VMStatus.Down);
}
@Override
public Map<String, String> getJobMessageProperties() {
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
jobProperties.put(VdcObjectType.Snapshot.name().toLowerCase(),
StringUtils.defaultString(getSnapshotName()));
}
return jobProperties;
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
Map<String, Pair<String, String>> thisLocks = Collections.singletonMap(getSourceVmFromDb().getId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
Map<String, Pair<String, String>> parentLocks = super.getExclusiveLocks();
if (parentLocks == null) {
return thisLocks;
}
Map<String, Pair<String, String>> union = new HashMap<>();
union.putAll(parentLocks);
union.putAll(thisLocks);
return union;
}
@Override
protected Guid getSourceVmId() {
return getVmIdFromSnapshot();
}
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
List<PermissionSubject> permissionList = super.getPermissionCheckSubjects();
permissionList.add(new PermissionSubject(getVmIdFromSnapshot(),
VdcObjectType.VM,
getActionType().getActionGroup()));
return permissionList;
}
@Override
protected void endSuccessfully() {
super.endSuccessfully();
unlockEntities();
}
@Override
protected void endWithFailure() {
super.endWithFailure();
unlockEntities();
}
@Override
protected void updateOriginalTemplate(VmStatic vmStatic) {
// do not update it - it is already correctly configured from the snapshot
}
public VmValidator createVmValidator(VM vm) {
return new VmValidator(vm);
}
@Override
public CommandCallback getCallback() {
return getParameters().isUseCinderCommandCallback() ? new ConcurrentChildCommandsExecutionCallback() : null;
}
}