JUnits for receiveUntil method
[appc.git] / appc-config / appc-config-adaptor / provider / src / main / java / org / onap / appc / ccadaptor / SshJcraftWrapper.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * =============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  *
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.appc.ccadaptor;
26
27 import com.att.eelf.configuration.EELFLogger;
28 import com.att.eelf.configuration.EELFManager;
29 import com.jcraft.jsch.Channel;
30 import com.jcraft.jsch.ChannelSftp;
31 import com.jcraft.jsch.ChannelShell;
32 import com.jcraft.jsch.ChannelSubsystem;
33 import com.jcraft.jsch.JSch;
34 import com.jcraft.jsch.JSchException;
35 import com.jcraft.jsch.Session;
36 import com.jcraft.jsch.SftpException;
37 import com.jcraft.jsch.UIKeyboardInteractive;
38 import com.jcraft.jsch.UserInfo;
39 import java.io.BufferedInputStream;
40 import java.io.BufferedReader;
41 import java.io.BufferedWriter;
42 import java.io.ByteArrayInputStream;
43 import java.io.ByteArrayOutputStream;
44 import java.io.DataInputStream;
45 import java.io.DataOutputStream;
46 import java.io.File;
47 import java.io.FileWriter;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.InputStreamReader;
51 import java.io.OutputStream;
52 import java.io.RandomAccessFile;
53 import java.text.DateFormat;
54 import java.text.SimpleDateFormat;
55 import java.util.Calendar;
56 import java.util.Date;
57 import java.util.StringTokenizer;
58 import java.util.concurrent.TimeUnit;
59 import javax.annotation.Nonnull;
60 import org.apache.commons.lang.StringUtils;
61
62 public class SshJcraftWrapper {
63
64     private static final EELFLogger log = EELFManager.getInstance().getLogger(SshJcraftWrapper.class);
65     static final int DEFAULT_PORT = 22;
66     static final String EOL = "\n";
67     static final String CHANNEL_SHELL_TYPE = "shell";
68     static final String CHANNEL_SUBSYSTEM_TYPE = "subsystem";
69     private static final String TERMINAL_BASIC_MODE = "vt102";
70     static final String STRICT_HOST_CHECK_KEY = "StrictHostKeyChecking";
71     static final String STRICT_HOST_CHECK_VALUE = "no";
72     static final String DELIMITERS_SEPARATOR = "|";
73
74     private TelnetListener listener = null;
75     private String routerLogFileName = null;
76     private BufferedReader reader = null;
77     private BufferedWriter out = null;
78     private File tmpFile = null;
79     private JSch jsch = null;
80     private Session session = null;
81     private Channel channel = null;
82     private String aggregatedReceivedString = "";
83     private String routerCmdType = "XML";
84     private String routerFileName = null;
85     private File jcraftReadSwConfigFileFromDisk = new File("/tmp/jcraftReadSwConfigFileFromDisk");
86     private String equipNameCode = null;
87     private String routerName = null;
88     private String hostName = null;
89     private String userName = null;
90     private String passWord = null;
91     private int readIntervalMs = 500;
92     private int readBufferSizeBytes = 512_000;
93     private char[] charBuffer;
94     private Runtime runtime = Runtime.getRuntime();
95
96     public SshJcraftWrapper() {
97         this.jsch = new JSch();
98         this.charBuffer = new char[readBufferSizeBytes];
99     }
100
101     SshJcraftWrapper(JSch jsch, int readIntervalMs, int readBufferSizeBytes) {
102         this.readIntervalMs = readIntervalMs;
103         this.jsch = jsch;
104         this.readBufferSizeBytes = readBufferSizeBytes;
105         this.charBuffer = new char[readBufferSizeBytes];
106     }
107
108     public void connect(String hostname, String username, String password, String prompt, int timeOut)
109         throws IOException {
110         log.debug("Attempting to connect to {0} username={1} prompt='{2}' timeOut={3}",
111             hostname, username, prompt, timeOut);
112         routerName = hostname;
113         hostName = hostname;
114         userName = username;
115         passWord = password;
116         try {
117             channel = provideSessionChannel(CHANNEL_SHELL_TYPE, DEFAULT_PORT, timeOut);
118             ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
119             reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), readBufferSizeBytes);
120             channel.connect();
121             log.info("Successfully connected. Flushing input buffer.");
122             try {
123                 receiveUntil(prompt, 3000, "No cmd was sent, just waiting");
124             } catch (IOException e) {
125                 log.warn("Caught an Exception: Nothing to flush out.", e);
126             }
127         } catch (JSchException e) {
128             log.error("Could not connect to host=" + hostname, e);
129             throw new IOException(e.toString());
130         }
131     }
132
133     // User specifies the port number.
134     public void connect(String hostname, String username, String password, String prompt, int timeOut, int portNum)
135         throws IOException {
136         log.debug("Attempting to connect to {0} username={1} prompt='{2}' timeOut={3} portNum={4}",
137             hostname, username, prompt, timeOut, portNum);
138         routerName = hostname;
139         hostName = hostname;
140         userName = username;
141         passWord = password;
142         try {
143             channel = provideSessionChannel(CHANNEL_SHELL_TYPE, portNum, timeOut);
144             ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
145             reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), readBufferSizeBytes);
146             channel.connect();
147             log.info("Successfully connected. Flushing input buffer.");
148             try {
149                 if ("]]>]]>".equals(prompt)) {
150                     receiveUntil("]]>]]>", 10000, "No cmd was sent, just waiting");
151                 } else {
152                     receiveUntil(":~#", 5000, "No cmd was sent, just waiting");
153                 }
154             } catch (IOException e) {
155                 log.warn("Caught an Exception: Nothing to flush out.", e);
156             }
157         } catch (JSchException e) {
158             log.error("Could not connect to host=" + hostname, e);
159             throw new IOException(e.toString());
160         }
161     }
162
163
164     public String receiveUntil(String delimeters, int timeout, String cmdThatWasSent) throws IOException {
165         checkConnection();
166         boolean match = false;
167         boolean cliPromptCmd = false;
168         StringBuilder sb = new StringBuilder();
169         StringBuilder sbReceive = new StringBuilder();
170         log.debug("delimeters='{0}' timeout={1} cmdThatWasSent='{2}'", delimeters, timeout, cmdThatWasSent);
171         int readCounts = 0;
172         aggregatedReceivedString = "";
173         FileWriter fileWriter = null;
174
175         long deadline = new Date().getTime() + timeout;
176         try {
177             session.setTimeout(timeout);  // This is the socket timeout value.
178             while (!match) {
179                 if (new Date().getTime() > deadline) {
180                     String formattedCmd = removeWhiteSpaceAndNewLineCharactersAroundString(cmdThatWasSent);
181                     log.error("Routine has timed out: routerName={0} CmdThatWasSent={1}", routerName, formattedCmd);
182                     throw new TimedOutException("Routine has timed out");
183                 }
184                 sleep(readIntervalMs);
185                 int len = reader.read(charBuffer, 0, readBufferSizeBytes);
186                 log.trace("After reader. Read command len={0}", len);
187                 if (len <= 0) {
188                     log.error("Reader failed to read any bytes. Suspected socket timeout, router={0}", routerName);
189                     throw new TimedOutException("Received a SocketTimeoutException router=" + routerName);
190                 }
191                 if (!cliPromptCmd) {
192                     if (cmdThatWasSent.indexOf("IOS_XR_uploadedSwConfigCmd") != -1) {
193                         if (out == null) {
194                             // This is a IOS XR sw config file. We will write it to the disk.
195                             timeout = timeout * 2;
196                             deadline = new Date().getTime() + timeout;
197                             log.debug("IOS XR upload for software config: timeout={0}", timeout);
198                             StringTokenizer st = new StringTokenizer(cmdThatWasSent);
199                             st.nextToken();
200                             routerFileName = st.nextToken();
201                             fileWriter = new FileWriter(routerFileName);
202                             out = new BufferedWriter(fileWriter);
203                             routerLogFileName = "/tmp/" + routerName;
204                             tmpFile = new File(routerLogFileName);
205                             log.debug("Prepared for writing swConfigFile to disk, routerFileName=" + routerFileName);
206                         }
207                         out.write(charBuffer, 0, len);
208                         out.flush();
209                         log.debug("{0} bytes has been written to the disk", len);
210                         if (tmpFile.exists()) {
211                             appendToRouterFile(routerLogFileName, len);
212                         }
213                         match = checkIfReceivedStringMatchesDelimeter(len, "\nXML>");
214                         if (match) {
215                             out.flush();
216                             out.close();
217                             out = null;
218                             return null;
219                         }
220                     } else {
221                         readCounts++;
222                         log.debug("Reader read {0} of data within {1} read iteration", len, readCounts);
223                         int c;
224                         sb.setLength(0);
225                         for (int i = 0; i < len; i++) {
226                             c = charBuffer[i];
227                             if ((c != 7) && (c != 13) && (c != 0) && (c != 27)) {
228                                 sbReceive.append(charBuffer[i]);
229                                 sb.append(charBuffer[i]);
230                             }
231                         }
232                         appendToRouterFile("/tmp/" + routerName, len);
233                         if (listener != null) {
234                             listener.receivedString(sb.toString());
235                         }
236                         match = checkIfReceivedStringMatchesDelimeter(delimeters, sb.toString(), cmdThatWasSent);
237                         if (match) {
238                             log.trace("Match was true, breaking the loop.");
239                             break;
240                         }
241                     }
242                 } else {
243                     log.trace("cliPromptCmd");
244                     sb.setLength(0);
245                     for (int i = 0; i < len; i++) {
246                         sbReceive.append(charBuffer[i]);
247                         sb.append(charBuffer[i]);
248                     }
249                     appendToRouterFile("/tmp/" + routerName, sb);
250                     if (listener != null) {
251                         listener.receivedString(sb.toString());
252                     }
253                     log.debug("sb2={0}  delimiters={1}", sb.toString(), delimeters);
254                     if (sb.toString().contains("\nariPrompt>")) {
255                         log.debug("Found ari prompt");
256                         break;
257                     }
258                 }
259             }
260         } catch (JSchException e) {
261             log.error("JSchException occurred", e);
262             throw new TimedOutException(e.getMessage());
263         } catch (IOException e) {
264             log.error("IOException occurred", e);
265             throw new TimedOutException(e.getMessage());
266         } finally {
267             try {
268                 if (fileWriter != null) {
269                     fileWriter.close();
270                 }
271             } catch (IOException ex) {
272                 log.warn("Failed to close fileWriter output stream", ex);
273             }
274         }
275         return stripOffCmdFromRouterResponse(sbReceive.toString());
276     }
277
278     private void sleep(long timeoutMs) {
279         try {
280             TimeUnit.MILLISECONDS.sleep(timeoutMs);
281         } catch (java.lang.InterruptedException ee) {
282             Thread.currentThread().interrupt();
283         }
284     }
285
286     private void checkConnection() {
287         try {
288             if (!isConnected() || !reader.ready()) {
289                 throw new IllegalStateException("Connection not established. Cannot perform action.");
290             }
291         } catch (IOException e) {
292             throw new IllegalStateException("Reader stream is closed. Cannot perform action.", e);
293         }
294     }
295
296     public boolean checkIfReceivedStringMatchesDelimeter(String delimeters, String receivedString,
297         String cmdThatWasSent) {
298         // The delimeters are in a '|' seperated string. Return true on the first match.
299         log.debug("Entered checkIfReceivedStringMatchesDelimeter: delimeters={0} cmdThatWasSent={1} receivedString={2}",
300             delimeters, cmdThatWasSent, receivedString);
301         StringTokenizer st = new StringTokenizer(delimeters, DELIMITERS_SEPARATOR);
302
303         if ((delimeters.contains("#$")) || ("CLI".equals(routerCmdType)))  // This would be an IOS XR, CLI command.
304         {
305             int x = receivedString.lastIndexOf('#');
306             int y = receivedString.length() - 1;
307             log.debug("IOS XR, CLI command");
308             if (log.isTraceEnabled()) {
309                 log.trace("cmdThatWasSent={0}, lastIndexOf hash delimiter={1}, maxIndexNum={2}", cmdThatWasSent, x, y);
310             }
311             return (x != -1) && (y == x);
312         }
313         if (cmdThatWasSent.contains("show config")) {
314             log.trace("In the block for 'show config'");
315             while (st.hasMoreTokens()) {
316                 String delimeter = st.nextToken();
317                 // Make sure we don't get faked out by a response of " #".
318                 // Proc #0
319                 //   # signaling-local-address ipv6 FD00:F4D5:EA06:1::110:136:254
320                 // LAAR2#
321                 int x = receivedString.lastIndexOf(delimeter);
322                 if ((receivedString.lastIndexOf(delimeter) != -1) && (receivedString.lastIndexOf(" #") != x - 1)) {
323                     log.debug("receivedString={0}", receivedString);
324                     log.trace("Found ending for 'show config' command, exiting.");
325                     return true;
326                 }
327             }
328         } else {
329             aggregatedReceivedString = aggregatedReceivedString + receivedString;
330             appendToFile("/tmp/aggregatedReceivedString.debug", aggregatedReceivedString);
331
332             log.debug("receivedString={0}", receivedString);
333             while (st.hasMoreTokens()) {
334                 String delimeter = st.nextToken();
335                 log.debug("Looking for an delimiter of:{0}", delimeter);
336                 if (aggregatedReceivedString.indexOf(delimeter) != -1) {
337                     log.debug("Found delimiter={0}, exiting", delimeter);
338                     aggregatedReceivedString = "";
339                     return true;
340                 }
341             }
342         }
343         return false;
344     }
345
346     public boolean checkIfReceivedStringMatchesDelimeter(int len, String delimeter) {
347         int x;
348         int c;
349         String str = StringUtils.EMPTY;
350
351         if (jcraftReadSwConfigFileFromDisk()) {
352             log.trace("jcraftReadSwConfigFileFromDisk block");
353             File fileName = new File(routerFileName);
354             log.debug("jcraftReadSwConfigFileFromDisk::: Will read the tail end of the file from the disk");
355             try {
356                 str = getLastFewLinesOfFile(fileName, 3);
357             } catch (IOException e) {
358                 log.warn("IOException occurred, while reading file=" + fileName, e);
359             }
360         } else {
361             // When looking at the end of the charBuffer, don't include any linefeeds or spaces. We only want to make the smallest string possible.
362             for (x = len - 1; x >= 0; x--) {
363                 c = charBuffer[x];
364                 if ((c != 10) && (c != 32)) // Not a line feed nor a space.
365                 {
366                     break;
367                 }
368             }
369             if ((x + 1 - 13) >= 0) {
370                 str = new String(charBuffer, x + 1 - 13, 13);
371                 log.debug("str:{0}", str);
372             } else {
373                 File fileName = new File(routerFileName);
374                 log.debug("Will read the tail end of the file from the disk, x={0} len={1} str={2} routerFileName={3}",
375                     x, len, str, routerFileName);
376                 try {
377                     str = getLastFewLinesOfFile(fileName, 3);
378                 } catch (IOException e) {
379                     log.warn("IOException occurred, while reading file=" + fileName, e);
380                 }
381             }
382         }
383
384         log.debug("Parsed string was str='{0}', searched delimiter was {1}");
385         return str.contains(delimeter);
386     }
387
388     public void closeConnection() {
389         log.info("Closing connection");
390         try {
391             if (reader != null) {
392                 reader.close();
393             }
394         } catch(IOException ex) {
395             log.warn("Could not close reader instance", ex);
396         } finally {
397             if(isConnected()) {
398                 channel.disconnect();
399                 session.disconnect();
400                 channel = null;
401                 session = null;
402             }
403             reader = null;
404         }
405     }
406
407     boolean isConnected() {
408         return (channel != null && session != null);
409     }
410
411     public void send(String cmd) throws IOException {
412         try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
413             sendSshCommand(cmd, dos);
414         } catch (IOException e) {
415             log.error("IOException occurred while sending command=" + cmd, e);
416             throw e;
417         }
418     }
419
420     public void sendChar(int v) throws IOException {
421         try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
422             if (log.isTraceEnabled()) {
423                 log.trace("Sending charCode: {0}", v);
424             }
425             dos.writeChar(v);
426             dos.flush();
427         } catch (IOException e) {
428             log.error("IOException occurred while writing char to channel output stream", e);
429             throw e;
430         }
431     }
432
433     public void send(byte[] b, int off, int len) throws IOException {
434         try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
435             dos.write(b, off, len);
436             dos.flush();
437         } catch (IOException e) {
438             log.error("IOException occurred while writing bytes to channel output stream", e);
439             throw e;
440         }
441     }
442
443     public static class MyUserInfo implements UserInfo, UIKeyboardInteractive {
444
445         @Override
446         public String getPassword() {
447             return null;
448         }
449
450         @Override
451         public boolean promptYesNo(String str) {
452             return false;
453         }
454
455         @Override
456         public String getPassphrase() {
457             return null;
458         }
459
460         @Override
461         public boolean promptPassphrase(String message) {
462             return false;
463         }
464
465         @Override
466         public boolean promptPassword(String message) {
467             return false;
468         }
469
470         @Override
471         public void showMessage(String message) {
472             //stub
473         }
474
475         @Override
476         public String[] promptKeyboardInteractive(String destination,
477             String name,
478             String instruction,
479             String[] prompt,
480             boolean[] echo) {
481             return new String[0];
482         }
483     }
484
485     public void addListener(TelnetListener listener) {
486         this.listener = listener;
487     }
488
489     private void appendToFile(String fileName, String dataToWrite) {
490         File outputFile = new File(fileName);
491         if (outputFile.exists()) {
492             try (FileWriter fw = new FileWriter(fileName, true); BufferedWriter ow = new BufferedWriter(fw)) {
493                 ow.write(dataToWrite);
494                 ow.close();
495             } catch (IOException e) {
496                 log.error("IOException occurred while writing to file=" + fileName, e);
497             }
498         }
499     }
500
501     public String getTheDate() {
502         DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy H:mm:ss  ");
503         return dateFormat.format(Calendar.getInstance().getTime());
504     }
505
506
507     public void appendToRouterFile(String fileName, StringBuilder dataToWrite) {
508         appendToFile(fileName, dataToWrite.toString());
509     }
510
511     public void appendToRouterFile(String fileName, int len) {
512         File outputFile = new File(fileName);
513         if (outputFile.exists()) {
514             try (FileWriter fw = new FileWriter(fileName, true); BufferedWriter ow = new BufferedWriter(fw)) {
515                 ow.write(charBuffer, 0, len);
516                 ow.close();
517             } catch (IOException e) {
518                 log.error("IOException occurred while writing to router file=" + fileName, e);
519             }
520         }
521     }
522
523     public String removeWhiteSpaceAndNewLineCharactersAroundString(String str) {
524         if (str != null && !StringUtils.EMPTY.equals(str)) {
525             StringTokenizer strTok = new StringTokenizer(str, EOL);
526             StringBuilder sb = new StringBuilder();
527
528             while (strTok.hasMoreTokens()) {
529                 String line = strTok.nextToken();
530                 sb.append(line);
531             }
532             return sb.toString().trim();
533         }
534         return StringUtils.EMPTY;
535     }
536
537     public String stripOffCmdFromRouterResponse(String routerResponse) {
538         // The session of SSH will echo the command sent to the router, in the router's response.
539         // Since all our commands are terminated by a '\n', strip off the first line
540         // of the response from the router. This first line contains the orginal command.
541
542         String[] responseTokens = routerResponse.split(EOL, 2);
543         return responseTokens[responseTokens.length-1];
544     }
545
546     public void setRouterCommandType(String type) {
547         this.routerCmdType = type;
548         log.debug("Router command type is set to: {0}", type);
549     }
550
551     public String getLastFewLinesOfFile(File file, int linesToRead) throws IOException {
552         RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
553         int lines = 0;
554         StringBuilder builder = new StringBuilder();
555         String tail = "";
556         long length = file.length();
557         length--;
558         randomAccessFile.seek(length);
559         for (long seek = length; seek >= 0; --seek) {
560             randomAccessFile.seek(seek);
561             char c = (char) randomAccessFile.read();
562             builder.append(c);
563             if (c == '\n') {
564                 builder = builder.reverse();
565                 tail = builder.toString() + tail;
566                 lines++;
567                 builder.setLength(0);
568                 if (lines == linesToRead) {
569                     break;
570                 }
571             }
572         }
573         randomAccessFile.close();
574         if (log.isDebugEnabled()) {
575             log.debug("Content read from file={0} was tail={1}", file.getName(), tail);
576         }
577         return tail;
578     }
579
580     public boolean jcraftReadSwConfigFileFromDisk() {
581         return jcraftReadSwConfigFileFromDisk.exists();
582     }
583
584     public String getEquipNameCode() {
585         return equipNameCode;
586     }
587
588     public void setEquipNameCode(String equipNameCode) {
589         this.equipNameCode = equipNameCode;
590     }
591
592     public String getRouterName() {
593         return routerName;
594     }
595
596     // Routine does reads until it has read 'nchars' or times out.
597     public void receiveUntilBufferFlush(int ncharsSent, int timeout, String message) throws IOException {
598         log.debug("ncharsSent={0}, timeout={1}, message={2}", ncharsSent, timeout, message);
599         int ncharsTotalReceived = 0;
600         int ncharsRead = 0;
601
602         long deadline = new Date().getTime() + timeout;
603         logMemoryUsage();
604         try {
605             session.setTimeout(timeout);  // This is the socket timeout value.
606             while (true) {
607                 if (new Date().getTime() > deadline) {
608                     log.error("Routine has timed out: ncharsSent={0}, ncharsTotalReceived={1}", ncharsSent,
609                         ncharsTotalReceived);
610                     throw new TimedOutException("Routine has timed out");
611                 }
612                 ncharsRead = reader.read(charBuffer, 0, readBufferSizeBytes);
613                 if (listener != null) {
614                     listener.receivedString(String.copyValueOf(charBuffer, 0, ncharsRead));
615                 }
616                 appendToRouterFile("/tmp/" + routerName, ncharsRead);
617                 ncharsTotalReceived = ncharsTotalReceived + ncharsRead;
618                 if (ncharsTotalReceived >= ncharsSent) {
619                     log.debug("Received the correct number of characters, ncharsSent={0}, ncharsTotalReceived={1}",
620                         ncharsSent, ncharsTotalReceived);
621                     logMemoryUsage();
622                     return;
623                 }
624             }
625         } catch (JSchException e) {
626             log.error("JSchException occurred while command sending", e);
627             log.debug("ncharsSent={0}, ncharsTotalReceived={1}, ncharsRead={2} until error occurred",
628                 ncharsSent, ncharsTotalReceived, ncharsRead);
629             throw new TimedOutException(e.getMessage());
630         }
631     }
632
633     public String getHostName() {
634         return hostName;
635     }
636
637     public String getUserName() {
638         return userName;
639     }
640
641     public String getPassWord() {
642         return passWord;
643     }
644
645     public void sftpPutFile(String sourcePath, String destDirectory) throws IOException {
646         try {
647             Session sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
648             UserInfo ui = new MyUserInfo();
649             sftpSession.setPassword(passWord);
650             sftpSession.setUserInfo(ui);
651             sftpSession.connect(30 * 1000);
652             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
653             sftp.connect();
654             log.debug("Sending via sftp from source: {0} to destination: {1}", sourcePath, destDirectory);
655             sftp.put(sourcePath, destDirectory, ChannelSftp.OVERWRITE);
656             sftpSession.disconnect();
657         } catch (JSchException ex) {
658             log.error("JSchException occurred while handling sftp session", ex);
659             throw new IOException(ex.getMessage());
660         } catch (SftpException ex) {
661             log.error("SftpException occurred during file transfer", ex);
662             throw new IOException(ex.getMessage());
663         }
664     }
665
666     public void sftpPutStringData(String stringOfData, String fullPathDest) throws IOException {
667         try {
668             Session sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
669             UserInfo ui = new MyUserInfo();
670             sftpSession.setPassword(passWord);
671             sftpSession.setUserInfo(ui);
672             sftpSession.connect(30 * 1000);
673             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
674             sftp.connect();
675             InputStream is = new ByteArrayInputStream(stringOfData.getBytes());
676             log.debug("Sending via sftp stringOfData to destination: {0}", fullPathDest);
677             sftp.put(is, fullPathDest, ChannelSftp.OVERWRITE);
678             sftpSession.disconnect();
679         } catch (JSchException ex) {
680             log.error("JSchException occurred while handling sftp session", ex);
681             throw new IOException(ex.getMessage());
682         } catch (SftpException ex) {
683             log.error("SftpException occurred during data transfer", ex);
684             throw new IOException(ex.getMessage());
685         }
686     }
687
688     public String sftpGet(String fullFilePathName) throws IOException {
689         try {
690             Session sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
691             UserInfo ui = new MyUserInfo();
692             sftpSession.setPassword(passWord);
693             sftpSession.setUserInfo(ui);
694             sftpSession.connect(30 * 1000);
695             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
696             sftp.connect();
697             InputStream in = sftp.get(fullFilePathName);
698             String sftpFileString = readInputStreamAsString(in);
699             log.debug("Received data via sftp connection sftpFileString={0} from fullFilePathName={1}",
700                 sftpFileString, fullFilePathName);
701             sftpSession.disconnect();
702             return sftpFileString;
703         } catch (JSchException ex) {
704             log.error("JSchException occurred while handling sftp session", ex);
705             throw new IOException(ex.getMessage());
706         } catch (SftpException ex) {
707             log.error("SftpException occurred during data transfer", ex);
708             throw new IOException(ex.getMessage());
709         }
710     }
711
712     public static String readInputStreamAsString(InputStream in) throws IOException {
713         BufferedInputStream bis = new BufferedInputStream(in);
714         ByteArrayOutputStream buf = new ByteArrayOutputStream();
715         int result = bis.read();
716         while (result != -1) {
717             byte b = (byte) result;
718             buf.write(b);
719             result = bis.read();
720         }
721         return buf.toString();
722     }
723
724
725     public void logMemoryUsage() {
726         int mb = 1024 * 1024;
727         long usedMemory;
728         long maxMemoryAvailable;
729         long memoryLeftOnHeap;
730         maxMemoryAvailable =runtime.maxMemory() / mb;
731         usedMemory = (runtime.totalMemory() / mb) - (runtime.freeMemory() / mb);
732         memoryLeftOnHeap = maxMemoryAvailable - usedMemory;
733         log.info("Memory usage: maxMemoryAvailable={0}, usedMemory={1}, memoryLeftOnHeap={2}",
734             maxMemoryAvailable, usedMemory, memoryLeftOnHeap);
735     }
736
737     public void connect(String hostname, String username, String password, int timeOut, int portNum,
738         String subsystem) throws IOException {
739
740         if (log.isDebugEnabled()) {
741             log.debug(
742                 "Attempting to connect to {0} username={1} timeOut={2} portNum={3} subsystem={4}",
743                 hostname, username, timeOut, portNum, subsystem);
744         }
745         this.routerName = hostname;
746         this.hostName = hostname;
747         this.userName = username;
748         this.passWord = password;
749         try {
750             channel = provideSessionChannel(CHANNEL_SUBSYSTEM_TYPE, portNum, timeOut);
751             ((ChannelSubsystem) channel).setSubsystem(subsystem);
752             ((ChannelSubsystem) channel).setPty(true); //expected ptyType vt102
753             reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), readBufferSizeBytes);
754             channel.connect(5000);
755         } catch (JSchException e) {
756             log.error("JschException occurred ", e);
757             throw new IOException(e.getMessage());
758         }
759     }
760
761     public void connect(String hostName, String username, String password) throws IOException {
762         log.debug("Attempting to connect to {0} username={1} portNumber={2}", hostName, username, DEFAULT_PORT);
763         this.routerName = hostName;
764         this.hostName = hostName;
765         this.userName = username;
766         this.passWord = password;
767         try {
768             channel = provideSessionChannel(CHANNEL_SHELL_TYPE, DEFAULT_PORT, 30000);
769             ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
770             reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), readBufferSizeBytes);
771             channel.connect();
772             try {
773                 receiveUntil(":~#", 9000, "No cmd was sent, just waiting, but we can stop on a '~#'");
774             } catch (Exception e) {
775                 log.warn("Caught an Exception: Nothing to flush out.", e);
776             }
777
778         } catch (JSchException e) {
779             log.error("JschException occurred ", e);
780             throw new IOException(e.getMessage());
781         }
782     }
783
784
785     public void put(String sourcePath, String destDirectory) throws IOException {
786         try {
787             Session sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
788             UserInfo ui = new MyUserInfo();
789             sftpSession.setPassword(passWord);
790             sftpSession.setUserInfo(ui);
791             sftpSession.connect(30 * 1000);
792             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
793             sftp.connect();
794             log.debug("Sending via sftp from source: {0} to destination: {1}", sourcePath, destDirectory);
795             sftp.put(sourcePath, destDirectory, ChannelSftp.OVERWRITE);
796             sftpSession.disconnect();
797         } catch (JSchException ex) {
798             log.error("JSchException occurred while handling sftp session", ex);
799             throw new IOException(ex.getMessage());
800         } catch (SftpException ex) {
801             log.error("SftpException occurred during file transfer", ex);
802             throw new IOException(ex.getMessage());
803         }
804     }
805
806     public void put(InputStream is, String fullPathDest, String hostName, String userName, String passWord)
807         throws IOException {
808         Session sftpSession = null;
809         try {
810             log.debug("Sftp put invoked, connection details: username={1} hostname={2}",
811                 userName, hostName);
812             jsch = new JSch();
813             java.util.Properties config = new java.util.Properties();
814             config.put("StrictHostKeyChecking", "no");
815             sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
816             UserInfo ui = new MyUserInfo();
817             sftpSession.setPassword(passWord);
818             sftpSession.setUserInfo(ui);
819             sftpSession.setConfig(config);
820             sftpSession.connect(30 * 1000);
821             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
822             sftp.connect();
823             String oldFiles = fullPathDest + "*";
824             log.debug("Deleting old files: {0}", oldFiles);
825             try {
826                 sftp.rm(oldFiles);
827             } catch (SftpException ex) {
828                 String exp = "No such file";
829                 if (ex.getMessage() != null && ex.getMessage().contains(exp)) {
830                     log.warn("No files found, continue");
831                 } else {
832                     log.error("SftpException while invoking rm command over sftp", ex);
833                     throw ex;
834                 }
835             }
836             log.debug("Sending stringOfData to destination {0}", fullPathDest);
837             sftp.put(is, fullPathDest, ChannelSftp.OVERWRITE);
838         } catch (JSchException ex) {
839             log.error("JSchException occurred while handling sftp session", ex);
840             throw new IOException(ex.getMessage());
841         } catch (SftpException ex) {
842             log.error("SftpException occurred during file transfer", ex);
843             throw new IOException(ex.getMessage());
844         } finally {
845             if (sftpSession != null) {
846                 sftpSession.disconnect();
847             }
848         }
849     }
850
851     public String get(String fullFilePathName, String hostName, String userName, String passWord) throws IOException {
852         Session sftpSession = null;
853         try {
854             log.debug("Sftp get invoked, connection details: username={1} hostname={2}",
855                 userName, hostName);
856             jsch = new JSch();
857             sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
858             java.util.Properties config = new java.util.Properties();
859             config.put("StrictHostKeyChecking", "no");
860             UserInfo ui = new MyUserInfo();
861             sftpSession.setPassword(passWord);
862             sftpSession.setUserInfo(ui);
863             sftpSession.setConfig(config);
864             sftpSession.connect(30 * 1000);
865             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
866             sftp.connect();
867             InputStream in = sftp.get(fullFilePathName);
868             return readInputStreamAsString(in);
869         } catch (JSchException ex) {
870             log.error("JSchException occurred while handling sftp session", ex);
871             throw new IOException(ex.getMessage());
872         } catch (SftpException ex) {
873             log.error("SftpException occurred during file transfer", ex);
874             throw new IOException(ex.getMessage());
875         } finally {
876             if (sftpSession != null) {
877                 sftpSession.disconnect();
878             }
879         }
880     }
881
882     public String send(String cmd, String delimiter) throws IOException {
883         try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
884             sendSshCommand(cmd, dos);
885             return receiveUntil(delimiter, 300000, cmd);
886         } catch (IOException ex) {
887             log.error("IOException occurred", ex);
888             throw new IOException(ex.getMessage());
889         }
890     }
891
892     private void sendSshCommand(@Nonnull String originalCommand, @Nonnull DataOutputStream channelOutputStream)
893         throws IOException {
894         String command = enhanceCommandWithEOL(originalCommand);
895         int length = command.length(); // 2,937,706
896         int charsChunkSize = 300000;
897         int charsTotalSent = 0;
898
899         log.debug("Sending ssh command: length={0}, payload: {1}", length, command);
900         if (isCmdLengthEnoughToSendInChunks(length, charsChunkSize)) {
901             int timeout = 9000;
902             for (int i = 0; i < length; i += charsChunkSize) {
903                 String commandChunk = command.substring(i, Math.min(length, i + charsChunkSize));
904                 int numCharsSentInChunk = commandChunk.length();
905                 charsTotalSent = charsTotalSent + commandChunk.length();
906                 log.debug("Iteration nr:{0}, sending command chunk: {1}", i, numCharsSentInChunk);
907                 channelOutputStream.writeBytes(commandChunk);
908                 channelOutputStream.flush();
909                 try {
910                     if (numCharsSentInChunk < length) {
911                         receiveUntilBufferFlush(numCharsSentInChunk, timeout, "buffer flush  i=" + i);
912                     } else {
913                         log.trace("i={0}, flush immediately", i);
914                         channelOutputStream.flush();
915                     }
916                 } catch (IOException ex) {
917                     log.warn("IOException occurred: nothing to flush out", ex);
918                 }
919             }
920         } else {
921             channelOutputStream.writeBytes(command);
922         }
923         channelOutputStream.flush();
924     }
925
926     private boolean isCmdLengthEnoughToSendInChunks(int length, int chunkSize) {
927         return length > 2 * chunkSize;
928     }
929
930     private String enhanceCommandWithEOL(@Nonnull String originalCommand) {
931         char commandEnding = originalCommand.charAt(originalCommand.length() - 1);
932         if (commandEnding != '\n' && commandEnding != '\r') {
933             return originalCommand + EOL;
934         }
935         return originalCommand;
936     }
937
938     private Channel provideSessionChannel(String channelType, int port, int timeout) throws JSchException {
939         session = jsch.getSession(this.userName, this.hostName, port);
940         session.setPassword(this.passWord);
941         session.setUserInfo(new MyUserInfo()); //needed?
942         session.setConfig(STRICT_HOST_CHECK_KEY, STRICT_HOST_CHECK_VALUE);
943         session.connect(timeout);
944         session.setServerAliveCountMax(
945             0); // If this is not set to '0', then socket timeout on all reads will not work!!!!
946         return session.openChannel(channelType);
947     }
948
949 }