package org.ovirt.engine.core.bll;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_NOT_SHAREABLE;
import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_SNAPABLE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.ovirt.engine.core.bll.snapshots.SnapshotsValidator;
import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter;
import org.ovirt.engine.core.bll.storage.domain.IsoDomainListSynchronizer;
import org.ovirt.engine.core.bll.validator.RunVmValidator;
import org.ovirt.engine.core.bll.validator.VmValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator;
import org.ovirt.engine.core.bll.validator.storage.StorageDomainValidator;
import org.ovirt.engine.core.bll.validator.storage.StoragePoolValidator;
import org.ovirt.engine.core.common.BackendService;
import org.ovirt.engine.core.common.action.RunVmParams;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.StoragePool;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.businessentities.VmDynamic;
import org.ovirt.engine.core.common.businessentities.VmPoolMap;
import org.ovirt.engine.core.common.businessentities.storage.Disk;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.ClusterDao;
import org.ovirt.engine.core.dao.DbUserDao;
import org.ovirt.engine.core.dao.DiskDao;
import org.ovirt.engine.core.dao.SnapshotDao;
import org.ovirt.engine.core.dao.StorageDomainDao;
import org.ovirt.engine.core.dao.StoragePoolDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.dao.VmDynamicDao;
import org.ovirt.engine.core.dao.VmPoolDao;
import org.ovirt.engine.core.di.Injector;
import org.ovirt.engine.core.utils.lock.EngineLock;
import org.ovirt.engine.core.utils.lock.LockManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class VmPoolHandler implements BackendService {
@FunctionalInterface
public interface ErrorProcessor {
void process(Guid vmId, List<String> errors);
}
private static final Logger log = LoggerFactory.getLogger(VmPoolHandler.class);
@Inject
private LockManager lockManager;
@Inject
private VmPoolDao vmPoolDao;
@Inject
private VmDao vmDao;
@Inject
private DiskDao diskDao;
@Inject
private StoragePoolDao storagePoolDao;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private VmDynamicDao vmDynamicDao;
@Inject
private SnapshotDao snapshotDao;
@Inject
private DbUserDao dbUserDao;
@Inject
private ClusterDao clusterDao;
@Inject
private IsoDomainListSynchronizer isoDomainListSynchronizer;
@Inject
private VmHandler vmHandler;
@Inject
private SnapshotsValidator snapshotsValidator;
public EngineLock createLock(Guid vmId) {
return new EngineLock(
RunVmCommandBase.getExclusiveLocksForRunVm(vmId, getLockMessage()),
RunVmCommandBase.getSharedLocksForRunVm());
}
private String getLockMessage() {
return EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED.name();
}
private boolean acquireLock(EngineLock lock) {
boolean success = lockManager.acquireLock(lock).getFirst();
if (success) {
log.info("Lock Acquired to object '{}'", lock);
} else {
log.info("Failed to Acquire Lock to object '{}'", lock);
}
return success;
}
public Guid acquireVm(Guid vmId, boolean leaveLocked) {
if (!leaveLocked) {
return vmId;
}
EngineLock lock = createLock(vmId);
return acquireLock(lock) ? vmId : Guid.Empty;
}
private Stream<Guid> selectVms(Guid vmPoolId, VMStatus vmStatus, Predicate<Guid> vmIdFilter, boolean leaveLocked) {
List<VmPoolMap> vmPoolMaps = vmPoolDao.getVmMapsInVmPoolByVmPoolIdAndStatus(vmPoolId, vmStatus);
if (vmPoolMaps == null) {
return Stream.empty();
}
return vmPoolMaps.stream()
.map(VmPoolMap::getVmId)
.filter(vmIdFilter)
.map(vmId -> acquireVm(vmId, leaveLocked))
.filter(vmId -> !Guid.Empty.equals(vmId));
}
public Stream<Guid> selectPrestartedVms(Guid vmPoolId, boolean isStatefulPool, ErrorProcessor errorProcessor) {
return selectVms(vmPoolId,
VMStatus.Up,
vmId -> isPrestartedVmFree(vmId, isStatefulPool, errorProcessor),
false);
}
public Stream<Guid> selectNonPrestartedVms(Guid vmPoolId, ErrorProcessor errorProcessor) {
return selectVms(vmPoolId,
VMStatus.Down,
vmId -> isNonPrestartedVmFree(vmId, errorProcessor),
true);
}
public Guid selectPrestartedVm(Guid vmPoolId, boolean isStatefulPool, ErrorProcessor errorProcessor) {
return selectPrestartedVms(vmPoolId, isStatefulPool, errorProcessor).findFirst().orElse(Guid.Empty);
}
public Guid selectNonPrestartedVm(Guid vmPoolId, ErrorProcessor errorProcessor) {
return selectNonPrestartedVms(vmPoolId, errorProcessor).findFirst().orElse(Guid.Empty);
}
/**
* Checks if a running VM is free.
* @param vmId The VM GUID to check.
* @param isStatefulPool True if the VM is part of a stateful pool, false otherwise.
* @param errorProcessor the error messages processor object or lambda expression (may be <code>null</code>)
* @return True if can be attached, false otherwise.
*/
public boolean isPrestartedVmFree(Guid vmId, boolean isStatefulPool, ErrorProcessor errorProcessor) {
// check that there is no user already attached to this VM
// and make sure the VM is running statelessly
List<String> messages = new ArrayList<>();
boolean isFree = !vmAssignedToUser(vmId, messages)
&& (isStatefulPool && !vmIsStartedByRunOnce(vmId) || vmIsRunningStateless(vmId));
if (errorProcessor != null && !messages.isEmpty()) {
errorProcessor.process(vmId, messages);
}
return isFree;
}
/**
* Check if a specific VM is free. A VM is considered free if it isn't attached to a user and not during preview
* @param vmId The VM ID
* @param errorProcessor the error messages processor object or lambda expression (may be <code>null</code>)
* @return True if the VM is free, false otherwise
*/
public boolean isNonPrestartedVmFree(Guid vmId, ErrorProcessor errorProcessor) {
List<String> messages = new ArrayList<>();
// check that there is no user already attached to this VM
if (vmAssignedToUser(vmId, messages)) {
return failVmFree(errorProcessor, vmId, messages);
}
// check that VN can be run
if (!canRunPoolVm(vmId, messages)) {
return failVmFree(errorProcessor, vmId, messages);
}
// check VM images
ValidationResult vmDuringSnapshotResult = snapshotsValidator.vmNotDuringSnapshot(vmId);
if (!vmDuringSnapshotResult.isValid()) {
return failVmFree(errorProcessor, vmId, vmDuringSnapshotResult.getMessagesAsStrings());
}
ValidationResult vmInPreviewResult = snapshotsValidator.vmNotInPreview(vmId);
if (!vmInPreviewResult.isValid()) {
return failVmFree(errorProcessor, vmId, vmInPreviewResult.getMessagesAsStrings());
}
List<Disk> disks = diskDao.getAllForVm(vmId);
List<DiskImage> vmImages = DisksFilter.filterImageDisks(disks, ONLY_NOT_SHAREABLE, ONLY_SNAPABLE);
VM vm = vmDao.get(vmId);
StoragePool sp = storagePoolDao.get(vm.getStoragePoolId());
ValidationResult spUpResult = new StoragePoolValidator(sp).isUp();
if (!spUpResult.isValid()) {
return failVmFree(errorProcessor, vmId, spUpResult.getMessagesAsStrings());
}
Guid storageDomainId = vmImages.size() > 0 ? vmImages.get(0).getStorageIds().get(0) : Guid.Empty;
if (!Guid.Empty.equals(storageDomainId)) {
StorageDomainValidator storageDomainValidator =
new StorageDomainValidator(storageDomainDao
.getForStoragePool(storageDomainId, sp.getId()));
ValidationResult domainActiveResult = storageDomainValidator.isDomainExistAndActive();
if (!domainActiveResult.isValid()) {
return failVmFree(errorProcessor, vmId, domainActiveResult.getMessagesAsStrings());
}
}
DiskImagesValidator diskImagesValidator = new DiskImagesValidator(vmImages);
ValidationResult disksNotLockedResult = diskImagesValidator.diskImagesNotLocked();
if (!disksNotLockedResult.isValid()) {
messages.addAll(disksNotLockedResult.getMessagesAsStrings());
messages.addAll(disksNotLockedResult.getVariableReplacements());
return failVmFree(errorProcessor, vmId, messages);
}
ValidationResult vmNotLockResult = new VmValidator(vm).vmNotLocked();
if (!vmNotLockResult.isValid()) {
return failVmFree(errorProcessor, vmId, vmNotLockResult.getMessagesAsStrings());
}
return true;
}
private boolean failVmFree(ErrorProcessor errorProcessor, Guid vmId, List<String> messages) {
if (errorProcessor != null) {
List<String> errors = new ArrayList<>(messages);
errors.add(EngineMessage.VAR__TYPE__DESKTOP_POOL.toString());
errors.add(EngineMessage.VAR__ACTION__ATTACH_DESKTOP_TO.toString());
errorProcessor.process(vmId, errors);
}
return false;
}
private boolean vmIsStartedByRunOnce(Guid vmId) {
VmDynamic vmDynamic = vmDynamicDao.get(vmId);
return vmDynamic != null && vmDynamic.isRunOnce();
}
private boolean vmIsRunningStateless(Guid vmId) {
return snapshotDao.exists(vmId, Snapshot.SnapshotType.STATELESS);
}
private boolean vmAssignedToUser(Guid vmId, List<String> messages) {
if (dbUserDao.getAllForVm(vmId).size() > 0) {
messages.add(EngineMessage.VM_POOL_CANNOT_ADD_VM_WITH_USERS_ATTACHED_TO_POOL.toString());
return true;
}
return false;
}
private boolean canRunPoolVm(Guid vmId, List<String> messages) {
VM vm = vmDao.get(vmId);
if (vm == null) {
messages.add(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND.name());
return false;
}
// TODO: This is done to keep consistency with VmDao.getById.
// It can probably be removed, but that requires some more research
vmHandler.updateNetworkInterfacesFromDb(vm);
RunVmParams runVmParams = new RunVmParams(vmId);
return Injector.injectMembers(new RunVmValidator(vm, runVmParams, false, findActiveISODomain(vm.getStoragePoolId())))
.canRunVm(
messages,
fetchStoragePool(vm.getStoragePoolId()),
Collections.emptyList(),
Collections.emptyList(),
clusterDao.get(vm.getClusterId()),
false);
}
private Guid findActiveISODomain(Guid storagePoolId) {
return isoDomainListSynchronizer.findActiveISODomain(storagePoolId);
}
private StoragePool fetchStoragePool(Guid storagePoolId) {
return storagePoolDao.get(storagePoolId);
}
}