Package org.apache.oozie.action.ssh

Source Code of org.apache.oozie.action.ssh.SshActionExecutor

/**
* Copyright (c) 2010 Yahoo! Inc. All rights reserved.
* Licensed 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. See accompanying LICENSE file.
*/
package org.apache.oozie.action.ssh;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.Callable;

import org.apache.oozie.client.WorkflowAction;
import org.apache.oozie.client.OozieClient;
import org.apache.oozie.client.WorkflowAction.Status;
import org.apache.oozie.action.ActionExecutor;
import org.apache.oozie.action.ActionExecutorException;
import org.apache.oozie.service.CallbackService;
import org.apache.oozie.servlet.CallbackServlet;
import org.apache.oozie.service.Services;
import org.apache.oozie.util.IOUtils;
import org.apache.oozie.util.PropertiesUtils;
import org.apache.oozie.util.XLog;
import org.apache.oozie.util.XmlUtils;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;

/**
* Ssh action executor. <p/> <ul> <li>Execute the shell commands on the remote host</li> <li>Copies the base and wrapper
* scripts on to the remote location</li> <li>Base script is used to run the command on the remote host</li> <li>Wrapper
* script is used to check the status of the submitted command</li> <li>handles the submission failures</li> </ul>
*/
public class SshActionExecutor extends ActionExecutor {
    public static final String ACTION_TYPE = "ssh";

    /**
     * Configuration parameter which specifies whether the specified ssh user is allowed, or has to be the job user.
     */
    public static final String CONF_SSH_ALLOW_USER_AT_HOST = CONF_PREFIX + "ssh.allow.user.at.host";

    protected static final String SSH_COMMAND_OPTIONS =
            "-o PasswordAuthentication=no -o KbdInteractiveDevices=no -o StrictHostKeyChecking=no -o ConnectTimeout=20 ";

    protected static final String SSH_COMMAND_BASE = "ssh " + SSH_COMMAND_OPTIONS;
    protected static final String SCP_COMMAND_BASE = "scp " + SSH_COMMAND_OPTIONS;

    public static final String ERR_SETUP_FAILED = "SETUP_FAILED";
    public static final String ERR_EXECUTION_FAILED = "EXECUTION_FAILED";
    public static final String ERR_UNKNOWN_ERROR = "UNKOWN_ERROR";
    public static final String ERR_COULD_NOT_CONNECT = "COULD_NOT_CONNECT";
    public static final String ERR_HOST_RESOLUTION = "COULD_NOT_RESOLVE_HOST";
    public static final String ERR_FNF = "FNF";
    public static final String ERR_AUTH_FAILED = "AUTH_FAILED";
    public static final String ERR_NO_EXEC_PERM = "NO_EXEC_PERM";
    public static final String ERR_USER_MISMATCH = "ERR_USER_MISMATCH";
    public static final String ERR_EXCEDE_LEN = "ERR_OUTPUT_EXCEED_MAX_LEN";

    public static final String DELETE_TMP_DIR = "oozie.action.ssh.delete.remote.tmp.dir";

    public static final String HTTP_COMMAND = "oozie.action.ssh.http.command";

    public static final String HTTP_COMMAND_OPTIONS = "oozie.action.ssh.http.command.post.options";

    private static final String EXT_STATUS_VAR = "#status";

    private static int maxLen;
    private static boolean allowSshUserAtHost;

    protected SshActionExecutor() {
        super(ACTION_TYPE);
    }

    /**
     * Initialize Action.
     */
    @Override
    public void initActionType() {
        super.initActionType();
        maxLen = getOozieConf().getInt(CallbackServlet.CONF_MAX_DATA_LEN, 2 * 1024);
        allowSshUserAtHost = getOozieConf().getBoolean(CONF_SSH_ALLOW_USER_AT_HOST, true);
        registerError(InterruptedException.class.getName(), ActionExecutorException.ErrorType.ERROR, "SH001");
        registerError(JDOMException.class.getName(), ActionExecutorException.ErrorType.ERROR, "SH002");
        initSshScripts();
    }

    /**
     * Check ssh action status.
     *
     * @param context action execution context.
     * @param action action object.
     */
    @Override
    public void check(Context context, WorkflowAction action) throws ActionExecutorException {
        Status status = getActionStatus(context, action);
        boolean captureOutput = false;
        try {
            Element eConf = XmlUtils.parseXml(action.getConf());
            Namespace ns = eConf.getNamespace();
            captureOutput = eConf.getChild("capture-output", ns) != null;
        }
        catch (JDOMException ex) {
            throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "ERR_XML_PARSE_FAILED",
                                              "unknown error", ex);
        }
        XLog log = XLog.getLog(getClass());
        log.debug("Capture Output: {0}", captureOutput);
        if (status == Status.OK) {
            if (captureOutput) {
                String outFile = getRemoteFileName(context, action, "stdout", false, true);
                String dataCommand = SSH_COMMAND_BASE + action.getTrackerUri() + " cat " + outFile;
                log.debug("Ssh command [{0}]", dataCommand);
                try {
                    Process process = Runtime.getRuntime().exec(dataCommand.split("\\s"));
                    StringBuffer buffer = new StringBuffer();
                    boolean overflow = false;
                    drainBuffers(process, buffer, null, maxLen);
                    if (buffer.length() > maxLen) {
                        overflow = true;
                    }
                    if (overflow) {
                        throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR,
                                                          "ERR_OUTPUT_EXCEED_MAX_LEN", "unknown error");
                    }
                    context.setExecutionData(status.toString(), PropertiesUtils.stringToProperties(buffer.toString()));
                }
                catch (Exception ex) {
                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "ERR_UNKNOWN_ERROR",
                                                      "unknown error", ex);
                }
            }
            else {
                context.setExecutionData(status.toString(), null);
            }
        }
        else {
            if (status == Status.ERROR) {
                context.setExecutionData(status.toString(), null);
            }
            else {
                context.setExternalStatus(status.toString());
            }
        }
    }

    /**
     * Kill ssh action.
     *
     * @param context action execution context.
     * @param action object.
     */
    @Override
    public void kill(Context context, WorkflowAction action) throws ActionExecutorException {
        String command = "ssh " + action.getTrackerUri() + " kill  -KILL " + action.getExternalId();
        int returnValue = getReturnValue(command);
        if (returnValue != 0) {
            throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FAILED_TO_KILL", XLog.format(
                    "Unable to kill process {0} on {1}", action.getExternalId(), action.getTrackerUri()));
        }
        context.setEndData(WorkflowAction.Status.KILLED, "ERROR");
    }

    /**
     * Start the ssh action execution.
     *
     * @param context action execution context.
     * @param action action object.
     */
    @SuppressWarnings("unchecked")
    @Override
    public void start(final Context context, final WorkflowAction action) throws ActionExecutorException {
        XLog log = XLog.getLog(getClass());
        log.info("start() begins");
        String confStr = action.getConf();
        Element conf;
        try {
            conf = XmlUtils.parseXml(confStr);
        }
        catch (Exception ex) {
            throw convertException(ex);
        }
        Namespace nameSpace = conf.getNamespace();
        Element hostElement = conf.getChild("host", nameSpace);
        String hostString = hostElement.getValue().trim();
        hostString = prepareUserHost(hostString, context);
        final String host = hostString;
        final String dirLocation = execute(new Callable<String>() {
            public String call() throws Exception {
                return setupRemote(host, context, action);
            }

        });

        String runningPid = execute(new Callable<String>() {
            public String call() throws Exception {
                return checkIfRunning(host, context, action);
            }
        });
        String pid = "";

        if (runningPid == null) {
            final Element commandElement = conf.getChild("command", nameSpace);
            final boolean ignoreOutput = conf.getChild("capture-output", nameSpace) == null;

            if (commandElement != null) {
                List<Element> argsList = conf.getChildren("args", nameSpace);
                StringBuilder args = new StringBuilder("");
                if ((argsList != null) && (argsList.size() > 0)) {
                    for (Element argsElement : argsList) {
                        args = args.append(argsElement.getValue()).append(" ");
                    }
                    args.setLength(args.length() - 1);
                }
                final String argsString = args.toString();
                final String recoveryId = context.getRecoveryId();
                pid = execute(new Callable<String>() {

                    @Override
                    public String call() throws Exception {
                        return doExecute(host, dirLocation, commandElement.getValue(), argsString, ignoreOutput,
                                         action, recoveryId);
                    }

                });
            }
            context.setStartData(pid, host, host);
        }
        else {
            pid = runningPid;
            context.setStartData(pid, host, host);
            check(context, action);
        }
        log.info("start() ends");
    }

    private String checkIfRunning(String host, final Context context, final WorkflowAction action) {
        String pid = null;
        String outFile = getRemoteFileName(context, action, "pid", false, false);
        String getOutputCmd = SSH_COMMAND_BASE + host + " cat " + outFile;
        try {
            Process process = Runtime.getRuntime().exec(getOutputCmd.split("\\s"));
            StringBuffer buffer = new StringBuffer();
            drainBuffers(process, buffer, null, maxLen);
            pid = getFirstLine(buffer);

            if (Long.valueOf(pid) > 0) {
                return pid;
            }
            else {
                return null;
            }
        }
        catch (Exception e) {
            return null;
        }
    }

    /**
     * Get remote host working location.
     *
     * @param context action execution context
     * @param action Action
     * @param fileExtension Extension to be added to file name
     * @param dirOnly Get the Directory only
     * @param useExtId Flag to use external ID in the path
     * @return remote host file name/Directory.
     */
    public String getRemoteFileName(Context context, WorkflowAction action, String fileExtension, boolean dirOnly,
                                    boolean useExtId) {
        String path = getActionDirPath(context.getWorkflow().getId(), action, ACTION_TYPE, false) + "/";
        if (dirOnly) {
            return path;
        }
        if (useExtId) {
            path = path + action.getExternalId() + ".";
        }
        path = path + context.getRecoveryId() + "." + fileExtension;
        return path;
    }

    /**
     * Utility method to execute command.
     *
     * @param command Command to execute as String.
     * @return exit status of the execution.
     * @throws IOException if process exits with status nonzero.
     * @throws InterruptedException if process does not run properly.
     */
    public int executeCommand(String command) throws IOException, InterruptedException {
        Runtime runtime = Runtime.getRuntime();
        Process p = runtime.exec(command.split("\\s"));

        StringBuffer errorBuffer = new StringBuffer();
        int exitValue = drainBuffers(p, null, errorBuffer, maxLen);

        String error = null;
        if (exitValue != 0) {
            error = getTruncatedString(errorBuffer);
            throw new IOException(XLog.format("Not able to perform operation [{0}]", command) + " | " + "ErrorStream: "
                    + error);
        }
        return exitValue;
    }

    /**
     * Do ssh action execution setup on remote host.
     *
     * @param host host name.
     * @param context action execution context.
     * @param action action object.
     * @return remote host working directory.
     * @throws IOException thrown if failed to setup.
     * @throws InterruptedException thrown if any interruption happens.
     */
    protected String setupRemote(String host, Context context, WorkflowAction action) throws IOException, InterruptedException {
        XLog log = XLog.getLog(getClass());
        log.info("Attempting to copy ssh base scripts to remote host [{0}]", host);
        String localDirLocation = Services.get().getRuntimeDir() + "/ssh";
        if (localDirLocation.endsWith("/")) {
            localDirLocation = localDirLocation.substring(0, localDirLocation.length() - 1);
        }
        File file = new File(localDirLocation + "/ssh-base.sh");
        if (!file.exists()) {
            throw new IOException("Required Local file " + file.getAbsolutePath() + " not present.");
        }
        file = new File(localDirLocation + "/ssh-wrapper.sh");
        if (!file.exists()) {
            throw new IOException("Required Local file " + file.getAbsolutePath() + " not present.");
        }
        String remoteDirLocation = getRemoteFileName(context, action, null, true, true);
        String command = XLog.format("{0}{1}  mkdir -p {2} ", SSH_COMMAND_BASE, host, remoteDirLocation).toString();
        executeCommand(command);
        command = XLog.format("{0}{1}/ssh-base.sh {2}/ssh-wrapper.sh {3}:{4}", SCP_COMMAND_BASE, localDirLocation,
                              localDirLocation, host, remoteDirLocation);
        executeCommand(command);
        command = XLog.format("{0}{1}  chmod +x {2}ssh-base.sh {3}ssh-wrapper.sh ", SSH_COMMAND_BASE, host,
                              remoteDirLocation, remoteDirLocation);
        executeCommand(command);
        return remoteDirLocation;
    }

    /**
     * Execute the ssh command.
     *
     * @param host hostname.
     * @param dirLocation location of the base and wrapper scripts.
     * @param cmnd command to be executed.
     * @param args command arguments.
     * @param ignoreOutput ignore output option.
     * @param action action object.
     * @param recoveryId action id + run number to enable recovery in rerun
     * @return process id of the running command.
     * @throws IOException thrown if failed to run the command.
     * @throws InterruptedException thrown if any interruption happens.
     */
    protected String doExecute(String host, String dirLocation, String cmnd, String args, boolean ignoreOutput,
                               WorkflowAction action, String recoveryId) throws IOException, InterruptedException {
        XLog log = XLog.getLog(getClass());
        Runtime runtime = Runtime.getRuntime();
        String callbackPost = ignoreOutput ? "_" : getOozieConf().get(HTTP_COMMAND_OPTIONS).replace(" ", "%%%");
        // TODO check
        String callBackUrl = Services.get().get(CallbackService.class)
                .createCallBackUrl(action.getId(), EXT_STATUS_VAR);
        String command = XLog.format("{0}{1} {2}ssh-base.sh {3} \"{4}\" \"{5}\" {6} {7} {8} ", SSH_COMMAND_BASE, host,
                                     dirLocation, getOozieConf().get(HTTP_COMMAND), callBackUrl, callbackPost, recoveryId, cmnd, args)
                .toString();
        log.trace("Executing ssh command [{0}]", command);
        Process p = runtime.exec(command.split("\\s"));
        String pid = "";

        StringBuffer inputBuffer = new StringBuffer();
        StringBuffer errorBuffer = new StringBuffer();
        int exitValue = drainBuffers(p, inputBuffer, errorBuffer, maxLen);

        pid = getFirstLine(inputBuffer);

        String error = null;
        if (exitValue != 0) {
            error = getTruncatedString(errorBuffer);
            throw new IOException(XLog.format("Not able to execute ssh-base.sh on {0}", host) + " | " + "ErrorStream: "
                    + error);
        }
        return pid;
    }

    /**
     * End action execution.
     *
     * @param context action execution context.
     * @param action action object.
     * @throws ActionExecutorException thrown if action end execution fails.
     */
    public void end(final Context context, final WorkflowAction action) throws ActionExecutorException {
        if (action.getExternalStatus().equals("OK")) {
            context.setEndData(WorkflowAction.Status.OK, WorkflowAction.Status.OK.toString());
        }
        else {
            context.setEndData(WorkflowAction.Status.ERROR, WorkflowAction.Status.ERROR.toString());
        }
        boolean deleteTmpDir = getOozieConf().getBoolean(DELETE_TMP_DIR, true);
        if (deleteTmpDir) {
            String tmpDir = getRemoteFileName(context, action, null, true, false);
            String removeTmpDirCmd = SSH_COMMAND_BASE + action.getTrackerUri() + " rm -rf " + tmpDir;
            int retVal = getReturnValue(removeTmpDirCmd);
            if (retVal != 0) {
                XLog.getLog(getClass()).warn("Cannot delete temp dir {0}", tmpDir);
            }
        }
    }

    /**
     * Get the return value of a process.
     *
     * @param command command to be executed.
     * @return zero if execution is successful and any non zero value for failure.
     * @throws ActionExecutorException
     */
    private int getReturnValue(String command) throws ActionExecutorException {
        int returnValue;
        Process ps = null;
        try {
            ps = Runtime.getRuntime().exec(command.split("\\s"));
            returnValue = drainBuffers(ps, null, null, 0);
        }
        catch (IOException e) {
            throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "FAILED_OPERATION", XLog.format(
                    "Not able to perform operation {0}", command), e);
        }
        finally {
            ps.destroy();
        }
        return returnValue;
    }

    /**
     * Copy the ssh base and wrapper scripts to the local directory.
     */
    private void initSshScripts() {
        String dirLocation = Services.get().getRuntimeDir() + "/ssh";
        File path = new File(dirLocation);
        if (!path.mkdirs()) {
            throw new RuntimeException(XLog.format("Not able to create required directory {0}", dirLocation));
        }
        try {
            IOUtils.copyCharStream(IOUtils.getResourceAsReader("ssh-base.sh", -1), new FileWriter(dirLocation
                    + "/ssh-base.sh"));
            IOUtils.copyCharStream(IOUtils.getResourceAsReader("ssh-wrapper.sh", -1), new FileWriter(dirLocation
                    + "/ssh-wrapper.sh"));
        }
        catch (IOException ie) {
            throw new RuntimeException(XLog.format("Not able to copy required scripts file to {0} "
                    + "for SshActionHandler", dirLocation));
        }
    }

    /**
     * Get action status.
     *
     * @param action action object.
     * @return status of the action(RUNNING/OK/ERROR).
     * @throws ActionExecutorException thrown if there is any error in getting status.
     */
    protected Status getActionStatus(Context context, WorkflowAction action) throws ActionExecutorException {
        String command = SSH_COMMAND_BASE + action.getTrackerUri() + " ps -p " + action.getExternalId();
        Status aStatus;
        int returnValue = getReturnValue(command);
        if (returnValue == 0) {
            aStatus = Status.RUNNING;
        }
        else {
            String outFile = getRemoteFileName(context, action, "error", false, true);
            String checkErrorCmd = SSH_COMMAND_BASE + action.getTrackerUri() + " ls " + outFile;
            int retVal = getReturnValue(checkErrorCmd);
            if (retVal == 0) {
                aStatus = Status.ERROR;
            }
            else {
                aStatus = Status.OK;
            }
        }
        return aStatus;
    }

    /**
     * Execute the callable.
     *
     * @param callable required callable.
     * @throws ActionExecutorException thrown if there is any error in command execution.
     */
    private <T> T execute(Callable<T> callable) throws ActionExecutorException {
        XLog log = XLog.getLog(getClass());
        try {
            return callable.call();
        }
        catch (IOException ex) {
            log.warn("Error while executing ssh EXECUTION");
            String errorMessage = ex.getMessage();
            if (null == errorMessage) { // Unknown IOException
                throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, ERR_UNKNOWN_ERROR, ex
                        .getMessage(), ex);
            } // Host Resolution Issues
            else {
                if (errorMessage.contains("Could not resolve hostname") ||
                        errorMessage.contains("service not known")) {
                    throw new ActionExecutorException(ActionExecutorException.ErrorType.TRANSIENT, ERR_HOST_RESOLUTION, ex
                            .getMessage(), ex);
                } // Connection Timeout. Host temporarily down.
                else {
                    if (errorMessage.contains("timed out")) {
                        throw new ActionExecutorException(ActionExecutorException.ErrorType.TRANSIENT, ERR_COULD_NOT_CONNECT,
                                                          ex.getMessage(), ex);
                    }// Local ssh-base or ssh-wrapper missing
                    else {
                        if (errorMessage.contains("Required Local file")) {
                            throw new ActionExecutorException(ActionExecutorException.ErrorType.TRANSIENT, ERR_FNF,
                                                              ex.getMessage(), ex); // local_FNF
                        }// Required oozie bash scripts missing, after the copy was
                        // successful
                        else {
                            if (errorMessage.contains("No such file or directory")
                                    && (errorMessage.contains("ssh-base") || errorMessage.contains("ssh-wrapper"))) {
                                throw new ActionExecutorException(ActionExecutorException.ErrorType.TRANSIENT, ERR_FNF,
                                                                  ex.getMessage(), ex); // remote
                                // FNF
                            } // Required application execution binary missing (either
                            // caught by ssh-wrapper
                            else {
                                if (errorMessage.contains("command not found")) {
                                    throw new ActionExecutorException(ActionExecutorException.ErrorType.NON_TRANSIENT, ERR_FNF, ex
                                            .getMessage(), ex); // remote
                                    // FNF
                                } // Permission denied while connecting
                                else {
                                    if (errorMessage.contains("Permission denied")) {
                                        throw new ActionExecutorException(ActionExecutorException.ErrorType.NON_TRANSIENT, ERR_AUTH_FAILED, ex
                                                .getMessage(), ex);
                                    } // Permission denied while executing
                                    else {
                                        if (errorMessage.contains(": Permission denied")) {
                                            throw new ActionExecutorException(ActionExecutorException.ErrorType.NON_TRANSIENT, ERR_NO_EXEC_PERM, ex
                                                    .getMessage(), ex);
                                        }
                                        else {
                                            throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, ERR_UNKNOWN_ERROR, ex
                                                    .getMessage(), ex);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } // Any other type of exception
        catch (Exception ex) {
            throw convertException(ex);
        }
    }

    /**
     * Checks whether the system is configured to always use the oozie user for ssh, and injects the user if required.
     *
     * @param host the host string.
     * @param context the execution context.
     * @return the modified host string with a user parameter added on if required.
     * @throws ActionExecutorException in case the flag to use the oozie user is turned on and there is a mismatch
     * between the user specified in the host and the oozie user.
     */
    private String prepareUserHost(String host, Context context) throws ActionExecutorException {
        String oozieUser = context.getProtoActionConf().get(OozieClient.USER_NAME);
        if (allowSshUserAtHost) {
            if (!host.contains("@")) {
                host = oozieUser + "@" + host;
            }
        }
        else {
            if (host.contains("@")) {
                if (!host.toLowerCase().startsWith(oozieUser + "@")) {
                    throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, ERR_USER_MISMATCH,
                                                      XLog.format("user mismatch between oozie user [{0}] and ssh host [{1}]", oozieUser, host));
                }
            }
            else {
                host = oozieUser + "@" + host;
            }
        }
        return host;
    }

    public boolean isCompleted(String externalStatus) {
        return true;
    }

    /**
     * Truncate the string to max length.
     *
     * @param strBuffer
     * @return truncated string string
     */
    private String getTruncatedString(StringBuffer strBuffer) {

        if (strBuffer.length() <= maxLen) {
            return strBuffer.toString();
        }
        else {
            return strBuffer.substring(0, maxLen);
        }
    }

    /**
     * Drains the inputStream and errorStream of the Process being executed. The contents of the streams are stored if a
     * buffer is provided for the stream.
     *
     * @param p The Process instance.
     * @param inputBuffer The buffer into which STDOUT is to be read. Can be null if only draining is required.
     * @param errorBuffer The buffer into which STDERR is to be read. Can be null if only draining is required.
     * @param maxLength The maximum data length to be stored in these buffers. This is an indicative value, and the
     * store content may exceed this length.
     * @return the exit value of the process.
     * @throws IOException
     */
    private int drainBuffers(Process p, StringBuffer inputBuffer, StringBuffer errorBuffer, int maxLength)
            throws IOException {
        int exitValue = -1;
        BufferedReader ir = new BufferedReader(new InputStreamReader(p.getInputStream()));
        BufferedReader er = new BufferedReader(new InputStreamReader(p.getErrorStream()));

        int inBytesRead = 0;
        int errBytesRead = 0;

        boolean processEnded = false;

        try {
            while (!processEnded) {
                try {
                    exitValue = p.exitValue();
                    processEnded = true;
                }
                catch (IllegalThreadStateException ex) {
                    // Continue to drain.
                }

                inBytesRead += drainBuffer(ir, inputBuffer, maxLength, inBytesRead, processEnded);
                errBytesRead += drainBuffer(er, errorBuffer, maxLength, errBytesRead, processEnded);
            }
        }
        finally {
            ir.close();
            er.close();
        }
        return exitValue;
    }

    /**
     * Reads the contents of a stream and stores them into the provided buffer.
     *
     * @param br The stream to be read.
     * @param storageBuf The buffer into which the contents of the stream are to be stored.
     * @param maxLength The maximum number of bytes to be stored in the buffer. An indicative value and may be
     * exceeded.
     * @param bytesRead The number of bytes read from this stream to date.
     * @param readAll If true, the stream is drained while their is data available in it. Otherwise, only a single chunk
     * of data is read, irrespective of how much is available.
     * @return
     * @throws IOException
     */
    private int drainBuffer(BufferedReader br, StringBuffer storageBuf, int maxLength, int bytesRead, boolean readAll)
            throws IOException {
        int bReadSession = 0;
        if (br.ready()) {
            char[] buf = new char[1024];
            do {
                int bReadCurrent = br.read(buf, 0, 1024);
                if (storageBuf != null && bytesRead < maxLength) {
                    storageBuf.append(buf, 0, bReadCurrent);
                }
                bReadSession += bReadCurrent;
            } while (br.ready() && readAll);
        }
        return bReadSession;
    }

    /**
     * Returns the first line from a StringBuffer, recognized by the new line character \n.
     *
     * @param buffer The StringBuffer from which the first line is required.
     * @return The first line of the buffer.
     */
    private String getFirstLine(StringBuffer buffer) {
        int newLineIndex = 0;
        newLineIndex = buffer.indexOf("\n");
        if (newLineIndex == -1) {
            return buffer.toString();
        }
        else {
            return buffer.substring(0, newLineIndex);
        }
    }
}
TOP

Related Classes of org.apache.oozie.action.ssh.SshActionExecutor

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.