2 * Copyright © 2019 Bell Canada
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.core
19 import org.mockito.Mockito
20 import org.mockito.kotlin.any
21 import org.junit.Before
23 import org.mockito.kotlin.never
24 import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.api.DeviceInfo
25 import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.api.NetconfReceivedEvent
26 import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.api.NetconfSession
27 import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.api.NetconfSessionListener
28 import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.utils.RpcMessageUtils
29 import java.io.IOException
30 import java.io.InputStream
31 import java.io.OutputStream
32 import java.nio.charset.StandardCharsets
33 import java.util.concurrent.CompletableFuture
34 import java.util.concurrent.ConcurrentHashMap
35 import java.util.regex.Pattern
36 import kotlin.test.assertEquals
37 import kotlin.test.assertFalse
38 import kotlin.test.assertTrue
40 class NetconfDeviceCommunicatorTest {
42 private lateinit var netconfSession: NetconfSession
43 private lateinit var netconfSessionListener: NetconfSessionListener
44 private lateinit var mockInputStream: InputStream
45 private lateinit var mockOutputStream: OutputStream
46 private lateinit var stubInputStream: InputStream
47 private lateinit var replies: MutableMap<String, CompletableFuture<String>>
48 private val endPatternCharArray: List<Int> = stringToCharArray(RpcMessageUtils.END_PATTERN)
52 private val chunkedEnding = "\n##\n"
54 // using example from section 4.2 of RFC6242 (https://tools.ietf.org/html/rfc6242#section-4.2)
55 private val validChunkedEncodedMsg = """
63 | xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
70 private fun stringToCharArray(str: String): List<Int> {
71 return str.toCharArray().map(Char::toInt)
76 netconfSession = Mockito.mock(NetconfSession::class.java)
77 netconfSessionListener = Mockito.mock(NetconfSessionListener::class.java)
78 mockInputStream = Mockito.mock(InputStream::class.java)
79 mockOutputStream = Mockito.mock(OutputStream::class.java)
80 replies = ConcurrentHashMap()
84 fun `NetconfDeviceCommunicator should read from supplied reader`() {
85 Mockito.`when`(mockInputStream.read()).thenReturn(-1)
86 Mockito.`when`(mockInputStream.read(any(), any(), any())).thenReturn(-1)
87 val communicator: NetconfDeviceCommunicator =
88 NetconfDeviceCommunicator(mockInputStream, mockOutputStream, genDeviceInfo(), netconfSessionListener, replies)
91 Mockito.verify(mockInputStream).read(any(), any(), any())
95 fun `NetconfDeviceCommunicator unregisters device on END_PATTERN`() {
96 // The reader will generate RpcMessageUtils.END_PATTERN "]]>]]>" which tells Netconf
97 // to unregister the device.
98 // we want to capture the slot to return the value as inputStreamReader will pass a char array
99 // create a slot where NetconfReceivedEvent will be placed to further verify Type.DEVICE_UNREGISTERED
100 val captured = mutableListOf<NetconfReceivedEvent>()
102 captured.add(it.getArgument(0))
103 }.`when`(netconfSessionListener).accept(any())
104 stubInputStream = RpcMessageUtils.END_PATTERN.byteInputStream(StandardCharsets.UTF_8)
106 val communicator = NetconfDeviceCommunicator(
107 stubInputStream, mockOutputStream,
108 genDeviceInfo(), netconfSessionListener, replies
112 assertTrue(captured.size == 1)
113 assertEquals(NetconfReceivedEvent.Type.DEVICE_UNREGISTERED, captured[0].type)
114 assertEquals(genDeviceInfo(), captured[0].deviceInfo)
118 fun `NetconfDeviceCommunicator on IOException generated DEVICE_ERROR event`() {
119 val captured = mutableListOf<NetconfReceivedEvent>()
121 captured.add(it.getArgument(0))
122 }.`when`(netconfSessionListener).accept(any())
123 stubInputStream = "".byteInputStream(StandardCharsets.UTF_8)
124 Mockito.`when`(mockInputStream.read(any(), any(), any()))
125 .thenReturn(1).thenThrow(IOException("Fake IO"))
127 val communicator = NetconfDeviceCommunicator(
128 mockInputStream, mockOutputStream,
129 genDeviceInfo(), netconfSessionListener, replies
133 assertTrue(captured.size == 1)
134 assertEquals(genDeviceInfo(), captured[0].deviceInfo)
135 assertEquals(NetconfReceivedEvent.Type.DEVICE_ERROR, captured[0].type)
139 fun `NetconfDeviceCommunicator in END_PATTERN state but fails RpcMessageUtils end pattern validation`() {
140 val captured = mutableListOf<NetconfReceivedEvent>()
142 captured.add(it.getArgument(0))
143 }.`when`(netconfSessionListener).accept(any())
144 val payload = "<rpc-reply>blah</rpc-reply>"
145 stubInputStream = "$payload${RpcMessageUtils.END_PATTERN}".byteInputStream(StandardCharsets.UTF_8)
147 val bytes = stubInputStream.readAllBytes()
148 bytes.forEachIndexed { index, byte ->
149 (it.getArgument(0) as ByteArray)[index] = byte
152 }.doReturn(-1).`when`(mockInputStream).read(any(), any(), any())
154 val communicator = NetconfDeviceCommunicator(
155 mockInputStream, mockOutputStream,
156 genDeviceInfo(), netconfSessionListener, replies
160 Mockito.verify(mockInputStream, never()).close() // make sure the reader is not closed as this could cause problems
161 assertTrue(captured.size == 1)
162 // eventually, sessionListener is called with message type DEVICE_REPLY
163 assertEquals(NetconfReceivedEvent.Type.DEVICE_REPLY, captured[0].type)
164 assertEquals(payload, captured[0].messagePayload)
168 fun `NetconfDeviceCommunicator in END_CHUNKED_PATTERN but validation failing produces DEVICE_ERROR`() {
169 val captured = mutableListOf<NetconfReceivedEvent>()
171 captured.add(it.getArgument(0))
172 }.`when`(netconfSessionListener).accept(any())
173 val payload = "<rpc-reply>blah</rpc-reply>"
174 val payloadWithChunkedEnding = "$payload$chunkedEnding"
176 stubInputStream = payloadWithChunkedEnding.byteInputStream(StandardCharsets.UTF_8)
178 val bytes = stubInputStream.readAllBytes()
179 bytes.forEachIndexed { index, byte ->
180 (it.getArgument(0) as ByteArray)[index] = byte
183 }.doReturn(-1).`when`(mockInputStream).read(any(), any(), any())
185 val communicator = NetconfDeviceCommunicator(
186 mockInputStream, mockOutputStream, genDeviceInfo(),
187 netconfSessionListener, replies
191 Mockito.verify(mockInputStream, never()).close() // make sure the reader is not closed as this could cause problems
192 assertTrue(captured.size == 1)
193 // eventually, sessionListener is called with message type DEVICE_REPLY
194 assertEquals(NetconfReceivedEvent.Type.DEVICE_ERROR, captured[0].type)
195 assertEquals("", captured[0].messagePayload)
199 fun `NetconfDeviceCommunicator in END_CHUNKED_PATTERN passing validation generates DEVICE_REPLY`() {
200 val captured = mutableListOf<NetconfReceivedEvent>()
202 captured.add(it.getArgument(0))
203 }.`when`(netconfSessionListener).accept(any())
204 stubInputStream = validChunkedEncodedMsg.byteInputStream(StandardCharsets.UTF_8)
206 val bytes = stubInputStream.readAllBytes()
207 bytes.forEachIndexed { index, byte ->
208 (it.getArgument(0) as ByteArray)[index] = byte
211 }.doReturn(-1).`when`(mockInputStream).read(any(), any(), any())
213 NetconfDeviceCommunicator(mockInputStream, mockOutputStream, genDeviceInfo(), netconfSessionListener, replies).join()
215 Mockito.verify(mockOutputStream, never()).close() // make sure the reader is not closed as this could cause problems
216 assertTrue(captured.size == 1)
217 // eventually, sessionListener is called with message type DEVICE_REPLY
218 assertEquals(NetconfReceivedEvent.Type.DEVICE_REPLY, captured[0].type)
221 <rpc message-id="102"
222 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
226 captured[0].messagePayload
231 // test to ensure that we have a valid test message to be then used in the case of chunked message
232 // validation code path
233 fun `chunked sample is validated by the chunked response regex`() {
234 val test1 = "\n#10\nblah\n##\n"
235 val chunkedFramingPattern = Pattern.compile("(\\n#([1-9][0-9]*)\\n(.+))+\\n##\\n", Pattern.DOTALL)
236 val matcher = chunkedFramingPattern.matcher(test1)
237 assertTrue { matcher.matches() }
241 // Verify that our test sample passes the second pattern for chunked size
242 fun `chunkSizeMatcher pattern finds matches in chunkedMessageSample`() {
243 val sizePattern = Pattern.compile("\\n#([1-9][0-9]*)\\n")
244 val matcher = sizePattern.matcher(validChunkedEncodedMsg)
245 assertTrue { matcher.find() }
249 fun `sendMessage writes the request to NetconfDeviceCommunicator Writer`() {
250 val msgPayload = "some text"
252 stubInputStream = "".byteInputStream(StandardCharsets.UTF_8) // no data available in the stream...
254 val communicator = NetconfDeviceCommunicator(
255 stubInputStream, mockOutputStream,
256 genDeviceInfo(), netconfSessionListener, replies
258 val completableFuture = communicator.sendMessage(msgPayload, msgId)
261 Mockito.verify(mockOutputStream).write(any(), any(), any())
262 Mockito.verify(mockOutputStream).flush()
263 assertFalse { completableFuture.isCompletedExceptionally }
267 fun `sendMessage on IOError returns completed exceptionally future`() {
268 val msgPayload = "some text"
270 Mockito.`when`(mockOutputStream.write(any(), any(), any()))
271 .thenThrow(IOException("Some IO error occurred!"))
272 stubInputStream = "".byteInputStream(StandardCharsets.UTF_8) // no data available in the stream...
274 val communicator = NetconfDeviceCommunicator(
275 stubInputStream, mockOutputStream,
276 genDeviceInfo(), netconfSessionListener, replies
278 val completableFuture = communicator.sendMessage(msgPayload, msgId)
280 Mockito.verify(mockOutputStream).write(any(), any(), any())
281 Mockito.verify(mockOutputStream, never()).flush()
282 assertTrue { completableFuture.isCompletedExceptionally }
285 private fun genDeviceInfo(): DeviceInfo {
286 return DeviceInfo().apply {
289 ipAddress = "localhost"