Package lcmc.cluster.service.ssh

Source Code of lcmc.cluster.service.ssh.Ssh

/*
* This file is part of DRBD Management Console by LINBIT HA-Solutions GmbH
* written by Rasto Levrinc.
*
* Copyright (C) 2009, LINBIT HA-Solutions GmbH.
* Copyright (C) 2011-2012, Rastislav Levrinc.
*
* DRBD Management Console is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* DRBD Management Console is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with drbd; see the file COPYING.  If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package lcmc.cluster.service.ssh;

import ch.ethz.ssh2.LocalPortForwarder;
import ch.ethz.ssh2.SCPClient;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import lcmc.configs.DistResource;
import lcmc.common.ui.GUIData;
import lcmc.common.domain.Application;
import lcmc.host.domain.Host;
import lcmc.common.ui.ProgressBar;
import lcmc.cluster.ui.SSHGui;
import lcmc.common.domain.ConnectionCallback;
import lcmc.common.domain.ExecCallback;
import lcmc.logger.Logger;
import lcmc.logger.LoggerFactory;
import lcmc.common.domain.util.Tools;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;

@Named
public final class Ssh {
    private static final Logger LOG = LoggerFactory.getLogger(Ssh.class);
    public static final int DEFAULT_COMMAND_TIMEOUT = Tools.getDefaultInt("SSH.Command.Timeout");
    public static final int DEFAULT_COMMAND_TIMEOUT_LONG =
                                                    Tools.getDefaultInt("SSH.Command.Timeout.Long");
    public static final int NO_COMMAND_TIMEOUT = 0;
    public static final String SUDO_PROMPT = "DRBD MC sudo pwd: ";
    public static final String SUDO_FAIL = "Sorry, try again";
    private static final ConnectionCallback NO_CONNECTION_CALLBACK = null;
    private static final ProgressBar NO_PROGRESS_BAR = null;
    private static final String MESSAGE_CANCELED = "canceled";
    private static final String LOGOUT_COMMAND = "logout";
    private static final SshOutput NOT_CONNECTED_ERROR = new SshOutput("", 112);
    /** SSHGui object for enter password dialogs etc. */
    private SSHGui sshGui;
    /** Callback when connection is failed or properly closed. */
    private ConnectionCallback connectionCallback;
    private Host host;
    @Inject
    private Provider<ConnectionThread> connectionThreadProvider;
    private ConnectionThread connectionThread;
    private ProgressBar progressBar = null;

    private final LastSuccessfulPassword lastSuccessfulPassword = new LastSuccessfulPassword();
    private final Lock mConnectionLock = new ReentrantLock();
    private final Lock mConnectionThreadLock = new ReentrantLock();
    private LocalPortForwarder localPortForwarder = null;
    @Inject
    private GUIData guiData;
    @Inject
    private Application application;
    @Inject
    private Provider<Authentication> authenticationProvider;

    boolean reconnect() {
        application.isNotSwingThread();
        mConnectionThreadLock.lock();
        if (connectionThread == null) {
            mConnectionThreadLock.unlock();
        } else {
            try {
                final ConnectionThread ct = connectionThread;
                mConnectionThreadLock.unlock();
                ct.join(20000);
                if (ct.isAlive()) {
                    return false;
                }
            } catch (final InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        if (connectionThread.isDisconnectedForGood()) {
            return false;
        }
        if (!isConnected()) {
            LOG.debug1("reconnect: connecting: " + host.getName());
            this.connectionCallback = NO_CONNECTION_CALLBACK;
            this.progressBar = NO_PROGRESS_BAR;
            this.sshGui = new SSHGui(guiData.getMainFrame(), host, null);
            authenticateAndConnect();
        }
        return true;
    }

    /** Connects the host. */
    public void connect(final SSHGui sshGui, final ConnectionCallback connectionCallback, final Host host) {
        this.sshGui = sshGui;
        this.connectionCallback = connectionCallback;
        this.host = host;
        if (connectionThread != null && connectionThread.isConnectionEstablished()) {
            connectionThread.setConnectionFailed(false);
            // already connected
            if (connectionCallback != null) {
                connectionCallback.done(1);
            }
            return;
        }
        authenticateAndConnect();
    }

    /**
     * Sets passwords that will be tried first while connecting, but only if
     * they were not set before.
     */
    public void setPasswords(final String lastDsaKey,
                             final String lastRsaKey,
                             final String lastPassword) {

        lastSuccessfulPassword.setPasswordsIfNoneIsSet(lastDsaKey, lastRsaKey, lastPassword);
    }

    public String getLastSuccessfulDsaKey() {
        return lastSuccessfulPassword.getDsaKey();
    }

    public String getLastSuccessfulRsaKey() {
        return lastSuccessfulPassword.getRsaKey();
    }

    /** Returns last successful password. */
    public String getLastSuccessfulPassword() {
        return lastSuccessfulPassword.getPassword();
    }

    /** Waits till connection is established or is failed. */
    public void waitForConnection() {
        mConnectionThreadLock.lock();
        if (connectionThread == null) {
            mConnectionThreadLock.unlock();
        } else {
            try {
                final ConnectionThread ct = connectionThread;
                mConnectionThreadLock.unlock();
                ct.join();
            } catch (final InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }


    /** Connects the host. */
    public void connect(final SSHGui sshGuiT,
                        final ProgressBar progressBar,
                        final ConnectionCallback callbackT,
                        final Host hostT) {
        this.progressBar = progressBar;
        connect(sshGuiT, callbackT, hostT);
    }

    /** Cancels the creating of connection to the sshd. */
    public void cancelConnection() {
        mConnectionThreadLock.lock();
        final ConnectionThread ct;
        try {
            ct = connectionThread;
        } finally {
            mConnectionThreadLock.unlock();
        }
        if (ct != null) {
            ct.cancel();
        }
        final String message = MESSAGE_CANCELED;
        LOG.debug1("cancelConnection: message: " + message);
        host.getTerminalPanel().addCommandOutput(message + '\n');
        host.getTerminalPanel().nextCommand();
        connectionThread.setConnectionFailed(true);
        if (connectionCallback != null) {
            connectionCallback.doneError(message);
        }
        // connection will be established anyway after cancel in the
        // background and it will be closed after that, because conn.connect
        // call cannot be interrupted
    }

    /** Cancels the session (execution of command). */
    public void cancelSession(final ExecCommandThread execCommandThread) {
        execCommandThread.cancelTheSession();
        final String message = MESSAGE_CANCELED;
        LOG.debug1("cancelSession: message" + message);
        host.getTerminalPanel().addCommandOutput("\n");
        host.getTerminalPanel().nextCommand();
    }

    /** Disconnects this host if it has been connected. */
    public void disconnect() {
        mConnectionLock.lock();
        try {
            if (connectionThread == null || !connectionThread.isConnectionEstablished()) {
                return;
            }
            connectionThread.disconnectForGood();
            connectionThread.closeConnectionForGood();
        } finally {
            mConnectionLock.unlock();
        }
        LOG.debug("disconnect: host: " + host.getName());
        host.getTerminalPanel().addCommand(LOGOUT_COMMAND);
        host.getTerminalPanel().nextCommand();
    }

    /**
     * Disconnects this host if it was connected. This should be called if
     * there is an assumption that the connection was lost.There is no way
     * with ganymed ssh library to find out if connection was lost at the
     * moment. The difference to the normal disconnect is that the
     * Connection.close is not called which would hang. The old connection
     * thread will probably will stay there, so here is an infrequent leak.
     *
     * After this method is called a reconnect will work as expected.
     */
    public void forceReconnect() {
        mConnectionLock.lock();
        try {
            if (!connectionThread.isConnectionEstablished()) {
                return;
            }
            connectionThread.closeConnection();
        } finally {
            mConnectionLock.unlock();
        }
        LOG.debug("forceReconnect: host: " + host.getName());
        host.getTerminalPanel().addCommand(LOGOUT_COMMAND);
        host.getTerminalPanel().nextCommand();
    }

    /** Force disconnection. */
    public void forceDisconnect() {
        mConnectionLock.lock();
        try {
           if (!connectionThread.isConnectionEstablished()) {
               return;
           }
           connectionThread.disconnectForGood();
           connectionThread.closeConnectionForGood();
        } finally {
            mConnectionLock.unlock();
        }
        LOG.debug("forceDisconnect: host: " + host.getName());
        host.getTerminalPanel().addCommand("logout");
        host.getTerminalPanel().nextCommand();
    }

    /** Returns true if connection is established. */
    public boolean isConnected() {
        mConnectionLock.lock();
        try {
            if (connectionThread == null) {
                return false;
            }
            return connectionThread.isConnectionEstablished();
        } finally {
            mConnectionLock.unlock();
        }
    }

    public boolean isConnectionFailed() {
        return connectionThread.isConnectionFailed();
    }

    /**
     * Executes command and returns an exit code.
     * 100 is timeout
     * 101 no host
     * 102 no io error
     */
    public SshOutput execCommandAndWait(final ExecCommandConfig execCommandConfig) {
        if (host == null) {
            return new SshOutput("", 101);
        }

        if (!reconnect()) {
            return NOT_CONNECTED_ERROR;
        }

        final String[] answer = new String[]{""};
        final Integer[] exitCode = new Integer[]{100};
        final ExecCallback execCallback = new ExecCallback() {
            @Override
            public void done(final String ans) {
                answer[0] = ans;
                exitCode[0] = 0;
            }
            @Override
            public void doneError(final String ans,
                                  final int ec) {
                answer[0] = ans;
                exitCode[0] = ec;
            }
        };

        execCommandConfig.host(host)
                         .connectionThread(connectionThread)
                         .sshGui(sshGui)
                         .execCallback(execCallback)
                         .execute(guiData).block();
        return new SshOutput(answer[0], exitCode[0]);
    }

    /**
     * Executes command. Command is executed in a new thread, after command
     * is finished execCallback.done function will be called. In case of error,
     * execCallback.doneError is called.
     */
    public ExecCommandThread execCommand(final ExecCommandConfig execCommandConfig) {
        reconnect();
        return execCommandConfig.host(host)
                                .connectionThread(connectionThread)
                                .sshGui(sshGui)
                                .execute(guiData);
    }

    public SshOutput captureCommand(final ExecCommandConfig execCommandConfig) {
        reconnect();
        return execCommandConfig.host(host)
                                .connectionThread(connectionThread)
                                .sshGui(sshGui)
                                .capture(guiData);
    }

    /** Installs gui-helper on the remote host. */
    public void installGuiHelper() {
        if (!application.getKeepHelper()) {
            final String fileName = "/help-progs/lcmc-gui-helper";
            final String file = Tools.getFile(fileName);
            if (file != null) {
                scp(file, "@GUI-HELPER-PROG@", "0700", false, null, null, null);
            }
        }
    }

    /** Installs test suite on the remote host. */
    public void installTestFiles() throws IOException {
        if (!connectionThread.isConnectionEstablished()) {
            return;
        }
        final SCPClient scpClient = new SCPClient(connectionThread.getConnection());
        final String fileName = "lcmc-test.tar";
        final String file = Tools.getFile('/' + fileName);
        try {
            scpClient.put(file.getBytes(), fileName, "/tmp");
        } catch (final IOException e) {
            LOG.appError("installTestFiles: could not copy: " + fileName, "", e);
            return;
        }

        final SshOutput sshOutput = execCommandAndWait(new ExecCommandConfig()
                                                           .command("tar xf /tmp/lcmc-test.tar -C /tmp/")
                                                           .sshCommandTimeout(60000));
        if (!sshOutput.isSuccess()) {
            LOG.appWarning("installing test files failed: " + sshOutput.getExitCode());
        }
    }

    /**
     * Creates config on the host with specified name in the specified
     * directory.
     *
     * @param config
     *          config content as a string
     * @param fileName
     *          file name of the config
     * @param dir
     *          directory where the config should be stored
     * @param mode
     *          mode, e.g. "0700"
     * @param makeBackup
                whether to make backup or not
     */
    public void createConfig(final String config,
                             final String fileName,
                             final String dir,
                             final String mode,
                             final boolean makeBackup,
                             final String preCommand,
                             final String postCommand) {
        LOG.debug1("createConfig: " + dir + fileName + "\n" + config);
        scp(config, dir + fileName, mode, makeBackup, null, /* install command */ preCommand, postCommand);
    }

    /**
     * Copies file to the /tmp/ dir on the remote host.
     *
     * @param remoteFilename
     *          new file name on the other host
     */
    public void scp(final String fileContent,
                    final String remoteFilename,
                    final String mode,
                    final boolean makeBackup,
                    String installCommand,
                    final String preCommand,
                    final String postCommand) {
        if  (!isConnected()) {
            return;
        }
        final String commands = buildScpCommand(remoteFilename, makeBackup, preCommand);
        String modeString = "";
        if (mode != null) {
            modeString = " && chmod " + mode + ' ' + remoteFilename + ".new";
        }
        String postCommandString = "";
        if (postCommand != null) {
            postCommandString = " && " + postCommand;
        }
        final String backupString = buildBackupScpCommands(remoteFilename, makeBackup);
        if (installCommand == null) {
            installCommand = "mv " + remoteFilename + ".new " + remoteFilename;
        }
        final String commandTail = "\">" + remoteFilename + ".new"
                                   + modeString

                                   + "&& "
                                   + installCommand

                                   + postCommandString
                                   + backupString;
        LOG.debug1("scp: " + commands + "echo \"..." + commandTail);
        final String escapedBashCommand = DistResource.SUDO
                                          + "bash -c \""
                                          + Tools.escapeQuotes(commands
                                                               + "echo \""
                                                               + Tools.escapeQuotes(fileContent, 1)
                                                               + commandTail, 1)
                                          + '"';
        execCommand(new ExecCommandConfig()
                        .command(escapedBashCommand)
                        .execCallback(new ExecCallback() {
                                          @Override
                                          public void done(final String ans) {
                                              /* ok */
                                          }

                                          @Override
                                          public void doneError(final String ans, final int exitCode) {
                                              if (ans == null) {
                                                  return;
                                              }
                                              scpCommandFailed(ans);
                                          }
                                      })
                        .sshCommandTimeout(10000) /* smaller timeout */
                        .silentCommand()
                        .silentOutput()).block();
    }

    public void startVncPortForwarding(final String remoteHost, final int remotePort) throws IOException {
        final int localPort = remotePort + application.getVncPortOffset();
        try {
            localPortForwarder = connectionThread.getConnection().createLocalPortForwarder(localPort,
                                                                                           "127.0.0.1",
                                                                                           remotePort);
        } catch (final IOException e) {
            throw e;
        }
    }

    public void stopVncPortForwarding(final int remotePort)
        throws IOException {
        try {
            localPortForwarder.close();
        } catch (final IOException e) {
            throw e;
        }
    }

    public boolean isConnectionCanceled() {
        return connectionThread != null && connectionThread.isDisconnectedForGood();
    }

    private void scpCommandFailed(final String ans) {
        for (final String line : ans.split("\n")) {
             if (line.indexOf("error:") != 0) {
                 continue;
             }
             final Thread t = new Thread(
             new Runnable() {
                 @Override
                 public void run() {
                     guiData.progressIndicatorFailed(host.getName(), line, 3000);
                 }
             });
             t.start();
         }
    }

    private void authenticateAndConnect() {
        host.getTerminalPanel().addCommand("ssh " + host.getUserAtHost());
        final Authentication authentication = authenticationProvider.get();
        authentication.init(lastSuccessfulPassword, host, sshGui);
        final ConnectionThread newConnectionThread = connectionThreadProvider.get();
        newConnectionThread.init(host, sshGui, progressBar, connectionCallback, authentication);
        newConnectionThread.start();
        connectionThread = newConnectionThread;
    }

    private String buildScpCommand(final String remoteFilename, final boolean makeBackup, final String preCommand) {
        final StringBuilder commands = new StringBuilder(40);
        if (preCommand != null) {
            commands.append(preCommand);
            commands.append(';');
        }
        if (makeBackup) {
            commands.append("cp ");
            commands.append(remoteFilename);
            commands.append("{,.bak} 2>/dev/null;");
        }
        final int index = remoteFilename.lastIndexOf('/');
        if (index > 0) {
            final String dir = remoteFilename.substring(0, index + 1);
            commands.append("mkdir -p ");
            commands.append(dir);
            commands.append(';');
        }
        return commands.toString();
    }

    private String buildBackupScpCommands(final String remoteFilename, final boolean makeBackup) {
        final StringBuilder backupString = new StringBuilder(50);
        if (makeBackup) {
            backupString.append(" && if ! diff ");
            backupString.append(remoteFilename);
            backupString.append("{,.bak}>/dev/null 2>&1; then ");
            backupString.append("mv ");
            backupString.append(remoteFilename);
            backupString.append("{.bak,.`date +'%s'`} 2>/dev/null;true;");
            backupString.append(" else ");
            backupString.append("rm -f ");
            backupString.append(remoteFilename);
            backupString.append(".bak;");
            backupString.append(" fi ");
        }
        return backupString.toString();
    }
}
TOP

Related Classes of lcmc.cluster.service.ssh.Ssh

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.