/** * 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.storage.allocator; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; 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.agent.manager.allocator.impl.FirstFitAllocator; import com.cloud.agent.api.to.DiskCharacteristicsTO; import com.cloud.capacity.CapacityVO; import com.cloud.capacity.dao.CapacityDao; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.host.Host; import com.cloud.service.ServiceOffering; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.ServiceOffering.GuestIpType; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.Inject; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.vm.State; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmCharacteristics; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import com.cloud.storage.StoragePoolVO; // // TODO // Rush to make LocalStoragePoolAllocator use static allocation status, we should revisit the overall // allocation process to make it more reliable in next release. The code put in here is pretty ugly // @Local(value=StoragePoolAllocator.class) public class LocalStoragePoolAllocator extends FirstFitStoragePoolAllocator { private static final Logger s_logger = Logger.getLogger(LocalStoragePoolAllocator.class); @Inject StoragePoolHostDao _poolHostDao; @Inject VMInstanceDao _vmInstanceDao; @Inject UserVmDao _vmDao; @Inject ServiceOfferingDao _offeringDao; @Inject CapacityDao _capacityDao; @Inject ConfigurationDao _configDao; HostAllocator _allocator = null; protected SearchBuilder<VMInstanceVO> VmsOnPoolSearch; private int _hoursToSkipStoppedVMs = 24; private int _secStorageVmRamSize = 1024; private int _proxyRamSize = 256; private int _routerRamSize = 128; @Override public boolean allocatorIsCorrectType(DiskCharacteristicsTO dskCh, VMInstanceVO vm, ServiceOffering offering) { return localStorageAllocationNeeded(dskCh, vm, offering); } @Override public StoragePool allocateToPool(DiskCharacteristicsTO dskCh, ServiceOffering offering, DataCenterVO dc, HostPodVO pod, Long clusterId, VMInstanceVO vm, VMTemplateVO template, Set<? extends StoragePool> avoid) { // Check that the allocator type is correct if (!allocatorIsCorrectType(dskCh, vm, offering)) { return null; } Set<StoragePool> myAvoids = new HashSet<StoragePool>(avoid); VmCharacteristics vmc = new VmCharacteristics(vm.getType()); StoragePool pool = null; while ((pool = super.allocateToPool(dskCh, offering, dc, pod, clusterId, vm, template, myAvoids)) != null) { myAvoids.add(pool); if (pool.getPoolType().isShared()) { return pool; } if (_allocator.allocateTo(vmc, offering, Host.Type.Routing, dc, pod, (StoragePoolVO)pool, template, new HashSet<Host>()) == null) { continue; } List<StoragePoolHostVO> hostsInSPool = _poolHostDao.listByPoolId(pool.getId()); assert(hostsInSPool.size() == 1) : "Local storage pool should be one host per pool"; StoragePoolHostVO spHost = hostsInSPool.get(0); SearchCriteria sc = VmsOnPoolSearch.create(); sc.setJoinParameters("volumeJoin", "poolId", pool.getId()); sc.setParameters("state", State.Expunging); List<Object[]> results = _vmInstanceDao.searchAll(sc, null); List<Long> vmsOnHost = new ArrayList<Long>(); for(Object[] row : results) { vmsOnHost.add(new Long(((BigInteger)row[0]).longValue())); } if(s_logger.isDebugEnabled()) { s_logger.debug("Found " + vmsOnHost.size() + " VM instances are alloacated at host " + spHost.getHostId() + " with local storage pool " + pool.getName()); for(Long vmId : vmsOnHost) s_logger.debug("VM " + vmId + " is allocated on host " + spHost.getHostId() + " with local storage pool " + pool.getName()); } if(hostHasCpuMemoryCapacity(spHost.getHostId(), vmsOnHost, vm)) return pool; s_logger.debug("Found pool " + pool.getId() + " but host doesn't fit."); } s_logger.debug("Unable to find storage pool to fit the vm"); return null; } private boolean hostHasCpuMemoryCapacity(long hostId, List<Long> vmOnHost, VMInstanceVO vm) { ServiceOffering so = null; if(vm.getType() == VirtualMachine.Type.User) { UserVmVO userVm = _vmDao.findById(vm.getId()); if (userVm != null) so = _offeringDao.findById(userVm.getServiceOfferingId()); } else if(vm.getType() == VirtualMachine.Type.ConsoleProxy) { so = new ServiceOfferingVO("Fake Offering For DomP", 1, _proxyRamSize, 0, 0, 0, false, null, GuestIpType.Virtualized, false, true, null); } else if(vm.getType() == VirtualMachine.Type.SecondaryStorageVm) { so = new ServiceOfferingVO("Fake Offering For Secondary Storage VM", 1, _secStorageVmRamSize, 0, 0, 0, false, null, GuestIpType.Virtualized, false, true, null); } else if(vm.getType() == VirtualMachine.Type.DomainRouter) { so = new ServiceOfferingVO("Fake Offering For DomR", 1, _routerRamSize, 0, 0, 0, false, null, GuestIpType.Virtualized, false, true, null); } else { assert(false) : "Unsupported system vm type"; so = new ServiceOfferingVO("Fake Offering For unknow system VM", 1, 128, 0, 0, 0, false, null, GuestIpType.Virtualized, false, true, null); } long usedMemory = calcHostAllocatedCpuMemoryCapacity(vmOnHost, CapacityVO.CAPACITY_TYPE_MEMORY); if(s_logger.isDebugEnabled()) s_logger.debug("Calculated static-allocated memory for VMs on host " + hostId + ": " + usedMemory + " bytes, requesting memory: " + (so != null ? so.getRamSize()*1024L*1024L : "") + " bytes"); SearchCriteria sc = _capacityDao.createSearchCriteria(); sc.addAnd("hostOrPoolId", SearchCriteria.Op.EQ, hostId); sc.addAnd("capacityType", SearchCriteria.Op.EQ, CapacityVO.CAPACITY_TYPE_MEMORY); List<CapacityVO> capacities = _capacityDao.search(sc, null); if(capacities.size() > 0) { if(capacities.get(0).getTotalCapacity() < usedMemory + (so != null ? so.getRamSize()* 1024L * 1024L : 0)) { if(s_logger.isDebugEnabled()) s_logger.debug("Host " + hostId + " runs out of memory capacity"); return false; } } else { s_logger.warn("Host " + hostId + " has not reported memory capacity yet"); return false; } long usedCpu = calcHostAllocatedCpuMemoryCapacity(vmOnHost, CapacityVO.CAPACITY_TYPE_CPU); if(s_logger.isDebugEnabled()) s_logger.debug("Calculated static-allocated CPU for VMs on host " + hostId + ": " + usedCpu + " GHz, requesting cpu: " + (so != null ? so.getCpu()*so.getSpeed() : "") + " GHz"); sc = _capacityDao.createSearchCriteria(); sc.addAnd("hostOrPoolId", SearchCriteria.Op.EQ, hostId); sc.addAnd("capacityType", SearchCriteria.Op.EQ, CapacityVO.CAPACITY_TYPE_CPU); capacities = _capacityDao.search(sc, null); if(capacities.size() > 0) { if(capacities.get(0).getTotalCapacity() < usedCpu + (so != null ? so.getCpu() * so.getSpeed() : 0)) { if(s_logger.isDebugEnabled()) s_logger.debug("Host " + hostId + " runs out of CPU capacity"); return false; } } else { s_logger.warn("Host " + hostId + " has not reported CPU capacity yet"); return false; } return true; } private boolean skipCalculation(VMInstanceVO vm) { if(vm == null) return true; if(vm.getState() == State.Expunging) { if(s_logger.isDebugEnabled()) s_logger.debug("Skip counting capacity for Expunging VM : " + vm.getInstanceName()); return true; } if(vm.getState() == State.Destroyed && vm.getType() != VirtualMachine.Type.User) return true; if(vm.getState() == State.Stopped || vm.getState() == State.Destroyed) { // for stopped/Destroyed VMs, we will skip counting it if it hasn't been used for a while long millisecondsSinceLastUpdate = DateUtil.currentGMTTime().getTime() - vm.getUpdateTime().getTime(); if(millisecondsSinceLastUpdate > _hoursToSkipStoppedVMs*3600000L) { if(s_logger.isDebugEnabled()) s_logger.debug("Skip counting vm " + vm.getInstanceName() + " in capacity allocation as it has been stopped for " + millisecondsSinceLastUpdate/60000 + " minutes"); return true; } } return false; } private long calcHostAllocatedCpuMemoryCapacity(List<Long> vmOnHost, short capacityType) { assert(capacityType == CapacityVO.CAPACITY_TYPE_MEMORY || capacityType == CapacityVO.CAPACITY_TYPE_CPU) : "Invalid capacity type passed in calcHostAllocatedCpuCapacity()"; long usedCapacity = 0; for (Long vmId : vmOnHost) { VMInstanceVO vm = _vmInstanceDao.findById(vmId); if(skipCalculation(vm)) continue; ServiceOffering so = null; if(vm.getType() == VirtualMachine.Type.User) { UserVmVO userVm = _vmDao.findById(vm.getId()); if (userVm == null) { continue; } so = _offeringDao.findById(userVm.getServiceOfferingId()); } else if(vm.getType() == VirtualMachine.Type.ConsoleProxy) { so = new ServiceOfferingVO("Fake Offering For DomP", 1, _proxyRamSize, 0, 0, 0, false, null, GuestIpType.Virtualized, false, true, null); } else if(vm.getType() == VirtualMachine.Type.SecondaryStorageVm) { so = new ServiceOfferingVO("Fake Offering For Secondary Storage VM", 1, _secStorageVmRamSize, 0, 0, 0, false, null, GuestIpType.Virtualized, false, true, null); } else if(vm.getType() == VirtualMachine.Type.DomainRouter) { so = new ServiceOfferingVO("Fake Offering For DomR", 1, _routerRamSize, 0, 0, 0, false, null, GuestIpType.Virtualized, false, true, null); } else { assert(false) : "Unsupported system vm type"; so = new ServiceOfferingVO("Fake Offering For unknow system VM", 1, 128, 0, 0, 0, false, null, GuestIpType.Virtualized, false, true, null); } if(capacityType == CapacityVO.CAPACITY_TYPE_MEMORY) { usedCapacity += so.getRamSize() * 1024L * 1024L; } else if(capacityType == CapacityVO.CAPACITY_TYPE_CPU) { usedCapacity += so.getCpu() * so.getSpeed(); } } return usedCapacity; } @Override public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { super.configure(name, params); _storageOverprovisioningFactor = 1; _extraBytesPerVolume = NumbersUtil.parseLong((String) params.get("extra.bytes.per.volume"), 50 * 1024L * 1024L); Map<String, String> configs = _configDao.getConfiguration("management-server", params); String value = configs.get("capacity.skipcounting.hours"); _hoursToSkipStoppedVMs = NumbersUtil.parseInt(value, 24); // TODO this is not good, there should be one place to get these values _secStorageVmRamSize = NumbersUtil.parseInt(configs.get("secstorage.vm.ram.size"), 256); _routerRamSize = NumbersUtil.parseInt(configs.get("router.ram.size"), 128); _proxyRamSize = NumbersUtil.parseInt(configs.get("consoleproxy.ram.size"), 1024); VmsOnPoolSearch = _vmInstanceDao.createSearchBuilder(); VmsOnPoolSearch.select(Func.DISTINCT, VmsOnPoolSearch.entity().getId()); VmsOnPoolSearch.and("removed", VmsOnPoolSearch.entity().getRemoved(), SearchCriteria.Op.NULL); VmsOnPoolSearch.and("state", VmsOnPoolSearch.entity().getState(), SearchCriteria.Op.NIN); SearchBuilder<VolumeVO> sbVolume = _volumeDao.createSearchBuilder(); sbVolume.and("poolId", sbVolume.entity().getPoolId(), SearchCriteria.Op.EQ); VmsOnPoolSearch.join("volumeJoin", sbVolume, VmsOnPoolSearch.entity().getId(), sbVolume.entity().getInstanceId()); sbVolume.done(); VmsOnPoolSearch.done(); _allocator = new FirstFitAllocator(); _allocator.configure("First fit", params); return true; } public LocalStoragePoolAllocator() { } }