/*******************************************************************************
*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() {}
}