diff options
2 files changed, 275 insertions, 31 deletions
diff --git a/appc-config/appc-config-adaptor/provider/src/main/java/org/onap/appc/ccadaptor/SshJcraftWrapper.java b/appc-config/appc-config-adaptor/provider/src/main/java/org/onap/appc/ccadaptor/SshJcraftWrapper.java index acdb87b90..07eb431f4 100644 --- a/appc-config/appc-config-adaptor/provider/src/main/java/org/onap/appc/ccadaptor/SshJcraftWrapper.java +++ b/appc-config/appc-config-adaptor/provider/src/main/java/org/onap/appc/ccadaptor/SshJcraftWrapper.java @@ -55,24 +55,24 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.StringTokenizer; +import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import org.apache.commons.lang.StringUtils; public class SshJcraftWrapper { private static final EELFLogger log = EELFManager.getInstance().getLogger(SshJcraftWrapper.class); - - private static final int BUFFER_SIZE = 512000; static final int DEFAULT_PORT = 22; + static final String EOL = "\n"; static final String CHANNEL_SHELL_TYPE = "shell"; static final String CHANNEL_SUBSYSTEM_TYPE = "subsystem"; private static final String TERMINAL_BASIC_MODE = "vt102"; static final String STRICT_HOST_CHECK_KEY = "StrictHostKeyChecking"; static final String STRICT_HOST_CHECK_VALUE = "no"; + static final String DELIMITERS_SEPARATOR = "|"; + private TelnetListener listener = null; private String routerLogFileName = null; - private String routerName = null; - private char[] charBuffer = new char[BUFFER_SIZE]; private BufferedReader reader = null; private BufferedWriter out = null; private File tmpFile = null; @@ -84,18 +84,25 @@ public class SshJcraftWrapper { private String routerFileName = null; private File jcraftReadSwConfigFileFromDisk = new File("/tmp/jcraftReadSwConfigFileFromDisk"); private String equipNameCode = null; + private String routerName = null; private String hostName = null; private String userName = null; private String passWord = null; + private int readIntervalMs = 500; + private int readBufferSizeBytes = 512_000; + private char[] charBuffer; private Runtime runtime = Runtime.getRuntime(); - public SshJcraftWrapper() { this.jsch = new JSch(); + this.charBuffer = new char[readBufferSizeBytes]; } - SshJcraftWrapper(JSch jsch) { + SshJcraftWrapper(JSch jsch, int readIntervalMs, int readBufferSizeBytes) { + this.readIntervalMs = readIntervalMs; this.jsch = jsch; + this.readBufferSizeBytes = readBufferSizeBytes; + this.charBuffer = new char[readBufferSizeBytes]; } public void connect(String hostname, String username, String password, String prompt, int timeOut) @@ -109,7 +116,7 @@ public class SshJcraftWrapper { try { channel = provideSessionChannel(CHANNEL_SHELL_TYPE, DEFAULT_PORT, timeOut); ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE); - reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), BUFFER_SIZE); + reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), readBufferSizeBytes); channel.connect(); log.info("Successfully connected. Flushing input buffer."); try { @@ -135,7 +142,7 @@ public class SshJcraftWrapper { try { channel = provideSessionChannel(CHANNEL_SHELL_TYPE, portNum, timeOut); ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE); - reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), BUFFER_SIZE); + reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), readBufferSizeBytes); channel.connect(); log.info("Successfully connected. Flushing input buffer."); try { @@ -155,6 +162,7 @@ public class SshJcraftWrapper { public String receiveUntil(String delimeters, int timeout, String cmdThatWasSent) throws IOException { + checkConnection(); boolean match = false; boolean cliPromptCmd = false; StringBuilder sb = new StringBuilder(); @@ -173,12 +181,8 @@ public class SshJcraftWrapper { log.error("Routine has timed out: routerName={0} CmdThatWasSent={1}", routerName, formattedCmd); throw new TimedOutException("Routine has timed out"); } - try { - Thread.sleep(500); - } catch (java.lang.InterruptedException ee) { - Thread.currentThread().interrupt(); - } - int len = reader.read(charBuffer, 0, BUFFER_SIZE); + sleep(readIntervalMs); + int len = reader.read(charBuffer, 0, readBufferSizeBytes); log.trace("After reader. Read command len={0}", len); if (len <= 0) { log.error("Reader failed to read any bytes. Suspected socket timeout, router={0}", routerName); @@ -271,12 +275,30 @@ public class SshJcraftWrapper { return stripOffCmdFromRouterResponse(sbReceive.toString()); } + private void sleep(long timeoutMs) { + try { + TimeUnit.MILLISECONDS.sleep(timeoutMs); + } catch (java.lang.InterruptedException ee) { + Thread.currentThread().interrupt(); + } + } + + private void checkConnection() { + try { + if (!isConnected() || !reader.ready()) { + throw new IllegalStateException("Connection not established. Cannot perform action."); + } + } catch (IOException e) { + throw new IllegalStateException("Reader stream is closed. Cannot perform action.", e); + } + } + public boolean checkIfReceivedStringMatchesDelimeter(String delimeters, String receivedString, String cmdThatWasSent) { // The delimeters are in a '|' seperated string. Return true on the first match. log.debug("Entered checkIfReceivedStringMatchesDelimeter: delimeters={0} cmdThatWasSent={1} receivedString={2}", delimeters, cmdThatWasSent, receivedString); - StringTokenizer st = new StringTokenizer(delimeters, "|"); + StringTokenizer st = new StringTokenizer(delimeters, DELIMITERS_SEPARATOR); if ((delimeters.contains("#$")) || ("CLI".equals(routerCmdType))) // This would be an IOS XR, CLI command. { @@ -500,7 +522,7 @@ public class SshJcraftWrapper { public String removeWhiteSpaceAndNewLineCharactersAroundString(String str) { if (str != null && !StringUtils.EMPTY.equals(str)) { - StringTokenizer strTok = new StringTokenizer(str, "\n"); + StringTokenizer strTok = new StringTokenizer(str, EOL); StringBuilder sb = new StringBuilder(); while (strTok.hasMoreTokens()) { @@ -516,17 +538,9 @@ public class SshJcraftWrapper { // The session of SSH will echo the command sent to the router, in the router's response. // Since all our commands are terminated by a '\n', strip off the first line // of the response from the router. This first line contains the orginal command. - StringTokenizer rr = new StringTokenizer(routerResponse, "\n"); - StringBuilder sb = new StringBuilder(); - int numTokens = rr.countTokens(); - if (numTokens > 1) { - rr.nextToken(); //Skip the first line. - while (rr.hasMoreTokens()) { - sb.append(rr.nextToken()).append("\n"); - } - } - return sb.toString(); + String[] responseTokens = routerResponse.split(EOL, 2); + return responseTokens[responseTokens.length-1]; } public void setRouterCommandType(String type) { @@ -595,7 +609,7 @@ public class SshJcraftWrapper { ncharsTotalReceived); throw new TimedOutException("Routine has timed out"); } - ncharsRead = reader.read(charBuffer, 0, BUFFER_SIZE); + ncharsRead = reader.read(charBuffer, 0, readBufferSizeBytes); if (listener != null) { listener.receivedString(String.copyValueOf(charBuffer, 0, ncharsRead)); } @@ -736,7 +750,7 @@ public class SshJcraftWrapper { channel = provideSessionChannel(CHANNEL_SUBSYSTEM_TYPE, portNum, timeOut); ((ChannelSubsystem) channel).setSubsystem(subsystem); ((ChannelSubsystem) channel).setPty(true); //expected ptyType vt102 - reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), BUFFER_SIZE); + reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), readBufferSizeBytes); channel.connect(5000); } catch (JSchException e) { log.error("JschException occurred ", e); @@ -753,7 +767,7 @@ public class SshJcraftWrapper { try { channel = provideSessionChannel(CHANNEL_SHELL_TYPE, DEFAULT_PORT, 30000); ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE); - reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), BUFFER_SIZE); + reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())), readBufferSizeBytes); channel.connect(); try { receiveUntil(":~#", 9000, "No cmd was sent, just waiting, but we can stop on a '~#'"); @@ -916,7 +930,7 @@ public class SshJcraftWrapper { private String enhanceCommandWithEOL(@Nonnull String originalCommand) { char commandEnding = originalCommand.charAt(originalCommand.length() - 1); if (commandEnding != '\n' && commandEnding != '\r') { - return originalCommand + "\n"; + return originalCommand + EOL; } return originalCommand; } @@ -931,4 +945,5 @@ public class SshJcraftWrapper { 0); // If this is not set to '0', then socket timeout on all reads will not work!!!! return session.openChannel(channelType); } + } diff --git a/appc-config/appc-config-adaptor/provider/src/test/java/org/onap/appc/ccadaptor/SshJcraftWrapperTest.java b/appc-config/appc-config-adaptor/provider/src/test/java/org/onap/appc/ccadaptor/SshJcraftWrapperTest.java index 2495c64d0..76b4c628f 100644 --- a/appc-config/appc-config-adaptor/provider/src/test/java/org/onap/appc/ccadaptor/SshJcraftWrapperTest.java +++ b/appc-config/appc-config-adaptor/provider/src/test/java/org/onap/appc/ccadaptor/SshJcraftWrapperTest.java @@ -25,6 +25,7 @@ package org.onap.appc.ccadaptor; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; @@ -68,8 +69,12 @@ public class SshJcraftWrapperTest { private static final String HOST = "hostname"; private static final String SUBSYSTEM = "netconf"; private static final String PROMPT = "]]>]]>"; + private static final String SEARCH_STR = "</rpc-reply>"; + private static final int READ_TIMEOUT = 180_000; private static final int PORT_NUM = 23; private static final int SESSION_TIMEOUT = 30_000; + private static final int READ_INTERVAL_MS = 1; + private static final int READ_BUFFER_SIZE = 10; private SshJcraftWrapper cut; @Mock @@ -91,7 +96,7 @@ public class SshJcraftWrapperTest { given(session.openChannel(SshJcraftWrapper.CHANNEL_SHELL_TYPE)).willReturn(channelShell); given(session.openChannel(SshJcraftWrapper.CHANNEL_SUBSYSTEM_TYPE)).willReturn(channelSubsystem); given(jSchMock.getSession(anyString(), anyString(), anyInt())).willReturn(session); - cut = new SshJcraftWrapper(jSchMock); + cut = new SshJcraftWrapper(jSchMock, READ_INTERVAL_MS, READ_BUFFER_SIZE); } @Ignore @@ -451,4 +456,228 @@ public class SshJcraftWrapperTest { assertTrue(cut.isConnected()); } + //receiveUntil tests begin + @Test(expected = IllegalStateException.class) + public void receiveUntil_shouldThrowIllegalStateException_whenInstanceIsNotConnected() throws Exception { + //given + assertFalse(cut.isConnected()); + + //when + cut.receiveUntil(SEARCH_STR, READ_TIMEOUT, ""); + + //then + fail("IllegalStateException should be thrown"); + } + + @Test(expected = IllegalStateException.class) + public void receiveUntil_shouldThrowIllegalStateException_whenJschReaderStreamIsNotAvailable() throws Exception { + //given + provideConnectedSubsystemInstance(); + given(channelIs.available()).willReturn(0); + + //when + cut.receiveUntil(SEARCH_STR, READ_TIMEOUT, ""); + + //then + fail("IllegalStateException should be thrown"); + } + + @Test(expected = TimedOutException.class) + public void receiveUntil_shouldThrowTimedOutException_whenSessionFails() throws Exception { + //given + given(channelSubsystem.getInputStream()).willReturn(IOUtils.toInputStream("test input stream:~#", "UTF-8")); + cut.connect(HOST, USER, PASS, SESSION_TIMEOUT, PORT_NUM, SUBSYSTEM); + assertTrue(cut.isConnected()); + doThrow(new JSchException("Session is not available")).when(session).setTimeout(anyInt()); + + //when + cut.receiveUntil(SEARCH_STR, READ_TIMEOUT, ""); + + //then + fail("TimedOutException should be thrown"); + } + + @Test(expected = TimedOutException.class) + public void receiveUntil_shouldThrowTimedOutException_whenReadFails() throws Exception { + //given + provideConnectedSubsystemInstance(); + given(channelIs.available()).willReturn(1); + given(channelIs.read(any(), anyInt(), anyInt())).willThrow(new IOException("Could not read stream")); + + //when + cut.receiveUntil(SEARCH_STR, READ_TIMEOUT, ""); + + //then + fail("TimedOutException should be thrown"); + } + + @Test(expected = TimedOutException.class) + public void receiveUntil_shouldThrowException_whenTimeoutIsReached() throws Exception { + //given + String streamContent = "test input stream:~#"; + provideConnectedSubsystemInstanceWithStreamContent(streamContent); + + //when + cut.receiveUntil(SEARCH_STR, -1000, " Some fake command\n"); + + //then + fail("TimedOutException should be thrown"); + } + + @Test(expected = TimedOutException.class) + public void receiveUntil_shouldThrowException_whenReachedEndOfStream_andCouldNotReadMoreBytes() throws Exception { + //given + provideConnectedSubsystemInstance(); + given(channelIs.available()).willReturn(1); + given(channelIs.read(any(), anyInt(), anyInt())).willReturn(-1); + + //when + cut.receiveUntil(SEARCH_STR, READ_TIMEOUT, ""); + + //then + fail("TimedOutException should be thrown"); + } + + @Test + public void receiveUntil_shouldReadUnderlyingStream_andStripOffFirstLine() throws Exception { + //given + String command = "Command"+SshJcraftWrapper.EOL; + String reply = "Reply"+SshJcraftWrapper.EOL; + String streamContent = command+reply+PROMPT; + provideConnectedSubsystemInstanceWithStreamContent(streamContent); + + //when + String result = cut.receiveUntil(PROMPT, SESSION_TIMEOUT, command); + + //then + assertEquals(reply+PROMPT, result); + } + + @Test + public void receiveUntil_shouldReadUnderlyingStream_andReturnWholeReadString() throws Exception { + //given + String streamContent = "Command and Reply in just one line"+PROMPT; + provideConnectedSubsystemInstanceWithStreamContent(streamContent); + + //when + String result = cut.receiveUntil(PROMPT, SESSION_TIMEOUT, streamContent); + + //then + assertEquals(streamContent, result); + } + + @Test + public void receiveUntil_shouldCutOffSpecialCharactersFromStream() throws Exception { + //given + char special1 = Character.UNASSIGNED; + char special2 = Character.ENCLOSING_MARK; + char special3 = Character.LINE_SEPARATOR; + char special4 = Character.MODIFIER_SYMBOL; + StringBuilder sb = new StringBuilder("Command"); + sb.append(special1).append("With").append(special2).append("Special") + .append(special3).append("Characters").append(special4).append("Set").append(PROMPT); + + provideConnectedSubsystemInstanceWithStreamContent(sb.toString()); + + //when + String result = cut.receiveUntil(PROMPT, SESSION_TIMEOUT, ""); + + //then + assertEquals("CommandWithSpecialCharactersSet"+PROMPT, result); + } + + @Test + public void receiveUntil_shouldReadUnderlyingStream_untilCLIDelimiterFound_whenProperDelimiterSet() throws Exception { + //given + String cliDelimiter = "#$"; + String delimiters = PROMPT+SshJcraftWrapper.DELIMITERS_SEPARATOR+cliDelimiter; + String streamContent = "Command for CLI invocation #"; + provideConnectedSubsystemInstanceWithStreamContent(streamContent); + + //when + String result = cut.receiveUntil(delimiters, SESSION_TIMEOUT, streamContent); + + //then + assertEquals(streamContent, result); + } + + @Test + public void receiveUntil_shouldReadUnderlyingStream_untilCLIDelimiterFound_whenCLICommandSet() throws Exception { + //given + String streamContent = "Command for CLI invocation #"; + provideConnectedSubsystemInstanceWithStreamContent(streamContent); + cut.setRouterCommandType("CLI"); + + //when + String result = cut.receiveUntil("", SESSION_TIMEOUT, streamContent); + + //then + assertEquals(streamContent, result); + } + + @Test + public void receiveUntil_shouldReadUnderlyingStream_untilCLIDelimiterFound_forShowConfigCommand() throws Exception { + //given + String streamContent = "show config\nconfig content#"; + provideConnectedSubsystemInstanceWithStreamContent(streamContent); + + //when + String result = cut.receiveUntil("#", SESSION_TIMEOUT, streamContent); + + //then + assertEquals("config content#", result); + } + + @Test + public void receiveUntil_shouldWriteOutputToRouterFile_whenReadingIOSXRswConfigFile_confirmFromFile() throws Exception { + receiveUntil_shouldWriteOutputToRouterFile_whenReadingIOSXRswConfigFile(); + } + + @Test + public void receiveUntil_shouldWriteOutputToRouterFile_whenReadingIOSXRswConfigFile_confirmFromBuffer() throws Exception { + //given + int biggerBufferSize = 32; + cut = new SshJcraftWrapper(jSchMock, READ_INTERVAL_MS, biggerBufferSize); + + receiveUntil_shouldWriteOutputToRouterFile_whenReadingIOSXRswConfigFile(); + } + + private void receiveUntil_shouldWriteOutputToRouterFile_whenReadingIOSXRswConfigFile() throws Exception { + //given + String routerName = "router"; + String command = "RP/0/RP0/CPU0: "+routerName+" #IOS_XR_uploadedSwConfigCmd"; + String configFileEnding = "\nXML>"; + String streamContent = "Config file\ncontent"+configFileEnding; + provideConnectedSubsystemInstanceWithStreamContent(streamContent); + + //when + String result = cut.receiveUntil("", SESSION_TIMEOUT, command); + + //then + assertNull(result); //TO-DO: it would be better to return empty string in this situation + assertFileExist(routerName); + + //after + teardownFile(routerName); + } + + private void provideConnectedSubsystemInstanceWithStreamContent( String streamContent) throws Exception { + given(channelSubsystem.getInputStream()).willReturn(IOUtils.toInputStream(streamContent, "UTF-8")); + cut.connect(HOST, USER, PASS, SESSION_TIMEOUT, PORT_NUM, SUBSYSTEM); + assertTrue(cut.isConnected()); + } + + private void teardownFile(String routerName) { + File file = new File(routerName); + if(file.exists() && file.isFile()) { + file.delete(); + } + } + + private void assertFileExist(String fileName) { + File file = new File(fileName); + assertTrue(file.exists()); + assertTrue(file.isFile()); + } + } |