All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gemstone.gemfire.admin.jmx.internal.AgentLauncher Maven / Gradle / Ivy

/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License. See accompanying
 * LICENSE file.
 */

package com.gemstone.gemfire.admin.jmx.internal;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;

import com.gemstone.gemfire.GemFireException;
import com.gemstone.gemfire.SystemFailure;
import com.gemstone.gemfire.admin.AdminException;
import com.gemstone.gemfire.admin.jmx.Agent;
import com.gemstone.gemfire.admin.jmx.AgentConfig;
import com.gemstone.gemfire.admin.jmx.AgentFactory;
import com.gemstone.gemfire.distributed.internal.DistributionManager;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.OSProcess;
import com.gemstone.gemfire.internal.LogWriterImpl.GemFireThreadGroup;
import com.gemstone.gemfire.internal.SocketCreator;
import com.gemstone.gemfire.internal.cache.CacheServerLauncher;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.shared.NativeCalls;
import com.gemstone.gemfire.internal.util.IOUtils;
import com.gemstone.gemfire.internal.util.JavaCommandBuilder;

/**
 * A command line utility inspired by the CacheServerLauncher that is responsible for administering
 * a stand-along GemFire JMX {@link Agent}.
 * 

* @author David Whitlock * @since 3.5 */ public class AgentLauncher { /** Should the launch command be printed? */ public static final boolean PRINT_LAUNCH_COMMAND = Boolean.getBoolean(AgentLauncher.class.getSimpleName() + ".PRINT_LAUNCH_COMMAND"); /* constants used to define state */ static final int SHUTDOWN = 0; static final int STARTING = 1; protected static final int RUNNING = 2; static final int SHUTDOWN_PENDING = 3; static final int SHUTDOWN_PENDING_AFTER_FAILED_STARTUP = 4; static final int UNKNOWN = 6; /** Agent configuration options */ static final String AGENT_PROPS = "agent-props"; /** A flag to indicate if the current log file should be kept. Used only when 'start' is used to fork off the 'server' */ static final String APPENDTO_LOG_FILE = "appendto-log-file"; /** optional and additional classpath entries */ static final String CLASSPATH = "classpath"; /** The directory argument */ static final String DIR = "dir"; /** Extra VM arguments */ static final String VMARGS = "vmargs"; /** Environment arguments */ protected static final String ENVARGS = "envargs"; /** The directory in which the agent's output resides */ private File workingDirectory = null; /** A logger used for error messages */ protected LogWriterI18n logger = null; /** The Status object for the agent */ private Status status = null; /** base name for the agent to be launched */ protected final String basename; /** The name for the start up log file */ private final String startLogFileName; /** The name of the status file */ private final String statusFileName; /** * Instantiates an AgentLauncher for execution and control of the GemFire JMX Agent process. This constructor is * package private to prevent direct instantiation or subclassing by classes outside this package, but does allow * the class to be tested as needed. *

* @param basename base name for the application to be launched */ protected AgentLauncher(final String basename) { assert basename != null : "The base name used by the AgentLauncher to create files cannot be null!"; this.basename = basename; final String formattedBasename = this.basename.toLowerCase().replace(" ", ""); this.startLogFileName = "start_" + formattedBasename + ".log"; this.statusFileName = "." + formattedBasename + ".ser"; } /** * Prints information about the agent configuration options */ public void configHelp() { PrintStream out = System.out; Properties props = AgentConfigImpl.getDefaultValuesForAllProperties(); out.println("\n"); out.println(LocalizedStrings.AgentLauncher_AGENT_CONFIGURATION_PROPERTIES.toString()); SortedMap map = new TreeMap(); int maxLength = 0; for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) { String prop = (String) iter.next(); int length = prop.length(); if (length > maxLength) { maxLength = length; } map.put(prop, AgentConfigImpl.getPropertyDescription(prop) + " (" + LocalizedStrings.AgentLauncher_DEFAULT.toLocalizedString() + " \"" + props.getProperty(prop) + "\")"); } Iterator> entries = map.entrySet().iterator(); while (entries.hasNext()) { Entry entry = entries.next(); String prop = entry.getKey(); out.print(" "); out.println(prop); String description = entry.getValue(); StringTokenizer st = new StringTokenizer(description, " "); out.print(" "); int printed = 6; while (st.hasMoreTokens()) { String word = st.nextToken(); if (printed + word.length() > 72) { out.print("\n "); printed = 6; } out.print(word); out.print(" "); printed += word.length() + 1; } out.println(""); } out.println(""); System.exit(1); } /** * Returns a map that maps the name of the start options to its value on the command line. If no value is * specified on the command line, a default one is provided. */ protected Map getStartOptions(final String[] args) throws Exception { final Map options = new HashMap(); options.put(APPENDTO_LOG_FILE, "false"); options.put(DIR, IOUtils.tryGetCanonicalFileElseGetAbsoluteFile(new File("."))); final List vmArgs = new ArrayList(); options.put(VMARGS, vmArgs); final Properties agentProps = new Properties(); options.put(AGENT_PROPS, agentProps); final Map envArgs = new HashMap(); options.put(ENVARGS, envArgs); for (final String arg : args) { if (arg.startsWith("-classpath=")) { options.put(CLASSPATH, arg.substring("-classpath=".length())); } else if (arg.startsWith("-dir=")) { final File workingDirectory = processDirOption(options, arg.substring("-dir=".length())); System.setProperty(AgentConfigImpl.AGENT_PROPSFILE_PROPERTY_NAME, new File(workingDirectory, AgentConfig.DEFAULT_PROPERTY_FILE).getPath()); } else if (arg.startsWith("-J")) { vmArgs.add(arg.substring(2)); } else if (arg.contains("=")) { final int index = arg.indexOf("="); final String key = arg.substring(0, index); final String value = arg.substring(index + 1); if (key.startsWith("-")) { processStartOption(key.substring(1), value, options, vmArgs, envArgs, agentProps); } else { // if appendto-log-file is set, put it in options; it is not set as an // agent prop if (key.equals(APPENDTO_LOG_FILE)) { options.put(APPENDTO_LOG_FILE, value); continue; } // verify the property is valid AgentConfigImpl.getPropertyDescription(key); // Note, the gfAgentPropertyFile System property is ultimately read // in the constructor of the AgentImpl class in order to make any // properties defined in this file not only accessible to the // DistributedSystem but to the GemFire Agent as well. if (key.equals(AgentConfigImpl.PROPERTY_FILE_NAME)) { System.setProperty(AgentConfigImpl.AGENT_PROPSFILE_PROPERTY_NAME, value); } // The Agent properties file (specified with the command-line // key=value) is used to pass configuration settings to the GemFire // DistributedSystem. A property file can be passed using the // property-file command-line switch is a large number of properties // are specified, or the properties maybe individually specified on // the command-line as property=value arguments. processStartArg(key, value, options, vmArgs, agentProps); } } else if (arg.equalsIgnoreCase("-password")) { processStartOption(arg.substring(1), null, options, vmArgs, envArgs, agentProps); } } return options; } protected void processServerEnv(Properties props) throws Exception { } /** * Process a command-line options of the form "key=value". */ protected void processStartArg(final String key, final String value, final Map options, final List vmArgs, final Properties props) throws Exception { props.setProperty(key, value); } /** * Process a command-line option of the form "-key=value". * @param envArgs environment variables in the form of 'name=value'. */ protected void processStartOption(final String key, final String value, final Map options, final List vmArgs, final Map envArgs, final Properties props) throws Exception { processUnknownStartOption(key, value, options, vmArgs, props); } /** * Process a command-line option of the form "-key=value" unknown to the base class. */ protected void processUnknownStartOption(final String key, final String value, final Map options, final List vmArgs, final Properties props) { throw new IllegalArgumentException(LocalizedStrings .AgentConfigImpl_UNKNOWN_CONFIG_PROPERTY_0.toLocalizedString(key)); } /** * After parsing the command line arguments, spawn the Java VM that will host the GemFire JMX Agent. */ public void start(final String[] args) throws Exception { final Map options = getStartOptions(args); workingDirectory = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile((File) options.get(DIR)); // verify that any GemFire JMX Agent process has been properly shutdown and delete any remaining status files... verifyAndClearStatus(); // start the GemFire JMX Agent process... runCommandLine(options, buildCommandLine(options)); // wait for the GemFire JMX Agent process to complete startup and begin running... // it is also possible the Agent process may fail to start, so this should not wait indefinitely unless // the status file was not successfully written to pollAgentUntilRunning(); System.exit(0); } private void verifyAndClearStatus() throws Exception { final Status status = getStatus(); if (status != null && status.state != SHUTDOWN) { throw new IllegalStateException(LocalizedStrings.AgentLauncher_JMX_AGENT_EXISTS_BUT_WAS_NOT_SHUTDOWN.toLocalizedString()); } deleteStatus(); } private String[] buildCommandLine(final Map options) { final List commands = JavaCommandBuilder.buildCommand(this.getClass().getName(), (String) options.get(CLASSPATH), null, (List) options.get(VMARGS)); commands.add("server"); commands.add("-dir=" + workingDirectory); final Properties agentProps = (Properties) options.get(AGENT_PROPS); for (final Object key : agentProps.keySet()) { commands.add(key + "=" + agentProps.get(key.toString())); } return commands.toArray(new String[commands.size()]); } private void printCommandLine(final String[] commandLine) { if (PRINT_LAUNCH_COMMAND) { System.out.print("Starting " + this.basename + " with command:\n"); for (final String command : commandLine) { System.out.print(command); System.out.print(' '); } System.out.println(); } } private int runCommandLine(final Map options, final String[] commandLine) throws IOException { // initialize the startup log starting with a fresh log file (where all startup messages are printed) final File startLogFile = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile( new File(workingDirectory, startLogFileName)); if (startLogFile.exists() && !startLogFile.delete()) { throw new IOException(LocalizedStrings.AgentLauncher_UNABLE_TO_DELETE_FILE_0.toLocalizedString( startLogFile.getAbsolutePath())); } Map env = (Map)options.get(ENVARGS); if (env == null) { env = new HashMap(); } // read the passwords from command line SocketCreator.readSSLProperties(env, true); printCommandLine(commandLine); final int pid = OSProcess.bgexec(commandLine, workingDirectory, startLogFile, false, env); System.out.println(LocalizedStrings .AgentLauncher_STARTING_JMX_AGENT_WITH_PID_0.toLocalizedString(pid)); return pid; } private void pollAgentUntilRunning() throws Exception { Status status = spinReadStatus(); // TODO this loop could recurse indefinitely if the GemFire JMX Agent's state never changes from STARTING // to something else (like RUNNING), which could happen if server process fails to startup correctly // and did not or could not write to the status file! // TODO should we really allow the InterruptedException from the Thread.sleep call to break this loop (yeah, I // think so given the fact this could loop indefinitely)? while (status != null && status.state == STARTING) { Thread.sleep(500); status = spinReadStatus(); } if (status == null) { // TODO throw a more appropriate Exception here! throw new Exception(LocalizedStrings.AgentLauncher_NO_AVAILABLE_STATUS.toLocalizedString()); } else { System.out.println(status); } } /** * Extracts configuration information used when launching the cache server VM. */ protected Map getServerOptions(final String[] args) throws Exception { final Map options = new HashMap(); options.put(APPENDTO_LOG_FILE, "false"); options.put(DIR, IOUtils.tryGetCanonicalFileElseGetAbsoluteFile(new File("."))); final Properties agentProps = new Properties(); options.put(AGENT_PROPS, agentProps); for (final String arg : args) { if (arg.startsWith("-classpath=")) { options.put(CLASSPATH, arg.substring("-classpath=".length())); } else if (arg.startsWith("-dir=")) { final File workingDirectory = processDirOption(options, arg.substring("-dir=".length())); System.setProperty(AgentConfigImpl.AGENT_PROPSFILE_PROPERTY_NAME, new File(workingDirectory, AgentConfig.DEFAULT_PROPERTY_FILE).getPath()); } else if (arg.contains("=")) { final int index = arg.indexOf("="); final String key = arg.substring(0, index); final String value = arg.substring(index + 1); if (key.startsWith("-")) { options.put(key.substring(1), value); } else { // if appendto-log-file is set, put it in options; it is not set as an // agent prop if (key.equals(APPENDTO_LOG_FILE)) { options.put(APPENDTO_LOG_FILE, value); continue; } if (key.equals(AgentConfigImpl.PROPERTY_FILE_NAME)) { System.setProperty(AgentConfigImpl.AGENT_PROPSFILE_PROPERTY_NAME, value); } agentProps.setProperty(key, value); } } System.out.println(); } // any last minute processing of environment variables or otherwise processServerEnv(agentProps); return options; } /** * Starts the GemFire JMX Agent "server" process with the given command line arguments. */ public void server(final String[] args) throws Exception { final Map options = getServerOptions(args); // make the process a UNIX daemon if possible String errMsg = makeDaemon(); workingDirectory = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile((File) options.get(DIR)); Status status = createStatus(this.basename, STARTING, OSProcess.getId()); status.msg = errMsg; writeStatus(status); Agent agent = startAgentVM((Properties) options.get(AGENT_PROPS), status); // periodically check and see if the JMX Agent has been told to stop pollAgentForPendingShutdown(agent); } protected String makeDaemon() { try { NativeCalls.getInstance().daemonize(null); } catch (UnsupportedOperationException e) { return "WARNING: " + e.getMessage(); } catch (IllegalStateException ignored) { } return null; } protected Agent startAgentVM(Properties props, Status status) throws Exception { final Agent agent = createAgent(props); final Thread thread = createAgentProcessThread( createAgentProcessThreadGroup(), agent, status); thread.setDaemon(true); thread.start(); return agent; } private Agent createAgent(final Properties props) throws IOException, AdminException { DistributionManager.isDedicatedAdminVM = true; SystemFailure.setExitOK(true); final AgentConfigImpl config = createAgentConfig(props); // see bug 43760 if (config.getLogFile() == null || "".equals(config.getLogFile().trim())) { config.setLogFile(AgentConfigImpl.DEFAULT_LOG_FILE); } logger = config.getLogWriter(); OSProcess.redirectOutput(new File(config.getLogFile())); // redirect output to the configured log file return AgentFactory.getAgent(config); } private AgentConfigImpl createAgentConfig(Properties props) { return new AgentConfigImpl(props); } private ThreadGroup createAgentProcessThreadGroup() { return new GemFireThreadGroup(LocalizedStrings.AgentLauncher_STARTING_AGENT.toLocalizedString()) { @Override public void uncaughtException(final Thread t, final Throwable e) { if (e instanceof Error && SystemFailure.isJVMFailureError((Error)e)) { SystemFailure.setFailure((Error)e); } setServerError(LocalizedStrings.AgentLauncher_UNCAUGHT_EXCEPTION_IN_THREAD_0.toLocalizedString(t.getName()), e); } }; } private Thread createAgentProcessThread(final ThreadGroup group, final Agent agent, final Status status) { return new Thread(group, createAgentProcessRunnable(agent, status), "Start agent"); } private Runnable createAgentProcessRunnable(final Agent agent, final Status status) { return new Runnable() { public void run() { try { agent.start(); writeStatus(createStatus(status, RUNNING)); } catch (IOException e) { e.printStackTrace(); } catch (GemFireException e) { e.printStackTrace(); handleGemFireException(e); } } private void handleGemFireException(final GemFireException e) { String message = LocalizedStrings.AgentLauncher_SERVER_FAILED_TO_START_0.toLocalizedString(e.getMessage()); if (e.getCause() != null) { if (e.getCause().getCause() != null) { message += ", " + e.getCause().getCause().getMessage(); } } setServerError(null, new Exception(message)); } }; } /** * Notes that an error has occurred in the agent and that it has shut down because of it. */ private void setServerError(final String message, final Throwable cause) { try { writeStatus(createStatus(this.basename, SHUTDOWN_PENDING_AFTER_FAILED_STARTUP, OSProcess.getId(), message, cause)); } catch (Exception e) { if (logger != null) { logger.severe(e); } else { e.printStackTrace(); } System.exit(1); } } private void pollAgentForPendingShutdown(final Agent agent) throws Exception { while (true) { pause(500); spinReadStatus(); if (isStatus(SHUTDOWN_PENDING, SHUTDOWN_PENDING_AFTER_FAILED_STARTUP)) { disconnect(agent); final int exitCode = (isStatus(SHUTDOWN_PENDING_AFTER_FAILED_STARTUP) ? 1 : 0); writeStatus(createStatus(this.status, SHUTDOWN)); System.exit(exitCode); } } } protected void disconnect(Agent agent) throws Exception { agent.stop(); } /** * Extracts configuration information for stopping a agent based on the * contents of the command line. This method can also be used with getting * the status of a agent. */ protected Map getStopOptions(final String[] args) throws Exception { final Map options = new HashMap(); options.put(DIR, IOUtils.tryGetCanonicalFileElseGetAbsoluteFile(new File("."))); for (final String arg : args) { if (arg.equals("stop") || arg.equals("status")) { // expected } else if (arg.startsWith("-dir=")) { processDirOption(options, arg.substring("-dir=".length())); } else { throw new Exception(LocalizedStrings.AgentLauncher_UNKNOWN_ARGUMENT_0.toLocalizedString(arg)); } } return options; } /** * Stops a running JMX Agent by setting the status to "shutdown pending". */ public void stop(final String[] args) throws Exception { final Map options = getStopOptions(args); workingDirectory = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile((File) options.get(DIR)); int exitStatus = 1; if (new File(workingDirectory, statusFileName).exists()) { spinReadStatus(); if (!isStatus(SHUTDOWN)) { writeStatus(createStatus(this.basename, SHUTDOWN_PENDING, status.pid)); } pollAgentForShutdown(); if (isStatus(SHUTDOWN)) { System.out.println(LocalizedStrings.AgentLauncher_0_HAS_STOPPED.toLocalizedString(this.basename)); deleteStatus(); exitStatus = 0; } else { System.out.println(LocalizedStrings.AgentLauncher_TIMEOUT_WAITING_FOR_0_TO_SHUTDOWN_STATUS_IS_1 .toLocalizedString(this.basename, status)); } } else { System.out.println(LocalizedStrings.AgentLauncher_THE_SPECIFIED_WORKING_DIRECTORY_0_CONTAINS_NO_STATUS_FILE .toLocalizedString(workingDirectory)); } System.exit(exitStatus); } private void pollAgentForShutdown() throws InterruptedException { final long endTime = (System.currentTimeMillis() + 20000); long clock = 0; while (clock < endTime && !isStatus(SHUTDOWN)) { pause(500); spinReadStatus(); clock = System.currentTimeMillis(); } } /** * Prints the status of the GemFire JMX Agent running in the configured working directory. */ public void status(final String[] args) throws Exception { this.workingDirectory = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile((File) getStopOptions(args).get(DIR)); System.out.println(getStatus()); System.exit(0); } /** * Returns the Status of the GemFire JMX Agent in the workingDirectory. */ protected Status getStatus() throws Exception { Status status; if (new File(workingDirectory, statusFileName).exists()) { status = spinReadStatus(); } else { status = createStatus(this.basename, SHUTDOWN, 0, LocalizedStrings.AgentLauncher_0_IS_NOT_RUNNING_IN_SPECIFIED_WORKING_DIRECTORY_1 .toLocalizedString(this.basename, this.workingDirectory), null); } return status; } /** * Determines if the Status.state is one of the specified states in the given array of states. Note, the status * of the Agent, as indicated in the .agent.ser status file, should never have a written value of UNKNOWN. *

* @param states an array of possible acceptable states satisfying the condition of the Agent's status. * @return a boolean value indicating whether the Agent's status satisfies one of the specified states. */ private boolean isStatus(final Integer... states) { return (this.status != null && Arrays.asList(defaultToUnknownStateIfNull(states)).contains(this.status.state)); } /** * Removes an agent's status file */ protected void deleteStatus() throws IOException { final File statusFile = new File(workingDirectory, statusFileName); if (statusFile.exists() && !statusFile.delete()) { throw new IOException("Could not delete status file (" + statusFile.getAbsolutePath() + ")"); } } /** * Reads the GemFire JMX Agent's status from the status file (.agent.ser) in it's working directory. *

* @return a Status object containing the state persisted to the .agent.ser file in the working directory * and representing the status of the Agent * @throws IOException if the status file was unable to be read. * @throws RuntimeException if the class of the object written to the .agent.ser file is not of type Status. */ protected Status readStatus() throws IOException { FileInputStream fileIn = null; ObjectInputStream objectIn = null; try { fileIn = new FileInputStream(new File(workingDirectory, statusFileName)); objectIn = new ObjectInputStream(fileIn); this.status = (Status) objectIn.readObject(); return this.status; } catch (ClassNotFoundException e) { throw new RuntimeException(e); } finally { IOUtils.close(objectIn); IOUtils.close(fileIn); } } /** * A wrapper method for the readStatus method to make one last check for the GemFire JMX Agent process if running * with the native libraries. * @return the Status object as returned from readStatus unless running in native mode and a determination is made * such that the Agent process is not running. * @throws IOException if the state of the Agent process could not be read from the .agent.ser status file. * @see #readStatus() */ protected Status nativeReadStatus() throws IOException { Status status = readStatus(); // @see Bug #32760 - the bug is still possible in pure Java mode if (status != null && status.state != SHUTDOWN && status.pid > 0 && !CacheServerLauncher.isExistingProcess(status.pid)) { status = createStatus(status, SHUTDOWN); } return status; } /** * Reads the JMX Agent's status from the .agent.ser status file. If the status file cannot be read due * to I/O problems, the method will keep attempting to read the file for up to 20 seconds. *

* @return the Status of the GemFire JMX Agent as determined by the .agent.ser status file, or natively * based on the presence/absence of the Agent process. */ protected Status spinReadStatus() { Status status = null; final long endTime = (System.currentTimeMillis() + 20000); long clock = 0; while (status == null && clock < endTime) { try { status = nativeReadStatus(); } catch (Exception ignore) { // see bug 31575 // see bug 36998 // try again after a short delay... the status file might have been read prematurely before it existed // or while the server was trying to write to it resulting in a possible EOFException, or other IOException. pause(500); } finally { clock = System.currentTimeMillis(); } } return status; } /** * Sets the status of the GemFire JMX Agent by serializing a Status object to a status file * in the Agent's working directory. *

* @param status the Status object representing the state of the Agent process to persist to disk. * @return the written Status object. * @throws IOException if the Status could not be successfully persisted to disk. */ public Status writeStatus(final Status status) throws IOException { FileOutputStream fileOut = null; ObjectOutputStream objectOut = null; try { fileOut = new FileOutputStream(new File(workingDirectory, statusFileName)); objectOut = new ObjectOutputStream(fileOut); objectOut.writeObject(status); objectOut.flush(); this.status = status; return this.status; } finally { IOUtils.close(objectOut); IOUtils.close(fileOut); } } protected static Status createStatus(final String basename, final int state, final int pid) { return createStatus(basename, state, pid, null, null); } protected static Status createStatus(final String basename, final int state, final int pid, final String msg, final Throwable t) { final Status status = new Status(basename); status.state = state; status.pid = pid; status.msg = msg; status.exception = t; return status; } protected static Status createStatus(final Status status, final int state) { assert status != null : "The status to clone cannot be null!"; return createStatus(status.baseName, state, status.pid, status.msg, status.exception); } protected static Integer[] defaultToUnknownStateIfNull(final Integer... states) { return (states != null ? states : new Integer[] { UNKNOWN }); } protected static boolean pause(final int milliseconds) { try { Thread.sleep(milliseconds); return true; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } protected static File processDirOption(final Map options, final String dirValue) throws FileNotFoundException { final File workingDirectory = new File(dirValue); if (!workingDirectory.exists()) { throw new FileNotFoundException(LocalizedStrings.AgentLauncher_THE_INPUT_WORKING_DIRECTORY_DOES_NOT_EXIST_0 .toLocalizedString(dirValue)); } options.put(DIR, workingDirectory); return workingDirectory; } /** * Prints usage information for the AgentLauncher to the command line. *

* @param message a String to output to the command line indicating the user error. */ protected static void usage(final String message) { final PrintStream out = System.out; out.println("\n** " + message + "\n"); out.println("agent start [-J]* [-dir=

] [prop=value]*"); out.println(LocalizedStrings.AgentLauncher_STARTS_THE_GEMFIRE_JMX_AGENT.toLocalizedString()); out.println("\t" + LocalizedStrings.AgentLauncher_VMARG.toLocalizedString() ); out.println("\t" + LocalizedStrings.AgentLauncher_DIR.toLocalizedString()); out.println("\t" + LocalizedStrings.AgentLauncher_PROP.toLocalizedString()); out.println("\t" + LocalizedStrings.AgentLauncher_SEE_HELP_CONFIG.toLocalizedString()); out.println(); out.println("agent stop [-dir=]"); out.println(LocalizedStrings.AgentLauncher_STOPS_A_GEMFIRE_JMX_AGENT.toLocalizedString()); out.println("\t" + LocalizedStrings.AgentLauncher_DIR.toLocalizedString()); out.println(""); out.println("agent status [-dir=]"); out.println(LocalizedStrings.AgentLauncher_REPORTS_THE_STATUS_AND_THE_PROCESS_ID_OF_A_GEMFIRE_JMX_AGENT.toLocalizedString()); out.println("\t" + LocalizedStrings.AgentLauncher_DIR.toLocalizedString()); out.println(); System.exit(1); } /** * Bootstrap method to launch the GemFire JMX Agent process to monitor and manage a GemFire Distributed System/Cache. * Main will read the arguments passed on the command line and dispatch the command to the appropriate handler. */ public static void main(final String[] args) { if (args.length < 1) { usage(LocalizedStrings.AgentLauncher_MISSING_COMMAND.toLocalizedString()); } // TODO is this only needed on 'agent server'? 'agent {start|stop|status}' technically do no run any GemFire Cache // or DS code inside the current process. SystemFailure.loadEmergencyClasses(); final AgentLauncher launcher = new AgentLauncher("Agent"); launcher.run(args); } protected void run(final String[] args) { try { final String command = args[0]; if (command.equalsIgnoreCase("start")) { start(args); } else if (command.equalsIgnoreCase("server")) { server(args); } else if (command.equalsIgnoreCase("stop")) { stop(args); } else if (command.equalsIgnoreCase("status")) { status(args); } else if (command.toLowerCase().matches("-{0,2}help")) { if (args.length > 1) { final String topic = args[1]; if (topic.equals("config")) { configHelp(); } else { usage(LocalizedStrings.AgentLauncher_NO_HELP_AVAILABLE_FOR_0.toLocalizedString(topic)); } } usage(LocalizedStrings.AgentLauncher_AGENT_HELP.toLocalizedString()); } else { usage(LocalizedStrings.AgentLauncher_UNKNOWN_COMMAND_0.toLocalizedString(command)); } } catch (Throwable t) { Error err; if (t instanceof Error && SystemFailure.isJVMFailureError( err = (Error)t)) { SystemFailure.initiateFailure(err); // If this ever returns, rethrow the error. We're poisoned // now, so don't let this thread continue. throw err; } SystemFailure.checkFailure(); t.printStackTrace(); System.err.println(LocalizedStrings.AgentLauncher_ERROR_0.toLocalizedString(t.getLocalizedMessage())); System.exit(1); } } /** * A class representing the current state of the GemFire JMX Agent process. Instances of this class are serialized * to a {@linkplain #statusFileName file} on disk in the specified working directory {@linkplain #workingDirectory}. *

* @see #SHUTDOWN * @see #STARTING * @see #RUNNING * @see #SHUTDOWN_PENDING * @see #SHUTDOWN_PENDING_AFTER_FAILED_STARTUP */ // TODO refactor this class and internalize the state // TODO refactor the class and make immutable protected static class Status implements Serializable { private static final long serialVersionUID = -7758402454664266174L; int pid = 0; int state = 0; final String baseName; String msg; Throwable exception; public Status(final String baseName) { this.baseName = baseName; } @Override public String toString() { final StringBuilder buffer = new StringBuilder(); if (pid == Integer.MIN_VALUE && state == SHUTDOWN && msg != null) { buffer.append(msg); } else { buffer.append(LocalizedStrings.AgentLauncher_0_PID_1_STATUS.toLocalizedString(this.baseName, pid)); switch (state) { case SHUTDOWN: buffer.append(LocalizedStrings.AgentLauncher_SHUTDOWN.toLocalizedString()); break; case STARTING: buffer.append(LocalizedStrings.AgentLauncher_STARTING.toLocalizedString()); break; case RUNNING: buffer.append(LocalizedStrings.AgentLauncher_RUNNING.toLocalizedString()); break; case SHUTDOWN_PENDING: buffer.append(LocalizedStrings.AgentLauncher_SHUTDOWN_PENDING.toLocalizedString()); break; case SHUTDOWN_PENDING_AFTER_FAILED_STARTUP: buffer.append(LocalizedStrings.AgentLauncher_SHUTDOWN_PENDING_AFTER_FAILED_STARTUP.toLocalizedString()); break; default: buffer.append(LocalizedStrings.AgentLauncher_UNKNOWN.toLocalizedString()); break; } if (msg != null) { buffer.append("\n").append(msg); } if (exception != null) { buffer.append(" - "); { buffer.append("\n " + LocalizedStrings.AgentLauncher_EXCEPTION_IN_0_1 .toLocalizedString(this.baseName, exception.getMessage()) + " - "); } buffer.append(LocalizedStrings.AgentLauncher_SEE_LOG_FILE_FOR_DETAILS.toLocalizedString()); } } return buffer.toString(); } } }