/* * 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-2010], VMware, Inc. * This file is part of Hyperic. * * 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.agent.server; import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Constructor; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.hyperic.hq.agent.AgentAssertionException; import org.hyperic.hq.agent.AgentConfig; import org.hyperic.hq.agent.AgentConfigException; import org.hyperic.hq.agent.AgentLifecycle; import org.hyperic.hq.agent.AgentMonitorValue; import org.hyperic.hq.agent.AgentStartupCallback; import org.hyperic.hq.agent.AgentUpgradeManager; import org.hyperic.hq.agent.diagnostics.AgentDiagnostics; import org.hyperic.hq.agent.server.monitor.AgentMonitorException; import org.hyperic.hq.agent.server.monitor.AgentMonitorInterface; import org.hyperic.hq.agent.server.monitor.AgentMonitorSimple; import org.hyperic.hq.agent.stats.AgentStatsWriter; import org.hyperic.hq.bizapp.client.PlugininventoryCallbackClient; import org.hyperic.hq.bizapp.client.StorageProviderFetcher; import org.hyperic.hq.product.GenericPlugin; import org.hyperic.hq.product.PluginException; import org.hyperic.hq.product.PluginInfo; import org.hyperic.hq.product.PluginManager; import org.hyperic.hq.product.ProductPlugin; import org.hyperic.hq.product.ProductPluginManager; import org.hyperic.util.PluginLoader; import org.hyperic.util.security.SecurityUtil; import org.tanukisoftware.wrapper.WrapperManager; /** * The main daemon which processes requests from clients. The Agent has * the responsibility of being the 'live' entity on remote machines. * Bootstrap configuration is done entirely within this class. */ public class AgentDaemon extends AgentMonitorSimple { public static final String NOTIFY_AGENT_UP = AgentDaemon.class.getName() + ".agentUp"; public static final String NOTIFY_AGENT_DOWN = AgentDaemon.class.getName() + ".agentDown"; public static final String NOTIFY_AGENT_FAILED_START = AgentDaemon.class.getName()+ ".agentFailedStart"; public static final String PROP_CERTDN = "agent.certDN"; public static final String PROP_HOSTNAME = "agent.hostName"; private static final String AGENT_COMMANDS_SERVER_JAR_NAME = "hq-agent-handler-commands"; private static AgentDaemon mainInstance; private static Object mainInstanceLock = new Object(); private final double startTime; private final static Log logger = LogFactory.getLog(AgentDaemon.class); private final ServerHandlerLoader handlerLoader; private final PluginLoader handlerClassLoader; private CommandDispatcher dispatcher; private AgentStorageProvider storageProvider; private CommandListener listener; private AgentTransportLifecycle agentTransportLifecycle; private Vector<AgentServerHandler> serverHandlers; private Vector<AgentServerHandler> startedHandlers = new Vector<AgentServerHandler>(); private final Hashtable<String, Vector<AgentNotificationHandler>> notifyHandlers = new Hashtable<String, Vector<AgentNotificationHandler>>(); private Hashtable<String, AgentMonitorInterface> monitorClients; private final AtomicBoolean running = new AtomicBoolean(false); private ProductPluginManager ppm; private final AgentConfig config; private AgentDiagnostics agentDiagnostics; public static AgentDaemon getMainInstance(){ synchronized(AgentDaemon.mainInstanceLock){ return AgentDaemon.mainInstance; } } private AgentDaemon(AgentConfig config){ // Fields which get re-used are initialized here. // See cleanup()/configure() for fields which can // be re-configured this.handlerClassLoader = PluginLoader.create("ServerHandlerLoader", getClass().getClassLoader()); this.handlerLoader = new ServerHandlerLoader(this.handlerClassLoader); this.startTime = System.currentTimeMillis(); this.config = config; synchronized(AgentDaemon.mainInstanceLock){ if(AgentDaemon.mainInstance == null){ AgentDaemon.mainInstance = this; } } } public CommandDispatcher getCommandDispatcher(){ return dispatcher; } private static File getAgentCommandsServerJar(String libHandlersDir) throws FileNotFoundException { File[] jars = new File(libHandlersDir).listFiles(new FileFilter() { public boolean accept(File file) { String name = file.getName(); return name.startsWith(AGENT_COMMANDS_SERVER_JAR_NAME); } }); if ((jars == null) || (jars.length != 1)) { throw new FileNotFoundException(AGENT_COMMANDS_SERVER_JAR_NAME+" jar is not optional"); } return jars[0]; } private static File[] getOtherCommandsServerJars(String libHandlersDir) { File[] jars = new File(libHandlersDir).listFiles(new FileFilter() { public boolean accept(File file) { String name = file.getName(); return name.endsWith(".jar") && !name.startsWith(AGENT_COMMANDS_SERVER_JAR_NAME); } }); if (jars == null) { return new File[0]; } return jars; } /** * Create a new AgentDaemon object based on a passed configuration. * * @param cfg Configuration the new Agent should use. * * @throws AgentConfigException indicating the passed configuration is * invalid. */ public static AgentDaemon newInstance(AgentConfig cfg) throws AgentConfigException { AgentDaemon res = new AgentDaemon(cfg); try { res.configure(); } catch(AgentRunningException exc){ throw new AgentAssertionException("New agent should not be running", exc); } return res; } /** * Retrieve a plugin manager. * * @param type The type of plugin manager that is wanted * @return The plugin manager for the type given * * @throws AgentRunningException Indicating the agent was not running * when the request was made. * * @throws PluginException If the requested manager was not found. */ public PluginManager getPluginManager(String type) throws AgentRunningException, PluginException { if (!this.isRunning()) { throw new AgentRunningException("Plugin manager cannot be " + "retrieved if the Agent is " + "not running"); } return this.ppm.getPluginManager(type); } /** * Retrieve the storage object in use by the Agent. This routine should * be used primarily by server handlers wishing to do any kind of * storage. * * @return The storage provider object used by the Agent * * @throws AgentRunningException indicating the Agent was not running * when the request was made. */ public AgentStorageProvider getStorageProvider() throws AgentRunningException { if(!this.isRunning()){ throw new AgentRunningException("Storage cannot be retrieved if " + "the Agent is not running"); } return this.storageProvider; } /** * Get the bootstrap configuration that the Agent was initialized with. * * @return The configuration object used to initialize the Agent. */ public AgentConfig getBootConfig() { return this.config; } /** * @return The current agent bundle name. */ public String getCurrentAgentBundle() { String agentBundleHome = getBootConfig() .getBootProperties().getProperty(AgentConfig.PROP_BUNDLEHOME[0]); File bundleDir = new File(agentBundleHome); return bundleDir.getName(); } /** * Retrieve the agent transport lifecycle. * * @throws AgentRunningException indicating the Agent was not running * when the request was made. */ public AgentTransportLifecycle getAgentTransportLifecycle() throws AgentRunningException { if(!this.isRunning()){ throw new AgentRunningException("Agent Transport Lifecycle cannot be retrieved if " + "the Agent is not running"); } return this.agentTransportLifecycle; } /** * Register an object to be called when a notification of the specified * message class occurs; * * @param handler Handler to call to process the notification message * @param msgClass Message class to register with */ public void registerNotifyHandler(AgentNotificationHandler handler, String msgClass) { synchronized(this.notifyHandlers){ Vector<AgentNotificationHandler> handlers; if((handlers = this.notifyHandlers.get(msgClass)) == null){ handlers = new Vector<AgentNotificationHandler>(); this.notifyHandlers.put(msgClass, handlers); } handlers.add(handler); } } /** * Send a notification event to all notification handlers which * have registered with the specified message class. * * @param msgClass Message class that the message belongs to * @param message Message to send */ public void sendNotification(String msgClass, String message){ synchronized(this.notifyHandlers){ Vector<AgentNotificationHandler> handlers; if((handlers = this.notifyHandlers.get(msgClass)) == null){ return; } for (AgentNotificationHandler handler : handlers) { handler.handleNotification(msgClass, message); } } } /** * Cleanup any internal resources the agent is using. The Agent must * not be running when this function is called. It may raise an * exception if some of the cleanup fails, however the Agent will be * in a pristine state after calling this function. Cleanup may be * called more than one time without re-configuring in between cleanup() * calls. * * @throws AgentRunningException indicating the Agent was running when * the routine was called. */ private synchronized void cleanup() throws AgentRunningException { if(this.isRunning()){ throw new AgentRunningException("Agent cannot be cleaned up while running"); } // Shutdown the serverhandlers first, in case they need to write // something to storage if (this.startedHandlers != null){ for(int i=0; i < this.startedHandlers.size(); i++){ AgentServerHandler handler = this.startedHandlers.get(i); handler.shutdown(); } this.serverHandlers = null; this.startedHandlers = null; } this.dispatcher = null; if(this.listener != null){ this.listener.cleanup(); this.listener = null; } if(this.storageProvider != null){ try { this.storageProvider.flush(); } catch(AgentStorageException exc){ logger.error("Failed to flush Agent storage", exc); } finally { this.storageProvider.dispose(); this.storageProvider = null; } } try { this.ppm.shutdown(); } catch (PluginException e) { // Not much we can do } } public static AgentStorageProvider createStorageProvider(AgentConfig cfg) throws AgentConfigException { AgentStorageProvider provider; Class<?> storageClass; try { storageClass = Class.forName(cfg.getStorageProvider()); } catch(ClassNotFoundException exc){ throw new AgentConfigException("Storage provider not found: " + exc.getMessage()); } try { provider = (AgentStorageProvider)storageClass.newInstance(); provider.init(cfg.getStorageProviderInfo()); } catch(IllegalAccessException exc){ throw new AgentConfigException("Unable to access storage " + "provider '" + cfg.getStorageProvider() + "': " + exc.getMessage()); } catch(InstantiationException exc){ throw new AgentConfigException("Unable to instantiate storage " + "provider '" + cfg.getStorageProvider() + "': " + exc.getMessage()); } catch(AgentStorageException exc){ throw new AgentConfigException("Storage provider unable to " + "initialize: " + exc.getMessage()); } return provider; } /** * Configure the agent to run with new parameters. This routine will * load jars, open sockets, and other resources in preparation for * running. * * @throws AgentRunningException indicating the Agent was running when a * reconfiguration was attempted. * @throws AgentConfigException indicating the configuration was invalid. */ public void configure() throws AgentRunningException, AgentConfigException { DefaultConnectionListener defListener; if(this.isRunning()){ throw new AgentRunningException("Agent cannot be configured while"+ " running"); } //add lib/handlers/lib/*.jar classpath for handlers only String libHandlersLibDir = config.getBootProperties().getProperty(AgentConfig.PROP_LIB_HANDLERS_LIB[0]); File handlersLib = new File(libHandlersLibDir); if (handlersLib.exists()) { this.handlerClassLoader.addURL(handlersLib); } // Dispatcher this.dispatcher = new CommandDispatcher(); this.storageProvider = AgentDaemon.createStorageProvider(config); // Determine the hostname. This will be stored in the storage provider // and checked on each agent invocation. This determines if this agent // installation has been copied from another machine without removing // the data directory. // [HHQ-5547] Overriding property is fetched here directly because it's not yet initialized in GenericPlugin String platformName = config.getBootProperties().getProperty(ProductPlugin.PROP_PLATFORM_NAME); String currentHost = (null != platformName ? platformName : GenericPlugin.getPlatformName()); String storedHost = this.storageProvider.getValue(PROP_HOSTNAME); if ((storedHost == null) || (storedHost.length() == 0)) { this.storageProvider.setValue(PROP_HOSTNAME, currentHost); } else { // Validate if (!storedHost.equals(currentHost)) { String err = "Invalid hostname '" + currentHost + "'. This agent " + "has been configured for '" + storedHost + "'. If " + "this agent has been copied from a different machine " + "please remove the data directory and restart the agent"; throw new AgentConfigException(err); } } this.listener = new CommandListener(this.dispatcher); defListener = new DefaultConnectionListener(config); this.setConnectionListener(defListener); // set the lather proxy host and port if applicable if (config.isProxyServerSet()) { logger.info("Setting proxy server: host="+config.getProxyIp()+ "; port="+config.getProxyPort()); System.setProperty(AgentConfig.PROP_LATHER_PROXYHOST, config.getProxyIp()); System.setProperty(AgentConfig.PROP_LATHER_PROXYPORT, String.valueOf(config.getProxyPort())); } // Server Handlers this.serverHandlers = new Vector<AgentServerHandler>(); // Load server handlers on the fly from lib/*.jar. Server handler // jars must have a Main-Class that implements the AgentServerHandler // interface. String libHandlersDir = config.getBootProperties().getProperty(AgentConfig.PROP_LIB_HANDLERS[0]); // The AgentCommandsServer is *NOT* optional. Make sure it is loaded File agentCommandsServerJar; try { agentCommandsServerJar = getAgentCommandsServerJar(libHandlersDir); } catch (FileNotFoundException e) { throw new AgentConfigException(e.getMessage()); } loadAgentServerHandlerJars(new File[]{agentCommandsServerJar}); File[] otherCommandsServerJars = getOtherCommandsServerJars(libHandlersDir); loadAgentServerHandlerJars(otherCommandsServerJars); // Make sure the storage provider has a certificate DN. // If not, create one String certDN = this.storageProvider.getValue(PROP_CERTDN); if ( (certDN == null) || (certDN.length() == 0) ) { certDN = generateCertDN(); this.storageProvider.setValue(PROP_CERTDN, certDN); try { this.storageProvider.flush(); } catch ( AgentStorageException ase ) { throw new AgentConfigException("Error storing certdn in " + "agent storage: " + ase); } } } private void loadAgentServerHandlerJars(File[] libJars) throws AgentConfigException { AgentServerHandler loadedHandler; // Save the current context loader, and reset after we load plugin jars ClassLoader currentContext = Thread.currentThread().getContextClassLoader(); for (File libJar : libJars) { try { JarFile jarFile = new JarFile(libJar); Manifest manifest = jarFile.getManifest(); String mainClass = manifest.getMainAttributes(). getValue("Main-Class"); if (mainClass != null) { String jarPath = libJar.getAbsolutePath(); loadedHandler = this.handlerLoader.loadServerHandler(jarPath); this.serverHandlers.add(loadedHandler); this.dispatcher.addServerHandler(loadedHandler); } jarFile.close(); } catch (Exception e) { throw new AgentConfigException("Failed to load " + "'" + libJar + "': " + e.getMessage()); } } // Restore the class loader Thread.currentThread().setContextClassLoader(currentContext); } /** * Generates a new certificate DN. * @return The new DN that was generated. */ private String generateCertDN() { return "CAM-AGENT-" + SecurityUtil.generateRandomToken(); } /** * MONITOR METHOD: Get the monitors which are registered with the agent */ public String[] getMonitors() throws AgentMonitorException { return this.monitorClients.keySet().toArray(new String[0]); } /** * MONITOR METHOD: Get the time the agent started */ public double getStartTime() throws AgentMonitorException { return this.startTime; } /** * MONITOR METHOD: Get the time the agent has been running */ public double getUpTime() throws AgentMonitorException { return System.currentTimeMillis() - this.startTime; } /** * MONITOR METHOD: Get the JVMs total memory */ public double getJVMTotalMemory() throws AgentMonitorException { return Runtime.getRuntime().totalMemory(); } /** * MONITOR METHOD: Get the JVMs free memory */ public double getJVMFreeMemory() throws AgentMonitorException { return Runtime.getRuntime().freeMemory(); } /** * MONITOR METHOD: Get the # of active threads */ public double getNumActiveThreads() throws AgentMonitorException { return Thread.activeCount(); } public void registerMonitor(String monitorName, AgentMonitorInterface monitor) { this.monitorClients.put(monitorName, monitor); } public AgentMonitorValue[] getMonitorValues(String monitorName, String[] monitorKeys) { AgentMonitorInterface iface; iface = this.monitorClients.get(monitorName); if(iface == null){ AgentMonitorValue[] res; AgentMonitorValue badVal; badVal = new AgentMonitorValue(); badVal.setErrCode(AgentMonitorValue.ERR_BADMONITOR); res = new AgentMonitorValue[monitorKeys.length]; for(int i=0; i<res.length; i++){ res[i] = badVal; } return res; } return iface.getMonitorValues(monitorKeys); } /** * Determine if the Agent is currently running. * * @return true if the Agent is running (listening on its port) */ public boolean isRunning(){ return running.get(); } /** * Tell the Agent to close all connections, and die. * * @throws AgentRunningException indicating the Agent was not running * when die() was called. */ public void die() throws AgentRunningException { if(!running.get()){ throw new AgentRunningException("Agent is not running"); } listener.die(); running.set(false); storageProvider.dispose(); agentDiagnostics.die(); } /** * A stub API which allows handlers to set the connection listener * that the agent uses. */ public void setConnectionListener(AgentConnectionListener newListener) throws AgentRunningException { this.listener.setConnectionListener(newListener); } private void startPluginManagers() throws AgentStartException { try { Properties bootProps = this.config.getBootProperties(); String pluginDir; this.ppm = new ProductPluginManager(bootProps); this.ppm.init(); pluginDir = bootProps.getProperty(AgentConfig.PROP_PDK_PLUGIN_DIR[0]); Collection<PluginInfo> excludes = new TreeSet<PluginInfo>(new Comparator<PluginInfo>() { public int compare(PluginInfo p1, PluginInfo p2) { return p1.name.compareTo(p2.name); } }); Set<PluginInfo> plugins = new HashSet<PluginInfo>(); plugins.addAll(this.ppm.registerPlugins(pluginDir, excludes)); plugins.addAll(excludes); scanLegacyCustomDir(".."); Collection<PluginInfo> fromDirs = ppm.getAllPluginInfoDirectFromFileSystem(pluginDir); plugins = mergeByName(fromDirs, plugins); sendPluginStatusToServer(plugins); logger.info("Product Plugin Manager initalized"); } catch(Exception e){ // ...an unexpected exception has occurred that was not handled // log it and bail... logger.error("Error initializing plugins ", e); throw new AgentStartException("Unable to initialize plugin " + "manager: " + e.getMessage()); } } private void scanLegacyCustomDir(String startDir) { File dir = new File(startDir).getAbsoluteFile(); String fs = File.separator; while (dir != null) { File customDir = null; try { customDir = new File(dir, "hq-plugins"); } catch (NullPointerException e) { logger.warn("cannot scan custom plugin dir " + dir + fs + "hq-plugins, please note the plugins in this " + "directory will not be supported anymore instead they must to be managed via the " + "HQ Server Plugin Manager UI."); continue; } if (customDir.exists() && customDir.isDirectory()) { File[] files = customDir.listFiles(); for (File file : files) { String name = file.getName(); if (name.endsWith("-plugin.jar") || name.endsWith("-plugin.xml")) { logger.warn("WARNING - custom plugins on the agent are no longer supported. " + "Will not load plugin - " + name + ", instead add this plugin " + "to the HQ Server via the Plugin Manager UI."); } } return; } dir = dir.getParentFile(); } } /** merge fromDirs into the plugins Collection keyed by pluginInfo.name and return plugins */ private Set<PluginInfo> mergeByName(Collection<PluginInfo> fromDirs, Set<PluginInfo> plugins) { final Collection<String> pluginFiles = new HashSet<String>(); for (final PluginInfo info : plugins) { pluginFiles.add(info.jar); } for (final PluginInfo info : fromDirs) { if (!pluginFiles.contains(info.jar)) { plugins.add(info); } } return plugins; } private void sendPluginStatusToServer(final Collection<PluginInfo> plugins) { // server may be down or Provider may not be setup. Either way we want to retry until // the data is sent Thread thread = new Thread("PluginStatusSender") { @Override public void run() { while (true) { try { AgentStorageProvider provider = getStorageProvider(); if (provider == null) { logger.debug("trying to send plugin status to the server but " + "provider has not been setup, will sleep 5 seconds and retry"); Thread.sleep(5000); continue; } PlugininventoryCallbackClient client = new PlugininventoryCallbackClient( new StorageProviderFetcher(provider), plugins); logger.info("Sending plugin status to server"); client.sendPluginReportToServer(); logger.info("Successfully sent plugin status to server"); break; } catch (Exception e) { logger.warn("could not send plugin status to server, will retry: " + e); logger.debug(e,e); } try { Thread.sleep(5000); } catch (InterruptedException e) { logger.debug(e,e); } } } }; thread.setDaemon(true); thread.start(); } private void startHandlers() throws AgentStartException { for(int i=0; i<this.serverHandlers.size(); i++){ AgentServerHandler handler; handler = this.serverHandlers.get(i); try { handler.startup(this); } catch(AgentStartException exc){ logger.error("Error starting plugin " + handler, exc); throw exc; } catch(Exception exc){ logger.error("Unknown exception", exc); throw new AgentStartException("Error starting plugin " + handler, exc); } this.startedHandlers.add(handler); } } private final void postInitActions() throws AgentStartException { for(AgentServerHandler handler : this.startedHandlers) { handler.postInitActions() ; }//EO while there are more agents }//EOM //these files should have already been deleted on normal //jvm shutdown. however, agent.exe start + CTRL-c on windows //leaves them behind. cleanout at startup. private void cleanTmpDir(String tmp) { File dir = new File(tmp); if (!dir.exists()) { return; } File[] files = dir.listFiles(); if (files == null) { return; } for (File file : files) { try { file.delete(); } catch (SecurityException e) { } } } private PrintStream newLogStream(String stream, Properties props) { Logger logger = Logger.getLogger(stream); String logLevel = props.getProperty("agent.logLevel." + stream); if (logLevel == null) { return null; } Level level = Level.toLevel(logLevel); return new PrintStream(new LoggingOutputStream(logger, level), true); } private void redirectStreams(Properties props) { //redirect System.{out,err} to log4j appender PrintStream stream; if ((stream = newLogStream("SystemOut", props)) != null) { System.setOut(stream); } if ((stream = newLogStream("SystemErr", props)) != null) { System.setErr(stream); } } /** * Start the Agent's listening process. This routine blocks for the * entire execution of the Agent. * * @throws AgentStartException indicating the Agent was unable to start, * or one of the plugins failed to start. */ public void start() throws AgentStartException { running.set(true); boolean agentStarted = false; try { logger.info("Agent starting up, bundle name="+getCurrentAgentBundle()); Properties bootProps = this.config.getBootProperties(); String tmpDir = bootProps.getProperty(AgentConfig.PROP_TMPDIR[0]); if (tmpDir != null) { try { // update plugins residing in tmp directory prior to cleaning it up List<String> updatedPlugins = AgentUpgradeManager.updatePlugins(bootProps); if (!updatedPlugins.isEmpty()) { logger.info("Successfully updated plugins: " + updatedPlugins); } } catch (IOException e) { logger.error("Failed to update plugins", e); } //this should always be the case. cleanTmpDir(tmpDir); System.setProperty("java.io.tmpdir", tmpDir); } this.monitorClients = new Hashtable<String, AgentMonitorInterface>(); this.registerMonitor("agent", this); this.registerMonitor("agent.commandListener", this.listener); redirectStreams(bootProps); // Load the agent transport in the server handler classloader. // This is necessary because we don't want the jboss remoting // classes in the root agent classloader - this causes conflicts // with the jboss plugins. String agentTransportLifecycleClass = "org.hyperic.hq.agent.server.AgentTransportLifecycleImpl"; try { Class<?> clazz = this.handlerClassLoader.loadClass(agentTransportLifecycleClass); Constructor<?> constructor = clazz.getConstructor( new Class[]{AgentDaemon.class, AgentConfig.class, AgentStorageProvider.class }); this.agentTransportLifecycle = (AgentTransportLifecycle)constructor.newInstance( new Object[]{this, getBootConfig(), getStorageProvider() }); } catch (ClassNotFoundException e) { throw new AgentStartException( "Cannot find agent transport lifecycle class: "+ agentTransportLifecycleClass); } this.startPluginManagers(); this.startHandlers(); // The started handlers should have already registered with the // agent transport lifecycle this.agentTransportLifecycle.startAgentTransport(); this.listener.setup(); tryForceAgentFailure(); logger.info("Agent started successfully"); this.sendNotification(NOTIFY_AGENT_UP, "we're up, baby!"); agentStarted = true; AgentStatsWriter statsWriter = new AgentStatsWriter(config); statsWriter.startWriter(); this.postInitActions() ; agentDiagnostics = AgentDiagnostics.getInstance(); agentDiagnostics.setConfig(config); agentDiagnostics.start(); this.listener.listenLoop(); this.sendNotification(NOTIFY_AGENT_DOWN, "goin' down, baby!"); statsWriter.stopWriter(); } catch(AgentStartException exc){ logger.error(exc.getMessage(), exc); throw exc; } catch(Exception exc){ logger.error("Error running agent", exc); throw new AgentStartException("Error running agent: " + exc.getMessage()); } catch(Throwable exc){ logger.error("Critical error running agent", exc); // We don't flush the storage here, since we may be out of // memory, etc -- stuff just isn't in a good state at all. if(this.storageProvider != null){ this.storageProvider.dispose(); this.storageProvider = null; } throw new AgentStartException("Critical shutdown"); } finally { if (!agentStarted) { logger.debug("Notifying that agent startup failed"); this.sendNotification(NOTIFY_AGENT_FAILED_START, "agent startup failed!"); } if (this.agentTransportLifecycle != null) { this.agentTransportLifecycle.stopAgentTransport(); } running.set(false); try { cleanup(); } catch(AgentRunningException e) { logger.error(e,e); } if(this.storageProvider != null) { this.storageProvider.dispose(); } logger.info("Agent shut down"); System.exit(0); } } private void tryForceAgentFailure() throws AgentStartException { String rollbackBundle = getBootConfig().getBootProperties() .getProperty(AgentConfig.PROP_ROLLBACK_AGENT_BUNDLE_UPGRADE[0]); if (getCurrentAgentBundle().equals(rollbackBundle)) { throw new AgentStartException(AgentConfig.PROP_ROLLBACK_AGENT_BUNDLE_UPGRADE[0]+ " property set to force rollback of agent bundle upgrade: "+ rollbackBundle); } } public static class RunnableAgent implements Runnable, AgentLifecycle { private final AgentConfig config; private AgentDaemon agent; public RunnableAgent(AgentConfig config) { this.config = config; } public void shutdown() { try { if (agent != null) { agent.die(); agent.cleanup(); } } catch (Throwable e) { logger.error(e,e); } } public void run() { boolean isConfigured = false; try { agent = AgentDaemon.newInstance(config); isConfigured = true; } catch (Throwable e) { logger.error("Agent configuration failed: ", e); } finally { if (!isConfigured) { cleanUpOnAgentConfigFailure(config); } } boolean isStarted = false; if (agent != null) { try { agent.start(); isStarted = true; } catch(AgentStartException e) { logger.error("Agent startup error: ", e); } catch (Exception e) { logger.error("Agent startup failed: ", e); } finally { if (!isStarted) { cleanUpOnAgentStartFailure(); } } } } private void cleanUpOnAgentConfigFailure(AgentConfig config) { try { AgentStartupCallback agentStartupCallback = new AgentStartupCallback(config); agentStartupCallback.onAgentStartup(false); } catch (Exception e) { logger.error("Failed to callback on startup failure.", e); } cleanUpOnAgentStartFailure(); } private void cleanUpOnAgentStartFailure() { if (!WrapperManager.isControlledByNativeWrapper()) { System.exit(-1); } else { rollbackAndRestartJVM(); } } // rollback agent bundle and issue JVM restart if in Java Service Wrapper mode private void rollbackAndRestartJVM() { logger.info("Attempting to rollback agent bundle"); boolean success = false; try { success = AgentUpgradeManager.rollback(); } catch (IOException e) { logger.error("Unable to rollback agent bundle", e); } if (success) { logger.info("Rollback of agent bundle was successful"); } else { logger.error("Rollback of agent bundle was not successful"); } logger.info("Restarting JVM..."); AgentUpgradeManager.restartJVM(); } } }