/**
* 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.scheduler;
import java.io.StringReader;
import java.rmi.NoSuchObjectException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import javax.persistence.EntityManager;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.apache.wink.common.internal.providers.entity.csv.CsvReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.abiquo.api.services.InfrastructureService;
import com.abiquo.model.enumerator.FitPolicy;
import com.abiquo.model.enumerator.NetworkType;
import com.abiquo.scheduler.workload.NotEnoughResourcesException;
import com.abiquo.scheduler.workload.VirtualimageAllocationService;
import com.abiquo.server.core.cloud.HypervisorDAO;
import com.abiquo.server.core.cloud.VirtualAppliance;
import com.abiquo.server.core.cloud.VirtualDatacenter;
import com.abiquo.server.core.cloud.VirtualMachine;
import com.abiquo.server.core.cloud.VirtualMachineState;
import com.abiquo.server.core.infrastructure.Datastore;
import com.abiquo.server.core.infrastructure.DatastoreDAO;
import com.abiquo.server.core.infrastructure.Machine;
import com.abiquo.server.core.infrastructure.Rack;
import com.abiquo.server.core.infrastructure.management.Rasd;
import com.abiquo.server.core.infrastructure.network.IpPoolManagement;
import com.abiquo.server.core.infrastructure.network.IpPoolManagementDAO;
import com.abiquo.server.core.infrastructure.network.NetworkAssignment;
import com.abiquo.server.core.infrastructure.network.NetworkAssignmentDAO;
import com.abiquo.server.core.infrastructure.network.VLANNetwork;
import com.abiquo.server.core.infrastructure.network.VLANNetworkDAO;
import com.abiquo.server.core.infrastructure.storage.DiskManagement;
import com.abiquo.server.core.scheduler.FitPolicyRule;
import com.abiquo.server.core.scheduler.FitPolicyRuleDAO;
import com.abiquo.server.core.scheduler.VirtualMachineRequirements;
/**
* Updates the following resource.
* <ul>
* <li>hardware resource (ram, cpu) on the physical machine</li>
* <li>datastore utilization (hd) on the physical machine.</li>
* <li>network resources on the virtual datacenter</li>
* </ul>
*/
@Component
public class ResourceUpgradeUse implements IResourceUpgradeUse
{
private final static Logger log = LoggerFactory.getLogger(ResourceUpgradeUse.class);
@Autowired
private DatastoreDAO datastoreDao;
@Autowired
private NetworkAssignmentDAO netAssignDao;
@Autowired
private IpPoolManagementDAO ipPoolManDao;
@Autowired
private VLANNetworkDAO vlanNetworkDao;
@Autowired
private HypervisorDAO hypervisorDao;
@Autowired
private VirtualimageAllocationService allocationService;
@Autowired
private InfrastructureService infrastructureService;
@Autowired
private FitPolicyRuleDAO fitPolicyDao;
public ResourceUpgradeUse()
{
}
// For Testign purposes only
public ResourceUpgradeUse(final EntityManager em)
{
this.datastoreDao = new DatastoreDAO(em);
this.netAssignDao = new NetworkAssignmentDAO(em);
this.ipPoolManDao = new IpPoolManagementDAO(em);
this.vlanNetworkDao = new VLANNetworkDAO(em);
this.hypervisorDao = new HypervisorDAO(em);
this.fitPolicyDao = new FitPolicyRuleDAO(em);
}
/**
* @throws ResourceUpgradeUseException, if the operation can be performed: there isn't enough
* resources to allocate the virtual machine, the virtual appliances is not on any
* virtual datacenter.
*/
// @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
@Override
public void updateUse(final VirtualAppliance virtualAppliance,
final VirtualMachine virtualMachine) throws ResourceUpgradeUseException
{
updateUse(virtualAppliance, virtualMachine, false);
}
@Override
public void updateUseHa(final VirtualMachine virtualMachine, final Machine machine)
{
try
{
updateUsagePhysicalMachine(machine, virtualMachine, false);
updateNewtorkingResourcesHA(machine, virtualMachine);
}
catch (final Exception e)
// HibernateException NotEnoughResourcesException NoSuchObjectException
{
e.printStackTrace(); // FIXME
throw new ResourceUpgradeUseException("Can not update resource utilization: "
+ e.getMessage());
}
}
private void updateUse(final VirtualAppliance virtualAppliance,
final VirtualMachine virtualMachine, final boolean isHA)
{
if (virtualMachine.getHypervisor() == null
|| virtualMachine.getHypervisor().getMachine() == null)
{
throw new ResourceUpgradeUseException("Virtual machine is not allocated on any hypervisor / machine");
}
Machine physicalMachine = virtualMachine.getHypervisor().getMachine();
try
{
updateUsagePhysicalMachine(physicalMachine, virtualMachine, false);
if (!isHA)
{
updateUsageDatastore(virtualMachine, false);
updateNetworkingResources(physicalMachine, virtualMachine, virtualAppliance);
virtualMachine.setState(VirtualMachineState.LOCKED); // FIXME
}
else
{
updateNewtorkingResourcesHA(physicalMachine, virtualMachine);
}
}
catch (final ConstraintViolationException cve)
{
final StringBuilder msg = new StringBuilder("Invalid database mapping, caused by:");
for (ConstraintViolation< ? > cv : cve.getConstraintViolations())
{
msg.append(String.format("\nEnitity :%s Property :%s InvalidValue :%s\n%s", cv
.getRootBeanClass().getName(), cv.getPropertyPath().toString(), cv
.getInvalidValue(), cv.getMessage()));
}
cve.printStackTrace(); // FIXME
throw new ResourceUpgradeUseException(msg.toString());
}
catch (final NotEnoughResourcesException e)
{
// throwed by the 'updatedNetworkResources' method.
throw e;
}
catch (final Exception e)
// HibernateException NotEnoughResourcesException NoSuchObjectException
{
e.printStackTrace(); // FIXME
throw new ResourceUpgradeUseException("Can not update resource utilization: "
+ e.getMessage());
}
}
@Override
public void rollbackUseHA(final VirtualMachine virtualMachine, final Machine machine)
{
try
{
// Virtual Machines in HA are in shared datastores, so there is no need to update usage.
// The disk is still there no matter what the hypervisor is
updateUsagePhysicalMachine(machine, virtualMachine, true);
}
catch (final Exception e)
{
throw new ResourceUpgradeUseException("Can not update resource use" + e.getMessage());
}
if (machine != null
&& getAllocationFitPolicyOnDatacenter(machine.getDatacenter().getId()).equals(
FitPolicy.PROGRESSIVE))
{
infrastructureService.adjustPoweredMachinesInRack(machine.getRack());
}
}
@Override
public void rollbackUse(final VirtualMachine virtualMachine)
{
final Machine physicalMachine = virtualMachine.getHypervisor().getMachine();
try
{
updateUsageDatastore(virtualMachine, true);
updateUsagePhysicalMachine(physicalMachine, virtualMachine, true);
rollbackNetworkingResources(physicalMachine, virtualMachine);
// TODO Ignasi must review this
virtualMachine.setVdrpIP(null);
virtualMachine.setVdrpPort(0);
virtualMachine.setHypervisor(null);
virtualMachine.setDatastore(null);
virtualMachine.setState(VirtualMachineState.NOT_ALLOCATED);
}
catch (final Exception e)
{
throw new ResourceUpgradeUseException("Can not update resource use" + e.getMessage());
}
if (physicalMachine != null
&& getAllocationFitPolicyOnDatacenter(physicalMachine.getDatacenter().getId()).equals(
FitPolicy.PROGRESSIVE))
{
infrastructureService.adjustPoweredMachinesInRack(physicalMachine.getRack());
}
}
/**
* Updates the ''vswitch'' of the RASD associated to the current Virtual Machine's
* IpPoolManagements.
*/
private void updateNewtorkingResourcesHA(final Machine haPhysicalTarget,
final VirtualMachine virtualMachine) throws NotEnoughResourcesException,
NoSuchObjectException
{
final String vswitch = haPhysicalTarget.getVirtualSwitch();
List<IpPoolManagement> ippoolManagementList =
ipPoolManDao.findIpsByVirtualMachine(virtualMachine);
log.debug("Update the vswitch to {}", vswitch);
for (final IpPoolManagement ipPoolManagement : ippoolManagementList)
{
// already assigned VLAN TAG if (vlanNetwork.getTag() == null)
Rasd rasd = ipPoolManagement.getRasd();
rasd.setConnection(vswitch);
}// iterate over VlanNetwork
}
/**
* Updates the networking resources
*
* @param session the session
* @param virtualMachine the virtual machine
* @param physicalTarget the physical target
* @param virtualApplianceId
* @throws NotEnoughResourcesException
* @throws NoSuchObjectException
*/
public void updateNetworkingResources(final Machine physicalTarget,
final VirtualMachine virtualMachine, final VirtualAppliance vapp)
throws NotEnoughResourcesException, NoSuchObjectException
{
// virtualMachine = vmachineDao.findById(virtualMachine.getId());// XXX
final VirtualDatacenter virtualDatacenter = vapp.getVirtualDatacenter();
final List<NetworkAssignment> networksAssignedList =
netAssignDao.findByVirtualDatacenter(virtualDatacenter);
List<IpPoolManagement> ippoolManagementList = virtualMachine.getIps();
// ipPoolManDao.findIpsByVirtualMachine(virtualMachine);
for (final IpPoolManagement ipPoolManagement : ippoolManagementList)
{
// Get the network and the rack, entities that perform the network assignment.
VLANNetwork vlanNetwork = ipPoolManagement.getVlanNetwork();
Rack rack = physicalTarget.getRack();
// Discover the tag of the vlan if it is the first address to be deployed
if (vlanNetwork.getTag() == null)
{
List<VLANNetwork> publicVLANs =
vlanNetworkDao.findPublicVLANNetworksByDatacenter(rack.getDatacenter(), null);
List<Integer> vlanTagsUsed = vlanNetworkDao.getVLANTagsUsedInRack(rack);
vlanTagsUsed.addAll(getPublicVLANTagsFROMVLANNetworkList(publicVLANs));
Integer freeTag = getFreeVLANFromUsedList(vlanTagsUsed, rack);
log.debug("The VLAN tag chosen for the vlan network: {} is : {}",
vlanNetwork.getId(), freeTag);
vlanNetwork.setTag(freeTag);
}
final NetworkAssignment nb =
new NetworkAssignment(virtualDatacenter, rack, vlanNetwork);
if (!networksAssignedList.contains(nb))
{
netAssignDao.persist(nb);
}
}
}
/**
* Rollback networking resources
*
* @param session the session
* @param virtualMachine the virtual machine
* @param physicalTarget the physical machine
*/
private void rollbackNetworkingResources(final Machine physicalTarget,
final VirtualMachine virtualMachine)
{
List<IpPoolManagement> ippoolManagementList =
ipPoolManDao.findIpsByVirtualMachine(virtualMachine);
for (final IpPoolManagement ipPoolManagement : ippoolManagementList)
{
VLANNetwork vlanNetwork = ipPoolManagement.getVlanNetwork();
final boolean assigned =
ipPoolManDao.isVlanAssignedToDifferentVM(virtualMachine.getId(), vlanNetwork);
if (!assigned)
{
if (vlanNetwork.getType().equals(NetworkType.INTERNAL))
{
vlanNetwork.setTag(null);
}
NetworkAssignment na = netAssignDao.findByVlanNetwork(vlanNetwork);
if (na != null)
{
netAssignDao.remove(na);
}
}
}
}
/**
* Set the resource usage on PhysicalMachine after instantiating the new VirtualMachine. It
* access DB throw Hibernate.
*
* @param machine, the machine to reduce/increase its resource capacity.
* @param used, the VirtualMachine requirements to substract/add.
* @param isAdd, true if reducing the amount of resources on the PhysicalMachine. Else it adds
* capacity (as a rollback on VirtualMachineTemplate deploy Exception).
*/
public void updateUsagePhysicalMachine(final Machine machine, final VirtualMachine used,
final boolean isRollback)
{
final int newCpu =
isRollback ? machine.getVirtualCpusUsed() - used.getCpu() : machine
.getVirtualCpusUsed() + used.getCpu();
final int newRam =
isRollback ? machine.getVirtualRamUsedInMb() - used.getRam() : machine
.getVirtualRamUsedInMb() + used.getRam();
if (used.getVirtualMachineTemplate().isStateful())
{
used.setHdInBytes(0l); // stateful virtual machine templatess doesn't use the datastores
}
// prevent to set negative usage
machine.setVirtualCpusUsed(newCpu >= 0 ? newCpu : 0);
machine.setVirtualRamUsedInMb(newRam >= 0 ? newRam : 0);
}
@Override
public void updateUsed(final Machine machine, final Datastore datastore,
final VirtualMachineRequirements requirements)
{
machine.setVirtualCpusUsed((int) (machine.getVirtualCpusUsed() + requirements.getCpu()));
machine.setVirtualRamUsedInMb((int) (machine.getVirtualRamUsedInMb() + requirements
.getRam()));
datastore.setUsedSize(datastore.getUsedSize() + requirements.getHd());
}
/**
* Updates the datastore with the used size by the virtual machine (if a shared datastore update
* all its references).
*
* @param virtual the virtual machine that contains the datastore to update
* @param session the hibernate session
*/
private void updateUsageDatastore(final VirtualMachine virtual, final boolean isRollback)
{
Long requestedSize;
if (!virtual.getVirtualMachineTemplate().isStateful())
{
requestedSize = virtual.getHdInBytes();
requestedSize += getDisksSizeInBytes(virtual);
}
else
{
// Stateful vmtemplate doesn't update the datastore utilization, except it has got hard
// disk.
requestedSize = getDisksSizeInBytes(virtual);
if (requestedSize.equals(0))
{
return;
}
}
Datastore datastore = virtual.getDatastore();
// updates the datastore utilization for all the shared datastores.
List<Datastore> datastoresShared = datastoreDao.findShares(datastore);
for (Datastore dstore : datastoresShared)
{
updateDatastore(dstore, requestedSize, isRollback);
}
}
private void updateDatastore(final Datastore datastore, final Long requestSize,
final boolean isRollback)
{
final Long actualSize = datastore.getUsedSize();
final Long newUsed = isRollback ? actualSize - requestSize : actualSize + requestSize;
if (newUsed > datastore.getSize())
{
log.error("Target datastore usage is over capacity !!!!! datastore : %s",
datastore.getName());
}
datastore.setUsedSize(newUsed >= 0 ? newUsed : 0); // prevent negative usage
}
/**
* Gets a free VLAN from the list used VLAN
*
* @param rack
* @param vlan ports
* @return
* @throws SchedulerException
*/
public Integer getFreeVLANFromUsedList(final List<Integer> vlanIds, final Rack rack)
throws NotEnoughResourcesException
{
// Adding Vlans Id not to add
vlanIds.addAll(getVlansIdAvoidAsCollection(rack));
// Create a HashSet which allows no duplicates
HashSet<Integer> hashSet = new HashSet<Integer>(vlanIds);
// Assign the HashSet to a new ArrayList
List<Integer> vlanIdsOrdered = new ArrayList<Integer>(hashSet);
Collections.sort(vlanIdsOrdered);
List<Integer> vlanTemp = new ArrayList<Integer>(vlanIdsOrdered);
// Removing used port groups minor than minId and bigger then maxId
// (that could be only public and external networks with tags ouside the
// minid-maxid range)
for (Integer vlanId : vlanTemp)
{
if (vlanId.intValue() < rack.getVlanIdMin())
{
vlanIdsOrdered.remove(vlanId);
}
}
for (Integer i = rack.getVlanIdMin(); i <= rack.getVlanIdMax(); i++)
{
if (!vlanIdsOrdered.contains(i))
{
return i;
}
}
throw new NotEnoughResourcesException("The maximun number of VLAN tag has been reached");
}
public Collection<Integer> getVlansIdAvoidAsCollection(final Rack rack)
{
Collection<Integer> vlans_avoided_collection = new HashSet<Integer>();
String avoidedVLANs = rack.getVlansIdAvoided();
if (avoidedVLANs == null || avoidedVLANs.isEmpty())
{
return vlans_avoided_collection;
}
CsvReader reader = new CsvReader(new StringReader(avoidedVLANs));
String[] line = reader.readLine();
if (line != null)
{
try
{
for (String vlan_id : line)
{
if (vlan_id.split("-").length > 1)
{
String[] interval = vlan_id.split("-");
Integer min = Integer.valueOf(interval[0]);
Integer max = Integer.valueOf(interval[1]);
if (min.compareTo(max) > 0)
{
Integer temp = max;
max = min;
min = temp;
}
else
{
for (int i = min; i <= max; i++)
{
if (i > rack.getVlanIdMin() || i < rack.getVlanIdMax())
{
vlans_avoided_collection.add(i);
}
}
}
}
else
{
Integer vlanIdCandidate = Integer.valueOf(vlan_id);
if (vlanIdCandidate > rack.getVlanIdMin()
|| vlanIdCandidate < rack.getVlanIdMax())
{
vlans_avoided_collection.add(vlanIdCandidate);
}
}
}
}
catch (NumberFormatException e)
{
log.debug("Ignoring not recognize vlan's id", e);
}
}
return vlans_avoided_collection;
}
public List<Integer> getPublicVLANTagsFROMVLANNetworkList(
final List<VLANNetwork> vlanNetworkList)
{
List<Integer> publicTagsList = new ArrayList<Integer>();
for (VLANNetwork vlanNetwork : vlanNetworkList)
{
publicTagsList.add(vlanNetwork.getTag());
}
return publicTagsList;
}
/**
* By datacenter
*/
protected FitPolicy getAllocationFitPolicyOnDatacenter(final Integer idDatacenter)
{
FitPolicyRule fit = fitPolicyDao.getFitPolicyForDatacenter(idDatacenter);
return fit != null ? fit.getFitPolicy() : fitPolicyDao.getGlobalFitPolicy().getFitPolicy();
}
private long getDisksSizeInBytes(final VirtualMachine vm)
{
long size = 0;
if (vm.getDisks() != null && !vm.getDisks().isEmpty())
{
for (DiskManagement disk : vm.getDisks())
{
// bytes
size += disk.getSizeInMb() * 1024 * 1024;
}
}
return size;
}
}