package org.ovirt.engine.core.bll.network; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import javax.transaction.Transaction; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.context.CompensationContext; import org.ovirt.engine.core.bll.network.macpool.MacPool; import org.ovirt.engine.core.bll.network.macpool.ReadMacPool; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.errors.EngineError; import org.ovirt.engine.core.common.errors.EngineException; import org.ovirt.engine.core.common.osinfo.OsRepository; import org.ovirt.engine.core.common.utils.SimpleDependencyInjector; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableBase; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.network.VmNetworkInterfaceDao; import org.ovirt.engine.core.dao.network.VmNetworkStatisticsDao; import org.ovirt.engine.core.dao.network.VmNicDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helper class to use for adding/removing {@link VmNic}s. */ public class VmInterfaceManager { private Logger log = LoggerFactory.getLogger(getClass()); private MacPool macPool; public VmInterfaceManager() { } public VmInterfaceManager(MacPool macPool) { this.macPool = macPool; } /** * Add a {@link VmNic} to the VM. Allocates a MAC from the * {@link MacPool} if necessary, otherwise, if * {@code ConfigValues.HotPlugEnabled} is true, forces adding the MAC address to the * {@link MacPool}. If * HotPlug is not enabled tries to add the {@link VmNic}'s MAC address to the * {@link MacPool}, and throws a * {@link EngineException} if it fails. * * @param iface * The interface to save. * @param compensationContext * Used to snapshot the saved entities. * @param reserveExistingMac * Used to denote if we want to reserve the NIC's MAC address in the {@link MacPool} * @param assignNewMac * Used to denote if we want to assign a new MAC address from the {@link MacPool} to the NIC. Note that * <code>true</code> value in reserveExistingMac takes precedence over this one. * @param clusterCompatibilityVersion * the compatibility version of the cluster */ public void add(final VmNic iface, CompensationContext compensationContext, boolean reserveExistingMac, boolean assignNewMac, int osId, Version clusterCompatibilityVersion) { if (reserveExistingMac) { if (getOsRepository().hasNicHotplugSupport(osId, clusterCompatibilityVersion)) { macPool.forceAddMac(iface.getMacAddress()); } else if (!macPool.addMac(iface.getMacAddress())) { auditLogMacInUse(iface); throw new EngineException(EngineError.MAC_ADDRESS_IS_IN_USE); } } else if (assignNewMac) { final String macAddress = macPool.allocateNewMac(); iface.setMacAddress(macAddress); } getVmNicDao().save(iface); getVmNetworkStatisticsDao().save(iface.getStatistics()); compensationContext.snapshotNewEntity(iface); compensationContext.snapshotNewEntity(iface.getStatistics()); } public OsRepository getOsRepository() { return SimpleDependencyInjector.getInstance().get(OsRepository.class); } public void auditLogMacInUse(final VmNic iface) { TransactionSupport.executeInNewTransaction(() -> { AuditLogableBase logable = createAuditLog(iface); log(logable, AuditLogType.MAC_ADDRESS_IS_IN_USE); log.warn("Network Interface '{}' has MAC address '{}' which is in use, " + "therefore the action for VM '{}' failed.", iface.getName(), iface.getMacAddress(), iface.getVmId()); return null; }); } public void auditLogMacInUseUnplug(final VmNic iface, String vmName) { TransactionSupport.executeInNewTransaction(() -> { AuditLogableBase logable = createAuditLog(iface); logable.addCustomValue("VmName", vmName); log(logable, AuditLogType.MAC_ADDRESS_IS_IN_USE_UNPLUG); log.warn("Network Interface '{}' has MAC address '{}' which is in use, " + "therefore it is being unplugged from VM '{}'.", iface.getName(), iface.getMacAddress(), iface.getVmId()); return null; }); } /** * Remove all {@link VmNic}s from the VM, and remove the Mac addresses from {@link MacPool}. * * @param vmId * The ID of the VM to remove from. */ public void removeAll(Guid vmId) { List<VmNic> interfaces = getVmNicDao().getAllForVm(vmId); if (interfaces != null) { removeFromExternalNetworks(interfaces); for (VmNic iface : interfaces) { macPool.freeMac(iface.getMacAddress()); getVmNicDao().remove(iface.getId()); getVmNetworkStatisticsDao().remove(iface.getId()); } } } protected void removeFromExternalNetworks(List<VmNic> interfaces) { Transaction transaction = TransactionSupport.suspend(); for (VmNic iface : interfaces) { new ExternalNetworkManager(iface).deallocateIfExternal(); } TransactionSupport.resume(transaction); } /*** * Returns whether or not there are too many network interfaces with * the same MAC address as the given interface plugged in * * @param iface * the network interface with MAC address * @return <code>true</code> if the MAC is used by too many other plugged network interface, * <code>false</code> otherwise. */ public boolean tooManyPluggedInterfaceWithSameMac(VmNic iface, ReadMacPool readMacPool) { return !readMacPool.isDuplicateMacAddressesAllowed() && existsPluggedInterfaceWithSameMac(iface); } /*** * Returns whether or not there is a plugged network interface with the same MAC address as the given interface * * @param interfaceToPlug * the network interface that needs to be plugged * @return <code>true</code> if the MAC is used by another plugged network interface, <code>false</code> otherwise. */ private boolean existsPluggedInterfaceWithSameMac(VmNic interfaceToPlug) { List<VmNic> vmNetworkIntrefaces = getVmNicDao().getPluggedForMac(interfaceToPlug.getMacAddress()); for (VmNic vmNetworkInterface : vmNetworkIntrefaces) { if (!interfaceToPlug.getId().equals(vmNetworkInterface.getId())) { return true; } } return false; } /** * Sorts the list of NICs. The comparison is done either via PCI address, MAC address, or name, depending * on the information we have available * * @param nics * The list of NICs to sort * @param vmInterfaceDevices * The device information we have on those NICs */ public void sortVmNics(List<? extends VmNic> nics, final Map<Guid, VmDevice> vmInterfaceDevices) { Collections.sort(nics, new Comparator<VmNic>() { @Override public int compare(VmNic nic1, VmNic nic2) { if (vmInterfaceDevices != null) { // If both devices have a PCI address then we compare by it // Otherwise if both devices have a MAC address then we compare by it // Otherwise we compare by name VmDevice nic1Device = vmInterfaceDevices.get(nic1.getId()); VmDevice nic2Device = vmInterfaceDevices.get(nic2.getId()); if (nic1Device != null && nic2Device != null) { if (StringUtils.isNotEmpty(nic1Device.getAddress()) && StringUtils.isNotEmpty(nic2Device.getAddress())) { return nic1Device.getAddress().compareTo(nic2Device.getAddress()); } } } if (StringUtils.isNotEmpty(nic1.getMacAddress()) && StringUtils.isNotEmpty(nic2.getMacAddress())) { return nic1.getMacAddress().compareTo(nic2.getMacAddress()); } return nic1.getName().compareTo(nic2.getName()); } }); } /** * Log the given loggable & message to the {@link AuditLogDirector}. */ private void log(AuditLogableBase logable, AuditLogType auditLogType) { getAuditLogDirector().log(logable, auditLogType); } AuditLogDirector getAuditLogDirector() { return new AuditLogDirector(); } protected VmNetworkStatisticsDao getVmNetworkStatisticsDao() { return DbFacade.getInstance().getVmNetworkStatisticsDao(); } protected VmNetworkInterfaceDao getVmNetworkInterfaceDao() { return DbFacade.getInstance().getVmNetworkInterfaceDao(); } protected VmNicDao getVmNicDao() { return DbFacade.getInstance().getVmNicDao(); } protected VmDao getVmDao() { return DbFacade.getInstance().getVmDao(); } private AuditLogableBase createAuditLog(final VmNic iface) { AuditLogableBase logable = new AuditLogableBase(); logable.addCustomValue("MACAddr", iface.getMacAddress()); logable.addCustomValue("IfaceName", iface.getName()); return logable; } }