You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by kl...@apache.org on 2016/10/18 18:57:47 UTC

[17/42] incubator-geode git commit: Move Admin API to internal

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/EnabledManagedEntityController.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/EnabledManagedEntityController.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/EnabledManagedEntityController.java
new file mode 100755
index 0000000..aae8b2f
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/EnabledManagedEntityController.java
@@ -0,0 +1,411 @@
+/*
+ * 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.impl;
+
+import org.apache.geode.internal.admin.api.AdminDistributedSystem;
+import org.apache.geode.internal.admin.api.DistributedSystemConfig;
+import org.apache.geode.internal.admin.api.ManagedEntity;
+import org.apache.geode.internal.admin.api.ManagedEntityConfig;
+import org.apache.geode.distributed.internal.DistributionConfig;
+import org.apache.geode.internal.ProcessOutputReader;
+import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.LoggingThreadGroup;
+import org.apache.geode.internal.logging.log4j.LocalizedMessage;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Properties;
+
+import static org.apache.geode.distributed.ConfigurationProperties.*;
+
+/**
+ * Implements the actual administration (starting, stopping, etc.) of
+ * GemFire {@link ManagedEntity}s.  It {@link Runtime#exec(java.lang.String) executes}
+ * commands to administer the entities based on information provided
+ * by the {@link InternalManagedEntity} object.  Note that it does not
+ * use <code>SystemAdmin</code> to manage "local" entities; it always
+ * execs the scripts.
+ *
+ * <P>
+ *
+ * This class is a refactoring of <code>Systemcontroller</code>,
+ * <code>RemoteCommand</code>, and <code>LocatorRemoteCommand</code>.
+ *
+ * @since GemFire 4.0
+ */
+class EnabledManagedEntityController implements ManagedEntityController {
+  private static final Logger logger = LogService.getLogger();
+
+//  /** A lock to ensure that only entity is managed at a time.  See bug
+//   * 31374. */
+//  private static Object startStopLock = new Object();
+
+  /** Known strings found in output indicating error. */
+  private static final String[] ERROR_OUTPUTS = new String[] {
+    "No such file or directory",
+    "The system cannot find the file specified.",
+    "Access is denied.",
+    "cannot open",
+    "ERROR"
+  };
+
+  /** Token in command prefix to be replaced with actual HOST */
+  private static final String HOST = "{HOST}";
+
+  /** Token in command prefix to be replaced with actual execution CMD */
+  private static final String CMD = "{CMD}";
+
+  //////////////////////  Instance Fields  //////////////////////
+
+  /** The thread group in which threads launched by this system
+   * controller reside. */
+  private final ThreadGroup threadGroup;
+
+  /** System to which the managed entities belong */
+  private final AdminDistributedSystem system;
+
+  ///////////////////////  Constructors  ///////////////////////
+
+  /**
+   * Creates a new <code>ManagedEntityController</code> for entities
+   * in the given distributed system.
+   */
+  EnabledManagedEntityController(AdminDistributedSystem system) {
+    this.system = system;
+    this.threadGroup =
+      LoggingThreadGroup.createThreadGroup("ManagedEntityController threads", logger);
+  }
+
+  /////////////////////  Instance Methods  /////////////////////
+
+  /**
+   * Returns <code>true</code> if the <code>output</code> string
+   * contains a known error message.
+   */
+  private boolean outputIsError(String output) {
+    if (output == null) return false;
+    boolean error = false;
+    for (int i = 0; i < ERROR_OUTPUTS.length; i++) {
+      error = output.indexOf(ERROR_OUTPUTS[i]) > -1;
+      if (error) return error;
+    }
+    return error;
+  }
+
+  /**
+   * Executes a command using {@link Runtime#exec(java.lang.String)}.
+   *
+   * @param command
+   *        The full command to remotely execute
+   *
+   * @return Output from the command that was executed or
+   *         <code>null</code> if the executing the command failed.
+   */
+  protected String execute(String command,
+                         InternalManagedEntity entity) {
+    /* TODO: this is getting ugly... clients of this method really need to
+       have the ability to do their own parsing/checking of 'output' */
+    if (command == null || command.length() == 0) {
+      throw new IllegalArgumentException(LocalizedStrings.ManagedEntityController_EXECUTION_COMMAND_IS_EMPTY.toLocalizedString());
+    }
+
+    File workingDir =
+      new File(entity.getEntityConfig().getWorkingDirectory());
+    logger.info(LocalizedMessage.create(LocalizedStrings.ManagedEntityController_EXECUTING_REMOTE_COMMAND_0_IN_DIRECTORY_1, new Object[] {command, workingDir}));
+    Process p = null;
+    try {
+      p = Runtime.getRuntime().exec(command, null /* env */,
+                                    workingDir);
+
+    } catch (java.io.IOException e) {
+      logger.fatal(LocalizedMessage.create(LocalizedStrings.ManagedEntityController_WHILE_EXECUTING_0, command), e);
+      return null;
+    }
+
+    final ProcessOutputReader pos = new ProcessOutputReader(p);
+    int retCode = pos.getExitCode();
+    final String output = pos.getOutput();
+    logger.info(LocalizedMessage.create(LocalizedStrings.ManagedEntityController_RESULT_OF_EXECUTING_0_IS_1, new Object[] {command, Integer.valueOf(retCode)}));
+    logger.info(LocalizedMessage.create(LocalizedStrings.ManagedEntityController_OUTPUT_OF_0_IS_1, new Object[] {command, output}));
+
+    if (retCode != 0 || outputIsError(output)) {
+      logger.warn(LocalizedMessage.create(LocalizedStrings.ManagedEntityController_REMOTE_EXECUTION_OF_0_FAILED, command));
+      return null;
+    }
+
+    return output;
+  }
+
+  /** Returns true if the path ends with a path separator. */
+  private boolean endsWithSeparator(String path) {
+    return path.endsWith("/") || path.endsWith("\\");
+  }
+
+  /** Translates the path between Windows and UNIX. */
+  private String getOSPath(String path) {
+    if (pathIsWindows(path)) {
+      return path.replace('/', '\\');
+    } else {
+      return path.replace('\\', '/');
+    }
+  }
+
+//  /** Returns true if the path is on Windows. */
+//  private boolean pathIsWindows(File path) {
+//    return pathIsWindows(path.toString());
+//  }
+
+  /** Returns true if the path is on Windows. */
+  private boolean pathIsWindows(String path) {
+    if (path != null && path.length() > 1) {
+      return (Character.isLetter(path.charAt(0)) && path.charAt(1) == ':') ||
+        (path.startsWith("//") || path.startsWith("\\\\"));
+    }
+    return false;
+  }
+
+  /**
+   * If the managed entity resides on a remote host, then
+   * <code>command</code> is munged to take the remote command into account.
+   *
+   * @throws IllegalStateException
+   *        If a remote command is required, but one has not been
+   *        specified.
+   */
+  private String arrangeRemoteCommand(InternalManagedEntity entity,
+                                      String cmd) {
+
+    String host = entity.getEntityConfig().getHost();
+    if (InetAddressUtil.isLocalHost(host)) {
+      // No arranging necessary
+      return cmd;
+    }
+
+    String prefix = entity.getEntityConfig().getRemoteCommand();
+    if (prefix == null || prefix.length() <= 0) {
+      prefix = entity.getDistributedSystem().getRemoteCommand();
+    }
+
+    if (prefix == null || prefix.length() <= 0) {
+      throw new IllegalStateException(LocalizedStrings.ManagedEntityController_A_REMOTE_COMMAND_MUST_BE_SPECIFIED_TO_OPERATE_ON_A_MANAGED_ENTITY_ON_HOST_0
+          .toLocalizedString(host));
+    }
+
+    int hostIdx = prefix.indexOf(HOST);
+    int cmdIdx = prefix.indexOf(CMD);
+    if (hostIdx == -1 && cmdIdx == -1) {
+      return prefix + " " + host + " " + cmd;
+    }
+
+    if (hostIdx >= 0) {
+      String start = prefix.substring(0, hostIdx);
+      String end = null;
+      if (hostIdx + HOST.length() >= prefix.length()) {
+        end = "";
+      } else {
+        end = prefix.substring(hostIdx + HOST.length());
+      }
+      prefix = start + host + end;
+      cmdIdx = prefix.indexOf(CMD); //recalculate;
+    }
+
+    if (cmdIdx >= 0) {
+      String start = prefix.substring(0, cmdIdx);
+      String end = null;
+      if (cmdIdx + CMD.length() >= prefix.length()) {
+        end = "";
+      } else {
+        end = prefix.substring(cmdIdx + CMD.length());
+      }
+      prefix = start + cmd + end;
+    }
+    return prefix;
+  }
+
+  /**
+   * Returns the full path to the executable in
+   * <code>$GEMFIRE/bin</code> taking into account the {@linkplain
+   * ManagedEntityConfig#getProductDirectory product directory} and the
+   * platform's file separator.
+   *
+   * <P>
+   *
+   * Note: we should probably do a better job of determine whether or
+   * not the machine on which the entity runs is Windows or Linux.
+   *
+   * @param executable
+   *        The name of the executable that resides in
+   *        <code>$GEMFIRE/bin</code>.
+   */
+  public String getProductExecutable(InternalManagedEntity entity,
+                                     String executable) {
+    String productDirectory =
+      entity.getEntityConfig().getProductDirectory();
+    String path = null;
+    File productDir = new File(productDirectory);
+//    if (productDir != null) (cannot be null)
+    {
+      path = productDir.getPath();
+      if (!endsWithSeparator(path)) {
+        path += File.separator;
+      }
+      path += "bin" + File.separator;
+    }
+//    else {
+//      path = "";
+//    }
+
+    String bat = "";
+    if (pathIsWindows(path)) {
+      bat = ".bat";
+    }
+    return getOSPath(path) + executable + bat;
+  }
+
+  /**
+   * Builds optional SSL command-line arguments.  Returns null if SSL is not
+   * enabled for the distributed system.
+   */
+  public String buildSSLArguments(DistributedSystemConfig config) {
+    Properties sslProps = buildSSLProperties(config, true);
+    if (sslProps == null) return null;
+
+    StringBuffer sb = new StringBuffer();
+    for (Iterator iter = sslProps.keySet().iterator(); iter.hasNext();) {
+      String key = (String) iter.next();
+      String value = sslProps.getProperty(key);
+      sb.append(" -J-D" + key + "=" + value);
+    }
+
+    return sb.toString();
+  }
+
+  /**
+   * Builds optional SSL properties for DistributionLocator. Returns null if SSL
+   * is not enabled for the distributed system.
+   *
+   * @param forCommandLine
+   *                true indicates that
+   *                {@link DistributionConfig#GEMFIRE_PREFIX} should be
+   *                prepended so the argument will become -Dgemfire.xxxx
+   */
+  private Properties buildSSLProperties(DistributedSystemConfig config,
+                                        boolean forCommandLine) {
+    if (!config.isSSLEnabled()) return null;
+
+    String prefix = "";
+    if (forCommandLine) prefix = DistributionConfig.GEMFIRE_PREFIX;
+
+    Properties sslProps = (Properties) config.getSSLProperties().clone();
+    // add ssl-enabled, etc...
+    sslProps.setProperty(prefix +
+            MCAST_PORT,
+                         "0");
+    sslProps.setProperty(prefix +
+                         CLUSTER_SSL_ENABLED,
+                         String.valueOf(config.isSSLEnabled()));
+    sslProps.setProperty(prefix +
+                         CLUSTER_SSL_CIPHERS,
+                         config.getSSLCiphers());
+    sslProps.setProperty(prefix +
+                         CLUSTER_SSL_PROTOCOLS,
+                         config.getSSLProtocols());
+    sslProps.setProperty(prefix +
+                         CLUSTER_SSL_REQUIRE_AUTHENTICATION,
+                         String.valueOf(config.isSSLAuthenticationRequired()));
+    return sslProps;
+  }
+
+
+  /**
+   * Starts a managed entity.
+   */
+  public void start(final InternalManagedEntity entity) {
+    final String command =
+      arrangeRemoteCommand(entity, entity.getStartCommand());
+    Thread start = new Thread(this.threadGroup, new Runnable() {
+        public void run() {
+          execute(command, entity);
+        }
+      }, "Start " + entity.getEntityType());
+    start.start();
+  }
+
+  /**
+   * Stops a managed entity.
+   */
+  public void stop(final InternalManagedEntity entity) {
+    final String command =
+      arrangeRemoteCommand(entity, entity.getStopCommand());
+    Thread stop = new Thread(this.threadGroup, new Runnable() {
+        public void run() {
+          execute(command, entity);
+        }
+      }, "Stop " + entity.getEntityType());
+    stop.start();
+  }
+
+  /**
+   * Returns whether or not a managed entity is running
+   */
+  public boolean isRunning(InternalManagedEntity entity) {
+    final String command =
+      arrangeRemoteCommand(entity, entity.getIsRunningCommand());
+    String output = execute(command, entity);
+
+    if (output == null ||
+        (output.indexOf("stop" /* "ing" "ped" */) != -1) ||
+        (output.indexOf("killed") != -1) ||
+        (output.indexOf("starting") != -1)) {
+      return false;
+
+    } else if (output.indexOf("running") != -1) {
+      return true;
+
+    } else {
+      throw new IllegalStateException(LocalizedStrings.ManagedEntityController_COULD_NOT_DETERMINE_IF_MANAGED_ENTITY_WAS_RUNNING_0
+          .toLocalizedString(output));
+    }
+  }
+
+  /**
+   * Returns the contents of a locator's log file.  Other APIs are
+   * used to get the log file of managed entities that are also system
+   * members.
+   */
+  public String getLog(DistributionLocatorImpl locator) {
+    String command =
+      arrangeRemoteCommand(locator, locator.getLogCommand());
+    return execute(command, locator);
+  }
+
+  /**
+   * Returns the contents of the given directory using the given
+   * managed entity to determine the host and remote command.
+   */
+  private String listDirectory(InternalManagedEntity entity,
+                               String dir) {
+    ManagedEntityConfig config = entity.getEntityConfig();
+    String listFile =
+        pathIsWindows(config.getProductDirectory()) ? "dir " : "ls ";
+    String command =
+      arrangeRemoteCommand(entity, listFile + dir);
+    return execute(command, entity);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FinishBackupRequest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FinishBackupRequest.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FinishBackupRequest.java
new file mode 100644
index 0000000..323248a
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FinishBackupRequest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.impl;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.CancelException;
+import org.apache.geode.DataSerializer;
+import org.apache.geode.cache.persistence.PersistentID;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.distributed.internal.DM;
+import org.apache.geode.distributed.internal.DistributionManager;
+import org.apache.geode.distributed.internal.DistributionMessage;
+import org.apache.geode.distributed.internal.ReplyException;
+import org.apache.geode.internal.admin.remote.AdminFailureResponse;
+import org.apache.geode.internal.admin.remote.AdminMultipleReplyProcessor;
+import org.apache.geode.internal.admin.remote.AdminResponse;
+import org.apache.geode.internal.admin.remote.CliLegacyMessage;
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.logging.log4j.LocalizedMessage;
+
+/**
+ * A request send from an admin VM to all of the peers to indicate
+ * that that should complete the backup operation.
+ * 
+ *
+ */
+public class FinishBackupRequest  extends CliLegacyMessage {
+  private static final Logger logger = LogService.getLogger();
+  
+  private File targetDir;
+  private File baselineDir;
+  private boolean abort;
+  
+  public FinishBackupRequest() {
+    super();
+  }
+
+  public FinishBackupRequest(File targetDir,File baselineDir, boolean abort) {
+    this.targetDir = targetDir;
+    this.baselineDir = baselineDir;
+    this.abort = abort;
+  }
+  
+  public static Map<DistributedMember, Set<PersistentID>> send(DM dm, Set recipients, File targetDir, File baselineDir, boolean abort) {
+    FinishBackupRequest request = new FinishBackupRequest(targetDir,baselineDir, abort);
+    request.setRecipients(recipients);
+
+    FinishBackupReplyProcessor replyProcessor = new FinishBackupReplyProcessor(dm, recipients);
+    request.msgId = replyProcessor.getProcessorId();
+    dm.putOutgoing(request);
+    try {
+      replyProcessor.waitForReplies();
+    } catch (ReplyException e) {
+      if(!(e.getCause() instanceof CancelException)) {
+        throw e;
+      }
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    AdminResponse response = request.createResponse((DistributionManager)dm);
+    response.setSender(dm.getDistributionManagerId());
+    replyProcessor.process(response);
+    return replyProcessor.results;
+  }
+  
+  @Override
+  protected AdminResponse createResponse(DistributionManager dm) {
+    GemFireCacheImpl cache = GemFireCacheImpl.getInstance();
+    HashSet<PersistentID> persistentIds;
+    if(cache == null || cache.getBackupManager() == null) {
+      persistentIds = new HashSet<PersistentID>();
+    } else {
+      try {
+        persistentIds = cache.getBackupManager().finishBackup(targetDir, baselineDir, abort);
+      } catch (IOException e) {
+        logger.error(LocalizedMessage.create(LocalizedStrings.CliLegacyMessage_ERROR, this.getClass()), e);
+        return AdminFailureResponse.create(dm, getSender(), e);
+      }
+    }
+    
+    return new FinishBackupResponse(this.getSender(), persistentIds);
+  }
+
+  public int getDSFID() {
+    return FINISH_BACKUP_REQUEST;
+  }
+  
+  @Override
+  public void fromData(DataInput in) throws IOException, ClassNotFoundException {
+    super.fromData(in);
+    targetDir = DataSerializer.readFile(in);
+    baselineDir = DataSerializer.readFile(in);
+    abort = DataSerializer.readBoolean(in);
+  }
+
+  @Override
+  public void toData(DataOutput out) throws IOException {
+    super.toData(out);
+    DataSerializer.writeFile(targetDir, out);
+    DataSerializer.writeFile(baselineDir, out);
+    DataSerializer.writeBoolean(abort, out);
+  }
+
+  private static class FinishBackupReplyProcessor extends AdminMultipleReplyProcessor {
+    Map<DistributedMember, Set<PersistentID>> results = Collections.synchronizedMap(new HashMap<DistributedMember, Set<PersistentID>>());
+    public FinishBackupReplyProcessor(DM dm, Collection initMembers) {
+      super(dm, initMembers);
+    }
+    
+    @Override
+    protected boolean stopBecauseOfExceptions() {
+      return false;
+    }
+
+    
+    
+    @Override
+    protected int getAckWaitThreshold() {
+      //Disable the 15 second warning if the backup is taking a long time
+      return 0;
+    }
+
+    @Override
+    public long getAckSevereAlertThresholdMS() {
+      //Don't log severe alerts for backups either
+      return Long.MAX_VALUE;
+    }
+
+    @Override
+    protected void process(DistributionMessage msg, boolean warn) {
+      if(msg instanceof FinishBackupResponse) {
+        final HashSet<PersistentID> persistentIds = ((FinishBackupResponse) msg).getPersistentIds();
+        if(persistentIds != null && !persistentIds.isEmpty()) {
+          results.put(msg.getSender(), persistentIds);
+        }
+      }
+      super.process(msg, warn);
+    }
+    
+    
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FinishBackupResponse.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FinishBackupResponse.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FinishBackupResponse.java
new file mode 100644
index 0000000..f2da628
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FinishBackupResponse.java
@@ -0,0 +1,78 @@
+/*
+ * 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.impl;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.HashSet;
+
+import org.apache.geode.DataSerializer;
+import org.apache.geode.cache.persistence.PersistentID;
+import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
+import org.apache.geode.internal.admin.remote.AdminResponse;
+
+/**
+ * The reply for a {@link FinishBackupRequest}. The
+ * reply contains the persistent ids of the disk stores
+ * that were backed up on this member.
+ * 
+ *
+ */
+public class FinishBackupResponse extends AdminResponse {
+  
+  private HashSet<PersistentID> persistentIds;
+  
+  public FinishBackupResponse() {
+    super();
+  }
+
+  public FinishBackupResponse(InternalDistributedMember sender, HashSet<PersistentID> persistentIds) {
+    this.setRecipient(sender);
+    this.persistentIds = persistentIds;
+  }
+  
+  public HashSet<PersistentID> getPersistentIds() {
+    return persistentIds;
+  }
+
+  @Override
+  public void fromData(DataInput in) throws IOException, ClassNotFoundException {
+    super.fromData(in);
+    persistentIds = DataSerializer.readHashSet(in);
+  }
+
+  @Override
+  public void toData(DataOutput out) throws IOException {
+    super.toData(out);
+    DataSerializer.writeHashSet(persistentIds, out);
+  }
+
+  @Override
+  protected Object clone() throws CloneNotSupportedException {
+    return super.clone();
+  }
+
+  public int getDSFID() {
+    return FINISH_BACKUP_RESPONSE;
+  }
+  
+  @Override
+  public String toString() {
+    return getClass().getName() + ": " + persistentIds;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FlushToDiskRequest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FlushToDiskRequest.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FlushToDiskRequest.java
new file mode 100644
index 0000000..9eec442
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FlushToDiskRequest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.impl;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.geode.CancelException;
+import org.apache.geode.cache.persistence.PersistentID;
+import org.apache.geode.distributed.internal.DM;
+import org.apache.geode.distributed.internal.DistributionManager;
+import org.apache.geode.distributed.internal.ReplyException;
+import org.apache.geode.internal.admin.remote.AdminMultipleReplyProcessor;
+import org.apache.geode.internal.admin.remote.AdminResponse;
+import org.apache.geode.internal.admin.remote.CliLegacyMessage;
+import org.apache.geode.internal.cache.DiskStoreImpl;
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+
+/**
+ * A request to from an admin VM to all non admin members
+ * to start a backup. In the prepare phase of the backup,
+ * the members will suspend bucket destroys to make sure
+ * buckets aren't missed during the backup.
+ * 
+ *
+ */
+public class FlushToDiskRequest  extends CliLegacyMessage {
+  
+  public FlushToDiskRequest() {
+    
+  }
+  
+  public static void send(DM dm, Set recipients) {
+    FlushToDiskRequest request = new FlushToDiskRequest();
+    request.setRecipients(recipients);
+
+    FlushToDiskProcessor replyProcessor = new FlushToDiskProcessor(dm, recipients);
+    request.msgId = replyProcessor.getProcessorId();
+    dm.putOutgoing(request);
+    try {
+      replyProcessor.waitForReplies();
+    } catch (ReplyException e) {
+      if(!(e.getCause() instanceof CancelException)) {
+        throw e;
+      }
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+    AdminResponse response = request.createResponse((DistributionManager)dm);
+    response.setSender(dm.getDistributionManagerId());
+    replyProcessor.process(response);
+  }
+  
+  @Override
+  protected AdminResponse createResponse(DistributionManager dm) {
+    GemFireCacheImpl cache = GemFireCacheImpl.getInstance();
+    HashSet<PersistentID> persistentIds;
+    if(cache != null) {
+      Collection<DiskStoreImpl> diskStores = cache.listDiskStoresIncludingRegionOwned();
+      for(DiskStoreImpl store : diskStores) {
+        store.flush();
+      }
+    }
+    
+    return new FlushToDiskResponse(this.getSender());
+  }
+
+  public int getDSFID() {
+    return FLUSH_TO_DISK_REQUEST;
+  }
+  
+  private static class FlushToDiskProcessor extends AdminMultipleReplyProcessor {
+    public FlushToDiskProcessor(DM dm, Collection initMembers) {
+      super(dm, initMembers);
+    }
+    
+    @Override
+    protected boolean stopBecauseOfExceptions() {
+      return false;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FlushToDiskResponse.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FlushToDiskResponse.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FlushToDiskResponse.java
new file mode 100644
index 0000000..e044932
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/FlushToDiskResponse.java
@@ -0,0 +1,45 @@
+/*
+ * 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.impl;
+
+import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
+import org.apache.geode.internal.admin.remote.AdminResponse;
+
+/**
+ * The response to the {@link FlushToDiskRequest}
+ * 
+ *
+ */
+public class FlushToDiskResponse extends AdminResponse {
+
+  public FlushToDiskResponse() {
+    super();
+  }
+
+  public FlushToDiskResponse(InternalDistributedMember sender) {
+    this.setRecipient(sender);
+  }
+  
+  public int getDSFID() {
+    return FLUSH_TO_DISK_RESPONSE;
+  }
+  
+  @Override
+  public String toString() {
+    return getClass().getName();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthConfigImpl.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthConfigImpl.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthConfigImpl.java
new file mode 100644
index 0000000..d3a6a84
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthConfigImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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.impl;
+
+import org.apache.geode.internal.admin.api.GemFireHealthConfig;
+import org.apache.geode.internal.i18n.LocalizedStrings;
+
+// @todo davidw Delegate to a "parent" config for properties that are not overridden.
+// This will be made easier with a special <code>HealthConfigAttribute</code> class.
+/**
+ * The implementation of <code>GemFireHealthConfig</code>
+ *
+ *
+ *
+ * @since GemFire 3.5
+ */
+public class GemFireHealthConfigImpl
+  extends CacheHealthConfigImpl
+  implements GemFireHealthConfig {
+
+  private static final long serialVersionUID = -6797673296902808018L;
+
+  /** The name of the host to which this configuration applies. */
+  private String hostName;
+
+  /** The number of seconds to wait between evaluating the health of
+   * GemFire. */
+  private int interval = DEFAULT_HEALTH_EVALUATION_INTERVAL;
+
+  ////////////////////////  Constructors  ////////////////////////
+
+  /**
+   * Creates a new <code>GemFireHealthConfigImpl</code> that applies
+   * to the host with the given name.
+   *
+   * @param hostName
+   *        The name of the host to which this configuration applies.
+   *        If <code>null</code>, then this is the "default"
+   *        configuration. 
+   */
+  public GemFireHealthConfigImpl(String hostName) {
+    this.hostName = hostName;
+  }
+
+  ///////////////////////  Instance Methods  ///////////////////////
+
+  public String getHostName() {
+    return this.hostName;
+  }
+
+  public void setHealthEvaluationInterval(int interval) {
+    this.interval = interval;
+  }
+
+  public int getHealthEvaluationInterval() {
+    return this.interval;
+  }
+
+  @Override
+  public String toString() {
+    if (this.hostName == null) {
+      return LocalizedStrings.GemFireHealthConfigImpl_DEFAULT_GEMFIRE_HEALTH_CONFIGURATION.toLocalizedString();
+
+    } else {
+      return LocalizedStrings.GemFireHealthConfigImpl_GEMFIRE_HEALTH_CONFIGURATION_FOR_HOST_0.toLocalizedString(this.hostName);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthEvaluator.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthEvaluator.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthEvaluator.java
new file mode 100644
index 0000000..74590b7
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthEvaluator.java
@@ -0,0 +1,187 @@
+/*
+ * 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.impl;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.internal.admin.api.GemFireHealth;
+import org.apache.geode.internal.admin.api.GemFireHealthConfig;
+import org.apache.geode.distributed.internal.DistributionManager;
+import org.apache.geode.internal.Assert;
+import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LogService;
+
+/**
+ * Evaluates the health of various GemFire components in the VM
+ * according to a {@link GemFireHealthConfig}.
+ *
+ * <P>
+ *
+ * Note that evaluators never reside in the administration VM, they
+ * only in member VMs.  They are not <code>Serializable</code> and
+ * aren't meant to be.
+ *
+ * @see MemberHealthEvaluator
+ * @see CacheHealthEvaluator
+ *
+ *
+ * @since GemFire 3.5
+ */
+public class GemFireHealthEvaluator {
+
+  private static final Logger logger = LogService.getLogger();
+  
+  /** Determines how the health of GemFire is determined */
+  private GemFireHealthConfig config;
+
+  /** Evaluates the health of this member of the distributed system */
+  private MemberHealthEvaluator memberHealth;
+
+  /** Evaluates the health of the Cache hosted in this VM */
+  private CacheHealthEvaluator cacheHealth;
+
+  /** The most recent <code>OKAY_HEALTH</code> diagnoses of the
+   * GemFire system */
+  private List okayDiagnoses;
+
+  /** The most recent <code>POOR_HEALTH</code> diagnoses of the
+   * GemFire system */
+  private List poorDiagnoses;
+
+  ///////////////////////  Constructors  ///////////////////////
+
+  /**
+   * Creates a new <code>GemFireHealthEvaluator</code>
+   *
+   * @param config
+   *        The configuration that determines whether or GemFire is
+   *        healthy 
+   * @param dm
+   *        The distribution manager 
+   */
+  public GemFireHealthEvaluator(GemFireHealthConfig config,
+                                DistributionManager dm) {
+    if (config == null) {
+      throw new NullPointerException(LocalizedStrings.GemFireHealthEvaluator_NULL_GEMFIREHEALTHCONFIG.toLocalizedString());
+    }
+
+    this.config = config;
+    this.memberHealth = new MemberHealthEvaluator(config, dm);
+    this.cacheHealth = new CacheHealthEvaluator(config, dm);
+    this.okayDiagnoses = new ArrayList();
+    this.poorDiagnoses = new ArrayList();
+  }
+
+  //////////////////////  Instance Methods  //////////////////////
+
+  /**
+   * Evaluates the health of the GemFire components in this VM.
+   *
+   * @return The aggregate health code (such as {@link
+   *         GemFireHealth#OKAY_HEALTH}) of the GemFire components.
+   */
+  public GemFireHealth.Health evaluate() {
+    List status = new ArrayList();
+    this.memberHealth.evaluate(status);
+    this.cacheHealth.evaluate(status);
+
+    GemFireHealth.Health overallHealth = GemFireHealth.GOOD_HEALTH;
+    this.okayDiagnoses.clear();
+    this.poorDiagnoses.clear();
+
+    for (Iterator iter = status.iterator(); iter.hasNext(); ) {
+      AbstractHealthEvaluator.HealthStatus health =
+        (AbstractHealthEvaluator.HealthStatus) iter.next();
+      if (overallHealth == GemFireHealth.GOOD_HEALTH) {
+        if ((health.getHealthCode() != GemFireHealth.GOOD_HEALTH)) {
+          overallHealth = health.getHealthCode();
+        }
+
+      } else if (overallHealth == GemFireHealth.OKAY_HEALTH) {
+        if (health.getHealthCode() == GemFireHealth.POOR_HEALTH) {
+          overallHealth = GemFireHealth.POOR_HEALTH;
+        }
+      }
+
+      GemFireHealth.Health healthCode = health.getHealthCode();
+      if (healthCode == GemFireHealth.OKAY_HEALTH) {
+        this.okayDiagnoses.add(health.getDiagnosis());
+
+      } else if (healthCode == GemFireHealth.POOR_HEALTH) {
+        this.poorDiagnoses.add(health.getDiagnosis());
+      }
+    }
+
+    if (logger.isDebugEnabled()) {
+      logger.debug("Evaluated health to be {}", overallHealth);
+    }
+    return overallHealth;
+  }
+
+  /**
+   * Returns detailed information explaining the current health status.
+   * Each array element is a different cause for the current status.
+   * An empty array will be returned if the current status is {@link
+   * GemFireHealth#GOOD_HEALTH}. 
+   */
+  public String[] getDiagnosis(GemFireHealth.Health healthCode) {
+    if (healthCode == GemFireHealth.GOOD_HEALTH) {
+      return new String[0];
+
+    } else if (healthCode == GemFireHealth.OKAY_HEALTH) {
+      String[] array = new String[this.okayDiagnoses.size()];
+      this.okayDiagnoses.toArray(array);
+      return array;
+
+    } else {
+      Assert.assertTrue(healthCode == GemFireHealth.POOR_HEALTH);
+      String[] array = new String[this.poorDiagnoses.size()];
+      this.poorDiagnoses.toArray(array);
+      return array;
+    }
+  }
+
+  /**
+   * Resets the state of this evaluator
+   */
+  public void reset() {
+    this.okayDiagnoses.clear();
+    this.poorDiagnoses.clear();
+  }
+
+  /**
+   * Returns the heath evaluation interval, in seconds.
+   *
+   * @see GemFireHealthConfig#getHealthEvaluationInterval
+   */
+  public int getEvaluationInterval() {
+    return this.config.getHealthEvaluationInterval();
+  }
+
+  /**
+   * Closes this evaluator and releases all of its resources
+   */
+  public void close() {
+    this.memberHealth.close();
+    this.cacheHealth.close();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthImpl.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthImpl.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthImpl.java
new file mode 100644
index 0000000..e2e3f47
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/GemFireHealthImpl.java
@@ -0,0 +1,538 @@
+/*
+ * 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.impl;
+
+import org.apache.geode.CancelException;
+import org.apache.geode.internal.Assert;
+import org.apache.geode.internal.admin.*;
+import org.apache.geode.internal.admin.api.AdminDistributedSystem;
+import org.apache.geode.internal.admin.api.DistributedSystemHealthConfig;
+import org.apache.geode.internal.admin.api.GemFireHealth;
+import org.apache.geode.internal.admin.api.GemFireHealthConfig;
+import org.apache.geode.internal.i18n.LocalizedStrings;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.*;
+
+/**
+ * Provides the implementation of the <code>GemFireHealth</code>
+ * administration API.  This class is responsible for {@linkplain
+ * GemFireVM#addHealthListener sending} the {@link
+ * GemFireHealthConfig}s to the remote member VM in which the health
+ * is calcualted.
+ *
+ *
+ * @since GemFire 3.5
+ */
+public class GemFireHealthImpl
+  implements GemFireHealth, JoinLeaveListener, HealthListener {
+
+  /** The distributed system whose health is being monitored */
+  private final GfManagerAgent agent;
+
+  /** The default configuration for checking GemFire health */
+  protected GemFireHealthConfig defaultConfig;
+
+  /** Maps the name of a host to its <code>GemFireHealthConfig</code>.
+   * Note that the mappings are created lazily. */
+  private final Map hostConfigs;
+
+  /** Maps the name of a host to all of the members
+   * (<code>GemFireVM</code>s) that run on that host. */
+  private final Map hostMembers;
+
+  /** The members that are known to be in {@link #OKAY_HEALTH}. */
+  private Collection okayHealth;
+
+  /** The members that are known to be in {@link #POOR_HEALTH}. */
+  private Collection poorHealth;
+
+  /** The overall health of GemFire */
+  private GemFireHealth.Health overallHealth;
+
+  /** Is this GemFireHealthImpl closed? */
+  private boolean isClosed;
+
+  /** The configuration specifying how the health of the distributed
+   * system should be computed.  */
+  protected volatile DistributedSystemHealthConfig dsHealthConfig;
+
+  /** Monitors the health of the entire distributed system */
+  private DistributedSystemHealthMonitor dsHealthMonitor = null;
+
+  /** The distributed system whose health is monitored by this
+   * <Code>GemFireHealth</code>. */
+  private final AdminDistributedSystem system;
+
+  
+  ///////////////////////  Constructors  ///////////////////////
+
+  /**
+   * Creates a new <code>GemFireHealthImpl</code> that monitors the
+   * health of member of the given distributed system.
+   */
+  protected GemFireHealthImpl(GfManagerAgent agent,
+                              AdminDistributedSystem system) {
+//     agent.getDM().getLogger().info("Creating GemFireHealthImpl",
+//                                    new Exception("Stack trace"));
+
+    this.agent = agent;
+    this.system = system;
+
+    this.hostConfigs = new HashMap();
+    this.hostMembers = new HashMap();
+    this.okayHealth = new HashSet();
+    this.poorHealth = new HashSet();
+    this.overallHealth = GOOD_HEALTH;
+    this.isClosed = false;
+
+    GemFireVM[] apps = this.agent.listApplications();
+    for (int i = 0; i < apps.length; i++) {
+      GemFireVM member = apps[i];
+      this.noteNewMember(member);
+    }
+
+    agent.addJoinLeaveListener(this);
+    setDefaultGemFireHealthConfig(createGemFireHealthConfig(null));
+    setDistributedSystemHealthConfig(createDistributedSystemHealthConfig());
+  }
+
+  @Override
+  public String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("closed=" + isClosed);
+    sb.append("; hostMembers=" + hostMembers);
+    sb.append("; okayHealth=" + okayHealth);
+    sb.append("; poorHealth=" + poorHealth);
+    sb.append("; overallHealth=" + overallHealth);
+    sb.append("; diagnosis=" + getDiagnosis());
+    return sb.toString();
+  }
+  //////////////////////  Instance Methods  //////////////////////
+
+  /**
+   * Returns the <code>DistributedSystem</code> whose health this
+   * <code>GemFireHealth</code> monitors.
+   */
+  public AdminDistributedSystem getDistributedSystem() {
+    return this.system;
+  }
+
+  /**
+   * A "template factory" method for creating a
+   * <code>DistributedSystemHealthConfig</code>. It can be overridden
+   * by subclasses to produce instances of different
+   * <code>DistributedSystemHealthConfig</code> implementations.
+   */
+  protected DistributedSystemHealthConfig
+    createDistributedSystemHealthConfig() {
+
+    return new DistributedSystemHealthConfigImpl();
+  }
+
+  /**
+   * A "template factory" method for creating a
+   * <code>GemFireHealthConfig</code>.  It can be overridden by
+   * subclasses to produce instances of different
+   * <code>GemFireHealthConfig</code> implementations.
+   *
+   * @param hostName
+   *        The host whose health we are configuring
+   */
+  protected GemFireHealthConfig
+    createGemFireHealthConfig(String hostName) {
+
+    return new GemFireHealthConfigImpl(hostName);
+  }
+
+  /**
+   * Throws an {@link IllegalStateException} if this
+   * <code>GemFireHealthImpl</code> is closed.
+   */
+  private void checkClosed() {
+    if (this.isClosed) {
+      throw new IllegalStateException(LocalizedStrings.GemFireHealthImpl_CANNOT_ACCESS_A_CLOSED_GEMFIREHEALTH_INSTANCE.toLocalizedString());
+    }
+  }
+
+  /**
+   * Returns the overall health of GemFire.  Note that this method
+   * does not contact any of the member VMs.  Instead, it relies on
+   * the members to alert it of changes in its health via a {@link
+   * HealthListener}.
+   */
+  public GemFireHealth.Health getHealth() {
+    checkClosed();
+    return this.overallHealth;
+  }
+
+  /**
+   * Resets the overall health to be {@link #GOOD_HEALTH}.  It also
+   * resets the health in the member VMs.
+   *
+   * @see GemFireVM#resetHealthStatus
+   */
+  public void resetHealth() {
+    checkClosed();
+
+    this.overallHealth = GOOD_HEALTH;
+    this.okayHealth.clear();
+    this.poorHealth.clear();
+
+    synchronized (this) {
+      for (Iterator iter = hostMembers.values().iterator();
+           iter.hasNext(); ) {
+        List members = (List) iter.next();
+        for (Iterator iter2 = members.iterator(); iter2.hasNext(); ) {
+          GemFireVM member = (GemFireVM) iter2.next();
+          member.resetHealthStatus();
+        }
+      }
+    }
+  }
+
+  /**
+   * Aggregates the diagnoses from all members of the distributed
+   * system. 
+   */
+  public String getDiagnosis() {
+    checkClosed();
+
+    StringBuffer sb = new StringBuffer();
+
+    synchronized (this) {
+      for (Iterator iter = hostMembers.values().iterator();
+           iter.hasNext(); ) {
+        List members = (List) iter.next();
+        for (Iterator iter2 = members.iterator(); iter2.hasNext(); ) {
+          GemFireVM member = (GemFireVM) iter2.next();
+          String[] diagnoses =
+            member.getHealthDiagnosis(this.overallHealth);
+          for (int i = 0; i < diagnoses.length; i++) {
+            sb.append(diagnoses[i]).append("\n");;
+          }
+        }
+      }
+    }
+
+    return sb.toString();
+  }
+
+  /**
+   * Starts a new {@link DistributedSystemHealthMonitor}
+   */
+  public void setDistributedSystemHealthConfig(DistributedSystemHealthConfig
+                                               config) {
+    synchronized (this.hostConfigs) {
+      // If too many threads are changing the health config, then we
+      // will might get an OutOfMemoryError trying to start a new
+      // health monitor thread.
+
+      if (this.dsHealthMonitor != null) {
+        this.dsHealthMonitor.stop();
+      }
+
+      this.dsHealthConfig = config;
+
+      DistributedSystemHealthEvaluator eval =
+        new DistributedSystemHealthEvaluator(config, this.agent.getDM());
+      int interval =
+        this.getDefaultGemFireHealthConfig().getHealthEvaluationInterval();
+      this.dsHealthMonitor =
+        new DistributedSystemHealthMonitor(eval, this, interval);
+      this.dsHealthMonitor.start();
+    }
+  }
+
+  public DistributedSystemHealthConfig
+    getDistributedSystemHealthConfig() {
+
+    checkClosed();
+    return this.dsHealthConfig;
+  }
+
+  public GemFireHealthConfig getDefaultGemFireHealthConfig() {
+    checkClosed();
+    return this.defaultConfig;
+  }
+
+  public void setDefaultGemFireHealthConfig(GemFireHealthConfig config) {
+    checkClosed();
+
+    if (config.getHostName() != null) {
+      throw new IllegalArgumentException(LocalizedStrings.GemFireHealthImpl_THE_GEMFIREHEALTHCONFIG_FOR_FOR_0_CANNOT_SERVE_AS_THE_DEFAULT_HEALTH_CONFIG.toLocalizedString(config.getHostName()));
+    }
+
+    this.defaultConfig = config;
+
+    synchronized (this) {
+      for (Iterator iter = this.hostMembers.entrySet().iterator();
+           iter.hasNext(); ) {
+        Map.Entry entry = (Map.Entry) iter.next();
+        InetAddress hostIpAddress = (InetAddress) entry.getKey();
+        List members = (List) entry.getValue();
+
+        GemFireHealthConfig hostConfig =
+          (GemFireHealthConfig) hostConfigs.get(hostIpAddress);
+        if (hostConfig == null) {
+          hostConfig = config;
+        }
+
+        for (Iterator iter2 = members.iterator(); iter2.hasNext(); ) {
+          GemFireVM member = (GemFireVM) iter2.next();
+          Assert.assertTrue(member.getHost().equals(hostIpAddress));
+          member.addHealthListener(this, hostConfig);
+        }
+      }
+    }
+
+    // We only need to do this if the health monitoring interval has
+    // change.  This is probably not the most efficient way of doing
+    // things.
+    if (this.dsHealthConfig != null) {
+      setDistributedSystemHealthConfig(this.dsHealthConfig);
+    }
+  }
+
+  /**
+   * Returns the GemFireHealthConfig object for the given host name.
+   * 
+   * @param hostName
+   *          host name for which the GemFire Health Config is needed
+   * 
+   * @throws IllegalArgumentException
+   *           if host with given name could not be found
+   */
+  public synchronized GemFireHealthConfig
+    getGemFireHealthConfig(String hostName){
+
+    checkClosed();
+
+    InetAddress hostIpAddress = null;
+    try {
+      hostIpAddress = InetAddress.getByName(hostName);
+    } catch (UnknownHostException e) {
+      throw new IllegalArgumentException(
+          LocalizedStrings.GemFireHealthImpl_COULD_NOT_FIND_A_HOST_WITH_NAME_0
+              .toLocalizedString(hostName), e);
+    }
+    
+    GemFireHealthConfig config =
+      (GemFireHealthConfig) this.hostConfigs.get(hostIpAddress);
+    if (config == null) {
+      config = createGemFireHealthConfig(hostName);
+      this.hostConfigs.put(hostIpAddress, config);
+    }
+
+    return config;
+  }
+
+  /**
+   * Sets the GemFireHealthConfig object for the given host name.
+   * 
+   * @param hostName
+   *          host name for which the GemFire Health Config is needed
+   * @param config
+   *          GemFireHealthConfig object to set
+   * 
+   * @throws IllegalArgumentException
+   *           if (1) given host name & the host name in the given config do not
+   *           match OR (2) host with given name could not be found OR (3) there
+   *           are no GemFire components running on the given host
+   */
+  public void setGemFireHealthConfig(String hostName,
+                                     GemFireHealthConfig config) {
+    checkClosed();
+
+    synchronized (this) {
+      String configHost = config.getHostName();
+      if (configHost == null || !configHost.equals(hostName)) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("The GemFireHealthConfig configures ");
+        if (configHost == null) {
+          sb.append("the default host ");
+
+        } else {
+          sb.append("host \"");
+          sb.append(config.getHostName());
+          sb.append("\" ");
+        }
+        sb.append("not \"" + hostName + "\"");
+        throw new IllegalArgumentException(sb.toString());
+      }
+      InetAddress hostIpAddress = null;
+      try {
+        hostIpAddress = InetAddress.getByName(hostName);
+      } catch (UnknownHostException e) {
+        throw new IllegalArgumentException(
+            LocalizedStrings.GemFireHealthImpl_COULD_NOT_FIND_A_HOST_WITH_NAME_0
+                .toLocalizedString(hostName), e);
+      }
+      
+      List members = (List) this.hostMembers.get(hostIpAddress);
+      if (members == null || members.isEmpty()) {
+        throw new IllegalArgumentException(
+            LocalizedStrings.GemFireHealthImpl_THERE_ARE_NO_GEMFIRE_COMPONENTS_ON_HOST_0
+                .toLocalizedString(hostName));
+      }
+
+      for (Iterator iter = members.iterator(); iter.hasNext(); ) {
+        GemFireVM member = (GemFireVM) iter.next();
+        member.addHealthListener(this, config);
+      }
+    }
+  }
+
+  /**
+   * Tells the members of the distributed system that we are no longer
+   * interested in monitoring their health.
+   *
+   * @see GemFireVM#removeHealthListener
+   */
+  public void close(){
+    this.agent.removeJoinLeaveListener(this);
+    
+    synchronized (this) {
+      if (this.isClosed) {
+        return;
+      }
+
+      this.isClosed = true;
+
+      if (this.dsHealthMonitor != null) {
+        this.dsHealthMonitor.stop();
+        this.dsHealthMonitor = null;
+      }
+
+      try {
+        for (Iterator iter = hostMembers.values().iterator();
+             iter.hasNext(); ) {
+          List members = (List) iter.next();
+          for (Iterator iter2 = members.iterator(); iter2.hasNext(); ) {
+            GemFireVM member = (GemFireVM) iter2.next();
+            member.removeHealthListener();
+          }
+        }
+      } catch (CancelException e) {
+        // if the DS is disconnected, stop trying to distribute to other members
+      }
+
+      hostConfigs.clear();
+      hostMembers.clear();
+      okayHealth.clear();
+      poorHealth.clear();
+    }
+  }
+
+  public boolean isClosed() {
+    return this.isClosed;
+  }
+
+  /**
+   * Makes note of the newly-joined member
+   */
+  private void noteNewMember(GemFireVM member) {
+    InetAddress hostIpAddress = member.getHost();
+    List members = (List) this.hostMembers.get(hostIpAddress);
+    if (members == null) {
+      members = new ArrayList();
+      this.hostMembers.put(hostIpAddress, members);
+    }
+    members.add(member);
+
+  }
+
+  public synchronized void nodeJoined(GfManagerAgent source,
+                                      GemFireVM joined) {
+    noteNewMember(joined);
+
+    InetAddress hostIpAddress = joined.getHost();
+
+    GemFireHealthConfig config =
+      (GemFireHealthConfig) this.hostConfigs.get(hostIpAddress);
+    if (config == null) {
+      config = this.getDefaultGemFireHealthConfig();
+    }
+    joined.addHealthListener(this, config);
+  }
+
+  /**
+   * Makes note of the newly-left member
+   */
+  public synchronized void nodeLeft(GfManagerAgent source,
+                                    GemFireVM left) {
+    InetAddress hostIpAddress = left.getHost();
+    List members = (List) this.hostMembers.get(hostIpAddress);
+    if (members != null) {
+      members.remove(left);
+      if (members.isEmpty()) {
+        // No more members on the host
+        this.hostConfigs.remove(hostIpAddress);
+        this.hostMembers.remove(hostIpAddress);
+      }
+    }
+
+    this.okayHealth.remove(left);
+    this.poorHealth.remove(left);
+
+    reevaluateHealth();
+  }
+
+  /**
+   * Does the same thing as {@link #nodeLeft}
+   */
+  public void nodeCrashed(GfManagerAgent source, GemFireVM crashed) {
+    nodeLeft(source, crashed);
+  }
+
+  /**
+   * Re-evaluates the overall health of GemFire
+   */
+  private void reevaluateHealth() {
+    if (!this.poorHealth.isEmpty()) {
+      this.overallHealth = POOR_HEALTH;
+
+    } else if (!this.okayHealth.isEmpty()) {
+      this.overallHealth = OKAY_HEALTH;
+
+    } else {
+      this.overallHealth = GOOD_HEALTH;
+    }
+  }
+
+  public void healthChanged(GemFireVM member, GemFireHealth.Health status) {
+    if (status == GOOD_HEALTH) {
+      this.okayHealth.remove(member);
+      this.poorHealth.remove(member);
+
+    } else if (status == OKAY_HEALTH) {
+      this.okayHealth.add(member);
+      this.poorHealth.remove(member);
+
+    } else if (status == POOR_HEALTH) {
+      this.okayHealth.remove(member);
+      this.poorHealth.add(member);
+
+    } else {
+      Assert.assertTrue(false, "Unknown health code: " + status);
+    }
+
+    reevaluateHealth();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/InetAddressUtil.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/InetAddressUtil.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/InetAddressUtil.java
new file mode 100755
index 0000000..d5f76cf
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/InetAddressUtil.java
@@ -0,0 +1,209 @@
+/*
+ * 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.impl;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.GemFireIOException;
+import org.apache.geode.internal.Assert;
+import org.apache.geode.internal.net.SocketCreator;
+import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.logging.LogService;
+
+
+/**
+ * Provides static utilities for manipulating, validating, and converting
+ * InetAddresses and host strings.
+ *
+ * @since GemFire     3.5
+ */
+@Deprecated
+public class InetAddressUtil {
+  
+  private static final Logger logger = LogService.getLogger();
+  
+  /** InetAddress instance representing the local host  */
+  public static final InetAddress LOCALHOST = createLocalHost();
+  
+  public static  final String LOOPBACK_ADDRESS =
+    SocketCreator.preferIPv6Addresses() ? "::1" : "127.0.0.1"; 
+  
+  public static final InetAddress LOOPBACK = 
+      InetAddressUtil.toInetAddress(LOOPBACK_ADDRESS);
+  
+  /** Disallows InetAddressUtil instantiation. */
+  private InetAddressUtil() {}
+
+  /** 
+   * Returns a string version of InetAddress which can be converted back to an 
+   * InetAddress later.  Essentially any leading slash is trimmed.
+   *
+   * @param val the InetAddress or String to return a formatted string of
+   * @return string version the InetAddress minus any leading slash
+   */
+  public static String toString(Object val) {
+    if (val instanceof String) {
+      return trimLeadingSlash((String) val);
+
+    } else if (val instanceof InetAddress) {
+      return ((InetAddress) val).getHostAddress();
+
+    } else {
+      return trimLeadingSlash(val.toString());
+    }
+  }
+  
+  /** 
+   * Converts the string host to an instance of InetAddress.  Returns null if
+   * the string is empty.  Fails Assertion if the conversion would result in
+   * <code>java.lang.UnknownHostException</code>.
+   * <p>
+   * Any leading slashes on host will be ignored.
+   *
+   * @param   host  string version the InetAddress
+   * @return  the host converted to InetAddress instance
+   */
+  public static InetAddress toInetAddress(String host) {
+    if (host == null || host.length() == 0) {
+      return null;
+    }
+    try {
+      if (host.indexOf("/") > -1) {
+        return InetAddress.getByName(host.substring(host.indexOf("/") + 1));
+      }
+      else {
+        return InetAddress.getByName(host);
+      }
+    } catch (java.net.UnknownHostException e) {
+      logStackTrace(e);
+      Assert.assertTrue(false, "Failed to get InetAddress: " + host);
+      return null; // will never happen since the Assert will fail
+    }
+  }
+
+  /** 
+   * Creates an InetAddress representing the local host.  The checked exception
+   * <code>java.lang.UnknownHostException</code> is captured and results in
+   * an Assertion failure instead.
+   *
+   * @return InetAddress instance representing the local host
+   */
+  public static InetAddress createLocalHost() {
+    try {
+      return SocketCreator.getLocalHost();
+    } catch (java.net.UnknownHostException e) {
+      logStackTrace(e);
+      Assert.assertTrue(false, "Failed to get local host");
+      return null; // will never happen
+    }
+  }
+
+  /** 
+   * Validates the host by making sure it can successfully be used to get an
+   * instance of InetAddress.  If the host string is null, empty or would result
+   * in <code>java.lang.UnknownHostException</code> then null is returned.
+   * <p>
+   * Any leading slashes on host will be ignored.
+   *
+   * @param   host  string version the InetAddress
+   * @return  the host converted to InetAddress instance
+   */
+  public static String validateHost(String host) {
+    if (host == null || host.length() == 0) {
+      return null; 
+    }
+    try {
+      InetAddress.getByName(trimLeadingSlash(host));
+      return host;
+    } catch (java.net.UnknownHostException e) { 
+      logStackTrace(e);
+      return null;
+    }
+  }
+
+  /** Returns true if host matches the LOCALHOST. */
+  public static boolean isLocalHost(Object host) {
+    if (host instanceof InetAddress) {
+      if (LOCALHOST.equals(host)) {
+        return true;
+      }
+      else {
+//        InetAddress hostAddr = (InetAddress)host;
+        try {
+          Enumeration en=NetworkInterface.getNetworkInterfaces();
+          while(en.hasMoreElements()) {
+            NetworkInterface i=(NetworkInterface)en.nextElement();
+            for(Enumeration en2=i.getInetAddresses(); en2.hasMoreElements();) {
+              InetAddress addr=(InetAddress)en2.nextElement();
+              if (host.equals(addr)) {
+                return true;
+              }
+            }
+          }
+          return false;
+        }
+        catch (SocketException e) {
+          throw new GemFireIOException(LocalizedStrings.InetAddressUtil_UNABLE_TO_QUERY_NETWORK_INTERFACE.toLocalizedString(), e);
+        }
+      }
+    }
+    else {
+      return isLocalHost(InetAddressUtil.toInetAddress(host.toString()));
+    }
+  }
+  
+  /** Returns true if host matches the LOOPBACK (127.0.0.1). */
+  public static boolean isLoopback(Object host) {
+    if (host instanceof InetAddress) {
+      return LOOPBACK.equals(host);
+    }
+    else {
+      return isLoopback(InetAddressUtil.toInetAddress(host.toString()));
+    }
+  }
+  
+  /** Returns a version of the value after removing any leading slashes */
+  private static String trimLeadingSlash(String value) {
+    if (value == null) return "";
+    while (value.indexOf("/") > -1) {
+      value = value.substring(value.indexOf("/") + 1);
+    }
+    return value;
+  }
+
+  /**
+   * Logs the stack trace for the given Throwable if logger is initialized else
+   * prints the stack trace using System.out. If logged the logs are logged at 
+   * WARNING level.
+   * 
+   * @param throwable
+   *          Throwable to log stack trace for
+   */
+  private static void logStackTrace(Throwable throwable) {
+    AdminDistributedSystemImpl adminDS = 
+                              AdminDistributedSystemImpl.getConnectedInstance();
+
+    logger.warn(throwable.getMessage(), throwable);
+  }  
+  
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/InternalManagedEntity.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/InternalManagedEntity.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/InternalManagedEntity.java
new file mode 100644
index 0000000..44e55e9
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/InternalManagedEntity.java
@@ -0,0 +1,105 @@
+/*
+ * 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.impl;
+
+import org.apache.geode.internal.admin.api.AdminDistributedSystem;
+import org.apache.geode.internal.admin.api.ManagedEntity;
+import org.apache.geode.internal.admin.api.ManagedEntityConfig;
+
+/**
+ * Provides internal-only functionality that is expected of all
+ * <code>ManagedEntity<code>s.  This functionality is used by the
+ * {@link ManagedEntityController} to manage the entity.
+ *
+ * @since GemFire 4.0
+ */
+public interface InternalManagedEntity extends ManagedEntity {
+
+  /** The state of a managed entity is unknown. */
+  public static final int UNKNOWN = 10;
+
+  /** A managed entity is stopped */
+  public static final int STOPPED = 11;
+
+  /** A managed entity is stopping (being stopped) */
+  public static final int STOPPING = 12;
+
+  /** A managed entity is starting */
+  public static final int STARTING = 13;
+
+  /** A managed entity is running (is started) */
+  public static final int RUNNING = 14;
+
+  //////////////////////  Instance Methods  //////////////////////
+
+  /**
+   * Returns the <code>ManagedEntityConfig</code> for this
+   * <code>ManagedEntity</code>. 
+   */
+  public ManagedEntityConfig getEntityConfig();
+
+  /**
+   * Returns a brief description (such as "locator") of this managed
+   * entity. 
+   */
+  public String getEntityType();
+
+  /**
+   * Returns the (local) command to execute in order to start this
+   * managed entity.  The command includes the full path to the
+   * executable (include <code>$GEMFIRE/bin</code>) and any
+   * command-line arguments.  It does not take the {@linkplain
+   * ManagedEntityConfig#getRemoteCommand remote command} into account.
+   */
+  public String getStartCommand();
+
+  /**
+   * Returns the (local) command to execute in order to stop this
+   * managed entity.
+   */
+  public String getStopCommand();
+
+  /**
+   * Returns the (local) command to execute in order to determine
+   * whether or not this managed entity is runing.
+   */
+  public String getIsRunningCommand();
+
+  /**
+   * Returns a descriptive, one-word, unique id for a newly-created
+   * <code>ManagedEntity</code>.   This ensures that we do not have
+   * collisions in the ids of entities.
+   */
+  public String getNewId();
+
+  /**
+   * Returns the distributed system to which this managed entity
+   * belongs.
+   */
+  public AdminDistributedSystem getDistributedSystem();
+
+  /**
+   * Sets the state of this managed entity and informs threads that
+   * are waiting for a state change.  See bug 32455.
+   *
+   * @return The previous state of this managed entity.
+   *
+   * @see #RUNNING
+   */
+  public int setState(int state);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/LogCollator.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/LogCollator.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/LogCollator.java
new file mode 100755
index 0000000..f51e634
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/LogCollator.java
@@ -0,0 +1,137 @@
+/*
+ * 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.impl;
+
+import org.apache.geode.internal.admin.GfManagerAgent;
+import org.apache.geode.internal.admin.GemFireVM;
+import org.apache.geode.internal.admin.ApplicationVM;
+import org.apache.geode.internal.logging.MergeLogFiles;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;  
+import java.io.PrintWriter;  
+import java.io.StringWriter;  
+import java.util.ArrayList;
+import java.util.List;
+
+public class LogCollator {
+  
+  private GfManagerAgent system;
+  private List logTails;
+    
+  public LogCollator() {
+  }
+  
+  public String collateLogs(GfManagerAgent system) {
+    try {
+      if (system == null) {
+        return "";
+      }
+      this.system = system;
+      this.logTails = new ArrayList();
+      gatherActiveLogs();
+      gatherInactiveLogs();
+      return mergeLogs();
+    }
+    finally {
+      this.system = null;
+      this.logTails = null;
+    }
+  }
+
+  // -------------------------------------------------------------------------
+  
+  private String mergeLogs() {
+    // combine logs...
+    InputStream[] logFiles = new InputStream[this.logTails.size()];
+    String[] logFileNames = new String[logFiles.length];
+    for (int i = 0; i < this.logTails.size(); i++) {
+      Loglet loglet = (Loglet) this.logTails.get(i);
+      logFiles[i] = new ByteArrayInputStream(loglet.tail.getBytes());
+      logFileNames[i] = loglet.name;
+    }
+    
+    // delegate to MergeLogFiles...
+    StringWriter writer = new StringWriter();
+    PrintWriter mergedLog = new PrintWriter(writer);
+    if (!MergeLogFiles.mergeLogFiles(logFiles, logFileNames, mergedLog)) {
+      return writer.toString();
+    } 
+    else {
+      return "";
+    }
+  }
+
+  private void gatherActiveLogs() {
+    ApplicationVM[] runningsApps = this.system.listApplications();
+    for (int i = 0; i < runningsApps.length; i++) {
+      addLogFrom(runningsApps[i]);
+    }
+  }
+  
+  private void gatherInactiveLogs() {
+    /* not yet supported....
+    if (useStopped) {
+      LogViewHelper helper = new LogViewHelper();
+      for (Iterator iter = stoppedNodes.iterator(); iter.hasNext(); ) {
+        Object adminEntity = iter.next();
+        helper.setAdminEntity(adminEntity);
+        try {
+          if (helper.logViewAvailable()) {
+            String[] logs = helper.getSystemLogs();
+            addTail(allTails, logs, adminEntity.toString());
+          }
+        } catch (Exception e) {
+          Service.getService().reportSystemError(e);
+        }
+      }
+    }
+    */
+  }
+  
+  private void addLogFrom(GemFireVM vm) {
+    String name = null;
+    name = vm.toString();
+    String[] logs = vm.getSystemLogs();
+    addTail(name, logs);
+  }
+
+  private void addTail(String logName, String[] logs) {
+    if (logs.length > 0) {
+      String tail = (logs.length > 1) ? logs[1] : logs[0];      
+      this.logTails.add(new Loglet(logName, tail));
+    }
+  }
+
+  /*
+  public void setUseStoppedManagers(boolean useStopped) {
+    this.useStopped = useStopped;
+  }
+  */
+
+  private static class Loglet {
+    String name;
+    String tail;
+    Loglet(String name, String tail) {
+      this.name = name;
+      this.tail = tail;
+    }
+  }
+  
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/c0221bed/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/ManagedEntityConfigImpl.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/ManagedEntityConfigImpl.java b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/ManagedEntityConfigImpl.java
new file mode 100644
index 0000000..9dcf802
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/admin/api/impl/ManagedEntityConfigImpl.java
@@ -0,0 +1,260 @@
+/*
+ * 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.impl;
+
+import org.apache.geode.internal.admin.api.ManagedEntityConfig;
+import org.apache.geode.internal.admin.GemFireVM;
+import org.apache.geode.internal.i18n.LocalizedStrings;
+import org.apache.geode.internal.GemFireVersion;
+import org.apache.geode.internal.net.SocketCreator;
+
+import java.io.File;
+import java.net.*;
+
+/**
+ * The abstract superclass of objects that configure a managed entity
+ * such as a GemFire cache server or a distribution locator.
+ * It contains configuration state and behavior common to all managed
+ * entities.
+ *
+ * @since GemFire 4.0
+ */
+public abstract class ManagedEntityConfigImpl
+  implements ManagedEntityConfig {
+
+  /** The name of the host on which the managed entity runs */
+  private String host;
+
+  /** Directory in which the locator runs */
+  private String workingDirectory;
+
+  /** The directory in which GemFire is installed */
+  private String productDirectory;
+
+  /** Command used to launch locator on remote machine */
+  private String remoteCommand;
+
+  /** The managed entity configured by this object.
+   *
+   * @see #isReadOnly */
+  private InternalManagedEntity entity = null;
+
+  /////////////////////  Static Methods  /////////////////////
+
+  /**
+   * Returns the {@linkplain InetAddress#getCanonicalHostName
+   * canonical name} of the local machine.
+   */
+  protected static String getLocalHostName() {
+    try {
+      return SocketCreator.getLocalHost().getCanonicalHostName();
+
+    } catch (UnknownHostException ex) {
+      IllegalStateException ex2 = new IllegalStateException(LocalizedStrings.ManagedEntityConfigImpl_COULD_NOT_DETERMINE_LOCALHOST.toLocalizedString());
+      ex2.initCause(ex);
+      throw ex2;
+    }
+  }
+
+  /**
+   * Returns the current working directory for this VM.
+   */
+  private static File getCurrentWorkingDirectory() {
+    File cwd = new File(System.getProperty("user.dir"));
+    return cwd.getAbsoluteFile();
+  }
+
+  /**
+   * Returns the location of the GemFire product installation.  This
+   * is determined by finding the location of the gemfire jar
+   * and working backwards.
+   */
+  private static File getGemFireInstallation() {
+    URL url = GemFireVersion.getJarURL();
+    if (url == null) {
+      throw new IllegalStateException(LocalizedStrings.ManagedEntityConfigImpl_COULD_NOT_FIND_GEMFIREJAR.toLocalizedString());
+    }
+
+    File gemfireJar = new File(url.getPath());
+    File lib = gemfireJar.getParentFile();
+    File product = lib.getParentFile();
+
+    return product;
+  }
+
+  //////////////////////  Constructors  //////////////////////
+
+  /**
+   * Creates a <code>ManagedEntityConfigImpl</code> with the default
+   * configuration.
+   */
+  protected ManagedEntityConfigImpl() {
+    this.host = getLocalHostName();
+    this.workingDirectory =
+      getCurrentWorkingDirectory().getAbsolutePath(); 
+    this.productDirectory =
+      getGemFireInstallation().getAbsolutePath();
+    this.remoteCommand = null;  // Delegate to AdminDistributedSystem
+  }
+
+  /**
+   * Creates a new <code>ManagedEntityConfigImpl</code> based on the
+   * configuration of a running <code>GemFireVM</code>
+   */
+  protected ManagedEntityConfigImpl(GemFireVM vm) {
+    this.host = SocketCreator.getHostName(vm.getHost());
+    this.workingDirectory = vm.getWorkingDirectory().getAbsolutePath();
+    this.productDirectory = vm.getGemFireDir().getAbsolutePath();
+    this.remoteCommand = null;
+  }
+
+  /**
+   * A copy constructor that creates a new
+   * <code>ManagedEntityConfigImpl</code> with the same configuration
+   * as another <code>ManagedEntityConfig</code>.
+   */
+  protected ManagedEntityConfigImpl(ManagedEntityConfig other) {
+    this.host = other.getHost();
+    this.workingDirectory = other.getWorkingDirectory();
+    this.productDirectory = other.getProductDirectory();
+    this.remoteCommand = other.getRemoteCommand();
+  }
+
+  ////////////////////  Instance Methods  ////////////////////
+
+  /**
+   * Checks to see if this config object is "read only".  If it is,
+   * then an {@link IllegalStateException} is thrown.  It should be
+   * called by every setter method.
+   *
+   * @see #isReadOnly
+   */
+  public void checkReadOnly() {
+    if (this.isReadOnly()) {
+      throw new IllegalStateException(LocalizedStrings.ManagedEntityConfigImpl_THIS_CONFIGURATION_CANNOT_BE_MODIFIED_WHILE_ITS_MANAGED_ENTITY_IS_RUNNING.toLocalizedString());
+    }
+  }
+
+  /**
+   * Returns whether or not this <code>ManagedEntityConfigImpl</code>
+   * is read-only (can be modified).
+   */
+  protected boolean isReadOnly() {
+    return this.entity != null && this.entity.isRunning();
+  }
+
+  /**
+   * Sets the entity that is configured by this config object.  Once
+   * the entity is running, the config object cannot be modified.
+   *
+   * @see #checkReadOnly
+   */
+  public void setManagedEntity(InternalManagedEntity entity) {
+    this.entity = entity;
+  }
+
+  /**
+   * Notifies any configuration listeners that this configuration has
+   * changed.
+   */
+  protected abstract void configChanged();
+
+  public String getHost() {
+    return this.host;
+  }
+
+  public void setHost(String host) {
+    checkReadOnly();
+    this.host = host;
+    configChanged();
+  }
+
+  public String getWorkingDirectory() {
+    String dir = this.workingDirectory;
+    return dir;
+  }
+
+  public void setWorkingDirectory(String workingDirectory) {
+    checkReadOnly();
+    this.workingDirectory = workingDirectory;
+    configChanged();
+  }
+
+  public String getProductDirectory() {
+    return this.productDirectory;
+  }
+
+  public void setProductDirectory(String productDirectory) {
+    checkReadOnly();
+    this.productDirectory = productDirectory;
+    configChanged();
+  }
+
+  public String getRemoteCommand() {
+    return this.remoteCommand;
+  }
+
+  public void setRemoteCommand(String remoteCommand) {
+    checkReadOnly();
+    this.remoteCommand = remoteCommand;
+    configChanged();
+  }
+
+  /**
+   * Validates this configuration.
+   *
+   * @throws IllegalStateException
+   *         If this config is not valid
+   */
+  public void validate() {
+    if (InetAddressUtil.validateHost(this.host) == null) {
+      throw new IllegalStateException(LocalizedStrings.ManagedEntityConfigImpl_INVALID_HOST_0.toLocalizedString(this.host));
+    }
+  }
+
+  @Override
+  public Object clone() throws CloneNotSupportedException {
+    // Since all fields are immutable objects, no deep cloning is
+    // necessary.
+    ManagedEntityConfigImpl clone =
+      (ManagedEntityConfigImpl) super.clone();
+    clone.entity = null;
+    return clone;
+  }
+
+  @Override
+  public String toString() {
+    String className = this.getClass().getName();
+    int index = className.lastIndexOf('.');
+    className = className.substring(index + 1);
+
+    StringBuffer sb = new StringBuffer();
+    sb.append(className);
+    
+    sb.append(" host=");
+    sb.append(this.getHost());
+    sb.append(" workingDirectory=");
+    sb.append(this.getWorkingDirectory());
+    sb.append(" productDirectory=");
+    sb.append(this.getProductDirectory());
+    sb.append(" remoteCommand=\"");
+    sb.append(this.getRemoteCommand());
+    sb.append("\"");
+
+    return sb.toString();
+  }
+}