/** * 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.vsm.monitor; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.abiquo.vsm.VSMManager; import com.abiquo.vsm.events.VMEventType; import com.abiquo.vsm.exception.MonitorException; import com.abiquo.vsm.model.PhysicalMachine; import com.abiquo.vsm.model.VirtualMachine; import com.abiquo.vsm.model.VirtualMachinesCache; import com.abiquo.vsm.monitor.Monitor.Type; import com.abiquo.vsm.monitor.esxi.ExecutorBasedESXiPoller; import com.abiquo.vsm.monitor.hyperv.HyperVMonitor; import com.abiquo.vsm.monitor.libvirt.KVMMonitor; import com.abiquo.vsm.monitor.libvirt.XenMonitor; import com.abiquo.vsm.monitor.vbox.VirtualBoxMonitor; import com.abiquo.vsm.monitor.xenserver.XenServerMonitor; import com.abiquo.vsm.redis.dao.RedisDao; import com.abiquo.vsm.redis.dao.RedisDaoFactory; /** * Manages physical machine monitor life cycle. * * @author ibarrera */ public class MonitorManager { /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(MonitorManager.class); /** The list of available monitors, indexed by type. */ protected Map<Type, Class< ? extends AbstractMonitor>> monitorClasses; /** The monitors that are running. */ private Map<Type, List<AbstractMonitor>> runningMonitors; /** The dao used to access stored data. */ protected RedisDao dao; /** * Creates the <code>MonitorManager</code> and registers all the monitors. */ public MonitorManager() { super(); monitorClasses = new HashMap<Type, Class< ? extends AbstractMonitor>>(); runningMonitors = new HashMap<Type, List<AbstractMonitor>>(); dao = RedisDaoFactory.getInstance(); // Register monitors registerMonitor(KVMMonitor.class); registerMonitor(XenMonitor.class); registerMonitor(ExecutorBasedESXiPoller.class); registerMonitor(HyperVMonitor.class); registerMonitor(XenServerMonitor.class); registerMonitor(VirtualBoxMonitor.class); } /** * Start monitoring the target physical machine. * <p> * Creates the monitor or reuses an existing one to monitor the target physical machine. * * @param physicalMachineAddress The address of the physical machine to monitor. * @param type The hypervisor type of the physical machine. * @param username The user name used to connect to the physical machine. * @param password The password used to connect to the physical machine. * @return The physical machine details. * @throws MonitorException If the monitor cannot be created. */ public PhysicalMachine monitor(final String physicalMachineAddress, final Type type, final String username, final String password) throws MonitorException { PhysicalMachine pm = null; try { pm = dao.findPhysicalMachineByAddress(physicalMachineAddress); if (pm != null) { LOGGER.debug("The physical machine at " + physicalMachineAddress + " is already monitored."); return pm; } VirtualMachinesCache cache = new VirtualMachinesCache(); dao.save(cache); pm = new PhysicalMachine(); pm.setAddress(physicalMachineAddress); pm.setVirtualMachines(cache); pm.setUsername(username); pm.setPassword(password); pm.setType(type.name()); dao.save(pm); } catch (Exception ex) { throw new MonitorException("The physical machine at " + physicalMachineAddress + " could not be added to the list of monitored machines", ex); } try { createAndStartMonitor(physicalMachineAddress, type, username, password); } catch (MonitorException e) { dao.delete(pm); throw e; } return pm; } /** * Creates and starts a new monitor, wihtout persisting it in the Redis database. * <p> * this method will be invoked by the {@link VSMManager} in the startup process to reload the * existing monitors. * * @param physicalMachineAddress The address of the physical machine to monitor. * @param type The hypervisor type of the physical machine. * @param username The user name used to connect to the physical machine. * @param password The password used to connect to the physical machine. * @throws MonitorException If the monitor cannot be created. */ public void createAndStartMonitor(final String physicalMachineAddress, final Type type, final String username, final String password) throws MonitorException { LOGGER.info("Start monitoring {} of type {}", physicalMachineAddress, type.name()); // Get the monitor AbstractMonitor monitor = findAvailableMonitor(type); // Create and start a new one if necessary if (monitor == null) { monitor = createMonitor(type); monitor.start(); // Once the monitor has started without errors, add it to the list // of running monitors addRunningMonitor(monitor, type); } // Start monitoring the target machine monitor.addPhysicalMachine(physicalMachineAddress); } /** * Stop monitoring the target physical machine. * * @param physicalMachineAddress The address of the physical machine to stop monitoring. * @param type The hypervisor type of the physical machine. * @throws MonitorException If the shutdown operation cannot be performed. */ public void shutdown(final String physicalMachineAddress, final Type type) throws MonitorException { AbstractMonitor monitor = findRunningMonitor(physicalMachineAddress, type); if (monitor != null) { LOGGER.info("Shutting down monitor for: {}", physicalMachineAddress); monitor.removePhysicalMachine(physicalMachineAddress); removeRunningMonitor(monitor, type); // Remove machine from db PhysicalMachine pm = getPhysicalMachine(physicalMachineAddress); dao.delete(pm); } else { LOGGER.info("No running monitor found for: {}", physicalMachineAddress); } } /** * Shutdown all running monitors. * * @throws MonitorException If the shutdown operation fails. */ public void stopAllMonitors() { for (List<AbstractMonitor> monitors : runningMonitors.values()) { for (AbstractMonitor monitor : monitors) { monitor.shutdown(); } } } /** * Add a subscription for a virtual machine events. * * @param physicalMachineAddress The address of the physical machine where the virtual machine * is deployed. * @param type The hypervisor type of the physical machine. * @param virtualMachineName The virtual machine to subscribe to. * @return The subscription details. * @throws MonitorException If the subscription cannot be performed. */ public VirtualMachine subscribe(final String physicalMachineAddress, final Type type, final String virtualMachineName) throws MonitorException { LOGGER.info("Subscribing to {} on {}", virtualMachineName, physicalMachineAddress); PhysicalMachine pm = getPhysicalMachine(physicalMachineAddress); if (dao.findVirtualMachineByName(virtualMachineName) != null) { throw new MonitorException("Virtual machine " + virtualMachineName + " already persisted."); } VirtualMachine vm = new VirtualMachine(); vm.setName(virtualMachineName); vm.setPhysicalMachine(pm); vm.setLastKnownState(VMEventType.UNKNOWN.name()); dao.save(vm); return vm; } /** * Remove a subscription for a virtual machine. * * @param physicalMachineAddress The address of the physical machine where the virtual machine * is deployed. * @param type The hypervisor type of the physical machine. * @param virtualMachineName The virtual machine to unsubscribe from. * @throws MonitorException If the unsubscription cannot be performed. */ public void unsubscribe(final String physicalMachineAddress, final Type type, final String virtualMachineName) throws MonitorException { LOGGER.info("Unsubscribing from {} on {}", virtualMachineName, physicalMachineAddress); VirtualMachine vm = dao.findVirtualMachineByName(virtualMachineName); if (vm == null) { throw new MonitorException("There is no subscription for virtual machine " + virtualMachineName); } dao.delete(vm); } /** * Get the state of the given virtual machine. * * @param physicalMachineAddress The address of the physical machine where the virtual machine * is deployed. * @param type The hypervisor type of the physical machine. * @param virtualMachineName The virtual machine to get the state from. * @throws MonitorException If the state cannot be retrieved. */ public void getState(final String physicalMachineAddress, final Type type, final String virtualMachineName) throws MonitorException { AbstractMonitor monitor = findRunningMonitor(physicalMachineAddress, type); if (monitor != null) { LOGGER.info("Getting state of virtual machine {} on {}", virtualMachineName, physicalMachineAddress); monitor.publishState(physicalMachineAddress, virtualMachineName); } else { throw new MonitorException(String.format("Machine %s doesn't have a running monitor", physicalMachineAddress)); } } /** * invalidate the last known state of the given virtual machine. * * @param physicalMachineAddress The address of the physical machine where the virtual machine * is deployed. * @param type The hypervisor type of the physical machine. * @param virtualMachineName The virtual machine to invalidate the last known state from. * @throws MonitorException If the slast known tate cannot be invalidated. */ public void invalidateLastKnownState(final String physicalMachineAddress, final Type type, final String virtualMachineName) throws MonitorException { AbstractMonitor monitor = findRunningMonitor(physicalMachineAddress, type); if (monitor != null) { LOGGER.info("Invalidating last known state of virtual machine {} on {}", virtualMachineName, physicalMachineAddress); monitor.invalidateLastKnownState(physicalMachineAddress, virtualMachineName); } else { throw new MonitorException(String.format("Machine %s doesn't have a running monitor", physicalMachineAddress)); } } /** * Find a running monitor with free slots for the given hypervisor type. Return * <code>null</code> if no monitors are available. * * @param type The type of the hypervisor to monitor. * @return A monitor with available slots, or <code>null</code> if none is available. */ protected AbstractMonitor findAvailableMonitor(final Type type) { List< ? extends AbstractMonitor> candidates = runningMonitors.get(type); if (candidates != null) { for (AbstractMonitor monitor : candidates) { if (monitor.hasAvailableSlots()) { return monitor; } } } return null; } /** * Find a running monitor that monitors the given physical machine. * * @param physicalMachineAddress The address of the physical machine monitored by the monitor to * find. * @param type The type of the hypervisor to monitor. * @return The monitor that monitors the given physical machine. */ protected AbstractMonitor findRunningMonitor(final String physicalMachineAddress, final Type type) { List< ? extends AbstractMonitor> candidates = runningMonitors.get(type); if (candidates != null) { for (AbstractMonitor monitor : candidates) { if (monitor.monitors(physicalMachineAddress)) { return monitor; } } } return null; } /** * Instantiates a monitor for the given type of hypervisor. * * @param type The type of the hypervisor to monitor. * @return The monitor. * @throws MonitorException If the monitor cannot be created. */ protected AbstractMonitor createMonitor(final Type type) throws MonitorException { try { Class< ? extends AbstractMonitor> monitorClass = monitorClasses.get(type); if (monitorClass != null) { LOGGER.info("Creating a new monitor of type {}", type.name()); return monitorClass.newInstance(); } throw new MonitorException("There is no monitor defined of type: " + type.name()); } catch (Exception ex) { throw new MonitorException("There is no monitor defined of type: " + type.name(), ex); } } /** * Adds the given monitor class to the list of available monitors. * * @param monitorClass The monitor class to add. */ protected void registerMonitor(final Class< ? extends AbstractMonitor> monitorClass) { Monitor config = monitorClass.getAnnotation(Monitor.class); if (config == null) { LOGGER.warn("Ignoring monitor: {}", monitorClass.getName()); } else { LOGGER.info("Adding {} monitor: {}", config.type(), monitorClass.getName()); monitorClasses.put(config.type(), monitorClass); } } /** * Adds the given monitor to the list of running monitors. * * @param monitor The monitor to add. * @param type The type of the hypervisor that the monitor monitors. */ private void addRunningMonitor(final AbstractMonitor monitor, final Type type) { List<AbstractMonitor> currentMonitors = runningMonitors.get(type); if (currentMonitors == null) { currentMonitors = new LinkedList<AbstractMonitor>(); } currentMonitors.add(monitor); runningMonitors.put(type, currentMonitors); } /** * Removes the given monitor from the list of running monitors. * * @param monitor The monitor to remove. * @param type The type of the hypervisor that the monitor monitors. */ private void removeRunningMonitor(final AbstractMonitor monitor, final Type type) { List<AbstractMonitor> currentMonitors = runningMonitors.get(type); if (currentMonitors != null && currentMonitors.contains(monitor) && monitor.getMonitoredMachines().isEmpty()) { currentMonitors.remove(monitor); runningMonitors.put(type, currentMonitors); } } /** * Get the given physical machine from the database. * * @param physicalMachineAddress The address of the physical machine. * @return The physical machine. * @throws MonitorException If the physical machine is not found. */ private PhysicalMachine getPhysicalMachine(final String physicalMachineAddress) throws MonitorException { PhysicalMachine pm = dao.findPhysicalMachineByAddress(physicalMachineAddress); if (pm == null) { throw new MonitorException("The physical machine at " + physicalMachineAddress + " is not being monitored"); } return pm; } }