/**
* ZXTM Java Extension for performing VMWare Power Control.
*
* TrafficScript usage: java.run("com.zeus.vmware.VMPoolControl", "poweron|suspend", "Pool Name");
*
*/
package com.zeus.vmware;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.ListIterator;
import java.util.Scanner;
import java.util.Vector;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.xml.rpc.ServiceException;
import com.vmware.vim.*;
import com.vmware.apputils.*;
import com.vmware.apputils.vim.*;
import com.zeus.ZXTMServlet.ZXTMServletRequest;
import com.zeus.ZXTMServlet.ZXTMServletResponse;
import com.zeus.soap.zxtm._1_0.PoolLocator;
import com.zeus.soap.zxtm._1_0.PoolPort;
/**
* This is a threaded class which can be used to initiate power functions on VMware virtual
* machines, and then modify a ZXTM pool to include/exclude nodes based on their power state.
* It is designed to be used as a ZXTM Java Extension and thus extends the JAVA Servlet API.
*
* We need the location of the ZEUSHOME (Zeus install dir), so that we can read in our configuration
* file. The configuration file should be called vmpoolcontrol.cfg and uploaded to the ZXTM extras
* directory.
*
* We require a single initialisation parameter:
* ZEUSHOME -> The location of the Zeus installation
*
* The service() method does validation of the calling arguments and checks to see if there is a VM
* available for the requested power change. It does this by calling a method on the pool object, either
* poweron() or poweroff(). These methods return a VM name, or null if all VMs are already in the requested
* state. If all validation checks pass, and their is an available VM, service() sets a flag and returns.
* The vmThread then handles all of the SOAP calls to the Virtual Infrastructure server, and the ZXTM.
*
* You need to create a configuration file which contains all of the pools you wish to manage and the
* nodes therein. As well as several mandatory configuration elements....
*
* viurl = https://10.100.1.42/sdk
* viuser = admin
* vipass = admin
* vidatacenter = Zeus Systems Engineering
* zxtmurl = https://admin:admin@10.100.50.10:9090/soap
* seconds = 10
* webservice = vmWeb1,10.100.50.21:80,on vmWeb2,10.100.50.22:80,on vmWeb3,10.100.50.23:80,off
*
* The pools line is: poolname=VMname,ip:port,state VMname,ip:port,state
* Ie node values are comma separated (vmname, node, power state (on|off)),
* and individual nodes are space separated.
*
*
* TrafficScript usage: java.run("com.zeus.vmware.VMPoolControl", "poweron|suspend", "Pool Name");
*
*
* @author Mark Boddington <mbod-SP-ding-AM-ton@zeus.com>
*/
public class VMPoolControl extends GenericServlet implements Runnable {
//private static enum state { NONE, INIT, DOGET, POWERON, SUSPEND };
private static final long serialVersionUID = 1L;
private static AppUtil cb = null;
private static VMPoolControl vmThread = null;
private static OptionSpec [] useroptions = null;
private static String[] args = new String[10];
private static HashMap poolMap = null;
private static String poolname = null;
private static String vmname = null;
private static boolean running = true;
private static ManagedObjectReference taskmor= null;
private static boolean taskRunning = false;
private static Object taskLock = new Object();
private static String taskInfo = null;
private static int sleepSeconds = 0;
/**
* This constructor is empty, we need it so we can create an instance of ourselves
* and run the VMWare server polling in a separate thread.
*/
public VMPoolControl() {
// empty constructor, purely so we can instantiate ourselves as a thread
}
/**
* Override the Servlet init function, and setup our environment.
* We only need to set these once, so we do it all in init() to be as efficient as possible.
*/
public void init() throws ServletException {
// We've just started, initialising.
taskInfo = "INIT";
// There is no running task
taskRunning = false;
// The number of seconds to sleep after completing a power change, before setting that task back to NONE.
// SLM updates it's conformance stats every 5 seconds, so our default is 10 seconds
sleepSeconds = 10;
// We need an OptionSpec for vmware API, but we pass all our options in an
// args arrays, so do the bare minimum
useroptions = new OptionSpec[3];
useroptions[0] = new OptionSpec("vmname","String",0
,"Name of the virtual machine"
,null);
useroptions[1] = new OptionSpec("datacenter","String",0
,"Name of the datacenter"
,null);
useroptions[2] = new OptionSpec("operation","String",1,
"operation to be performed",
null);
// Get the ServletConfig object so we can read our initParameters.
ServletConfig config = getServletConfig();
String zeushome = config.getInitParameter("ZEUSHOME");
if ( zeushome == null || zeushome.isEmpty()) {
throw new ServletException("Missing mandatory parameter. Please specify ZEUSHOME.");
}
// The poolmap will hold all of the pools configured in the config file
poolMap = new HashMap();
// Parse the config file to get our settings and populate our pool map
processConfig(zeushome);
// If we don't have all the mandatory options in the config file, then throw an exception
for ( int i=0 ; i< 7 ; i++ ) {
if ( args[i] == null || args[i] == ""){
throw new ServletException("Servlet init failed. Mandatory configuration elements missing from config file");
}
}
// Create the thread which will track running tasks.
// the thread will call our run() method, see below
vmThread = new VMPoolControl();
new Thread(vmThread).start();
// No task
taskInfo = "NONE";
}
/**
* Process the configfile vmpoolcontrol.cfg
*
* @param zeushome - The Zeus installation directory
* @throws ServletException on error
*/
private void processConfig(String zeushome) throws ServletException {
FileInputStream fis = null;
String zxtmUrl = null;
args[0] = "--url";
args[2] = "--username";
args[4] = "--password";
args[6] = "--datacenter";
args[8] = "--operation";
args[9] = "";
try {
fis = new FileInputStream(zeushome + "/zxtm/conf/extra/vmpoolcontrol.cfg");
} catch ( FileNotFoundException e ) {
throw new ServletException("Please upload your vmpoolcontrol.cfg",e);
}
Scanner confParser = new Scanner(new BufferedInputStream(fis));
confParser.useDelimiter("[\r\n]+");
while ( confParser.hasNext() ) {
String line = confParser.next();
String[] values = line.split("=",2);
values[0] = values[0].trim();
values[1] = values[1].trim();
if ( values[0].equals("viurl")) {
args[1] = values[1];
} else if (values[0].equals("viuser")) {
args[3] = values[1];
} else if (values[0].equals("vipass")) {
args[5] = values[1];
} else if (values[0].equals("vidatacenter")) {
args[7] = values[1];
} else if ( values[0].equals("zxtmurl")) {
zxtmUrl = values[1];
} else if ( values[0].equals("seconds")) {
sleepSeconds = Integer.parseInt(values[1]);
} else {
PoolObject pool = new PoolObject(values[0], zxtmUrl );
String nodePairs[] = values[1].split( " " );
for ( int i=0; i < nodePairs.length; i++) {
String thisNode[] = nodePairs[i].split(",");
if ( thisNode[2].equals("on")) {
pool.addNode(thisNode[0], thisNode[1], true);
} else {
pool.addNode(thisNode[0], thisNode[1], false);
}
}
poolMap.put(values[0], pool);
}
}
try {
fis.close();
} catch (IOException e) {
throw new ServletException("Failed to close config file.", e);
}
return;
}
/**
* When a servlet is terminated, the JRE calls the destroy() function.
* Attempt to disconnect gracefully from the VMware server when that happens.
*/
public void destroy() {
running = false;
try {
cb.disConnect();
} catch (Exception e) {
// We probably weren't connected. No point complaining about it.
}
super.destroy();
}
/**
* This method is called by ZXTM when java.run() is executed. We first check to see if a task is already
* in progress, if it is, then we return immediately. Otherwise we sanity check the calling arguments, and
* ascertain whether any VM is available for being powered on or suspended. If all our checks pass then
* we set taskRunning to true and return. The background thread will then pick up the task and perform the
* necessary steps.
*
* This method expects to be given a power state (poweron|suspend), and the name of a pool. The VM to to
* be changed will be selected by the Pool objects poweron() or poweroff() method. Pool objects are created
* during initialisation from the vmpoolcontrol.cfg configuration file.
*
* @param req - The ServletRequest
* @param res - The ServletResponse
* @throws ServletException on error
*/
public void service(ServletRequest req, ServletResponse res) throws ServletException
{
// Cast the Servlet* objects into the ZXTMServlet* versions.
ZXTMServletRequest zreq = (ZXTMServletRequest)req;
ZXTMServletResponse zres = (ZXTMServletResponse)res;
// The taskLock check is synchronised, to stop more than one invocation attempting to
// to perform a power operation at the same time.
synchronized(taskLock) {
if ( ! taskInfo.equals("NONE") ) {
return;
}
taskInfo = "SERVICE";
}
// Read in any arguments passed into the function
String[] callArgs = (String[]) zreq.getAttribute( "args" );
// If we have no arguments or too few arguments then exit.
if ( callArgs == null || callArgs.length < 2) {
System.err.println("Usage: java.run( \"com.zeus.vmware.VMPoolControl\", (poweron|suspend), );");
taskInfo = "NONE";
return;
}
if ( callArgs[0].equalsIgnoreCase("poweron") || callArgs[0].equalsIgnoreCase("suspend") ) {
poolname = callArgs[1];
PoolObject pool = poolMap.get(poolname);
if ( pool == null ) {
taskInfo = "NONE";
System.err.println("Error, The pool specified is not listed in vmpoolcontrol.cfg\n" +
"You need to reload the java applet after making changes to the configuration file");
return;
}
// Push the power option into the args array
args[9] = callArgs[0].toLowerCase();
// Get a node to power on from the list
if ( args[9].equals("poweron")) {
vmname = pool.powerOn();
} else {
vmname = pool.powerOff();
}
// If no node is suitable for the requested powerchange then the pool returns null
// throw a servlet exception with the appropriate error.
if ( vmname == null ) {
taskInfo = "NONE";
System.err.println("All machines in pool: " + poolname + " are already in state: " + args[9] );
return;
}
// Set the task to running and exit. The vmThread will do the work for us.
taskRunning = true;
} else {
// The user specified an action which was not suspend or poweron
System.err.println("Invalid power option specified, " + callArgs[0] );
taskInfo = "NONE";
}
}
/**
* This method is called by VMThread, to get the power task result. The waitForTask method (as
* its name suggests) blocks. So the thread will wait for a response, and return success or
* failure as a boolean return.
*
* @param taskmor - The task reference
* @return - task succeeded or failed.
* @throws Exception
*/
private boolean getTaskInfo(ManagedObjectReference taskmor)throws Exception{
String res = cb.getServiceUtil().waitForTask(taskmor);
if(res.equalsIgnoreCase("sucess")) {
return true;
}
return false;
}
/**
* This method is run by our vmThread when we call start(). If a task is found, then we
* initiate the power change, and then call the getTaskInfo method to wait for the task to finish.
* Finally we either add or remove the node to ZXTMs pool configuration, sleep for sleepSeconds
* seconds and then set the task back to NONE. The sleep is to allow ZXTM load monitoring to
* evaluate the state, with the new pool configuration.
*/
public void run() {
while (running) {
if ( taskRunning ) {
// Attempt to initiate the power change.
try {
// Setup the connection to the VI Server
cb = AppUtil.initialize("VMPowerOps", useroptions, args);
cb.getConnection().ignoreCert();
cb.connect();
// Get the list of VMs which match the vmname in the provided data centre
VMUtils vmUtils = new VMUtils(cb);
ArrayList vmList = new ArrayList();
String[][] filter = new String[][] { new String[] { "name", vmname, } };
vmList = vmUtils.getVMs("VirtualMachine",args[7],null,
null,vmname,null,filter);
// Start the power task for the first VM we found.
ManagedObjectReference vmMOR = (ManagedObjectReference)vmList.get(0);
if ( args[9].equals("poweron") ) {
System.err.println("Initiating PowerOn for " + vmname);
taskmor = cb.getConnection().getService().powerOnVM_Task(vmMOR, null);
taskInfo = "POWERON";
} else {
System.err.println("Initiating Suspend for " + vmname);
taskmor = cb.getConnection().getService().suspendVM_Task(vmMOR);
taskInfo = "SUSPEND";
}
boolean result = getTaskInfo(taskmor);
if(result){
System.err.println("VMWare command succeeded: " + taskInfo + ", for: " + vmname);
PoolObject pool = poolMap.get(poolname);
if ( taskInfo.equals("POWERON"))
pool.addtoZxtm(vmname);
else
pool.removefromZxtm(vmname);
} else {
System.err.println("VMWare command failed: " + taskInfo + ", for: " + vmname);
}
System.err.println("Sleeping for " + sleepSeconds + " seconds, to let ZXTM re-evaluate load...");
try { Thread.sleep( sleepSeconds * 1000); } catch (Exception e) { e.printStackTrace(); }
cb.disConnect();
taskRunning = false;
taskInfo = "NONE";
} catch(Exception e) {
// Something went wrong. Barf!
System.err.println("Failed to initiate power state change. Operation: "
+ args[9] + ". Virtual Machine: " + vmname);
System.err.println("VM Error: " + e.getMessage());
taskInfo = "NONE";
}
}
try { Thread.sleep(10*1000); } catch (Exception e) { e.printStackTrace(); }
}
System.err.println("VMThread Exiting... Bye bye");
}
/**
* This inner class represents the Pool object. The pool is in charge of the node configuration,
* including the VMware power status.
*/
public class PoolObject {
private static final long serialVersionUID = 1L;
// The name of this pool
public String poolName = null;
// String holding the ZXTM connection URL
private String zxtmConn = null;
// ZXTM SOAP Pool Locator service.
private PoolLocator pl;
// ZXTM SOAP Pool Port service
private PoolPort pp;
// Vector to hold the node names
private Vector nodes = null;
// Map the nodes to their IP:port numbers
private HashMap nodeMap = null;
// Map the nodes to their power state
private HashMap powerMap = null;
/**
* Create a new pool object. We require the Name of this pool, and the SOAP
* URI for communicating with the ZXTM
*
* @param arg0 - The pool name
* @param arg1 - The ZXTM SOAP URI
*/
public PoolObject(String arg0, String arg1) {
poolName = arg0;
zxtmConn = arg1;
nodeMap = new HashMap();
powerMap = new HashMap();
nodes = new Vector();
}
/**
* Add a node to this pool object. We require the name of the node (which
* should match the vmware name), the IP:port of the node for use by ZXTM,
* and the power state, on == true, off == false
*
* @param name - The node name
* @param node - The node ip:port
* @param on - The power state
*/
public void addNode(String name, String node, boolean on) {
nodes.add(name);
nodeMap.put(name,node);
powerMap.put(name, on);
}
/**
* Return the next node, which should be powered on for this pool.
* @return The nodeName
*/
String powerOn() {
ListIterator iterator = nodes.listIterator();
while (iterator.hasNext()) {
String thisNode = iterator.next();
if ( powerMap.get(thisNode) == false ) {
return thisNode;
}
}
return null;
}
/**
* Return the next node, which should be powered off for this pool.
* @return the nodeName
*/
String powerOff() {
ListIterator iterator = nodes.listIterator(nodes.size());
while (iterator.hasPrevious() ) {
String thisNode = iterator.previous();
if ( powerMap.get(thisNode) == true ) {
return thisNode;
}
}
return null;
}
/**
* Add the specified node to the ZXTM pool.
* @param node
*/
public void addtoZxtm(String node) {
pl = new PoolLocator();
pl.setPoolPortEndpointAddress( zxtmConn );
String[] poolname = { poolName };
String[][] nodename = {{ nodeMap.get(node) }};
System.err.println("Adding node: " + node + ", to pool: " + poolName);
try {
pp = pl.getPoolPort();
pp.addNodes(poolname, nodename);
powerMap.put(node, true);
} catch (ServiceException e) {
System.err.println("Service Exception while adding node to pool: " + e.getMessage());
} catch (RemoteException e) {
System.err.println("Remote Exception while adding node to pool: " + e.getMessage());
}
}
/**
* Remove the specified node from the ZXTM pool.
* @param node
*/
public void removefromZxtm(String node) {
pl = new PoolLocator();
pl.setPoolPortEndpointAddress( zxtmConn );
String[] poolname = { poolName };
String[][] nodename = {{ nodeMap.get(node) }};
System.err.println("Removing node: " + node + ", from pool: " + poolName);
try {
pp = pl.getPoolPort();
pp.removeNodes(poolname, nodename);
powerMap.put(node, false);
} catch (ServiceException e) {
System.err.println("Service Exception while removing node from pool: " + e.getMessage());
}catch (RemoteException e) {
System.err.println("Remote Exception while removing node from pool: " + e.getMessage());
}
}
}
}