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.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.storage.disk.image.ImagesHandler;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.AddVmTemplateFromSnapshotParameters;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.VdcActionType;
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.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
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.utils.transaction.TransactionSupport;
/**
* This class adds a template VM from a snapshot (disks are cloned).
*/
@DisableInPrepareMode
@NonTransactiveCommandAttribute(forceCompensation = true)
public class AddVmTemplateFromSnapshotCommand<T extends AddVmTemplateFromSnapshotParameters> extends AddVmTemplateCommand<T> {
@Inject
private DiskImageDao diskImageDao;
@Inject
private SnapshotDao snapshotDao;
private Snapshot cachedSnapshot;
public AddVmTemplateFromSnapshotCommand(Guid commandId) {
super(commandId);
}
public AddVmTemplateFromSnapshotCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
@Override
protected void init() {
if (getParameters().getVm() == null) {
return;
}
setClusterId(getParameters().getVm().getClusterId());
if (getCluster() == null) {
return;
}
setStoragePoolId(getCluster().getStoragePoolId());
VM vm = getVmFromConfiguration();
if (vm == null) {
return;
}
vm.setClusterId(getClusterId());
vm.setStoragePoolId(getStoragePoolId());
setVm(vm);
getParameters().setVm(vm);
setSnapshotName(getSnapshot() != null ? getSnapshot().getDescription() : null);
super.init();
}
@Override
protected boolean validate() {
if (!validate(snapshotsValidator.snapshotExists(getSnapshot()))
|| !validate(snapshotsValidator.vmNotDuringSnapshot(getSnapshot().getVmId()))
|| !validate(snapshotsValidator.snapshotVmConfigurationBroken(getSnapshot(), getVmName()))) {
return false;
}
if (getVm() == null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_SNAPSHOT_HAS_NO_CONFIGURATION,
String.format("$VmName %1$s", getVmName()),
String.format("$SnapshotName %1$s", getSnapshotName()));
}
if (getCluster() == null) {
return failValidation(EngineMessage.VMT_CLUSTER_IS_NOT_VALID);
}
return super.validate();
}
@Override
protected boolean isVmStatusValid(VMStatus status) {
if (getSnapshot().getType() == Snapshot.SnapshotType.ACTIVE) {
return status == VMStatus.Down;
}
return true;
}
@Override
protected List<DiskImage> getVmDisksFromDB() {
List<DiskImage> disksFromDb =
DisksFilter.filterImageDisks(getVm().getDiskMap().values(), ONLY_SNAPABLE, ONLY_ACTIVE);
disksFromDb.addAll(DisksFilter.filterCinderDisks(getVm().getDiskMap().values(), ONLY_PLUGGED));
return disksFromDb;
}
@Override
protected Map<Guid, Guid> addAllTemplateDisks() {
processIllegalDisks();
Map<Guid, Guid> srcDeviceIdToTargetDeviceIdMapping = super.addAllTemplateDisks();
if (!srcDeviceIdToTargetDeviceIdMapping.isEmpty()) {
lockSnapshot();
}
return srcDeviceIdToTargetDeviceIdMapping;
}
private void processIllegalDisks() {
for (DiskImage diskImage : images) {
if (diskImage.getImageStatus() == ImageStatus.ILLEGAL) {
DiskImage snapshotImageInDb =
diskImageDao.getSnapshotById(diskImage.getImageId());
if (snapshotImageInDb == null) {
// If the snapshot diskImage is null, it means the disk was probably
// erased after the snapshot was created.
// Create a disk to reflect the fact the disk existed during snapshot
saveIllegalDisk(diskImage);
}
}
}
}
private void saveIllegalDisk(final DiskImage diskImage) {
TransactionSupport.executeInNewTransaction(() -> {
// Allocating new IDs for image and disk as it's possible
// that more than one clone will be made from this source
// So this is required to avoid PK violation at DB.
diskImage.setImageId(Guid.newGuid());
diskImage.setId(Guid.newGuid());
diskImage.setParentId(Guid.Empty);
diskImage.setImageTemplateId(Guid.Empty);
ImagesHandler.setDiskAlias(diskImage, getVm());
ImagesHandler.addDiskImage(diskImage, getVmId());
return null;
});
}
@Override
protected VdcActionType getAddAllTemplateDisksActionType() {
return VdcActionType.CreateAllTemplateDisksFromSnapshot;
}
/**
* 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 lockSnapshot() {
TransactionSupport.executeInNewTransaction(() -> {
getCompensationContext().snapshotEntityStatus(getSnapshot());
snapshotDao.updateStatus(getParameters().getSourceSnapshotId(), SnapshotStatus.LOCKED);
getCompensationContext().stateChanged();
return null;
});
}
protected void unlockSnapshot() {
// Assumption - this is last DB change of command, no need for compensation here
snapshotDao.updateStatus(getParameters().getSourceSnapshotId(), SnapshotStatus.OK);
}
private Snapshot getSnapshot() {
if (cachedSnapshot == null) {
cachedSnapshot = snapshotDao.get(getParameters().getSourceSnapshotId());
}
return cachedSnapshot;
}
protected VM getVmFromConfiguration() {
VdcQueryReturnValue queryReturnValue = runInternalQuery(
VdcQueryType.GetVmConfigurationBySnapshot,
new IdQueryParameters(getParameters().getSourceSnapshotId()));
return queryReturnValue.getSucceeded() ? queryReturnValue.getReturnValue() : null;
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Command);
}
@Override
protected Map<String, Pair<String, String>> getSharedLocks() {
Map<String, Pair<String, String>> locks = new HashMap<>();
Map<String, Pair<String, String>> parentLocks = super.getSharedLocks();
if (parentLocks != null) {
locks.putAll(parentLocks);
}
if (getVm() != null) {
locks.put(getVmId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
}
return locks;
}
@Override
protected void endSuccessfully() {
super.endSuccessfully();
unlockSnapshot();
}
@Override
protected void endWithFailure() {
super.endWithFailure();
unlockSnapshot();
}
@Override
public Map<String, String> getJobMessageProperties() {
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
jobProperties.put(VdcObjectType.Snapshot.name().toLowerCase(),
StringUtils.defaultString(getSnapshotName()));
}
return jobProperties;
}
}