All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.impl.diagnostic.ssh.SSHClient Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

The newest version!
/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.diagnostic.ssh;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import oracle.kv.impl.admin.param.BootstrapParams;
import oracle.kv.impl.diagnostic.JavaVersionVerifier;
import oracle.kv.impl.diagnostic.SNAInfo;
import oracle.kv.impl.security.util.ShellPasswordReader;
import oracle.kv.impl.util.ConfigUtils;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;

/**
 * A SSH client is to connect with SSH server and it executes all remote
 * commands in SSH server to exchange info, generate files and get files.
 */
public class SSHClient {
    /* SSH port */
    private int SSH_PORT = 22;
    private String EXE_CMD = "exec";
    private String SFTP_CMD = "sftp";

    /* Files to ignore, using shell file pattern syntax */
    private String[] FILTERS = new String[] {"*.bup",
                                             "*.jdb",
                                             "*/admin*/webapp/*"};
    private int BUFFER_SZIE = 4 * 1024;
    private int INTERVAL_TIME = 500;
    private String UNDER_LINE = "_";
    private String LINUX_HOME_SIGN = "~";
    private long begin;
    private long end;

    private String USER_HOME = System.getProperty("user.home");

    private JSch jsch = null;
    private Session jschSession = null;
    private String host = null;
    private String username = null;
    private boolean openStatus = false;
    private String errorMessage;

    public SSHClient(String host, String username) {
        jsch = new JSch();
        this.host = host;
        this.username = username;
    }

    /**
     * Check whether client is open
     *
     * @return true when client is open, or false
     */
    public boolean isOpen() {
        return openStatus;
    }

    /**
     * Get error message indicating the reason causing client is not open
     * correctly
     *
     * @return error message
     */
    public String getErrorMessage() {
        if (openStatus)
            return null;
        return errorMessage;
    }

    /**
     * Open client
     */
    public void open() {
        /* Create session by public authenticated file */
        jschSession = createSessionByAuthFile();
        if (jschSession == null || !jschSession.isConnected()) {
            /* create session by password */
            jschSession = createSessionByPassword();
        }

        setStatus();
    }

    /**
     * Open client for unit test
     */
    public void open(String password) {
        /* Create session by public authenticated file */
        jschSession = createSessionByAuthFile();
        if (jschSession == null || !jschSession.isConnected()) {
            /* create session by password */
            jschSession = createSessionByPassword(password);
        }

        setStatus();
    }

    /**
     * Open client by public authenticated file
     */
    public void openByAuthenticatedFile() {
        jschSession = createSessionByAuthFile();
        setStatus();
    }

    /**
     * Open client by password
     */
    public void openByPassword() {
        jschSession = createSessionByPassword();
        setStatus();
    }

    /**
     * Open client by password for unit test
     */
    public void openByPassword(String password) {
        jschSession = createSessionByPassword(password);
        setStatus();
    }

    /**
     * Close client
     */
    public void close() {
        if (jschSession != null && jschSession.isConnected()) {
            jschSession.disconnect();
        }
    }

    /**
     * Set status of client
     */
    private void setStatus() {
        if (jschSession != null && jschSession.isConnected()) {
            errorMessage = null;
            openStatus = true;
        } else {
            openStatus = false;
        }
    }

    /**
     * Create a session for SSH to connect SSH server by authenticated file.
     *
     * @return session of SSH
     * @throws Exception
     */
    private Session createSessionByAuthFile() {

        /*
         * return null when can not find public authentication key directory
         */
        File authKeyDirectory = new File(USER_HOME + "/.ssh");
        if (!authKeyDirectory.exists() || authKeyDirectory.isFile()) {
            return null;
        }

        File[] files = authKeyDirectory.listFiles();
        Session session = null;

        /* Try all authentication file in the .ssh directory in parallel */
        int numberSSHThread = files.length;
        ThreadPoolExecutor threadExecutor =
                new ThreadPoolExecutor(numberSSHThread,
                                       numberSSHThread,
                                       0L,
                                       TimeUnit.MILLISECONDS,
                                       new LinkedBlockingQueue());
        try {
            /*
             * Create session by authenticated files in parallel to improve
             * performance
             */
            List> sessionFutureList =
                    new ArrayList>();
            for (final File f : files) {
                if (f.isFile()) {
                    Callable clientOpenCallable =
                            new Callable() {

                        @Override
                        public Session call() {
                            try {
                                /* Create session by a authenticated file */
                                return createSessionByOneAuthFile(
                                    f.getAbsolutePath());
                            } catch (Exception ex) {
                                errorMessage = ex.getMessage();
                                return null;
                            }
                        }
                    };
                    sessionFutureList.
                        add(threadExecutor.submit(clientOpenCallable));
                }
            }

            /* Check returns of all threads to get valid session */
            for (Future fs : sessionFutureList) {
                session = fs.get();
                if (session != null && session.isConnected()) {
                    break;
                }
            }
        } catch (Exception ex) {
            if (session == null || !session.isConnected()) {
                errorMessage = ex.getMessage();
            }
        } finally {
            threadExecutor.shutdown();
        }
        return session;
    }

    /**
     * Create SSH session to connect SSH server by a public authentication key
     * file
     *
     * @param authFilePath public authentication key file
     * @return
     * @throws JSchException
     */
    private Session createSessionByOneAuthFile(String authFilePath)
        throws Exception {
        Session session = null;
        try {
            /* Add authenticated file for jsch to connect remote machine */
            jsch.addIdentity(authFilePath);
            session = jsch.getSession(username, host, SSH_PORT);
            UserInfo ui = new AuthUserInfo();
            session.setUserInfo(ui);
            session.setDaemonThread(true);

            session.connect();
        } catch (JSchException ex) {
            String msg = ex.getMessage();
            if (msg.contains("java.net.UnknownHostException")) {
                throw new Exception("Unreachable host " + host);
            } else if (msg.contains("Connection refused")) {
                throw new Exception("Cannot establish SSH to connect " + host);
            } else {
                errorMessage = null;
            }
        }
        return session;
    }

    /**
     * Create SSH session using prompt password
     *
     * @return JSchSession
     */
    private Session createSessionByPassword() {
    	return createSessionByPassword(null);
    }


    /**
     * Create SSH session using password when password is not null, or using
     * prompt password
     *
     * @param password
     * @return JSchSession
     */
    private Session createSessionByPassword(String password) {
        Session session = null;
        try {
            session = jsch.getSession(username, host, SSH_PORT);
            UserInfo ui = new PasswordUserInfo(password);
            session.setUserInfo(ui);
            session.setDaemonThread(true);

            session.connect();
        } catch (JSchException ex) {
            String msg = ex.getMessage();
            if (msg.equals("Auth cancel")) {
                errorMessage = "Authentication cancel for " + username
                        + " to connect " + host;
            } else if (msg.equals("Auth fail")) {
                errorMessage = "Authentication fail for " + username
                        + " to connect " + host;
            } else if (msg.contains("authentication failures")) {
                errorMessage = "Incorrect password for " + username
                        + " to connect " + host;
            } else if (msg.contains("java.net.UnknownHostException")) {
                errorMessage = "Unreachable host " + host;
            } else if (msg.contains("Connection refused")) {
                errorMessage = "Cannot establish SSH to connect " + host;
            } else {
                errorMessage = ex.getMessage();
            }
        }
        return session;
    }

    /**
     * Check whether a file exists in remote machine via system commands
     *
     * @param filePath file path in remote machine
     * @return true when the file exist; or return false
     * @throws Exception
     */
    public boolean checkFile(String filePath) throws Exception {
        /* Generate command of checking */
        String command = "ls " + filePath;

        StringBuffer out = new StringBuffer();
        StringBuffer err = new StringBuffer();
        boolean status = executeCommand(command, out, err);

        /*
         * The file does exist in remote machine, when the status of execution
         * of "ls "
         */
        if (status) {
            return true;
        }

        return false;
    }

    /**
     * Get the absolute path of the a file in the remote host
     * @param remotePath the path in the remote host
     * @return the absolute path in the remote host
     * @throws Exception
     */
    private String getRemoteAbsolutePath(String remotePath) throws Exception {
        ChannelSftp channel = null;
        try {
            /* Get sftp channel */
            channel = (ChannelSftp) jschSession.openChannel(SFTP_CMD);
            /* Connect channel */
            channel.connect();
            if (remotePath.startsWith(LINUX_HOME_SIGN)) {
                String homePath = channel.pwd();
                remotePath = remotePath.replaceAll(LINUX_HOME_SIGN,
                                                       homePath);
            }
        } catch (Exception ex) {
            throw new Exception("Problem get absolute path for file : " +
                        remotePath,
                        ex.getCause());
        } finally {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
        }
        return remotePath;
    }

    /**
     * Copy a file or a directory from remote machine to local machine via SFTP
     *
     * @param remoteSource the file path in remote machine
     * @param localTarget the destination directory in local machine
     * @param isRecursive indicate whether copy files recursively or not
     * @throws Exception
     */
    private void sftp(String remoteSource,
                      File localTarget,
                      boolean isRecursive) throws Exception {

        ChannelSftp channel = null;
        try {
            /* Get sftp channel */
            channel = (ChannelSftp) jschSession.openChannel(SFTP_CMD);
            /* Connect channel */
            channel.connect();

            if (remoteSource.startsWith(LINUX_HOME_SIGN)) {
                String homePath = channel.pwd();
                remoteSource = remoteSource.replaceAll(LINUX_HOME_SIGN,
                                                       homePath);
            }

            /* Get attribute of remote file */
            SftpATTRS attrs = channel.stat(remoteSource);
            if (attrs.isDir()) {
                /* Iterate the remote file when it is a directory */
                String topFileName = null;
                channel.cd(remoteSource);
                remoteSource = channel.pwd();
                topFileName = getDirectoryLastName(remoteSource);

                /*
                 * Iterate the remote file path and get all files under this
                 * directory, except jdb files
                 */
                if (topFileName == null) {
                    iterateDirectory(channel, remoteSource, localTarget,
                                     FILTERS, isRecursive);
                } else {
                    iterateDirectory(channel, remoteSource,
                                     new File(localTarget, topFileName),
                                     FILTERS, isRecursive);
                }
            } else {
                /* Get the file when the remote file path is a file */
                int index = remoteSource.lastIndexOf("/");
                String parent = ".";
                if (index > 0) {
                    parent = remoteSource.substring(0, index);
                }
                channel.cd(parent);
                Collection files = channel.ls(remoteSource);
                LsEntry entry = (LsEntry) files.toArray()[0];
                /* Get file from remote machine to local machine */
                getFile(channel, entry.getFilename(), localTarget);
            }
        } catch (Exception ex) {
            throw new Exception("Problem sftp file : " + remoteSource,
                                ex.getCause());
        } finally {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
        }
    }

    /**
     * Get last name of the specified full directory path of remote machine
     *
     * @param directory the specified full directory path
     * @return last name
     */
    private String getDirectoryLastName(String directory) {
        String lastName = null;
        if (directory == null || directory.equals("")) {
            return null;
        }

        /* root directory return it directly */
        if (directory.equals("/")) {
            return directory;
        }

        if (directory.endsWith("/")) {
            directory = directory.substring(0, directory.length() - 1);
        }

        int index = directory.lastIndexOf("/");
        if (index > 0) {
            lastName = directory.substring(index + 1);
        }

        return lastName;
    }

    /**
     * Iterate all files under a specified directory from remote machine to
     * local machine.
     *
     * @param channel Sftp channel
     * @param directory remote directory path
     * @param targetFile local directory
     * @param filterPatterns filename patterns for files that should be excluded
     * @param isRecursive indicates whether to iterate the directory recursively
     * and to iterate its children directories or not
     * @throws SftpException
     */
    private void iterateDirectory(ChannelSftp channel, String directory,
                                  File targetFile, String[] filteredPatterns,
                                  boolean isRecursive)
                                        throws SftpException {
        /* cd to directory in remote machine */
        channel.cd(directory);

        /* create new directory when it does not exist */
        if (!targetFile.exists()) {
            targetFile.mkdirs();
        }

        /* List all files under directory in remote machine */
        Collection entries = channel.ls(directory);

        /*
         * Iterate all files under directory. If the listed file is a directory
         * and call iterateDirectory recursively; if the listed file is a file,
         * call getFile to get file from remote machine
         */
        for (Object object : entries) {
            if (object instanceof LsEntry) {
                LsEntry entry = (LsEntry) object;
                String name = entry.getFilename();
                String fullName = directory + "/" + name;
                if (entry.getAttrs().isDir()) {
                    /* Add separator for the path of the directory */
                    fullName += "/";
                    /* Iterate the children directories */
                    if (isRecursive) {
                        /*
                         * Filter current directory and parent directory and
                         * the path contain the filtered patterns
                         */
                        if (name.equals(".") || name.equals("..") ||
                                isFiltered(fullName)) {
                            continue;
                        }
                        iterateDirectory(channel,
                               channel.pwd() + "/" + name + "/",
                               new File(targetFile, entry.getFilename()),
                               filteredPatterns, isRecursive);
                    }
                } else {
                    /*
                     * Get file from remote machine when the path of the file
                     * does not match filtered pattern
                     */
                    if (!isFiltered(fullName)) {
                        getFile(channel, entry.getFilename(), targetFile);
                    }
                }
            }
        }
        channel.cd("..");
    }

    /**
     * Check whether a path should be filtered or not.
     *
     * @return true when the path contain any one filter pattern; or return
     * false
     */
    private boolean isFiltered(String path) {
        for (String pattern : FILTERS) {
            /*
             * Convert the pattern to be fit for java regular expressions.
             * (.) sign mean any characters, so it should be replaced by (\.).
             * replaceAll accepts regular expressions, so (.) should be escaped
             * as (\\.) and (\.) should be escaped as (\\\\.).
             *
             * (*) in the pattern also should be converted as (.*) which is fit
             * for java regular expressions. replaceAll accepts regular
             * expressions, so (*) should be escaped as (\\*).
             */
            pattern = pattern.replaceAll("\\.", "\\\\.");
            pattern = pattern.replaceAll("\\*", ".\\*");
            if (path.matches(pattern)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get a file from remote machine
     *
     * @param channel SFTP channel
     * @param fileName the file name in remote machine
     * @param targetFile the destination directory in local machine
     * @throws SftpException
     */
    private void getFile(ChannelSftp channel, String fileName, File targetFile)
        throws SftpException {
        /* Create a new folder if it does not exist */
        if (!targetFile.exists()) {
            targetFile.mkdirs();
        }

        /* Invoke get API to fetch file from remote machine */
        channel.get(fileName, targetFile.getAbsolutePath());
    }

    /**
     * Get all log files of a SNA from remote machine
     *
     * @param snaInfo the info of a SNA
     * @param outputdir directory to store the log files
     * @param isCompress determine whether to compress the log files
     * @return the directory contains the log files
     * @throws Exception
     */
    public File getLogFiles(SNAInfo snaInfo,
                            String outputdir,
                            boolean isCompress) throws Exception {
        String localSNDir = snaInfo.getStoreName() + UNDER_LINE +
                snaInfo.getStorageNodeName();
        File resultDir = new File(outputdir, localSNDir);
        String rootdir = snaInfo.getRootdir();
        String localKVRootDir = getDirectoryLastName(rootdir);


        getFileFromRemoteDirectory(rootdir,
                                   resultDir,
                                   resultDir.getParentFile(),
                                   localSNDir,
                                   isCompress);
        /*
         * Iterate all files in specified root directory and determine which
         * one is the configuration file
         */
        Set set = new HashSet();
        File localKVRoot = new File(resultDir, localKVRootDir);
        File[] files = localKVRoot.listFiles();
        for (File file : files) {
            try {
                if (file.isDirectory()) {
                    continue;
                }
                BootstrapParams bp = ConfigUtils.
                    getBootstrapParams(file, false /* check read only*/);

                /* Check whether explicit storage directories are set for SNs */
                for (String path : bp.getStorageDirPaths()) {
                    set.add(path);
                }
                /* Check whether explicit rnlog directories are set for SNs */
                for (String path : bp.getRNLogDirPaths()) {
                    set.add(path);
                }
                /* Check whether explicit admin directory is set for SNs */
                for (String path : bp.getAdminDirPath()) {
                    set.add(path);
                }
            } catch (Exception ignore) {
                /*
                 * Ignore this exception. This exception is used to check
                 * whether the files under kvroot are configuration XML files
                 */
            }
        }

        /*
         * Copy the log files in the mount points
         */
        for (String mountPoint : set) {
            String localStorageDirName = mountPoint.replace('/', '_');
            getFileFromRemoteDirectory(mountPoint,
                                       resultDir,
                                       resultDir,
                                       localStorageDirName,
                                       isCompress);

            /* Rename the storage directory to avoid overwrite */
            File originalDir = new File(resultDir,
                                        getDirectoryLastName(mountPoint));
            File targetDir = new File(resultDir, localStorageDirName);
            originalDir.renameTo(targetDir);
        }

        return resultDir;
    }

    private void getFileFromRemoteDirectory(String remotedir,
                                            File resultDir,
                                            File targetDir,
                                            String targetDirName,
                                            boolean isCompress)
                                                    throws Exception {
        String remotedirName = getDirectoryLastName(remotedir);

        if (isCompress) {
            /* Generate command to zip log files in remote machine */
            String zipfileName = targetDirName + ".zip";
            String remoteZipPath = "/tmp/" + zipfileName;
            String command = "cd " + remotedir + "/.. && zip -r " +
                                remoteZipPath + " " + remotedirName;
            String filterPatterns = getFilterPatterns();
            if (!filterPatterns.isEmpty()) {
                command += " -x " + filterPatterns;
            }

            /* Zip log files in remote machine */
            StringBuffer out = new StringBuffer();
            StringBuffer err = new StringBuffer();
            boolean status = executeCommand(command, out, err);

            if (!status) {
                throw new Exception("Generate zip file failed. " +
                		"Please specify -nocompress flag to retry.");
            }

            /*
             * Get all log files need copy the whole directory and its
             * children directories
             */
            sftp(remoteZipPath, targetDir, true);

            /* Clear standard output and err buffer */
            out.setLength(0);
            err.setLength(0);

            /* Delete generated zip file in remote machine */
            executeCommand("rm -rf " + remoteZipPath, out, err);

            unzip(targetDir + File.separator + zipfileName,
                  resultDir.getAbsolutePath());

        } else {
            /* Call sftp method to get all log file from a remote machine */
            sftp(remotedir, resultDir, true);
        }
    }

    /**
     * Get the latency between the client and a remote host. The algorithm is
     * as follows:
     * 1. client records the begin time
     * 2. client executes a command to show the date in remote host
     * 3. client gets the result from remote host and record the end time
     * 4. Assumed that the time taken to send command to and get result from
     * remote host are same, so the latency follow the formula:
     * latency = (remote time - begin time) - delta time
     * delta time = (end time - begin time) / 2
     * @return the latency; return Long.MIN_VALUE when the remote host can not
     * support date +%s%N command.
     * @throws Exception
     */
    public long getTimeLatency() throws Exception {
        /* To get accurate result, to several times */
        long minLatency = Long.MAX_VALUE;

        for (int i=0; i<5; i++) {
            StringBuffer out = new StringBuffer();
            StringBuffer err = new StringBuffer();
            boolean status = executeCommand("date +%s%N", out, err);
            if (!status) {
                return 0;
            }

            long remoteTime = 0;
            try {
                remoteTime = Long.parseLong(out.toString().trim()) /
                        (1000 * 1000);
            } catch (NumberFormatException nfe) {
                return Long.MIN_VALUE;
            }

            long latency = remoteTime - (end - begin) / 2 - begin;
            if (latency < minLatency) {
                minLatency = latency;
            }
        }

        return minLatency;
    }

    /**
     * Get the JDK version information from remote hosts
     * @return the JDK version and JDK vendor
     * @throws Exception
     */
    public JavaVersionVerifier getJavaVersion() throws Exception {
        StringBuffer out = new StringBuffer();
        StringBuffer err = new StringBuffer();
        executeCommand("java -XshowSettings:properties", out, err);

        String settings = err.toString().trim();
        List settingList = Arrays.asList(settings.split("\n"));
        String vendor = null;
        String version = null;
        for (String item : settingList) {
            if (item.contains("java.vendor = ")) {
                vendor = item.replace("java.vendor = ", "").trim();
            }

            if (item.contains("java.version = ")) {
                version = item.replace("java.version = ", "").trim();
            }
        }

        JavaVersionVerifier verifier = new JavaVersionVerifier();
        verifier.setJKDVersionInfo(vendor, version);
        return verifier;
    }

    /**
     * Get the network connectivity status from the connected host to others
     * in the list
     * @param list the list contains the others hosts
     * @return the map contains the network connectivity status
     * @throws Exception
     */
    public Map getNetWorkStatus(List list)
            throws Exception {
        /* Distinct host in SNA info list */
        Map ipSNAMap =
                new HashMap();

        /* Get IP Address of other hosts */
        for (SNAInfo si : list) {
            ipSNAMap.put(si.getIP(), si);
        }

        /*
         * Execute ping command in the connected host to ping other hosts to
         * determine whether the network is connected between them
         */
        Map map = new HashMap();
        StringBuffer out = new StringBuffer();
        StringBuffer err = new StringBuffer();
        for (Map.Entry entry : ipSNAMap.entrySet()) {
            String command = "ping -c 1 " + entry.getValue().getHost();
            boolean status = executeCommand(command, out, err);
            map.put(entry.getValue(), status);
        }
        return map;
    }

    /**
     * Get all configuration files in the specified root directory of the
     * connected host.
     * @param snaInfo
     * @param saveFolder
     * @return the list of the local path of the configuration files
     * @throws Exception
     */
    public List getConfigFile(SNAInfo snaInfo, File saveFolder)
            throws Exception {
        if (!saveFolder.exists()) {
            saveFolder.mkdirs();
        }

        String rootdir = snaInfo.getRootdir();
        String zipfileName = saveFolder.getName() + ".zip";
        String remoteZipPath = "/tmp/" + zipfileName;
        String command = "cd " + rootdir + " && zip " + remoteZipPath + " ./*";
        String filterPatterns = getFilterPatterns();
        if (!filterPatterns.isEmpty()) {
            command += " -x " + filterPatterns;
        }

        /* Zip log files in remote machine */
        StringBuffer out = new StringBuffer();
        StringBuffer err = new StringBuffer();
        boolean status = executeCommand(command, out, err);

        if (!status) {
            throw new Exception("Generate zip file failed");
        }

        String remoteRoot = getRemoteAbsolutePath(snaInfo.getRootdir());
        /* Fetch generated zip file to local machine */
        sftp(remoteZipPath, saveFolder, true);

        /* Clear standard output and err buffer */
        out.setLength(0);
        err.setLength(0);

        /* Delete generated zip file in remote machine */
        executeCommand("rm -rf " + remoteZipPath, out, err);

        unzip(saveFolder + File.separator + zipfileName,
              saveFolder.getAbsolutePath());

        new File(saveFolder + File.separator + zipfileName).delete();

        snaInfo.setRemoteRootdir(remoteRoot);

        /*
         * Iterate all files in specified root directory and determine which
         * one is the configuration file
         */
        List list = new ArrayList();
        File[] files = saveFolder.listFiles();
        Map securityDirMap = new HashMap();
        for (File file : files) {
            try {
                if (file.isDirectory()) {
                    continue;
                }
                BootstrapParams bp = ConfigUtils.
                    getBootstrapParams(file, false /* check read only*/);

                /*
                 * Check whether the configuration file indicates to enable
                 * security or not
                 */
                if (bp.getSecurityDir() != null &&
                        !bp.getSecurityDir().isEmpty()) {
                    /*
                     * Copy security files when configuration file indicates to
                     * enable security. If the security folder has existed in
                     * local, no need to copy it
                     */
                    String dir = securityDirMap.get(bp.getSecurityDir());
                    if (dir == null) {
                        getSecurityFile(file.getParentFile(),
                                        snaInfo.getRootdir(),
                                        bp.getSecurityDir());
                        securityDirMap.put(bp.getSecurityDir(),
                                           bp.getSecurityDir());
                    }
                }
                list.add(file);
            } catch (Exception ignore) {
                /*
                 * Ignore this exception. This exception is used to check
                 * whether the files under kvroot are configuration XML files
                 */
            }
        }
        return list;
    }

    /**
     * Copy security folder from remote host
     * @param targetFile
     * @param source
     * @param securityFolder
     * @throws Exception
     */
    private void getSecurityFile(File targetFile, String source,
                                String securityFolder) throws Exception {
        String remoteSource;
        if (source.endsWith("/")) {
            remoteSource = source + securityFolder;
        } else {
            remoteSource = source + "/" + securityFolder;
        }

        String zipfileName = targetFile.getName() + "_" + securityFolder +
                ".zip";
        String remoteZipPath = "/tmp/" + zipfileName;
        String command = "cd " + remoteSource + " && zip -r " + remoteZipPath +
                " ./*";

        String filterPatterns = getFilterPatterns();
        if (!filterPatterns.isEmpty()) {
            command += " -x " + filterPatterns;
        }

        /* Zip log files in remote machine */
        StringBuffer out = new StringBuffer();
        StringBuffer err = new StringBuffer();
        boolean status = executeCommand(command, out, err);

        if (!status) {
            throw new Exception("Generate zip file failed");
        }

        File saveFolder = new File(targetFile, securityFolder);

        /* Fetch generated zip file to local machine */
        sftp(remoteZipPath, saveFolder, true);

        /* Clear standard output and err buffer */
        out.setLength(0);
        err.setLength(0);

        /* Delete generated zip file in remote machine */
        executeCommand("rm -rf " + remoteZipPath, out, err);

        unzip(saveFolder + File.separator + zipfileName,
              saveFolder.getAbsolutePath());

        new File(saveFolder + File.separator + zipfileName).delete();
    }

    /**
     * Generate the filter patterns for zip command.
     */
    private String getFilterPatterns() {
        /*
         * zip 2.31 needs to add \ before each *, so to make it compatible
         * with zip 2.31 and 3.0, we add \ before each *.
         */
        String patterns = "";
        for (String pattern : FILTERS) {
            StringBuilder sb = new StringBuilder();
            for (char c : pattern.toCharArray()) {
                if (c == '*') {
                    sb.append("\\*");
                } else {
                    sb.append(c);
                }
            }
            patterns += sb.toString() + " ";
        }
        return patterns.trim();
    }

    /**
     * Unzip a zip file
     *
     * @param zipPath the path of the source zip file
     * @param outputdir the destination directory
     * @throws Exception
     */
    private void unzip(String zipPath, String outputdir) throws Exception {
        ZipFile zipFile = null;
        try {
            zipFile = new ZipFile(zipPath);
            Enumeration entries = zipFile.entries();
            /* Iterate all files in zip file */
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();

                /* Create a new directory when the entry is directory in zip */
                if (entry.isDirectory()) {
                    new File(outputdir, entry.getName()).mkdirs();
                    continue;
                }

                /*
                 * Read the file from zip and write in local machine when it is
                 * a file
                 */
                BufferedInputStream bis = null;
                FileOutputStream fos = null;
                BufferedOutputStream bos = null;
                try {
                    bis = new BufferedInputStream(zipFile.
                                                  getInputStream(entry));
                    File file = new File(outputdir, entry.getName());

                    File parent = file.getParentFile();
                    if (parent != null && (!parent.exists())) {
                        parent.mkdirs();
                    }
                    fos = new FileOutputStream(file);
                    bos = new BufferedOutputStream(fos, BUFFER_SZIE);

                    int count;
                    byte data[] = new byte[BUFFER_SZIE];
                    while ((count = bis.read(data, 0, BUFFER_SZIE)) != -1) {
                        bos.write(data, 0, count);
                    }
                } catch (Exception ex) {
                    throw new Exception("Problem unzipping file: " + zipPath);
                } finally {
                    if (bos != null) {
                        bos.flush();
                        bos.close();
                    }

                    if (fos != null) {
                        fos.flush();
                        fos.close();
                    }

                    if (bis != null) {
                        bis.close();
                    }
                }
            }
        } catch (Exception ex) {
            throw new Exception("Problem unzipping file: " + zipPath);
        } finally {
            if (zipFile != null) {
                zipFile.close();
            }
        }
    }

    /**
     * Execute command in remote machine via SSH
     *
     * @param command the command to be executed via SSH
     * @param out is the standard output of executed command
     * @param err is the standard error message of executed command
     * @return the status of execution, true if command is executed
     * successfully; or false
     * @throws IOException
     */
    private boolean executeCommand(String command,
                                   StringBuffer out,
                                   StringBuffer err) throws Exception {
        InputStream outStream = null;
        InputStream errStream = null;
        boolean status = false;

        ChannelExec channel = null;
        try {
            /* Open execution channel */
            channel = (ChannelExec) jschSession.openChannel(EXE_CMD);
            /* Set command */
            channel.setCommand(command);
            outStream = channel.getInputStream();
            errStream = channel.getErrStream();

            /*
             * Connect and execute command and record the begin time and
             * end time
             */
            begin = System.currentTimeMillis();
            channel.connect();
            end = System.currentTimeMillis();

            byte[] buffer = new byte[BUFFER_SZIE];
            while (true) {
                /* Read the standard output of the command executed */
                while (outStream.available() > 0) {
                    int len = outStream.read(buffer, 0, BUFFER_SZIE);
                    out.append(new String(buffer, 0, len));
                }

                /* Read the standard err of the command executed */
                while (errStream.available() > 0) {
                    int len = errStream.read(buffer, 0, BUFFER_SZIE);
                    err.append(new String(buffer, 0, len));
                }

                /* Continue read when there is still available data existing */
                if (channel.isClosed()) {
                    if (outStream.available() > 0 ||
                            errStream.available() > 0) {
                        continue;
                    }
                    break;
                }

                /*
                 * Sleep some time and let JSch get std out and std err from
                 * command executed in remote host
                 */
                try {
                    Thread.sleep(INTERVAL_TIME);
                } catch (Exception ignore) {
                }
            }

            /* The execution is successful when status code is 0 */
            if (channel.getExitStatus() == 0) {
                status = true;
            }
            return status;
        } catch (JSchException ex) {
            throw new Exception("Problem executing command : " + command,
                                ex.getCause());
        } finally {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
        }
    }

    /**
     * An abstract class which derives from UserInfo
     */
    private abstract class SSHUserInfo implements UserInfo,
            UIKeyboardInteractive {
        @Override
        public boolean promptYesNo(String str) {
            return true;
        }

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

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

        @Override
        public void showMessage(String message) {
            System.out.println(message);
        }

        @Override
        public String[] promptKeyboardInteractive(String destination,
                                                  String name,
                                                  String instruction,
                                                  String[] prompt,
                                                  boolean[] echo) {
            return new String[] { getPassword() };
        }
    }

    /**
     * An implementation of UserInfo, it is used for authentication when
     * connect SSH server
     */
    private class PasswordUserInfo extends SSHUserInfo {
    	String password = null;
    	PasswordUserInfo(String password) {
    		this.password = password;
    	}

        @Override
        public String getPassword() {
            /* Prompt users to enter password */

            String prompt = username + "@" + host + "'s password: ";
            if (password != null) {
            	return password;
            }
            try {
            	ShellPasswordReader passwordReader = new ShellPasswordReader();
                password = new String(passwordReader.readPassword(prompt));
            } catch (IOException e) {

            }
            return password;
        }

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

    /**
     * An implementation of UserInfo, it is used for authentication when
     * connect SSH server
     */
    private class AuthUserInfo extends SSHUserInfo {
        @Override
        public String getPassword() {
            return null;
        }

        @Override
        public boolean promptPassphrase(String message) {
            System.out.println(message);
            return true;
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy