package org.ovirt.engine.core.bll; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VmOperationParameterBase; import org.ovirt.engine.core.common.businessentities.IVdsAsyncCommand; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VDSType; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VdsSelectionAlgorithm; import org.ovirt.engine.core.common.businessentities.VmDynamic; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.vdscommands.FailedToRunVmVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.UpdateVdsDynamicDataVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.UpdateVmDynamicDataVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VmMonitorCommandVDSCommandParameters; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.LogCompat; import org.ovirt.engine.core.compat.LogFactoryCompat; import org.ovirt.engine.core.compat.StringHelper; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil; /** * Base class for asincronious running process handling */ public abstract class RunVmCommandBase<T extends VmOperationParameterBase> extends VmCommand<T> implements IVdsAsyncCommand { private static VdsSelectionAlgorithm _defaultSelectionAlgorithm = VdsSelectionAlgorithm.EvenlyDistribute; protected static final java.util.HashMap<Guid, Integer> _vds_pending_vm_count = new java.util.HashMap<Guid, Integer>(); private VdsSelector privateVdsSelector; protected RunVmCommandBase(Guid commandId) { super(commandId); } public RunVmCommandBase(T parameters) { super(parameters); } protected VdsSelector getVdsSelector() { return privateVdsSelector; } protected void setVdsSelector(VdsSelector value) { privateVdsSelector = value; } static { try { _defaultSelectionAlgorithm = VdsSelectionAlgorithm.valueOf(Config .<String> GetValue(ConfigValues.VdsSelectionAlgorithm)); } catch (java.lang.Exception e) { // todo } } /** * List on all vdss, vm run on. In the case of problem to run vm will be more then one */ private java.util.ArrayList<Guid> getRunVdssList() { return getVdsSelector().getRunVdssList(); } public static VdsSelectionAlgorithm getDefaultSelectionAlgorithm() { return _defaultSelectionAlgorithm; } protected boolean _isRerun = false; /** * Check if the given host has enough CPU to run the VM, without exceeding the high utilization threshold. * * @param vds * The host to check. * @param vm * The VM to check. * @return Does this host has enough CPU (without exceeding the threshold) to run the VM. */ public static boolean hasCpuToRunVM(VDS vds, VM vm) { if (vds.getusage_cpu_percent() == null || vm.getusage_cpu_percent() == null) { return false; } // The predicted CPU is actually the CPU that the VM will take considering how many cores it has and now many // cores the host has. This is why we take both parameters into consideration. int predictedVmCpu = (vm.getusage_cpu_percent() * vm.getnum_of_cpus()) / vds.getcpu_cores(); boolean result = vds.getusage_cpu_percent() + predictedVmCpu <= vds.gethigh_utilization(); if (log.isDebugEnabled()) { log.debugFormat("Host {0} has {1}% CPU load; VM {2} is predicted to have {3}% CPU load; " + "High threshold is {4}%. Host is {5}suitable in terms of CPU.", vds.getvds_name(), vds.getusage_cpu_percent(), vm.getvm_name(), predictedVmCpu, vds.gethigh_utilization(), (result ? "" : "not ")); } return result; } public static boolean hasMemoryToRunVM(VDS curVds, VM vm) { boolean retVal = false; if (curVds.getmem_commited() != null && curVds.getphysical_mem_mb() != null && curVds.getreserved_mem() != null) { // VB & C# TO JAVA CONVERTER TODO TASK: Arithmetic operations // involving nullable type instances are not converted to null-value // logic: double vdsCurrentMem = curVds.getmem_commited() + curVds.getpending_vmem_size() + curVds.getguest_overhead() + curVds .getreserved_mem() + vm.getMinAllocatedMem(); double vdsMemLimit = curVds.getmax_vds_memory_over_commit() * curVds.getphysical_mem_mb() / 100.0; if (log.isDebugEnabled()) { log.debugFormat("hasMemoryToRunVM: host {0} pending vmem size is : {1} MB", curVds.getvds_name(), curVds.getpending_vmem_size()); log.debugFormat("Host Mem Conmmitted: {0}, Host Reserved Mem: {1}, Host Guest Overhead {2}, VM Min Allocated Mem {3}", curVds.getmem_commited(), curVds.getreserved_mem(), curVds.getguest_overhead(), vm.getMinAllocatedMem()); log.debugFormat("{0} <= ??? {1}", vdsCurrentMem, vdsMemLimit); } retVal = (vdsCurrentMem <= vdsMemLimit); } return retVal; } public static boolean hasCapacityToRunVM(VDS curVds) { boolean hasCapacity = true; if (curVds.getvds_type() == VDSType.PowerClient) { log.infoFormat( "Checking capacity for a power client - id:{0}, name:{1}, host_name(ip):{2}, vds.vm_count:{3}, PowerClientMaxNumberOfConcurrentVMs:{4}", curVds.getvds_id(), curVds.getvds_name(), curVds.gethost_name(), curVds.getvm_count(), Config.<Integer> GetValue(ConfigValues.PowerClientMaxNumberOfConcurrentVMs)); int pending_vm_count = 0; if (Config.<Boolean> GetValue(ConfigValues.PowerClientRunVmShouldVerifyPendingVMsAsWell) && _vds_pending_vm_count.containsKey(curVds.getvds_id())) { pending_vm_count = _vds_pending_vm_count.get(curVds.getvds_id()); } // VB & C# TO JAVA CONVERTER TODO TASK: Arithmetic operations // involving nullable type instances are not converted to null-value // logic: if ((curVds.getvm_count() + pending_vm_count + 1) > Config .<Integer> GetValue(ConfigValues.PowerClientMaxNumberOfConcurrentVMs)) { log.infoFormat( "No capacity for a power client - id:{0}, name:{1}, host_name(ip):{2}, vds.vm_count:{3}, PowerClientMaxNumberOfConcurrentVMs:{4}", curVds.getvds_id(), curVds.getvds_name(), curVds.gethost_name(), curVds.getvm_count(), Config.<Integer> GetValue(ConfigValues.PowerClientMaxNumberOfConcurrentVMs)); hasCapacity = false; } } return hasCapacity; } public static void DoCompressionCheck(VDS vds, VmDynamic vm) { if (Config.<Boolean> GetValue(ConfigValues.PowerClientSpiceDynamicCompressionManagement)) { // comrpession allways enabled on VDS if (vds.getvds_type() != VDSType.PowerClient) { return; } else { String compression_enabled = "on"; if (StringHelper.EqOp(vds.gethost_name(), vm.getclient_ip())) { compression_enabled = "off"; } log.infoFormat( "VdcBLL.VmHandler.DoCompressionCheck - sending monitor command for vmid: {0} - set_red_image_compression and set_red_streaming_video to {1}", vm.getId(), compression_enabled); Backend.getInstance() .getResourceManager() .RunVdsCommand( VDSCommandType.VmMonitorCommand, new VmMonitorCommandVDSCommandParameters(vds.getvds_id(), vm.getId(), "set_red_image_compression " + compression_enabled)); Backend.getInstance() .getResourceManager() .RunVdsCommand( VDSCommandType.VmMonitorCommand, new VmMonitorCommandVDSCommandParameters(vds.getvds_id(), vm.getId(), "set_red_streaming_video " + compression_enabled)); } } } /** * This function called when vds failed to run vm. Vm will be run on another vds (if exist) that's why the function * should be run at a new thread because of it will lock a new VDSM */ @Override public final void Rerun() { ThreadPoolUtil.execute(new Runnable() { @Override public void run() { rerunInternal(); } }); } protected void rerunInternal() { Guid vdsId = getDestinationVds() != null ? getDestinationVds().getvds_id() : getCurrentVdsId(); DecreasePendingVms(vdsId); setSucceeded(false); setVm(null); /** * Rerun VM only if not exceeded maximum rerun attempts. for example if there are 10 hosts that can run VM and * predefine maximum 3 attempts to rerun VM - on 4th turn vm will stop to run despite there are still available * hosts to run it DO NOT TRY TO RERUN IF RESUME FAILED. */ if (getRunVdssList().size() < Config.<Integer> GetValue(ConfigValues.MaxRerunVmOnVdsCount) && getVm().getstatus() != VMStatus.Paused) { _isRerun = true; // restore CanDoAction value to false so CanDoAction checks will run again getReturnValue().setCanDoAction(false); log(); ExecuteAction(); if (!getReturnValue().getCanDoAction()) { _isRerun = false; log(); FailedToRunVm(); } } else { Backend.getInstance().getResourceManager().RemoveAsyncRunningCommand(getVmId()); FailedToRunVm(); _isRerun = false; } } protected void FailedToRunVm() { ThreadPoolUtil.execute(new Runnable() { @Override public void run() { AnonymousMethod1(); } }); } private void AnonymousMethod1() { VmPoolHandler.ProcessVmPoolOnStopVm(getVm().getvm_guid()); } /** * Asyncronious event, send by vds on running vm success. Vm decided successfully run when it's status turn to Up. * If there are vdss, not succeded to run vm - treat them as suspicious. */ @Override public void RunningSucceded() { DecreasePendingVms(getCurrentVdsId()); setSucceeded(true); setActionReturnValue(VMStatus.Up); log(); for (Guid vdsId : getRunVdssList()) { if (!getCurrentVdsId().equals(vdsId)) { Backend.getInstance().getResourceManager() .RunVdsCommand(VDSCommandType.FailedToRunVm, new FailedToRunVmVDSCommandParameters(vdsId)); } } if (getVm().getlast_vds_run_on() == null || !getVm().getlast_vds_run_on().equals(getCurrentVdsId())) { getVm().setlast_vds_run_on(getCurrentVdsId()); } if (!StringHelper.isNullOrEmpty(getVm().gethibernation_vol_handle())) { HandleHibernatedVm(VdcActionType.RunVm, true); // In order to prevent a race where VdsUpdateRuntimeInfo saves the Vm Dynamic as UP prior to execution of // this method (which is a part of the cached VM command, // so the state this method is aware to is RESTORING, in case of RunVmCommand after the VM got suspended. // In addition, as the boolean return value of HandleHIbernateVm is ignored here, it is safe to set the // status to up. getVm().setstatus(VMStatus.Up); getVm().sethibernation_vol_handle(null); Backend.getInstance() .getResourceManager() .RunVdsCommand(VDSCommandType.UpdateVmDynamicData, new UpdateVmDynamicDataVDSCommandParameters(getCurrentVdsId(), getVm().getDynamicData())); } } @Override protected void EndVmCommand() { setCommandShouldBeLogged(false); setSucceeded(true); } protected Guid getCurrentVdsId() { return getVds().getvds_id(); } @Override public boolean getAutoStart() { return getVm().getauto_startup(); } @Override public Guid getAutoStartVdsId() { return null; } protected VDS _destinationVds; protected abstract VDS getDestinationVds(); private final Object _decreaseLock = new Object(); protected void DecreasePendingVms(Guid vdsId) { synchronized (_decreaseLock) { boolean updateDynamic = false; VDS vds = DbFacade.getInstance().getVdsDAO().get(vdsId); if (vds == null) return; // VCPU if (vds.getpending_vcpus_count() != null && !vds.getpending_vcpus_count().equals(0)) { vds.setpending_vcpus_count(vds.getpending_vcpus_count() - getVm().getnum_of_cpus()); updateDynamic = true; } else if (log.isDebugEnabled()) { log.debugFormat( "DecreasePendingVms::Decreasing vds {0} pending vcpu count failed, its already 0 or null", vds.getvds_name(), getVm().getvm_name()); } // VMEM if (vds.getpending_vmem_size() > 0) { // decrease min memory assigned, because it is already taken in account when VM is up updateDynamic = true; if (vds.getpending_vmem_size() >= getVm().getMinAllocatedMem()) { vds.setpending_vmem_size(vds.getpending_vmem_size() - getVm().getMinAllocatedMem()); } else { if (log.isDebugEnabled()) { log.debugFormat("Pending host {0} vmem {1} is smaller than VM min allocated memory {2},Setting pending host vmem to 0.", vds.getvds_name(), vds.getpending_vmem_size(), getVm().getMinAllocatedMem()); } vds.setpending_vmem_size(0); } } else if (log.isDebugEnabled()) { log.debugFormat( "DecreasePendingVms::Decreasing vds {0} pending vmem size failed, its already 0 or null", vds.getvds_name(), getVm().getvm_name()); } if (updateDynamic) { Backend.getInstance() .getResourceManager() .RunVdsCommand(VDSCommandType.UpdateVdsDynamicData, new UpdateVdsDynamicDataVDSCommandParameters(vds.getDynamicData())); if (log.isDebugEnabled()) { log.debugFormat("DecreasePendingVms::Decreasing vds {0} pending vcpu count, now {1}. Vm: {2}", vds.getvds_name(), vds.getpending_vcpus_count(), getVm().getvm_name()); log.debugFormat("DecreasePendingVms::Decreasing vds {0} pending vmem size, now {1}. Vm: {2}", vds.getvds_name(), vds.getpending_vmem_size(), getVm().getvm_name()); } } } } private static LogCompat log = LogFactoryCompat.getLog(RunVmCommandBase.class); }