/** * Abiquo community edition * cloud management application for hybrid clouds * Copyright (C) 2008-2010 - Abiquo Holdings S.L. * * This application is free software; you can redistribute it and/or * modify it under the terms of the GNU LESSER GENERAL PUBLIC * LICENSE as published by the Free Software Foundation under * version 3 of the License * * This software 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 * LESSER GENERAL PUBLIC LICENSE v.3 for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package com.abiquo.api.services; import java.util.List; import javax.jms.ResourceAllocationException; import javax.persistence.EntityManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.abiquo.api.exceptions.APIError; import com.abiquo.api.exceptions.ConflictException; import com.abiquo.model.enumerator.FitPolicy; import com.abiquo.model.enumerator.MachineState; import com.abiquo.model.transport.error.CommonError; import com.abiquo.scheduler.ResourceUpgradeUse; import com.abiquo.scheduler.ResourceUpgradeUseException; import com.abiquo.scheduler.SchedulerLock; import com.abiquo.scheduler.VirtualMachineFactory; import com.abiquo.scheduler.VirtualMachineRequirementsFactory; import com.abiquo.scheduler.check.IMachineCheck; import com.abiquo.scheduler.check.MachineCheck; import com.abiquo.scheduler.limit.EnterpriseLimitChecker; import com.abiquo.scheduler.limit.LimitExceededException; import com.abiquo.scheduler.workload.AllocatorException; import com.abiquo.scheduler.workload.NotEnoughResourcesException; import com.abiquo.scheduler.workload.VirtualimageAllocationService; import com.abiquo.server.core.cloud.VirtualAppliance; import com.abiquo.server.core.cloud.VirtualApplianceDAO; import com.abiquo.server.core.cloud.VirtualMachine; import com.abiquo.server.core.cloud.VirtualMachineDAO; import com.abiquo.server.core.cloud.VirtualMachineRep; import com.abiquo.server.core.infrastructure.Machine; import com.abiquo.server.core.infrastructure.Rack; import com.abiquo.server.core.infrastructure.UcsRack; import com.abiquo.server.core.scheduler.VirtualMachineRequirements; /** * Selects the target machine to allocate a virtual machines. * <p> * Virtual machine requirements are defined on the virtual image and additional storage or network * configurations. * <p> * Before select the machine check if the current allowed limits are exceeded. * <p> * Enterprise edition support the definition of affinity, exclusion and work load rules. * * @author apuig */ @Service public class VirtualMachineAllocatorService extends DefaultApiService { protected final static Logger LOG = LoggerFactory .getLogger(VirtualMachineAllocatorService.class); @Autowired private VirtualMachineRequirementsFactory vmRequirements; @Autowired private VirtualApplianceDAO virtualAppDao; @Autowired protected VirtualimageAllocationService allocationService; @Autowired protected VirtualMachineFactory vmFactory; @Autowired protected IMachineCheck machineChecker; @Autowired protected VirtualMachineDAO virtualMachineDao; @Autowired protected EnterpriseLimitChecker checkEnterpirse; @Autowired protected ResourceUpgradeUse upgradeUse; @Autowired protected InfrastructureService infrastructureService; @Autowired protected VirtualMachineRep vmRepo; public VirtualMachineAllocatorService() { } public VirtualMachineAllocatorService(final EntityManager em) { this.virtualAppDao = new VirtualApplianceDAO(em); this.allocationService = new VirtualimageAllocationService(em); this.vmFactory = new VirtualMachineFactory(em); this.machineChecker = new MachineCheck(); this.virtualMachineDao = new VirtualMachineDAO(em); this.checkEnterpirse = new EnterpriseLimitChecker(em); this.upgradeUse = new ResourceUpgradeUse(em); this.vmRequirements = new VirtualMachineRequirementsFactory(); this.vmRepo = new VirtualMachineRep(em); } /** * Check if we can allocate the new virtual machine according to the new values. * * @param idVirtualApp * @param vmachine * @param newvmachine * @param foreceEnterpriseSoftLimits */ @Transactional(readOnly = true, propagation = Propagation.REQUIRED) public void checkAllocate(final Integer idVirtualApp, final VirtualMachine vmachine, final VirtualMachineRequirements increaseRequirements, final boolean foreceEnterpriseSoftLimits) { final VirtualAppliance vapp = virtualAppDao.findById(idVirtualApp); final Machine machine = vmachine.getHypervisor().getMachine(); if (vmachine.getHypervisor() == null || vmachine.getHypervisor().getMachine() == null) { addConflictErrors(APIError.CHECK_EDIT_NO_TARGET_MACHINE); flushErrors(); } try { checkLimist(vapp, increaseRequirements, foreceEnterpriseSoftLimits, true); boolean check = allocationService.checkVirtualMachineResourceIncrease(machine, increaseRequirements, idVirtualApp); if (!check) { final String cause = String.format("Current workload rules (RAM and CPU oversubscription) " + "on the target machine: %s disallow the required resources increment.", machine.getName()); throw new AllocatorException(cause); } upgradeUse.updateUsed(vmachine.getHypervisor().getMachine(), vmachine.getDatastore(), increaseRequirements); upgradeUse.updateNetworkingResources(vmachine.getHypervisor().getMachine(), vmachine, vapp); } catch (NotEnoughResourcesException e) { addConflictErrors(createErrorWithExceptionDetails(APIError.NOT_ENOUGH_RESOURCES, vmachine, e)); } catch (LimitExceededException limite) { if (limite.isHardLimit()) { addConflictErrors(new CommonError(APIError.LIMIT_EXCEEDED.name(), limite.toString())); } else { addConflictErrors(new CommonError(APIError.SOFT_LIMIT_EXCEEDED.name(), limite.toString())); } } catch (AllocatorException e) { addConflictErrors(createErrorWithExceptionDetails(APIError.ALLOCATOR_ERROR, vmachine, e)); } catch (Exception e) { addUnexpectedErrors(createErrorWithExceptionDetails(APIError.ALLOCATOR_ERROR, vmachine, e)); } finally { flushErrors(); } } /** * Test entrance */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) protected VirtualMachine allocate(final Integer vmid, final Integer vapid, final Boolean foreceEnterpriseSoftLimits) { return allocateVirtualMachine(virtualMachineDao.findById(vmid), virtualAppDao.findById(vapid), foreceEnterpriseSoftLimits); } @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void deallocate(final Integer vmid) { deallocateVirtualMachine(virtualMachineDao.findById(vmid)); } /** * Creates a virtual machine using some hypervisor on the current virtual appliance datacenter. * <p> * Physical Infrastructure synchronized. @see {@link SchedulerLock} * * @param targetImage, target vmtemplate to deploy (virtual machine template). Determine basic * resource utilization (CPU, RAM, HD) (additionally repository utilization) * @param resources, additional resources configurations to be added on the virtual machine * @param user, the user performing the virtual machine creation. * @param virtualAppId, the virtual appliance id requiring this virtual machine (contains * information about the target {@link VirtualDataCenterHB}). * @param foreceEnterpriseSoftLimits, indicating if the virtual appliance should be started even * when the soft limit is exceeded. if false and the soft limit is reached a * {@link SoftLimitExceededException} is thrown, otherwise generate a EVENT TRACE. * @return a Virtual Machine based on the provided virtual machine template on some (best) * machine. * @throws AllocatorException, direct subclasses {@link HardLimitExceededException} when the * current virtual machine template requirements will exceed the total allowed * resource reservation, {@link SoftLimitExceededException} on * ''foreceEnterpriseSoftLimits'' = false and the soft limit is exceeded. * @throws ResourceAllocationException, if none of the machines on the datacenter can be used to * perform this operation. */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public VirtualMachine allocateVirtualMachine(final VirtualMachine vmachine, final VirtualAppliance vapp, final Boolean foreceEnterpriseSoftLimits) { try { final VirtualMachineRequirements requirements = vmRequirements.createVirtualMachineRequirements(vmachine); final Integer idDatacenter = vapp.getVirtualDatacenter().getDatacenter().getId(); final FitPolicy fitPolicy = getAllocationFitPolicyOnDatacenter(idDatacenter); checkLimist(vapp, requirements, foreceEnterpriseSoftLimits, false); VirtualMachine allocatedvm = selectPhysicalMachineAndAllocateResources(vmachine, vapp, fitPolicy, requirements); // vmdao.detachVirtualMachine(vmachine); return allocatedvm; } catch (NotEnoughResourcesException e) { addConflictErrors(createErrorWithExceptionDetails(APIError.NOT_ENOUGH_RESOURCES, vmachine, e)); } catch (LimitExceededException limite) { if (limite.isHardLimit()) { addConflictErrors(new CommonError(APIError.LIMIT_EXCEEDED.name(), limite.toString())); } else { addConflictErrors(new CommonError(APIError.SOFT_LIMIT_EXCEEDED.name(), limite.toString())); } } catch (AllocatorException e) { addConflictErrors(createErrorWithExceptionDetails(APIError.ALLOCATOR_ERROR, vmachine, e)); } catch (Exception e) { addUnexpectedErrors(createErrorWithExceptionDetails(APIError.ALLOCATOR_ERROR, vmachine, e)); } finally { flushErrors(); } return null; // unreachable code } @Transactional(readOnly = true, propagation = Propagation.REQUIRED) private VirtualMachine selectPhysicalMachineAndAllocateResources(final VirtualMachine vmachine, final VirtualAppliance vapp, final FitPolicy fitPolicy, final VirtualMachineRequirements requirements) { Machine targetMachine = allocationService.findBestTarget(requirements, fitPolicy, vapp); LOG.info("Attempt to use physical machine [{}] to allocate VirtualMachine [{}]", targetMachine.getName(), vmachine.getName()); // CREATE THE VIRTUAL MACHINE VirtualMachine allocatedVirtualMachine = vmFactory.createVirtualMachine(targetMachine, vmachine); try { upgradeUse.updateUse(vapp, allocatedVirtualMachine); } catch (ResourceUpgradeUseException e) // TODO with this error no other machine candidate { APIError error = APIError.NOT_ENOUGH_RESOURCES; error.addCause(String.format("%s\n%s", virtualMachineInfo(vmachine), e.getMessage())); addConflictErrors(error); } finally { flushErrors(); } return allocatedVirtualMachine; } /** * <p> * .DO NOT perform any resource limitation check (Enterprise, VDC or DC). As the original * virtual machine running on the original hypervisor will be deallocated one the hypervisor can * be reached. * * @param, vmachineId, an already allocated virtual machine (hypervisor and datastore are set) * but we wants to move it. */ @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public VirtualMachine allocateHAVirtualMachine(final VirtualMachine vmId, final VirtualMachineRequirements requirements) throws AllocatorException, ResourceAllocationException { LOG.error("community can't *allocateHAVirtualMachine*"); return null; } @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void updateResourcesUsageOnTargetMachine(final VirtualMachine virtualMachine, final Machine machine) throws ResourceAllocationException { LOG.error("community can't *updateResourcesUsageOnTargetMachine*"); } protected CommonError createErrorWithExceptionDetails(final APIError apiError, final VirtualMachine virtualMachine, final Exception e) { final String msg = String.format("%s (%s)\n%s", apiError.getMessage(), virtualMachineInfo(virtualMachine), e.getMessage()); return new CommonError(apiError.getCode(), msg); } /** * Roll back the changes on the target physical machine after the virtual machine is destroyed * (of excluded by some exception on the virtual machine creation on the hypervisor). * <p> * Physical Infrastructure synchronized. @see {@link SchedulerLock} * * @param machine, the target machine holding the virtual machine to be undeployed. * @throws AllocationException, it there are some problem updating the physical machine */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void deallocateVirtualMachine(final VirtualMachine vmachine) { try { if (vmachine.isManaged()) { upgradeUse.rollbackUse(vmachine); } else { vmRepo.deleteVirtualMachine(vmachine); } } catch (ResourceUpgradeUseException e) { APIError error = APIError.NOT_ENOUGH_RESOURCES; error.addCause(String.format("%s\n%s", virtualMachineInfo(vmachine), e.getMessage())); addConflictErrors(error); } finally { flushErrors(); } } @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void deallocateVirtualMachineOnSourceMachine(final VirtualMachine vmachine, final Machine machine) throws AllocatorException, ResourceAllocationException { LOG.error("community can't *deallocateHAVirtualMachine*"); } protected String virtualMachineInfo(final VirtualMachine vm) { return String.format("Virtual Machine id:%d name:%s UUID:%s.", vm.getId(), vm.getName(), vm.getUuid()); } /** ##### CHECK LIMITS ###### */ /** * Check the current allowed Enterprise resource utilization is not exceeded. Overloaded method * because en case of deploying VM is not necessary check VLAN limits. * <p> * The checks are performed on the specified order. Enterprise limits are higher than Datacenter * limits, and Datacenter higher than VirtualDatacenter (this requirement is satisfied on the * Limit creation) * * @param vapp, the target virtual appliance. * @param required, the required resources. * @param force, if false the soft limits can not be exceeded (throws an * SoftLimitExceededException) otherwise only be traced if reached. * @throws ResourceAllocationException, (NotEnoughResources) if there aren't any machine to full * fits the requirements. * @throws LimitExceededException, it the allowed resources are exceeded. * {@link HardLimitExceededException} when the current virtual machine template * requirements will exceed the total allowed resource reservation, * {@link SoftLimitExceededException} on ''foreceEnterpriseSoftLimits'' = false and * the soft limit is exceeded. */ @Transactional(readOnly = true, propagation = Propagation.REQUIRED) public void checkLimist(final VirtualAppliance vapp, final VirtualMachineRequirements required, final Boolean force) { try { checkLimist(vapp, required, force, false); } catch (LimitExceededException limite) { if (limite.isHardLimit()) { addConflictErrors(new CommonError(APIError.LIMIT_EXCEEDED.name(), limite.toString())); } else { addConflictErrors(new CommonError(APIError.SOFT_LIMIT_EXCEEDED.name(), limite.toString())); } } catch (Exception e) { addUnexpectedErrors(new CommonError(APIError.ALLOCATOR_ERROR.name(), e.toString())); } finally { flushErrors(); } } /** * @param vapp * @param required * @param force * @param checkVLAN * @throws LimitExceededException */ protected void checkLimist(final VirtualAppliance vapp, final VirtualMachineRequirements required, final Boolean force, final Boolean checkVLAN) throws LimitExceededException { checkEnterpirse.checkLimits(vapp.getEnterprise(), required, force, checkVLAN, false); } /** * Gets the {@link FitPolicy} of the current datacenter. * * @param idDatacenter, fit policy is based at datacenter level. * @return the default ''Global'' Fit Policy (for any Datacenter). */ protected FitPolicy getAllocationFitPolicyOnDatacenter(final Integer idDatacenter) { return FitPolicy.PROGRESSIVE; // community fix the fit policy } /** * We check how many empty machines are in a rack. Then we power on or off to fit the * configuration. In 2.0 only in {@link UcsRack}. This method is not transactional since there * is no need to perform rollback. All of the functions that made changes to the database are * marked as REQUIRES_NEW. * * @param targetMachine machine we are deploy void * @since 2.0 */ public void adjustPoweredMachinesInRack(final Rack rack) { if (!(rack instanceof UcsRack)) { LOG.debug("We can only adjust max machines on in UCS"); return; } Integer max = ((UcsRack) rack).getMaxMachinesOn(); if (max == null || max == 0) { LOG.debug("Max machines on feature is disabled for rack: {}", rack.getId()); return; } Integer emptyMachinesOn = this.allocationService.getEmptyOnMachines(rack.getId()); if (max > emptyMachinesOn) { LOG.debug("Not enough machines on rack: {} should be {} but there are {}", new Object[] {rack.getId(), max, emptyMachinesOn}); int howMany = max - emptyMachinesOn; List<Machine> machines = this.allocationService.getRandomMachinesToStartFromRack(rack.getId(), howMany); if (machines != null && !machines.isEmpty()) { LOG.debug("Requesting {} machines to boot , retrieved {}", new Object[] {howMany, machines.size()}); powerOnMachine(machines); return; } LOG.debug("There are no machines available to start up on rack: {}", rack.getId()); } else if (max < emptyMachinesOn) { // If there is more than one machine to power off LOG.debug("Too many machines rack: {} should be {} but there are {}", new Object[] { rack.getId(), max, emptyMachinesOn}); int howMany = emptyMachinesOn - max; List<Machine> machines = this.allocationService.getAllEmptyOnMachines(rack.getId()); if (machines != null && !machines.isEmpty()) { LOG.debug("Requesting {} machines to shut , retrieved {}", new Object[] {howMany, machines.size()}); shutDownMachines(machines, howMany); return; } LOG.debug("There are no machines available to shut down on rack: {}", rack.getId()); } else { LOG.debug("Enough machines rack: {}", rack.getId()); } } /** * There are special requirements for this method to be ok. Most of those prerequisites can not * be satisfied by Abiquo. The machine must be associated with a Logic Server in UCS. * * @see com.abiquo.scheduler.Allocator#powerOnMachine(java.util.List) */ protected void powerOnMachine(final List<Machine> machines) { LOG.debug("Starting {} machines", machines.size()); for (Machine machine : machines) { try { infrastructureService.powerOn(machine.getId()); } catch (Exception e) { LOG.error( "Could not power on the machine id {} name {} the error: {}: {}", new Object[] {machine.getId(), machine.getName(), e.getClass().getName(), e.getMessage()}); } } } /** * There are special requirements for this method to be ok. Most of those prerequisites can not * be satisfied by Abiquo. The machine must be associated with a Logic Server in UCS. Since we * do not persist the Logic Server we need to try to shutdown all. * * @see com.abiquo.scheduler.Allocator#shutDownMachines(java.util.List) */ protected void shutDownMachines(final List<Machine> machines, int howMany) { LOG.debug("Stopping {} machines", howMany); for (Machine machine : machines) { // Since we don't know in advance which are associated we must try all of them // Even if we precalculate since there is many options of changing the UCS actual status // does not guarantees if (howMany <= 0) { LOG.debug( "shutDownMachines UCS not machines need to shutdown, it should be 0 and is {}", howMany); return; } try { infrastructureService.powerOff(machine.getId(), MachineState.HALTED_FOR_SAVE); howMany--; } catch (ConflictException e) { LOG.error( "Could not power off the machine id {} name {} the error conflict error: {}: {}", new Object[] {machine.getId(), machine.getName(), e.getErrors(), e.getMessage()}); } catch (Exception e) { LOG.error( "Could not power off the machine id {} name {} the error: {}: {}", new Object[] {machine.getId(), machine.getName(), e.getClass().getName(), e.getMessage()}); } } if (howMany > 0) { LOG.error( "Could not power off all of the required machines, {} left to power off. Either empty or with service profile associated!", howMany); } } }