| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You 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. |
| */ |
| package org.apache.geode.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.lang.Thread.UncaughtExceptionHandler; |
| 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 org.apache.logging.log4j.Logger; |
| |
| import org.apache.geode.GemFireException; |
| import org.apache.geode.SystemFailure; |
| import org.apache.geode.admin.AdminException; |
| import org.apache.geode.admin.jmx.Agent; |
| import org.apache.geode.admin.jmx.AgentConfig; |
| import org.apache.geode.admin.jmx.AgentFactory; |
| import org.apache.geode.distributed.internal.ClusterDistributionManager; |
| import org.apache.geode.internal.ExitCode; |
| import org.apache.geode.internal.OSProcess; |
| import org.apache.geode.internal.net.SocketCreator; |
| import org.apache.geode.internal.util.IOUtils; |
| import org.apache.geode.internal.util.JavaCommandBuilder; |
| import org.apache.geode.logging.internal.executors.LoggingThread; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| |
| /** |
| * A command line utility that is responsible for administering a stand-alone GemFire JMX |
| * {@link Agent}. |
| * |
| * @since GemFire 3.5 |
| */ |
| public class AgentLauncher { |
| |
| private static final Logger logger = LogService.getLogger(); |
| |
| /** 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; |
| 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"; |
| |
| /** The directory in which the agent's output resides */ |
| private File workingDirectory = null; |
| |
| /** The Status object for the agent */ |
| private Status status = null; |
| |
| /** base name for the agent to be launched */ |
| private 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. |
| * <p/> |
| * |
| * @param basename base name for the application to be launched |
| */ |
| 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("Agent configuration properties"); |
| |
| SortedMap<String, String> map = new TreeMap<String, String>(); |
| |
| int maxLength = 0; |
| for (Iterator<Object> 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) + " (Default \"" |
| + props.getProperty(prop) + "\")"); |
| } |
| |
| Iterator<Entry<String, String>> entries = map.entrySet().iterator(); |
| while (entries.hasNext()) { |
| Entry<String, String> 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(""); |
| |
| ExitCode.FATAL.doSystemExit(); |
| } |
| |
| /** |
| * 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<String, Object> getStartOptions(final String[] args) throws Exception { |
| final Map<String, Object> options = new HashMap<String, Object>(); |
| |
| options.put(APPENDTO_LOG_FILE, "false"); |
| options.put(DIR, IOUtils.tryGetCanonicalFileElseGetAbsoluteFile(new File("."))); |
| |
| final List<String> vmArgs = new ArrayList<String>(); |
| options.put(VMARGS, vmArgs); |
| |
| 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.startsWith("-J")) { |
| vmArgs.add(arg.substring(2)); |
| } else if (arg.contains("=")) { |
| final int index = arg.indexOf("="); |
| final String prop = arg.substring(0, index); |
| final String value = arg.substring(index + 1); |
| |
| // if appendto-log-file is set, put it in options; it is not set as an agent prop |
| if (prop.equals(APPENDTO_LOG_FILE)) { |
| options.put(APPENDTO_LOG_FILE, value); |
| continue; |
| } |
| |
| // verify the property is valid |
| AgentConfigImpl.getPropertyDescription(prop); |
| |
| // 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 (AgentConfigImpl.PROPERTY_FILE_NAME.equals(prop)) { |
| 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. |
| agentProps.setProperty(prop, value); |
| } |
| } |
| |
| return options; |
| } |
| |
| /** |
| * 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<String, Object> 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(); |
| |
| ExitCode.NORMAL.doSystemExit(); |
| } |
| |
| private void verifyAndClearStatus() throws Exception { |
| final Status status = getStatus(); |
| |
| if (status != null && status.state != SHUTDOWN) { |
| throw new IllegalStateException( |
| "JMX Agent exists but was not shutdown."); |
| } |
| |
| deleteStatus(); |
| } |
| |
| private String[] buildCommandLine(final Map<String, Object> options) { |
| final List<String> commands = JavaCommandBuilder.buildCommand(AgentLauncher.class.getName(), |
| (String) options.get(CLASSPATH), null, (List<String>) 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[0]); |
| } |
| |
| 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<String, Object> 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(String.format("Unable to delete file %s.", |
| startLogFile.getAbsolutePath())); |
| } |
| |
| Map<String, String> env = new HashMap<String, String>(); |
| // 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( |
| String.format("Starting JMX Agent with pid: %s", 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("No available status."); |
| } else { |
| System.out.println(status); |
| } |
| } |
| |
| /** |
| * Starts the GemFire JMX Agent "server" process with the given command line arguments. |
| */ |
| public void server(final String[] args) throws Exception { |
| final Map<String, Object> options = getStartOptions(args); |
| |
| workingDirectory = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile((File) options.get(DIR)); |
| |
| writeStatus(createStatus(this.basename, STARTING, OSProcess.getId())); |
| |
| final Agent agent = createAgent((Properties) options.get(AGENT_PROPS)); |
| |
| final Thread thread = createAgentProcessThread(agent); |
| thread.start(); |
| |
| // periodically check and see if the JMX Agent has been told to stop |
| pollAgentForPendingShutdown(agent); |
| } |
| |
| private Agent createAgent(final Properties props) throws IOException, AdminException { |
| ClusterDistributionManager.setIsDedicatedAdminVM(true); |
| SystemFailure.setExitOK(true); |
| |
| final AgentConfigImpl config = new AgentConfigImpl(props); |
| |
| // see bug 43760 |
| if (config.getLogFile() == null || "".equals(config.getLogFile().trim())) { |
| config.setLogFile(AgentConfigImpl.DEFAULT_LOG_FILE); |
| } |
| |
| // LOG:TODO: redirectOutput called here |
| OSProcess.redirectOutput(new File(config.getLogFile())); // redirect output to the configured |
| // log file |
| |
| return AgentFactory.getAgent(config); |
| } |
| |
| private UncaughtExceptionHandler createUncaughtExceptionHandler() { |
| return (t, e) -> { |
| if (e instanceof VirtualMachineError) { |
| SystemFailure.setFailure((VirtualMachineError) e); |
| } |
| setServerError(String.format("Uncaught exception in thread %s", |
| t.getName()), e); |
| }; |
| } |
| |
| private Thread createAgentProcessThread(final Agent agent) { |
| Thread thread = new LoggingThread("Start agent", createAgentProcessRunnable(agent)); |
| thread.setUncaughtExceptionHandler(createUncaughtExceptionHandler()); |
| return thread; |
| } |
| |
| private Runnable createAgentProcessRunnable(final Agent agent) { |
| return new Runnable() { |
| @Override |
| public void run() { |
| try { |
| agent.start(); |
| writeStatus(createStatus(AgentLauncher.this.basename, RUNNING, OSProcess.getId())); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } catch (GemFireException e) { |
| e.printStackTrace(); |
| handleGemFireException(e); |
| } |
| } |
| |
| private void handleGemFireException(final GemFireException e) { |
| String message = String.format("Server failed to start: %s", |
| 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) { |
| logger.fatal(e.getMessage(), e); |
| ExitCode.FATAL.doSystemExit(); |
| } |
| } |
| |
| private void pollAgentForPendingShutdown(final Agent agent) throws Exception { |
| while (true) { |
| pause(500); |
| spinReadStatus(); |
| |
| if (isStatus(SHUTDOWN_PENDING, SHUTDOWN_PENDING_AFTER_FAILED_STARTUP)) { |
| agent.stop(); |
| final ExitCode exitCode = |
| (isStatus(SHUTDOWN_PENDING_AFTER_FAILED_STARTUP) ? ExitCode.FATAL : ExitCode.NORMAL); |
| writeStatus(createStatus(this.status, SHUTDOWN)); |
| exitCode.doSystemExit(); |
| } |
| } |
| } |
| |
| /** |
| * 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<String, Object> getStopOptions(final String[] args) throws Exception { |
| final Map<String, Object> options = new HashMap<String, Object>(); |
| |
| 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( |
| String.format("Unknown argument: %s", 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<String, Object> options = getStopOptions(args); |
| |
| workingDirectory = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile((File) options.get(DIR)); |
| |
| ExitCode exitCode = ExitCode.FATAL; |
| |
| 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(String.format("The %s has shut down.", this.basename)); |
| deleteStatus(); |
| exitCode = ExitCode.NORMAL; |
| } else { |
| System.out |
| .println(String.format("Timeout waiting for %s to shutdown, status is: %s", |
| this.basename, status)); |
| } |
| } else { |
| System.out.println( |
| String.format("The specified working directory (%s) contains no status file", |
| workingDirectory)); |
| } |
| |
| exitCode.doSystemExit(); |
| } |
| |
| 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()); |
| ExitCode.NORMAL.doSystemExit(); |
| } |
| |
| /** |
| * Returns the <code>Status</code> of the GemFire JMX Agent in the <code>workingDirectory</code>. |
| */ |
| protected Status getStatus() throws Exception { |
| Status status; |
| |
| if (new File(workingDirectory, statusFileName).exists()) { |
| status = spinReadStatus(); |
| } else { |
| status = createStatus(this.basename, SHUTDOWN, 0, |
| String.format("%s is not running in the specified working directory: (%s).", |
| 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. |
| * <p/> |
| * |
| * @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 { |
| deleteStatus(workingDirectory); |
| } |
| |
| void deleteStatus(final File workingDirectory) 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. |
| * <p/> |
| * |
| * @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 { |
| return readStatus(workingDirectory); |
| } |
| |
| Status readStatus(final File workingDirectory) 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); |
| } |
| } |
| |
| /** |
| * 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. |
| * <p/> |
| * |
| * @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 = readStatus(); |
| } 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 <code>Status</code> object to a |
| * status file in the Agent's working directory. |
| * <p/> |
| * |
| * @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 { |
| return writeStatus(status, workingDirectory); |
| } |
| |
| Status writeStatus(final Status status, final File workingDirectory) 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<String, Object> options, final String dirValue) |
| throws FileNotFoundException { |
| final File workingDirectory = new File(dirValue); |
| |
| if (!workingDirectory.exists()) { |
| throw new FileNotFoundException( |
| String.format("The input working directory does not exist: %s", |
| dirValue)); |
| } |
| |
| options.put(DIR, workingDirectory); |
| |
| return workingDirectory; |
| } |
| |
| /** |
| * Prints usage information for the AgentLauncher to the command line. |
| * <p/> |
| * |
| * @param message a String to output to the command line indicating the user error. |
| */ |
| private static void usage(final String message) { |
| final PrintStream out = System.out; |
| |
| out.println("\n** " + message + "\n"); |
| |
| out.println("agent start [-J<vmarg>]* [-dir=<dir>] [prop=value]*"); |
| out.println("Starts the GemFire JMX Agent"); |
| out.println("\t" |
| + "<vmarg> a VM-option passed to the agent's VM, example -J-Xmx1024M for a 1 Gb heap"); |
| out.println("\t<dir> Directory in which agent runs, default is the current directory"); |
| out.println("\t<prop> A configuration property/value passed to the agent"); |
| out.println("\t(see help config for more details)"); |
| out.println(); |
| |
| out.println("agent stop [-dir=<dir>]"); |
| out.println("Stops a GemFire JMX Agent"); |
| out.println("\t<dir> Directory in which agent runs, default is the current directory"); |
| out.println(""); |
| out.println("agent status [-dir=<dir>]"); |
| out.println( |
| "Reports the status and the process id of a GemFire JMX Agent"); |
| out.println("\t<dir> Directory in which agent runs, default is the current directory"); |
| out.println(); |
| |
| ExitCode.FATAL.doSystemExit(); |
| } |
| |
| /** |
| * 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("Missing command"); |
| } |
| |
| // 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"); |
| |
| try { |
| final String command = args[0]; |
| |
| if (command.equalsIgnoreCase("start")) { |
| launcher.start(args); |
| } else if (command.equalsIgnoreCase("server")) { |
| launcher.server(args); |
| } else if (command.equalsIgnoreCase("stop")) { |
| launcher.stop(args); |
| } else if (command.equalsIgnoreCase("status")) { |
| launcher.status(args); |
| } else if (command.toLowerCase().matches("-{0,2}help")) { |
| if (args.length > 1) { |
| final String topic = args[1]; |
| |
| if (topic.equals("config")) { |
| launcher.configHelp(); |
| } else { |
| usage(String.format("No help available for %s", topic)); |
| } |
| } |
| |
| usage("agent help"); |
| } else { |
| usage(String.format("Unknown command: %s", command)); |
| } |
| } catch (VirtualMachineError e) { |
| SystemFailure.initiateFailure(e); |
| throw e; |
| } catch (Throwable t) { |
| SystemFailure.checkFailure(); |
| t.printStackTrace(); |
| System.err.println( |
| String.format("Error : %s", t.getLocalizedMessage())); |
| ExitCode.FATAL.doSystemExit(); |
| } |
| } |
| |
| /** |
| * 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}. |
| * <p/> |
| * |
| * @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 |
| 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( |
| String.format("%s pid: %d status: ", this.baseName, pid)); |
| |
| switch (state) { |
| case SHUTDOWN: |
| buffer.append("shutdown"); |
| break; |
| case STARTING: |
| buffer.append("starting"); |
| break; |
| case RUNNING: |
| buffer.append("running"); |
| break; |
| case SHUTDOWN_PENDING: |
| buffer.append("shutdown pending"); |
| break; |
| case SHUTDOWN_PENDING_AFTER_FAILED_STARTUP: |
| buffer.append("shutdown pending after failed startup"); |
| break; |
| default: |
| buffer.append("unknown"); |
| break; |
| } |
| |
| if (exception != null) { |
| if (msg != null) { |
| buffer.append("\n").append(msg).append(" - "); |
| } else { |
| buffer.append("\n " + String.format("Exception in %s : %s ", |
| this.baseName, exception.getMessage()) + " - "); |
| } |
| buffer |
| .append("See log file for details."); |
| } |
| } |
| |
| return buffer.toString(); |
| } |
| } |
| |
| } |