/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.product; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Properties; import org.hyperic.hq.agent.AgentConfig; import org.hyperic.sigar.ProcState; import org.hyperic.sigar.Sigar; import org.hyperic.sigar.SigarException; import org.hyperic.sigar.win32.Win32; import org.hyperic.util.config.ConfigSchema; import org.hyperic.util.config.ConfigResponse; import org.hyperic.util.config.StringConfigOption; import org.hyperic.util.config.IntegerConfigOption; import org.hyperic.util.exec.Execute; import org.hyperic.util.exec.ExecuteWatchdog; import org.hyperic.util.exec.PumpStreamHandler; import org.hyperic.util.ArrayUtil; import org.hyperic.util.StringUtil; /** * This class is mainly helpful for control plugins which are * script/process driven. */ public abstract class ServerControlPlugin extends ControlPlugin { public static final String PROP_PIDFILE = "pidfile"; public static final String PROP_PROGRAM = "program"; public static final String PROP_PROGRAMPREFIX = "prefix"; private static final String PROGRAM_PREFIX = ""; private ByteArrayOutputStream output = new ByteArrayOutputStream(); private String installPrefix = null; private String controlProgram = null; private String controlProgramPrefix = null; private String pidFile = null; private int backgroundWaitTime = 0; private Sigar sigar = null; //assumes we are running on the agent; which is ok since the control //files we exec are too. private static final String BACKGROUND_SCRIPT = "background" + getScriptExtension(); private int exitCode; public ServerControlPlugin() { super(); setControlProgramPrefix(PROGRAM_PREFIX); } public String getInstallPrefix() { return this.installPrefix; } public void setInstallPrefix(String val) { this.installPrefix = val; } public String getControlProgram() { if (this.controlProgram == null) { //UI default for xml-only plugin return getTypeProperty("DEFAULT_PROGRAM"); } else { return this.controlProgram; } } public void setControlProgram(String val) { this.controlProgram = val; } public String getControlProgramPrefix() { return this.controlProgramPrefix; } public void setControlProgramPrefix(String val) { this.controlProgramPrefix = val; } public String getPidFile() { return this.pidFile; } public void setPidFile(String val) { this.pidFile = val; } protected boolean useSigar() { return false; } public void configure(ConfigResponse config) throws PluginException { super.configure(config); String val; val = config.getValue(ControlPlugin.PROP_TIMEOUT); if (val != null) { setTimeout(val); } val = config.getValue(ProductPlugin.PROP_INSTALLPATH); if (val != null) { setInstallPrefix(val); } val = config.getValue(PROP_PROGRAM); if (val != null) { setControlProgram(val); } val = config.getValue(PROP_PROGRAMPREFIX); if (val != null) { try { setControlProgramPrefix(val); } catch (IllegalArgumentException e) { // Unable to parse args, probably due to mismatched quotes throw new PluginException("Unable to parse prefix arguments"); } } val = config.getValue(PROP_PIDFILE); if (val != null) { setPidFile(val); } if (useSigar()) { this.sigar = new Sigar(); } } public void shutdown() throws PluginException { super.shutdown(); if (this.sigar != null) { this.sigar.close(); } } protected void getServerConfigSchema(TypeInfo info, ConfigSchema schema, ConfigResponse response) { //getConfigSchema happens server-side; so we cant use File.separator String fileSep = info.isWin32Platform() ? "\\" : "/"; String entName = info.getName(); StringConfigOption opt; String installPrefix = response.getValue(ProductPlugin.PROP_INSTALLPATH, getInstallPrefix()); if (installPrefix == null) { installPrefix = getDefaultInstallPath(); } String pidFile = getPidFile(); if (pidFile != null) { opt = new StringConfigOption(PROP_PIDFILE, "Full path to " + entName + " pid file", installPrefix + fileSep + pidFile); schema.addOption(opt); } String controlProgram = getControlProgram(); if (controlProgram != null) { opt = new StringConfigOption(PROP_PROGRAM, "Full path to " + entName + " control program", installPrefix + fileSep + controlProgram); schema.addOption(opt); } String controlProgramPrefix = getControlProgramPrefix(); if (controlProgramPrefix != null) { opt = new StringConfigOption(PROP_PROGRAMPREFIX, "Prefix arguments to control " + "program", controlProgramPrefix); opt.setOptional(true); schema.addOption(opt); } IntegerConfigOption timeout = new IntegerConfigOption(ControlPlugin.PROP_TIMEOUT, "Timeout of control operations " + "(in seconds)", new Integer(getTimeout())); schema.addOption(timeout); } //override to ask for more public ConfigSchema getConfigSchema(TypeInfo info, ConfigResponse config) { ConfigSchema schema = super.getConfigSchema(info, config); if (useConfigSchema(info)) { getServerConfigSchema(info, schema, config); } return schema; } protected boolean useConfigSchema(TypeInfo info) { return info.getType() == TypeInfo.TYPE_SERVER; } //override for to use something other than pid file protected boolean isRunning() { return isProcessRunning(getPidFile()); } protected boolean isProcessRunning(int pid) { if (this.sigar == null) { return false; } try { ProcState ps = this.sigar.getProcState(pid); // XXX: check process state? return true; } catch (SigarException e) { } return false; } protected boolean isProcessRunning(String pidFile) { String pid; if (pidFile == null) { return false; } // Get the process id using the pid file. It is assumed that // the pidfile contains a single line with the process if of // the server process. try { BufferedReader in = new BufferedReader(new FileReader(pidFile)); pid = in.readLine(); } catch (FileNotFoundException e) { // If the pid file does not exist, we are not running String err = "Pid file: " + pidFile + " not found"; getLog().debug(err); return false; } catch (IOException e) { // XXX is it right to Assume we are running? getLog().info("Could not read pidFile=" + pidFile); return true; } int processId; try { processId = Integer.parseInt(pid); } catch (NumberFormatException e) { String err = "Failed to parse pid from pid file: " + pidFile; getLog().debug(err); setMessage(err); return false; } return isProcessRunning(processId); } protected File getWorkingDirectory() { File file = new File(getControlProgram()).getParentFile(); if (file == null || !file.isAbsolute()) { file = new File(installPrefix); } return file; } protected String getControlProgramDir() { return new File(getControlProgram()).getParent(); } protected void validateControlProgram(String name) throws PluginException { String pgm = getControlProgram(); File script = new File(pgm); if (!script.exists()) { String msg = name + " control program not found: " + pgm; throw new PluginException(msg); } } protected boolean isBackgroundCommand() { return false; } /** * @return Seconds to wait on a background process */ protected int getBackgroundWaitTime() { String time = getPluginProperty("CONTROL_WAIT_TIME"); if (time != null) { return Integer.parseInt(time); } else { return this.backgroundWaitTime; } } protected void setBackgroundWaitTime(int seconds) { this.backgroundWaitTime = seconds; } /** * Override to add any additional arguments to the command line. */ protected String[] getCommandArgs() { return new String[0]; } /** * Override to pass any addition environment variables to the command. */ protected String[] getCommandEnv() { return null; } protected int doCommand() { return doCommand(getControlProgram(), new String[0]); } protected int doCommand(String command) { if (command == null) { return doCommand(); } else { return doCommand(new String[] { command }); } } protected int doCommand(String[] args) { return doCommand(getControlProgram(), args); } protected int doCommand(String program, String arg) { return doCommand(program, new String[] { arg }); } private String[] combine(String[] a1, String[] a2) { return (String[])ArrayUtil.combine(a1, a2); } protected int doCommand(String program, String[] params) { ArrayList args = new ArrayList(); ExecuteWatchdog watchdog = new ExecuteWatchdog(getTimeoutMillis()); this.output.reset(); Execute ex = new Execute(new PumpStreamHandler(this.output), watchdog); //weblogic and jboss scripts for example //must be run from the directory where the script lives File wd = getWorkingDirectory(); if (wd.exists()) { ex.setWorkingDirectory(wd); } if (isBackgroundCommand()) { //XXX bloody ugly hack for startup scripts such as weblogic //which do not background themselves Properties props = getManager().getProperties(); String cwd = System.getProperty("user.dir"); String dir = ""; try{ dir=props.getProperty(AgentConfig.PROP_INSTALLHOME[0]); }catch(java.lang.NoClassDefFoundError e){ // in standalone -> Exception in thread "main" java.lang.NoClassDefFoundError: org/hyperic/hq/agent/AgentConfig dir = props.getProperty("agent.install.home", cwd); } if (dir.equals(".")) { //XXX: ./background.sh command silently fails when running //in the agent, even tho ./ is the same place as user.dir dir = cwd; } File background = new File(dir, BACKGROUND_SCRIPT); if (!background.exists()) { //try relative to pdk.dir for command-line usage File pdk = new File(ProductPluginManager.getPdkDir()); if(!pdk.isAbsolute()){ pdk=new File(new File(cwd),ProductPluginManager.getPdkDir()); } background = new File(pdk, "../"+BACKGROUND_SCRIPT); } if (background.exists()) { try { args.add(background.getCanonicalPath()); } catch (IOException ex1) { args.add(background.getAbsolutePath()); getLog().debug(ex1); } } getLog().info("background="+background); } String prefix = getControlProgramPrefix(); if (prefix != null && prefix.length() != 0) { try { String[] prefixArgs = StringUtil.explodeQuoted(getControlProgramPrefix()); for (int i=0; i<prefixArgs.length; i++) { args.add(prefixArgs[i]); } } catch (IllegalArgumentException e) { // Unable to parse args, probably due to mismatched quotes getLog().error("Unable to parse arguments: " + prefix); } } if (HypericOperatingSystem.IS_WIN32) { //Runtime.exec does not handle file associations //such as foo.pl -> perl.exe, foo.py -> python.exe. String exe = Win32.findScriptExecutable(program); if (exe != null) { args.add(exe); } } if (new File(program).isAbsolute()) args.add(program); else args.add(installPrefix+File.separator+program); if (params != null) { for (int i=0; i<params.length; i++) { if (params[i] == null) { continue; } args.add(params[i]); } } String[] commandArgs = getCommandArgs(); for (int i=0; i<commandArgs.length; i++) { if (commandArgs[i] == null) { continue; } args.add(commandArgs[i]); } getLog().info("doCommand args=" + args); ex.setCommandline((String[])args.toArray(new String[0])); //set some environment variables for use by control scripts String[] env = { "HQ_CONTROL_RESOURCE=" + getName(), "HQ_CONTROL_TYPE=" + getTypeInfo().getName(), "HQ_CONTROL_WAIT=" + getBackgroundWaitTime(), }; env = combine(env, ex.getEnvironment()); String[] cmdEnv = getCommandEnv(); if (cmdEnv != null) { env = combine(env, cmdEnv); } ex.setEnvironment(env); this.exitCode = RESULT_FAILURE; try { this.exitCode = ex.execute(); } catch (Exception e) { getLog().error(e.getMessage(), e); setMessage(e.getMessage()); } if (this.exitCode == 0) { setResult(RESULT_SUCCESS); } else { setResult(RESULT_FAILURE); } // Check for watchdog timeout. Note this does not work with scripts // that are backgrounded. if (watchdog.killedProcess()) { String err = "Command did not complete within timeout of " + getTimeout() + " seconds"; getLog().error(err); setMessage(err); setResult(RESULT_FAILURE); return getResult(); } getLog().info("doCommand result=" + getResult() + ", exitCode=" + this.exitCode); String message = this.output.toString(); if (message.length() > 0) { setMessage(message); } return getResult(); } /** * @return program exit code from doCommand() */ protected int getExitCode() { return this.exitCode; } protected void handleResult(String stateWanted) { // don't bother waiting for the desired state if the startup // script does not return 0. if (getResult() != RESULT_SUCCESS) { if (getMessage() == null) { setMessage("Unknown Error (exit code=" + this.exitCode + ")"); } return; } String state = waitForState(stateWanted); if (!state.equals(stateWanted)) { setResult(RESULT_FAILURE); if (getMessage() == null) { setMessage("Control action timed out after " + getTimeout() + " seconds. Server still in state " + state); } } } protected int start(String command) { int res = doCommand(command); waitForState(STATE_STARTED); return res; } }