summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--cds-ui/server/.dockerignore4
-rw-r--r--cds-ui/server/Dockerfile28
-rw-r--r--cds-ui/server/pom.xml69
-rw-r--r--ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/api/DeviceInfo.kt10
-rw-r--r--ms/blueprintsprocessor/functions/netconf-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/core/NetconfDeviceCommunicatorTest.kt260
6 files changed, 372 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 5dbd1d403..e3e0e1f22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -144,3 +144,4 @@ MacOS
# To Remove Kotlin Script Generated Jars
**/*cba-kts.jar
+/target/
diff --git a/cds-ui/server/.dockerignore b/cds-ui/server/.dockerignore
new file mode 100644
index 000000000..3b3ee0060
--- /dev/null
+++ b/cds-ui/server/.dockerignore
@@ -0,0 +1,4 @@
+node_modules
+npm-debug.log
+/dist
+
diff --git a/cds-ui/server/Dockerfile b/cds-ui/server/Dockerfile
new file mode 100644
index 000000000..bca90e201
--- /dev/null
+++ b/cds-ui/server/Dockerfile
@@ -0,0 +1,28 @@
+# Check out https://hub.docker.com/_/node to select a new base image
+FROM node:10-slim
+
+# Set to a non-root built-in user `node`
+USER node
+
+# Create app directory (with user `node`)
+RUN mkdir -p /home/node/app
+
+WORKDIR /home/node/app
+
+# Install app dependencies
+# A wildcard is used to ensure both package.json AND package-lock.json are copied
+# where available (npm@5+)
+COPY --chown=node package*.json ./
+
+RUN npm install
+
+# Bundle app source code
+COPY --chown=node . .
+
+RUN npm run build
+
+# Bind to all network interfaces so that it can be mapped to the host OS
+ENV HOST=0.0.0.0 PORT=3000
+
+EXPOSE ${PORT}
+CMD [ "node", "." ]
diff --git a/cds-ui/server/pom.xml b/cds-ui/server/pom.xml
index 59b14b890..1c05d5521 100644
--- a/cds-ui/server/pom.xml
+++ b/cds-ui/server/pom.xml
@@ -39,6 +39,8 @@ limitations under the License.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<npm.executable>npm</npm.executable>
<onap.nexus.url>https://nexus.onap.org</onap.nexus.url>
+ <image.name>onap/ccsdk-cds-ui-server</image.name>
+ <docker.push.phase>deploy</docker.push.phase>
</properties>
<build>
@@ -91,6 +93,73 @@ limitations under the License.
</executions>
</plugin>
+ <plugin>
+ <groupId>org.codehaus.groovy.maven</groupId>
+ <artifactId>gmaven-plugin</artifactId>
+ <version>1.0</version>
+ <executions>
+ <execution>
+ <phase>validate</phase>
+ <goals>
+ <goal>execute</goal>
+ </goals>
+ <configuration>
+ <source>${basedir}/../../TagVersion.groovy</source>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
+
+ <profiles>
+ <profile>
+ <id>docker</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>io.fabric8</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <version>0.26.1</version>
+ <inherited>false</inherited>
+ <configuration>
+ <images>
+ <image>
+ <name>${image.name}</name>
+ <build>
+ <cleanup>try</cleanup>
+ <dockerFileDir>${basedir}</dockerFileDir>
+ <tags>
+ <tag>${project.docker.latestminortag.version}</tag>
+ <tag>${project.docker.latestfulltag.version}</tag>
+ <tag>${project.docker.latesttagtimestamp.version}</tag>
+ </tags>
+ </build>
+ </image>
+ </images>
+ <verbose>true</verbose>
+ </configuration>
+ <executions>
+ <execution>
+ <id>generate-images</id>
+ <phase>package</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>push-images</id>
+ <phase>${docker.push.phase}</phase>
+ <goals>
+ <goal>build</goal>
+ <goal>push</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ </profiles>
</project>
diff --git a/ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/api/DeviceInfo.kt b/ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/api/DeviceInfo.kt
index 5eeaf6046..f5567b7a2 100644
--- a/ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/api/DeviceInfo.kt
+++ b/ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/api/DeviceInfo.kt
@@ -40,4 +40,14 @@ class DeviceInfo {
override fun toString(): String {
return "$ipAddress:$port"
}
+ //TODO: should this be a data class instead? Is anything using the JSON serdes?
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
} \ No newline at end of file
diff --git a/ms/blueprintsprocessor/functions/netconf-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/core/NetconfDeviceCommunicatorTest.kt b/ms/blueprintsprocessor/functions/netconf-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/core/NetconfDeviceCommunicatorTest.kt
new file mode 100644
index 000000000..cb023fd9a
--- /dev/null
+++ b/ms/blueprintsprocessor/functions/netconf-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/core/NetconfDeviceCommunicatorTest.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright © 2019 Bell Canada
+ *
+ * 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.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.core
+
+import io.mockk.CapturingSlot
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.spyk
+import io.mockk.verify
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.api.DeviceInfo
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.api.NetconfReceivedEvent
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.api.NetconfSession
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.api.NetconfSessionListener
+import org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor.utils.RpcMessageUtils
+import java.io.ByteArrayInputStream
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.io.Reader
+import java.io.Writer
+import java.nio.charset.StandardCharsets
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ConcurrentHashMap
+import java.util.regex.Pattern
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class NetconfDeviceCommunicatorTest {
+ private lateinit var netconfSession: NetconfSession
+ private lateinit var netconfSessionListener: NetconfSessionListener
+ private lateinit var mockInputStream: InputStream
+ private lateinit var mockOutputStream: OutputStream
+ private lateinit var stubInputStream: InputStream
+ private lateinit var replies: MutableMap<String, CompletableFuture<String>>
+ private val endPatternCharArray: List<Int> = stringToCharArray(RpcMessageUtils.END_PATTERN)
+
+
+ companion object {
+ private val chunkedEnding = "\n##\n"
+ //using example from section 4.2 of RFC6242 (https://tools.ietf.org/html/rfc6242#section-4.2)
+ private val validChunkedEncodedMsg = """
+ |
+ |#4
+ |<rpc
+ |
+ |#18
+ | message-id="102"
+ |
+ |#79
+ | xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ | <close-session/>
+ |</rpc>
+ |##
+ |""".trimMargin()
+ }
+
+ private fun stringToCharArray(str: String): List<Int> {
+ return str.toCharArray().map(Char::toInt)
+ }
+
+ @Before
+ fun setup() {
+ netconfSession = mockk()
+ netconfSessionListener = mockk()
+ mockInputStream = mockk()
+ mockOutputStream = mockk()
+ replies = ConcurrentHashMap()
+ }
+
+ @Test
+ fun `NetconfDeviceCommunicator should read from supplied reader`() {
+ every { mockInputStream.read() } returns -1
+ every { mockInputStream.read(any(), any(), any()) } returns -1
+ val communicator: NetconfDeviceCommunicator =
+ NetconfDeviceCommunicator(mockInputStream, mockOutputStream, genDeviceInfo(), netconfSessionListener, replies)
+ communicator.join()
+ //verify
+ verify { mockInputStream.read(any(), any(), any()) }
+ }
+
+ @Test
+ fun `NetconfDeviceCommunicator unregisters device on END_PATTERN`() {
+ //The reader will generate RpcMessageUtils.END_PATTERN "]]>]]>" which tells Netconf
+ //to unregister the device.
+ //we want to capture the slot to return the value as inputStreamReader will pass a char array
+ //create a slot where NetconfReceivedEvent will be placed to further verify Type.DEVICE_UNREGISTERED
+ val eventSlot = CapturingSlot<NetconfReceivedEvent>()
+ every { netconfSessionListener.accept(event = capture(eventSlot)) } just Runs
+ stubInputStream = RpcMessageUtils.END_PATTERN.byteInputStream(StandardCharsets.UTF_8)
+ val inputStreamSpy = spyk(stubInputStream)
+ //RUN the test
+ val communicator = NetconfDeviceCommunicator(inputStreamSpy, mockOutputStream,
+ genDeviceInfo(), netconfSessionListener, replies)
+ communicator.join()
+ //Verify
+ verify { inputStreamSpy.close() }
+ assertTrue { eventSlot.isCaptured }
+ assertEquals(NetconfReceivedEvent.Type.DEVICE_UNREGISTERED, eventSlot.captured.type)
+ assertEquals(genDeviceInfo(), eventSlot.captured.deviceInfo)
+ }
+
+ @Test
+ fun `NetconfDeviceCommunicator on IOException generated DEVICE_ERROR event`() {
+ val eventSlot = CapturingSlot<NetconfReceivedEvent>()
+ every { netconfSessionListener.accept(event = capture(eventSlot)) } just Runs
+ stubInputStream = "".byteInputStream(StandardCharsets.UTF_8)
+ val inputStreamSpy = spyk(stubInputStream)
+ every { inputStreamSpy.read(any(), any(), any()) } returns 1 andThenThrows IOException("Fake IO Exception")
+ //RUN THE TEST
+ val communicator = NetconfDeviceCommunicator(inputStreamSpy, mockOutputStream,
+ genDeviceInfo(), netconfSessionListener, replies)
+ communicator.join()
+ //Verify
+ assertTrue { eventSlot.isCaptured }
+ assertEquals(genDeviceInfo(), eventSlot.captured.deviceInfo)
+ assertEquals(NetconfReceivedEvent.Type.DEVICE_ERROR, eventSlot.captured.type)
+ }
+
+ @Test
+ fun `NetconfDeviceCommunicator in END_PATTERN state but fails RpcMessageUtils end pattern validation`() {
+ val eventSlot = CapturingSlot<NetconfReceivedEvent>()
+ val payload = "<rpc-reply>blah</rpc-reply>"
+ stubInputStream = "$payload${RpcMessageUtils.END_PATTERN}".byteInputStream(StandardCharsets.UTF_8)
+ every { netconfSessionListener.accept(event = capture(eventSlot)) } just Runs
+ //RUN the test
+ val communicator = NetconfDeviceCommunicator(stubInputStream, mockOutputStream,
+ genDeviceInfo(), netconfSessionListener, replies)
+ communicator.join()
+ //Verify
+ verify(exactly = 0) { mockInputStream.close() } //make sure the reader is not closed as this could cause problems
+ assertTrue { eventSlot.isCaptured }
+ //eventually, sessionListener is called with message type DEVICE_REPLY
+ assertEquals(NetconfReceivedEvent.Type.DEVICE_REPLY, eventSlot.captured.type)
+ assertEquals(payload, eventSlot.captured.messagePayload)
+ }
+
+ @Test
+ fun `NetconfDeviceCommunicator in END_CHUNKED_PATTERN but validation failing produces DEVICE_ERROR`() {
+ val eventSlot = CapturingSlot<NetconfReceivedEvent>()
+ val payload = "<rpc-reply>blah</rpc-reply>"
+ val payloadWithChunkedEnding = "$payload$chunkedEnding"
+ every { netconfSessionListener.accept(event = capture(eventSlot)) } just Runs
+
+ stubInputStream = payloadWithChunkedEnding.byteInputStream(StandardCharsets.UTF_8)
+ //we have to ensure that the input stream is processed, so need to create a spy object.
+ val inputStreamSpy = spyk(stubInputStream)
+ //RUN the test
+ val communicator = NetconfDeviceCommunicator(inputStreamSpy, mockOutputStream, genDeviceInfo(),
+ netconfSessionListener, replies)
+ communicator.join()
+ //Verify
+ verify(exactly = 0) { inputStreamSpy.close() } //make sure the reader is not closed as this could cause problems
+ assertTrue { eventSlot.isCaptured }
+ //eventually, sessionListener is called with message type DEVICE_REPLY
+ assertEquals(NetconfReceivedEvent.Type.DEVICE_ERROR, eventSlot.captured.type)
+ assertEquals("", eventSlot.captured.messagePayload)
+ }
+
+ @Ignore //TODO: Not clear on validateChunkedFraming, the size validation part could be discarding valid msg..
+ @Test
+ fun `NetconfDeviceCommunicator in END_CHUNKED_PATTERN passing validation generates DEVICE_REPLY`() {
+ val eventSlot = CapturingSlot<NetconfReceivedEvent>()
+ stubInputStream = validChunkedEncodedMsg.byteInputStream(StandardCharsets.UTF_8)
+ val inputStreamSpy = spyk(stubInputStream)
+ every { netconfSessionListener.accept(event = capture(eventSlot)) } just Runs
+ //RUN the test
+ NetconfDeviceCommunicator(inputStreamSpy, mockOutputStream, genDeviceInfo(), netconfSessionListener, replies).join()
+ //Verify
+ verify(exactly = 0) { inputStreamSpy.close() } //make sure the reader is not closed as this could cause problems
+ assertTrue { eventSlot.isCaptured }
+ //eventually, sessionListener is called with message type DEVICE_REPLY
+ assertEquals(NetconfReceivedEvent.Type.DEVICE_REPLY, eventSlot.captured.type)
+ assertEquals("", eventSlot.captured.messagePayload)
+ }
+
+ @Test
+ //test to ensure that we have a valid test message to be then used in the case of chunked message
+ // validation code path
+ fun `chunked sample is validated by the chunked response regex`() {
+ val test1 = "\n#10\nblah\n##\n"
+ val chunkedFramingPattern = Pattern.compile("(\\n#([1-9][0-9]*)\\n(.+))+\\n##\\n", Pattern.DOTALL)
+ val matcher = chunkedFramingPattern.matcher(validChunkedEncodedMsg)
+ assertTrue { matcher.matches() }
+ }
+
+ @Test
+ //Verify that our test sample passes the second pattern for chunked size
+ fun `chunkSizeMatcher pattern finds matches in chunkedMessageSample`() {
+ val sizePattern = Pattern.compile("\\n#([1-9][0-9]*)\\n")
+ val matcher = sizePattern.matcher(validChunkedEncodedMsg)
+ assertTrue { matcher.find() }
+ }
+
+ @Test
+ fun `sendMessage writes the request to NetconfDeviceCommunicator Writer`() {
+ val msgPayload = "some text"
+ val msgId = "100"
+ stubInputStream = "".byteInputStream(StandardCharsets.UTF_8) //no data available in the stream...
+ every { mockOutputStream.write(any(), any(), any()) } just Runs
+ every { mockOutputStream.write(msgPayload.toByteArray(Charsets.UTF_8)) } just Runs
+ every { mockOutputStream.flush() } just Runs
+ //Run the command
+ val communicator = NetconfDeviceCommunicator(
+ stubInputStream, mockOutputStream,
+ genDeviceInfo(), netconfSessionListener, replies)
+ val completableFuture = communicator.sendMessage(msgPayload, msgId)
+ communicator.join()
+ //verify
+ verify { mockOutputStream.write(any(), any(), any()) }
+ verify { mockOutputStream.flush() }
+ assertFalse { completableFuture.isCompletedExceptionally }
+ }
+
+ @Test
+ fun `sendMessage on IOError returns completed exceptionally future`() {
+ val msgPayload = "some text"
+ val msgId = "100"
+ every { mockOutputStream.write(any(), any(), any()) } throws IOException("Some IO error occurred!")
+ stubInputStream = "".byteInputStream(StandardCharsets.UTF_8) //no data available in the stream...
+ //Run the command
+ val communicator = NetconfDeviceCommunicator(
+ stubInputStream, mockOutputStream,
+ genDeviceInfo(), netconfSessionListener, replies)
+ val completableFuture = communicator.sendMessage(msgPayload, msgId)
+ //verify
+ verify { mockOutputStream.write(any(), any(), any()) }
+ verify(exactly = 0) { mockOutputStream.flush() }
+ assertTrue { completableFuture.isCompletedExceptionally }
+ }
+
+ private fun genDeviceInfo(): DeviceInfo {
+ return DeviceInfo().apply {
+ username = "user"
+ password = "pass"
+ ipAddress = "localhost"
+ port = 4567
+ }
+ }
+
+} \ No newline at end of file