http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/20a32286/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/AgentLauncher.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/AgentLauncher.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/AgentLauncher.java new file mode 100644 index 0000000..932fe21 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/AgentLauncher.java @@ -0,0 +1,918 @@ +/* + * 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.internal.admin.api.jmx.impl; + +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 org.apache.logging.log4j.Logger; + +import org.apache.geode.GemFireException; +import org.apache.geode.SystemFailure; +import org.apache.geode.internal.admin.api.AdminException; +import org.apache.geode.internal.admin.api.jmx.Agent; +import org.apache.geode.internal.admin.api.jmx.AgentConfig; +import org.apache.geode.internal.admin.api.jmx.AgentFactory; +import org.apache.geode.distributed.internal.DistributionManager; +import org.apache.geode.internal.OSProcess; +import org.apache.geode.internal.PureJavaMode; +import org.apache.geode.internal.net.SocketCreator; +import org.apache.geode.internal.i18n.LocalizedStrings; +import org.apache.geode.internal.logging.LogService; +import org.apache.geode.internal.util.IOUtils; +import org.apache.geode.internal.util.JavaCommandBuilder; + +/** + * A command line utility inspired by the <code>CacheServerLauncher</code> that is responsible for + * administering a stand-along GemFire JMX {@link Agent}. + * <p/> + * + * @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(LocalizedStrings.AgentLauncher_AGENT_CONFIGURATION_PROPERTIES.toString()); + + 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) + " (" + + LocalizedStrings.AgentLauncher_DEFAULT.toLocalizedString() + " \"" + + 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(""); + + 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<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(); + + 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<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[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<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(LocalizedStrings.AgentLauncher_UNABLE_TO_DELETE_FILE_0 + .toLocalizedString(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( + 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); + } + } + + /** + * 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(createAgentProcessThreadGroup(), agent); + thread.setDaemon(true); + 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 { + DistributionManager.isDedicatedAdminVM = 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 ThreadGroup createAgentProcessThreadGroup() { + return new ThreadGroup(LocalizedStrings.AgentLauncher_STARTING_AGENT.toLocalizedString()) { + @Override + public void uncaughtException(final Thread t, final Throwable e) { + if (e instanceof VirtualMachineError) { + SystemFailure.setFailure((VirtualMachineError) e); + } + setServerError(LocalizedStrings.AgentLauncher_UNCAUGHT_EXCEPTION_IN_THREAD_0 + .toLocalizedString(t.getName()), e); + } + }; + } + + private Thread createAgentProcessThread(final ThreadGroup group, final Agent agent) { + return new Thread(group, createAgentProcessRunnable(agent), "Start agent"); + } + + private Runnable createAgentProcessRunnable(final Agent agent) { + return new Runnable() { + 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 = 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) { + logger.fatal(e.getMessage(), e); + 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)) { + agent.stop(); + final int exitCode = (isStatus(SHUTDOWN_PENDING_AFTER_FAILED_STARTUP) ? 1 : 0); + writeStatus(createStatus(this.status, SHUTDOWN)); + System.exit(exitCode); + } + } + } + + /** + * 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( + 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<String, Object> 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 <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, + 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. + * <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 { + 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 { + 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 && !PureJavaMode.isPure() && !OSProcess.exists(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. + * <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 = 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 <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 { + 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( + 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. + * <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(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=<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=<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"); + + 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(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 (VirtualMachineError e) { + SystemFailure.initiateFailure(e); + throw e; + } catch (Throwable t) { + 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}. + * <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( + 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 (exception != null) { + if (msg != null) { + buffer.append("\n").append(msg).append(" - "); + } else { + 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(); + } + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/20a32286/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/CacheServerJmxImpl.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/CacheServerJmxImpl.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/CacheServerJmxImpl.java new file mode 100644 index 0000000..ef6c7c4 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/CacheServerJmxImpl.java @@ -0,0 +1,588 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.management.MalformedObjectNameException; +import javax.management.Notification; +import javax.management.ObjectName; +import javax.management.modelmbean.ModelMBean; +import javax.naming.OperationNotSupportedException; + +import org.apache.commons.modeler.ManagedBean; +import org.apache.logging.log4j.Logger; + +import org.apache.geode.internal.admin.api.AdminException; +import org.apache.geode.internal.admin.api.CacheServerConfig; +import org.apache.geode.internal.admin.api.CacheVmConfig; +import org.apache.geode.internal.admin.api.ConfigurationParameter; +import org.apache.geode.internal.admin.api.StatisticResource; +import org.apache.geode.internal.admin.api.SystemMemberCache; +import org.apache.geode.internal.admin.api.SystemMemberCacheEvent; +import org.apache.geode.internal.admin.api.SystemMemberRegionEvent; +import org.apache.geode.internal.admin.api.CacheVm; +import org.apache.geode.internal.admin.api.impl.CacheServerImpl; +import org.apache.geode.internal.admin.api.impl.ConfigurationParameterImpl; +import org.apache.geode.internal.admin.ClientMembershipMessage; +import org.apache.geode.internal.admin.GemFireVM; +import org.apache.geode.internal.admin.StatResource; +import org.apache.geode.internal.i18n.LocalizedStrings; +import org.apache.geode.internal.logging.LogService; + +/** + * MBean representation of a {@link CacheVm}. + * + * @since GemFire 4.0 + */ +public class CacheServerJmxImpl extends CacheServerImpl + implements ManagedResource, CacheVmConfig, CacheServerConfig, SystemMemberJmx { + + private static final Logger logger = LogService.getLogger(); + + /** + * Interval in seconds between refreshes. Value less than one results in no refreshing + */ + private int refreshInterval = 0; + + /** The object name of this managed resource */ + private ObjectName objectName; + + /** The name of the MBean that will manage this resource */ + private String mbeanName; + + /** The ModelMBean that is configured to manage this resource */ + private ModelMBean modelMBean; + + /** Reference to the cache MBean representing a Cache in the Cache VM Member */ + private SystemMemberCacheJmxImpl managedSystemMemberCache; + + /** collection to collect all the resources created for this member */ + private Map<StatResource, StatisticResourceJmxImpl> managedStatisticsResourcesMap = + new HashMap<StatResource, StatisticResourceJmxImpl>(); + + ////////////////////// Constructors ////////////////////// + + /** + * Creates a new <code>CacheServerJmxImpl</code> for an existing cache server. + */ + CacheServerJmxImpl(AdminDistributedSystemJmxImpl system, GemFireVM vm) throws AdminException { + + super(system, vm); + initializeMBean(); + } + + /** + * Creates a new <code>CacheServerJmxImpl</code> for an newly-created cache server. + */ + CacheServerJmxImpl(AdminDistributedSystemJmxImpl system, CacheVmConfig config) + throws AdminException { + + super(system, config); + initializeMBean(); + } + + ////////////////////// Instance Methods ////////////////////// + + /** + * Creates and registers the MBean to manage this resource + */ + private void initializeMBean() throws AdminException { + // initialize Managed Resources for stats & cache first. + // initializeManagedResources(); + + this.mbeanName = new StringBuffer("GemFire.CacheVm:").append("id=") + .append(MBeanUtil.makeCompliantMBeanNameProperty(getId())).append(",type=") + .append(MBeanUtil.makeCompliantMBeanNameProperty(getType().getName())).toString(); + + this.objectName = + MBeanUtil.createMBean(this, addDynamicAttributes(MBeanUtil.lookupManagedBean(this))); + + // Refresh Interval + AdminDistributedSystemJmxImpl sysJmx = (AdminDistributedSystemJmxImpl) system; + if (sysJmx.getRefreshInterval() > 0) + this.refreshInterval = sysJmx.getRefreshInterval(); + } + + public String getMBeanName() { + return this.mbeanName; + } + + public ModelMBean getModelMBean() { + return this.modelMBean; + } + + public void setModelMBean(ModelMBean modelMBean) { + this.modelMBean = modelMBean; + } + + public ObjectName getObjectName() { + return this.objectName; + } + + public ManagedResourceType getManagedResourceType() { + return ManagedResourceType.CACHE_VM; + } + + /** + * Un-registers all the statistics & cache managed resource created for this member. After + * un-registering the resource MBean instances, clears managedStatisticsResourcesMap collection & + * sets managedSystemMemberCache to null. + * + * Creates ConfigurationParameterJmxImpl, StatisticResourceJmxImpl and SystemMemberCacheJmxImpl. + * But cleans up only StatisticResourceJmxImpl and SystemMemberCacheJmxImpl which are of type + * ManagedResource. + */ + public void cleanupResource() { + synchronized (this.managedStatisticsResourcesMap) { + ConfigurationParameter[] names = getConfiguration(); + if (names != null) { + for (int i = 0; i < names.length; i++) { + ConfigurationParameter parm = names[i]; + ((ConfigurationParameterImpl) parm).removeConfigurationParameterListener(this); + } + } + this.parms.clear(); + + Collection<StatisticResourceJmxImpl> statisticResources = + managedStatisticsResourcesMap.values(); + + for (StatisticResourceJmxImpl statisticResource : statisticResources) { + MBeanUtil.unregisterMBean(statisticResource); + } + + this.managedStatisticsResourcesMap.clear(); + } + + MBeanUtil.unregisterMBean(this.managedSystemMemberCache); + this.managedSystemMemberCache = null; + } + + /////////////////////// Configuration /////////////////////// + + @Override + public String getHost() { + return this.getConfig().getHost(); + } + + public void setHost(String host) { + this.getConfig().setHost(host); + } + + @Override + public String getWorkingDirectory() { + return this.getConfig().getWorkingDirectory(); + } + + @Override + public void setWorkingDirectory(String dir) { + this.getConfig().setWorkingDirectory(dir); + } + + @Override + public String getProductDirectory() { + return this.getConfig().getProductDirectory(); + } + + @Override + public void setProductDirectory(String dir) { + this.getConfig().setProductDirectory(dir); + } + + public String getRemoteCommand() { + return this.getConfig().getRemoteCommand(); + } + + public void setRemoteCommand(String remoteCommand) { + this.getConfig().setRemoteCommand(remoteCommand); + } + + public void validate() { + throw new UnsupportedOperationException(LocalizedStrings.SHOULDNT_INVOKE.toLocalizedString()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + throw new UnsupportedOperationException(LocalizedStrings.SHOULDNT_INVOKE.toLocalizedString()); + } + + public String getCacheXMLFile() { + return this.getConfig().getCacheXMLFile(); + } + + public void setCacheXMLFile(String cacheXMLFile) { + this.getConfig().setCacheXMLFile(cacheXMLFile); + } + + public String getClassPath() { + return this.getConfig().getClassPath(); + } + + public void setClassPath(String classpath) { + this.getConfig().setClassPath(classpath); + } + + // ------------------------------------------------------------------------- + // MBean attribute accessors/mutators + // ------------------------------------------------------------------------- + + /** + * Gets the interval in seconds between config refreshes + * + * @return the current refresh interval in seconds + */ + public int getRefreshInterval() { + return this.refreshInterval; + } + + /** + * Sets interval in seconds between cache config refreshes; zero or less turns off auto + * refreshing. Manual refreshing has no effect on when the next scheduled refresh will occur. + * + * @param refreshInterval the new refresh interval in seconds + */ + public void _setRefreshInterval(int refreshInterval) { + boolean isRegistered = MBeanUtil.isRefreshNotificationRegistered(this, + RefreshNotificationType.SYSTEM_MEMBER_CONFIG); + + if (isRegistered && (getRefreshInterval() == refreshInterval)) + return; + + this.refreshInterval = Helper.setAndReturnRefreshInterval(this, refreshInterval); + } + + /** + * RefreshInterval is now set only through the AdminDistributedSystem property refreshInterval. + * Attempt to set refreshInterval on CacheServerJmx MBean would result in an + * OperationNotSupportedException Auto-refresh is enabled on demand when a call to refreshConfig + * is made + * + * @param refreshInterval the new refresh interval in seconds + * @deprecated since 6.0 use DistributedSystemConfig.refreshInterval instead + */ + @Deprecated + public void setRefreshInterval(int refreshInterval) throws OperationNotSupportedException { + throw new OperationNotSupportedException( + LocalizedStrings.MANAGED_RESOURCE_REFRESH_INTERVAL_CANT_BE_SET_DIRECTLY + .toLocalizedString()); + } + + // ------------------------------------------------------------------------- + // MBean Operations + // ------------------------------------------------------------------------- + + public void refreshConfig() throws AdminException { + // 1st call to refreshConfig would trigger + // the auto-refresh if an interval is set + if (this.refreshInterval > 0) { + this._setRefreshInterval(this.refreshInterval); + } + + super.refreshConfig(); + } + + /** + * Initializes Cache & Statistics managed resources. + * + * @throws AdminException if initialization of managed resources fails + */ + // private void initializeManagedResources() throws AdminException { + // try { + // manageCache(); + // } catch (MalformedObjectNameException e) { + // throw new + // AdminException(LocalizedStrings.SystemMemberJmxImpl_EXCEPTION_OCCURRED_WHILE_INITIALIZING_0_MBEANS_FOR_1.toLocalizedString( + // new Object[] {"Cache", getId()}), + // e); + // } catch (AdminException ae) { + // if + // (LocalizedStrings.SystemMemberJmx_THIS_SYSTEM_MEMBER_DOES_NOT_HAVE_A_CACHE.toLocalizedString().equals(ae.getMessage())) + // { + // //ignore this exception for a cache-less peer + // } else { + // throw ae; + // } + // } + // try { + // manageStats(); + // } catch (MalformedObjectNameException e) { + // throw new + // AdminException(LocalizedStrings.SystemMemberJmxImpl_EXCEPTION_OCCURRED_WHILE_INITIALIZING_0_MBEANS_FOR_1.toLocalizedString( + // new Object[] {"Statistics", getId()}), + // e); + // } + // } + + /** + * Gets this member's cache. + * + * @return array of ObjectName for this member's cache + */ + public ObjectName manageCache() throws AdminException, MalformedObjectNameException { + return Helper.manageCache(this); + } + + /** + * Gets all active StatisticResources for this manager. + * + * @return array of ObjectName instances + */ + public ObjectName[] manageStats() throws AdminException, MalformedObjectNameException { + return Helper.manageStats(this); + } + + /** + * Gets the active StatisticResources for this manager, based on the typeName as the key + * + * @return ObjectName of StatisticResourceJMX instance + */ + public ObjectName[] manageStat(String statisticsTypeName) + throws AdminException, MalformedObjectNameException { + + return Helper.manageStat(this, statisticsTypeName); + } + + // ------------------------------------------------------------------------- + // JMX Notification listener + // ------------------------------------------------------------------------- + + /** + * Handles notification to refresh. Reacts by refreshing the values of this GemFireManager's + * ConfigurationParamaters. Any other notification is ignored. Given notification is handled only + * if there is any JMX client connected to the system. + * + * @param notification the JMX notification being received + * @param hb handback object is unused + */ + public void handleNotification(Notification notification, Object hb) { + AdminDistributedSystemJmxImpl systemJmx = (AdminDistributedSystemJmxImpl) this.system; + + if (!systemJmx.isRmiClientCountZero()) { + Helper.handleNotification(this, notification, hb); + } + } + + // ------------------------------------------------------------------------- + // Template methods overriden from superclass... + // ------------------------------------------------------------------------- + + /** + * Template method for creating instance of ConfigurationParameter. Overridden to return + * ConfigurationParameterJmxImpl. + */ + @Override + protected ConfigurationParameter createConfigurationParameter(String name, String description, + Object value, Class type, boolean userModifiable) { + return new ConfigurationParameterJmxImpl(name, description, value, type, userModifiable); + } + + + + /** + * Override createStatisticResource by instantiating StatisticResourceJmxImpl if it was not + * created earlier otherwise returns the same instance. + * + * @param stat StatResource reference for which this JMX resource is to be created + * @return StatisticResourceJmxImpl - JMX Implementation of StatisticResource + * @throws AdminException if constructing StatisticResourceJmxImpl instance fails + */ + @Override + protected StatisticResource createStatisticResource(StatResource stat) throws AdminException { + StatisticResourceJmxImpl managedStatisticResource = null; + + synchronized (this.managedStatisticsResourcesMap) { + /* + * Ensuring that a single instance of Statistic Resource is created per StatResource. + */ + StatisticResourceJmxImpl statisticResourceJmxImpl = managedStatisticsResourcesMap.get(stat); + if (statisticResourceJmxImpl != null) { + managedStatisticResource = statisticResourceJmxImpl; + } else { + managedStatisticResource = new StatisticResourceJmxImpl(stat, this); + managedStatisticResource.getStatistics();// inits timer + managedStatisticsResourcesMap.put(stat, managedStatisticResource); + } + } + return managedStatisticResource; + } + + /** + * Override createSystemMemberCache by instantiating SystemMemberCacheJmxImpl if it was not + * created earlier. + * + * @param vm GemFireVM reference for which this JMX resource is to be created + * @return SystemMemberCacheJmxImpl - JMX Implementation of SystemMemberCache + * @throws AdminException if constructing SystemMemberCacheJmxImpl instance fails + */ + @Override + protected SystemMemberCache createSystemMemberCache(GemFireVM vm) throws AdminException { + if (managedSystemMemberCache == null) { + managedSystemMemberCache = new SystemMemberCacheJmxImpl(vm); + } + return managedSystemMemberCache; + } + + // ------------------------------------------------------------------------- + // Create MBean attributes for each ConfigurationParameter + // ------------------------------------------------------------------------- + + /** + * Add MBean attribute definitions for each ConfigurationParameter. + * + * @param managed the mbean definition to add attributes to + * @return a new instance of ManagedBean copied from <code>managed</code> but with the new + * attributes added + */ + public ManagedBean addDynamicAttributes(ManagedBean managed) throws AdminException { + return Helper.addDynamicAttributes(this, managed); + } + + /** + * Cleans up Managed Resources created for the client that was connected to the server represented + * by this class. + * + * @param clientId id of the client to be removed + * @return List of ManagedResources associated with the client of given client id + */ + /* + * This clean up is for the clients. The clients are started with a loner DM. Hence the clientId + * is not supposed to contain '/' as per InternalDistributedMember.toString(). + */ + public List<ManagedResource> cleanupBridgeClientResources(String clientId) { + List<ManagedResource> returnedResources = new ArrayList<ManagedResource>(); + + String compatibleId = "id_" + MBeanUtil.makeCompliantMBeanNameProperty(clientId); + synchronized (this.managedStatisticsResourcesMap) { + Set<Entry<StatResource, StatisticResourceJmxImpl>> entrySet = + this.managedStatisticsResourcesMap.entrySet(); + + for (Iterator<Entry<StatResource, StatisticResourceJmxImpl>> it = entrySet.iterator(); it + .hasNext();) { + Entry<StatResource, StatisticResourceJmxImpl> entry = it.next(); + StatisticResourceJmxImpl resource = entry.getValue(); + if (resource.getMBeanName().contains(compatibleId)) { + it.remove(); // remove matching entry + returnedResources.add(resource); + } + } + } + return returnedResources; + } + + /** + * Implementation handles client membership changes. + * + * @param clientId id of the client for whom membership change happened + * @param eventType membership change type; one of {@link ClientMembershipMessage#JOINED}, + * {@link ClientMembershipMessage#LEFT}, {@link ClientMembershipMessage#CRASHED} + */ + public void handleClientMembership(String clientId, int eventType) { + String notifType = null; + List<ManagedResource> cleanedUp = null; + + if (eventType == ClientMembershipMessage.LEFT) { + notifType = NOTIF_CLIENT_LEFT; + cleanedUp = cleanupBridgeClientResources(clientId); + } else if (eventType == ClientMembershipMessage.CRASHED) { + notifType = NOTIF_CLIENT_CRASHED; + cleanedUp = cleanupBridgeClientResources(clientId); + } else if (eventType == ClientMembershipMessage.JOINED) { + notifType = NOTIF_CLIENT_JOINED; + } + + if (cleanedUp != null) { + for (ManagedResource resource : cleanedUp) { + MBeanUtil.unregisterMBean(resource); + } + } + + Helper.sendNotification(this, new Notification(notifType, this.modelMBean, + Helper.getNextNotificationSequenceNumber(), clientId)); + } + + /** + * Implementation handles creation of cache by extracting the details from the given event object + * and sending the {@link SystemMemberJmx#NOTIF_CACHE_CREATED} notification to the connected JMX + * Clients. + * + * @param event event object corresponding to the creation of the cache + */ + public void handleCacheCreate(SystemMemberCacheEvent event) { + Helper.sendNotification(this, new Notification(NOTIF_CACHE_CREATED, this.modelMBean, + Helper.getNextNotificationSequenceNumber(), Helper.getCacheEventDetails(event))); + } + + /** + * Implementation handles closure of cache by extracting the details from the given event object + * and sending the {@link SystemMemberJmx#NOTIF_CACHE_CLOSED} notification to the connected JMX + * Clients. + * + * @param event event object corresponding to the closure of the cache + */ + public void handleCacheClose(SystemMemberCacheEvent event) { + Helper.sendNotification(this, new Notification(NOTIF_CACHE_CLOSED, this.modelMBean, + Helper.getNextNotificationSequenceNumber(), Helper.getCacheEventDetails(event))); + } + + /** + * Implementation handles creation of region by extracting the details from the given event object + * and sending the {@link SystemMemberJmx#NOTIF_REGION_CREATED} notification to the connected JMX + * Clients. Region Path is set as User Data in Notification. + * + * @param event event object corresponding to the creation of a region + */ + public void handleRegionCreate(SystemMemberRegionEvent event) { + Notification notification = new Notification(NOTIF_REGION_CREATED, this.modelMBean, + Helper.getNextNotificationSequenceNumber(), Helper.getRegionEventDetails(event)); + + notification.setUserData(event.getRegionPath()); + + Helper.sendNotification(this, notification); + } + + /** + * Implementation should handle loss of region by extracting the details from the given event + * object and sending the {@link SystemMemberJmx#NOTIF_REGION_LOST} notification to the connected + * JMX Clients. Region Path is set as User Data in Notification. Additionally, it also clears the + * ManagedResources created for the region that is lost. + * + * @param event event object corresponding to the loss of a region + */ + public void handleRegionLoss(SystemMemberRegionEvent event) { + SystemMemberCacheJmxImpl cacheResource = this.managedSystemMemberCache; + + if (cacheResource != null) { + ManagedResource cleanedUp = cacheResource.cleanupRegionResources(event.getRegionPath()); + + if (cleanedUp != null) { + MBeanUtil.unregisterMBean(cleanedUp); + } + } + + Notification notification = new Notification(NOTIF_REGION_LOST, this.modelMBean, + Helper.getNextNotificationSequenceNumber(), Helper.getRegionEventDetails(event)); + + notification.setUserData(event.getRegionPath()); + + Helper.sendNotification(this, notification); + } +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/20a32286/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ConfigAttributeInfo.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ConfigAttributeInfo.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ConfigAttributeInfo.java new file mode 100755 index 0000000..e94f521 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ConfigAttributeInfo.java @@ -0,0 +1,66 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import org.apache.geode.internal.Assert; +import org.apache.geode.internal.admin.api.ConfigurationParameter; + +import javax.management.Descriptor; +import javax.management.modelmbean.DescriptorSupport; +import javax.management.modelmbean.ModelMBeanAttributeInfo; + +/** + * Subclass of AttributeInfo with {@link ConfigurationParameter} added for use as the + * {@link javax.management.modelmbean.ModelMBeanAttributeInfo} descriptor's <i>targetObject</i> + * value. + * + * @since GemFire 3.5 + * + */ +class ConfigAttributeInfo extends org.apache.commons.modeler.AttributeInfo { + private static final long serialVersionUID = -1918437700841687078L; + + private final ConfigurationParameterJmxImpl config; + + public ConfigAttributeInfo(ConfigurationParameterJmxImpl config) { + super(); + this.config = config; + } + + public ConfigurationParameterJmxImpl getConfig() { + return this.config; + } + + @Override + public ModelMBeanAttributeInfo createAttributeInfo() { + Descriptor desc = new DescriptorSupport(new String[] {"name=" + this.displayName, + "descriptorType=attribute", "currencyTimeLimit=-1", // always stale + "displayName=" + this.displayName, "getMethod=getJmxValue", "setMethod=setJmxValue"}); + + Assert.assertTrue(this.config != null, "Config target object is null!"); + desc.setField("targetObject", this.config); + + ModelMBeanAttributeInfo info = new ModelMBeanAttributeInfo(this.displayName, // name + this.type, // type + this.description, // description + this.readable, // isReadable + this.writeable, // isWritable + this.is, // isIs + desc); + + return info; + } +} + http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/20a32286/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ConfigurationParameterJmxImpl.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ConfigurationParameterJmxImpl.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ConfigurationParameterJmxImpl.java new file mode 100755 index 0000000..0476fdb --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/ConfigurationParameterJmxImpl.java @@ -0,0 +1,164 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import java.io.IOException; +import java.io.Serializable; + +import org.apache.logging.log4j.Level; + +import org.apache.geode.SystemFailure; +import org.apache.geode.internal.admin.api.UnmodifiableConfigurationException; +import org.apache.geode.internal.Assert; +import org.apache.geode.internal.admin.api.impl.ConfigurationParameterImpl; +import org.apache.geode.internal.i18n.LocalizedStrings; + +/** + * Provides MBean support for managing accessing a ConfigurationParameter. + * <p> + * Implements java.io.Serializable because several MBeans have attributes of type + * ConfigurationParameter. This means that calls to getMBeanInfo which may be serialized for remote + * clients will be broken unless those attributes support serialization. + * <p> + * TODO: refactor to implement ConfigurationParameter and delegate to ConfigurationParameterImpl. + * Wrap all delegate calls w/ e.printStackTrace() since the HttpAdaptor devours them + * + * @since GemFire 3.5 + * + */ +public class ConfigurationParameterJmxImpl extends ConfigurationParameterImpl + implements Serializable { + + private static final long serialVersionUID = -7822171853906772375L; + private boolean deserialized = false; + + // ------------------------------------------------------------------------- + // Constructor(s) + // ------------------------------------------------------------------------- + + protected ConfigurationParameterJmxImpl(String name, String description, Object value, Class type, + boolean userModifiable) { + super(name, description, value, type, userModifiable); + } + + protected ConfigurationParameterJmxImpl(String name, Object value) { + super(name, value); + } + + /** Constructor to allow serialization */ + protected ConfigurationParameterJmxImpl() { + super(); + } + + @Override + public void setValue(Object value) throws UnmodifiableConfigurationException { + if (deserialized) { + throw new UnsupportedOperationException( + LocalizedStrings.ConfigurationParameterJmxImpl_REMOTE_MUTATION_OF_CONFIGURATIONPARAMETER_IS_CURRENTLY_UNSUPPORTED + .toLocalizedString()); + } + try { + super.setValue(value); + } catch (UnmodifiableConfigurationException e) { + MBeanUtil.logStackTrace(Level.WARN, e); + throw e; + } catch (java.lang.RuntimeException e) { + MBeanUtil.logStackTrace(Level.WARN, e); + throw e; + } catch (VirtualMachineError err) { + SystemFailure.initiateFailure(err); + // If this ever returns, rethrow the error. We're poisoned + // now, so don't let this thread continue. + throw err; + } catch (java.lang.Error e) { + // Whenever you catch Error or Throwable, you must also + // catch VirtualMachineError (see above). However, there is + // _still_ a possibility that you are dealing with a cascading + // error condition, so you also need to check to see if the JVM + // is still usable: + SystemFailure.checkFailure(); + MBeanUtil.logStackTrace(Level.ERROR, e); + throw e; + } + } + + // ------------------------------------------------------------------------- + // HACK + // ------------------------------------------------------------------------- + public void setJmxValue(Integer value) throws UnmodifiableConfigurationException { + setValue(value); + } + + public void setJmxValue(String value) throws UnmodifiableConfigurationException { + setValue(value); + } + + public void setJmxValue(java.io.File value) throws UnmodifiableConfigurationException { + setValue(value); + } + + public void setJmxValue(Boolean value) throws UnmodifiableConfigurationException { + setValue(value); + } + + public Class getJmxValueType() { + if (isInetAddress() || isFile() || isOctal()) { + return java.lang.String.class; + } + return getValueType(); + } + + public Object getJmxValue() { + if (isInetAddress() || isFile() || isOctal()) { + return getValueAsString(); + } + return getValue(); + } + + /** + * Override writeObject which is used in serialization. This class is serialized when JMX client + * acquires MBeanInfo for ConfigurationParameter MBean. Super class is not serializable. + */ + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.writeObject(this.name); + out.writeObject(this.description); + out.writeObject(this.value); + out.writeObject(this.type); + out.writeBoolean(this.userModifiable); + } + + /** + * Override readObject which is used in serialization. Customize serialization of this exception + * to avoid escape of InternalRole which is not Serializable. + */ + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + String inName = (String) in.readObject(); + String inDescription = (String) in.readObject(); + Object inValue = in.readObject(); + Class inClass = (Class) in.readObject(); + boolean inUserModifiable = in.readBoolean(); + + Assert.assertTrue(inName != null); + Assert.assertTrue(inDescription != null); + Assert.assertTrue(inValue != null); + Assert.assertTrue(inClass != null); + + this.deserialized = true; + this.name = inName; + setInternalState(inDescription, inValue, inClass, inUserModifiable); + } + +} + http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/20a32286/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DistributedSystemHealthConfigJmxImpl.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DistributedSystemHealthConfigJmxImpl.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DistributedSystemHealthConfigJmxImpl.java new file mode 100644 index 0000000..af13bbb --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DistributedSystemHealthConfigJmxImpl.java @@ -0,0 +1,98 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import org.apache.geode.internal.admin.api.AdminException; +import org.apache.geode.internal.admin.api.GemFireHealth; +import org.apache.geode.internal.admin.api.impl.DistributedSystemHealthConfigImpl; +import javax.management.*; +import javax.management.modelmbean.*; + +/** + * The JMX "managed resource" that represents the configuration for the health of a distributed + * system. Basically, it provides the behavior of <code>DistributedSystemHealthConfigImpl</code>, + * but does some JMX stuff like registering beans with the agent. + * + * @see GemFireHealthJmxImpl#createDistributedSystemHealthConfig + * + * + * @since GemFire 3.5 + */ +public class DistributedSystemHealthConfigJmxImpl extends DistributedSystemHealthConfigImpl + implements ManagedResource { + + /** The <code>GemFireHealth</code> that we help configure */ + private GemFireHealth health; + + /** The name of the MBean that will manage this resource */ + private String mbeanName; + + /** The ModelMBean that is configured to manage this resource */ + private ModelMBean modelMBean; + + /** The JMX object name of the MBean for this managed resource */ + private final ObjectName objectName; + + /////////////////////// Constructors /////////////////////// + + /** + * Creates a new <code>DistributedSystemHealthCOnfigJmxImpl</code> that configures the health of + * the distributed system monitored by <code>health</code>. + */ + DistributedSystemHealthConfigJmxImpl(GemFireHealthJmxImpl health) throws AdminException { + + super(); + this.health = health; + this.mbeanName = + new StringBuffer().append(MBEAN_NAME_PREFIX).append("DistributedSystemHealthConfig,id=") + .append(MBeanUtil.makeCompliantMBeanNameProperty(health.getDistributedSystem().getId())) + .toString(); + this.objectName = MBeanUtil.createMBean(this); + } + + ////////////////////// Instance Methods ////////////////////// + + /** + * Applies the changes made to this config back to the health monitor. + * + * @see GemFireHealth#setDistributedSystemHealthConfig + */ + public void applyChanges() { + this.health.setDistributedSystemHealthConfig(this); + } + + public String getMBeanName() { + return this.mbeanName; + } + + public ModelMBean getModelMBean() { + return this.modelMBean; + } + + public void setModelMBean(ModelMBean modelMBean) { + this.modelMBean = modelMBean; + } + + public ManagedResourceType getManagedResourceType() { + return ManagedResourceType.DISTRIBUTED_SYSTEM_HEALTH_CONFIG; + } + + public ObjectName getObjectName() { + return this.objectName; + } + + public void cleanupResource() {} + +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/20a32286/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DistributionLocatorJmxImpl.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DistributionLocatorJmxImpl.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DistributionLocatorJmxImpl.java new file mode 100755 index 0000000..8da7482 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DistributionLocatorJmxImpl.java @@ -0,0 +1,163 @@ +/* + * 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.internal.admin.api.jmx.impl; + +import org.apache.geode.internal.admin.api.DistributionLocatorConfig; +import org.apache.geode.internal.admin.api.impl.AdminDistributedSystemImpl; +import org.apache.geode.internal.admin.api.impl.DistributionLocatorImpl; +import org.apache.geode.internal.i18n.LocalizedStrings; +import javax.management.ObjectName; +import javax.management.modelmbean.ModelMBean; + +/** + * Provides MBean support for managing a distribution locator. + * + */ +public class DistributionLocatorJmxImpl extends DistributionLocatorImpl + implements ManagedResource, DistributionLocatorConfig { + + /** The JMX object name of this managed resource */ + private ObjectName objectName; + + // ------------------------------------------------------------------------- + // Constructor(s) + // ------------------------------------------------------------------------- + + /** + * Constructs new instance of DistributionLocatorJmxImpl for managing a distribution locator + * service via JMX. + */ + public DistributionLocatorJmxImpl(DistributionLocatorConfig config, + AdminDistributedSystemImpl system) { + super(config, system); + initializeMBean(); + } + + /** Create and register the MBean to manage this resource */ + private void initializeMBean() { + this.mbeanName = + "GemFire:type=DistributionLocator,id=" + MBeanUtil.makeCompliantMBeanNameProperty(getId()); + this.objectName = MBeanUtil.createMBean(this, MBeanUtil.lookupManagedBean(this)); + } + + //////////////////////// Configuration //////////////////////// + + public String getHost() { + return this.getConfig().getHost(); + } + + public void setHost(String host) { + this.getConfig().setHost(host); + } + + public String getWorkingDirectory() { + return this.getConfig().getWorkingDirectory(); + } + + public void setWorkingDirectory(String dir) { + this.getConfig().setWorkingDirectory(dir); + } + + public String getProductDirectory() { + return this.getConfig().getProductDirectory(); + } + + public void setProductDirectory(String dir) { + this.getConfig().setProductDirectory(dir); + } + + public String getRemoteCommand() { + return this.getConfig().getRemoteCommand(); + } + + public void setRemoteCommand(String remoteCommand) { + this.getConfig().setRemoteCommand(remoteCommand); + } + + public java.util.Properties getDistributedSystemProperties() { + return this.getConfig().getDistributedSystemProperties(); + } + + public void setDistributedSystemProperties(java.util.Properties props) { + this.getConfig().setDistributedSystemProperties(props); + } + + public void validate() { + throw new UnsupportedOperationException(LocalizedStrings.SHOULDNT_INVOKE.toLocalizedString()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + throw new UnsupportedOperationException(LocalizedStrings.SHOULDNT_INVOKE.toLocalizedString()); + } + + public int getPort() { + return this.getConfig().getPort(); + } + + public void setPort(int port) { + this.getConfig().setPort(port); + } + + public String getBindAddress() { + return this.getConfig().getBindAddress(); + } + + public void setBindAddress(String bindAddress) { + this.getConfig().setBindAddress(bindAddress); + } + + // ------------------------------------------------------------------------- + // MBean attributes - accessors/mutators + // ------------------------------------------------------------------------- + + // ------------------------------------------------------------------------- + // JMX Notification listener + // ------------------------------------------------------------------------- + + // ------------------------------------------------------------------------- + // ManagedResource implementation + // ------------------------------------------------------------------------- + + /** The name of the MBean that will manage this resource */ + private String mbeanName; + + /** The ModelMBean that is configured to manage this resource */ + private ModelMBean modelMBean; + + public String getMBeanName() { + return this.mbeanName; + } + + public ModelMBean getModelMBean() { + return this.modelMBean; + } + + public void setModelMBean(ModelMBean modelMBean) { + this.modelMBean = modelMBean; + } + + public ObjectName getObjectName() { + return this.objectName; + } + + public ManagedResourceType getManagedResourceType() { + return ManagedResourceType.DISTRIBUTION_LOCATOR; + } + + public void cleanupResource() {} + +} + http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/20a32286/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DynamicManagedBean.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DynamicManagedBean.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DynamicManagedBean.java new file mode 100755 index 0000000..5693a5a --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/jmx/impl/DynamicManagedBean.java @@ -0,0 +1,143 @@ +/* + * 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.internal.admin.api.jmx.impl; + + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.modeler.AttributeInfo; +import org.apache.commons.modeler.OperationInfo; +import org.apache.commons.modeler.ManagedBean; + +/** + * Extends ManagedBean to allow for dynamically creating new instances of ManagedBean based on an + * existing instance of ManagedBean. + * + * @since GemFire 5.0.1 + */ +public class DynamicManagedBean extends org.apache.commons.modeler.ManagedBean { + private static final long serialVersionUID = 4051924500150228160L; + + public DynamicManagedBean(ManagedBean managed) { + super(); + + this.attributes = managed.getAttributes(); + this.className = managed.getClassName(); + this.constructors = managed.getConstructors(); + this.description = managed.getDescription(); + this.domain = managed.getDomain(); + this.group = managed.getGroup(); + this.name = managed.getName(); + this.fields = managed.getFields(); + this.notifications = managed.getNotifications(); + this.operations = managed.getOperations(); + this.type = managed.getType(); + + /* + * we don't use modelerType and it's nice to remove it to keep the list of attributes cleaned + * up... + */ + removeAttribute("modelerType"); + } + + /** + * Removes an attribute from this ManagedBean's attribute descriptor list. + * + * @param name the attribute to be removed + */ + public void removeAttribute(String name) { + if (name == null || name.length() < 1) { + return; + } + synchronized (this.attributes) { + List attributesList = new ArrayList(this.attributes.length); + for (int i = 0; i < this.attributes.length; i++) { + if (!name.equals(this.attributes[i].getName())) { + attributesList.add(this.attributes[i]); + } + } + this.attributes = + (AttributeInfo[]) attributesList.toArray(new AttributeInfo[attributesList.size()]); + + /* + * super.info should be nulled out anytime the structure is changed, such as altering the + * attributes, operations, or notifications + * + * however super.info is private, so we need the following hack to cause the super class to + * null it out for us... + */ + setType(this.type); // causes this in super: "this.info = null;" + } + } + + /** + * Removes the operation with the given name from thie <code>ManageBean</code>'s operation + * descriptor list. + * + * @since GemFire 4.0 + */ + public void removeOperation(String name) { + if (name == null || name.length() < 1) { + return; + } + + synchronized (operations) { + List operationsList = new ArrayList(this.operations.length); + for (int i = 0; i < this.operations.length; i++) { + if (!name.equals(this.operations[i].getName())) { + operationsList.add(this.operations[i]); + } + } + this.operations = + (OperationInfo[]) operationsList.toArray(new OperationInfo[operationsList.size()]); + + /* + * super.info should be nulled out anytime the structure is changed, such as altering the + * operations, operations, or notifications + * + * however super.info is private, so we need the following hack to cause the super class to + * null it out for us... + */ + setType(this.type); // causes this in super: "this.info = null;" + } + } + + /** + * Return a string representation of this managed bean. + */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer("DynamicManagedBean["); + sb.append("name="); + sb.append(name); + sb.append(", className="); + sb.append(className); + sb.append(", description="); + sb.append(description); + if (group != null) { + sb.append(", group="); + sb.append(group); + } + sb.append(", type="); + sb.append(type); + sb.append(", attributes="); + sb.append(Arrays.asList(attributes)); + sb.append("]"); + return (sb.toString()); + } +} +
