From c63e5322713e3ba06ca591c712515bbc22d8f0e1 Mon Sep 17 00:00:00 2001 From: "adrian.f.cole" Date: Tue, 21 Jul 2009 16:50:58 +0000 Subject: [PATCH] Issue 79: improved session handling git-svn-id: http://jclouds.googlecode.com/svn/trunk@1825 3d8758e0-26b5-11de-8745-db77d3ebf521 --- .../java/org/jclouds/ssh/ExecResponse.java | 60 ++++++++++++++ .../java/org/jclouds/ssh/SshConnection.java | 23 ++++++ .../java/org/jclouds/ssh/SshException.java | 23 ++++++ .../jclouds/ssh/jsch/JschSshConnection.java | 80 ++++++++++++++----- .../ssh/jsch/JschSshConnectionLiveTest.java | 38 ++++++--- .../config/JschSshConnectionModuleTest.java | 23 ++++++ 6 files changed, 218 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/jclouds/ssh/ExecResponse.java b/core/src/main/java/org/jclouds/ssh/ExecResponse.java index bca47127c9..d5fb937fee 100644 --- a/core/src/main/java/org/jclouds/ssh/ExecResponse.java +++ b/core/src/main/java/org/jclouds/ssh/ExecResponse.java @@ -1,9 +1,33 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + */ package org.jclouds.ssh; /** * @author Adrian Cole */ public class ExecResponse { + private final String error; private final String output; @@ -20,4 +44,40 @@ public class ExecResponse { return output; } + @Override + public String toString() { + return "ExecResponse [error=" + error + ", output=" + output + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((error == null) ? 0 : error.hashCode()); + result = prime * result + ((output == null) ? 0 : output.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ExecResponse other = (ExecResponse) obj; + if (error == null) { + if (other.error != null) + return false; + } else if (!error.equals(other.error)) + return false; + if (output == null) { + if (other.output != null) + return false; + } else if (!output.equals(other.output)) + return false; + return true; + } + } \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/ssh/SshConnection.java b/core/src/main/java/org/jclouds/ssh/SshConnection.java index 67683014b4..6171f65208 100644 --- a/core/src/main/java/org/jclouds/ssh/SshConnection.java +++ b/core/src/main/java/org/jclouds/ssh/SshConnection.java @@ -1,3 +1,26 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + */ package org.jclouds.ssh; import java.io.InputStream; diff --git a/core/src/main/java/org/jclouds/ssh/SshException.java b/core/src/main/java/org/jclouds/ssh/SshException.java index 7972d10b4b..207950b34e 100644 --- a/core/src/main/java/org/jclouds/ssh/SshException.java +++ b/core/src/main/java/org/jclouds/ssh/SshException.java @@ -1,3 +1,26 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + */ package org.jclouds.ssh; /** diff --git a/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java b/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java index 951f8b4a85..3e04abb48a 100644 --- a/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java +++ b/extensions/ssh/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshConnection.java @@ -1,9 +1,33 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + */ package org.jclouds.ssh.jsch; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; @@ -11,6 +35,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; +import org.apache.commons.io.input.ProxyInputStream; import org.apache.commons.io.output.ByteArrayOutputStream; import org.jclouds.logging.Logger; import org.jclouds.ssh.ExecResponse; @@ -20,7 +45,6 @@ import org.jclouds.util.Utils; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; -import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; @@ -34,11 +58,27 @@ import com.jcraft.jsch.SftpException; */ public class JschSshConnection implements SshConnection { + private final class CloseFtpChannelOnCloseInputStream extends ProxyInputStream { + + private final ChannelSftp sftp; + + private CloseFtpChannelOnCloseInputStream(InputStream proxy, ChannelSftp sftp) { + super(proxy); + this.sftp = sftp; + } + + @Override + public void close() throws IOException { + super.close(); + if (sftp != null) + sftp.disconnect(); + } + } + private final InetAddress host; private final int port; private final String username; private final String password; - private ChannelSftp sftp; @Resource protected Logger logger = Logger.NULL; private Session session; @@ -54,10 +94,20 @@ public class JschSshConnection implements SshConnection { } public InputStream get(String path) { - checkConnected(); checkNotNull(path, "path"); + + checkConnected(); + logger.debug("%s@%s:%d: Opening sftp Channel.", username, host.getHostAddress(), port); + ChannelSftp sftp = null; try { - return sftp.get(path); + sftp = (ChannelSftp) session.openChannel("sftp"); + sftp.connect(); + } catch (JSchException e) { + throw new SshException(String.format("%s@%s:%d: Error connecting to sftp.", username, host + .getHostAddress(), port), e); + } + try { + return new CloseFtpChannelOnCloseInputStream(sftp.get(path), sftp); } catch (SftpException e) { throw new SshException(String.format("%s@%s:%d: Error getting path: %s", username, host .getHostAddress(), port, path), e); @@ -65,17 +115,15 @@ public class JschSshConnection implements SshConnection { } private void checkConnected() { - checkState(sftp != null && sftp.isConnected(), String.format("%s@%s:%d: SFTP not connected!", - username, host.getHostAddress(), port)); + checkState(session != null && session.isConnected(), String.format( + "%s@%s:%d: SFTP not connected!", username, host.getHostAddress(), port)); } @PostConstruct public void connect() { - if (sftp != null && sftp.isConnected()) - return; + disconnect(); JSch jsch = new JSch(); session = null; - Channel channel = null; try { session = jsch.getSession(username, host.getHostAddress(), port); } catch (JSchException e) { @@ -94,24 +142,16 @@ public class JschSshConnection implements SshConnection { host.getHostAddress(), port), e); } logger.debug("%s@%s:%d: Session connected.", username, host.getHostAddress(), port); - logger.debug("%s@%s:%d: Opening sftp Channel.", username, host.getHostAddress(), port); - try { - channel = session.openChannel("sftp"); - channel.connect(); - } catch (JSchException e) { - throw new SshException(String.format("%s@%s:%d: Error connecting to sftp.", username, host - .getHostAddress(), port), e); - } - sftp = (ChannelSftp) channel; } @PreDestroy public void disconnect() { - if (sftp != null && sftp.isConnected()) - sftp.quit(); + if (session != null && session.isConnected()) + session.disconnect(); } public ExecResponse exec(String command) { + checkConnected(); ChannelExec executor = null; try { try { diff --git a/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/JschSshConnectionLiveTest.java b/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/JschSshConnectionLiveTest.java index 0050661979..e66b7e75f6 100644 --- a/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/JschSshConnectionLiveTest.java +++ b/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/JschSshConnectionLiveTest.java @@ -1,3 +1,26 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + */ package org.jclouds.ssh.jsch; import static org.testng.Assert.assertEquals; @@ -12,7 +35,6 @@ import org.jclouds.ssh.ExecResponse; import org.jclouds.ssh.SshConnection; import org.jclouds.ssh.jsch.config.JschSshConnectionModule; import org.jclouds.util.Utils; -import org.testng.ITestContext; import org.testng.annotations.BeforeGroups; import org.testng.annotations.Test; @@ -31,18 +53,15 @@ public class JschSshConnectionLiveTest { protected static final String sshUser = System.getProperty("jclouds.test.ssh.username"); protected static final String sshPass = System.getProperty("jclouds.test.ssh.password"); - protected SshConnection connection; - @BeforeGroups(groups = { "live" }) - public void setupConnection(ITestContext context) throws NumberFormatException, - UnknownHostException { + public SshConnection setupConnection() throws NumberFormatException, UnknownHostException { int port = (sshPort != null) ? Integer.parseInt(sshPort) : 22; InetAddress host = (sshHost != null) ? InetAddress.getByName(sshHost) : InetAddress .getLocalHost(); if (sshUser == null || sshPass == null || sshUser.trim().equals("") || sshPass.trim().equals("")) { System.err.println("ssh credentials not present. Tests will be lame"); - connection = new SshConnection() { + return new SshConnection() { public void connect() { } @@ -72,19 +91,20 @@ public class JschSshConnectionLiveTest { } else { Injector i = Guice.createInjector(new JschSshConnectionModule()); SshConnection.Factory factory = i.getInstance(SshConnection.Factory.class); - connection = factory.create(host, port, sshUser, sshPass); + SshConnection connection = factory.create(host, port, sshUser, sshPass); connection.connect(); + return connection; } } public void testGetEtcPassword() throws IOException { - InputStream input = connection.get("/etc/passwd"); + InputStream input = setupConnection().get("/etc/passwd"); String contents = Utils.toStringAndClose(input); assert contents.indexOf("root") >= 0 : "no root in " + contents; } public void testExecHostname() throws IOException { - ExecResponse response = connection.exec("hostname"); + ExecResponse response = setupConnection().exec("hostname"); assertEquals(response.getError(), ""); assertEquals(response.getOutput().trim(), InetAddress.getLocalHost().getHostName()); } diff --git a/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/config/JschSshConnectionModuleTest.java b/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/config/JschSshConnectionModuleTest.java index 452125064d..9b79a02d40 100644 --- a/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/config/JschSshConnectionModuleTest.java +++ b/extensions/ssh/jsch/src/test/java/org/jclouds/ssh/jsch/config/JschSshConnectionModuleTest.java @@ -1,3 +1,26 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + */ package org.jclouds.ssh.jsch.config; import java.net.InetAddress;