/**
* 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.esxi;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.abiquo.vsm.events.VMEvent;
import com.abiquo.vsm.events.VMEventType;
import com.abiquo.vsm.model.PhysicalMachine;
import com.abiquo.vsm.monitor.AbstractMonitor;
import com.abiquo.vsm.monitor.esxi.util.ExtendedAppUtil;
import com.abiquo.vsm.redis.dao.RedisDao;
import com.abiquo.vsm.redis.dao.RedisDaoFactory;
import com.vmware.vim25.ArrayOfEvent;
import com.vmware.vim25.Event;
import com.vmware.vim25.EventFilterSpec;
import com.vmware.vim25.EventFilterSpecByEntity;
import com.vmware.vim25.EventFilterSpecRecursionOption;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.ObjectSpec;
import com.vmware.vim25.ObjectUpdate;
import com.vmware.vim25.PropertyChange;
import com.vmware.vim25.PropertyChangeOp;
import com.vmware.vim25.PropertyFilterSpec;
import com.vmware.vim25.PropertyFilterUpdate;
import com.vmware.vim25.PropertySpec;
import com.vmware.vim25.SelectionSpec;
import com.vmware.vim25.ServiceContent;
import com.vmware.vim25.UpdateSet;
import com.vmware.vim25.VimPortType;
import com.vmware.vim25.VmEvent;
import com.vmware.vim25.mo.ServiceInstance;
import com.vmware.vim25.mo.util.OptionSpec;
/**
* This class performs the polling towards the Vmware ESXi hypervisor
*/
public class ESXiPoller implements Callable<Integer>
{
/** The logger object. */
private final static Logger logger = LoggerFactory.getLogger(ESXiPoller.class);
/** The event translation. */
private Hashtable<String, VMEventType> eventTranslation;
private AbstractMonitor monitor;
// VMWare dependent
/** The opts entered. */
private HashMap<String, String> optsEntered;
/** The prop coll. */
ManagedObjectReference propColl;
private static OptionSpec[] optionSpec;
/** The Redis dao. */
private RedisDao dao;
static
{
optionSpec = constructOptions();
}
/**
* Instantiates a new eS xi poller.
*/
public ESXiPoller(AbstractMonitor monitor)
{
eventTranslation = new Hashtable<String, VMEventType>();
this.monitor = monitor;
eventTranslation.put("VmRegisteredEvent", VMEventType.CREATED);
eventTranslation.put("VmPoweredOnEvent", VMEventType.POWER_ON);
eventTranslation.put("VmPoweredOffEvent", VMEventType.POWER_OFF);
eventTranslation.put("VmSuspendedEvent", VMEventType.PAUSED);
eventTranslation.put("VmResumingEvent", VMEventType.RESUMED);
eventTranslation.put("VmRemovedEvent", VMEventType.DESTROYED);
// VMWare dependent
optsEntered = new HashMap<String, String>();
dao = RedisDaoFactory.getInstance();
}
/**
* Initializes the login configuration
*
* @param user the admin user
* @param password admin password
*/
public void init(String user, String password)
{
optsEntered.put("username", user);
optsEntered.put("password", password);
}
/**
* It adds parameters from the configuration file.
*
* @param config the config
*/
private void builtinOptionsEntered()
{
optsEntered.put("ignorecert", "true");
optsEntered.put("datacentername", "ha-datacenter");
}
/**
* Creates the event history collector.
*
* @param url the url
* @param apputil the apputil
* @return the managed object reference
* @throws Exception the exception
*/
private ManagedObjectReference createEventHistoryCollector(ExtendedAppUtil apputil)
throws Exception
{
// Create an Entity Event Filter Spec to
// specify the MoRef of the VM to be get events filtered for
ServiceContent sic = apputil.getServiceInstance().getServiceContent();
EventFilterSpecByEntity entitySpec = new EventFilterSpecByEntity();
ManagedObjectReference rootFolder = sic.getRootFolder();
entitySpec.setEntity(rootFolder);
entitySpec.setRecursion(EventFilterSpecRecursionOption.children);
// set the entity spec in the EventFilter
EventFilterSpec eventFilter = new EventFilterSpec();
eventFilter.setEntity(entitySpec);
// we are only interested in getting events for the VM.
// Add as many events you want to track relating to vm.
// Refer to API Data Object vmEvent and see the extends class list for elaborate list of
// vmEvents
/*
* String[] arrayString = new String[filters.get(url).getFilter().size()];
* filters.get(url).getFilter().toArray(arrayString);
*/
String[] arrayString = new String[] {};
eventFilter.setType(arrayString);
/*
* eventFilter.setType( new String[] {"VmPoweredOffEvent", "VmPoweredOnEvent",
* "VmSuspendedEvent","VmRenamedEvent"} );
*/
eventFilter.setType(new String[] {"VmPoweredOffEvent", "VmPoweredOnEvent",
"VmSuspendedEvent", "VmRenamedEvent", "VmRemovedEvent", "VmRegisteredEvent",
"VmResumingEvent"});
// create the EventHistoryCollector to monitor events for a VM
// and get the ManagedObjectReference of the EventHistoryCollector returned
VimPortType service = apputil.getServiceUtil().getVimService();
ManagedObjectReference eventManager = sic.getEventManager();
ManagedObjectReference eventHistoryCollector =
service.createCollectorForEvents(eventManager, eventFilter);
return eventHistoryCollector;
}
/**
* Creates the event filter spec.
*
* @param eventHistoryCollector the event history collector
* @return the property filter spec
*/
private PropertyFilterSpec createEventFilterSpec(ManagedObjectReference eventHistoryCollector)
{
// Set up a PropertySpec to use the latestPage attribute
// of the EventHistoryCollector
PropertySpec propSpec = new PropertySpec();
propSpec.setAll(new Boolean(false));
propSpec.setPathSet(new String[] {"latestPage"});
propSpec.setType(eventHistoryCollector.getType());
// PropertySpecs are wrapped in a PropertySpec array
PropertySpec[] propSpecAry = new PropertySpec[] {propSpec};
// Set up an ObjectSpec with the above PropertySpec for the
// EventHistoryCollector we just created
// as the Root or Starting Object to get Attributes for.
ObjectSpec objSpec = new ObjectSpec();
objSpec.setObj(eventHistoryCollector);
objSpec.setSkip(new Boolean(false));
// Get Event objects in "latestPage" from "EventHistoryCollector"
// and no "traversl" further, so, no SelectionSpec is specified
objSpec.setSelectSet(new SelectionSpec[] {});
// ObjectSpecs are wrapped in an ObjectSpec array
ObjectSpec[] objSpecAry = new ObjectSpec[] {objSpec};
PropertyFilterSpec spec = new PropertyFilterSpec();
spec.setPropSet(propSpecAry);
spec.setObjectSet(objSpecAry);
return spec;
}
/**
* Handle update.
*
* @param update the update
* @return the hashtable< string, list< vm event>>
*/
private Hashtable<String, List<VmEvent>> handleUpdate(UpdateSet update)
{
Hashtable<String, List<VmEvent>> events = new Hashtable<String, List<VmEvent>>();
ArrayList<ObjectUpdate> vmUpdates = new ArrayList<ObjectUpdate>();
PropertyFilterUpdate[] pfus = update.getFilterSet();
for (PropertyFilterUpdate pfui : pfus)
{
ObjectUpdate[] ous = pfui.getObjectSet();
for (ObjectUpdate oui : ous)
{
if (oui.getObj().getType().equals("EventHistoryCollector"))
{
vmUpdates.add(oui);
}
}
}
if (vmUpdates.size() > 0)
{
logger.trace("Virtual Machine updates:");
for (ObjectUpdate vmi : vmUpdates)
{
handleObjectUpdate(events, vmi);
}
}
return events;
}
/**
* Handle object update.
*
* @param events the events
* @param oUpdate the o update
*/
void handleObjectUpdate(Hashtable<String, List<VmEvent>> events, ObjectUpdate oUpdate)
{
PropertyChange[] pc = oUpdate.getChangeSet();
logger.trace(" {} Data:", oUpdate.getKind().toString());
handleChanges(events, pc);
}
/**
* Handle changes.
*
* @param events the events
* @param changes the changes
*/
private void handleChanges(Hashtable<String, List<VmEvent>> events, PropertyChange[] changes)
{
for (int pci = 0; pci < changes.length; ++pci)
{
Object value = changes[pci].getVal();
PropertyChangeOp op = changes[pci].getOp();
if (value != null && !op.name().equalsIgnoreCase("remove"))
{
if (value instanceof ArrayOfEvent)
{
ArrayOfEvent aoe = (ArrayOfEvent) value;
Event[] evts = aoe.getEvent();
for (Event event : evts)
{
handleVMEvent(event, events, "[ARRAY] Event received: {} Time Stamp: {}");
}
}
else
{
handleVMEvent(value, events, "Event received: {} Time Stamp: {}");
}
}
}
}
private void handleVMEvent(Object event, Hashtable<String, List<VmEvent>> events, String log)
{
if (event instanceof VmEvent)
{
VmEvent vmEvent = (VmEvent) event;
if (eventTranslation.containsKey(vmEvent.getClass().getSimpleName()))
{
String UUID = vmEvent.getVm().getName();
logger.debug(log, vmEvent.getClass().getSimpleName(), vmEvent.getCreatedTime()
.getTime().toString());
logger.debug("Wiht id: " + vmEvent.getChainId());
if (!events.containsKey(UUID))
{
events.put(UUID, new ArrayList<VmEvent>());
}
events.get(UUID).add(vmEvent);
}
}
}
/**
* It constructs the basic options needed to work.
*
* @return the option spec[]
*/
private static OptionSpec[] constructOptions()
{
OptionSpec[] useroptions = new OptionSpec[8];
useroptions[0] = new OptionSpec("vmname", "String", 1, "Name of the virtual machine", null);
useroptions[1] =
new OptionSpec("datacentername", "String", 1, "Name of the datacenter", null);
useroptions[2] = new OptionSpec("hostname", "String", 0, "Name of the host", null);
useroptions[3] =
new OptionSpec("guestosid", "String", 0, "Type of Guest OS", "winXPProGuest");
useroptions[4] = new OptionSpec("cpucount", "Integer", 0, "Total CPU Count", "1");
useroptions[5] = new OptionSpec("disksize", "Integer", 0, "Size of the Disk", "64");
useroptions[6] =
new OptionSpec("memorysize",
"Integer",
0,
"Size of the Memory in the blocks of 1024 MB",
"1024");
useroptions[7] =
new OptionSpec("datastorename", "String", 0, "Name of the datastore", null);
return useroptions;
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Callable#call()
*/
@Override
public Integer call() throws Exception
{
ExtendedAppUtil apputil = null;
builtinOptionsEntered();
Map<String, String> credentialCache = new HashMap<String, String>();
while (true)
{
List<String> monitoredMachines = monitor.getMonitoredMachines();
List<String> copy;
// It is important to synchronize the copy of monitoredMachines list to avoid errors if
// a machine is added or removed while iterating the list.
synchronized (monitoredMachines)
{
copy = new ArrayList<String>(monitoredMachines);
}
for (String url : copy)
{
try
{
logger.trace("Monitoring the VMWARE ESXI located in: {}", url);
URL tempUrl = new URL(url);
String connectionUrl = "https://" + tempUrl.getHost() + ":443/sdk";
this.optsEntered.put("url", connectionUrl);
if (!credentialCache.containsKey(url))
{
PhysicalMachine pm = dao.findPhysicalMachineByAddress(url);
if (pm == null)
{
logger.error("Unable to retrieve physical machine " + url
+ " from redis. Skipping machine.");
continue;
}
credentialCache.put(url, pm.getUsername() + "#" + url + "#"
+ pm.getPassword());
init(pm.getUsername(), pm.getPassword());
}
else
{
String credentialStr = credentialCache.get(url);
String[] credentials = credentialStr.split("#" + url + "#");
init(credentials[0], credentials[1]);
}
ServiceInstance serviceInstance =
new ServiceInstance(new URL(connectionUrl),
optsEntered.get("username"),
optsEntered.get("password"),
true);
apputil =
ExtendedAppUtil.init(serviceInstance, constructOptions(), optsEntered);
// Filters configuration
ManagedObjectReference eventHistoryCollector =
createEventHistoryCollector(apputil);
propColl = apputil.getServiceInstance().getPropertyCollector().getMOR();
PropertyFilterSpec eventFilterSpec =
createEventFilterSpec(eventHistoryCollector);
VimPortType service = apputil.getServiceUtil().getVimService();
// Creates the filter
ManagedObjectReference propFilter =
service.createFilter(propColl, eventFilterSpec, true);
String version = "";
UpdateSet update = service.waitForUpdates(propColl, version);
if (update != null && update.getFilterSet() != null)
{
Hashtable<String, List<VmEvent>> events = this.handleUpdate(update);
version = update.getVersion();
if (events.size() > 0)
{
filterAndSend(events, url);
}
}
service.cancelWaitForUpdates(propColl);
// Destroying the property poller
service.destroyPropertyFilter(propFilter);
}
catch (Exception e)
{
logger.warn(e.getMessage() + " ignoring it.", e);
}
finally
{
apputil.disConnect();
logger.trace("Disconnected from the VMWARE ESXI located in: {}", url);
}
}
}
}
/**
* It filters the event and sends it
*
* @param physicalMachineAddress the physical machine address
* @param events the events
*/
private void filterAndSend(Hashtable<String, List<VmEvent>> events,
String physicalMachineAddress)
{
// logger.debug("Calling check and send demands: {} events: {}", list.size(),
// events.size());
for (String machine : events.keySet())
{
for (VmEvent event : events.get(machine))
{
// If it is an event supported and the event has not been notified
if (eventTranslation.containsKey(event.getClass().getSimpleName()))
{
VMEventType state = eventTranslation.get(event.getClass().getSimpleName());
VMEvent eventToNotify = new VMEvent(state, physicalMachineAddress, machine);
monitor.notify(eventToNotify);
}
}
}
}
}