/**
* 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
import org.apache.log4j.Logger;
import com.cloud.agent.manager.allocator.PodAllocator;
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.dc.dao.HostPodDao;
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.VirtualMachineTemplate;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VMTemplateHostDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.DateUtil;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.component.Inject;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.State;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
@Local(value=PodAllocator.class)
public class UserConcentratedAllocator implements PodAllocator {
private final static Logger s_logger = Logger.getLogger(UserConcentratedAllocator.class);
String _name;
@Inject UserVmDao _vmDao;
@Inject VolumeDao _volumeDao;
@Inject HostPodDao _podDao;
@Inject VMTemplateHostDao _templateHostDao;
@Inject VMTemplatePoolDao _templatePoolDao;
@Inject ServiceOfferingDao _offeringDao;
@Inject CapacityDao _capacityDao;
@Inject ConfigurationDao _configDao;
@Inject VMInstanceDao _vmInstanceDao;
Random _rand = new Random(System.currentTimeMillis());
private final GlobalLock m_capacityCheckLock = GlobalLock.getInternLock("capacity.check");
private int _hoursToSkipStoppedVMs = 24;
private int _secStorageVmRamSize = 1024;
private int _proxyRamSize = 256;
private int _routerRamSize = 128;
@Override
public Pair<HostPodVO, Long> allocateTo(VirtualMachineTemplate template, ServiceOfferingVO offering, DataCenterVO zone, long accountId, Set<Long> avoids) {
long zoneId = zone.getId();
List<HostPodVO> podsInZone = _podDao.listByDataCenterId(zoneId);
if (podsInZone.size() == 0) {
s_logger.debug("No pods found in zone " + zone.getName());
return null;
}
// Find pods that have enough CPU/memory capacity
List<HostPodVO> availablePods = new ArrayList<HostPodVO>();
Map<Long, Long> podHostCandidates = new HashMap<Long, Long>();
for (HostPodVO pod: podsInZone) {
long podId = pod.getId();
if (!avoids.contains(podId)) {
if (template != null && !templateAvailableInPod(template.getId(), pod.getDataCenterId(), podId)) {
continue;
}
if (offering != null) {
// test for enough memory in the pod (make sure to check for enough memory for the service offering, plus some extra padding for xen overhead
long[] hostCandiates = new long[1];
boolean enoughCapacity = dataCenterAndPodHasEnoughCapacity(zoneId, podId, (offering.getRamSize()) * 1024L * 1024L, CapacityVO.CAPACITY_TYPE_MEMORY, hostCandiates);
if (!enoughCapacity) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Not enough RAM available in zone/pod to allocate storage for user VM (zone: " + zoneId + ", pod: " + podId + ")");
}
continue;
}
// test for enough CPU in the pod
enoughCapacity = dataCenterAndPodHasEnoughCapacity(zoneId, podId, (offering.getCpu()*offering.getSpeed()), CapacityVO.CAPACITY_TYPE_CPU, hostCandiates);
if (!enoughCapacity) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Not enough cpu available in zone/pod to allocate storage for user VM (zone: " + zoneId + ", pod: " + podId + ")");
}
continue;
}
podHostCandidates.put(podId, hostCandiates[0]);
}
// If the pod has VMs or volumes in it, return this pod
List<UserVmVO> vmsInPod = _vmDao.listByAccountAndPod(accountId, pod.getId());
if (!vmsInPod.isEmpty()) {
return new Pair<HostPodVO, Long>(pod, podHostCandidates.get(podId));
}
List<VolumeVO> volumesInPod = _volumeDao.findByAccountAndPod(accountId, pod.getId());
if (!volumesInPod.isEmpty()) {
return new Pair<HostPodVO, Long>(pod, podHostCandidates.get(podId));
}
availablePods.add(pod);
}
}
if (availablePods.size() == 0) {
s_logger.debug("There are no pods with enough memory/CPU capacity in zone " + zone.getName());
return null;
} else {
// Return a random pod
int next = _rand.nextInt(availablePods.size());
HostPodVO selectedPod = availablePods.get(next);
s_logger.debug("Found pod " + selectedPod.getName() + " in zone " + zone.getName());
return new Pair<HostPodVO, Long>(selectedPod, podHostCandidates.get(selectedPod.getId()));
}
}
private boolean dataCenterAndPodHasEnoughCapacity(long dataCenterId, long podId, long capacityNeeded, short capacityType, long[] hostCandidate) {
List<CapacityVO> capacities = null;
SearchCriteria sc = _capacityDao.createSearchCriteria();
sc.addAnd("capacityType", SearchCriteria.Op.EQ, capacityType);
sc.addAnd("dataCenterId", SearchCriteria.Op.EQ, dataCenterId);
sc.addAnd("podId", SearchCriteria.Op.EQ, podId);
s_logger.trace("Executing search");
capacities = _capacityDao.search(sc, null);
s_logger.trace("Done with search");
boolean enoughCapacity = false;
if (capacities != null) {
for (CapacityVO capacity : capacities) {
if(capacityType == CapacityVO.CAPACITY_TYPE_CPU || capacityType == CapacityVO.CAPACITY_TYPE_MEMORY) {
//
// for CPU/Memory, we now switch to static allocation
//
if ((capacity.getTotalCapacity() -
calcHostAllocatedCpuMemoryCapacity(capacity.getHostOrPoolId(), capacityType)) >= capacityNeeded) {
hostCandidate[0] = capacity.getHostOrPoolId();
enoughCapacity = true;
break;
}
} else {
if ((capacity.getTotalCapacity() - capacity.getUsedCapacity()) >= capacityNeeded) {
hostCandidate[0] = capacity.getHostOrPoolId();
enoughCapacity = true;
break;
}
}
}
}
return enoughCapacity;
}
private boolean skipCalculation(VMInstanceVO vm) {
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;
}
/**
*
* @param hostId Host id to calculate against
* @param capacityType CapacityVO.CAPACITY_TYPE_MEMORY or CapacityVO.CAPACITY_TYPE_CPU
* @return
*/
private long calcHostAllocatedCpuMemoryCapacity(long hostId, short capacityType) {
assert(capacityType == CapacityVO.CAPACITY_TYPE_MEMORY || capacityType == CapacityVO.CAPACITY_TYPE_CPU) : "Invalid capacity type passed in calcHostAllocatedCpuCapacity()";
List<VMInstanceVO> vms = _vmInstanceDao.listByLastHostId(hostId);
long usedCapacity = 0;
for (VMInstanceVO vm : vms) {
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;
if (s_logger.isDebugEnabled()) {
s_logger.debug("Counting memory capacity used by vm: " + vm.getId() + ", size: " + so.getRamSize() + "MB, host: " + hostId + ", currently counted: " + usedCapacity + " Bytes");
}
} else if(capacityType == CapacityVO.CAPACITY_TYPE_CPU) {
usedCapacity += so.getCpu() * so.getSpeed();
if (s_logger.isDebugEnabled()) {
s_logger.debug("Counting cpu capacity used by vm: " + vm.getId() + ", cpu: " + so.getCpu() + ", speed: " + so.getSpeed() + ", currently counted: " + usedCapacity + " Bytes");
}
}
}
return usedCapacity;
}
private boolean templateAvailableInPod(long templateId, long dcId, long podId) {
return true;
/*
List<VMTemplateHostVO> thvoList = _templateHostDao.listByTemplateStatus(templateId, dcId, podId, Status.DOWNLOADED);
List<VMTemplateStoragePoolVO> tpvoList = _templatePoolDao.listByTemplateStatus(templateId, dcId, podId, Status.DOWNLOADED);
if (thvoList != null && thvoList.size() > 0) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Found " + thvoList.size() + " storage hosts in pod " + podId + " with template " + templateId);
}
return true;
} else if (tpvoList != null && tpvoList.size() > 0) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Found " + tpvoList.size() + " storage pools in pod " + podId + " with template " + templateId);
}
return true;
}else {
return false;
}
*/
}
@Override
public String getName() {
return _name;
}
@Override
public boolean start() {
return true;
}
@Override
public boolean stop() {
return true;
}
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
_name = name;
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);
/*
ComponentLocator locator = ComponentLocator.getCurrentLocator();
_vmDao = locator.getDao(UserVmDao.class);
if (_vmDao == null) {
throw new ConfigurationException("Unable to find UserVMDao.");
}
_volumeDao = locator.getDao(VolumeDao.class);
if (_volumeDao == null) {
throw new ConfigurationException("Unable to find VolumeDao.");
}
_templateHostDao = locator.getDao(VMTemplateHostDao.class);
if (_templateHostDao == null) {
throw new ConfigurationException("Unable to get template host dao.");
}
_templatePoolDao = locator.getDao(VMTemplatePoolDao.class);
if (_templatePoolDao == null) {
throw new ConfigurationException("Unable to get template pool dao.");
}
_podDao = locator.getDao(HostPodDao.class);
if (_podDao == null) {
throw new ConfigurationException("Unable to find HostPodDao.");
}
_capacityDao = locator.getDao(CapacityDao.class);
if (_capacityDao == null) {
throw new ConfigurationException("Unable to retrieve " + CapacityDao.class);
}
*/
return true;
}
}