com.eucalyptus.blockstorage.san.common.ShellSessionManager.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.blockstorage.san.common.ShellSessionManager.java

Source

/*******************************************************************************
 *Copyright (c) 2009  Eucalyptus Systems, Inc.
 * 
 *  This program 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, only version 3 of the License.
 * 
 * 
 *  This file 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *  Please contact Eucalyptus Systems, Inc., 130 Castilian
 *  Dr., Goleta, CA 93101 USA or visit <http://www.eucalyptus.com/licenses/>
 *  if you need additional information or have any questions.
 * 
 *  This file may incorporate work covered under the following copyright and
 *  permission notice:
 * 
 *    Software License Agreement (BSD License)
 * 
 *    Copyright (c) 2008, Regents of the University of California
 *    All rights reserved.
 * 
 *    Redistribution and use of this software in source and binary forms, with
 *    or without modification, are permitted provided that the following
 *    conditions are met:
 * 
 *      Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 * 
 *      Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * 
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 *    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 *    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 *    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF
 *    THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE
 *    LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS
 *    SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *    IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA
 *    BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN
 *    THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT
 *    OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR
 *    WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH
 *    ANY SUCH LICENSES OR RIGHTS.
 *******************************************************************************/
/*
 *
 * Author: Neil Soman neil@eucalyptus.com
 */

package com.eucalyptus.blockstorage.san.common;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import org.apache.log4j.Logger;

import com.eucalyptus.blockstorage.san.common.SessionManager.TaskRunner;
import com.eucalyptus.blockstorage.san.common.entities.SANInfo;
import com.eucalyptus.util.EucalyptusCloudException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

public class ShellSessionManager implements SessionManager, TaskRunner {
    private BufferedWriter writer;
    private BufferedReader reader;
    private Channel channel;
    private static Logger LOG = Logger.getLogger(ShellSessionManager.class);
    private static long promptTimeout = Long
            .parseLong(System.getProperty("com.eucalyptus.blockstorage.shell.promptTimeout", "15000"));
    private static boolean logTiming = Boolean
            .valueOf(System.getProperty("com.eucalyptus.blockstorage.shell.logTiming"));

    public ShellSessionManager() {
    }

    private void readToPrompt(long timeout) throws IOException, InterruptedException {
        String text = "";
        char[] buffer = new char[512];
        long until = System.currentTimeMillis() + timeout;
        while (System.currentTimeMillis() < until) {
            while (reader.ready()) {
                int read = reader.read(buffer);
                text += new String(buffer, 0, read);
            }
            if (!text.isEmpty() && !text.endsWith("\r") && !text.endsWith("\n"))
                return;
            Thread.sleep(50);
        }
        LOG.warn("Timed out reading prompt");
    }

    public synchronized void connect() throws EucalyptusCloudException {
        try {
            JSch jsch = new JSch();
            Session session;
            SANInfo sanInfo = SANInfo.getStorageInfo();
            session = jsch.getSession(sanInfo.getSanUser(), sanInfo.getSanHost());
            session.setConfig("StrictHostKeyChecking", "no");
            session.setPassword(sanInfo.getSanPassword());
            session.connect();
            channel = session.openChannel("shell");
            PipedOutputStream outStream = new PipedOutputStream();
            channel.setInputStream(new PipedInputStream(outStream));
            PipedInputStream inStream = new PipedInputStream();
            channel.setOutputStream(new PipedOutputStream(inStream));
            channel.connect();
            writer = new BufferedWriter(new OutputStreamWriter(outStream, "utf-8"));
            reader = new BufferedReader(new InputStreamReader(inStream, "utf-8"));
            readToPrompt(promptTimeout);
        } catch (JSchException | IOException | InterruptedException e) {
            throw new EucalyptusCloudException(e);
        }
    }

    /**
     *
     * Caller must be synchronized:
     *
     * ShellSessionManager manager = ... synchronized( manager ) { try ( final TaskRunner runner = manager.getTaskRunner( ) ) { ... } }
     *
     */
    public TaskRunner getTaskRunner(String description) {
        return getTaskRunner(description, null);
    }

    /**
     *
     * Caller must be synchronized
     */
    private TaskRunner getTaskRunner(final String description, @Nullable final Map<String, Long> timings) {
        return new TaskRunner() {
            private boolean connected = false;
            private int taskNumber = 1;
            private Map<String, Long> timingMap = timings == null ? Maps.<String, Long>newLinkedHashMap() : timings;

            private void init() {
                try {
                    connect();
                    connected = true;
                } catch (EucalyptusCloudException e) {
                    LOG.error(e);
                }
            }

            @Override
            public String runTask(final AbstractSANTask task) throws InterruptedException {
                String returnValue = "";
                if (!connected) {
                    timingMap.put("begin", System.currentTimeMillis());
                    init();
                    timingMap.put("connect", System.currentTimeMillis());
                }
                if (!connected)
                    return returnValue;
                timingMap.put("pre-task-" + taskNumber, System.currentTimeMillis());
                try {
                    writer.write("" + task.getCommand() + task.getEOFCommand());
                    writer.flush();
                    for (String line = null; (line = reader.readLine()) != null;) {
                        line = line + "\r";
                        if (line.contains("" + task.getEOFCommand()))
                            break;
                        returnValue += line;
                    }
                } catch (IOException e) {
                    LOG.error(e, e);
                } finally {
                    timingMap.put("task-" + taskNumber, System.currentTimeMillis());
                    taskNumber++;
                }
                return returnValue;
            }

            @Override
            public void close() {
                timingMap.put("pre-close", System.currentTimeMillis());
                try {
                    if (writer != null)
                        try {
                            writer.write("logout\r");
                            writer.flush();
                        } catch (Exception e) {
                            LOG.warn("Error logging out of session", e);
                        }

                    if (reader != null) {
                        Closeables.close(reader, true);
                        reader = null;
                    }

                    if (writer != null) {
                        Closeables.close(writer, true);
                        writer = null;
                    }

                    // Tear it down. Do not persist session.
                    // Doing so causes more issues than it is worth.
                    // EQL serializes anyway and the overhead is
                    // minor.
                    if (channel != null) {
                        channel.getSession().disconnect();
                        channel.disconnect();
                        channel = null;
                    }

                } catch (JSchException | IOException e) {
                    LOG.error(e, e);
                } finally {
                    timingMap.put("close", System.currentTimeMillis());
                    if (logTiming)
                        dumpTiming(timingMap, description);
                }
            }
        };
    }

    public String runTask(final AbstractSANTask task) throws InterruptedException {
        String returnValue = "";
        final Map<String, Long> timingMap = Maps.newLinkedHashMap();
        timingMap.put("start", System.currentTimeMillis());
        synchronized (this) {
            try (final TaskRunner runner = getTaskRunner(String.valueOf(task.getCommand()))) {
                returnValue = runner.runTask(task);
            }
        }
        return returnValue;
    }

    private void dumpTiming(final Map<String, Long> timings, final String command) {
        final List<String> timingInfo = Lists.newArrayList();
        Long firstTime = 0l;
        Long lastTime = 0l;
        for (final Map.Entry<String, Long> timingEntry : timings.entrySet()) {
            if (lastTime != 0) {
                timingInfo.add(timingEntry.getKey() + ": " + (timingEntry.getValue() - lastTime) + "ms");
            } else {
                firstTime = timingEntry.getValue();
            }
            lastTime = timingEntry.getValue();
        }
        LOG.debug("Command '" + (command.replace('\r', ';')) + "', took " + (lastTime - firstTime) + "ms "
                + timingInfo);
    }

    public void stop() throws EucalyptusCloudException {
        // Do not disconnect the channel while operations are in flight
    }

    @Override
    public void close() {
    }
}