Projects >> legacy-jclouds >>e1f0cdcfa58eb334817d86c82508e44558ae0e07

Chunk
Conflicting content
<<<<<<< HEAD
/**
 *
 * Copyright (C) 2011 Cloud Conscious, LLC. 
 *
 * ====================================================================
 * 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.
 * ====================================================================
 */
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 static com.google.common.base.Predicates.instanceOf;
import static com.google.common.base.Predicates.or;
import static com.google.common.base.Throwables.getCausalChain;
import static com.google.common.base.Throwables.getRootCause;
import static com.google.common.collect.Iterables.any;

import java.io.IOException;

import java.io.InputStream;
import java.net.ConnectException;
import java.util.Arrays;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.inject.Named;

import org.apache.commons.io.input.ProxyInputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.logging.Logger;
import org.jclouds.net.IPSocket;
import org.jclouds.ssh.SshClient;
import org.jclouds.ssh.SshException;
import org.jclouds.util.Strings2;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.io.Closeables;
import com.google.inject.Inject;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;

/**
 * This class needs refactoring. It is not thread safe.
 * 
 * @author Adrian Cole
 */
public class JschSshClient implements SshClient {

   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 String host;
   private final int port;
   private final String username;
   private final String password;

   @Inject(optional = true)
   @Named("jclouds.ssh.max_retries")
   @VisibleForTesting
   int sshRetries = 5;

   @Inject(optional = true)
   @Named("jclouds.ssh.retryable_messages")
   @VisibleForTesting
   String retryableMessages = "invalid data,End of IO Stream Read,Connection reset,connection is closed by foreign host,socket is not established";

   @Inject(optional = true)
   @Named("jclouds.ssh.retry_predicate")
   private Predicate retryPredicate = or(instanceOf(ConnectException.class), instanceOf(IOException.class));

   @Resource
   @Named("jclouds.ssh")
   protected Logger logger = Logger.NULL;

   private Session session;
   private final byte[] privateKey;
   final byte[] emptyPassPhrase = new byte[0];
   private final int timeout;
   private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;

   public JschSshClient(BackoffLimitedRetryHandler backoffLimitedRetryHandler, IPSocket socket, int timeout,
            String username, String password, byte[] privateKey) {
      this.host = checkNotNull(socket, "socket").getAddress();
      checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
      checkArgument(password != null || privateKey != null, "you must specify a password or a key");
      this.port = socket.getPort();
      this.username = checkNotNull(username, "username");
      this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler");
      this.timeout = timeout;
      this.password = password;
      this.privateKey = privateKey;
   }
   public Payload get(String path) {
      checkNotNull(path, "path");

      ChannelSftp sftp = getSftp();
      try {
         return Payloads.newInputStreamPayload(new CloseFtpChannelOnCloseInputStream(sftp.get(path), sftp));
      } catch (SftpException e) {
         throw new SshException(String.format("%s@%s:%d: Error getting path: %s", username, host, port, path), e);
      }
   }

   @Override
   public void put(String path, Payload contents) {
      checkNotNull(path, "path");
      checkNotNull(contents, "contents");
      ChannelSftp sftp = getSftp();
      try {
         sftp.put(contents.getInput(), path);
      } catch (SftpException e) {
         throw new SshException(String.format("%s@%s:%d: Error putting path: %s", username, host, port, path), e);
      } finally {
         Closeables.closeQuietly(contents);
      }
   }

   @Override
   public void put(String path, String contents) {
      put(path, Payloads.newStringPayload(checkNotNull(contents, "contents")));
   }

   private ChannelSftp getSftp() {
      checkConnected();
      logger.debug("%s@%s:%d: Opening sftp Channel.", username, host, port);
      ChannelSftp sftp = null;
      try {
         sftp = (ChannelSftp) session.openChannel("sftp");
         sftp.connect();
      } catch (JSchException e) {
         throw new SshException(String.format("%s@%s:%d: Error connecting to sftp.", username, host, port), e);
      }
      return sftp;
   }

   private void checkConnected() {
      checkState(session != null && session.isConnected(), String.format("%s@%s:%d: SFTP not connected!", username,
               host, port));
   }

   @PostConstruct
   public void connect() {
      disconnect();
      Exception e = null;
      RETRY_LOOP: for (int i = 0; i < sshRetries; i++) {
         try {
            newSession();
            e = null;
            break RETRY_LOOP;
         } catch (Exception from) {
            e = from;
            disconnect();

            if (i == sshRetries)
               throw propagate(from);

            if (shouldRetry(from)) {
               backoffForAttempt(i + 1, String.format("%s@%s:%d: connection error: %s", username, host, port, from
                        .getMessage()));
               continue;
            }

            throw propagate(from);
         }
      }
      if (e != null)
         throw propagate(e);
   }

   @VisibleForTesting
   boolean shouldRetry(Exception from) {
      final String rootMessage = getRootCause(from).getMessage();
      return any(getCausalChain(from), retryPredicate)
               || Iterables.any(Splitter.on(",").split(retryableMessages), new Predicate() {

                  @Override
                  public boolean apply(String input) {
                     return rootMessage.indexOf(input) != -1;
                  }

               });
   }

   private void backoffForAttempt(int retryAttempt, String message) {
      backoffLimitedRetryHandler.imposeBackoffExponentialDelay(200L, 2, retryAttempt, sshRetries, message);
   }

   private void newSession() throws JSchException {
      JSch jsch = new JSch();
      session = null;
      try {
         session = jsch.getSession(username, host, port);
         if (timeout != 0)
            session.setTimeout(timeout);
         logger.debug("%s@%s:%d: Session created.", username, host, port);
         if (password != null) {
            session.setPassword(password);
         } else {
            // jsch wipes out your private key
            jsch.addIdentity(username, Arrays.copyOf(privateKey, privateKey.length), null, emptyPassPhrase);
         }
      } catch (JSchException e) {
         throw new SshException(String.format("%s@%s:%d: Error creating session.", username, host, port), e);
      }
      java.util.Properties config = new java.util.Properties();
      config.put("StrictHostKeyChecking", "no");
      session.setConfig(config);
      session.connect();
      logger.debug("%s@%s:%d: Session connected.", username, host, port);
   }

   private SshException propagate(Exception e) {
      throw new SshException(String.format("%s@%s:%d: Error connecting to session.", username, host, port), e);
   }

   @PreDestroy
   public void disconnect() {
      if (session != null && session.isConnected()) {
         session.disconnect();
         session = null;
      }
   }

   public ExecResponse exec(String command) {
      checkConnected();

      ChannelExec executor = null;
      ByteArrayOutputStream error = null;

      int j = 0;
      do {
         try {
            executor = (ChannelExec) session.openChannel("exec");
         } catch (JSchException e) {
            // unrecoverable fail because ssh session closed
            throw new SshException(String.format("%s@%s:%d: Error connecting to exec.", username, host, port), e);
         }

         error = new ByteArrayOutputStream();
         executor.setPty(true);
         executor.setCommand(command);
         executor.setErrStream(error);

         try {
             executor.connect();
         } catch (JSchException e) {
             // performing a retry if connect has thrown an exception
             executor.disconnect();
             backoffForAttempt(++j, String.format("%s@%s:%d: Failed to connect ChannelExec", username, host, port));
         }
      } while (j < this.sshRetries && !executor.isConnected());

      if (!executor.isConnected())
         throw new SshException(String.format("%s@%s:%d: Failed to connect ChannelExec executing %s",
                  username, host, port, command));

      try {
         String outputString = Strings2.toStringAndClose(executor.getInputStream());
         String errorString = error.toString();
         int errorStatus = executor.getExitStatus();
         int i = 0;
         while ((errorStatus = executor.getExitStatus()) == -1 && i < this.sshRetries)
            backoffForAttempt(++i, String.format("%s@%s:%d: bad status: -1", username, host, port));
         if (errorStatus == -1)
            throw new SshException(String.format("%s@%s:%d: received exit status %d executing %s", username, host,
                     port, executor.getExitStatus(), command));
         return new ExecResponse(outputString, errorString, errorStatus);
      } catch (Exception e) {
         throw new SshException(String
                  .format("%s@%s:%d: Error executing command: %s", username, host, port, command), e);
      }
      finally {
         executor.disconnect();
      }
   }

   @Override
   public String getHostAddress() {
      return this.host;
   }

   @Override
   public String getUsername() {
      return this.username;
   }

}
=======
/**
 *

 * Copyright (C) 2011 Cloud Conscious, LLC. 
 *
 * ====================================================================
 * 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.
 * ====================================================================
 */
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 static com.google.common.base.Predicates.instanceOf;
import static com.google.common.base.Predicates.or;
import static com.google.common.base.Throwables.getCausalChain;
import static com.google.common.base.Throwables.getRootCause;
import static com.google.common.collect.Iterables.any;

import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.Arrays;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.inject.Named;

import org.apache.commons.io.input.ProxyInputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.logging.Logger;
import org.jclouds.net.IPSocket;
import org.jclouds.ssh.SshClient;
import org.jclouds.ssh.SshException;
import org.jclouds.util.Strings2;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.io.Closeables;
import com.google.inject.Inject;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

/**
 * This class needs refactoring. It is not thread safe.
 * 
 * @author Adrian Cole
 */
public class JschSshClient implements SshClient {

   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 String host;
   private final int port;
   private final String username;
   private final String password;

   @Inject(optional = true)
   @Named("jclouds.ssh.max_retries")
   @VisibleForTesting
   int sshRetries = 5;

   @Inject(optional = true)
   @Named("jclouds.ssh.retryable_messages")
   @VisibleForTesting
   String retryableMessages = "failed to send channel request,channel is not opened,invalid data,End of IO Stream Read,Connection reset,connection is closed by foreign host,socket is not established";
   @Inject(optional = true)
   @Named("jclouds.ssh.retry_predicate")
   private Predicate retryPredicate = or(instanceOf(ConnectException.class), instanceOf(IOException.class));

   @Resource
   @Named("jclouds.ssh")
   protected Logger logger = Logger.NULL;

   private Session session;
   private final byte[] privateKey;
   final byte[] emptyPassPhrase = new byte[0];
   private final int timeout;
   private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;

   public JschSshClient(BackoffLimitedRetryHandler backoffLimitedRetryHandler, IPSocket socket, int timeout,
            String username, String password, byte[] privateKey) {
      this.host = checkNotNull(socket, "socket").getAddress();
      checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
      checkArgument(password != null || privateKey != null, "you must specify a password or a key");
      this.port = socket.getPort();
      this.username = checkNotNull(username, "username");
      this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler");
      this.timeout = timeout;
      this.password = password;
      this.privateKey = privateKey;
   }

   @Override
   public void put(String path, String contents) {
      put(path, Payloads.newStringPayload(checkNotNull(contents, "contents")));
   }

   private void checkConnected() {
      checkState(session != null && session.isConnected(), String.format("(%s) Session not connected!", toString()));
   }

   public static interface Connection {
      void clear();

      T create() throws Exception;
   }

   Connection sessionConnection = new Connection() {

      @Override
      public void clear() {
         if (session != null && session.isConnected()) {
            session.disconnect();
            session = null;
         }
      }

      @Override
      public Session create() throws Exception {
         JSch jsch = new JSch();
         session = jsch.getSession(username, host, port);
         if (timeout != 0)
            session.setTimeout(timeout);
         if (password != null) {
            session.setPassword(password);
         } else {
            // jsch wipes out your private key
            jsch.addIdentity(username, Arrays.copyOf(privateKey, privateKey.length), null, emptyPassPhrase);
         }
         java.util.Properties config = new java.util.Properties();
         config.put("StrictHostKeyChecking", "no");
         session.setConfig(config);
         session.connect();
         return session;
      }

      @Override
      public String toString() {

         return String.format("Session(%s)", JschSshClient.this.toString());
      }
   };

   protected > T acquire(C connection) {
      connection.clear();
      Exception e = null;
      String errorMessage = String.format("(%s) error acquiring %s", toString(), connection);
      for (int i = 0; i < sshRetries; i++) {
         try {
            logger.debug(">> (%s) acquiring %s", toString(), connection);
            T returnVal = connection.create();
            logger.debug("<< (%s) acquired %s", toString(), returnVal);
            return returnVal;
         } catch (Exception from) {
            e = from;
            connection.clear();

            if (i == sshRetries)
               throw propagate(from, errorMessage);

            if (shouldRetry(from)) {
               logger.warn("<< " + errorMessage + ": " + from.getMessage());
               backoffForAttempt(i + 1, errorMessage + ": " + from.getMessage());
               continue;
            }
            throw propagate(from, errorMessage);
         }
      }
      if (e != null)
         throw propagate(e, errorMessage);
      return null;
   }

   @PostConstruct
   public void connect() {
      acquire(sessionConnection);
   }

   Connection sftpConnection = new Connection() {

      private ChannelSftp sftp;

      @Override
      public void clear() {
         if (sftp != null)
            sftp.disconnect();
      }

      @Override
      public ChannelSftp create() throws Exception {
         checkConnected();
         sftp = (ChannelSftp) session.openChannel("sftp");
         sftp.connect();
         return sftp;
      }

      @Override
      public String toString() {
         return "ChannelSftp(" + JschSshClient.this.toString() + ")";
      }
   };

   class GetConnection implements Connection {
      private final String path;
      private ChannelSftp sftp;

      GetConnection(String path) {
         this.path = checkNotNull(path, "path");
      }

      @Override
      public void clear() {
         if (sftp != null)
            sftp.disconnect();
      }

      @Override
      public Payload create() throws Exception {
         sftp = acquire(sftpConnection);
         return Payloads.newInputStreamPayload(new CloseFtpChannelOnCloseInputStream(sftp.get(path), sftp));
      }

      @Override
      public String toString() {
         return "Payload(" + JschSshClient.this.toString() + ")[" + path + "]";
      }
   };

   public Payload get(String path) {
      return acquire(new GetConnection(path));
   }

   class PutConnection implements Connection {
      private final String path;
      private final Payload contents;
      private ChannelSftp sftp;

      PutConnection(String path, Payload contents) {
         this.path = checkNotNull(path, "path");
         this.contents = checkNotNull(contents, "contents");
      }

      @Override
      public void clear() {
         if (sftp != null)
            sftp.disconnect();
      }
      @Override
      public Void create() throws Exception {
         sftp = acquire(sftpConnection);
         try {
            sftp.put(contents.getInput(), path);
         } finally {
            Closeables.closeQuietly(contents);
            clear();
         }
         return null;
      }

      @Override
      public String toString() {
         return "Put(" + JschSshClient.this.toString() + ")[" + path + "]";
      }
   };

   @Override
   public void put(String path, Payload contents) {
      acquire(new PutConnection(path, contents));
   }

   @VisibleForTesting
   boolean shouldRetry(Exception from) {
   public ExecResponse exec(String command) {
      final String rootMessage = getRootCause(from).getMessage();
      return any(getCausalChain(from), retryPredicate)
               || Iterables.any(Splitter.on(",").split(retryableMessages), new Predicate() {

                  @Override
                  public boolean apply(String input) {
                     return rootMessage.indexOf(input) != -1;
                  }

               });
   }

   private void backoffForAttempt(int retryAttempt, String message) {
      backoffLimitedRetryHandler.imposeBackoffExponentialDelay(200L, 2, retryAttempt, sshRetries, message);
   }

   private SshException propagate(Exception e, String message) {
      message += ": " + e.getMessage();
      logger.error(e, "<< " + message);
      throw new SshException(message, e);
   }

   @Override
   public String toString() {
      return String.format("%s@%s:%d", username, host, port);
   }

   @PreDestroy
   public void disconnect() {
      sessionConnection.clear();
   }

   Connection execConnection = new Connection() {

      private ChannelExec executor = null;

      @Override
      public void clear() {
         if (executor != null)
            executor.disconnect();
      }

      @Override
      public ChannelExec create() throws Exception {
         checkConnected();
         executor = (ChannelExec) session.openChannel("exec");
         executor.setPty(true);
         return executor;
      }

      @Override
      public String toString() {
         return "ChannelExec(" + JschSshClient.this.toString() + ")";
      }

   };

   class ExecConnection implements Connection {
      private final String command;
      private ChannelExec executor;

      ExecConnection(String command) {
         this.command = checkNotNull(command, "command");
      }

      @Override
      public void clear() {
         if (executor != null)
            executor.disconnect();
      }

      @Override
      public ExecResponse create() throws Exception {
         executor = acquire(execConnection);
         executor.setCommand(command);
         ByteArrayOutputStream error = new ByteArrayOutputStream();
         executor.setErrStream(error);
         try {
            executor.connect();
            String outputString = Strings2.toStringAndClose(executor.getInputStream());
            String errorString = error.toString();
            int errorStatus = executor.getExitStatus();
            int i = 0;
            String message = String.format("bad status -1 %s", toString());
            while ((errorStatus = executor.getExitStatus()) == -1 && i < JschSshClient.this.sshRetries) {
               logger.warn("<< " + message);
               backoffForAttempt(++i, message);
            }
            if (errorStatus == -1)
               throw new SshException(message);
            return new ExecResponse(outputString, errorString, errorStatus);
         } finally {
            if (executor != null)
               executor.disconnect();
         }
      }

      @Override
      public String toString() {
         return "ExecResponse(" + JschSshClient.this.toString() + ")[" + command + "]";
      }

   };

      return acquire(new ExecConnection(command));
   }

   @Override
   public String getHostAddress() {
      return this.host;
   }

   @Override
   public String getUsername() {
      return this.username;
   }

}
>>>>>>> 6d187ed9baaad1e00dbe65b36ea2989c951a5a28
Solution content
/**
 *

 * Copyright (C) 2011 Cloud Conscious, LLC. 
 *
 * ====================================================================
 * 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.
 * ====================================================================
 */
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 static com.google.common.base.Predicates.instanceOf;
import static com.google.common.base.Predicates.or;
import static com.google.common.base.Throwables.getCausalChain;
import static com.google.common.base.Throwables.getRootCause;
import static com.google.common.collect.Iterables.any;

import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.Arrays;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.inject.Named;

import org.apache.commons.io.input.ProxyInputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.logging.Logger;
import org.jclouds.net.IPSocket;
import org.jclouds.ssh.SshClient;
import org.jclouds.ssh.SshException;
import org.jclouds.util.Strings2;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.io.Closeables;
import com.google.inject.Inject;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

/**
 * This class needs refactoring. It is not thread safe.
 * 
 * @author Adrian Cole
 */
public class JschSshClient implements SshClient {

   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 String host;
   private final int port;
   private final String username;
   private final String password;

   @Inject(optional = true)
   @Named("jclouds.ssh.max_retries")
   @VisibleForTesting
   int sshRetries = 5;

   @Inject(optional = true)
   @Named("jclouds.ssh.retryable_messages")
   @VisibleForTesting
   String retryableMessages = "failed to send channel request,channel is not opened,invalid data,End of IO Stream Read,Connection reset,connection is closed by foreign host,socket is not established";
   @Inject(optional = true)
   @Named("jclouds.ssh.retry_predicate")
   private Predicate retryPredicate = or(instanceOf(ConnectException.class), instanceOf(IOException.class));

   @Resource
   @Named("jclouds.ssh")
   protected Logger logger = Logger.NULL;

   private Session session;
   private final byte[] privateKey;
   final byte[] emptyPassPhrase = new byte[0];
   private final int timeout;
   private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;

   public JschSshClient(BackoffLimitedRetryHandler backoffLimitedRetryHandler, IPSocket socket, int timeout,
            String username, String password, byte[] privateKey) {
      this.host = checkNotNull(socket, "socket").getAddress();
      checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
      checkArgument(password != null || privateKey != null, "you must specify a password or a key");
      this.port = socket.getPort();
      this.username = checkNotNull(username, "username");
      this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler");
      this.timeout = timeout;
      this.password = password;
      this.privateKey = privateKey;
   }

   @Override
   public void put(String path, String contents) {
      put(path, Payloads.newStringPayload(checkNotNull(contents, "contents")));
   }

   private void checkConnected() {
      checkState(session != null && session.isConnected(), String.format("(%s) Session not connected!", toString()));
   }

   public static interface Connection {
      void clear();

      T create() throws Exception;
   }

   Connection sessionConnection = new Connection() {

      @Override
      public void clear() {
         if (session != null && session.isConnected()) {
            session.disconnect();
            session = null;
         }
      }

      @Override
      public Session create() throws Exception {
         JSch jsch = new JSch();
         session = jsch.getSession(username, host, port);
         if (timeout != 0)
            session.setTimeout(timeout);
         if (password != null) {
            session.setPassword(password);
         } else {
            // jsch wipes out your private key
            jsch.addIdentity(username, Arrays.copyOf(privateKey, privateKey.length), null, emptyPassPhrase);
         }
         java.util.Properties config = new java.util.Properties();
         config.put("StrictHostKeyChecking", "no");
         session.setConfig(config);
         session.connect();
         return session;
      }

      @Override
      public String toString() {
         return String.format("Session(%s)", JschSshClient.this.toString());
      }
   };

   protected > T acquire(C connection) {
      connection.clear();
      Exception e = null;
      String errorMessage = String.format("(%s) error acquiring %s", toString(), connection);
      for (int i = 0; i < sshRetries; i++) {
         try {
            logger.debug(">> (%s) acquiring %s", toString(), connection);
            T returnVal = connection.create();
            logger.debug("<< (%s) acquired %s", toString(), returnVal);
            return returnVal;
         } catch (Exception from) {
            e = from;
            connection.clear();

            if (i == sshRetries)
               throw propagate(from, errorMessage);

            if (shouldRetry(from)) {
               logger.warn("<< " + errorMessage + ": " + from.getMessage());
               backoffForAttempt(i + 1, errorMessage + ": " + from.getMessage());
               continue;
            }
            throw propagate(from, errorMessage);
         }
      }
      if (e != null)
         throw propagate(e, errorMessage);
      return null;
   }

   @PostConstruct
   public void connect() {
      acquire(sessionConnection);
   }

   Connection sftpConnection = new Connection() {

      private ChannelSftp sftp;

      @Override
      public void clear() {
         if (sftp != null)
            sftp.disconnect();
      }

      @Override
      public ChannelSftp create() throws Exception {
         checkConnected();
         sftp = (ChannelSftp) session.openChannel("sftp");
         sftp.connect();
         return sftp;
      }

      @Override
      public String toString() {
         return "ChannelSftp(" + JschSshClient.this.toString() + ")";
      }
   };

   class GetConnection implements Connection {
      private final String path;
      private ChannelSftp sftp;

      GetConnection(String path) {
         this.path = checkNotNull(path, "path");
      }

      @Override
      public void clear() {
         if (sftp != null)
            sftp.disconnect();
      }

      @Override
      public Payload create() throws Exception {
         sftp = acquire(sftpConnection);
         return Payloads.newInputStreamPayload(new CloseFtpChannelOnCloseInputStream(sftp.get(path), sftp));
      }

      @Override
      public String toString() {
         return "Payload(" + JschSshClient.this.toString() + ")[" + path + "]";
      }
   };

   public Payload get(String path) {
      return acquire(new GetConnection(path));
   }

   class PutConnection implements Connection {
      private final String path;
      private final Payload contents;
      private ChannelSftp sftp;

      PutConnection(String path, Payload contents) {
         this.path = checkNotNull(path, "path");
         this.contents = checkNotNull(contents, "contents");
      }

      @Override
      public void clear() {
         if (sftp != null)
            sftp.disconnect();
      }

      @Override
      public Void create() throws Exception {
         sftp = acquire(sftpConnection);
         try {
            sftp.put(contents.getInput(), path);
         } finally {
            Closeables.closeQuietly(contents);
            clear();
         }
         return null;
      }

      @Override
      public String toString() {
         return "Put(" + JschSshClient.this.toString() + ")[" + path + "]";
      }
   };

   @Override
   public void put(String path, Payload contents) {
      acquire(new PutConnection(path, contents));
   }

   @VisibleForTesting
   boolean shouldRetry(Exception from) {
   public ExecResponse exec(String command) {
      final String rootMessage = getRootCause(from).getMessage();
      return any(getCausalChain(from), retryPredicate)
               || Iterables.any(Splitter.on(",").split(retryableMessages), new Predicate() {

                  @Override
                  public boolean apply(String input) {
                     return rootMessage.indexOf(input) != -1;
                  }

               });
   }

   private void backoffForAttempt(int retryAttempt, String message) {
      backoffLimitedRetryHandler.imposeBackoffExponentialDelay(200L, 2, retryAttempt, sshRetries, message);
   }

   private SshException propagate(Exception e, String message) {
      message += ": " + e.getMessage();
      logger.error(e, "<< " + message);
      throw new SshException(message, e);
   }

   @Override
   public String toString() {
      return String.format("%s@%s:%d", username, host, port);
   }

   @PreDestroy
   public void disconnect() {
      sessionConnection.clear();
   }

   Connection execConnection = new Connection() {

      private ChannelExec executor = null;

      @Override
      public void clear() {
         if (executor != null)
            executor.disconnect();
      }

      @Override
      public ChannelExec create() throws Exception {
         checkConnected();
         executor = (ChannelExec) session.openChannel("exec");
         executor.setPty(true);
         return executor;
      }

      @Override
      public String toString() {
         return "ChannelExec(" + JschSshClient.this.toString() + ")";
      }

   };

   class ExecConnection implements Connection {
      private final String command;
      private ChannelExec executor;

      ExecConnection(String command) {
         this.command = checkNotNull(command, "command");
      }

      @Override
      public void clear() {
         if (executor != null)
            executor.disconnect();
      }

      @Override
      public ExecResponse create() throws Exception {
         executor = acquire(execConnection);
         executor.setCommand(command);
         ByteArrayOutputStream error = new ByteArrayOutputStream();
         executor.setErrStream(error);
         try {
            executor.connect();
            String outputString = Strings2.toStringAndClose(executor.getInputStream());
            String errorString = error.toString();
            int errorStatus = executor.getExitStatus();
            int i = 0;
            String message = String.format("bad status -1 %s", toString());
            while ((errorStatus = executor.getExitStatus()) == -1 && i < JschSshClient.this.sshRetries) {
               logger.warn("<< " + message);
               backoffForAttempt(++i, message);
            }
            if (errorStatus == -1)
               throw new SshException(message);
            return new ExecResponse(outputString, errorString, errorStatus);
         } finally {
            if (executor != null)
               executor.disconnect();
         }
      }

      @Override
      public String toString() {
         return "ExecResponse(" + JschSshClient.this.toString() + ")[" + command + "]";
      }

   };

      return acquire(new ExecConnection(command));
   }

   @Override
   public String getHostAddress() {
      return this.host;
   }

   @Override
   public String getUsername() {
      return this.username;
   }

}
File
JschSshClient.java
Developer's decision
Version 2
Kind of conflict
Annotation
Attribute
Class declaration
Comment
Import
Method declaration
Method invocation
Package declaration