package org.ovirt.engine.core.bll; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.job.ExecutionContext; import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil; import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.LockProperties; import org.ovirt.engine.core.common.action.RemoveVmFromPoolParameters; import org.ovirt.engine.core.common.action.RemoveVmParameters; import org.ovirt.engine.core.common.action.StopVmParameters; import org.ovirt.engine.core.common.action.StopVmTypeEnum; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.action.VmPoolParametersBase; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VmPool; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.job.Step; import org.ovirt.engine.core.common.job.StepEnum; import org.ovirt.engine.core.common.locks.LockingGroup; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.TransactionScopeOption; import org.ovirt.engine.core.dal.job.ExecutionMessageDirector; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.VmPoolDao; import org.ovirt.engine.core.utils.lock.EngineLock; import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @NonTransactiveCommandAttribute public class RemoveVmPoolCommand<T extends VmPoolParametersBase> extends VmPoolCommandBase<T> { private static final Logger log = LoggerFactory.getLogger(RemoveVmPoolCommand.class); @Inject private VmPoolDao vmPoolDao; @Inject private VmDao vmDao; private List<VM> cachedVmsInPool; private Set<Guid> vmsRemoved = new HashSet<>(); private boolean allVmsDown; public RemoveVmPoolCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } @Override protected void init() { super.init(); if (getVmPool() != null) { setClusterId(getVmPool().getClusterId()); } getParameters().setVmPoolName(getVmPoolName()); } private List<VM> getCachedVmsInPool() { if (cachedVmsInPool == null && getVmPoolId() != null) { cachedVmsInPool = vmDao.getAllForVmPool(getVmPoolId()); } return cachedVmsInPool; } @Override protected boolean validate() { if (getVmPool() == null) { return failValidation(EngineMessage.VM_POOL_NOT_FOUND); } return true; } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__REMOVE); addValidationMessage(EngineMessage.VAR__TYPE__DESKTOP_POOL); } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(LockProperties.Scope.Execution); } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { Map<String, Pair<String, String>> locks = new HashMap<>(); locks.put(getVmPoolId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM_POOL, new LockMessage(EngineMessage.ACTION_TYPE_FAILED_VM_POOL_IS_BEING_REMOVED) .withOptional("VmPoolName", getVmPool() != null ? getVmPool().getName() : null))); for (VM vm : getCachedVmsInPool()) { addVmLocks(vm, locks); } return locks; } private void addVmLocks(VM vm, Map<String, Pair<String, String>> locks) { locks.put(vm.getId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, getVmIsBeingRemovedMessage(vm))); } private LockMessage getVmIsBeingRemovedMessage(VM vm) { return new LockMessage(EngineMessage.ACTION_TYPE_FAILED_VM_POOL_IS_BEING_REMOVED_WITH_VM) .withOptional("VmPoolName", getVmPool() != null ? getVmPool().getName() : null) .withOptional("VmName", getVmPool() != null ? vm.getName() : null); } @Override protected void executeCommand() { TransactionSupport.executeInNewTransaction(() -> { getCompensationContext().snapshotEntity(getVmPool()); setPoolBeingDestroyed(); setPrestartedToZero(); getCompensationContext().stateChanged(); return null; }); setCommandShouldBeLogged(false); // disable logging at the end of command execution log(); // and log the message now stopVms(); if (allVmsDown) { endSuccessfully(); } else { setSucceeded(true); } } private void setPoolBeingDestroyed() { vmPoolDao.setBeingDestroyed(getVmPoolId(), true); } private void setPrestartedToZero() { VmPool vmPool = getVmPool(); if (vmPool.getPrestartedVms() > 0) { vmPool.setPrestartedVms(0); vmPoolDao.update(vmPool); } } private void stopVms() { allVmsDown = true; for (VM vm : getCachedVmsInPool()) { if (!vm.isDown()) { CommandCoordinatorUtil.executeAsyncCommand( VdcActionType.StopVm, withRootCommandInfo(new StopVmParameters(vm.getId(), StopVmTypeEnum.NORMAL)), cloneContextAndDetachFromParent()); allVmsDown = false; } else { removeVm(vm); } } } @Override protected void endSuccessfully() { setSucceeded(removeAllVmsInPool() && removeVmPool()); log(); } @Override protected void endWithFailure() { setSucceeded(true); log(); } private boolean removeVm(VM vm) { RemoveVmFromPoolParameters removeVmFromPoolParameters = new RemoveVmFromPoolParameters(vm.getId(), false, false); removeVmFromPoolParameters.setTransactionScopeOption(TransactionScopeOption.Suppress); VdcReturnValueBase result = runInternalActionWithTasksContext( VdcActionType.RemoveVmFromPool, removeVmFromPoolParameters); if (!result.getSucceeded()) { return false; } result = runInternalAction( VdcActionType.RemoveVm, new RemoveVmParameters(vm.getId(), false), createRemoveVmStepContext(vm)); if (!result.getSucceeded()) { return false; } vmsRemoved.add(vm.getId()); return true; } private boolean removeAllVmsInPool() { for (VM vm : getCachedVmsInPool()) { if (!vmsRemoved.contains(vm.getId()) && !removeVm(vm)) { return false; } } return true; } private boolean canRemoveVmPool(Guid vmPoolId) { return vmPoolDao.getVmPoolsMapByVmPoolId(vmPoolId).isEmpty(); } private boolean removeVmPool() { if (getVmPoolId() != null && canRemoveVmPool(getVmPoolId())) { vmPoolDao.remove(getVmPoolId()); return true; } else { if (getVmPoolId() == null) { log.error("Failed to remove VM Pool: VM Pool ID is null"); } else { log.error("Failed to remove VM Pool '{}': there are still VMs in the Pool", getVmPoolName()); } return false; } } private CommandContext createRemoveVmStepContext(VM vm) { CommandContext commandCtx = null; try { Map<String, String> values = Collections.singletonMap(VdcObjectType.VM.name().toLowerCase(), vm.getName()); Step removeVmStep = executionHandler.addSubStep(getExecutionContext(), getExecutionContext().getJob().getStep(StepEnum.EXECUTING), StepEnum.REMOVING_VM, ExecutionMessageDirector.resolveStepMessage(StepEnum.REMOVING_VM, values)); ExecutionContext ctx = new ExecutionContext(); ctx.setStep(removeVmStep); ctx.setMonitored(true); Map<String, Pair<String, String>> locks = new HashMap<>(); addVmLocks(vm, locks); EngineLock engineLock = new EngineLock(locks, null); commandCtx = cloneContext().withoutCompensationContext().withExecutionContext(ctx).withLock(engineLock); } catch (RuntimeException e) { log.error("Failed to create command context of removing VM '{}' that was in Pool '{}': {}", vm.getName(), getVmPoolName(), e.getMessage()); log.debug("Exception", e); } return commandCtx; } @Override public CommandCallback getCallback() { return new RemoveVmPoolCommandCallback(); } @Override public AuditLogType getAuditLogTypeValue() { if (getActionState() == CommandActionState.EXECUTE) { return AuditLogType.USER_REMOVE_VM_POOL_INITIATED; } else { return getSucceeded() ? AuditLogType.USER_REMOVE_VM_POOL : AuditLogType.USER_REMOVE_VM_POOL_FAILED; } } @Override public Map<String, String> getJobMessageProperties() { if (jobProperties == null) { jobProperties = super.getJobMessageProperties(); jobProperties.put(VdcObjectType.Cluster.name().toLowerCase(), getClusterName()); } return jobProperties; } }