/* * 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.distributed; import static org.apache.geode.distributed.ConfigurationProperties.*; import org.apache.geode.cache.client.internal.locator.*; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.distributed.internal.DistributionConfigImpl; import org.apache.geode.distributed.internal.InternalLocator; import org.apache.geode.distributed.internal.tcpserver.*; import org.apache.geode.internal.DistributionLocator; import org.apache.geode.internal.GemFireVersion; import org.apache.geode.internal.net.SocketCreator; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.lang.ObjectUtils; import org.apache.geode.internal.lang.StringUtils; import org.apache.geode.internal.lang.SystemUtils; import org.apache.geode.internal.process.*; import org.apache.geode.internal.util.IOUtils; import org.apache.geode.lang.AttachAPINotFoundException; import org.apache.geode.management.internal.cli.json.GfJsonArray; import org.apache.geode.management.internal.cli.json.GfJsonException; import org.apache.geode.management.internal.cli.json.GfJsonObject; import joptsimple.OptionException; import joptsimple.OptionParser; import joptsimple.OptionSet; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.ConnectException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; /** * The LocatorLauncher class is a launcher for a GemFire Locator. * * @see org.apache.geode.distributed.AbstractLauncher * @see org.apache.geode.distributed.ServerLauncher * @since GemFire 7.0 */ @SuppressWarnings({"unused"}) public final class LocatorLauncher extends AbstractLauncher<String> { /** * @deprecated This is specific to the internal implementation and may go away in a future * release. */ public static final Integer DEFAULT_LOCATOR_PORT = getDefaultLocatorPort(); private static final Boolean DEFAULT_LOAD_SHARED_CONFIG_FROM_DIR = Boolean.FALSE; private static final Map<String, String> helpMap = new HashMap<>(); static { helpMap.put("launcher", LocalizedStrings.LocatorLauncher_LOCATOR_LAUNCHER_HELP.toLocalizedString()); helpMap.put(Command.START.getName(), LocalizedStrings.LocatorLauncher_START_LOCATOR_HELP .toLocalizedString(String.valueOf(getDefaultLocatorPort()))); helpMap.put(Command.STATUS.getName(), LocalizedStrings.LocatorLauncher_STATUS_LOCATOR_HELP.toLocalizedString()); helpMap.put(Command.STOP.getName(), LocalizedStrings.LocatorLauncher_STOP_LOCATOR_HELP.toLocalizedString()); helpMap.put(Command.VERSION.getName(), LocalizedStrings.LocatorLauncher_VERSION_LOCATOR_HELP.toLocalizedString()); helpMap.put("bind-address", LocalizedStrings.LocatorLauncher_LOCATOR_BIND_ADDRESS_HELP.toLocalizedString()); helpMap.put("debug", LocalizedStrings.LocatorLauncher_LOCATOR_DEBUG_HELP.toLocalizedString()); helpMap.put("dir", LocalizedStrings.LocatorLauncher_LOCATOR_DIR_HELP.toLocalizedString()); helpMap.put("force", LocalizedStrings.LocatorLauncher_LOCATOR_FORCE_HELP.toLocalizedString()); helpMap.put("help", LocalizedStrings.SystemAdmin_CAUSES_GEMFIRE_TO_PRINT_OUT_INFORMATION_INSTEAD_OF_PERFORMING_THE_COMMAND_THIS_OPTION_IS_SUPPORTED_BY_ALL_COMMANDS .toLocalizedString()); helpMap.put("hostname-for-clients", LocalizedStrings.LocatorLauncher_LOCATOR_HOSTNAME_FOR_CLIENTS_HELP.toLocalizedString()); helpMap.put("member", LocalizedStrings.LocatorLauncher_LOCATOR_MEMBER_HELP.toLocalizedString()); helpMap.put("pid", LocalizedStrings.LocatorLauncher_LOCATOR_PID_HELP.toLocalizedString()); helpMap.put("port", LocalizedStrings.LocatorLauncher_LOCATOR_PORT_HELP .toLocalizedString(String.valueOf(getDefaultLocatorPort()))); helpMap.put("redirect-output", LocalizedStrings.LocatorLauncher_LOCATOR_REDIRECT_OUTPUT_HELP.toLocalizedString()); } private static final Map<Command, String> usageMap = new TreeMap<>(); static { usageMap.put(Command.START, "start <member-name> [--bind-address=<IP-address>] [--hostname-for-clients=<IP-address>] [--port=<port>] [--dir=<Locator-working-directory>] [--force] [--debug] [--help]"); usageMap.put(Command.STATUS, "status [--bind-address=<IP-address>] [--port=<port>] [--member=<member-ID/Name>] [--pid=<process-ID>] [--dir=<Locator-working-directory>] [--debug] [--help]"); usageMap.put(Command.STOP, "stop [--member=<member-ID/Name>] [--pid=<process-ID>] [--dir=<Locator-working-directory>] [--debug] [--help]"); usageMap.put(Command.VERSION, "version"); } /** * @deprecated This is specific to the internal implementation and may go away in a future * release. */ public static final String DEFAULT_LOCATOR_PID_FILE = "vf.gf.locator.pid"; private static final String DEFAULT_LOCATOR_LOG_EXT = ".log"; private static final String DEFAULT_LOCATOR_LOG_NAME = "locator"; private static final String LOCATOR_SERVICE_NAME = "Locator"; private static final AtomicReference<LocatorLauncher> INSTANCE = new AtomicReference<>(); // private volatile transient boolean debug; private final transient ControlNotificationHandler controlHandler; private final AtomicBoolean starting = new AtomicBoolean(false); private final boolean force; private final boolean help; private final boolean redirectOutput; private final Command command; private final boolean bindAddressSpecified; private final boolean portSpecified; private final boolean workingDirectorySpecified; private final InetAddress bindAddress; private final Integer pid; private final Integer port; private volatile transient InternalLocator locator; private final Properties distributedSystemProperties; private final String hostnameForClients; private final String memberName; private final String workingDirectory; // NOTE in addition to debug and locator, the other shared, mutable state private volatile transient String statusMessage; private volatile transient ControllableProcess process; private final transient LocatorControllerParameters controllerParameters; /** * Launches a GemFire Locator from the command-line configured with the given arguments. * * @param args the command-line arguments used to configure the GemFire Locator at runtime. */ public static void main(final String... args) { try { new Builder(args).build().run(); } catch (AttachAPINotFoundException e) { System.err.println(e.getMessage()); } } private static Integer getDefaultLocatorPort() { return Integer.getInteger(DistributionLocator.TEST_OVERRIDE_DEFAULT_PORT_PROPERTY, DistributionLocator.DEFAULT_LOCATOR_PORT); } /** * Gets the instance of the LocatorLauncher used to launch the GemFire Locator, or null if this VM * does not have an instance of LocatorLauncher indicating no GemFire Locator is running. * * @return the instance of LocatorLauncher used to launcher a GemFire Locator in this VM. */ public static LocatorLauncher getInstance() { return INSTANCE.get(); } /** * Gets the LocatorState for this process or null if this process was not launched using this VM's * LocatorLauncher reference. * * @return the LocatorState for this process or null. */ public static LocatorState getLocatorState() { return (getInstance() != null ? getInstance().status() : null); } /** * Private constructor used to properly construct an immutable instance of the LocatorLauncher * using a Builder. The Builder is used to configure a LocatorLauncher instance. The Builder can * process user input from the command-line or be used to properly construct an instance of the * LocatorLauncher programmatically using the API. * * @param builder an instance of LocatorLauncher.Builder for configuring and constructing an * instance of the LocatorLauncher. * @see org.apache.geode.distributed.LocatorLauncher.Builder */ private LocatorLauncher(final Builder builder) { this.command = builder.getCommand(); setDebug(Boolean.TRUE.equals(builder.getDebug())); this.force = Boolean.TRUE.equals(builder.getForce()); this.help = Boolean.TRUE.equals(builder.getHelp()); this.bindAddressSpecified = builder.isBindAddressSpecified(); this.bindAddress = builder.getBindAddress(); this.distributedSystemProperties = builder.getDistributedSystemProperties(); this.hostnameForClients = builder.getHostnameForClients(); this.memberName = builder.getMemberName(); // TODO:KIRK: set ThreadLocal for LogService with getLogFile or getLogFileName this.pid = builder.getPid(); this.portSpecified = builder.isPortSpecified(); this.port = builder.getPort(); this.redirectOutput = Boolean.TRUE.equals(builder.getRedirectOutput()); this.workingDirectorySpecified = builder.isWorkingDirectorySpecified(); this.workingDirectory = builder.getWorkingDirectory(); this.controllerParameters = new LocatorControllerParameters(); this.controlHandler = new ControlNotificationHandler() { @Override public void handleStop() { if (isStoppable()) { stopInProcess(); } } @Override public ServiceState<?> handleStatus() { return statusInProcess(); } }; } /** * Returns the status of the locator on the given host & port */ public static LocatorStatusResponse statusLocator(int port, InetAddress bindAddress) throws IOException { // final int timeout = (60 * 2 * 1000); // 2 minutes final int timeout = Integer.MAX_VALUE; // 2 minutes try { TcpClient client = new TcpClient(new DistributionConfigImpl(new Properties())); return (LocatorStatusResponse) client.requestToServer(bindAddress, port, new LocatorStatusRequest(), timeout, true); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } /** * Gets the reference to the Locator object representing the running GemFire Locator. * * @return a reference to the Locator. */ final InternalLocator getLocator() { return this.locator; } /** * Gets an identifier that uniquely identifies and represents the Locator associated with this * launcher. * * @return a String value identifier to uniquely identify the Locator and it's launcher. * @see #getBindAddressAsString() * @see #getPortAsString() */ public final String getId() { return LocatorState.getBindAddressAsString(this).concat("[") .concat(LocatorState.getPortAsString(this)).concat("]"); } /** * Get the Locator launcher command used to invoke the Locator. * * @return the Locator launcher command used to invoke the Locator. * @see org.apache.geode.distributed.LocatorLauncher.Command */ public Command getCommand() { return this.command; } /** * Determines whether the PID file is allowed to be overwritten when the Locator is started and a * PID file already exists in the Locator's specified working directory. * * @return boolean indicating if force has been enabled. */ public boolean isForcing() { return this.force; } /** * Determines whether this launcher will be used to display help information. If so, then none of * the standard Locator launcher commands will be used to affect the state of the Locator. A * launcher is said to be 'helping' if the user entered the "--help" option (switch) on the * command-line. * * @return a boolean value indicating if this launcher is used for displaying help information. * @see org.apache.geode.distributed.LocatorLauncher.Command */ public boolean isHelping() { return this.help; } /** * Determines whether this launcher will redirect output to system logs when starting a new * Locator process. * * @return a boolean value indicating if this launcher will redirect output to system logs when * starting a new Locator process */ public boolean isRedirectingOutput() { return this.redirectOutput; } /** * Gets the IP address of the NIC to which the Locator has bound itself listening for client * requests. * * @return an InetAddress object representing the configured bind address for the Locator. * @see java.net.InetAddress */ public InetAddress getBindAddress() { return this.bindAddress; } /** * Gets the host, as either hostname or IP address, on which the Locator was bound and running. An * attempt is made to get the canonical hostname for IP address to which the Locator was bound for * accepting client requests. If the bind address is null or localhost is unknown, then a default * String value of "localhost/127.0.0.1" is returned. * * Note, this information is purely information and should not be used to re-construct state or * for other purposes. * * @return the hostname or IP address of the host running the Locator, based on the bind-address, * or 'localhost/127.0.0.1' if the bind address is null and localhost is unknown. * @see java.net.InetAddress * @see #getBindAddress() */ protected String getBindAddressAsString() { try { if (getBindAddress() != null) { return getBindAddress().getCanonicalHostName(); } InetAddress localhost = SocketCreator.getLocalHost(); return localhost.getCanonicalHostName(); } catch (UnknownHostException ignore) { // TODO determine a better value for the host on which the Locator is running to return // here... // NOTE returning localhost/127.0.0.1 implies the bindAddress was null and no IP address for // localhost // could be found return "localhost/127.0.0.1"; } } /** * Gets the hostname that clients will use to lookup the running Locator. * * @return a String indicating the hostname used by clients to lookup the Locator. */ public String getHostnameForClients() { return this.hostnameForClients; } /** * Gets the name of the log file used to log information about this Locator. * * @return a String value indicating the name of this Locator's log file. */ public String getLogFileName() { return StringUtils.defaultIfBlank(getMemberName(), DEFAULT_LOCATOR_LOG_NAME) .concat(DEFAULT_LOCATOR_LOG_EXT); } /** * Gets the name of this member (this Locator) in the GemFire distributed system and determined by * the 'name' GemFire property. * * @return a String indicating the name of the member (this Locator) in the GemFire distributed * system. * @see AbstractLauncher#getMemberName() */ public String getMemberName() { return StringUtils.defaultIfBlank(this.memberName, super.getMemberName()); } /** * Gets the user-specified process ID (PID) of the running Locator that LocatorLauncher uses to * issue status and stop commands to the Locator. * * @return an Integer value indicating the process ID (PID) of the running Locator. */ @Override public Integer getPid() { return this.pid; } /** * Gets the port number on which the Locator listens for client requests. * * @return an Integer value indicating the port number on which the Locator is listening for * client requests. */ public Integer getPort() { if (locator != null) { return locator.getPort(); } return this.port; } /** * Gets the port number represented as a String value. If the port number is null, the the default * Locator port (10334) is returned; * * @return the port number as a String value. * @see #getPort() */ public String getPortAsString() { return ObjectUtils.defaultIfNull(getPort(), getDefaultLocatorPort()).toString(); } /** * Gets the GemFire Distributed System (cluster) Properties. * * @return a Properties object containing the configuration settings for the GemFire Distributed * System (cluster). * @see java.util.Properties */ public Properties getProperties() { return (Properties) this.distributedSystemProperties.clone(); } /** * Gets the name for a GemFire Locator. * * @return a String indicating the name for a GemFire Locator. */ public String getServiceName() { return LOCATOR_SERVICE_NAME; } /** * Gets the working directory pathname in which the Locator will be run. * * @return a String value indicating the pathname of the Locator's working directory. */ @Override public String getWorkingDirectory() { return this.workingDirectory; } /** * Displays help for the specified Locator launcher command to standard err. If the Locator * launcher command is unspecified, then usage information is displayed instead. * * @param command the Locator launcher command in which to display help information. * @see #usage() */ public void help(final Command command) { if (Command.isUnspecified(command)) { usage(); } else { info(StringUtils.wrap(helpMap.get(command.getName()), 80, "")); info("\n\nusage: \n\n"); info(StringUtils.wrap("> java ... " + getClass().getName() + " " + usageMap.get(command), 80, "\t\t")); info("\n\noptions: \n\n"); for (String option : command.getOptions()) { info(StringUtils.wrap("--" + option + ": " + helpMap.get(option) + "\n", 80, "\t")); } info("\n\n"); } } /** * Displays usage information on the proper invocation of the LocatorLauncher from the * command-line to standard err. * * @see #help(org.apache.geode.distributed.LocatorLauncher.Command) */ public void usage() { info(StringUtils.wrap(helpMap.get("launcher"), 80, "\t")); info("\n\nSTART\n\n"); help(Command.START); info("STATUS\n\n"); help(Command.STATUS); info("STOP\n\n"); help(Command.STOP); } /** * The Runnable method used to launch the Locator with the specified command. If 'start' has been * issued, then run will block as expected for the Locator to stop. The 'start' command is * implemented with a call to start() followed by a call to waitOnLocator(). * * @see java.lang.Runnable * @see LocatorLauncher.Command * @see LocatorLauncher#start() * @see LocatorLauncher#waitOnLocator() * @see LocatorLauncher#status() * @see LocatorLauncher#stop() * @see LocatorLauncher#version() * @see LocatorLauncher#help(org.apache.geode.distributed.LocatorLauncher.Command) * @see LocatorLauncher#usage() */ public void run() { if (!isHelping()) { switch (getCommand()) { case START: info(start()); waitOnLocator(); break; case STATUS: info(status()); break; case STOP: info(stop()); break; case VERSION: info(version()); break; default: usage(); } } else { help(getCommand()); } } /** * Gets a File reference with the path to the PID file for the Locator. * * @return a File reference to the path of the Locator's PID file. */ protected File getLocatorPidFile() { return new File(getWorkingDirectory(), ProcessType.LOCATOR.getPidFileName()); } /** * Determines whether a GemFire Locator can be started with this instance of LocatorLauncher. * * @return a boolean indicating whether a GemFire Locator can be started with this instance of * LocatorLauncher, which is true if the LocatorLauncher has not already started a Locator * or a Locator is not already running. * @see #start() */ private boolean isStartable() { return (!isRunning() && this.starting.compareAndSet(false, true)); } /** * Starts a Locator running on the specified port and bind address, as determined by getPort and * getBindAddress respectively, defaulting to 10334 and 'localhost' if not specified, with both * peer and server location enabled. * * 'start' is an asynchronous invocation of the Locator. As such, this method makes no guarantees * whether the Locator's location services (peer and server) are actually running before it * returns. The Locator's location-based services are initiated in separate, daemon Threads and * depends on the relative timing and scheduling of those Threads by the JVM. If the application * using this API wishes for the Locator to continue running after normal application processing * completes, then one must call <code>waitOnLocator</code>. * * Given the nature of start, the Locator's status will be in either 1 of 2 possible states. If * the 'request' to start the Locator proceeds without exception, the status will be 'STARTED'. * However, if any exception is encountered during the normal startup sequence, then a * RuntimeException is thrown and the status is set to 'STOPPED'. * * @return a LocatorState to reflect the state of the Locator after start. * @throws RuntimeException if the Locator failed to start for any reason. * @throws IllegalStateException if the Locator is already running. * @see #failOnStart(Throwable) * @see #getBindAddress() * @see #getDistributedSystemProperties() * @see #isForcing() * @see #getLogFile() * @see #getLocatorPidFile * @see #getPort() * @see #status() * @see #stop() * @see #waitOnLocator() * @see #waitOnStatusResponse(long, long, java.util.concurrent.TimeUnit) * @see org.apache.geode.distributed.LocatorLauncher.LocatorState * @see org.apache.geode.distributed.AbstractLauncher.Status#NOT_RESPONDING * @see org.apache.geode.distributed.AbstractLauncher.Status#ONLINE * @see org.apache.geode.distributed.AbstractLauncher.Status#STARTING */ @SuppressWarnings("deprecation") public LocatorState start() { if (isStartable()) { INSTANCE.compareAndSet(null, this); try { this.process = new ControllableProcess(this.controlHandler, new File(getWorkingDirectory()), ProcessType.LOCATOR, isForcing()); assertPortAvailable(getBindAddress(), getPort()); ProcessLauncherContext.set(isRedirectingOutput(), getOverriddenDefaults(), new StartupStatusListener() { @Override public void setStatus(final String statusMessage) { LocatorLauncher.this.statusMessage = statusMessage; } }); // TODO : remove the extra param for loadFromSharedConfigDir try { this.locator = InternalLocator.startLocator(getPort(), getLogFile(), null, null, null, getBindAddress(), getDistributedSystemProperties(), getHostnameForClients()); } finally { ProcessLauncherContext.remove(); } debug("Running Locator on (%1$s) in (%2$s) as (%2$s)...", getId(), getWorkingDirectory(), getMember()); running.set(true); return new LocatorState(this, Status.ONLINE); } catch (IOException e) { failOnStart(e); throw new RuntimeException(LocalizedStrings.Launcher_Command_START_IO_ERROR_MESSAGE .toLocalizedString(getServiceName(), getWorkingDirectory(), getId(), e.getMessage()), e); } catch (FileAlreadyExistsException e) { failOnStart(e); throw new RuntimeException( LocalizedStrings.Launcher_Command_START_PID_FILE_ALREADY_EXISTS_ERROR_MESSAGE .toLocalizedString(getServiceName(), getWorkingDirectory(), getId()), e); } catch (PidUnavailableException e) { failOnStart(e); throw new RuntimeException( LocalizedStrings.Launcher_Command_START_PID_UNAVAILABLE_ERROR_MESSAGE.toLocalizedString( getServiceName(), getId(), getWorkingDirectory(), e.getMessage()), e); } catch (Error e) { failOnStart(e); throw e; } catch (RuntimeException e) { failOnStart(e); throw e; } catch (Exception e) { failOnStart(e); throw new RuntimeException(e); } finally { this.starting.set(false); } } else { throw new IllegalStateException( LocalizedStrings.Launcher_Command_START_SERVICE_ALREADY_RUNNING_ERROR_MESSAGE .toLocalizedString(getServiceName(), getWorkingDirectory(), getId())); } } @Override protected Properties getDistributedSystemProperties() { Properties properties = super.getDistributedSystemProperties(getProperties()); return properties; } /** * A helper method to ensure the same sequence of actions are taken when the Locator fails to * start caused by some exception. * * @param cause the Throwable thrown during the startup or wait operation on the Locator. */ private void failOnStart(final Throwable cause) { if (cause != null) { logger.log(Level.INFO, "locator is exiting due to an exception", cause); } else { logger.log(Level.INFO, "locator is exiting normally"); } if (this.locator != null) { this.locator.stop(); this.locator = null; } if (this.process != null) { this.process.stop(); this.process = null; } INSTANCE.compareAndSet(this, null); this.running.set(false); } /** * Waits on the Locator to stop causing the calling Thread to join with the Locator's * location-based services Thread. * * @return the Locator's status once it stops. * @throws AssertionError if the Locator has not been started and the reference is null * (assertions must be enabled for the error to be thrown). * @see #failOnStart(Throwable) * @see org.apache.geode.distributed.AbstractLauncher.Status * @see org.apache.geode.distributed.LocatorLauncher.LocatorState */ public LocatorState waitOnLocator() { Throwable t = null; try { // make sure the Locator was started and the reference was set assert getLocator() != null : "The Locator must first be started with a call to start!"; debug("Waiting on Locator (%1$s) to stop...", getId()); // prevent the JVM from exiting by joining the Locator Thread getLocator().waitToStop(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); t = e; debug(e); } catch (RuntimeException e) { t = e; throw e; } catch (Throwable e) { t = e; throw e; } finally { failOnStart(t); } return new LocatorState(this, Status.STOPPED); } /** * Waits for a Locator status request response to be returned up to the specified timeout in the * given unit of time. This call will send status requests at fixed intervals in the given unit of * time until the timeout expires. If the request to determine the Locator's status is successful, * then the Locator is considered to be 'ONLINE'. Otherwise, the Locator is considered to be * unresponsive to the status request. * * However, this does not necessarily imply the Locator start was unsuccessful, only that a * response was not received in the given time period. * * Note, this method does not block or cause the Locator's location-based services (daemon * Threads) to continue running in anyway if the main application Thread terminates when running * the Locator in-process. If the caller wishes to start a Locator in an asynchronous manner * within the application process, then a call should be made to <code>waitOnLocator</code>. * * @param timeout a long value in time unit indicating when the period of time should expire in * attempting to determine the Locator's status. * @param interval a long value in time unit for how frequent the requests should be sent to the * Locator. * @param timeUnit the unit of time in which the timeout and interval are measured. * @return the state of the Locator, which will either be 'ONLINE' or "NOT RESPONDING'. If the * status returned is 'NOT RESPONDING', it just means the Locator did not respond to the * status request within the given time period. It should not be taken as the Locator * failed to start. * @see #waitOnLocator() */ public LocatorState waitOnStatusResponse(final long timeout, final long interval, final TimeUnit timeUnit) { final long endTimeInMilliseconds = (System.currentTimeMillis() + timeUnit.toMillis(timeout)); while (System.currentTimeMillis() < endTimeInMilliseconds) { try { LocatorStatusResponse response = statusLocator(getPort(), getBindAddress()); return new LocatorState(this, Status.ONLINE, response); } catch (Exception ignore) { try { synchronized (this) { timeUnit.timedWait(this, interval); } } catch (InterruptedException ignoreInterrupt) { // NOTE just go and send another status request to the Locator... } } } // NOTE just because we were not able to communicate with the Locator in the given amount of // time does not mean // the Locator is having problems. The Locator could be slow in starting up and the timeout may // not be // long enough. return new LocatorState(this, Status.NOT_RESPONDING); } /** * Attempts to determine the state of the Locator. The Locator's status will be in only 1 of 2 * possible states, either ONLINE or OFFLINE. This method behaves differently depending on which * parameters were specified when the LocatorLauncher was constructed with an instance of Builder. * If either the 'dir' or the 'pid' command-line option were specified, then an attempt is made to * determine the Locator's status by using the dir or pid to correctly identify the Locator's * MemberMXBean registered in the MBeanServer of the Locator's JVM, and invoking the 'status' * operation. The same behavior occurs if the caller specified the Locator's GemFire member name * or ID. * * However, if 'dir' or 'pid' were not specified, then determining the Locator's status defaults * to using the configured bind address and port. If the bind address or port was not specified * when using the Builder to construct a LocatorLauncher instance, then the defaults for both bind * address and port are used. In either case, an actual TCP/IP request is made to the Locator's * ServerSocket to ensure it is listening for client requests. This is true even when the * LocatorLauncher is used in-process by calling the API. * * If the conditions above hold, then the Locator is deemed to be 'ONLINE', otherwise, the Locator * is considered 'OFFLINE'. * * @return the Locator's state. * @see #start() * @see #stop() * @see org.apache.geode.distributed.AbstractLauncher.Status * @see org.apache.geode.distributed.LocatorLauncher.LocatorState */ public LocatorState status() { final LocatorLauncher launcher = getInstance(); // if this instance is starting then return local status if (this.starting.get()) { debug( "Getting status from the LocatorLauncher instance that actually launched the GemFire Locator.%n"); return new LocatorState(this, Status.STARTING); } // if this instance is running then return local status else if (isRunning()) { debug("Getting Locator status using host (%1$s) and port (%2$s)%n", getBindAddressAsString(), getPortAsString()); return statusWithPort(); } // if in-process do not use ProcessController else if (isPidInProcess() && launcher != null) { return launcher.statusInProcess(); } // attempt to get status using pid if provided else if (getPid() != null) { debug("Getting Locator status using process ID (%1$s)%n", getPid()); return statusWithPid(); } // attempt to get status using workingDirectory unless port was specified else if (!(this.bindAddressSpecified || this.portSpecified)) { debug("Getting Locator status using working directory (%1$s)%n", getWorkingDirectory()); return statusWithWorkingDirectory(); } // attempt to get status using host and port (Note, bind address doubles as host when the // launcher // is used to get the Locator's status). else { debug("Getting Locator status using host (%1$s) and port (%2$s)%n", getBindAddressAsString(), getPortAsString()); return statusWithPort(); } } private LocatorState statusInProcess() { if (this.starting.get()) { debug( "Getting status from the LocatorLauncher instance that actually launched the GemFire Locator.%n"); return new LocatorState(this, Status.STARTING); } else { debug("Getting Locator status using host (%1$s) and port (%2$s)%n", getBindAddressAsString(), getPortAsString()); return statusWithPort(); } } private LocatorState statusWithPid() { try { final ProcessController controller = new ProcessControllerFactory() .createProcessController(this.controllerParameters, getPid()); controller.checkPidSupport(); final String statusJson = controller.status(); return LocatorState.fromJson(statusJson); } catch (ConnectionFailedException e) { // failed to attach to locator JVM return createNoResponseState(e, "Failed to connect to locator with process id " + getPid()); } catch (IOException e) { // failed to open or read file or dir return createNoResponseState(e, "Failed to communicate with locator with process id " + getPid()); } catch (MBeanInvocationFailedException e) { // MBean either doesn't exist or method or attribute don't exist return createNoResponseState(e, "Failed to communicate with locator with process id " + getPid()); } catch (UnableToControlProcessException e) { return createNoResponseState(e, "Failed to communicate with locator with process id " + getPid()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return createNoResponseState(e, "Interrupted while trying to communicate with locator with process id " + getPid()); } catch (TimeoutException e) { return createNoResponseState(e, "Failed to communicate with locator with process id " + getPid()); } } private LocatorState statusWithPort() { try { LocatorStatusResponse response = statusLocator(getPort(), getBindAddress()); return new LocatorState(this, Status.ONLINE, response); } catch (Exception e) { return createNoResponseState(e, "Failed to connect to locator " + getId()); } } private LocatorState statusWithWorkingDirectory() { int parsedPid = 0; try { final ProcessController controller = new ProcessControllerFactory().createProcessController(this.controllerParameters, new File(getWorkingDirectory()), ProcessType.LOCATOR.getPidFileName(), READ_PID_FILE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); parsedPid = controller.getProcessId(); // note: in-process request will go infinite loop unless we do the following if (parsedPid == ProcessUtils.identifyPid()) { LocatorLauncher runningLauncher = getInstance(); if (runningLauncher != null) { return runningLauncher.status(); } } final String statusJson = controller.status(); return LocatorState.fromJson(statusJson); } catch (ConnectionFailedException e) { // failed to attach to locator JVM return createNoResponseState(e, "Failed to connect to locator with process id " + parsedPid); } catch (FileNotFoundException e) { // could not find pid file return createNoResponseState(e, "Failed to find process file " + ProcessType.LOCATOR.getPidFileName() + " in " + getWorkingDirectory()); } catch (IOException e) { // failed to open or read file or dir return createNoResponseState(e, "Failed to communicate with locator with process id " + parsedPid); } catch (MBeanInvocationFailedException e) { // MBean either doesn't exist or method or attribute don't exist return createNoResponseState(e, "Failed to communicate with locator with process id " + parsedPid); } catch (PidUnavailableException e) { // couldn't determine pid from within locator JVM return createNoResponseState(e, "Failed to find usable process id within file " + ProcessType.LOCATOR.getPidFileName() + " in " + getWorkingDirectory()); } catch (UnableToControlProcessException e) { return createNoResponseState(e, "Failed to communicate with locator with process id " + parsedPid); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return createNoResponseState(e, "Interrupted while trying to communicate with locator with process id " + parsedPid); } catch (TimeoutException e) { return createNoResponseState(e, "Failed to communicate with locator with process id " + parsedPid); } } /** * Determines whether the Locator can be stopped in-process, such as when a Locator is embedded in * an application and the LocatorLauncher API is being used. * * @return a boolean indicating whether the Locator can be stopped in-process (the application's * process with an embedded Locator). */ protected boolean isStoppable() { return (isRunning() && getLocator() != null); } /** * Stop shuts the running Locator down. Using the API, the Locator is requested to stop by calling * the Locator object's 'stop' method. Internally, this method is no different than using the * LocatorLauncher class from the command-line or from within GemFire shell (Gfsh). In every * single case, stop sends a TCP/IP 'shutdown' request on the configured address/port to which the * Locator is bound and listening. * * If the "shutdown" request is successful, then the Locator will be 'STOPPED'. Otherwise, the * Locator is considered 'OFFLINE' since the actual state cannot be fully assessed (as in the * application process in which the Locator was hosted may still be running and the Locator object * may still exist even though it is no longer responding to location-based requests). The later * is particularly important in cases where the system resources (such as Sockets) may not have * been cleaned up yet. Therefore, by returning a status of 'OFFLINE', the value is meant to * reflect this in-deterministic state. * * @return a LocatorState indicating the state of the Locator after stop has been requested. * @see #start() * @see #status() * @see org.apache.geode.distributed.LocatorLauncher.LocatorState * @see org.apache.geode.distributed.AbstractLauncher.Status#NOT_RESPONDING * @see org.apache.geode.distributed.AbstractLauncher.Status#STOPPED */ public LocatorState stop() { final LocatorLauncher launcher = getInstance(); // if this instance is running then stop it if (isStoppable()) { return stopInProcess(); } // if in-process but difference instance of LocatorLauncher else if (isPidInProcess() && launcher != null) { return launcher.stopInProcess(); } // attempt to stop Locator using pid... else if (getPid() != null) { return stopWithPid(); } // attempt to stop Locator using the working directory... // else if (this.workingDirectorySpecified) { else if (getWorkingDirectory() != null) { return stopWithWorkingDirectory(); } else { return new LocatorState(this, Status.NOT_RESPONDING); } } private LocatorState stopInProcess() { if (isStoppable()) { this.locator.stop(); this.locator = null; this.process.stop(); this.process = null; INSTANCE.compareAndSet(this, null); // note: other thread may return Status.NOT_RESPONDING now this.running.set(false); return new LocatorState(this, Status.STOPPED); } else { return new LocatorState(this, Status.NOT_RESPONDING); } } private LocatorState stopWithPid() { try { final ProcessController controller = new ProcessControllerFactory() .createProcessController(new LocatorControllerParameters(), getPid()); controller.checkPidSupport(); controller.stop(); return new LocatorState(this, Status.STOPPED); } catch (ConnectionFailedException e) { // failed to attach to locator JVM return createNoResponseState(e, "Failed to connect to locator with process id " + getPid()); } catch (IOException e) { // failed to open or read file or dir return createNoResponseState(e, "Failed to communicate with locator with process id " + getPid()); } catch (MBeanInvocationFailedException e) { // MBean either doesn't exist or method or attribute don't exist return createNoResponseState(e, "Failed to communicate with locator with process id " + getPid()); } catch (UnableToControlProcessException e) { return createNoResponseState(e, "Failed to communicate with locator with process id " + getPid()); } } @Deprecated private LocatorState stopWithPort() { try { new TcpClient().stop(getBindAddress(), getPort()); return new LocatorState(this, Status.STOPPED); } catch (ConnectException e) { if (getBindAddress() == null) { return createNoResponseState(e, "Failed to connect to locator on port " + getPort()); } else { return createNoResponseState(e, "Failed to connect to locator on " + getId()); } } } private LocatorState stopWithWorkingDirectory() { int parsedPid = 0; try { final ProcessController controller = new ProcessControllerFactory().createProcessController(this.controllerParameters, new File(getWorkingDirectory()), ProcessType.LOCATOR.getPidFileName(), READ_PID_FILE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); parsedPid = controller.getProcessId(); // NOTE in-process request will go infinite loop unless we do the following if (parsedPid == ProcessUtils.identifyPid()) { final LocatorLauncher runningLauncher = getInstance(); if (runningLauncher != null) { return runningLauncher.stopInProcess(); } } controller.stop(); return new LocatorState(this, Status.STOPPED); } catch (ConnectionFailedException e) { // failed to attach to locator JVM return createNoResponseState(e, "Failed to connect to locator with process id " + parsedPid); } catch (FileNotFoundException e) { // could not find pid file return createNoResponseState(e, "Failed to find process file " + ProcessType.LOCATOR.getPidFileName() + " in " + getWorkingDirectory()); } catch (IOException e) { // failed to open or read file or dir return createNoResponseState(e, "Failed to communicate with locator with process id " + parsedPid); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return createNoResponseState(e, "Interrupted while trying to communicate with locator with process id " + parsedPid); } catch (MBeanInvocationFailedException e) { // MBean either doesn't exist or method or attribute don't exist return createNoResponseState(e, "Failed to communicate with locator with process id " + parsedPid); } catch (PidUnavailableException e) { // couldn't determine pid from within locator JVM return createNoResponseState(e, "Failed to find usable process id within file " + ProcessType.LOCATOR.getPidFileName() + " in " + getWorkingDirectory()); } catch (TimeoutException e) { return createNoResponseState(e, "Timed out trying to find usable process id within file " + ProcessType.LOCATOR.getPidFileName() + " in " + getWorkingDirectory()); } catch (UnableToControlProcessException e) { return createNoResponseState(e, "Failed to communicate with locator with process id " + parsedPid); } } private LocatorState createNoResponseState(final Exception cause, final String errorMessage) { debug(cause); // info(errorMessage); return new LocatorState(this, Status.NOT_RESPONDING, errorMessage); } private Properties getOverriddenDefaults() { Properties overriddenDefaults = new Properties(); overriddenDefaults.put(ProcessLauncherContext.OVERRIDDEN_DEFAULTS_PREFIX.concat(LOG_FILE), getLogFileName()); for (String key : System.getProperties().stringPropertyNames()) { if (key.startsWith(ProcessLauncherContext.OVERRIDDEN_DEFAULTS_PREFIX)) { overriddenDefaults.put(key, System.getProperty(key)); } } return overriddenDefaults; } private class LocatorControllerParameters implements ProcessControllerParameters { @Override public File getPidFile() { return getLocatorPidFile(); } @Override public File getWorkingDirectory() { return new File(LocatorLauncher.this.getWorkingDirectory()); } @Override public int getProcessId() { return getPid(); } @Override public ProcessType getProcessType() { return ProcessType.LOCATOR; } @Override public ObjectName getNamePattern() { try { return ObjectName.getInstance("GemFire:type=Member,*"); } catch (MalformedObjectNameException e) { return null; } catch (NullPointerException e) { return null; } } @Override public String getPidAttribute() { return "ProcessId"; } @Override public String getStopMethod() { return "shutDownMember"; } @Override public String getStatusMethod() { return "status"; } @Override public String[] getAttributes() { return new String[] {"Locator", "Server"}; } @Override public Object[] getValues() { return new Object[] {Boolean.TRUE, Boolean.FALSE}; } } /** * Following the Builder design pattern, the LocatorLauncher Builder is used to configure and * create a properly initialized instance of the LocatorLauncher class for running the Locator and * performing other Locator operations. */ public static class Builder { protected static final Command DEFAULT_COMMAND = Command.UNSPECIFIED; private Boolean debug; private Boolean force; private Boolean help; private Boolean redirectOutput; private Boolean loadSharedConfigFromDir; private Command command; private InetAddress bindAddress; private Integer pid; private Integer port; private final Properties distributedSystemProperties = new Properties(); private String hostnameForClients; private String memberName; private String workingDirectory; /** * Default constructor used to create an instance of the Builder class for programmatical * access. */ public Builder() {} /** * Constructor used to create and configure an instance of the Builder class with the specified * arguments, often passed from the command-line when launching an instance of this class from * the command-line using the Java launcher. * * @param args the array of arguments used to configure the Builder. */ public Builder(final String... args) { parseArguments(args != null ? args : new String[0]); } /** * Gets an instance of the JOpt Simple OptionParser to parse the command-line arguments. * * @return an instance of the JOpt Simple OptionParser configured with the command-line options * used by the Locator. */ private OptionParser getParser() { final OptionParser parser = new OptionParser(true); parser.accepts("bind-address").withRequiredArg().ofType(String.class); parser.accepts("debug"); parser.accepts("dir").withRequiredArg().ofType(String.class); parser.accepts("force"); parser.accepts("help"); parser.accepts("hostname-for-clients").withRequiredArg().ofType(String.class); parser.accepts("pid").withRequiredArg().ofType(Integer.class); parser.accepts("port").withRequiredArg().ofType(Integer.class); parser.accepts("redirect-output"); parser.accepts("version"); return parser; } /** * Parses an array of arguments to configure this Builder with the intent of constructing a * Locator launcher to invoke a Locator. This method is called to parse the arguments specified * by the user on the command-line. * * @param args the array of arguments used to configure this Builder and create an instance of * LocatorLauncher. */ protected void parseArguments(final String... args) { try { parseCommand(args); parseMemberName(args); final OptionSet options = getParser().parse(args); setDebug(options.has("debug")); setForce(options.has("force")); setHelp(options.has("help")); setRedirectOutput(options.has("redirect-output")); if (!isHelping()) { if (options.has("bind-address")) { setBindAddress(ObjectUtils.toString(options.valueOf("bind-address"))); } if (options.has("dir")) { setWorkingDirectory(ObjectUtils.toString(options.valueOf("dir"))); } if (options.has("hostname-for-clients")) { setHostnameForClients(ObjectUtils.toString(options.valueOf("hostname-for-clients"))); } if (options.has("pid")) { setPid((Integer) options.valueOf("pid")); } if (options.has("port")) { setPort((Integer) options.valueOf("port")); } if (options.has("version")) { setCommand(Command.VERSION); } } } catch (OptionException e) { throw new IllegalArgumentException( LocalizedStrings.Launcher_Builder_PARSE_COMMAND_LINE_ARGUMENT_ERROR_MESSAGE .toLocalizedString("Locator", e.getMessage()), e); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } /** * Iterates the list of arguments in search of the target Locator launcher command. * * @param args an array of arguments from which to search for the Locator launcher command. * @see org.apache.geode.distributed.LocatorLauncher.Command#valueOfName(String) * @see #parseArguments(String...) */ protected void parseCommand(final String... args) { // search the list of arguments for the command; technically, the command should be the first // argument in the // list, but does it really matter? stop after we find one valid command. for (String arg : args) { final Command command = Command.valueOfName(arg); if (command != null) { setCommand(command); break; } } } /** * Iterates the list of arguments in search of the Locator's GemFire member name. If the * argument does not start with '-' or is not the name of a Locator launcher command, then the * value is presumed to be the member name for the Locator in GemFire. * * @param args the array of arguments from which to search for the Locator's member name in * GemFire. * @see org.apache.geode.distributed.LocatorLauncher.Command#isCommand(String) * @see #parseArguments(String...) */ protected void parseMemberName(final String[] args) { for (String arg : args) { if (!(arg.startsWith(OPTION_PREFIX) || Command.isCommand(arg))) { setMemberName(arg); break; } } } /** * Gets the Locator launcher command used during the invocation of the LocatorLauncher. * * @return the Locator launcher command used to invoke (run) the LocatorLauncher class. * @see #setCommand(org.apache.geode.distributed.LocatorLauncher.Command) * @see LocatorLauncher.Command */ public Command getCommand() { return ObjectUtils.defaultIfNull(this.command, DEFAULT_COMMAND); } /** * Sets the Locator launcher command used during the invocation of the LocatorLauncher * * @param command the targeted Locator launcher command used during the invocation (run) of * LocatorLauncher. * @return this Builder instance. * @see #getCommand() * @see LocatorLauncher.Command */ public Builder setCommand(final Command command) { this.command = command; return this; } /** * Determines whether the new instance of the LocatorLauncher will be set to debug mode. * * @return a boolean value indicating whether debug mode is enabled or disabled. * @see #setDebug(Boolean) */ public Boolean getDebug() { return this.debug; } /** * Sets whether the new instance of the LocatorLauncher will be set to debug mode. * * @param debug a boolean value indicating whether debug mode is to be enabled or disabled. * @return this Builder instance. * @see #getHelp() */ public Builder setDebug(final Boolean debug) { this.debug = debug; return this; } /** * Gets the GemFire Distributed System (cluster) Properties configuration. * * @return a Properties object containing configuration settings for the GemFire Distributed * System (cluster). * @see java.util.Properties */ public Properties getDistributedSystemProperties() { return this.distributedSystemProperties; } /** * Gets the boolean value used by the Locator to determine if it should overwrite the PID file * if it already exists. * * @return the boolean value specifying whether or not to overwrite the PID file if it already * exists. * @see org.apache.geode.internal.process.LocalProcessLauncher * @see #setForce(Boolean) */ public Boolean getForce() { return ObjectUtils.defaultIfNull(this.force, DEFAULT_FORCE); } /** * Sets the boolean value used by the Locator to determine if it should overwrite the PID file * if it already exists. * * @param force a boolean value indicating whether to overwrite the PID file when it already * exists. * @return this Builder instance. * @see org.apache.geode.internal.process.LocalProcessLauncher * @see #getForce() */ public Builder setForce(final Boolean force) { this.force = force; return this; } /** * Determines whether the new instance of LocatorLauncher will be used to output help * information for either a specific command, or for using LocatorLauncher in general. * * @return a boolean value indicating whether help will be output during the invocation of * LocatorLauncher. * @see #setHelp(Boolean) */ public Boolean getHelp() { return this.help; } /** * Determines whether help has been enabled. * * @return a boolean indicating if help was enabled. */ private boolean isHelping() { return Boolean.TRUE.equals(getHelp()); } /** * Sets whether the new instance of LocatorLauncher will be used to output help information for * either a specific command, or for using LocatorLauncher in general. * * @param help a boolean indicating whether help information is to be displayed during * invocation of LocatorLauncher. * @return this Builder instance. * @see #getHelp() */ public Builder setHelp(final Boolean help) { this.help = help; return this; } final boolean isBindAddressSpecified() { return (getBindAddress() != null); } /** * Gets the IP address to which the Locator has bound itself listening for client requests. * * @return an InetAddress with the IP address or hostname on which the Locator is bound and * listening. * @see #setBindAddress(String) * @see java.net.InetAddress */ public InetAddress getBindAddress() { return this.bindAddress; } /** * Sets the IP address as an java.net.InetAddress to which the Locator has bound itself * listening for client requests. * * @param bindAddress the InetAddress with the IP address or hostname on which the Locator is * bound and listening. * @return this Builder instance. * @throws IllegalArgumentException wrapping the UnknownHostException if the IP address or * hostname for the bind address is unknown. * @see #getBindAddress() * @see java.net.InetAddress */ public Builder setBindAddress(final String bindAddress) { if (StringUtils.isBlank(bindAddress)) { this.bindAddress = null; return this; } else { try { InetAddress address = InetAddress.getByName(bindAddress); if (SocketCreator.isLocalHost(address)) { this.bindAddress = address; return this; } else { throw new IllegalArgumentException( bindAddress + " is not an address for this machine."); } } catch (UnknownHostException e) { throw new IllegalArgumentException( LocalizedStrings.Launcher_Builder_UNKNOWN_HOST_ERROR_MESSAGE .toLocalizedString("Locator"), e); } } } /** * Gets the hostname used by clients to lookup the Locator. * * @return a String indicating the hostname Locator binding used in client lookups. * @see #setHostnameForClients(String) */ public String getHostnameForClients() { return this.hostnameForClients; } /** * Sets the hostname used by clients to lookup the Locator. * * @param hostnameForClients a String indicating the hostname Locator binding used in client * lookups. * @return this Builder instance. * @throws IllegalArgumentException if the hostname was not specified (is blank or empty), such * as when the --hostname-for-clients command-line option may have been specified * without any argument. * @see #getHostnameForClients() */ public Builder setHostnameForClients(final String hostnameForClients) { if (StringUtils.isEmpty(StringUtils.trim(hostnameForClients))) { throw new IllegalArgumentException( LocalizedStrings.LocatorLauncher_Builder_INVALID_HOSTNAME_FOR_CLIENTS_ERROR_MESSAGE .toLocalizedString()); } this.hostnameForClients = hostnameForClients; return this; } /** * Gets the member name of this Locator in GemFire. * * @return a String indicating the member name of this Locator in GemFire. * @see #setMemberName(String) */ public String getMemberName() { return this.memberName; } /** * Sets the member name of the Locator in GemFire. * * @param memberName a String indicating the member name of this Locator in GemFire. * @return this Builder instance. * @throws IllegalArgumentException if the member name is invalid. * @see #getMemberName() */ public Builder setMemberName(final String memberName) { if (StringUtils.isEmpty(StringUtils.trim(memberName))) { throw new IllegalArgumentException( LocalizedStrings.Launcher_Builder_MEMBER_NAME_ERROR_MESSAGE .toLocalizedString("Locator")); } this.memberName = memberName; return this; } /** * Gets the process ID (PID) of the running Locator indicated by the user as an argument to the * LocatorLauncher. This PID is used by the Locator launcher to determine the Locator's status, * or invoke shutdown on the Locator. * * @return a user specified Integer value indicating the process ID of the running Locator. * @see #setPid(Integer) */ public Integer getPid() { return this.pid; } /** * Sets the process ID (PID) of the running Locator indicated by the user as an argument to the * LocatorLauncher. This PID will be used by the Locator launcher to determine the Locator's * status, or invoke shutdown on the Locator. * * @param pid a user specified Integer value indicating the process ID of the running Locator. * @return this Builder instance. * @throws IllegalArgumentException if the process ID (PID) is not valid (greater than zero if * not null). * @see #getPid() */ public Builder setPid(final Integer pid) { if (pid != null && pid < 0) { throw new IllegalArgumentException( LocalizedStrings.Launcher_Builder_PID_ERROR_MESSAGE.toLocalizedString()); } this.pid = pid; return this; } boolean isPortSpecified() { return (this.port != null); } /** * Gets the port number used by the Locator to listen for client requests. If the port was not * specified, then the default Locator port (10334) is returned. * * @return the specified Locator port or the default port if unspecified. * @see #setPort(Integer) */ public Integer getPort() { return ObjectUtils.defaultIfNull(port, getDefaultLocatorPort()); } /** * Sets the port number used by the Locator to listen for client requests. The port number must * be between 1 and 65535 inclusive. * * @param port an Integer value indicating the port used by the Locator to listen for client * requests. * @return this Builder instance. * @throws IllegalArgumentException if the port number is not valid. * @see #getPort() */ public Builder setPort(final Integer port) { // NOTE if the user were to specify a port number of 0, then java.net.ServerSocket will pick // an ephemeral port // to bind the socket, which we do not want. if (port != null && (port < 0 || port > 65535)) { throw new IllegalArgumentException( LocalizedStrings.Launcher_Builder_INVALID_PORT_ERROR_MESSAGE .toLocalizedString("Locator")); } this.port = port; return this; } /** * Determines whether the new instance of LocatorLauncher will redirect output to system logs * when starting a Locator. * * @return a boolean value indicating if output will be redirected to system logs when starting * a Locator * * @see #setRedirectOutput(Boolean) */ public Boolean getRedirectOutput() { return this.redirectOutput; } /** * Determines whether redirecting of output has been enabled. * * @return a boolean indicating if redirecting of output was enabled. */ private boolean isRedirectingOutput() { return Boolean.TRUE.equals(getRedirectOutput()); } /** * Sets whether the new instance of LocatorLauncher will redirect output to system logs when * starting a Locator. * * @param redirectOutput a boolean value indicating if output will be redirected to system logs * when starting a Locator. * @return this Builder instance. * @see #getRedirectOutput() */ public Builder setRedirectOutput(final Boolean redirectOutput) { this.redirectOutput = redirectOutput; return this; } boolean isWorkingDirectorySpecified() { return !StringUtils.isBlank(this.workingDirectory); } /** * Gets the working directory pathname in which the Locator will be ran. If the directory is * unspecified, then working directory defaults to the current directory. * * @return a String indicating the working directory pathname. * @see #setWorkingDirectory(String) */ public String getWorkingDirectory() { return IOUtils.tryGetCanonicalPathElseGetAbsolutePath( new File(StringUtils.defaultIfBlank(this.workingDirectory, DEFAULT_WORKING_DIRECTORY))); } /** * Sets the working directory in which the Locator will be ran. This also the directory in which * all Locator files (such as log and license files) will be written. If the directory is * unspecified, then the working directory defaults to the current directory. * * @param workingDirectory a String indicating the pathname of the directory in which the * Locator will be ran. * @return this Builder instance. * @throws IllegalArgumentException wrapping a FileNotFoundException if the working directory * pathname cannot be found. * @see #getWorkingDirectory() * @see java.io.FileNotFoundException */ public Builder setWorkingDirectory(final String workingDirectory) { if (!new File(StringUtils.defaultIfBlank(workingDirectory, DEFAULT_WORKING_DIRECTORY)) .isDirectory()) { throw new IllegalArgumentException( LocalizedStrings.Launcher_Builder_WORKING_DIRECTORY_NOT_FOUND_ERROR_MESSAGE .toLocalizedString("Locator"), new FileNotFoundException(workingDirectory)); } this.workingDirectory = workingDirectory; return this; } /** * Sets a GemFire Distributed System Property. * * @param propertyName a String indicating the name of the GemFire Distributed System property * as described in {@link ConfigurationProperties} * @param propertyValue a String value for the GemFire Distributed System property. * @return this Builder instance. */ public Builder set(final String propertyName, final String propertyValue) { this.distributedSystemProperties.setProperty(propertyName, propertyValue); return this; } /** * Validates the configuration settings and properties of this Builder, ensuring that all * invariants have been met. Currently, the only invariant constraining the Builder is that the * user must specify the member name for the Locator in the GemFire distributed system as a * command-line argument, or by setting the memberName property programmatically using the * corresponding setter method. If the member name is not given, then the user must have * specified the pathname to the gemfire.properties file before validate is called. It is then * assumed, but not further validated, that the user has specified the Locator's member name in * the properties file. * * @throws IllegalStateException if the Builder is not properly configured. */ protected void validate() { if (!isHelping()) { validateOnStart(); validateOnStatus(); validateOnStop(); // no validation for 'version' required } } /** * Validates the arguments passed to the Builder when the 'start' command has been issued. * * @see org.apache.geode.distributed.LocatorLauncher.Command#START */ protected void validateOnStart() { if (Command.START.equals(getCommand())) { if (StringUtils.isBlank(getMemberName()) && !isSet(System.getProperties(), DistributionConfig.GEMFIRE_PREFIX + NAME) && !isSet(getDistributedSystemProperties(), NAME) && !isSet(loadGemFireProperties(DistributedSystem.getPropertyFileURL()), NAME)) { throw new IllegalStateException( LocalizedStrings.Launcher_Builder_MEMBER_NAME_VALIDATION_ERROR_MESSAGE .toLocalizedString("Locator")); } if (!SystemUtils.CURRENT_DIRECTORY.equals(getWorkingDirectory())) { throw new IllegalStateException( LocalizedStrings.Launcher_Builder_WORKING_DIRECTORY_OPTION_NOT_VALID_ERROR_MESSAGE .toLocalizedString("Locator")); } } } /** * Validates the arguments passed to the Builder when the 'status' command has been issued. * * @see org.apache.geode.distributed.LocatorLauncher.Command#STATUS */ protected void validateOnStatus() { if (Command.STATUS.equals(getCommand())) { } } /** * Validates the arguments passed to the Builder when the 'stop' command has been issued. * * @see org.apache.geode.distributed.LocatorLauncher.Command#STOP */ protected void validateOnStop() { if (Command.STOP.equals(getCommand())) { } } /** * Validates the Builder configuration settings and then constructs an instance of the * LocatorLauncher class to invoke operations on a GemFire Locator. * * @return a newly constructed instance of LocatorLauncher configured with this Builder. * @see #validate() * @see org.apache.geode.distributed.LocatorLauncher */ public LocatorLauncher build() { validate(); return new LocatorLauncher(this); } } /** * An enumerated type representing valid commands to the Locator launcher. */ public static enum Command { START("start", "bind-address", "hostname-for-clients", "port", "force", "debug", "help"), STATUS("status", "bind-address", "port", "member", "pid", "dir", "debug", "help"), STOP("stop", "member", "pid", "dir", "debug", "help"), VERSION("version"), UNSPECIFIED("unspecified"); private final List<String> options; private final String name; Command(final String name, final String... options) { assert !StringUtils .isBlank(name) : "The name of the locator launcher command must be specified!"; this.name = name; this.options = (options != null ? Collections.unmodifiableList(Arrays.asList(options)) : Collections.<String>emptyList()); } /** * Determines whether the specified name refers to a valid Locator launcher command, as defined * by this enumerated type. * * @param name a String value indicating the potential name of a Locator launcher command. * @return a boolean indicating whether the specified name for a Locator launcher command is * valid. */ public static boolean isCommand(final String name) { return (valueOfName(name) != null); } /** * Determines whether the given Locator launcher command has been properly specified. The * command is deemed unspecified if the reference is null or the Command is UNSPECIFIED. * * @param command the Locator launcher command. * @return a boolean value indicating whether the Locator launcher command is unspecified. * @see Command#UNSPECIFIED */ public static boolean isUnspecified(final Command command) { return (command == null || command.isUnspecified()); } /** * Looks up a Locator launcher command by name. The equality comparison on name is * case-insensitive. * * @param name a String value indicating the name of the Locator launcher command. * @return an enumerated type representing the command name or null if the no such command with * the specified name exists. */ public static Command valueOfName(final String name) { for (Command command : values()) { if (command.getName().equalsIgnoreCase(name)) { return command; } } return null; } /** * Gets the name of the Locator launcher command. * * @return a String value indicating the name of the Locator launcher command. */ public String getName() { return this.name; } /** * Gets a set of valid options that can be used with the Locator launcher command when used from * the command-line. * * @return a Set of Strings indicating the names of the options available to the Locator * launcher command. */ public List<String> getOptions() { return this.options; } /** * Determines whether this Locator launcher command has the specified command-line option. * * @param option a String indicating the name of the command-line option to this command. * @return a boolean value indicating whether this command has the specified named command-line * option. */ public boolean hasOption(final String option) { return getOptions().contains(StringUtils.toLowerCase(option)); } /** * Convenience method for determining whether this is the UNSPECIFIED Locator launcher command. * * @return a boolean indicating if this command is UNSPECIFIED. * @see #UNSPECIFIED */ public boolean isUnspecified() { return UNSPECIFIED.equals(this); } /** * Gets the String representation of this Locator launcher command. * * @return a String value representing this Locator launcher command. */ @Override public String toString() { return getName(); } } /** * The LocatorState is an immutable type representing the state of the specified Locator at any * given moment in time. The state of the Locator is assessed at the exact moment an instance of * this class is constructed. * * @see org.apache.geode.distributed.AbstractLauncher.ServiceState */ public static final class LocatorState extends ServiceState<String> { /** * Unmarshals a LocatorState instance from the JSON String. * * @return a LocatorState value unmarshalled from the JSON String. */ public static LocatorState fromJson(final String json) { try { final GfJsonObject gfJsonObject = new GfJsonObject(json); final Status status = Status.valueOfDescription(gfJsonObject.getString(JSON_STATUS)); final List<String> jvmArguments = Arrays.asList(GfJsonArray.toStringArray(gfJsonObject.getJSONArray(JSON_JVMARGUMENTS))); return new LocatorState(status, gfJsonObject.getString(JSON_STATUSMESSAGE), gfJsonObject.getLong(JSON_TIMESTAMP), gfJsonObject.getString(JSON_LOCATION), gfJsonObject.getInt(JSON_PID), gfJsonObject.getLong(JSON_UPTIME), gfJsonObject.getString(JSON_WORKINGDIRECTORY), jvmArguments, gfJsonObject.getString(JSON_CLASSPATH), gfJsonObject.getString(JSON_GEMFIREVERSION), gfJsonObject.getString(JSON_JAVAVERSION), gfJsonObject.getString(JSON_LOGFILE), gfJsonObject.getString(JSON_HOST), gfJsonObject.getString(JSON_PORT), gfJsonObject.getString(JSON_MEMBERNAME)); } catch (GfJsonException e) { throw new IllegalArgumentException( "Unable to create LocatorStatus from JSON: ".concat(json), e); } } public LocatorState(final LocatorLauncher launcher, final Status status) { // if status is NOT_RESPONDING then this is executing inside the JVM asking for he status; pid // etc will be set according to the caller's JVM instead this(status, launcher.statusMessage, System.currentTimeMillis(), launcher.getId(), identifyPid(), ManagementFactory.getRuntimeMXBean().getUptime(), launcher.getWorkingDirectory(), ManagementFactory.getRuntimeMXBean().getInputArguments(), System.getProperty("java.class.path"), GemFireVersion.getGemFireVersion(), System.getProperty("java.version"), getLogFileCanonicalPath(launcher), launcher.getBindAddressAsString(), launcher.getPortAsString(), launcher.getMemberName()); } public LocatorState(final LocatorLauncher launcher, final Status status, final String errorMessage) { this(status, // status errorMessage, // statusMessage System.currentTimeMillis(), // timestamp null, // locatorLocation null, // pid 0L, // uptime launcher.getWorkingDirectory(), // workingDirectory Collections.<String>emptyList(), // jvmArguments null, // classpath GemFireVersion.getGemFireVersion(), // gemfireVersion null, // javaVersion null, // logFile null, // host null, // port null);// memberName } private static String getBindAddressAsString(LocatorLauncher launcher) { if (InternalLocator.hasLocator()) { final InternalLocator locator = InternalLocator.getLocator(); final InetAddress bindAddress = locator.getBindAddress(); if (bindAddress != null) { if (StringUtils.isBlank(bindAddress.getHostAddress())) { return bindAddress.getHostAddress(); } } } return launcher.getBindAddressAsString(); } private static String getLogFileCanonicalPath(LocatorLauncher launcher) { if (InternalLocator.hasLocator()) { final InternalLocator locator = InternalLocator.getLocator(); final File logFile = locator.getLogFile(); if (logFile != null && logFile.isFile()) { final String logFileCanonicalPath = IOUtils.tryGetCanonicalPathElseGetAbsolutePath(logFile); if (!StringUtils.isBlank(logFileCanonicalPath)) { // this is probably not need but a safe // check none-the-less. return logFileCanonicalPath; } } } return launcher.getLogFileCanonicalPath(); } private static String getPortAsString(LocatorLauncher launcher) { if (InternalLocator.hasLocator()) { final InternalLocator locator = InternalLocator.getLocator(); final String portAsString = String.valueOf(locator.getPort()); if (!StringUtils.isBlank(portAsString)) { return portAsString; } } return launcher.getPortAsString(); } protected LocatorState(final Status status, final String statusMessage, final long timestamp, final String locatorLocation, final Integer pid, final Long uptime, final String workingDirectory, final List<String> jvmArguments, final String classpath, final String gemfireVersion, final String javaVersion, final String logFile, final String host, final String port, final String memberName) { super(status, statusMessage, timestamp, locatorLocation, pid, uptime, workingDirectory, jvmArguments, classpath, gemfireVersion, javaVersion, logFile, host, port, memberName); } private LocatorState(final LocatorLauncher launcher, final Status status, final LocatorStatusResponse response) { this(status, launcher.statusMessage, System.currentTimeMillis(), launcher.getId(), response.getPid(), response.getUptime(), response.getWorkingDirectory(), response.getJvmArgs(), response.getClasspath(), response.getGemFireVersion(), response.getJavaVersion(), response.getLogFile(), response.getHost(), String.valueOf(response.getPort()), response.getName()); } @Override protected String getServiceName() { return LOCATOR_SERVICE_NAME; } } }