templates.commands.RemoteLauncherCommands.java Source code

Java tutorial

Introduction

Here is the source code for templates.commands.RemoteLauncherCommands.java

Source

/*
 * =========================================================================
 *  Copyright (c) 2002-2014 Pivotal Software, Inc. All Rights Reserved.
 *  This product is protected by U.S. and international copyright
 *  and intellectual property laws. Pivotal products are covered by
 *  more patents listed at http://www.pivotal.io/patents.
 * ========================================================================
 */
package templates.commands;

import java.io.IOException;
import java.io.InputStream;

import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;

import com.gemstone.gemfire.cache.server.CacheServer;
import com.gemstone.gemfire.management.cli.CliMetaData;
import com.gemstone.gemfire.management.cli.ConverterHint;
import com.gemstone.gemfire.management.cli.Result;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;

/**
 * Gfsh commands to start a Server or Locator on a remote host using SSH.
 * 
 * @author David Hoots
 * @since 8.0
 */
public class RemoteLauncherCommands implements CommandMarker {

    @CliCommand(value = "start remote-locator", help = "Start a Locator on a remote host using SSH.")
    @CliMetaData(shellOnly = true, relatedTopic = { "Locator", "Lifecycle" })
    public Result startRemoteLocator(
            @CliOption(key = "host", mandatory = true, help = "Host on which to start the server.") final String host,
            @CliOption(key = "user", mandatory = true, help = "User to login to the host with.") final String user,
            @CliOption(key = "password", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Password for the user.  If not specifed SSH keys must have been installed to support password-less login.") final String password,
            @CliOption(key = "gemfire-bin", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Path to GemFire's \"bin\" directory.  If not specifed \"gfsh\" must be in the command search path.") final String gemfireBin,
            @CliOption(key = "name", mandatory = true, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Member name for this Locator service.") final String memberName,
            @CliOption(key = "bind-address", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "IP address on which the Locator will be bound. The default is to bind to all local addresses.") final String bindAddress,
            @CliOption(key = "force", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether to allow the PID file from a previous Locator run to be overwritten.") final Boolean force,
            @CliOption(key = "group", optionContext = ConverterHint.MEMBERGROUP, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Group(s) the Locator will be a part of.") final String group,
            @CliOption(key = "hostname-for-clients", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Hostname or IP address that will be sent to clients so they can connect to this Locator. The default is the bind-address of the Locator.") final String hostnameForClients,
            @CliOption(key = "locators", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Sets the list of Locators used by this Locator to join the appropriate GemFire cluster.") final String locators,
            @CliOption(key = "log-level", optionContext = ConverterHint.LOG_LEVEL, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Sets the level of output logged to the Locator log file.  Possible values for log-level include: finest, finer, fine, config, info, warning, severe, none.") final String logLevel,
            @CliOption(key = "mcast-address", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "The IP address or hostname used to bind the UPD socket for multi-cast networking so the Locator can locate other members in the GemFire cluster.  If mcast-port is zero, then mcast-address is ignored.") final String mcastBindAddress,
            @CliOption(key = "mcast-port", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Sets the port used for multi-cast networking so the Locator can locate other members of the GemFire cluster.  A zero value disables mcast.") final Integer mcastPort,
            @CliOption(key = "port", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Port the Locator will listen on.") final Integer port,
            @CliOption(key = "dir", optionContext = ConverterHint.DIR_PATHSTRING, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Directory in which the Locator will be run. The default is the current directory.") String workingDirectory,
            @CliOption(key = "properties-file", optionContext = ConverterHint.FILE_PATHSTRING, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "The gemfire.properties file for configuring the Locator's distributed system. The file's path can be absolute or relative to the Locator's directory (--dir=).") final String gemfirePropertiesPathname,
            @CliOption(key = "initial-heap", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Initial size of the heap in the same format as the JVM -Xms parameter.") final String initialHeap,
            @CliOption(key = "max-heap", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Maximum size of the heap in the same format as the JVM -Xmx parameter.") final String maxHeap,
            @CliOption(key = "J", optionContext = ConverterHint.STRING_LIST, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Argument passed to the JVM on which the Locator will run. For example, --J=-Dfoo.bar=true will set the property \"foo.bar\" to \"true\".") @CliMetaData(valueSeparator = ",") final String[] jvmArgsOpts) {
        StringBuilder stringBuilder = new StringBuilder();

        if (gemfireBin != null) {
            stringBuilder.append("cd ").append(gemfireBin).append("; ");
        }
        stringBuilder.append("gfsh start locator");

        if (memberName != null && !memberName.isEmpty()) {
            stringBuilder.append(" --name=").append(memberName);
        }
        if (bindAddress != null && !bindAddress.isEmpty()) {
            stringBuilder.append(" --bind-address=").append(bindAddress);
        }
        if (force != null) {
            stringBuilder.append(" --force=").append(force);
        }
        if (group != null && !group.isEmpty()) {
            stringBuilder.append(" --group=").append(group);
        }
        if (hostnameForClients != null && !hostnameForClients.isEmpty()) {
            stringBuilder.append(" --hostname-for-clients=").append(hostnameForClients);
        }
        if (locators != null && !locators.isEmpty()) {
            stringBuilder.append(" --locators=").append(locators);
        }
        if (logLevel != null && !logLevel.isEmpty()) {
            stringBuilder.append(" --log-level=").append(logLevel);
        }
        if (mcastBindAddress != null && !mcastBindAddress.isEmpty()) {
            stringBuilder.append(" --mcast-address=").append(mcastBindAddress);
        }
        if (mcastPort != null) {
            stringBuilder.append(" --mcast-port=").append(mcastPort);
        }
        if (port != null) {
            stringBuilder.append(" --port=").append(port);
        }
        if (workingDirectory != null && !workingDirectory.isEmpty()) {
            stringBuilder.append(" --dir=").append(workingDirectory);
        }
        if (gemfirePropertiesPathname != null && !gemfirePropertiesPathname.isEmpty()) {
            stringBuilder.append(" --properties-file=").append(gemfirePropertiesPathname);
        }
        if (initialHeap != null && !initialHeap.isEmpty()) {
            stringBuilder.append(" --initial-heap=").append(initialHeap);
        }
        if (maxHeap != null && !maxHeap.isEmpty()) {
            stringBuilder.append(" --max-heap=").append(maxHeap);
        }

        if (jvmArgsOpts != null) {
            for (String jvmArgsOpt : jvmArgsOpts) {
                stringBuilder.append("--J=").append(jvmArgsOpt);
            }
        }

        String command = stringBuilder.toString();
        return executeSshCommand(host, user, password, command);
    }

    @CliCommand(value = "start remote-server", help = "Start a Server on a remote host using SSH.")
    @CliMetaData(shellOnly = true, relatedTopic = { "Server", "Lifecycle" })
    public Result startServer(
            @CliOption(key = "host", mandatory = true, help = "Host on which to start the server.") final String host,
            @CliOption(key = "user", mandatory = true, help = "User to login to the host with.") final String user,
            @CliOption(key = "password", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Password for the user.  If not specifed SSH keys must have been installed to support password-less login.") final String password,
            @CliOption(key = "gemfire-bin", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Path to GemFire's \"bin\" directory.  If not specifed \"gfsh\" must be in the command search path.") final String gemfireBin,
            @CliOption(key = "assign-buckets", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether to assign buckets to the partitioned regions of the cache on server start.") final Boolean assignBuckets,
            @CliOption(key = "cache-xml-file", optionContext = ConverterHint.FILE_PATHSTRING, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Specifies the name of the XML file or resource to initialize the cache with when it is created.") final String cacheXmlPathname,
            @CliOption(key = "classpath", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Location of user classes required by the Cache Server. This path is appended to the current classpath.") final String classpath,
            @CliOption(key = "disable-default-server", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether the Cache Server will be started by default.") final Boolean disableDefaultServer,
            @CliOption(key = "enable-time-statistics", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, specifiedDefaultValue = "true", help = "Causes additional time-based statistics to be gathered for GemFire operations.") final Boolean enableTimeStatistics,
            @CliOption(key = "force", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether to allow the PID file from a previous Cache Server run to be overwritten.") final Boolean force,
            @CliOption(key = "properties-file", optionContext = ConverterHint.FILE_PATHSTRING, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "The gemfire.properties file for configuring the Cache Server's distributed system. The file's path can be absolute or relative to the Locator's directory (--dir=).") final String gemfirePropertiesPathname,
            @CliOption(key = "group", optionContext = ConverterHint.MEMBERGROUP, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Group(s) the Cache Server will be a part of.") final String group,
            @CliOption(key = "license-application-cache", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Specifies the serial number this distributed system member will use unless a <code>license-data-management</code> serial number is also specified. This distributed member will attempt to activate a GemFire Application Cache Node license which allows it to be a peer-to-peer application cache node. If a <code>license-data-management</code> serial number is provided, then the <code>license-application-cache</code> serial number may be specified as the license for cache clients connecting to this node.") final String licenseApplicationCache,
            @CliOption(key = "license-data-management", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Specifies the serial number(s) this distributed system member will use. This distributed member will attempt to activate a GemFire Data Management Node license which allows it to provide advanced data management services which includes hosting a cache server to which cache clients may connect.") final String licenseDataManagement,
            @CliOption(key = "locators", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Sets the list of Locators used by the Cache Server to join the appropriate GemFire cluster.") final String locators,
            @CliOption(key = "log-level", optionContext = ConverterHint.LOG_LEVEL, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Sets the level of output logged to the Cache Server log file.  Possible values for log-level include: finest, finer, fine, config, info, warning, severe, none.") final String logLevel,
            @CliOption(key = "mcast-address", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "The IP address or hostname used to bind the UPD socket for multi-cast networking so the Cache Server can locate other members in the GemFire cluster.  If mcast-port is zero, then mcast-address is ignored.") final String mcastBindAddress,
            @CliOption(key = "mcast-port", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Sets the port used for multi-cast networking so the Cache Server can locate other members of the GemFire cluster.  A zero value disables mcast.") final Integer mcastPort,
            @CliOption(key = "name", mandatory = true, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Member name for this Cache Server service.") final String memberName,
            @CliOption(key = "memcached-port", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Sets the port that the GemFire memcached service listens on for memcached clients.") final Integer memcachedPort,
            @CliOption(key = "memcached-protocol", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Sets the protocol that the GemFire memcached service uses (ASCII or BINARY).") final String memcachedProtocol,
            @CliOption(key = "rebalance", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether to initiate rebalancing across the GemFire cluster.") final Boolean rebalance,
            @CliOption(key = "server-bind-address", unspecifiedDefaultValue = CacheServer.DEFAULT_BIND_ADDRESS, help = "The IP address that this distributed system's server sockets in a client-server topology will be bound. If set to an empty string then all of the local machine's addresses will be listened on.") final String serverBindAddress,
            @CliOption(key = "server-port", unspecifiedDefaultValue = (""
                    + CacheServer.DEFAULT_PORT), help = "The port that the distributed system's server sockets in a client-server topology will listen on.  The default server-port is "
                            + CacheServer.DEFAULT_PORT + ".") final Integer serverPort,
            @CliOption(key = "statistic-archive-file", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "The file that statistic samples are written to.  An empty string (default) disables statistic archival.") final String statisticsArchivePathname,
            @CliOption(key = "dir", optionContext = ConverterHint.DIR_PATHSTRING, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Directory in which the Server will be run. The default is the current directory.") String workingDirectory,
            @CliOption(key = "initial-heap", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Initial size of the heap in the same format as the JVM -Xms parameter.") final String initialHeap,
            @CliOption(key = "max-heap", unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Maximum size of the heap in the same format as the JVM -Xmx parameter.") final String maxHeap,
            @CliOption(key = "J", optionContext = ConverterHint.STRING_LIST, unspecifiedDefaultValue = CliMetaData.ANNOTATION_NULL_VALUE, help = "Argument passed to the JVM on which the Locator will run. For example, --J=-Dfoo.bar=true will set the property \"foo.bar\" to \"true\".") @CliMetaData(valueSeparator = ",") final String[] jvmArgsOpts) {

        StringBuilder stringBuilder = new StringBuilder();

        if (gemfireBin != null) {
            stringBuilder.append("cd ").append(gemfireBin).append("; ");
        }
        stringBuilder.append("gfsh start server");

        if (assignBuckets != null) {
            stringBuilder.append(" --assign-buckets=").append(assignBuckets);
        }
        if (cacheXmlPathname != null && !cacheXmlPathname.isEmpty()) {
            stringBuilder.append(" --cache-xml-file=").append(cacheXmlPathname);
        }
        if (classpath != null && !classpath.isEmpty()) {
            stringBuilder.append(" --classpath=").append(classpath);
        }
        if (disableDefaultServer != null) {
            stringBuilder.append(" --disable-default-server=").append(disableDefaultServer);
        }
        if (enableTimeStatistics != null) {
            stringBuilder.append(" --enable-time-statistics=").append(enableTimeStatistics);
        }
        if (force != null) {
            stringBuilder.append(" --force=").append(force);
        }
        if (gemfirePropertiesPathname != null && !gemfirePropertiesPathname.isEmpty()) {
            stringBuilder.append(" --properties-file=").append(gemfirePropertiesPathname);
        }
        if (group != null && !group.isEmpty()) {
            stringBuilder.append(" --group=").append(group);
        }
        if (licenseApplicationCache != null && !licenseApplicationCache.isEmpty()) {
            stringBuilder.append(" --license-application-cache=").append(licenseApplicationCache);
        }
        if (licenseDataManagement != null && !licenseDataManagement.isEmpty()) {
            stringBuilder.append(" --license-data-management=").append(licenseDataManagement);
        }
        if (locators != null && !locators.isEmpty()) {
            stringBuilder.append(" --locators=").append(locators);
        }
        if (logLevel != null && !logLevel.isEmpty()) {
            stringBuilder.append(" --log-level=").append(logLevel);
        }
        if (mcastBindAddress != null && !mcastBindAddress.isEmpty()) {
            stringBuilder.append(" --mcast-address=").append(mcastBindAddress);
        }
        if (mcastPort != null) {
            stringBuilder.append(" --mcast-port=").append(mcastPort);
        }
        if (memberName != null && !memberName.isEmpty()) {
            stringBuilder.append(" --name=").append(memberName);
        }
        if (memcachedPort != null) {
            stringBuilder.append(" --memcached-port=").append(memcachedPort);
        }
        if (memcachedProtocol != null && !memcachedProtocol.isEmpty()) {
            stringBuilder.append(" --memcached-protocol=").append(memcachedProtocol);
        }
        if (rebalance != null) {
            stringBuilder.append(" --rebalance=").append(rebalance);
        }
        if (serverBindAddress != null && !serverBindAddress.isEmpty()) {
            stringBuilder.append(" --server-bind-address=").append(serverBindAddress);
        }
        if (serverPort != null) {
            stringBuilder.append(" --server-port=").append(serverPort);
        }
        if (statisticsArchivePathname != null && !statisticsArchivePathname.isEmpty()) {
            stringBuilder.append(" --statistic-archive-file=").append(statisticsArchivePathname);
        }
        if (workingDirectory != null && !workingDirectory.isEmpty()) {
            stringBuilder.append(" --dir=").append(workingDirectory);
        }
        if (initialHeap != null && !initialHeap.isEmpty()) {
            stringBuilder.append(" --initial-heap=").append(initialHeap);
        }
        if (maxHeap != null && !maxHeap.isEmpty()) {
            stringBuilder.append(" --max-heap=").append(maxHeap);
        }

        if (jvmArgsOpts != null) {
            for (String jvmArgsOpt : jvmArgsOpts) {
                stringBuilder.append("--J=").append(jvmArgsOpt);
            }
        }

        String command = stringBuilder.toString();
        return executeSshCommand(host, user, password, command);
    }

    /**
     * Indicates that these commands are available at all times, regardless
     * connection status.
     */
    @CliAvailabilityIndicator({ "start remote-locator", "start remote-server" })
    public boolean launcherCommandsAvailable() {
        return true;
    }

    /**
     * Create a simple Result object that will hold the provided message.
     * 
     * @param status
     *          Whether the commands was successful.
     * @param message
     *          Message to include in the result.
     * @return The newly created result.
     */
    private Result createResult(final Result.Status status, final String message) {
        return new Result() {
            private boolean commandPersisted = false;
            private boolean done = false;

            @Override
            public Status getStatus() {
                return status;
            }

            @Override
            public void resetToFirstLine() {
                this.done = true;
            }

            @Override
            public boolean hasNextLine() {
                return !this.done;
            }

            @Override
            public String nextLine() {
                this.done = true;
                return message;
            }

            @Override
            public boolean hasIncomingFiles() {
                return false;
            }

            @Override
            public void saveIncomingFiles(String directory) throws IOException {
                // No incoming files to handle
            }

            @Override
            public boolean failedToPersist() {
                return commandPersisted;
            }

            @Override
            public void setCommandPersisted(final boolean commandPersisted) {
                this.commandPersisted = commandPersisted;
            }
        };
    }

    /**
     * Create a UserInfo object for use by the JSCH library.
     * 
     * @param password
     *          Password to use when connecting using JSCH.
     * @return The new created UserInfo object.
     */
    private UserInfo createUserInfo(final String password) {

        return new UserInfo() {
            @Override
            public String getPassword() {
                return password;
            }

            @Override
            public boolean promptYesNo(String str) {
                return true;
            }

            @Override
            public String getPassphrase() {
                return null;
            }

            @Override
            public boolean promptPassphrase(String message) {
                return false;
            }

            @Override
            public boolean promptPassword(String message) {
                return true;
            }

            @Override
            public void showMessage(String message) {
                // This will never run interactively
            }
        };
    }

    /**
     * Connect to a remote host via SSH and execute a command.
     * 
     * @param host
     *          Host to connect to
     * @param user
     *          User to login with
     * @param password
     *          Password for the user
     * @param command
     *          Command to execute
     * @return The result of the command execution
     */
    private Result executeSshCommand(final String host, final String user, final String password,
            final String command) {

        StringBuilder result = new StringBuilder();

        try {
            JSch jsch = new JSch();
            Session session = jsch.getSession(user, host, 22);
            session.setUserInfo(createUserInfo(password));
            session.connect(5000);

            ChannelExec channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand(command);
            channel.setInputStream(null);
            channel.setErrStream(System.err);
            InputStream in = channel.getInputStream();

            channel.connect();

            byte[] tmp = new byte[1024];
            while (true) {
                while (in.available() > 0) {
                    int i = in.read(tmp, 0, 1024);
                    if (i < 0)
                        break;
                    result.append(new String(tmp, 0, i));
                }
                if (channel.isClosed()) {
                    break;
                }
            }
            channel.disconnect();
            session.disconnect();
        } catch (Exception jex) {
            return createResult(Result.Status.ERROR, jex.getMessage());
        }

        return createResult(Result.Status.OK, result.toString());
    }
}