http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/096b622d/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/096b622d/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/096b622d/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/096b622d/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/096b622d/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/096b622d/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/096b622d/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/096b622d/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/096b622d/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/096b622d/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/096b622d/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/096b622d/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(); + } +}
