package org.ovirt.engine.core.bll; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.hostdev.HostDeviceManager; import org.ovirt.engine.core.bll.job.ExecutionHandler; import org.ovirt.engine.core.bll.network.host.NetworkDeviceHelper; import org.ovirt.engine.core.bll.snapshots.SnapshotsManager; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.common.action.DetachUserFromVmFromPoolParameters; import org.ovirt.engine.core.common.action.ProcessDownVmParameters; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.action.VdsActionParameters; import org.ovirt.engine.core.common.action.VmManagementParametersBase; import org.ovirt.engine.core.common.action.VmOperationParameterBase; import org.ovirt.engine.core.common.businessentities.GraphicsDevice; import org.ovirt.engine.core.common.businessentities.GraphicsType; import org.ovirt.engine.core.common.businessentities.Snapshot; import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.VmPayload; import org.ovirt.engine.core.common.businessentities.VmPool; import org.ovirt.engine.core.common.businessentities.VmPoolType; import org.ovirt.engine.core.common.businessentities.VmRngDevice; import org.ovirt.engine.core.common.businessentities.VmWatchdog; import org.ovirt.engine.core.common.businessentities.aaa.DbUser; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.utils.VmDeviceCommonUtils; import org.ovirt.engine.core.common.utils.VmDeviceType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.DbUserDao; import org.ovirt.engine.core.dao.SnapshotDao; import org.ovirt.engine.core.dao.VmDeviceDao; import org.ovirt.engine.core.dao.VmPoolDao; import org.ovirt.engine.core.utils.lock.EngineLock; import org.ovirt.engine.core.utils.lock.LockManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @InternalCommandAttribute @NonTransactiveCommandAttribute public class ProcessDownVmCommand<T extends ProcessDownVmParameters> extends CommandBase<T> { private static final Logger log = LoggerFactory.getLogger(ProcessDownVmCommand.class); private boolean templateVersionChanged; @Inject private HostDeviceManager hostDeviceManager; @Inject private NetworkDeviceHelper networkDeviceHelper; @Inject private SnapshotsManager snapshotsManager; @Inject private LockManager lockManager; private VmPool vmPoolCached; @Inject private VmPoolDao vmPoolDao; @Inject private DbUserDao dbUserDao; @Inject private VmDeviceDao vmDeviceDao; @Inject private SnapshotDao snapshotDao; protected ProcessDownVmCommand(Guid commandId) { super(commandId); } public ProcessDownVmCommand(T parameters, CommandContext cmdContext) { super(parameters, cmdContext); setVmId(getParameters().getId()); } private VmPool getVmPoolCached() { if (vmPoolCached == null && getVm().getVmPoolId() != null) { vmPoolCached = vmPoolDao.get(getVm().getVmPoolId()); } return vmPoolCached; } private VmPoolType getVmPoolType() { VmPool pool = getVmPoolCached(); return (pool != null) ? pool.getVmPoolType() : null; } @Override protected boolean validate() { if (getVm() == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND); } return true; } private boolean isRemovingVmPool() { VmPool vmPool = getVmPoolCached(); return vmPool != null ? vmPool.isBeingDestroyed() : false; } @Override protected void executeCommand() { boolean removingVmPool = isRemovingVmPool(); if (!removingVmPool) { applyNextRunConfiguration(); boolean removedStatelessSnapshot = detachUsers(); if (!removedStatelessSnapshot && !templateVersionChanged) { // If template version didn't change, and we are dealing with a prestarted Vm // or a regular Vm - clean stateless images // Otherwise this was already done in DetachUserFromVmFromPoolCommand \ updateVmVersionCommand->RemoveVmCommand removeVmStatelessImages(); } } getQuotaManager().rollbackQuotaByVmId(getVmId()); if (!removingVmPool) { removeStatelessVmUnmanagedDevices(); boolean vmHasDirectPassthroughDevices = releaseUsedHostDevices(); Guid hostId = cleanupVfs(); // Only single dedicated host allowed for host devices, verified on validates Guid alternativeHostsList = vmHasDirectPassthroughDevices ? getVm().getDedicatedVmForVdsList().get(0) : null; refreshHostIfNeeded(hostId == null ? alternativeHostsList : hostId); } } private boolean releaseUsedHostDevices() { if (hostDeviceManager.checkVmNeedsDirectPassthrough(getVm())) { try { // Only single dedicated host allowed for host devices, verified on validates hostDeviceManager.acquireHostDevicesLock(getVm().getDedicatedVmForVdsList().get(0)); hostDeviceManager.freeVmHostDevices(getVmId()); } finally { // Only single dedicated host allowed for host devices, verified on validates hostDeviceManager.releaseHostDevicesLock(getVm().getDedicatedVmForVdsList().get(0)); } return true; } return false; } private Guid cleanupVfs() { Guid hostId = networkDeviceHelper.removeVmIdFromVfs(getVmId()); return hostId; } private void refreshHostIfNeeded(Guid hostId) { // refresh host to get the host devices that were detached from the VM and re-attached to the host if (!getParameters().isSkipHostRefresh() && hostId != null) { runInternalAction(VdcActionType.RefreshHost, new VdsActionParameters(hostId)); } } private boolean detachUsers() { // check if this is a VM from a VM pool if (getVm().getVmPoolId() == null) { return false; } List<DbUser> users = dbUserDao.getAllForVm(getVmId()); // check if this VM is attached to a user if (users == null || users.isEmpty()) { // if not, check if new version or need to restore stateless if (!templateVersionChanged) { // if template version was changed, no need to restore runInternalActionWithTasksContext(VdcActionType.RestoreStatelessVm, new VmOperationParameterBase(getVmId()), getLock()); } return true; } if (getVmPoolType() == VmPoolType.AUTOMATIC) { // should be only one user in the collection for (DbUser dbUser : users) { runInternalActionWithTasksContext(VdcActionType.DetachUserFromVmFromPool, new DetachUserFromVmFromPoolParameters(getVm().getVmPoolId(), dbUser.getId(), getVmId(), !templateVersionChanged), getLock()); } return true; } return false; } @Override public List<PermissionSubject> getPermissionCheckSubjects() { return Collections.emptyList(); } /** * Remove VM's unmanaged devices that are created during run-once or stateless run. */ private void removeStatelessVmUnmanagedDevices() { if (getVm().isStateless() || getVm().isRunOnce()) { List<VmDevice> unmanagedVmDevices = vmDeviceDao.getUnmanagedDevicesByVmId(getVmId()); vmDeviceDao.removeAllInBatch(unmanagedVmDevices.stream() // do not remove device if appears in white list .filter(device -> !VmDeviceCommonUtils.isInWhiteList(device.getType(), device.getDevice())) .collect(Collectors.toList())); } } /** * Update VM configuration with NEXT_RUN configuration, if exists. */ private void applyNextRunConfiguration() { // Remove snpashot first, in case other update is in progress, it will block this one with exclusive lock // and any newer update should be preffered to this one. Snapshot runSnap = snapshotDao.get(getVmId(), SnapshotType.NEXT_RUN); if (runSnap != null && getVm().getStatus() != VMStatus.Suspended) { log.debug("Attempt to apply NEXT_RUN snapshot for VM '{}'", getVmId()); EngineLock updateVmLock = createUpdateVmLock(); if (lockManager.acquireLock(updateVmLock).getFirst()) { snapshotDao.remove(runSnap.getId()); Date originalCreationDate = getVm().getVmCreationDate(); snapshotsManager.updateVmFromConfiguration(getVm(), runSnap.getVmConfiguration()); // override creation date because the value in the config is the creation date of the config, not the vm getVm().setVmCreationDate(originalCreationDate); VdcReturnValueBase result = runInternalAction(VdcActionType.UpdateVm, createUpdateVmParameters(), ExecutionHandler.createInternalJobContext(updateVmLock)); if (result.getActionReturnValue() != null && result.getActionReturnValue() .equals(VdcActionType.UpdateVmVersion)) { // Template-version changed templateVersionChanged = true; } } else { log.warn("Could not acquire lock for UpdateVmCommand to apply Next Run Config of VM '{}'", getVmId()); } } } private EngineLock createUpdateVmLock() { return new EngineLock( UpdateVmCommand.getExclusiveLocksForUpdateVm(getVm()), UpdateVmCommand.getSharedLocksForUpdateVm(getVm())); } private VmManagementParametersBase createUpdateVmParameters() { // clear non updateable fields got from config getVm().setExportDate(null); getVm().setOvfVersion(null); VmManagementParametersBase updateVmParams = new VmManagementParametersBase(getVm()); updateVmParams.setUpdateWatchdog(true); updateVmParams.setSoundDeviceEnabled(false); updateVmParams.setBalloonEnabled(false); updateVmParams.setVirtioScsiEnabled(false); updateVmParams.setClearPayload(true); updateVmParams.setUpdateRngDevice(true); for (GraphicsType graphicsType : GraphicsType.values()) { updateVmParams.getGraphicsDevices().put(graphicsType, null); } for (VmDevice device : getVm().getManagedVmDeviceMap().values()) { switch (device.getType()) { case WATCHDOG: updateVmParams.setWatchdog(new VmWatchdog(device)); break; case SOUND: updateVmParams.setSoundDeviceEnabled(true); break; case BALLOON: updateVmParams.setBalloonEnabled(true); break; case CONTROLLER: if (VmDeviceType.VIRTIOSCSI.getName().equals(device.getDevice())) { updateVmParams.setVirtioScsiEnabled(true); } break; case DISK: if (VmPayload.isPayload(device.getSpecParams())) { updateVmParams.setVmPayload(new VmPayload(device)); } break; case CONSOLE: updateVmParams.setConsoleEnabled(true); break; case RNG: updateVmParams.setRngDevice(new VmRngDevice(device)); break; case GRAPHICS: updateVmParams.getGraphicsDevices().put(GraphicsType.fromString(device.getDevice()), new GraphicsDevice(device)); break; default: } } // clear these fields as these are non updatable getVm().getManagedVmDeviceMap().clear(); getVm().getUnmanagedDeviceList().clear(); return updateVmParams; } private void removeVmStatelessImages() { if (snapshotDao.exists(getVmId(), SnapshotType.STATELESS) && getVmPoolType() != VmPoolType.MANUAL) { log.info("Deleting snapshot for stateless vm '{}'", getVmId()); runInternalAction(VdcActionType.RestoreStatelessVm, new VmOperationParameterBase(getVmId()), ExecutionHandler.createDefaultContextForTasks(getContext(), getLock())); } } }