/** * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. * * This software is licensed under the GNU General Public License v3 or later. * * It is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package com.cloud.agent.manager.allocator.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.ejb.Local; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.host.DetailVO; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.Host.Type; import com.cloud.host.dao.DetailsDao; import com.cloud.host.dao.HostDao; import com.cloud.service.ServiceOffering; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.GuestOSCategoryVO; import com.cloud.storage.GuestOSVO; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.StoragePoolVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.GuestOSCategoryDao; import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ComponentLocator; import com.cloud.vm.ConsoleProxyVO; import com.cloud.vm.DomainRouterVO; import com.cloud.vm.SecondaryStorageVmVO; import com.cloud.vm.UserVm; import com.cloud.vm.UserVmVO; import com.cloud.vm.VmCharacteristics; import com.cloud.vm.dao.ConsoleProxyDao; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.UserVmDao; /** * An allocator that tries to find a fit on a computing host. This allocator does not care whether or not the host supports routing. */ @Local(value={HostAllocator.class}) public class FirstFitAllocator implements HostAllocator { private static final Logger s_logger = Logger.getLogger(FirstFitAllocator.class); private String _name; protected HostDao _hostDao; protected DetailsDao _hostDetailsDao; protected UserVmDao _vmDao; protected ServiceOfferingDao _offeringDao; protected DomainRouterDao _routerDao; protected ConsoleProxyDao _consoleProxyDao; protected SecondaryStorageVmDao _secStorgaeVmDao; protected StoragePoolHostDao _storagePoolHostDao; protected ConfigurationDao _configDao; protected GuestOSDao _guestOSDao; protected GuestOSCategoryDao _guestOSCategoryDao; float _factor = 1; protected String _allocationAlgorithm = "random"; @Override public Host allocateTo(VmCharacteristics vm, ServiceOffering offering, Type type, DataCenterVO dc, HostPodVO pod, StoragePoolVO sp, VMTemplateVO template, Set<Host> avoid) { if (type == Host.Type.Storage) { // FirstFitAllocator should be used for user VMs only since it won't care whether the host is capable of routing or not return null; } s_logger.debug("Looking for hosts associated with storage pool " + sp.getId()); List<StoragePoolHostVO> poolhosts = _storagePoolHostDao.listByPoolId(sp.getId()); List<HostVO> hosts = new ArrayList<HostVO>(); for( StoragePoolHostVO poolhost : poolhosts ){ HostVO h = _hostDao.findById(poolhost.getHostId()); if( h != null && h.getType().equals(Type.Routing) && h.getStatus().equals(Status.Up)) { hosts.add(h); } } long podId = pod.getId(); List<HostVO> podHosts = new ArrayList<HostVO>(hosts.size()); Iterator<HostVO> it = hosts.iterator(); while (it.hasNext()) { HostVO host = it.next(); if (host.getPodId() == podId && !avoid.contains(host)) { if (s_logger.isDebugEnabled()) { s_logger.debug("Adding host " + host + " as possible pod host"); } podHosts.add(host); } } return allocateTo(offering, template, avoid, podHosts); } protected Host allocateTo(ServiceOffering offering, VMTemplateVO template, Set<Host> avoid, List<HostVO> hosts) { if (_allocationAlgorithm.equals("random")) { // Shuffle this so that we don't check the hosts in the same order. Collections.shuffle(hosts); } if (s_logger.isDebugEnabled()) { StringBuffer sb = new StringBuffer(); for(Host h : avoid) { sb.append(h.getName()).append(" "); } s_logger.debug("Found " + hosts.size() + " hosts for allocation and a avoid set of [" + sb + "]"); } if (s_logger.isDebugEnabled()) { s_logger.debug("Looking for speed=" + (offering.getCpu() * offering.getSpeed()) + "Mhz, Ram=" + offering.getRamSize()); } // We will try to reorder the host lists such that we give priority to hosts that have // the minimums to support a VM's requirements hosts = prioritizeHosts(template, hosts); if (s_logger.isDebugEnabled()) { s_logger.debug("Found " + hosts.size() + " hosts for allocation after prioritization"); } for (HostVO host : hosts) { if (avoid.contains(host)) { if (s_logger.isDebugEnabled()) { s_logger.debug("host " + host.getName() + " is in avoid set, skip and try other available hosts"); } continue; } long usedMemory = 0; double totalSpeed = 0d; List<DomainRouterVO> domainRouters = _routerDao.listUpByHostId(host.getId()); if (s_logger.isDebugEnabled()) { s_logger.debug("Found " + domainRouters.size() + " router domains on host " + host.getId()); } for (DomainRouterVO router : domainRouters) { usedMemory += router.getRamSize() * 1024L * 1024L; } List<ConsoleProxyVO> proxys = _consoleProxyDao.listUpByHostId(host.getId()); if (s_logger.isDebugEnabled()) { s_logger.debug("Found " + proxys.size() + " console proxy on host " + host.getId()); } for(ConsoleProxyVO proxy : proxys) { usedMemory += proxy.getRamSize() * 1024L * 1024L; } List<SecondaryStorageVmVO> secStorageVms = _secStorgaeVmDao.listUpByHostId(host.getId()); if (s_logger.isDebugEnabled()) { s_logger.debug("Found " + secStorageVms.size() + " secondary storage VM on host " + host.getId()); } for(SecondaryStorageVmVO secStorageVm : secStorageVms) { usedMemory += secStorageVm.getRamSize() * 1024L * 1024L; } List<UserVmVO> vms = _vmDao.listUpByHostId(host.getId()); if (s_logger.isDebugEnabled()) { s_logger.debug("Found " + vms.size() + " user VM on host " + host.getId()); } for (UserVmVO vm : vms) { ServiceOffering so = _offeringDao.findById(vm.getServiceOfferingId()); if (s_logger.isDebugEnabled()) { s_logger.debug("vm " + vm.getId() + ": speed=" + (so.getCpu() * so.getSpeed()) + "Mhz, RAM=" + so.getRamSize() + "MB"); } usedMemory += so.getRamSize() * 1024L * 1024L; totalSpeed += so.getCpu() * (so.getSpeed() * 0.99); } if (s_logger.isDebugEnabled()) { long availableSpeed = (long)(host.getCpus() * host.getSpeed() * _factor); double desiredSpeed = offering.getCpu() * (offering.getSpeed() * 0.99); long coreSpeed = host.getSpeed(); s_logger.debug("Host " + host.getId() + ": available speed=" + availableSpeed + "Mhz, core speed=" + coreSpeed + "Mhz, used speed=" + totalSpeed + "Mhz, desired speed=" + desiredSpeed + "Mhz, desired cores: " + offering.getCpu() + ", available cores: " + host.getCpus() + ", RAM=" + host.getTotalMemory() + ", avail RAM=" + (host.getTotalMemory() - usedMemory) + ", desired RAM=" + (offering.getRamSize() * 1024L * 1024L)); } boolean numCpusGood = host.getCpus().intValue() >= offering.getCpu(); boolean coreSpeedGood = host.getSpeed().doubleValue() >= (offering.getSpeed() * 0.99); boolean totalSpeedGood = ((host.getCpus().doubleValue() * host.getSpeed().doubleValue() * _factor) - totalSpeed) >= (offering.getCpu() * (offering.getSpeed() * 0.99)); boolean memoryGood = (host.getTotalMemory() - usedMemory) >= (offering.getRamSize() * 1024L * 1024L); if (numCpusGood && totalSpeedGood && coreSpeedGood && memoryGood) { if (s_logger.isDebugEnabled()) { s_logger.debug("found host " + host.getId()); } return host; } else { if (s_logger.isDebugEnabled()) { s_logger.debug("not using host " + host.getId() + "; numCpusGood: " + numCpusGood + ", coreSpeedGood: " + coreSpeedGood + ", totalSpeedGood: " + totalSpeedGood + ", memoryGood: " + memoryGood); } } } return null; } @Override public boolean isVirtualMachineUpgradable(UserVm vm, ServiceOffering offering) { // currently we do no special checks to rule out a VM being upgradable to an offering, so // return true return true; } protected List<HostVO> prioritizeHosts(VMTemplateVO template, List<HostVO> hosts) { if (template == null) { return hosts; } // Determine the guest OS category of the template String templateGuestOSCategory = getTemplateGuestOSCategory(template); List<HostVO> prioritizedHosts = new ArrayList<HostVO>(); // If a template requires HVM and a host doesn't support HVM, remove it from consideration List<HostVO> hostsToCheck = new ArrayList<HostVO>(); if (template.isRequiresHvm()) { for (HostVO host : hosts) { if (hostSupportsHVM(host)) { hostsToCheck.add(host); } } } else { hostsToCheck.addAll(hosts); } // If a host is tagged with the same guest OS category as the template, move it to a high priority list // If a host is tagged with a different guest OS category than the template, move it to a low priority list List<HostVO> highPriorityHosts = new ArrayList<HostVO>(); List<HostVO> lowPriorityHosts = new ArrayList<HostVO>(); for (HostVO host : hostsToCheck) { String hostGuestOSCategory = getHostGuestOSCategory(host); if (hostGuestOSCategory == null) { continue; } else if (templateGuestOSCategory.equals(hostGuestOSCategory)) { highPriorityHosts.add(host); } else { lowPriorityHosts.add(host); } } hostsToCheck.removeAll(highPriorityHosts); hostsToCheck.removeAll(lowPriorityHosts); // Prioritize the remaining hosts by HVM capability for (HostVO host : hostsToCheck) { if (!template.isRequiresHvm() && !hostSupportsHVM(host)) { // Host and template both do not support hvm, put it as first consideration prioritizedHosts.add(0, host); } else { // Template doesn't require hvm, but the machine supports it, make it last for consideration prioritizedHosts.add(host); } } // Merge the lists prioritizedHosts.addAll(0, highPriorityHosts); prioritizedHosts.addAll(lowPriorityHosts); return prioritizedHosts; } protected boolean hostSupportsHVM(HostVO host) { // Determine host capabilities String caps = host.getCapabilities(); if (caps != null) { String[] tokens = caps.split(","); for (String token : tokens) { if (token.contains("hvm")) { return true; } } } return false; } protected String getHostGuestOSCategory(HostVO host) { DetailVO hostDetail = _hostDetailsDao.findDetail(host.getId(), "guest.os.category.id"); if (hostDetail != null) { String guestOSCategoryIdString = hostDetail.getValue(); long guestOSCategoryId; try { guestOSCategoryId = Long.parseLong(guestOSCategoryIdString); } catch (Exception e) { return null; } GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(guestOSCategoryId); if (guestOSCategory != null) { return guestOSCategory.getName(); } else { return null; } } else { return null; } } protected String getTemplateGuestOSCategory(VMTemplateVO template) { long guestOSId = template.getGuestOSId(); GuestOSVO guestOS = _guestOSDao.findById(guestOSId); long guestOSCategoryId = guestOS.getCategoryId(); GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(guestOSCategoryId); return guestOSCategory.getName(); } @Override public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { _name = name; ComponentLocator locator = ComponentLocator.getCurrentLocator(); _hostDao = locator.getDao(HostDao.class); _hostDetailsDao = locator.getDao(DetailsDao.class); _vmDao = locator.getDao(UserVmDao.class); _offeringDao = locator.getDao(ServiceOfferingDao.class); _routerDao = locator.getDao(DomainRouterDao.class); _consoleProxyDao = locator.getDao(ConsoleProxyDao.class); _secStorgaeVmDao = locator.getDao(SecondaryStorageVmDao.class); _storagePoolHostDao = locator.getDao(StoragePoolHostDao.class); _configDao = locator.getDao(ConfigurationDao.class); _guestOSDao = locator.getDao(GuestOSDao.class); _guestOSCategoryDao = locator.getDao(GuestOSCategoryDao.class); if (_configDao != null) { Map<String, String> configs = _configDao.getConfiguration(params); String opFactor = configs.get("cpu.overprovisioning.factor"); _factor = NumbersUtil.parseFloat(opFactor, 1); //Over provisioning factor cannot be < 1. Reset to 1 in such cases if (_factor < 1){ _factor = 1; } String allocationAlgorithm = configs.get("vm.allocation.algorithm"); if (allocationAlgorithm != null && (allocationAlgorithm.equals("random") || allocationAlgorithm.equals("firstfit"))) { _allocationAlgorithm = allocationAlgorithm; } } return true; } @Override public String getName() { return _name; } @Override public boolean start() { return true; } @Override public boolean stop() { return true; } }