/** * 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()); } } } }