2020package com .cloud .utils .ssh ;
2121
2222import java .io .File ;
23+ import java .io .IOException ;
2324import java .io .InputStream ;
2425
2526import org .apache .log4j .Logger ;
2627
2728import com .trilead .ssh2 .ChannelCondition ;
28-
29+ import com .trilead .ssh2 .Connection ;
30+ import com .trilead .ssh2 .Session ;
2931import com .cloud .utils .Pair ;
3032
3133public class SshHelper {
3234 private static final int DEFAULT_CONNECT_TIMEOUT = 180000 ;
3335 private static final int DEFAULT_KEX_TIMEOUT = 60000 ;
3436
37+ /**
38+ * Waiting time to check if the SSH session was successfully opened. This value (of 1000
39+ * milliseconds) represents one (1) second.
40+ */
41+ private static final long WAITING_OPEN_SSH_SESSION = 1000 ;
42+
3543 private static final Logger s_logger = Logger .getLogger (SshHelper .class );
3644
3745 public static Pair <Boolean , String > sshExecute (String host , int port , String user , File pemKeyFile , String password , String command ) throws Exception {
@@ -40,19 +48,19 @@ public static Pair<Boolean, String> sshExecute(String host, int port, String use
4048 }
4149
4250 public static void scpTo (String host , int port , String user , File pemKeyFile , String password , String remoteTargetDirectory , String localFile , String fileMode )
43- throws Exception {
51+ throws Exception {
4452
4553 scpTo (host , port , user , pemKeyFile , password , remoteTargetDirectory , localFile , fileMode , DEFAULT_CONNECT_TIMEOUT , DEFAULT_KEX_TIMEOUT );
4654 }
4755
4856 public static void scpTo (String host , int port , String user , File pemKeyFile , String password , String remoteTargetDirectory , byte [] data , String remoteFileName ,
49- String fileMode ) throws Exception {
57+ String fileMode ) throws Exception {
5058
5159 scpTo (host , port , user , pemKeyFile , password , remoteTargetDirectory , data , remoteFileName , fileMode , DEFAULT_CONNECT_TIMEOUT , DEFAULT_KEX_TIMEOUT );
5260 }
5361
5462 public static void scpTo (String host , int port , String user , File pemKeyFile , String password , String remoteTargetDirectory , String localFile , String fileMode ,
55- int connectTimeoutInMs , int kexTimeoutInMs ) throws Exception {
63+ int connectTimeoutInMs , int kexTimeoutInMs ) throws Exception {
5664
5765 com .trilead .ssh2 .Connection conn = null ;
5866 com .trilead .ssh2 .SCPClient scpClient = null ;
@@ -88,7 +96,7 @@ public static void scpTo(String host, int port, String user, File pemKeyFile, St
8896 }
8997
9098 public static void scpTo (String host , int port , String user , File pemKeyFile , String password , String remoteTargetDirectory , byte [] data , String remoteFileName ,
91- String fileMode , int connectTimeoutInMs , int kexTimeoutInMs ) throws Exception {
99+ String fileMode , int connectTimeoutInMs , int kexTimeoutInMs ) throws Exception {
92100
93101 com .trilead .ssh2 .Connection conn = null ;
94102 com .trilead .ssh2 .SCPClient scpClient = null ;
@@ -123,7 +131,8 @@ public static void scpTo(String host, int port, String user, File pemKeyFile, St
123131 }
124132
125133 public static Pair <Boolean , String > sshExecute (String host , int port , String user , File pemKeyFile , String password , String command , int connectTimeoutInMs ,
126- int kexTimeoutInMs , int waitResultTimeoutInMs ) throws Exception {
134+ int kexTimeoutInMs ,
135+ int waitResultTimeoutInMs ) throws Exception {
127136
128137 com .trilead .ssh2 .Connection conn = null ;
129138 com .trilead .ssh2 .Session sess = null ;
@@ -144,7 +153,7 @@ public static Pair<Boolean, String> sshExecute(String host, int port, String use
144153 throw new Exception (msg );
145154 }
146155 }
147- sess = conn . openSession ( );
156+ sess = openConnectionSession ( conn );
148157
149158 sess .execCommand (command );
150159
@@ -156,22 +165,22 @@ public static Pair<Boolean, String> sshExecute(String host, int port, String use
156165
157166 int currentReadBytes = 0 ;
158167 while (true ) {
168+ throwSshExceptionIfStdoutOrStdeerIsNull (stdout , stderr );
169+
159170 if ((stdout .available () == 0 ) && (stderr .available () == 0 )) {
160- int conditions =
161- sess .waitForCondition (ChannelCondition .STDOUT_DATA | ChannelCondition .STDERR_DATA | ChannelCondition .EOF | ChannelCondition .EXIT_STATUS ,
171+ int conditions = sess .waitForCondition (ChannelCondition .STDOUT_DATA | ChannelCondition .STDERR_DATA | ChannelCondition .EOF | ChannelCondition .EXIT_STATUS ,
162172 waitResultTimeoutInMs );
163173
164- if ((conditions & ChannelCondition .TIMEOUT ) != 0 ) {
165- String msg = "Timed out in waiting SSH execution result" ;
166- s_logger .error (msg );
167- throw new Exception (msg );
168- }
174+ throwSshExceptionIfConditionsTimeout (conditions );
169175
170176 if ((conditions & ChannelCondition .EXIT_STATUS ) != 0 ) {
171- if ((conditions & (ChannelCondition .STDOUT_DATA | ChannelCondition .STDERR_DATA )) == 0 ) {
172- break ;
173- }
177+ break ;
178+ }
179+
180+ if (canEndTheSshConnection (waitResultTimeoutInMs , sess , conditions )) {
181+ break ;
174182 }
183+
175184 }
176185
177186 while (stdout .available () > 0 ) {
@@ -189,11 +198,12 @@ public static Pair<Boolean, String> sshExecute(String host, int port, String use
189198
190199 if (sess .getExitStatus () == null ) {
191200 //Exit status is NOT available. Returning failure result.
201+ s_logger .error (String .format ("SSH execution of command %s has no exit status set. Result output: %s" , command , result ));
192202 return new Pair <Boolean , String >(false , result );
193203 }
194204
195205 if (sess .getExitStatus () != null && sess .getExitStatus ().intValue () != 0 ) {
196- s_logger .error ("SSH execution of command " + command + " has an error status code in return. result output: " + result );
206+ s_logger .error (String . format ( "SSH execution of command %s has an error status code in return. Result output: %s" , command , result ) );
197207 return new Pair <Boolean , String >(false , result );
198208 }
199209
@@ -206,4 +216,87 @@ public static Pair<Boolean, String> sshExecute(String host, int port, String use
206216 conn .close ();
207217 }
208218 }
219+
220+ /**
221+ * It gets a {@link Session} from the given {@link Connection}; then, it waits
222+ * {@value #WAITING_OPEN_SSH_SESSION} milliseconds before returning the session, given a time to
223+ * ensure that the connection is open before proceeding the execution.
224+ *
225+ * @param conn
226+ * @return {@link Session}
227+ * @throws IOException
228+ * @throws InterruptedException
229+ */
230+ protected static Session openConnectionSession (Connection conn ) throws IOException , InterruptedException {
231+ Session sess = conn .openSession ();
232+ Thread .sleep (WAITING_OPEN_SSH_SESSION );
233+ return sess ;
234+ }
235+
236+ /**
237+ * Handles the SSH connection in case of timeout or exit. If the session ends with a timeout
238+ * condition, it throws an exception; if the channel reaches an end of file condition, but it
239+ * does not have an exit status, it returns true to break the loop; otherwise, it returns
240+ * false.
241+ *
242+ * @param waitResultTimeoutInMs
243+ * @param sess
244+ * @param conditions
245+ * @return boolean
246+ * @throws SshException
247+ */
248+ protected static boolean canEndTheSshConnection (int waitResultTimeoutInMs , com .trilead .ssh2 .Session sess , int conditions ) throws SshException {
249+ if (isChannelConditionEof (conditions )) {
250+ int newConditions = sess .waitForCondition (ChannelCondition .EXIT_STATUS , waitResultTimeoutInMs );
251+ throwSshExceptionIfConditionsTimeout (newConditions );
252+ if ((newConditions & ChannelCondition .EXIT_STATUS ) != 0 ) {
253+ return true ;
254+ }
255+ }
256+ return false ;
257+ }
258+
259+ /**
260+ * It throws a {@link SshException} if the channel condition is {@link ChannelCondition#TIMEOUT}
261+ *
262+ * @param conditions
263+ * @throws SshException
264+ */
265+ protected static void throwSshExceptionIfConditionsTimeout (int conditions ) throws SshException {
266+ if ((conditions & ChannelCondition .TIMEOUT ) != 0 ) {
267+ String msg = "Timed out in waiting for SSH execution exit status" ;
268+ s_logger .error (msg );
269+ throw new SshException (msg );
270+ }
271+ }
272+
273+ /**
274+ * Checks if the channel condition mask is of {@link ChannelCondition#EOF} and not
275+ * {@link ChannelCondition#STDERR_DATA} or {@link ChannelCondition#STDOUT_DATA}.
276+ *
277+ * @param conditions
278+ * @return boolean
279+ */
280+ protected static boolean isChannelConditionEof (int conditions ) {
281+ if ((conditions & ChannelCondition .EOF ) != 0 ) {
282+ return true ;
283+ }
284+ return false ;
285+ }
286+
287+ /**
288+ * Checks if the SSH session {@link com.trilead.ssh2.Session#getStdout()} or
289+ * {@link com.trilead.ssh2.Session#getStderr()} is null.
290+ *
291+ * @param stdout
292+ * @param stderr
293+ * @throws SshException
294+ */
295+ protected static void throwSshExceptionIfStdoutOrStdeerIsNull (InputStream stdout , InputStream stderr ) throws SshException {
296+ if (stdout == null || stderr == null ) {
297+ String msg = "Stdout or Stderr of SSH session is null" ;
298+ s_logger .error (msg );
299+ throw new SshException (msg );
300+ }
301+ }
209302}
0 commit comments