/**
* 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.xenserver;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.abiquo.vsm.events.VMEvent;
import com.abiquo.vsm.events.VMEventType;
import com.abiquo.vsm.exception.MonitorException;
import com.abiquo.vsm.model.PhysicalMachine;
import com.abiquo.vsm.model.VirtualMachinesCache;
import com.abiquo.vsm.monitor.AbstractMonitor;
import com.abiquo.vsm.monitor.Monitor;
import com.abiquo.vsm.monitor.Monitor.Type;
import com.abiquo.vsm.monitor.executor.AbstractTask;
import com.abiquo.vsm.monitor.executor.PeriodicalExecutor;
import com.xensource.xenapi.Types;
import com.xensource.xenapi.VM;
/**
* The XenServer monitor.
*
* @author destevez
*/
@Monitor(type = Type.XENSERVER)
public class XenServerMonitor extends AbstractMonitor
{
/** The logger. */
private final static Logger LOGGER = LoggerFactory.getLogger(XenServerMonitor.class);
/** Maximum number of machines this monitor can manage. */
public static final int MAX_MONITORED_MACHINES = 1;
/** Manages connections to XenServer hypervisor. */
private XenServerConnector connector;
// Polling stuff
/** The executor */
private PeriodicalExecutor executor;
/** The poller */
private Poller poller;
/**
* Creates the <code>XenServerMonitor</code>.
*/
public XenServerMonitor()
{
poller = new Poller();
connector = new XenServerConnector();
executor = createExecutor(poller, Poller.POLLING_INTERVAL);
}
@Override
public int getMaxNumberOfHypervisors()
{
return MAX_MONITORED_MACHINES;
}
@Override
public void start()
{
LOGGER.debug("Starting XenServer monitor");
executor.start();
}
@Override
public void shutdown()
{
String physicalmachines = StringUtils.join(monitoredMachines, ", ");
LOGGER.debug("Stopping XenServer monitor for: {}", physicalmachines);
executor.stop();
}
@Override
public void publishState(final String physicalMachineAddress, final String virtualMachineName)
throws MonitorException
{
super.publishState(physicalMachineAddress, virtualMachineName);
Types.VmPowerState powerState = null;
PhysicalMachine pm = getPhysicalMachine(physicalMachineAddress);
connector.connect(pm.getAddress(), pm.getUsername(), pm.getPassword());
try
{
// Connect to the hypervisor
powerState = connector.getState(virtualMachineName);
}
finally
{
if (connector.getConnection() != null)
{
connector.disconnect();
}
}
this.notify(new VMEvent(XenServerUtils.translateEvent(powerState),
physicalMachineAddress,
virtualMachineName));
}
/**
* Creates and initializes the {@link PeriodicalExecutor}.
*
* @param poller The poller to be executed.
* @param pollInterval The polling execution interval.
* @return The PeriodicalExecutor.
*/
private PeriodicalExecutor createExecutor(final Poller poller, final int pollInterval)
{
return new PeriodicalExecutor(poller, pollInterval)
{
@Override
public void executionFailure(final Throwable t)
{
LOGGER.trace("An unexpected error occured while performing monitor tasks", t);
}
};
}
/**
* Performs synchronous polling calls to get the state of the monitored virtual machines.
*
* @author ibarrera
*/
private class Poller extends AbstractTask
{
/** The polling interval. */
public static final int POLLING_INTERVAL = 5000;
@Override
public void execute() throws Exception
{
// It is important to synchronize the monitoredMachines list to avoid errors if a
// machine is added or removed while iterating the list
synchronized (monitoredMachines)
{
for (String physicalMachineAddress : monitoredMachines)
{
LOGGER.trace("Getting information from: {}", physicalMachineAddress);
PhysicalMachine pm = getPhysicalMachine(physicalMachineAddress);
VirtualMachinesCache cache = pm.getVirtualMachines();
connector.connect(physicalMachineAddress, pm.getUsername(), pm.getPassword());
// Current VMs in the hypervisor. Used to detect CREATE and DESTROY events
Set<String> currentVMs = new HashSet<String>();
try
{
LOGGER.trace("Getting information from the current VMS...");
// Get states
List<VM.Record> vms = connector.getAllVMs();
for (VM.Record vm : vms)
{
// Save the VM in the list of current VMs
String vmName = vm.nameLabel;
currentVMs.add(vmName);
if (connector.isBeingRebooted(vm))
{
// When XenServer reboots a VM, its state is always RUNNING
// If we detect a reboot, just invalidate the last known state, so
// we can notify the power on when reboot finishes
LOGGER.trace(
"VM {} is being rebooted. Invalidating last known state",
vmName);
invalidateLastKnownState(physicalMachineAddress, vmName);
}
else
{
// Get the new state of the VM
VMEventType state = XenServerUtils.translateEvent(vm.powerState);
LOGGER.trace("Found VM {} in state {}", vmName, state.name());
VMEvent event = new VMEvent(state, physicalMachineAddress, vmName);
// Propagate the event. RedisSubscriber will decide if it must be
// notified, based on subscription information
XenServerMonitor.this.notify(event);
}
}
if (LOGGER.isTraceEnabled())
{
String cacheStr = StringUtils.join(cache.getCache(), ", ");
LOGGER.trace(
"Cache for Machine {} before generating CREATE/DESTROY is: {}",
physicalMachineAddress, cacheStr);
}
// Propagate create and destroy events
propagateCreateAndDestroyEvents(pm, currentVMs);
// Update the physical machine with the current machines in the hypervisor
cache.getCache().clear();
cache.getCache().addAll(currentVMs);
if (LOGGER.isTraceEnabled())
{
String cacheStr = StringUtils.join(cache.getCache(), ", ");
LOGGER.trace(
"Cache for Machine {} after generating CREATE/DESTROY is: {}",
physicalMachineAddress, cacheStr);
}
}
finally
{
connector.disconnect();
}
}
}
}
/**
* Propagates events for each created and destroyed virtual machine.
*
* @param pm The physical machine being monitored.
* @param currentVMs The current virtual machines in the hypervisor.
*/
private void propagateCreateAndDestroyEvents(final PhysicalMachine pm,
final Set<String> currentVMs)
{
// Propagate DESTROY events
Set<String> removedVMs = pm.getVirtualMachines().getCache();
removedVMs.removeAll(currentVMs);
for (String removed : removedVMs)
{
VMEvent event = new VMEvent(VMEventType.DESTROYED, pm.getAddress(), removed);
LOGGER.trace("Removed VM {} from {}", removed, pm.getAddress());
XenServerMonitor.this.notify(event);
}
// Propagate CREATE events
Set<String> createdVMs = new HashSet<String>(currentVMs);
createdVMs.removeAll(pm.getVirtualMachines().getCache());
for (String created : createdVMs)
{
VMEvent event = new VMEvent(VMEventType.CREATED, pm.getAddress(), created);
LOGGER.trace("New VM {} at {}", created, pm.getAddress());
XenServerMonitor.this.notify(event);
}
}
}
}