aboutsummaryrefslogtreecommitdiffstats
path: root/netconfsimulator
diff options
context:
space:
mode:
authorBartosz Gardziejewski <bartosz.gardziejewski@nokia.com>2020-04-08 09:31:13 +0200
committerBogumil Zebek <bogumil.zebek@nokia.com>2020-04-08 09:43:31 +0000
commit3c494af52c476a86ae1389991b464914517774b8 (patch)
treee6d9b4f261eac5f7b3fd0f42e740840a106842e6 /netconfsimulator
parent75496bfc5b2f7e03e49ab4929d1f20962b39c992 (diff)
Move PNF simulator from /test/mocks to new project
This code is a copy of pnfsimulator located in integration repository (/test/mocks/pnfsimulator) with added profile "docker" in pom.xml located in pnfsimulator and netconfsimulator subprojects Issue-ID: INT-1517 Signed-off-by: Bartosz Gardziejewski <bartosz.gardziejewski@nokia.com> Change-Id: I725fa0530c41b13cb12705979dee8b8b354dc1a1
Diffstat (limited to 'netconfsimulator')
-rw-r--r--netconfsimulator/Dockerfile_app4
-rw-r--r--netconfsimulator/Dockerfile_netopeer6
-rw-r--r--netconfsimulator/README.md297
-rw-r--r--netconfsimulator/apt.conf5
-rw-r--r--netconfsimulator/config/netconf.env5
-rw-r--r--netconfsimulator/docker-compose.yml93
-rw-r--r--netconfsimulator/docker/Dockerfile4
-rw-r--r--netconfsimulator/ftpes/files/ftpes-noone.txt0
-rw-r--r--netconfsimulator/ftpes/files/onap/ftpes-onap.txt0
-rwxr-xr-xnetconfsimulator/ftpes/tls/pure-ftpd.pem49
-rwxr-xr-xnetconfsimulator/ftpes/userpass/pureftpd.passwd1
-rw-r--r--netconfsimulator/netconf/__init__.py19
-rwxr-xr-xnetconfsimulator/netconf/initialize_netopeer.sh37
-rw-r--r--netconfsimulator/netconf/load_server_certs.xml44
-rw-r--r--netconfsimulator/netconf/netopeer_change_saver.py107
-rw-r--r--netconfsimulator/netconf/newmodel.xml24
-rw-r--r--netconfsimulator/netconf/newmodel.yang9
-rw-r--r--netconfsimulator/netconf/pnf-simulator.data.xml24
-rw-r--r--netconfsimulator/netconf/pnf-simulator.yang9
-rwxr-xr-xnetconfsimulator/netconf/set-up-xmls.py162
-rw-r--r--netconfsimulator/netconf/test_yang_loader_server.py121
-rw-r--r--netconfsimulator/netconf/tls_listen.xml49
-rw-r--r--netconfsimulator/netconf/yang_loader.log1
-rw-r--r--netconfsimulator/netconf/yang_loader_server.py172
-rw-r--r--netconfsimulator/pom.xml318
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/Configuration.java34
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/Main.java31
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/SwaggerConfig.java43
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/Config.java70
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/MessageDTO.java31
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/StoreController.java59
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/StoreService.java91
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerEntry.java36
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerHandler.java67
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/model/KafkaMessage.java37
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/NetconfController.java111
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfBeanConfiguration.java60
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationEditor.java50
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationReader.java57
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationService.java76
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationTO.java32
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConnectionParams.java37
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfSessionHelper.java37
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/model/LoadModelResponse.java40
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/model/NetconfModelLoaderService.java104
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/EndpointConfig.java46
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/NetconfEndpoint.java95
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/message/NetconfMessageEncoder.java34
-rw-r--r--netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/message/NetconfMessageListener.java51
-rw-r--r--netconfsimulator/src/main/resources/application.properties8
-rw-r--r--netconfsimulator/src/test/java/integration/NetconfFunctionsIT.java211
-rw-r--r--netconfsimulator/src/test/java/integration/NetconfSimulatorClient.java150
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/EmbeddedKafkaConfig.java69
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/StoreControllerTest.java86
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/StoreServiceTest.java103
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerHandlerTest.java87
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/NetconfControllerTest.java172
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationEditorTest.java69
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationReaderTest.java94
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationServiceTest.java102
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/model/NetconfModelLoaderServiceTest.java121
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/websocket/NetconfEndpointTest.java135
-rw-r--r--netconfsimulator/src/test/java/org/onap/netconfsimulator/websocket/message/NetconfMessageListenerTest.java73
-rw-r--r--netconfsimulator/src/test/resources/application-it.properties0
-rw-r--r--netconfsimulator/src/test/resources/initialConfig.xml23
-rw-r--r--netconfsimulator/src/test/resources/invalidXmlFile.xml23
-rw-r--r--netconfsimulator/src/test/resources/newYangModel.yang8
-rw-r--r--netconfsimulator/src/test/resources/updatedConfig.xml24
-rw-r--r--netconfsimulator/src/test/resources/updatedConfigForCmHistory.xml24
-rw-r--r--netconfsimulator/ssh/ssh_host_rsa_key49
-rw-r--r--netconfsimulator/ssh/ssh_host_rsa_key.pub1
-rw-r--r--netconfsimulator/tls/ca.crt32
-rw-r--r--netconfsimulator/tls/ca.key54
-rw-r--r--netconfsimulator/tls/client.crt30
-rw-r--r--netconfsimulator/tls/client.key51
-rw-r--r--netconfsimulator/tls/client.req28
-rw-r--r--netconfsimulator/tls/server.req28
-rw-r--r--netconfsimulator/tls/server_cert.crt30
-rw-r--r--netconfsimulator/tls/server_key.pem51
79 files changed, 4825 insertions, 0 deletions
diff --git a/netconfsimulator/Dockerfile_app b/netconfsimulator/Dockerfile_app
new file mode 100644
index 0000000..a775219
--- /dev/null
+++ b/netconfsimulator/Dockerfile_app
@@ -0,0 +1,4 @@
+FROM openjdk:8-jre-alpine
+ADD target/libs /app/libs
+ADD target/netconfsimulator-5.0.0-SNAPSHOT.jar /app/netconf-simulator.jar
+CMD java -cp /app/libs/*:/app/netconf-simulator.jar org.onap.netconfsimulator.Main \ No newline at end of file
diff --git a/netconfsimulator/Dockerfile_netopeer b/netconfsimulator/Dockerfile_netopeer
new file mode 100644
index 0000000..9fe56e1
--- /dev/null
+++ b/netconfsimulator/Dockerfile_netopeer
@@ -0,0 +1,6 @@
+FROM sysrepo/sysrepo-netopeer2:v0.7.7
+ADD apt.conf /etc/apt/apt.conf
+RUN apt-get update && apt-get install -y python3 python3-pip python-pip && pip3 install flask flask_restful kafka-python && pip install kafka-python
+RUN cd /opt/dev/sysrepo && cmake -DGEN_PYTHON_VERSION=2 -DREPOSITORY_LOC:PATH=/etc/sysrepo . && make install
+
+CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
diff --git a/netconfsimulator/README.md b/netconfsimulator/README.md
new file mode 100644
index 0000000..b825226
--- /dev/null
+++ b/netconfsimulator/README.md
@@ -0,0 +1,297 @@
+# Netconf Simulator
+A simulator that is able to receive and print history of CM configurations.
+
+## Required software
+To run the simulator, the following software should be installed:
+- JDK 1.8
+- Maven
+- docker
+- docker-compose
+
+### API
+Simulator exposes both HTTP and native netconf interface.
+
+### Running simulator
+In order to run simulator, invoke *mvn clean install docker:build* to build required images.
+Add executable permission to initialize_netopeer.sh (by executing `sudo chmod +x netconf/initialize_netopeer.sh`)
+and then invoke *docker-compose up* command.
+In case of copying simulator files to another location, keep in mind to copy also *docker-compose.yml* and directories: *config, templates, netopeer-change-saver-native and netconf*.
+
+#### Restarting
+Restarting simulator can be done by first typing *docker-compose restart* in terminal.
+
+#### Shutting down
+The command *docker-compose down* can be used to shut the simulator down.
+
+## Usage of simulator
+
+### Netconf TLS support
+Embedded netconf server supports connections over TLS on port 6513. Default server and CA certificate have been generated using method described below. Please proceed with these steps to recreate own certificates. Important is to fulfill all needed data during certificate preparation because Netconf verifies certs description pretty strictly.
+
+Mentioned Github repository contains sample client certificate, which works out of the box.
+
+#### Replacing server certificates
+In order to replace TLS certificates with third-party ones, the following naming schema must be followed:
+* CA certificate file should be named 'ca.crt'
+* Netconf server certificate file should be named 'server_cert.crt'
+* Netconf server keyfile file should be named 'server_key.pem'
+* Client certificate file should be named 'client.crt'
+* Client keyfile should be named 'client.key'
+
+Certificates and keys should follow PEM formatting guidelines.
+Prepared files should be placed under _tls/_ directory (existing files must be overwritten).
+After copying, it is necessary to restart the Netconf Simulator (please refer to [restarting simulator](restarting) guide).
+
+This is a sample curl command to test client connection (the example assumes that Netconf Simulator runs on 127.0.0.1):
+```
+curl --cacert ca.crt --cert client.crt --key client.key https://127.0.0.1:6513 -kv --http0.9
+```
+or using openssl:
+```
+openssl s_client -connect 127.0.0.1:6513 -cert client.crt -key client.key -CAfile ca.crt
+```
+
+To regenerate all required certificates follow steps:
+1. Generate your private key and public certificate: ```openssl req -newkey rsa:4096 -keyform PEM -keyout ca.key -x509 -days 3650 -outform PEM -out ca.crt```
+2. Create a private client key:```openssl genrsa -out client.key 4096```
+3. Generate certificate signing request:```openssl req -new -key client.key -out client.req```
+4. Generating signed client certificate: ```openssl x509 -req -in client.req -CA ca.crt -CAkey ca.key -set_serial 101 -extensions client -days 365 -outform PEM -out client.crt```
+5. Create a private server key:```openssl genrsa -out server_key.pem 4096```
+6. Generate certificate signing request:```openssl req -new -key server_key.pem -out server.req -sha256```
+7. Generating signed server certificate: ```openssl x509 -req -in server.req -CA ca.crt -CAkey ca.key -set_serial 100 -extensions server -days 1460 -outform PEM -out server_cert.crt -sha256```
+
+Client authenticates using described TLS configuration, their username will resolve to test (more information in tls_listen.xml under the cert-to-name section). It is required that this username exists on the local system (just like for SSH), so you will need to (temporarily) create this user. The simplest way is executing # useradd -MN test, which creates the user without a home directory and user group.
+
+Currently by default there is only a possibility to substitute existing certificates for single user.
+```
+
+
+### Capturing netconf configuration changes
+
+The netconfsimulator tool will intercept changes in netconf configuration, done by edit-config command (invoked through simulator's edit-configuration endpoint or directly through exposed netconf-compliant interface). The following changes are intercepted:
+- creating new item
+- moving an item
+- modifying an item
+- deleting an item
+
+Each captured change contains fully qualified parameter name (including xpath - namespace and container name)
+
+#### REST API usage with examples
+
+Application of native netconf operations on YANG model is covered by REST API layer.
+Example invocation of operations with its requests and results are presented below.
+For basic edit-config and get config actions, response is in plain XML format, whereas stored data that can be accessed via API is returned in JSON format.
+
+**Load new YANG model**
+http method: POST
+```
+URL: http:<simulator_ip>:9000/netconf/model/<moduleName>
+```
+request: file content to be sent as multipart (form data)
+```
+module pnf-simulator {
+ namespace "http://onap.org/pnf-simulator";
+ prefix config;
+ container config {
+ config true;
+ leaf itemValue1 {type uint32;}
+ leaf itemValue2 {type uint32;}
+ leaf itemValue3 {type uint32;}
+ leaf-list allow-user {
+ type string;
+ ordered-by user;
+ description "A sample list of user names.";
+ }
+ }
+}
+```
+
+**Delete existing YANG model**
+http method: DELETE
+```
+URL: http:<simulator_ip>:9000/netconf/model/<moduleName>
+```
+request body should be empty.
+response: a HTTP 200 code indicating successful operation or 400/500 in case of errors.
+
+**Get all running configurations**
+http method: GET
+```
+URL: http:<simulator_ip>:9000/netconf/get
+```
+response: plain XML
+```
+<config xmlns="http://onap.org/pnf-simulator" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <itemValue1>2781</itemValue1>
+ <itemValue2>3782</itemValue2>
+ <itemValue3>3333</itemValue3>
+</config>
+<config2 xmlns="http://onap.org/pnf-simulator2" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <itemValue1>2781</itemValue1>
+ <itemValue2>3782</itemValue2>
+ <itemValue3>3333</itemValue3>
+</config2>
+```
+
+**Get running configuration**
+http method: GET
+```
+URL: http:<simulator_ip>:9000/netconf/get/'moduleName'/'container'
+```
+response: plain XML
+```
+<config xmlns="http://onap.org/pnf-simulator" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <itemValue1>2781</itemValue1>
+ <itemValue2>3782</itemValue2>
+ <itemValue3>3333</itemValue3>
+</config>
+```
+
+**Edit configuration**
+To edit configuration XML file must be prepared. No plain request body is used here,
+request content must be passed as multipart file (form data) with file name/key='editConfigXml' and file content in XML format
+
+http method: POST
+```
+URL: http:<simulator_ip>:9000/netconf/edit-config
+```
+request: file content to be sent as multipart (form data)
+```
+<config xmlns="http://onap.org/pnf-simulator">
+ <itemValue1>2781</itemValue1>
+ <itemValue2>3782</itemValue2>
+ <itemValue3>3333</itemValue3>
+</config>
+```
+
+response: actual, running configuration after editing config:
+```
+<config xmlns="http://onap.org/pnf-simulator" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <itemValue1>2781</itemValue1>
+ <itemValue2>3782</itemValue2>
+ <itemValue3>3333</itemValue3>
+</config>"
+```
+
+Captured change, that can be obtained from db also via REST API:
+
+http method: GET
+```
+URL: http://<simulator_ip>:9000/store/less?offset=1
+```
+response:
+```
+[{"timestamp": 1542877413979, "configuration": "CREATED: /pnf-simulator:config/itemValue3 = 3333"}]
+```
+
+Notice: if new value is the same as the old one, the change won’t be intercepted (because there is no state change). This is a limitation of used netconf implementation (Netopeer2).
+
+**Modify request**
+http method: POST
+```
+URL: http:<simulator_ip>:9000/netconf/edit-config
+```
+file content to be sent as multipart (form data):
+```
+<config xmlns="http://onap.org/pnf-simulator" >
+ <itemValue1>111</itemValue1>
+ <itemValue2>222</itemValue2>
+</config>
+```
+
+response: actual, running configuration after editing config:
+```
+<config xmlns="http://onap.org/pnf-simulator" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <itemValue1>111</itemValue1>
+ <itemValue2>222</itemValue2>
+</config>"
+```
+
+Captured change:
+http method: GET
+```
+URL: http://<simulator_ip>:9000/store/less?offset=2
+```
+```
+[{"timestamp": 1542877413979, "configuration": "MODIFIED: : old value: /pnf-simulator:config/itemValue1 = 2781, new value: /pnf-simulator:config/itemValue1 = 111",
+ {"timestamp": 1542877413979, "configuration": "MODIFIED: : old value: /pnf-simulator:config/itemValue2 = 3782, new value: /pnf-simulator:config/itemValue2 = 222"}]
+```
+
+**Move request** (inserting a value into leaf-list which in turn rearranges remaining elements)
+http method: POST
+```
+URL: http:<simulator_ip>:9000/netconf/edit-config
+```
+file content to be sent as multipart (form data):
+```
+<config xmlns="http://onap.org/pnf-simulator" xmlns:yang="urn:ietf:params:xml:ns:yang:1" xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <allow-user xc:operation="create" yang:insert="before" yang:value="bob">mike</allow-user>
+</config>
+```
+
+Captured change:
+http method: GET
+```
+URL: http://<simulator_ip>:9000/store/less?offset=2
+```
+```
+[{"timestamp": 1542877413979, "configuration": "CREATED: /pnf-simulator:config/allow-user = mike"},
+ {"timestamp": 1542877413979, "configuration": "MOVED: /pnf-simulator:config/allow-user = mike after /pnf-simulator:config/allow-user = alice"}]
+```
+
+**Delete request**
+http method: POST
+```
+URL: http:<simulator_ip>:9000/netconf/edit-config
+```
+file content to be sent as multipart (form data):
+```
+<config xmlns="http://onap.org/pnf-simulator">
+ <itemValue1>1111</itemValue1>
+ <itemValue2 xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0" xc:operation="delete"/>
+</config>
+```
+
+Captured change:
+http method: GET
+```
+URL: http://<simulator_ip>:9000/store/less?offset=1
+```
+```
+[{"timestamp": 1542877413979, "configuration": "DELETED: /pnf-simulator:config/itemValue2 = 222"}]
+```
+
+Getting all configuration changes:
+http method: GET
+```
+URL: http://<simulator_ip>:9000/store/cm-history
+```
+response:
+```
+[{"timestamp":1542877413979,"configuration":"MODIFIED: : old value: /pnf-simulator:config/itemValue1 = 2781, new value: /pnf-simulator:config/itemValue1 = 111"},
+ {"timestamp":1542877413979,"configuration":"MODIFIED: : old value: /pnf-simulator:config/itemValue2 = 3782, new value: /pnf-simulator:config/itemValue2 = 222"},
+ {"timestamp":1542877414000,"configuration":"CREATED: : /pnf-simulator:config/itemValue3 = 3333"},
+ {"timestamp":1542877414104,"configuration":"CREATED: : CREATED: /pnf-simulator:config/allow-user = mike"}
+ {"timestamp":1542877414107,"configuration":"MOVED: /pnf-simulator:config/allow-user = mike after /pnf-simulator:config/allow-user = alice"},
+ {"timestamp":1542877414275,"configuration":"DELETED: /pnf-simulator:config/itemValue2 = 222"}]
+```
+
+### Logging
+
+### Swagger
+
+## Developers Guide
+
+### Integration tests
+Integration tests use docker-compose for setting up cluster with all services.
+Those tests are not part of build pipeline, but can be run manually by invoking *mvn verify -DskipITs=false* from project command line.
+Tests can be found in netconfsimulator project in src/integration directory.
+
+## Troubleshooting
+Q: Simulator throws errors after shutting down with *docker-compose down* or *docker-compose restart*
+
+A: Remove docker containers that were left after stopping the simulator with the following commands:
+```
+docker stop $(docker ps | grep netconfsimulator | awk '{print $1;}')
+docker rm $(docker ps -a | grep netconfsimulator | awk '{print $1;}')
+```
diff --git a/netconfsimulator/apt.conf b/netconfsimulator/apt.conf
new file mode 100644
index 0000000..9146b6b
--- /dev/null
+++ b/netconfsimulator/apt.conf
@@ -0,0 +1,5 @@
+Acquire::http {
+ No-Cache "true";
+ No-Store "true";
+ Pipeline-Depth "0";
+ }; \ No newline at end of file
diff --git a/netconfsimulator/config/netconf.env b/netconfsimulator/config/netconf.env
new file mode 100644
index 0000000..6cf310a
--- /dev/null
+++ b/netconfsimulator/config/netconf.env
@@ -0,0 +1,5 @@
+NETCONF_ADDRESS=netopeer
+NETCONF_PORT=830
+NETCONF_MODEL=pnf-simulator
+NETCONF_MAIN_CONTAINER=config
+TZ=Europe/Warsaw
diff --git a/netconfsimulator/docker-compose.yml b/netconfsimulator/docker-compose.yml
new file mode 100644
index 0000000..5e9acbe
--- /dev/null
+++ b/netconfsimulator/docker-compose.yml
@@ -0,0 +1,93 @@
+version: '3'
+
+services:
+ zookeeper:
+ image: wurstmeister/zookeeper
+ ports:
+ - "2181:2181"
+ networks:
+ - netconfnetwork
+
+ kafka1:
+ image: wurstmeister/kafka:1.1.0
+ ports:
+ - "9092:9092"
+ hostname: kafka1
+ networks:
+ - netconfnetwork
+ environment:
+ KAFKA_ADVERTISED_PORT: 9092
+ KAFKA_ADVERTISED_HOST_NAME: kafka1
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ KAFKA_CREATE_TOPICS: "config:1:1"
+ KAFKA_DELETE_RETENTION_MS: 604800000
+ KAFKA_LOG_CLEANER_DELETE_RETENTION_MS: 604800000
+ depends_on:
+ - zookeeper
+
+ netconf-simulator:
+ image: nexus3.onap.org:10003/onap/netconfsimulator
+ ports:
+ - "9000:8080"
+ restart: on-failure
+ hostname: netconf-simulator
+ networks:
+ - netconfnetwork
+ depends_on:
+ - zookeeper
+ - kafka1
+ - netopeer
+
+ netopeer:
+ image: nexus3.onap.org:10003/netopeer
+ ports:
+ - "830:830"
+ - "5002:5002"
+ - "6513:6513"
+ volumes:
+ - ./netconf:/netconf
+ - ./netopeer-change-saver-native:/netopeer-change-saver
+ - ./tls:/tls
+ env_file:
+ - ./config/netconf.env
+ restart: on-failure
+ networks:
+ - netconfnetwork
+ depends_on:
+ - sftp-server
+ - ftpes-server
+ command:
+ - /netconf/initialize_netopeer.sh
+
+ sftp-server:
+ image: atmoz/sftp:alpine
+ ports:
+ - "2222:22"
+ volumes:
+ - ./sftp:/home/sftp-user/sftp
+ - ./ssh/ssh_host_rsa_key.pub:/home/sftp-user/.ssh/keys/ssh_host_rsa_key.pub
+ networks:
+ - netconfnetwork
+ restart: on-failure
+ command: sftp-user::1001
+
+ ftpes-server:
+ image: stilliard/pure-ftpd:latest
+ ports:
+ - "2221:21"
+ - "30000-30009:30000-30009"
+ volumes:
+ - ./ftpes/files:/home/ftpusers/onap
+ - ./ftpes/userpass/:/etc/pure-ftpd/passwd/
+ - ./ftpes/tls/:/etc/ssl/private/
+ networks:
+ - netconfnetwork
+ environment:
+ PUBLICHOST: localhost
+ ADDED_FLAGS: --tls=2
+ FTP_USER_HOME: onap
+ restart: on-failure
+
+networks:
+ netconfnetwork:
+ driver: bridge
diff --git a/netconfsimulator/docker/Dockerfile b/netconfsimulator/docker/Dockerfile
new file mode 100644
index 0000000..0e25fd3
--- /dev/null
+++ b/netconfsimulator/docker/Dockerfile
@@ -0,0 +1,4 @@
+FROM openjdk:8-jre-alpine
+ADD libs /app/libs
+ADD netconfsimulator-5.0.0-SNAPSHOT.jar /app/netconf-simulator.jar
+CMD java -cp /app/libs/*:/app/netconf-simulator.jar org.onap.netconfsimulator.Main
diff --git a/netconfsimulator/ftpes/files/ftpes-noone.txt b/netconfsimulator/ftpes/files/ftpes-noone.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/netconfsimulator/ftpes/files/ftpes-noone.txt
diff --git a/netconfsimulator/ftpes/files/onap/ftpes-onap.txt b/netconfsimulator/ftpes/files/onap/ftpes-onap.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/netconfsimulator/ftpes/files/onap/ftpes-onap.txt
diff --git a/netconfsimulator/ftpes/tls/pure-ftpd.pem b/netconfsimulator/ftpes/tls/pure-ftpd.pem
new file mode 100755
index 0000000..0ce676e
--- /dev/null
+++ b/netconfsimulator/ftpes/tls/pure-ftpd.pem
@@ -0,0 +1,49 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHbSk5/cABTpCt
+q54QyTkhuhb84nEz5ztKL0hY56fsVtAA2gSAde+qV9YwUIuReOUhAF4RVVt2Lkn/
+1R0yX+0IjrXnO7jYzDj6QhgyqYKb3iQlvShZMMQ7qihn8qBxobk7+O10x6VLS2L8
+WYpQxGXu7T1qXbw10RhrqG8nbXYX+aHMsv9zMt9OYqKSI073OZR2vk3K49Uqcurj
+sXuRJOa10MRsxgA726pr8OLWAWejsoFaqP2fQS3HeT2RnAqPyAgPc0P6n7gxo0JU
+U5dPnrPbsvfdegIFxfc57oZXrLz7nYXkJEcjYTBFSQ+JAaRfx9kNXZ7Gft7EAMyF
+BLemY/0VAgMBAAECggEARD9bSHlKaCgW4xhEM8JpRt2EWG62BukvJSghPiupD/x1
+mpUBzWSO7GC68DXgTZxt7WlOx+fKMRuOP3sTTtX9LFyKa+PIUokxRpOv7EaOaAER
+pciiMkO6JCELSueBeOG7noaF3N0l+CqIaYvLBfDwYV/XELubWV+BV/aAc6HGNFWi
+4bjM+BOBLQstrEeJh2jVylzv4CTtlTs2pwiHFSyrHhudTk5nnATAHn1gi+X42v1A
+zk3UfqADZJmMI0/Roup4YPZ3+6zUzDN2i+qasHexL0OKIjRcSqpgqQoIeKEbKKfw
+sOgiWIR2Xvj7EJmhzJlWgKjk8OLs/7U4QpnD+s0agQKBgQDu3ojqKNWnPHy0Nupm
+tmAs28WLK76R0iZeAd2nwsU2K6lnm9z5o2ab3ffTJVB9kAetKJa3UerKskF/qF9C
+MtjlEn6F++uYFitpLjQevnyrKSqFqbzytDXrQlk+gZLglmi6YylT5k9qLSREAu55
+XS/wbm9XU2Q7sl8oTnZHXptT7QKBgQDVunvqdDn1FaNU9EwQCGPS3QGu+go22xkM
+4Rs2CoHWfqmhGOo8lJKBElDqsXvxggrZLWJe/1lgnELT/9aXS8QLWBnZxpTj9wfd
+igH+CJc3mWnLThmUGdSV/tuHon2IdQ8/1CiGSwIr9kYCnStidUtOXjIbgc6kUTTi
+5wtIGHh4yQKBgQDXJ/0dJbDklRgiX4CdCdLxNPfnlnxt7mN+s6GK1WY7l/JcD8ln
+1qW66aGrP2YT42L2tqOi9hdNgmh66xb6ksBI/XKXjsWz1Ow/Lk3mD2BN76OMh8pY
+trgGc1ndcmrw/qnQkTcNilqn4YdT92wER0rB/0cs2kFjgBQ0QxBI0s+INQKBgA6Y
+2fW9UmgGvk0DEl7V89tm9MJ6mU/9zswuY6lhNlTr+bHi/bx9eTQPiC8/R/PKqesD
+SoCqd/Q9N+M6yfEzX4RW1A0nnuui54qd7lznQUyu0abtApo22WoVKfEti91SAWSe
+nNXvMYrHGyj6iwgCcs47aLiwOOjIExCcLw0RfsjhAoGAc1zaRbrtjjh66FJYjLiJ
+Q6EXfm31ptaQQUn5rQyHMD2VRlajCYV+fv75tezf2dQvJcqHYWrEuY8U+OTbB1TB
+IEqN8ETUeLegl5RgvWoyWinqdbv/0d9LtwVBdtiEQLoYumD934mshEDgzCOOjrBe
+Salcd1vc6y6NiFooPlvloXQ=
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDYDCCAkigAwIBAgIJAMH2upKd2yAJMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTgwOTEwMTI1ODE2WhcNMzgwOTA1MTI1ODE2WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAx20pOf3AAU6QraueEMk5IboW/OJxM+c7Si9IWOen7FbQANoEgHXvqlfW
+MFCLkXjlIQBeEVVbdi5J/9UdMl/tCI615zu42Mw4+kIYMqmCm94kJb0oWTDEO6oo
+Z/KgcaG5O/jtdMelS0ti/FmKUMRl7u09al28NdEYa6hvJ212F/mhzLL/czLfTmKi
+kiNO9zmUdr5NyuPVKnLq47F7kSTmtdDEbMYAO9uqa/Di1gFno7KBWqj9n0Etx3k9
+kZwKj8gID3ND+p+4MaNCVFOXT56z27L33XoCBcX3Oe6GV6y8+52F5CRHI2EwRUkP
+iQGkX8fZDV2exn7exADMhQS3pmP9FQIDAQABo1MwUTAdBgNVHQ4EFgQUt51lQ+ab
+MTq+w2U/knCsIPb3wrkwHwYDVR0jBBgwFoAUt51lQ+abMTq+w2U/knCsIPb3wrkw
+DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAQ69AktYLQ+VRbojz
+zC0XQ2M1FAkfJI2P0LvPoYxZgId2CnZW3sMIdnJdF+KjvOqeGyFmw+hn8WkoKiWj
+0sxuGmrWt5t+5bF2wcq0CtTeF1/o6DsRhRiJBzmcLe81ItrN6emZSg96xCKzkHBZ
+3nF4fG88vtiYgD932lMStDqQzSTx0FsCGpGaKh9xDmKvlP24NWdM9gyOEsRbDvqd
+vS1Q45Jx0jzkp7X5d0casqBWIZak3z0EVdK7c8Y/GxxTcWfIMINCl9+F9kpTA/ZX
+uARYzrPWaBfDBi2r5acWi/AHJM3U+LgzO5nCKa+38vtjNw3NtbslA4InQ5cU2B8X
+QN8NlQ==
+-----END CERTIFICATE-----
diff --git a/netconfsimulator/ftpes/userpass/pureftpd.passwd b/netconfsimulator/ftpes/userpass/pureftpd.passwd
new file mode 100755
index 0000000..7961e71
--- /dev/null
+++ b/netconfsimulator/ftpes/userpass/pureftpd.passwd
@@ -0,0 +1 @@
+onap:$6$Guq6OMhBdNZ6nTk0$7dLt6hOrAv.in36jzWGd5UgWeDqN3CuKjrzJ.izRTdgZRTszeNYbT2dk7UDh9CLD7pohnB0.k1NSZmRIUB/ID/:1001:1001::/home/ftpusers/onap/./::::::::::::
diff --git a/netconfsimulator/netconf/__init__.py b/netconfsimulator/netconf/__init__.py
new file mode 100644
index 0000000..aa8b4f9
--- /dev/null
+++ b/netconfsimulator/netconf/__init__.py
@@ -0,0 +1,19 @@
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2019 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
diff --git a/netconfsimulator/netconf/initialize_netopeer.sh b/netconfsimulator/netconf/initialize_netopeer.sh
new file mode 100755
index 0000000..59fc8a1
--- /dev/null
+++ b/netconfsimulator/netconf/initialize_netopeer.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2019 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
+
+cp /tls/* /usr/local/etc/keystored/keys/
+cp /netconf/*.xml /tmp/
+
+chmod +x /netconf/set-up-xmls.py
+/netconf/set-up-xmls.py /tls ca.crt server_cert.crt server_key.pem /tmp/load_server_certs.xml /tmp/tls_listen.xml client.crt
+
+/usr/bin/supervisord -c /etc/supervisord.conf &
+sysrepoctl --install --yang=/netconf/pnf-simulator.yang --owner=netconf:nogroup --permissions=777
+sysrepocfg --import=/netconf/pnf-simulator.data.xml --datastore=startup --format=xml --level=3 pnf-simulator
+sysrepocfg --merge=/tmp/load_server_certs.xml --format=xml --datastore=startup ietf-keystore
+sysrepocfg --merge=/tmp/tls_listen.xml --format=xml --datastore=startup ietf-netconf-server
+
+nohup python3 /netconf/yang_loader_server.py &
+
+python /netconf/netopeer_change_saver.py pnf-simulator kafka1:9092 config \ No newline at end of file
diff --git a/netconfsimulator/netconf/load_server_certs.xml b/netconfsimulator/netconf/load_server_certs.xml
new file mode 100644
index 0000000..b52f911
--- /dev/null
+++ b/netconfsimulator/netconf/load_server_certs.xml
@@ -0,0 +1,44 @@
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<keystore xmlns="urn:ietf:params:xml:ns:yang:ietf-keystore">
+ <private-keys>
+ <private-key>
+ <name>SERVER_KEY_NAME</name>
+ <certificate-chains>
+ <certificate-chain>
+ <name>SERVER_CERT_NAME</name>
+ <certificate>SERVER_CERTIFICATE_HERE</certificate>
+ </certificate-chain>
+ </certificate-chains>
+ </private-key>
+ </private-keys>
+ <trusted-certificates>
+ <name>test_trusted_ca_list</name>
+ <trusted-certificate>
+ <name>CA_CERT_NAME</name>
+ <certificate>CA_CERTIFICATE_HERE</certificate>
+ </trusted-certificate>
+ <trusted-certificate>
+ <name>CLIENT_CERT_NAME</name>
+ <certificate>CLIENT_CERTIFICATE_HERE</certificate>
+ </trusted-certificate>
+ </trusted-certificates>
+</keystore>
diff --git a/netconfsimulator/netconf/netopeer_change_saver.py b/netconfsimulator/netconf/netopeer_change_saver.py
new file mode 100644
index 0000000..92f8846
--- /dev/null
+++ b/netconfsimulator/netconf/netopeer_change_saver.py
@@ -0,0 +1,107 @@
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2019 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
+
+import sysrepo as sr
+import sys
+import json
+import time
+import logging
+from kafka import KafkaProducer
+from enum import Enum
+
+logging.basicConfig(filename='netopeer_change_saver.log', level=logging.DEBUG)
+
+kafka_producer = None
+topic = "config"
+
+
+class OperationType(Enum):
+ CREATED = sr.SR_OP_CREATED
+ DELETED = sr.SR_OP_DELETED
+ MODIFIED = sr.SR_OP_MODIFIED
+ MOVED = sr.SR_OP_MOVED
+
+
+def module_change_callback(session, name, event, private_ctx):
+ if sr.SR_EV_APPLY == event:
+ change_path = "/{}:*".format(name)
+ changes = session.get_changes_iter(change_path)
+ change = session.get_change_next(changes)
+ while change:
+ try:
+ process_change(change)
+ change = session.get_change_next(changes)
+ except Exception:
+ logging.exception("Exception occured")
+
+ return sr.SR_ERR_OK
+
+
+def process_change(change):
+ if change:
+ message = {"type": OperationType(change.oper()).name}
+ if change.old_val():
+ message["old"] = {"path": change.old_val().xpath(), "value": change.old_val().val_to_string()}
+ if change.new_val():
+ message["new"] = {"path": change.new_val().xpath(), "value": change.new_val().val_to_string()}
+ send_message(message)
+
+
+def send_message(message):
+ logging.debug("Message to kafka : %s", message)
+ response = kafka_producer.send(topic, message)
+ logging.info(response.get(timeout=90))
+
+
+def create_producer(server):
+ for i in range(10): # pylint: disable=W0612
+ try:
+ return KafkaProducer(bootstrap_servers=server, value_serializer=lambda v: json.dumps(v).encode('utf-8'))
+ except Exception:
+ time.sleep(15)
+ raise Exception("Could not connect to kafka server")
+
+
+def print_current_config(kafka_session, module):
+ name = "/{}:*//*".format(module)
+ logging.info("Retrieving current config for %s module", name)
+ values = kafka_session.get_items(name)
+ for i in range(values.val_cnt()):
+ logging.info(values.val(i).to_string())
+
+
+if __name__ == "__main__":
+ try:
+ module_name = sys.argv[1]
+ bootstrap_servers = sys.argv[2]
+ topic = sys.argv[3]
+ connection = sr.Connection("example_application2")
+ session = sr.Session(connection)
+ subscribe = sr.Subscribe(session)
+ subscribe.module_change_subscribe(module_name, module_change_callback)
+
+ print_current_config(session, module_name)
+
+ kafka_producer = create_producer(bootstrap_servers)
+
+ sr.global_loop()
+ except Exception as e:
+ logging.exception("Exception occured")
+ raise e
diff --git a/netconfsimulator/netconf/newmodel.xml b/netconfsimulator/netconf/newmodel.xml
new file mode 100644
index 0000000..90a3451
--- /dev/null
+++ b/netconfsimulator/netconf/newmodel.xml
@@ -0,0 +1,24 @@
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<config2 xmlns="http://onap.org/pnf-simulator2">
+ <item1>500</item1>
+ <item2>1000</item2>
+</config2>
diff --git a/netconfsimulator/netconf/newmodel.yang b/netconfsimulator/netconf/newmodel.yang
new file mode 100644
index 0000000..544f467
--- /dev/null
+++ b/netconfsimulator/netconf/newmodel.yang
@@ -0,0 +1,9 @@
+module newmodel {
+ namespace "http://onap.org/pnf-simulator2";
+ prefix config2;
+ container config2 {
+ config true;
+ leaf item1 {type uint32;}
+ leaf item2 {type uint32;}
+ }
+}
diff --git a/netconfsimulator/netconf/pnf-simulator.data.xml b/netconfsimulator/netconf/pnf-simulator.data.xml
new file mode 100644
index 0000000..c235f64
--- /dev/null
+++ b/netconfsimulator/netconf/pnf-simulator.data.xml
@@ -0,0 +1,24 @@
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<config xmlns="http://onap.org/pnf-simulator">
+ <itemValue1>42</itemValue1>
+ <itemValue2>35</itemValue2>
+</config>
diff --git a/netconfsimulator/netconf/pnf-simulator.yang b/netconfsimulator/netconf/pnf-simulator.yang
new file mode 100644
index 0000000..ba11585
--- /dev/null
+++ b/netconfsimulator/netconf/pnf-simulator.yang
@@ -0,0 +1,9 @@
+module pnf-simulator {
+ namespace "http://onap.org/pnf-simulator";
+ prefix config;
+ container config {
+ config true;
+ leaf itemValue1 {type uint32;}
+ leaf itemValue2 {type uint32;}
+ }
+}
diff --git a/netconfsimulator/netconf/set-up-xmls.py b/netconfsimulator/netconf/set-up-xmls.py
new file mode 100755
index 0000000..2ec1cf2
--- /dev/null
+++ b/netconfsimulator/netconf/set-up-xmls.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2019 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
+
+import os
+import sys
+import logging
+import logging.config
+
+logging.basicConfig()
+logger = logging.getLogger()
+logger.setLevel(logging.INFO)
+
+# Placeholders definition - this needs to match placeholders in
+# load_server_certs_xml_file and tls_listen_xml_file
+SERVER_KEY_NAME = "SERVER_KEY_NAME"
+SERVER_CERT_NAME = "SERVER_CERT_NAME"
+SERVER_CERTIFICATE_HERE = "SERVER_CERTIFICATE_HERE"
+CA_CERT_NAME = "CA_CERT_NAME"
+CLIENT_CERT_NAME = "CLIENT_CERT_NAME"
+CLIENT_CERTIFICATE_HERE="CLIENT_CERTIFICATE_HERE"
+CA_CERTIFICATE_HERE = "CA_CERTIFICATE_HERE"
+CLIENT_FINGERPRINT_HERE = "CLIENT_FINGERPRINT_HERE"
+SERVER_CERTIFICATE_ENV = "SERVER_CERTIFICATE_ENV"
+CA_CERTIFICATE_ENV = "CA_CERTIFICATE_ENV"
+
+
+class FileHelper(object):
+ @classmethod
+ def get_file_contents(cls, filename):
+ with open(filename, "r") as f:
+ return f.read()
+
+ @classmethod
+ def write_file_contents(cls, filename, data):
+ with open(filename, "w+") as f:
+ f.write(data)
+
+
+class CertHelper(object):
+ @classmethod
+ def get_pem_content_stripped(cls, pem_dir, pem_filename):
+ cmd = "cat {}/{} | grep -v '^-'".format(pem_dir, pem_filename)
+ content = CertHelper.system(cmd)
+ return content
+
+ @classmethod
+ def get_cert_fingerprint(cls, directory, cert_filename):
+ cmd = "openssl x509 -fingerprint -noout -in {}/{} | sed -e " \
+ "'s/SHA1 Fingerprint//; s/=//; s/=//p'" \
+ .format(directory, cert_filename)
+ fingerprint = CertHelper.system(cmd)
+ return fingerprint
+
+ @classmethod
+ def print_certs_info(cls, ca_cert, ca_fingerprint, server_cert):
+ logger.info("Will use server certificate: " + server_cert)
+ logger.info("Will use CA certificate: " + ca_cert)
+ logger.info("CA certificate fingerprint: " + ca_fingerprint)
+
+ @classmethod
+ def system(cls, cmd):
+ return os.popen(cmd).read().replace("\n", "")
+
+
+class App(object):
+ @classmethod
+ def patch_server_certs(cls, data, server_key_filename_noext,
+ server_cert_filename_noext, ca_cert_filename_noext,
+ server_cert, ca_cert, client_cert_filename_noext, client_cert):
+ data = data.replace(SERVER_KEY_NAME, server_key_filename_noext)
+ data = data.replace(SERVER_CERT_NAME, server_cert_filename_noext)
+ data = data.replace(CA_CERT_NAME, ca_cert_filename_noext)
+ data = data.replace(CLIENT_CERT_NAME, client_cert_filename_noext)
+ data = data.replace(CLIENT_CERTIFICATE_HERE, client_cert)
+ data = data.replace(SERVER_CERTIFICATE_HERE, server_cert)
+ data = data.replace(CA_CERTIFICATE_HERE, ca_cert)
+ return data
+
+ @classmethod
+ def patch_tls_listen(cls, data, server_cert_filename_noext, client_fingerprint,
+ server_cert, ca_cert):
+ data = data.replace(SERVER_CERT_NAME, server_cert_filename_noext)
+ data = data.replace(CLIENT_FINGERPRINT_HERE, client_fingerprint)
+ data = data.replace(SERVER_CERTIFICATE_HERE, server_cert)
+ data = data.replace(CA_CERTIFICATE_HERE, ca_cert)
+ return data
+
+ @classmethod
+ def run(cls):
+ # name things
+ cert_dir = sys.argv[1]
+ ca_cert_filename = sys.argv[2]
+ server_cert_filename = sys.argv[3]
+ server_key_filename = sys.argv[4]
+ load_server_certs_xml_file = sys.argv[5]
+ tls_listen_xml_file = sys.argv[6]
+ client_cert_filename = sys.argv[7]
+
+
+ # strip extensions
+ ca_cert_filename_noext = ca_cert_filename.replace(".crt", "")
+ server_cert_filename_noext = server_cert_filename.replace(".crt", "")
+ server_key_filename_noext = server_key_filename.replace(".pem", "")
+ client_cert_filename_noext = client_cert_filename.replace(".crt", "")
+
+ # get certificates from files
+ server_cert = CertHelper.get_pem_content_stripped(cert_dir,
+ server_cert_filename)
+ ca_cert = CertHelper.get_pem_content_stripped(cert_dir,
+ ca_cert_filename)
+ client_fingerprint = CertHelper.get_cert_fingerprint(cert_dir,
+ client_cert_filename)
+ CertHelper.print_certs_info(ca_cert, client_fingerprint, server_cert)
+
+ client_cert = CertHelper.get_pem_content_stripped(cert_dir,
+ client_cert_filename)
+ # patch TLS configuration files
+ data_srv = FileHelper.get_file_contents(load_server_certs_xml_file)
+ patched_srv = App.patch_server_certs(data_srv, server_key_filename_noext,
+ server_cert_filename_noext,
+ ca_cert_filename_noext,
+ server_cert, ca_cert,
+ client_cert_filename_noext, client_cert)
+ FileHelper.write_file_contents(load_server_certs_xml_file, patched_srv)
+
+ data_tls = FileHelper.get_file_contents(tls_listen_xml_file)
+ patched_tls = App.patch_tls_listen(data_tls, server_cert_filename_noext,
+ client_fingerprint, server_cert, ca_cert)
+ FileHelper.write_file_contents(tls_listen_xml_file, patched_tls)
+
+
+def main():
+ if len(sys.argv) is not 8:
+ print("Usage: {1} <cert_dir> <ca_cert_filename> <server_cert_filename> "
+ "<server_key_filename> <load_server_certs_xml_full_path> "
+ "<tls_listen_full_path> <client_cert_filename>", sys.argv[0])
+ return 1
+ App.run()
+ logger.info("XML files patched successfully")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/netconfsimulator/netconf/test_yang_loader_server.py b/netconfsimulator/netconf/test_yang_loader_server.py
new file mode 100644
index 0000000..a222087
--- /dev/null
+++ b/netconfsimulator/netconf/test_yang_loader_server.py
@@ -0,0 +1,121 @@
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2019 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
+
+import unittest
+
+from unittest import mock
+from werkzeug.datastructures import FileStorage
+
+from yang_loader_server import YangLoaderHelper, YangModelServer
+
+
+class TestYangLoaderHelper(unittest.TestCase):
+
+ def test_should_save_file_and_return_path(self):
+ helper = YangLoaderHelper()
+ mocked_file = mock.Mock(FileStorage)
+ mocked_file.filename = "sample"
+
+ path = helper.save_file(mocked_file)
+
+ self.assertEqual(path, "/tmp/sample")
+ mocked_file.save.assert_called_once_with("/tmp/sample")
+
+ @mock.patch('yang_loader_server.check_output')
+ def test_should_install_new_yang_model(self, mocked_output):
+ helper = YangLoaderHelper()
+
+ helper.install_new_model("path")
+
+ mocked_output.assert_called_with(
+ ['sysrepoctl', '--install', '--yang=path',
+ '--owner=netconf:nogroup', '--permissions=777'],
+ stderr=-2, universal_newlines=True)
+
+ @mock.patch('yang_loader_server.check_output')
+ def test_should_delete_yang_model(self, mocked_output):
+ helper = YangLoaderHelper()
+
+ helper.uninstall_a_model("modelName")
+
+ mocked_output.assert_called_with(
+ ['sysrepoctl', '--uninstall', '--module=modelName'],
+ stderr=-2, universal_newlines=True)
+
+ @mock.patch('yang_loader_server.check_output')
+ def test_should_set_default_configuration(self, mocked_output):
+ helper = YangLoaderHelper()
+
+ helper.set_default_configuration("samplePath", "sampleModuleName")
+
+ mocked_output.assert_called_with(
+ ['sysrepocfg', '--import=samplePath', '--datastore=startup',
+ '--format=xml', '--level=3', 'sampleModuleName'],
+ stderr=-2, universal_newlines=True)
+
+ @mock.patch('yang_loader_server.subprocess.Popen')
+ @mock.patch('yang_loader_server.check_output')
+ def test_should_verify_change_listener_for_model_properly(self, mocked_output, mocked_popen):
+ helper = YangLoaderHelper()
+
+ helper.start_change_listener_for_model("sampleModule")
+
+ mocked_output.assert_called_with(
+ ['pgrep', '-f', 'python /netconf/netopeer_change_saver.py sampleModule kafka1:9092 config'],
+ stderr=-2, universal_newlines=True)
+
+ @mock.patch('yang_loader_server.check_output')
+ def test_should_raise_exception_when_error_occurred_in_output(self,
+ mocked_output):
+ helper = YangLoaderHelper()
+ mocked_output.return_value = "abcd ERR"
+ with self.assertRaises(RuntimeError) as context:
+ helper._run_bash_command("sample command")
+
+ self.assertEqual('abcd ERR', str(context.exception))
+
+
+class TestYangModelServer(unittest.TestCase):
+
+ def __init__(self, methodName='runTest'):
+ super().__init__(methodName)
+ self._mocked_file = mock.Mock(FileStorage)
+
+ def test_should_properly_apply_and_start_new_model(self):
+ with mock.patch.object(YangModelServer, '_parse_request',
+ new=self._mock_request):
+ helper = mock.Mock(YangLoaderHelper)
+ helper.save_file.return_value = "sampleFile"
+ server = YangModelServer(helper)
+
+ server.post()
+
+ self.assertEqual(helper.save_file.call_count, 2)
+ helper.install_new_model.assert_called_once_with('sampleFile')
+ helper.set_default_configuration.assert_called_once_with(
+ 'sampleFile', 'sampleModuleName')
+ helper.start_change_listener_for_model.assert_called_once_with('sampleModuleName')
+
+ def _mock_request(self):
+ return {
+ 'yangModel': self._mocked_file,
+ 'initialConfig': self._mocked_file,
+ 'moduleName': "sampleModuleName"
+ }
diff --git a/netconfsimulator/netconf/tls_listen.xml b/netconfsimulator/netconf/tls_listen.xml
new file mode 100644
index 0000000..4f610b5
--- /dev/null
+++ b/netconfsimulator/netconf/tls_listen.xml
@@ -0,0 +1,49 @@
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
+ <listen>
+ <endpoint>
+ <name>test_tls_listen_endpt</name>
+ <tls>
+ <address>0.0.0.0</address>
+ <port>6513</port>
+ <certificates>
+ <certificate>
+ <name>SERVER_CERT_NAME</name>
+ </certificate>
+ </certificates>
+ <client-auth>
+ <trusted-ca-certs>test_trusted_ca_list</trusted-ca-certs>
+ <trusted-client-certs>test_trusted_ca_list</trusted-client-certs>
+ <cert-maps>
+ <cert-to-name>
+ <id>1</id>
+ <!-- This is not a typo - 0x02 should stay there -->
+ <fingerprint>02:CLIENT_FINGERPRINT_HERE</fingerprint>
+ <map-type xmlns:x509c2n="urn:ietf:params:xml:ns:yang:ietf-x509-cert-to-name">x509c2n:specified</map-type>
+ <name>test</name>
+ </cert-to-name>
+ </cert-maps>
+ </client-auth>
+ </tls>
+ </endpoint>
+ </listen>
+</netconf-server>
diff --git a/netconfsimulator/netconf/yang_loader.log b/netconfsimulator/netconf/yang_loader.log
new file mode 100644
index 0000000..61b95b3
--- /dev/null
+++ b/netconfsimulator/netconf/yang_loader.log
@@ -0,0 +1 @@
+INFO:werkzeug: * Running on http://0.0.0.0:5002/ (Press CTRL+C to quit)
diff --git a/netconfsimulator/netconf/yang_loader_server.py b/netconfsimulator/netconf/yang_loader_server.py
new file mode 100644
index 0000000..27d46ce
--- /dev/null
+++ b/netconfsimulator/netconf/yang_loader_server.py
@@ -0,0 +1,172 @@
+###
+# ============LICENSE_START=======================================================
+# Simulator
+# ================================================================================
+# Copyright (C) 2019 Nokia. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+###
+
+import logging
+import subprocess
+import os
+from subprocess import check_output, CalledProcessError
+from flask import Flask
+from flask_restful import Resource, Api, reqparse
+from werkzeug.datastructures import FileStorage
+import time
+
+app = Flask(__name__)
+api = Api(app)
+logger = logging.getLogger("yang-loader")
+logger.addHandler(logging.StreamHandler())
+KAFKA_BROKER_NAME="kafka1:9092"
+KAFKA_TOPIC_NAME="config"
+
+
+class YangLoaderHelper(object):
+
+ @classmethod
+ def save_file(cls, yang_model_file: FileStorage) -> str:
+ path = "/tmp/" + yang_model_file.filename
+ yang_model_file.save(path)
+ return path
+
+ @classmethod
+ def install_new_model(cls, yang_model_path: str):
+ logger.info("Installing new model: %s", yang_model_path)
+ command = "sysrepoctl --install --yang={} --owner=netconf:nogroup --permissions=777" \
+ .format(yang_model_path)
+ cls._run_bash_command(command)
+
+ @classmethod
+ def uninstall_a_model(cls, yang_model_name: str):
+ logger.info("Uninstalling a model: %s", yang_model_name)
+ command = "sysrepoctl --uninstall --module={}" \
+ .format(yang_model_name)
+ cls._run_bash_command(command)
+
+
+ @classmethod
+ def set_default_configuration(cls, init_conf_path: str, module_name: str):
+ logger.info("Attempting to set default configuration %s for module %s", init_conf_path, module_name)
+ command = "sysrepocfg --import={} --datastore=startup --format=xml --level=3 {}" \
+ .format(init_conf_path, module_name)
+ cls._run_bash_command(command)
+
+ @classmethod
+ def start_change_listener_for_model(cls, module_name: str):
+ logger.info("Starting listener for model: %s", module_name)
+ command = "python /netconf/netopeer_change_saver.py {} {} {}" \
+ .format(module_name, KAFKA_BROKER_NAME, KAFKA_TOPIC_NAME)
+ try:
+ check_output(["pgrep", "-f" , command], stderr=subprocess.STDOUT, universal_newlines=True)
+ logger.info("Change listener for {} already exist.".format(module_name))
+ except CalledProcessError:
+ subprocess.Popen(command.split(), stdout=subprocess.PIPE)
+
+ @classmethod
+ def stop_change_listener_for_model(cls, model_name):
+ logger.info("Stopping listener for model %s", model_name)
+ pid = cls.get_pid_by_name(model_name)
+ logger.info("pid is %s", pid)
+ command = "kill -2 {}".format(pid)
+ cls._run_bash_command(command)
+
+ @classmethod
+ def _run_bash_command(cls, command: str):
+ try:
+ logger.info("Attempts to invoke %s", command)
+ output = check_output(command.split(), stderr=subprocess.STDOUT,
+ universal_newlines=True)
+ logger.info("Output: %s", output)
+ if "ERR" in output:
+ raise RuntimeError(str(output))
+ except subprocess.CalledProcessError as e:
+ raise RuntimeError(e, str(e.stdout))
+
+ @classmethod
+ def get_pid_by_name(cls, name):
+ for dirname in os.listdir('/proc'):
+ if not dirname.isdigit():
+ continue
+ try:
+ with open('/proc/{}/cmdline'.format(dirname), mode='rb') as fd:
+ content = fd.read().decode().split('\x00')
+ except Exception as e:
+ print(e)
+ continue
+
+ if name in content:
+ return dirname
+
+
+class YangModelServer(Resource):
+ logger = logging.getLogger('YangModelServer')
+
+ def __init__(self, yang_loader_helper: YangLoaderHelper = YangLoaderHelper()):
+ self._yang_loader_helper = yang_loader_helper
+
+ def post(self):
+ args = self._parse_request()
+ yang_model_file = args['yangModel']
+ initial_config_file = args['initialConfig']
+ module_name = args['moduleName']
+ model_path = self._yang_loader_helper.save_file(yang_model_file)
+ conf_path = self._yang_loader_helper.save_file(initial_config_file)
+
+ try:
+ self._yang_loader_helper.install_new_model(model_path)
+ self._yang_loader_helper.set_default_configuration(conf_path,
+ module_name)
+ self._yang_loader_helper.start_change_listener_for_model(module_name)
+ except RuntimeError as e:
+ self.logger.error(e.args, exc_info=True)
+ return str(e.args), 400
+ return "Successfully started"
+
+ def delete(self):
+ args = self._parse_request()
+ yang_model_name = args['yangModelName']
+
+ try:
+ self._yang_loader_helper.stop_change_listener_for_model(yang_model_name)
+ time.sleep(5)
+ self._yang_loader_helper.uninstall_a_model(yang_model_name)
+ except RuntimeError as e:
+ self.logger.error(e.args, exc_info=True)
+ return str(e.args), 400
+ return "Successfully deleted"
+
+ @classmethod
+ def _parse_request(cls) -> reqparse.Namespace:
+ parse = reqparse.RequestParser()
+ parse.add_argument('yangModel',
+ type=FileStorage,
+ location='files')
+ parse.add_argument('initialConfig',
+ type=FileStorage,
+ location='files')
+ parse.add_argument('moduleName', type=str)
+ parse.add_argument('yangModelName', type=str)
+ return parse.parse_args()
+
+
+api.add_resource(YangModelServer, '/model')
+
+if __name__ == '__main__':
+ logging.basicConfig(filename=os.path.dirname(__file__) + "/yang_loader.log",
+ filemode="w",
+ level=logging.DEBUG)
+ app.run(host='0.0.0.0', port='5002')
diff --git a/netconfsimulator/pom.xml b/netconfsimulator/pom.xml
new file mode 100644
index 0000000..ecf5845
--- /dev/null
+++ b/netconfsimulator/pom.xml
@@ -0,0 +1,318 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.simulator</groupId>
+ <artifactId>simulator-parent</artifactId>
+ <version>5.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>netconfsimulator</artifactId>
+ <version>5.0.0-SNAPSHOT</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <maven.build.timestamp.format>yyyyMMdd'T'HHmmss</maven.build.timestamp.format>
+ <docker.registry>nexus3.onap.org:10003</docker.registry>
+ <docker.image.tag>latest</docker.image.tag>
+ <docker.image.name>onap/${project.artifactId}</docker.image.name>
+ <spring.boot.version>2.1.6.RELEASE</spring.boot.version>
+ <spring.kafka.version>2.2.7.RELEASE</spring.kafka.version>
+ <apache.httpclient.version>4.5.6</apache.httpclient.version>
+ <dependency.directory.name>libs</dependency.directory.name>
+ <dependency.directory.location>${project.build.directory}/${dependency.directory.name}
+ </dependency.directory.location>
+ <netopeer-saver-project-name>netopeer-change-saver</netopeer-saver-project-name>
+ <netopeer-saver-source-dir>${project.basedir}/netopeer-change-saver-native</netopeer-saver-source-dir>
+ <netopeer-saver-build-dir>${project.build.directory}/cmake</netopeer-saver-build-dir>
+ <netopeer-saver-executable-dir>${netopeer-saver-build-dir}/bin</netopeer-saver-executable-dir>
+ <skipITs>true</skipITs>
+ <proxy>""</proxy>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ <version>${spring.boot.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <version>${spring.boot.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>1.18.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>jnc</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-websocket</artifactId>
+ <version>${spring.boot.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.websocket</groupId>
+ <artifactId>javax.websocket-api</artifactId>
+ <version>1.1</version>
+ </dependency>
+
+ <!-- Kafka -->
+
+ <dependency>
+ <groupId>org.springframework.kafka</groupId>
+ <artifactId>spring-kafka</artifactId>
+ <version>${spring.kafka.version}</version>
+ </dependency>
+
+ <!-- TEST DEPENDENCIES -->
+
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>3.9.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.18.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>5.3.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.vintage</groupId>
+ <artifactId>junit-vintage-engine</artifactId>
+ <version>5.3.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <version>${spring.boot.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.kafka</groupId>
+ <artifactId>spring-kafka-test</artifactId>
+ <version>${spring.kafka.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bitbucket.radistao.test</groupId>
+ <artifactId>before-after-spring-test-runner</artifactId>
+ <version>0.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.palantir.docker.compose</groupId>
+ <artifactId>docker-compose-rule-junit4</artifactId>
+ <version>0.29.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.5.6</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ <version>4.5.6</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>${apache.httpclient.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ <version>${apache.httpclient.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger2</artifactId>
+ <version>2.9.2</version>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger-ui</artifactId>
+ <version>2.9.2</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <configuration>
+ <outputDirectory>${dependency.directory.location}</outputDirectory>
+ <includeScope>runtime</includeScope>
+ <silent>true</silent>
+ </configuration>
+ <executions>
+ <execution>
+ <id>copy-external-dependencies</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.19</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.junit.platform</groupId>
+ <artifactId>junit-platform-surefire-provider</artifactId>
+ <version>1.1.1</version>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <detail>true</detail>
+ <printSummary>true</printSummary>
+ <useSystemClassLoader>false</useSystemClassLoader>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>2.19.1</version>
+ <configuration>
+ <skipITs>${skipITs}</skipITs>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <profiles>
+ <profile>
+ <id>docker</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <properties>
+ <os.detected.name>linux</os.detected.name>
+ <os.detected.arch>x86_64</os.detected.arch>
+ <os.detected.classifier>${os.detected.name}-${os.detected.arch}</os.detected.classifier>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>io.fabric8</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <version>${docker-maven-plugin.version}</version>
+ <executions>
+ <execution>
+ <id>docker-build-image</id>
+ <phase>package</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>docker-push-image</id>
+ <phase>deploy</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <skipPush>${skipDockerPush}</skipPush>
+ <verbose>true</verbose>
+ <imagePullPolicy>IfNotPresent</imagePullPolicy>
+ <images>
+ <image>
+ <alias>${project.artifactId}</alias>
+ <name>${onap.nexus.dockerregistry.daily}/${docker.image.name}</name>
+ <registry>${onap.nexus.dockerregistry.daily}</registry>
+ <build>
+ <contextDir>${project.basedir}</contextDir>
+ <dockerFile>${project.basedir}/Dockerfile_app</dockerFile>
+ <tags>
+ <tag>${project.version}-${maven.build.timestamp}Z</tag>
+ </tags>
+ </build>
+ </image>
+ <image>
+ <name>${onap.nexus.dockerregistry.daily}/netopeer</name>
+ <build>
+ <contextDir>${project.basedir}</contextDir>
+ <dockerFile>${project.basedir}/Dockerfile_netopeer</dockerFile>
+ <args>
+ <PROXY>${proxy}</PROXY>
+ </args>
+ </build>
+ </image>
+ </images>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+ <repositories>
+ <repository>
+ <id>Palantir</id>
+ <url>https://dl.bintray.com/palantir/releases/</url>
+ </repository>
+ </repositories>
+</project>
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/Configuration.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/Configuration.java
new file mode 100644
index 0000000..92e5b23
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/Configuration.java
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator;
+
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.springframework.context.annotation.Bean;
+
+@org.springframework.context.annotation.Configuration
+public class Configuration {
+
+ @Bean
+ public HttpClient httpClient() {
+ return HttpClientBuilder.create().build();
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/Main.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/Main.java
new file mode 100644
index 0000000..e2a0ed0
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/Main.java
@@ -0,0 +1,31 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.netconfsimulator;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class, args);
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/SwaggerConfig.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/SwaggerConfig.java
new file mode 100644
index 0000000..2e9df99
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/SwaggerConfig.java
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@EnableSwagger2
+@Configuration
+class SwaggerConfig {
+
+ @Bean
+ Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("org.onap.netconfsimulator"))
+ .paths(PathSelectors.any())
+ .build();
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/Config.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/Config.java
new file mode 100644
index 0000000..9ae5641
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/Config.java
@@ -0,0 +1,70 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.onap.netconfsimulator.kafka.listener.KafkaListenerHandler;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.annotation.EnableKafka;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+
+@Configuration
+@EnableKafka
+class Config {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String bootstrapServer;
+
+ @Value("${spring.kafka.consumer.auto-offset-reset}")
+ private String offsetReset;
+
+ @Bean
+ ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(ConsumerFactory<String, String> consumerFactory) {
+ ConcurrentKafkaListenerContainerFactory<String, String> containerFactory = new ConcurrentKafkaListenerContainerFactory<>();
+ containerFactory.setConsumerFactory(consumerFactory);
+ return containerFactory;
+ }
+
+ @Bean
+ ConsumerFactory<String, String> consumerFactory() {
+ Map<String, Object> props = new HashMap<>();
+ props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServer);
+ props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+ props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+ props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, offsetReset);
+ return new DefaultKafkaConsumerFactory<>(props);
+ }
+
+
+ @Bean
+ KafkaListenerHandler kafkaListenerHandler(ConsumerFactory<String, String> consumerFactory) {
+ return new KafkaListenerHandler(consumerFactory);
+ }
+
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/MessageDTO.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/MessageDTO.java
new file mode 100644
index 0000000..4311cd6
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/MessageDTO.java
@@ -0,0 +1,31 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+class MessageDTO {
+ private long timestamp;
+ private String configuration;
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/StoreController.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/StoreController.java
new file mode 100644
index 0000000..33bbdf7
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/StoreController.java
@@ -0,0 +1,59 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@Slf4j
+@RequestMapping("/store")
+public class StoreController {
+
+ private StoreService service;
+
+ @Autowired
+ public StoreController(StoreService service) {
+ this.service = service;
+ }
+
+ @GetMapping("/ping")
+ String ping() {
+ return "pong";
+ }
+
+ @GetMapping("cm-history")
+ List<MessageDTO> getAllConfigurationChanges() {
+ return service.getAllMessages();
+ }
+
+ @GetMapping("/less")
+ List<MessageDTO> less(@RequestParam(value = "offset", required = false, defaultValue = "${spring.kafka.default-offset}") long offset) {
+ return service.getLastMessages(offset);
+ }
+
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/StoreService.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/StoreService.java
new file mode 100644
index 0000000..5fddff5
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/StoreService.java
@@ -0,0 +1,91 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.common.TopicPartition;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Slf4j
+@Service
+public class StoreService {
+
+ private static final String CONFIG_TOPIC = "config";
+ private static final long CONSUMING_DURATION_IN_MS = 1000;
+
+ private ConsumerFactory<String, String> consumerFactory;
+ static final List<String> TOPICS_TO_SUBSCRIBE = Collections.singletonList(CONFIG_TOPIC);
+
+ @Autowired
+ StoreService(ConsumerFactory<String, String> consumerFactory) {
+ this.consumerFactory = consumerFactory;
+ }
+
+ List<MessageDTO> getAllMessages() {
+ List<MessageDTO> messages = new ArrayList<>();
+ String clientID = Long.toString(Instant.now().getEpochSecond());
+ try (Consumer<String, String> consumer = consumerFactory.createConsumer(clientID, clientID)) {
+ consumer.subscribe(TOPICS_TO_SUBSCRIBE);
+ ConsumerRecords<String, String> consumerRecords = consumer.poll(CONSUMING_DURATION_IN_MS);
+ consumerRecords.forEach(
+ consumerRecord ->
+ messages.add(new MessageDTO(consumerRecord.timestamp(), consumerRecord.value())));
+ log.debug(String.format("consumed %d messages", consumerRecords.count()));
+ }
+ return messages;
+ }
+
+ List<MessageDTO> getLastMessages(long offset) {
+ List<MessageDTO> messages = new ArrayList<>();
+ try (Consumer<String, String> consumer = createConsumer(offset)) {
+ ConsumerRecords<String, String> consumerRecords = consumer.poll(CONSUMING_DURATION_IN_MS);
+ consumerRecords.forEach(consumerRecord ->
+ messages.add(new MessageDTO(consumerRecord.timestamp(), consumerRecord.value())));
+ }
+ return messages;
+ }
+
+ private Consumer<String, String> createConsumer(long offsetFromLastIndex) {
+ String clientID = Long.toString(Instant.now().getEpochSecond());
+ Consumer<String, String> consumer = consumerFactory.createConsumer(clientID, clientID);
+ consumer.subscribe(TOPICS_TO_SUBSCRIBE);
+ seekConsumerTo(consumer, offsetFromLastIndex);
+ return consumer;
+ }
+
+ private void seekConsumerTo(Consumer<String, String> consumer, long offsetFromLastIndex) {
+ consumer.seekToEnd(consumer.assignment());
+ consumer.poll(CONSUMING_DURATION_IN_MS);
+ TopicPartition topicPartition = consumer.assignment().iterator().next();
+ long topicCurrentSize = consumer.position(topicPartition);
+ long indexToSeek = offsetFromLastIndex > topicCurrentSize ? 0 : topicCurrentSize - offsetFromLastIndex;
+ consumer.seek(topicPartition, indexToSeek);
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerEntry.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerEntry.java
new file mode 100644
index 0000000..e3c04c9
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerEntry.java
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka.listener;
+
+import lombok.Getter;
+import org.springframework.kafka.listener.AbstractMessageListenerContainer;
+
+@Getter
+public class KafkaListenerEntry {
+
+ private String clientId;
+ private AbstractMessageListenerContainer listenerContainer;
+
+ public KafkaListenerEntry(String clientId, AbstractMessageListenerContainer listenerContainer) {
+ this.clientId = clientId;
+ this.listenerContainer = listenerContainer;
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerHandler.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerHandler.java
new file mode 100644
index 0000000..604315d
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerHandler.java
@@ -0,0 +1,67 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka.listener;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.kafka.core.ConsumerFactory;
+
+import org.springframework.kafka.listener.ContainerProperties;
+import org.springframework.kafka.listener.KafkaMessageListenerContainer;
+import org.springframework.kafka.listener.MessageListener;
+
+
+import org.springframework.kafka.support.TopicPartitionInitialOffset;
+
+import java.time.Instant;
+
+public class KafkaListenerHandler {
+
+ private static final int PARTITION = 0;
+ private static final long NUMBER_OF_HISTORICAL_MESSAGES_TO_SHOW = -10L;
+ private static final boolean RELATIVE_TO_CURRENT = false;
+ private ConsumerFactory<String, String> consumerFactory;
+
+
+ @Autowired
+ public KafkaListenerHandler(ConsumerFactory<String, String> consumerFactory) {
+ this.consumerFactory = consumerFactory;
+ }
+
+
+ public KafkaListenerEntry createKafkaListener(MessageListener messageListener, String topicName) {
+ String clientId = Long.toString(Instant.now().getEpochSecond());
+ ContainerProperties containerProperties = new ContainerProperties(topicName);
+ containerProperties.setGroupId(clientId);
+ KafkaMessageListenerContainer<String, String> listenerContainer = createListenerContainer(containerProperties,
+ topicName);
+
+ listenerContainer.setupMessageListener(messageListener);
+ return new KafkaListenerEntry(clientId, listenerContainer);
+ }
+
+
+ KafkaMessageListenerContainer<String, String> createListenerContainer(ContainerProperties containerProperties,
+ String topicName) {
+ TopicPartitionInitialOffset config = new TopicPartitionInitialOffset(topicName, PARTITION,
+ NUMBER_OF_HISTORICAL_MESSAGES_TO_SHOW, RELATIVE_TO_CURRENT);
+ return new KafkaMessageListenerContainer<>(consumerFactory, containerProperties, config);
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/model/KafkaMessage.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/model/KafkaMessage.java
new file mode 100644
index 0000000..90f283a
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/kafka/model/KafkaMessage.java
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka.model;
+
+import lombok.Getter;
+
+@Getter
+public class KafkaMessage {
+ private long timestamp;
+ private String configuration;
+
+ public KafkaMessage(long timestamp, String configuration) {
+ this.timestamp = timestamp;
+ this.configuration = configuration;
+ }
+
+ KafkaMessage() {
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/NetconfController.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/NetconfController.java
new file mode 100644
index 0000000..cdb4a8f
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/NetconfController.java
@@ -0,0 +1,111 @@
+/*
+ * ============LICENSE_START=======================================================
+ * NETCONF-CONTROLLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore;
+
+import com.tailf.jnc.JNCException;
+
+import java.io.IOException;
+
+import lombok.extern.slf4j.Slf4j;
+import org.onap.netconfsimulator.netconfcore.configuration.NetconfConfigurationService;
+import org.onap.netconfsimulator.netconfcore.model.LoadModelResponse;
+import org.onap.netconfsimulator.netconfcore.model.NetconfModelLoaderService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+@Slf4j
+@RestController
+@RequestMapping("netconf")
+class NetconfController {
+
+ private final NetconfConfigurationService netconfService;
+ private final NetconfModelLoaderService netconfModelLoaderService;
+
+ @Autowired
+ NetconfController(NetconfConfigurationService netconfService,
+ NetconfModelLoaderService netconfModelLoaderService) {
+ this.netconfService = netconfService;
+ this.netconfModelLoaderService = netconfModelLoaderService;
+ }
+
+ @GetMapping(value = "get", produces = "application/xml")
+ ResponseEntity<String> getNetconfConfiguration() throws IOException, JNCException {
+ return ResponseEntity.ok(netconfService.getCurrentConfiguration());
+ }
+
+ @GetMapping(value = "get/{model}/{container}", produces = "application/xml")
+ ResponseEntity<String> getNetconfConfiguration(@PathVariable String model,
+ @PathVariable String container)
+ throws IOException {
+ ResponseEntity<String> entity;
+ try {
+ entity = ResponseEntity.ok(netconfService.getCurrentConfiguration(model, container));
+ } catch (JNCException exception) {
+ log.error("Get configuration for model {} and container {} failed.", model, container,
+ exception);
+ entity = ResponseEntity.badRequest().body(exception.toString());
+ }
+ return entity;
+ }
+
+ @PostMapping(value = "edit-config", produces = "application/xml")
+ @ResponseStatus(HttpStatus.ACCEPTED)
+ ResponseEntity<String> editConfig(@RequestPart("editConfigXml") MultipartFile editConfig)
+ throws IOException, JNCException {
+ log.info("Loading updated configuration");
+ if (editConfig == null || editConfig.isEmpty()) {
+ throw new IllegalArgumentException("No XML file with proper name: editConfigXml found.");
+ }
+ return ResponseEntity
+ .status(HttpStatus.ACCEPTED)
+ .body(netconfService.editCurrentConfiguration(editConfig));
+ }
+
+ @PostMapping("model/{moduleName}")
+ ResponseEntity<String> loadNewYangModel(@RequestBody MultipartFile yangModel,
+ @RequestBody MultipartFile initialConfig, @PathVariable String moduleName)
+ throws IOException {
+ LoadModelResponse response = netconfModelLoaderService.loadYangModel(yangModel, initialConfig, moduleName);
+ return ResponseEntity
+ .status(response.getStatusCode())
+ .body(response.getMessage());
+ }
+
+ @DeleteMapping("model/{modelName}")
+ ResponseEntity<String> deleteYangModel(@PathVariable String modelName)
+ throws IOException {
+ LoadModelResponse response = netconfModelLoaderService.deleteYangModel(modelName);
+ return ResponseEntity
+ .status(response.getStatusCode())
+ .body(response.getMessage());
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfBeanConfiguration.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfBeanConfiguration.java
new file mode 100644
index 0000000..d90c60d
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfBeanConfiguration.java
@@ -0,0 +1,60 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+class NetconfBeanConfiguration {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NetconfBeanConfiguration.class);
+
+ @Value("${netconf.port}")
+ private Integer netconfPort;
+
+ @Value("${netconf.address}")
+ private String netconfAddress;
+
+ @Value("${netconf.user}")
+ private String netconfUser;
+
+ @Value("${netconf.password}")
+ private String netconfPassword;
+
+ @Bean
+ NetconfConfigurationReader configurationReader() {
+ NetconfConnectionParams params = new NetconfConnectionParams(netconfAddress, netconfPort, netconfUser, netconfPassword);
+ LOGGER.info("Configuration params are : {}", params);
+ return new NetconfConfigurationReader(params, new NetconfSessionHelper());
+ }
+
+ @Bean
+ NetconfConfigurationEditor configurationEditor() {
+ NetconfConnectionParams params =
+ new NetconfConnectionParams(netconfAddress, netconfPort, netconfUser, netconfPassword);
+ return new NetconfConfigurationEditor(params, new NetconfSessionHelper());
+ }
+
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationEditor.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationEditor.java
new file mode 100644
index 0000000..992c88d
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationEditor.java
@@ -0,0 +1,50 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import com.tailf.jnc.Element;
+import com.tailf.jnc.JNCException;
+import com.tailf.jnc.NetconfSession;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+
+@Slf4j
+public class NetconfConfigurationEditor {
+
+ private NetconfConnectionParams params;
+ private NetconfSessionHelper netconfSessionHelper;
+
+ public NetconfConfigurationEditor(NetconfConnectionParams params, NetconfSessionHelper netconfSessionHelper) {
+ this.params = params;
+ this.netconfSessionHelper = netconfSessionHelper;
+ }
+
+ void editConfig(Element configurationXmlElement) throws JNCException, IOException {
+ log.debug("New configuration passed to simulator: {}", configurationXmlElement.toXMLString());
+ NetconfSession session = netconfSessionHelper.createNetconfSession(params);
+ session.editConfig(configurationXmlElement);
+ session.closeSession();
+
+ log.info("Successfully updated configuration");
+ }
+
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationReader.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationReader.java
new file mode 100644
index 0000000..10fe40e
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationReader.java
@@ -0,0 +1,57 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import com.tailf.jnc.JNCException;
+import com.tailf.jnc.NetconfSession;
+import com.tailf.jnc.NodeSet;
+import java.io.IOException;
+import java.util.Objects;
+
+class NetconfConfigurationReader {
+
+ private NetconfConnectionParams params;
+ private NetconfSessionHelper netconfSessionHelper;
+
+ NetconfConfigurationReader(NetconfConnectionParams params, NetconfSessionHelper netconfSessionHelper) {
+ this.params = params;
+ this.netconfSessionHelper = netconfSessionHelper;
+ }
+
+ String getRunningConfig() throws IOException, JNCException {
+ NetconfSession session = netconfSessionHelper.createNetconfSession(params);
+ String config = session.getConfig().toXMLString();
+ session.closeSession();
+ return config;
+ }
+
+ String getRunningConfig(String modelPath) throws IOException, JNCException {
+ NetconfSession session = netconfSessionHelper.createNetconfSession(params);
+ NodeSet config = session.getConfig(modelPath);
+ if (Objects.isNull(config) || Objects.isNull(config.first())) {
+ throw new JNCException(JNCException.ELEMENT_MISSING, modelPath);
+ }
+ session.closeSession();
+ return config.first().toXMLString();
+ }
+
+
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationService.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationService.java
new file mode 100644
index 0000000..248aec4
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationService.java
@@ -0,0 +1,76 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import com.tailf.jnc.Element;
+import com.tailf.jnc.JNCException;
+import com.tailf.jnc.XMLParser;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import org.xml.sax.InputSource;
+
+@Service
+public class NetconfConfigurationService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NetconfConfigurationService.class);
+ private static final String CONFIGURATION_HAS_BEEN_ACTIVATED = "New configuration has been activated";
+
+ private final NetconfConfigurationReader netconfConfigurationReader;
+ private NetconfConfigurationEditor configurationEditor;
+ private XMLParser parser;
+
+ @Autowired
+ public NetconfConfigurationService(NetconfConfigurationReader netconfConfigurationReader,
+ NetconfConfigurationEditor netconfConfigurationEditor) throws JNCException {
+ this.netconfConfigurationReader = netconfConfigurationReader;
+ this.configurationEditor = netconfConfigurationEditor;
+ this.parser = new XMLParser();
+ }
+
+ public String getCurrentConfiguration() throws IOException, JNCException {
+ return netconfConfigurationReader.getRunningConfig();
+ }
+
+ public String getCurrentConfiguration(String model, String container) throws IOException, JNCException {
+ String path = String.format("/%s:%s", model, container);
+ return netconfConfigurationReader.getRunningConfig(path);
+ }
+
+ public String editCurrentConfiguration(MultipartFile newConfiguration) throws IOException, JNCException {
+ Element configurationElement = convertMultipartToXmlElement(newConfiguration);
+ configurationEditor.editConfig(configurationElement);
+
+ LOGGER.debug("Loading new configuration: \n{}", configurationElement.toXMLString());
+ return CONFIGURATION_HAS_BEEN_ACTIVATED;
+ }
+
+ private Element convertMultipartToXmlElement(MultipartFile editConfig) throws IOException, JNCException {
+ InputSource inputSourceUpdateConfig = new InputSource(new ByteArrayInputStream(editConfig.getBytes()));
+ return parser.parse(inputSourceUpdateConfig);
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationTO.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationTO.java
new file mode 100644
index 0000000..e43ff69
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationTO.java
@@ -0,0 +1,32 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class NetconfConfigurationTO {
+
+ private String configuration;
+
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConnectionParams.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConnectionParams.java
new file mode 100644
index 0000000..ace0ee0
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConnectionParams.java
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
+
+@AllArgsConstructor
+@ToString
+@Getter
+class NetconfConnectionParams {
+
+ private final String address;
+ private final int port;
+ private final String user;
+ private final String password;
+
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfSessionHelper.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfSessionHelper.java
new file mode 100644
index 0000000..69fda7d
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfSessionHelper.java
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import com.tailf.jnc.JNCException;
+import com.tailf.jnc.NetconfSession;
+import com.tailf.jnc.SSHConnection;
+import com.tailf.jnc.SSHSession;
+import java.io.IOException;
+
+class NetconfSessionHelper {
+
+ NetconfSession createNetconfSession(NetconfConnectionParams params) throws IOException, JNCException {
+ SSHConnection sshConnection = new SSHConnection(params.getAddress(), params.getPort());
+ sshConnection.authenticateWithPassword(params.getUser(), params.getPassword());
+ return new NetconfSession(new SSHSession(sshConnection));
+ }
+
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/model/LoadModelResponse.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/model/LoadModelResponse.java
new file mode 100644
index 0000000..a6e292f
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/model/LoadModelResponse.java
@@ -0,0 +1,40 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.model;
+
+public class LoadModelResponse {
+
+ private Integer statusCode;
+ private String message;
+
+ public LoadModelResponse(Integer statusCode, String message) {
+ this.statusCode = statusCode;
+ this.message = message;
+ }
+
+ public Integer getStatusCode() {
+ return this.statusCode;
+ }
+
+ public String getMessage() {
+ return this.message;
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/model/NetconfModelLoaderService.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/model/NetconfModelLoaderService.java
new file mode 100644
index 0000000..7e07395
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/netconfcore/model/NetconfModelLoaderService.java
@@ -0,0 +1,104 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.model;
+
+import java.io.IOException;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+@Service
+public class NetconfModelLoaderService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NetconfModelLoaderService.class);
+
+ @Value("${netconf.address}")
+ private String netconfIp;
+
+ @Value("${netconf.model-loader.port}")
+ private String modelLoaderPort;
+
+ private final HttpClient httpClient;
+
+ @Autowired
+ public NetconfModelLoaderService(HttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public LoadModelResponse deleteYangModel(String yangModelName) throws IOException {
+ String uri = getDeleteAddress(yangModelName);
+ HttpDelete httpDelete = new HttpDelete(uri);
+ HttpResponse httpResponse = httpClient.execute(httpDelete);
+ return parseResponse(httpResponse);
+ }
+
+ public LoadModelResponse loadYangModel(MultipartFile yangModel, MultipartFile initialConfig, String moduleName)
+ throws IOException {
+ HttpPost httpPost = new HttpPost(getBackendAddress());
+ HttpEntity httpEntity = MultipartEntityBuilder.create()
+ .addBinaryBody("yangModel", yangModel.getInputStream(), ContentType.MULTIPART_FORM_DATA,
+ yangModel.getOriginalFilename())
+ .addBinaryBody("initialConfig", initialConfig.getInputStream(), ContentType.MULTIPART_FORM_DATA,
+ initialConfig.getOriginalFilename())
+ .addTextBody("moduleName", moduleName)
+ .build();
+ httpPost.setEntity(httpEntity);
+ HttpResponse response = httpClient.execute(httpPost);
+ return parseResponse(response);
+ }
+
+ String getBackendAddress() {
+ return String.format("http://%s:%s/model", netconfIp, modelLoaderPort);
+ }
+
+ String getDeleteAddress(String yangModelName) {
+ return String.format("%s?yangModelName=%s", getBackendAddress(), yangModelName);
+ }
+
+
+ private LoadModelResponse parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ String responseBody = EntityUtils.toString(response.getEntity());
+
+ logResponse(statusCode, responseBody);
+ return new LoadModelResponse(statusCode, responseBody);
+ }
+
+ private void logResponse(int statusCode, String responseBody) {
+ if (statusCode >= HttpStatus.BAD_REQUEST.value()) {
+ LOGGER.error(responseBody);
+ } else {
+ LOGGER.info(responseBody);
+ }
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/EndpointConfig.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/EndpointConfig.java
new file mode 100644
index 0000000..4eaa850
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/EndpointConfig.java
@@ -0,0 +1,46 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.websocket;
+
+import java.util.Collections;
+import org.onap.netconfsimulator.websocket.message.NetconfMessageEncoder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+import org.springframework.web.socket.server.standard.ServerEndpointRegistration;
+
+@Configuration
+class EndpointConfig {
+
+ @Bean
+ ServerEndpointRegistration endpointRegistration() {
+ ServerEndpointRegistration serverEndpointRegistration = new ServerEndpointRegistration("/netconf",
+ NetconfEndpoint.class);
+ serverEndpointRegistration.setEncoders(Collections.singletonList(NetconfMessageEncoder.class));
+ return serverEndpointRegistration;
+ }
+
+ @Bean
+ ServerEndpointExporter endpointExporter() {
+ return new ServerEndpointExporter();
+ }
+}
+
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/NetconfEndpoint.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/NetconfEndpoint.java
new file mode 100644
index 0000000..5870ee1
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/NetconfEndpoint.java
@@ -0,0 +1,95 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.websocket;
+
+
+import java.util.Optional;
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+
+import org.onap.netconfsimulator.kafka.listener.KafkaListenerEntry;
+import org.onap.netconfsimulator.kafka.listener.KafkaListenerHandler;
+import org.onap.netconfsimulator.websocket.message.NetconfMessageListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.kafka.listener.AbstractMessageListenerContainer;
+import org.springframework.kafka.listener.MessageListener;
+import org.springframework.stereotype.Component;
+
+//instance of this class is created every each websocket request
+@Component
+class NetconfEndpoint extends Endpoint {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NetconfEndpoint.class);
+ private static final String TOPIC_NAME = "config";
+
+ private KafkaListenerHandler kafkaListenerHandler;
+
+ public Optional<KafkaListenerEntry> getEntry() {
+ return entry;
+ }
+
+ public void setEntry(Optional<KafkaListenerEntry> entry) {
+ this.entry = entry;
+ }
+
+ private Optional<KafkaListenerEntry> entry = Optional.empty();
+
+
+ @Autowired
+ NetconfEndpoint(KafkaListenerHandler listenerHandler) {
+ this.kafkaListenerHandler = listenerHandler;
+ }
+
+ @Override
+ public void onOpen(Session session, EndpointConfig endpointConfig) {
+ RemoteEndpoint.Basic basicRemote = session.getBasicRemote();
+
+ addKafkaListener(basicRemote);
+ entry.ifPresent(x -> LOGGER.info("Session with client: {} established", x.getClientId()));
+ }
+
+ @Override
+ public void onError(Session session, Throwable throwable) {
+ LOGGER.error("Unexpected error occurred", throwable);
+ }
+
+ @Override
+ public void onClose(Session session, CloseReason closeReason) {
+ entry.ifPresent(x -> x.getListenerContainer().stop());
+ entry.ifPresent(x -> LOGGER.info("Closing connection for client: {}", x.getClientId()));
+ }
+
+
+ private void addKafkaListener(RemoteEndpoint.Basic remoteEndpoint) {
+ MessageListener messageListener = new NetconfMessageListener(remoteEndpoint);
+
+ KafkaListenerEntry kafkaListener = kafkaListenerHandler.createKafkaListener(messageListener, TOPIC_NAME);
+
+ AbstractMessageListenerContainer listenerContainer = kafkaListener.getListenerContainer();
+ listenerContainer.start();
+ entry = Optional.of(kafkaListener);
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/message/NetconfMessageEncoder.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/message/NetconfMessageEncoder.java
new file mode 100644
index 0000000..349b7e2
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/message/NetconfMessageEncoder.java
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.websocket.message;
+
+import org.onap.netconfsimulator.kafka.model.KafkaMessage;
+import org.springframework.web.socket.adapter.standard.ConvertingEncoderDecoderSupport;
+
+public class NetconfMessageEncoder extends ConvertingEncoderDecoderSupport.TextEncoder<KafkaMessage> {
+
+ private static final String MESSAGE_FORMAT = "%s: %s";
+
+ @Override
+ public String encode(KafkaMessage netconfMessage) {
+ return String.format(MESSAGE_FORMAT, netconfMessage.getTimestamp(), netconfMessage.getConfiguration());
+ }
+}
diff --git a/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/message/NetconfMessageListener.java b/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/message/NetconfMessageListener.java
new file mode 100644
index 0000000..61610de
--- /dev/null
+++ b/netconfsimulator/src/main/java/org/onap/netconfsimulator/websocket/message/NetconfMessageListener.java
@@ -0,0 +1,51 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.websocket.message;
+
+import java.io.IOException;
+import javax.websocket.EncodeException;
+import javax.websocket.RemoteEndpoint;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.onap.netconfsimulator.kafka.model.KafkaMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.kafka.listener.MessageListener;
+
+public class NetconfMessageListener implements MessageListener<String, String> {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NetconfMessageListener.class);
+ private RemoteEndpoint.Basic remoteEndpoint;
+
+ public NetconfMessageListener(RemoteEndpoint.Basic remoteEndpoint) {
+ this.remoteEndpoint = remoteEndpoint;
+ }
+
+ @Override
+ public void onMessage(ConsumerRecord<String, String> message) {
+ LOGGER.debug("Attempting to send message to {}", remoteEndpoint);
+ try {
+ remoteEndpoint
+ .sendObject(new KafkaMessage(message.timestamp(), message.value()));
+ } catch (IOException | EncodeException exception) {
+ LOGGER.error("Error during sending message to remote", exception);
+ }
+ }
+}
diff --git a/netconfsimulator/src/main/resources/application.properties b/netconfsimulator/src/main/resources/application.properties
new file mode 100644
index 0000000..3947cf3
--- /dev/null
+++ b/netconfsimulator/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+netconf.port=830
+netconf.address=netopeer
+netconf.user=netconf
+netconf.password=netconf
+netconf.model-loader.port=5002
+spring.kafka.bootstrap-servers=kafka1:9092
+spring.kafka.default-offset=100
+spring.kafka.consumer.auto-offset-reset=earliest
diff --git a/netconfsimulator/src/test/java/integration/NetconfFunctionsIT.java b/netconfsimulator/src/test/java/integration/NetconfFunctionsIT.java
new file mode 100644
index 0000000..95ef586
--- /dev/null
+++ b/netconfsimulator/src/test/java/integration/NetconfFunctionsIT.java
@@ -0,0 +1,211 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package integration;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.palantir.docker.compose.connection.DockerMachine;
+import com.palantir.docker.compose.connection.waiting.HealthChecks;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.bitbucket.radistao.test.annotation.BeforeAllMethods;
+import org.bitbucket.radistao.test.runner.BeforeAfterSpringTestRunner;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import com.palantir.docker.compose.DockerComposeRule;
+import org.onap.netconfsimulator.kafka.model.KafkaMessage;
+import org.springframework.http.HttpStatus;
+
+import java.io.IOException;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static junit.framework.TestCase.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(BeforeAfterSpringTestRunner.class)
+public class NetconfFunctionsIT {
+
+ private static NetconfSimulatorClient client;
+ private static ObjectMapper objectMapper;
+
+ private static final DockerMachine dockerMachine = DockerMachine
+ .localMachine()
+ .build();
+
+ private static DockerComposeRule docker = DockerComposeRule.builder()
+ .file("docker-compose.yml")
+ .machine(dockerMachine)
+ .removeConflictingContainersOnStartup(true)
+ .waitingForService("sftp-server", HealthChecks.toHaveAllPortsOpen())
+ .waitingForService("ftpes-server", HealthChecks.toHaveAllPortsOpen())
+ .waitingForService("zookeeper", HealthChecks.toHaveAllPortsOpen())
+ .waitingForService("netopeer", HealthChecks.toHaveAllPortsOpen())
+ .waitingForService("kafka1", HealthChecks.toHaveAllPortsOpen())
+ .waitingForService("netconf-simulator", HealthChecks.toHaveAllPortsOpen())
+ .build();
+
+ @ClassRule
+ public static TestRule exposePortMappings = docker;
+
+ @BeforeClass
+ public static void setUpClass() {
+ objectMapper = new ObjectMapper();
+ client = new NetconfSimulatorClient(String.format("http://%s:%d", docker.containers().ip(), 9000));
+ }
+
+ @BeforeAllMethods
+ public void setupBeforeAll() throws InterruptedException {
+ if (client.isServiceAvailable(Instant.now(), Duration.ofSeconds(45))) {
+ Thread.sleep(60000);
+ return;
+ }
+ fail("Application failed to start within established timeout: 45 seconds. Exiting.");
+ }
+
+ @Before
+ public void setUp() {
+ client.reinitializeClient();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ client.releaseClient();
+ }
+
+ @Test
+ public void testShouldLoadModelEditConfigurationAndDeleteModule() throws IOException {
+ // do load
+ try (CloseableHttpResponse response = client
+ .loadModel("newyangmodel", "newYangModel.yang", "initialConfig.xml")) {
+ assertResponseStatusCode(response, HttpStatus.OK);
+ String original = client.getResponseContentAsString(response);
+ assertThat(original).isEqualTo("\"Successfully started\"\n");
+ }
+ // do edit-config
+ try (CloseableHttpResponse updateResponse = client.updateConfig()) {
+ String afterUpdateConfigContent = client.getResponseContentAsString(updateResponse);
+ assertResponseStatusCode(updateResponse, HttpStatus.ACCEPTED);
+ assertThat(afterUpdateConfigContent).isEqualTo("New configuration has been activated");
+ }
+ // do delete
+ try (CloseableHttpResponse deleteResponse = client.deleteModel("newyangmodel")) {
+ assertResponseStatusCode(deleteResponse, HttpStatus.OK);
+ String original = client.getResponseContentAsString(deleteResponse);
+ assertThat(original).isEqualTo("\"Successfully deleted\"\n");
+ }
+ }
+
+ @Test
+ public void testShouldGetCurrentConfigurationAndEditItSuccessfully() throws IOException {
+ try (CloseableHttpResponse updateResponse = client.updateConfig();
+ CloseableHttpResponse newCurrentConfigResponse = client.getCurrentConfig()) {
+ String afterUpdateConfigContent = client.getResponseContentAsString(updateResponse);
+
+ assertResponseStatusCode(updateResponse, HttpStatus.ACCEPTED);
+ assertResponseStatusCode(newCurrentConfigResponse, HttpStatus.OK);
+
+ assertThat(afterUpdateConfigContent).isEqualTo("New configuration has been activated");
+ }
+ }
+
+ @Test
+ public void testShouldPersistConfigChangesAndGetAllWhenRequested() throws IOException {
+ client.updateConfig();
+
+ try (CloseableHttpResponse newAllConfigChangesResponse = client.getAllConfigChanges()) {
+ String newAllConfigChangesString = client.getResponseContentAsString(newAllConfigChangesResponse);
+ assertResponseStatusCode(newAllConfigChangesResponse, HttpStatus.OK);
+
+ List<KafkaMessage> kafkaMessages = objectMapper
+ .readValue(newAllConfigChangesString, new TypeReference<List<KafkaMessage>>() {
+ });
+
+ assertThat(kafkaMessages.size()).isGreaterThanOrEqualTo(1);
+ Set<String> configChangeContent = kafkaMessages.stream().map(KafkaMessage::getConfiguration)
+ .collect(Collectors.toSet());
+ assertThat(configChangeContent)
+ .anyMatch(el -> el.contains("new value: /pnf-simulator:config/itemValue1 = 100"));
+ assertThat(configChangeContent)
+ .anyMatch(el -> el.contains("new value: /pnf-simulator:config/itemValue2 = 200"));
+ }
+ }
+
+ @Test
+ public void testShouldGetLastMessage() throws IOException {
+ client.updateConfig();
+
+ try (CloseableHttpResponse lastConfigChangesResponse = client.getLastConfigChanges(2)) {
+ String newAllConfigChangesString = client.getResponseContentAsString(lastConfigChangesResponse);
+ List<KafkaMessage> kafkaMessages = objectMapper
+ .readValue(newAllConfigChangesString, new TypeReference<List<KafkaMessage>>() {
+ });
+
+ assertThat(kafkaMessages).hasSize(2);
+ assertThat(kafkaMessages.get(0).getConfiguration())
+ .contains("new value: /pnf-simulator:config/itemValue1 = 100");
+ assertThat(kafkaMessages.get(1).getConfiguration())
+ .contains("new value: /pnf-simulator:config/itemValue2 = 200");
+ }
+ }
+
+ @Test
+ public void testShouldLoadNewYangModelAndReconfigure() throws IOException {
+ try (CloseableHttpResponse response = client
+ .loadModel("newyangmodel", "newYangModel.yang", "initialConfig.xml")) {
+ assertResponseStatusCode(response, HttpStatus.OK);
+
+ String original = client.getResponseContentAsString(response);
+
+ assertThat(original).isEqualTo("\"Successfully started\"\n");
+ }
+ }
+
+ @Test
+ public void shouldGetLoadedModelByName() throws IOException {
+ testShouldLoadNewYangModelAndReconfigure();
+
+ try (CloseableHttpResponse response = client.getConfigByModelAndContainerNames("newyangmodel", "config2")) {
+ assertResponseStatusCode(response, HttpStatus.OK);
+ String config = client.getResponseContentAsString(response);
+
+ assertThat(config).isEqualTo(
+ "<config2 xmlns=\"http://onap.org/newyangmodel\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + " <item1>100</item1>\n"
+ + "</config2>\n");
+ }
+
+ }
+
+ private void assertResponseStatusCode(HttpResponse response, HttpStatus expectedStatus) {
+ assertThat(response.getStatusLine().getStatusCode()).isEqualTo(expectedStatus.value());
+ }
+
+}
diff --git a/netconfsimulator/src/test/java/integration/NetconfSimulatorClient.java b/netconfsimulator/src/test/java/integration/NetconfSimulatorClient.java
new file mode 100644
index 0000000..61f2ef1
--- /dev/null
+++ b/netconfsimulator/src/test/java/integration/NetconfSimulatorClient.java
@@ -0,0 +1,150 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package integration;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.junit.platform.commons.logging.Logger;
+import org.junit.platform.commons.logging.LoggerFactory;
+import org.springframework.util.ResourceUtils;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+
+class NetconfSimulatorClient {
+
+ private CloseableHttpClient netconfClient;
+ private String simulatorBaseUrl;
+ private static final Logger LOG = LoggerFactory.getLogger(NetconfSimulatorClient.class);
+
+ NetconfSimulatorClient(String simulatorBaseUrl) {
+ this.netconfClient = HttpClients.createDefault();
+ this.simulatorBaseUrl = simulatorBaseUrl;
+ }
+
+ CloseableHttpResponse loadModel(String moduleName, String yangModelFileName, String initialiConfigFileName) throws IOException {
+ String updateConfigUrl = String.format("%s/netconf/model/%s", simulatorBaseUrl, moduleName);
+ HttpPost httpPost = new HttpPost(updateConfigUrl);
+ HttpEntity updatedConfig = MultipartEntityBuilder
+ .create()
+ .addBinaryBody("yangModel", ResourceUtils.getFile(String.format("classpath:%s", yangModelFileName)))
+ .addBinaryBody("initialConfig", ResourceUtils.getFile(String.format("classpath:%s",initialiConfigFileName)))
+ .addTextBody("moduleName", moduleName)
+ .build();
+ httpPost.setEntity(updatedConfig);
+ return netconfClient.execute(httpPost);
+ }
+
+ CloseableHttpResponse deleteModel(String moduleName) throws IOException {
+ String deleteModuleUrl = String.format("%s/netconf/model/%s", simulatorBaseUrl, moduleName);
+ HttpDelete httpDelete = new HttpDelete(deleteModuleUrl);
+ return netconfClient.execute(httpDelete);
+ }
+
+ boolean isServiceAvailable(Instant startTime, Duration maxWaitingDuration) throws InterruptedException {
+ boolean isServiceReady = false;
+ while (Duration.between(startTime, Instant.now()).compareTo(maxWaitingDuration) < 1){
+ if(checkIfSimResponds()){
+ return true;
+ }
+ else {
+ LOG.info(() -> "Simulator not ready yet, retrying in 5s...");
+ Thread.sleep(5000);
+ }
+ }
+ return isServiceReady;
+ }
+
+ private boolean checkIfSimResponds() throws InterruptedException {
+ try(CloseableHttpResponse pingResponse = getCurrentConfig()){
+ String responseString = getResponseContentAsString(pingResponse);
+ if(pingResponse.getStatusLine().getStatusCode() == 200 && !responseString.trim().isEmpty()){
+ return true;
+ }
+ }
+ catch(IOException ex){
+ LOG.error(ex, () -> "EXCEPTION");
+ Thread.sleep(5000);
+ }
+ return false;
+ }
+
+ CloseableHttpResponse getCurrentConfig() throws IOException {
+ String netconfAddress = String.format("%s/netconf/get", simulatorBaseUrl);
+ HttpGet get = new HttpGet(netconfAddress);
+ return netconfClient.execute(get);
+ }
+
+ CloseableHttpResponse getConfigByModelAndContainerNames(String model, String container) throws IOException {
+ String netconfAddress = String
+ .format("%s/netconf/get/%s/%s", simulatorBaseUrl, model, container);
+ HttpGet get = new HttpGet(netconfAddress);
+ return netconfClient.execute(get);
+ }
+
+ CloseableHttpResponse updateConfig() throws IOException {
+ String updateConfigUrl = String.format("%s/netconf/edit-config", simulatorBaseUrl);
+ HttpPost httpPost = new HttpPost(updateConfigUrl);
+ HttpEntity updatedConfig = MultipartEntityBuilder
+ .create()
+ .addBinaryBody("editConfigXml", ResourceUtils.getFile("classpath:updatedConfig.xml"))
+ .build();
+ httpPost.setEntity(updatedConfig);
+ return netconfClient.execute(httpPost);
+ }
+
+ CloseableHttpResponse getAllConfigChanges() throws IOException {
+ String netconfStoreCmHistoryAddress = String.format("%s/store/cm-history", simulatorBaseUrl);
+ HttpGet configurationChangesResponse = new HttpGet(netconfStoreCmHistoryAddress);
+ return netconfClient.execute(configurationChangesResponse);
+ }
+
+ CloseableHttpResponse getLastConfigChanges(int howManyLastChanges) throws IOException {
+ String netconfStoreCmHistoryAddress = String.format("%s/store/less?offset=%d", simulatorBaseUrl, howManyLastChanges);
+ HttpGet configurationChangesResponse = new HttpGet(netconfStoreCmHistoryAddress);
+ return netconfClient.execute(configurationChangesResponse);
+ }
+
+ void releaseClient() throws IOException {
+ netconfClient.close();
+ }
+
+ void reinitializeClient(){
+ netconfClient = HttpClients.createDefault();
+ }
+
+ String getResponseContentAsString(HttpResponse response) throws IOException {
+ HttpEntity entity = response.getEntity();
+ String entityStringRepr = EntityUtils.toString(entity);
+ EntityUtils.consume(entity);
+ return entityStringRepr;
+ }
+
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/EmbeddedKafkaConfig.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/EmbeddedKafkaConfig.java
new file mode 100644
index 0000000..5ddf2b2
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/EmbeddedKafkaConfig.java
@@ -0,0 +1,69 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka;
+
+
+import java.util.Map;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+import org.springframework.kafka.test.utils.KafkaTestUtils;
+
+import static org.onap.netconfsimulator.kafka.StoreServiceTest.embeddedKafka;
+
+@Configuration
+class EmbeddedKafkaConfig {
+
+ @Bean
+ KafkaTemplate<String, String> kafkaTemplate(){
+ return new KafkaTemplate<>(producerFactory());
+ }
+
+ @Bean
+ @Autowired
+ ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(ConsumerFactory<String, String> consumerFactory){
+ ConcurrentKafkaListenerContainerFactory<String, String> containerFactory = new ConcurrentKafkaListenerContainerFactory<>();
+ containerFactory.setConsumerFactory(consumerFactory);
+ return containerFactory;
+ }
+
+ @Bean
+ ConsumerFactory<String, String> consumerFactory(){
+ Map<String, Object> consumerProperties =
+ KafkaTestUtils.consumerProps("sender", "false", embeddedKafka.getEmbeddedKafka());
+ consumerProperties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
+ return new DefaultKafkaConsumerFactory<>(consumerProperties);
+ }
+
+ private ProducerFactory<String, String> producerFactory() {
+ Map<String, Object> senderProperties =
+ KafkaTestUtils.senderProps(embeddedKafka.getEmbeddedKafka().getBrokersAsString());
+ return new DefaultKafkaProducerFactory<>(senderProperties);
+ }
+
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/StoreControllerTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/StoreControllerTest.java
new file mode 100644
index 0000000..02eec12
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/StoreControllerTest.java
@@ -0,0 +1,86 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka;
+
+import java.time.Instant;
+import java.util.List;
+import org.assertj.core.api.Assertions;
+import org.assertj.core.util.Lists;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.mockito.Mockito.when;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+public class StoreControllerTest {
+
+ private static final String MESSAGE_3 = "message 3";
+ private static final String MESSAGE_2 = "message 2";
+ private static final String MESSAGE_1 = "message 1";
+
+ private static final List<MessageDTO> ALL_MESSAGES = Lists.newArrayList(new MessageDTO(Instant.now().getEpochSecond(), MESSAGE_1),
+ new MessageDTO(Instant.now().getEpochSecond(), MESSAGE_2),
+ new MessageDTO(Instant.now().getEpochSecond(), MESSAGE_3));
+
+ @Mock
+ private StoreService service;
+
+ @InjectMocks
+ private StoreController storeController;
+
+
+ @Test
+ public void lessShouldTakeAllMessagesTest() {
+ when(service.getLastMessages(3)).thenReturn(ALL_MESSAGES);
+
+ List<MessageDTO> lessResponse = storeController.less(3);
+
+ assertResponseContainsExpectedMessages(lessResponse, 3, MESSAGE_1, MESSAGE_2, MESSAGE_3);
+ }
+
+ @Test
+ public void lessShouldTakeTwoMessagesTest() {
+ when(service.getLastMessages(2)).thenReturn(Lists.newArrayList(new MessageDTO(Instant.now().getEpochSecond(), MESSAGE_1)));
+
+ List<MessageDTO> lessResult = storeController.less(2);
+
+ assertResponseContainsExpectedMessages(lessResult, 1, MESSAGE_1);
+ }
+
+ @Test
+ public void shouldGetAllMessages(){
+ when(service.getAllMessages()).thenReturn(ALL_MESSAGES);
+
+ List<MessageDTO> allMsgResult = storeController.getAllConfigurationChanges();
+
+ assertResponseContainsExpectedMessages(allMsgResult, 3, MESSAGE_1, MESSAGE_2, MESSAGE_3);
+ }
+
+ private void assertResponseContainsExpectedMessages(List<MessageDTO> actualMessages, int expectedMessageCount, String... expectedMessages){
+ Assertions.assertThat(actualMessages.stream().map(MessageDTO::getConfiguration))
+ .hasSize(expectedMessageCount)
+ .containsExactly(expectedMessages);
+ }
+
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/StoreServiceTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/StoreServiceTest.java
new file mode 100644
index 0000000..fd36116
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/StoreServiceTest.java
@@ -0,0 +1,103 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka;
+
+import org.bitbucket.radistao.test.annotation.BeforeAllMethods;
+import org.bitbucket.radistao.test.runner.BeforeAfterSpringTestRunner;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.test.context.EmbeddedKafka;
+import org.springframework.kafka.test.rule.EmbeddedKafkaRule;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(BeforeAfterSpringTestRunner.class)
+@SpringBootTest(classes = {StoreService.class, EmbeddedKafkaConfig.class})
+@EmbeddedKafka
+public class StoreServiceTest {
+
+ private static final String MESSAGE_1 = "message1";
+ private static final String MESSAGE_2 = "message2";
+ private static final String MESSAGE_3 = "message3";
+
+ @ClassRule
+ public static EmbeddedKafkaRule embeddedKafka = new EmbeddedKafkaRule(1, true, 1, "config");
+
+ @Autowired
+ StoreService service;
+
+ @Autowired
+ KafkaTemplate<String, String> kafkaTemplate;
+
+ @BeforeAllMethods
+ public void setupBeforeAll() {
+ prepareProducer();
+ }
+
+ @Test
+ public void testShouldReturnAllAvailableMessages(){
+
+ List<MessageDTO> actualMessages = service.getAllMessages();
+
+ assertResponseContainsExpectedMessages(actualMessages, 3, MESSAGE_1, MESSAGE_2, MESSAGE_3);
+ }
+
+ @Test
+ public void testShouldGetLastMessagesRespectingOffset(){
+
+ List<MessageDTO> wantedLastMsg = service.getLastMessages(1L);
+
+ assertResponseContainsExpectedMessages(wantedLastMsg, 1, MESSAGE_3);
+ }
+
+ @Test
+ public void testShouldGetAll3Messages() {
+ List<MessageDTO> wantedLastMsgs = service.getLastMessages(3L);
+
+ assertResponseContainsExpectedMessages(wantedLastMsgs, 3, MESSAGE_1, MESSAGE_2, MESSAGE_3);
+ }
+
+ private void prepareProducer(){
+ kafkaTemplate.send("config", "message1");
+ kafkaTemplate.send("config", "message2");
+ kafkaTemplate.send("config", "message3");
+ }
+
+ private void assertResponseContainsExpectedMessages(List<MessageDTO> actualMessages, int expectedMessageCount, String... expectedMessages){
+ assertThat(actualMessages.stream().map(MessageDTO::getConfiguration))
+ .hasSize(expectedMessageCount)
+ .containsExactly(expectedMessages);
+ }
+
+}
+
+
+
+
+
+
+
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerHandlerTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerHandlerTest.java
new file mode 100644
index 0000000..fcb7266
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/kafka/listener/KafkaListenerHandlerTest.java
@@ -0,0 +1,87 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.kafka.listener;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.listener.ContainerProperties;
+import org.springframework.kafka.listener.KafkaMessageListenerContainer;
+import org.springframework.kafka.listener.MessageListener;
+
+class KafkaListenerHandlerTest {
+
+ private static final String CLIENT_ID_REGEX = "[0-9]{10,}";
+ private static final String SAMPLE_TOPIC = "sampleTopic";
+
+ @Mock
+ private ConsumerFactory<String, String> consumerFactory;
+
+ @Mock
+ private KafkaMessageListenerContainer<String, String> kafkaMessageListenerContainer;
+
+ @Mock
+ private MessageListener messageListener;
+
+ @BeforeEach
+ void setUp() {
+ initMocks(this);
+ }
+
+
+ @Test
+ void shouldProperlyCreateKafkaListener() {
+ KafkaListenerHandler kafkaListenerHandler = spy(new KafkaListenerHandler(consumerFactory));
+ doReturn(kafkaMessageListenerContainer).when(kafkaListenerHandler)
+ .createListenerContainer(any(ContainerProperties.class), eq(SAMPLE_TOPIC));
+
+ KafkaListenerEntry kafkaListenerEntry = kafkaListenerHandler
+ .createKafkaListener(messageListener, SAMPLE_TOPIC);
+
+ assertThat(kafkaListenerEntry.getListenerContainer()).isEqualTo(kafkaMessageListenerContainer);
+ assertThat(kafkaListenerEntry.getClientId()).matches(CLIENT_ID_REGEX);
+ }
+
+ @Test
+ void shouldProperlyCreateContainer() {
+ KafkaListenerHandler kafkaListenerHandler = spy(new KafkaListenerHandler(consumerFactory));
+ ContainerProperties containerProperties = new ContainerProperties(SAMPLE_TOPIC);
+ containerProperties.setMessageListener(mock(MessageListener.class));
+
+ KafkaMessageListenerContainer<String, String> listenerContainer = kafkaListenerHandler
+ .createListenerContainer(containerProperties, SAMPLE_TOPIC);
+
+ ContainerProperties actualProperties = listenerContainer.getContainerProperties();
+ assertThat(actualProperties.getTopics()).isEqualTo(containerProperties.getTopics());
+ assertThat(actualProperties.getMessageListener()).isEqualTo(containerProperties.getMessageListener());
+ }
+
+
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/NetconfControllerTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/NetconfControllerTest.java
new file mode 100644
index 0000000..73fb627
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/NetconfControllerTest.java
@@ -0,0 +1,172 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.tailf.jnc.JNCException;
+import java.io.IOException;
+import java.nio.file.Files;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.onap.netconfsimulator.netconfcore.configuration.NetconfConfigurationService;
+import org.onap.netconfsimulator.netconfcore.model.LoadModelResponse;
+import org.onap.netconfsimulator.netconfcore.model.NetconfModelLoaderService;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.util.ResourceUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+class NetconfControllerTest {
+
+ private MockMvc mockMvc;
+
+ @Mock
+ private NetconfConfigurationService netconfService;
+
+ @Mock
+ private NetconfModelLoaderService netconfModelLoaderService;
+
+ @InjectMocks
+ private NetconfController controller;
+
+ private static final String SAMPLE_CONFIGURATION = "<config xmlns=\"http://onap.org/pnf-simulator\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><itemValue1>11</itemValue1><itemValue2>22</itemValue2></config>";
+
+ @BeforeEach
+ void setUp() {
+ initMocks(this);
+ mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+ }
+
+ @Test
+ void testShouldDigestMultipartFile() throws Exception {
+ byte[] bytes =
+ Files.readAllBytes(ResourceUtils.getFile("classpath:updatedConfig.xml").toPath());
+ MockMultipartFile file = new MockMultipartFile("editConfigXml", bytes);
+
+ mockMvc
+ .perform(MockMvcRequestBuilders.multipart("/netconf/edit-config").file(file))
+ .andExpect(status().isAccepted());
+
+ verify(netconfService).editCurrentConfiguration(any(MultipartFile.class));
+ }
+
+ @Test
+ void testShouldThrowExceptionWhenEditConfigFileWithIncorrectNameProvided() throws Exception {
+ MockMultipartFile file = new MockMultipartFile("wrongName", new byte[0]);
+
+ mockMvc
+ .perform(MockMvcRequestBuilders.multipart("/netconf/edit-config").file(file))
+ .andExpect(status().isBadRequest());
+
+ verify(netconfService, never()).editCurrentConfiguration(any(MultipartFile.class));
+ }
+
+ @Test
+ void testShouldReturnCurrentConfiguration() throws Exception {
+ when(netconfService.getCurrentConfiguration()).thenReturn(SAMPLE_CONFIGURATION);
+
+ String contentAsString =
+ mockMvc
+ .perform(get("/netconf/get"))
+ .andExpect(status().isOk())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ verify(netconfService).getCurrentConfiguration();
+ assertThat(contentAsString).isEqualTo(SAMPLE_CONFIGURATION);
+ }
+
+ @Test
+ void testShouldReturnConfigurationForGivenPath() throws Exception {
+ when(netconfService.getCurrentConfiguration("sampleModel", "sampleContainer"))
+ .thenReturn(SAMPLE_CONFIGURATION);
+
+ String contentAsString =
+ mockMvc
+ .perform(get("/netconf/get/sampleModel/sampleContainer"))
+ .andExpect(status().isOk())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ verify(netconfService).getCurrentConfiguration("sampleModel", "sampleContainer");
+ assertThat(contentAsString).isEqualTo(SAMPLE_CONFIGURATION);
+ }
+
+ @Test
+ void testShouldRaiseBadRequestWhenConfigurationIsNotPresent() throws Exception {
+ when(netconfService.getCurrentConfiguration("sampleModel", "sampleContainer2"))
+ .thenThrow(new JNCException(JNCException.ELEMENT_MISSING, "/sampleModel:sampleContainer2"));
+
+ String contentAsString =
+ mockMvc
+ .perform(get("/netconf/get/sampleModel/sampleContainer2"))
+ .andExpect(status().isBadRequest())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ assertThat(contentAsString).isEqualTo("Element does not exists: /sampleModel:sampleContainer2");
+ }
+
+ @Test
+ void shouldThrowExceptionWhenNoConfigurationPresent() throws IOException, JNCException {
+ when(netconfService.getCurrentConfiguration()).thenThrow(JNCException.class);
+
+ assertThatThrownBy(() -> mockMvc.perform(get("/netconf/get")))
+ .hasRootCauseExactlyInstanceOf(JNCException.class);
+ }
+
+ @Test
+ void testShouldDeleteYangModel() throws Exception {
+ String responseOkString = "Alles klar";
+ String yangModelName = "someModel";
+ LoadModelResponse loadModelResponse = new LoadModelResponse(200, responseOkString);
+ String uri = String.format("/netconf/model/%s", yangModelName);
+ when(netconfModelLoaderService.deleteYangModel(yangModelName)).thenReturn(loadModelResponse);
+
+ String contentAsString =
+ mockMvc
+ .perform(delete(uri))
+ .andExpect(status().isOk())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ verify(netconfModelLoaderService).deleteYangModel(yangModelName);
+ assertThat(contentAsString).isEqualTo(responseOkString);
+ }
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationEditorTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationEditorTest.java
new file mode 100644
index 0000000..371bdd8
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationEditorTest.java
@@ -0,0 +1,69 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import com.tailf.jnc.Element;
+import com.tailf.jnc.JNCException;
+import com.tailf.jnc.NetconfSession;
+import com.tailf.jnc.XMLParser;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.onap.netconfsimulator.netconfcore.configuration.NetconfConfigurationEditor;
+import org.springframework.util.ResourceUtils;
+import org.xml.sax.InputSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+class NetconfConfigurationEditorTest {
+
+ @Mock
+ private NetconfSession session;
+ @Mock
+ private NetconfSessionHelper netconfSessionHelper;
+
+ private NetconfConfigurationEditor editor;
+
+ @BeforeEach
+ void setUp() throws IOException, JNCException {
+ initMocks(this);
+ NetconfConnectionParams params = null;
+ Mockito.when(netconfSessionHelper.createNetconfSession(params)).thenReturn(session);
+ editor = new NetconfConfigurationEditor(params, netconfSessionHelper);
+ }
+
+ @Test
+ void testShouldEditConfigSuccessfully() throws IOException, JNCException {
+ byte[] bytes =
+ Files.readAllBytes(ResourceUtils.getFile("classpath:updatedConfig.xml").toPath());
+ Element editConfigXml = new XMLParser().parse(new InputSource(new ByteArrayInputStream(bytes)));
+
+ editor.editConfig(editConfigXml);
+
+ verify(session).editConfig(editConfigXml);
+ }
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationReaderTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationReaderTest.java
new file mode 100644
index 0000000..a0a15b9
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationReaderTest.java
@@ -0,0 +1,94 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.tailf.jnc.Element;
+import com.tailf.jnc.JNCException;
+import com.tailf.jnc.NetconfSession;
+import com.tailf.jnc.NodeSet;
+import java.io.IOException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+class NetconfConfigurationReaderTest {
+
+ private static final String NETCONF_MODEL_PATH = "";
+ private static final String EXPECTED_STRING_XML = "<?xml version=\"1.0\"?>";
+ private NetconfConfigurationReader reader;
+
+ @Mock
+ private NetconfSession netconfSession;
+
+ @Mock
+ private NetconfSessionHelper netconfSessionHelper;
+
+ @Mock
+ private NodeSet nodeSet;
+
+ @Mock
+ private Element element;
+
+ @BeforeEach
+ void setUp() throws IOException, JNCException {
+ MockitoAnnotations.initMocks(this);
+ NetconfConnectionParams params = null;
+ Mockito.when(netconfSessionHelper.createNetconfSession(params)).thenReturn(netconfSession);
+ reader = new NetconfConfigurationReader(params, netconfSessionHelper);
+ }
+
+ @Test
+ void properlyReadXML() throws IOException, JNCException {
+ when(netconfSession.getConfig()).thenReturn(nodeSet);
+ when(nodeSet.toXMLString()).thenReturn(EXPECTED_STRING_XML);
+
+ String result = reader.getRunningConfig();
+
+ verify(netconfSession).getConfig();
+ verify(nodeSet).toXMLString();
+ assertThat(result).isEqualTo(EXPECTED_STRING_XML);
+ }
+
+ @Test
+ void shouldProperlyReadXmlByName() throws IOException, JNCException {
+ when(netconfSession.getConfig("/sample:test")).thenReturn(nodeSet);
+ when(nodeSet.first()).thenReturn(element);
+ when(element.toXMLString()).thenReturn(EXPECTED_STRING_XML);
+
+ String result = reader.getRunningConfig("/sample:test");
+
+ verify(netconfSession).getConfig("/sample:test");
+ verify(nodeSet, times(2)).first();
+ verify(element).toXMLString();
+
+ assertThat(result).isEqualTo(EXPECTED_STRING_XML);
+ }
+
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationServiceTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationServiceTest.java
new file mode 100644
index 0000000..6da6572
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/configuration/NetconfConfigurationServiceTest.java
@@ -0,0 +1,102 @@
+/*
+ * ============LICENSE_START=======================================================
+ * PNF-REGISTRATION-HANDLER
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.tailf.jnc.Element;
+import com.tailf.jnc.JNCException;
+import java.io.IOException;
+import java.nio.file.Files;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.util.ResourceUtils;
+
+class NetconfConfigurationServiceTest {
+
+ @Mock
+ NetconfConfigurationReader reader;
+
+ @Mock
+ NetconfConfigurationEditor editor;
+
+ @InjectMocks
+ NetconfConfigurationService service;
+
+ private static String CURRENT_CONFIG_XML_STRING =
+ "<config xmlns=\"http://onap.org/pnf-simulator\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + " <itemValue1>100</itemValue1>\n"
+ + " <itemValue2>200</itemValue2>\n"
+ + "</config>\n";
+
+ @BeforeEach
+ void setUp() {
+ initMocks(this);
+ }
+
+ @Test
+ void testShouldReturnCorrectCurrentConfiguration() throws IOException, JNCException {
+ String expectedConfiguration = CURRENT_CONFIG_XML_STRING;
+ when(reader.getRunningConfig()).thenReturn(CURRENT_CONFIG_XML_STRING);
+
+ String actualCurrentConfiguration = service.getCurrentConfiguration();
+
+ assertThat(actualCurrentConfiguration).isEqualToIgnoringCase(expectedConfiguration);
+ }
+
+ @Test
+ void testShouldThrowExceptionWhenCurrentConfigurationDoesNotExists() throws IOException, JNCException{
+ when(reader.getRunningConfig()).thenThrow(JNCException.class);
+
+ assertThatThrownBy(() -> service.getCurrentConfiguration()).isInstanceOf(JNCException.class);
+ }
+
+ @Test
+ void testShouldEditConfigurationSuccessfully() throws IOException, JNCException{
+ byte[] bytes =
+ Files.readAllBytes(ResourceUtils.getFile("classpath:updatedConfig.xml").toPath());
+ MockMultipartFile editConfigXmlContent = new MockMultipartFile("editConfigXml", bytes);
+ ArgumentCaptor<Element> elementCaptor = ArgumentCaptor.forClass(Element.class);
+ doNothing().when(editor).editConfig(elementCaptor.capture());
+
+ service.editCurrentConfiguration(editConfigXmlContent);
+
+ assertThat(elementCaptor.getValue().toXMLString()).isEqualTo(CURRENT_CONFIG_XML_STRING);
+ }
+
+ @Test
+ void testShouldRaiseExceptionWhenMultipartFileIsInvalidXmlFile() throws IOException {
+ byte[] bytes =
+ Files.readAllBytes(ResourceUtils.getFile("classpath:invalidXmlFile.xml").toPath());
+ MockMultipartFile editConfigXmlContent = new MockMultipartFile("editConfigXml", bytes);
+
+ assertThatThrownBy(() -> service.editCurrentConfiguration(editConfigXmlContent)).isInstanceOf(JNCException.class);
+ }
+
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/model/NetconfModelLoaderServiceTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/model/NetconfModelLoaderServiceTest.java
new file mode 100644
index 0000000..a10876b
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/netconfcore/model/NetconfModelLoaderServiceTest.java
@@ -0,0 +1,121 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.netconfcore.model;
+
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.web.multipart.MultipartFile;
+
+class NetconfModelLoaderServiceTest {
+
+ @Mock
+ private HttpClient httpClient;
+
+ private NetconfModelLoaderService modelLoaderService;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.initMocks(this);
+ modelLoaderService = new NetconfModelLoaderService(httpClient);
+ }
+
+
+ @Test
+ void shouldSendMultipartToServer() throws IOException {
+ //given
+ String loadModelAddress = modelLoaderService.getBackendAddress();
+ makeMockClientReturnStatusOk(httpClient, HttpPost.class);
+ ArgumentCaptor<HttpPost> postArgumentCaptor = ArgumentCaptor.forClass(HttpPost.class);
+ MultipartFile yangMmodel = mock(MultipartFile.class);
+ MultipartFile initialConfig = mock(MultipartFile.class);
+ String moduleName = "moduleName";
+ when(yangMmodel.getInputStream()).thenReturn(getEmptyImputStream());
+ when(initialConfig.getInputStream()).thenReturn(getEmptyImputStream());
+
+ //when
+ LoadModelResponse response = modelLoaderService.loadYangModel(yangMmodel, initialConfig, moduleName);
+
+ //then
+ verify(httpClient).execute(postArgumentCaptor.capture());
+ HttpPost sentPost = postArgumentCaptor.getValue();
+ assertThat(response.getStatusCode()).isEqualTo(200);
+ assertThat(response.getMessage()).isEqualTo("");
+ assertThat(sentPost.getURI().toString()).isEqualTo(loadModelAddress);
+ assertThat(sentPost.getEntity().getContentType().getElements()[0].getName()).isEqualTo("multipart/form-data");
+ }
+
+ @Test
+ void shouldSendDeleteRequestToServer() throws IOException {
+ //given
+ String yangModelName = "sampleModel";
+ String deleteModelAddress = modelLoaderService.getDeleteAddress(yangModelName);
+ makeMockClientReturnStatusOk(httpClient, HttpDelete.class);
+ ArgumentCaptor<HttpDelete> deleteArgumentCaptor = ArgumentCaptor.forClass(HttpDelete.class);
+
+ //when
+ LoadModelResponse response = modelLoaderService.deleteYangModel(yangModelName);
+
+ //then
+ verify(httpClient).execute(deleteArgumentCaptor.capture());
+ HttpDelete sendDelete = deleteArgumentCaptor.getValue();
+ assertThat(response.getStatusCode()).isEqualTo(200);
+ assertThat(response.getMessage()).isEqualTo("");
+ assertThat(sendDelete.getURI().toString()).isEqualTo(deleteModelAddress);
+ }
+
+ private void makeMockClientReturnStatusOk(HttpClient client,
+ Class<? extends HttpRequestBase> httpMethodClass) throws IOException {
+ HttpResponse httpResponse = mock(HttpResponse.class);
+ StatusLine mockStatus = mock(StatusLine.class);
+ HttpEntity mockEntity = mock(HttpEntity.class);
+
+ when(client.execute(any(httpMethodClass))).thenReturn(httpResponse);
+ when(httpResponse.getStatusLine()).thenReturn(mockStatus);
+ when(mockStatus.getStatusCode()).thenReturn(200);
+ when(httpResponse.getEntity()).thenReturn(mockEntity);
+ when(mockEntity.getContent()).thenReturn(getEmptyImputStream());
+ }
+
+ private InputStream getEmptyImputStream() {
+ return new ByteArrayInputStream("".getBytes());
+ }
+
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/websocket/NetconfEndpointTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/websocket/NetconfEndpointTest.java
new file mode 100644
index 0000000..c1484d4
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/websocket/NetconfEndpointTest.java
@@ -0,0 +1,135 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.websocket;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.util.Map;
+import java.util.Optional;
+import javax.websocket.CloseReason;
+import javax.websocket.EndpointConfig;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import org.apache.kafka.common.Metric;
+import org.apache.kafka.common.MetricName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.onap.netconfsimulator.kafka.listener.KafkaListenerEntry;
+import org.onap.netconfsimulator.kafka.listener.KafkaListenerHandler;
+import org.onap.netconfsimulator.websocket.message.NetconfMessageListener;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.listener.AbstractMessageListenerContainer;
+
+import org.springframework.kafka.listener.ContainerProperties;
+import org.springframework.kafka.listener.GenericMessageListener;
+
+class NetconfEndpointTest {
+
+
+ @Mock
+ private KafkaListenerHandler kafkaListenerHandler;
+
+ @Mock
+ private Session session;
+
+ @Mock
+ private EndpointConfig endpointConfig;
+
+ @Mock
+ private RemoteEndpoint.Basic remoteEndpoint;
+
+
+ @BeforeEach
+ void setUp() {
+ initMocks(this);
+ }
+
+
+ @Test
+ void shouldCreateKafkaListenerWhenClientInitializeConnection() {
+ NetconfEndpoint netconfEndpoint = new NetconfEndpoint(kafkaListenerHandler);
+ AbstractMessageListenerContainer abstractMessageListenerContainer = getListenerContainer();
+ when(session.getBasicRemote()).thenReturn(remoteEndpoint);
+ KafkaListenerEntry kafkaListenerEntry = new KafkaListenerEntry("sampleGroupId",
+ abstractMessageListenerContainer);
+ when(kafkaListenerHandler.createKafkaListener(any(NetconfMessageListener.class), eq("config")))
+ .thenReturn(kafkaListenerEntry);
+
+ netconfEndpoint.onOpen(session, endpointConfig);
+
+ assertThat(netconfEndpoint.getEntry().get().getClientId()).isEqualTo("sampleGroupId");
+ assertThat(netconfEndpoint.getEntry().get().getListenerContainer()).isEqualTo(abstractMessageListenerContainer);
+
+ verify(abstractMessageListenerContainer).start();
+ }
+
+
+ @Test
+ void shouldCloseListenerWhenClientDisconnects() {
+ NetconfEndpoint netconfEndpoint = new NetconfEndpoint(kafkaListenerHandler);
+ AbstractMessageListenerContainer abstractMessageListenerContainer = getListenerContainer();
+ netconfEndpoint.setEntry( Optional.of(new KafkaListenerEntry("sampleGroupId", abstractMessageListenerContainer)) );
+
+ netconfEndpoint.onClose(session, mock(CloseReason.class));
+
+ verify(abstractMessageListenerContainer).stop();
+ }
+
+ class TestAbstractMessageListenerContainer extends AbstractMessageListenerContainer {
+
+
+ TestAbstractMessageListenerContainer(ContainerProperties containerProperties) {
+ super(mock(ConsumerFactory.class),containerProperties);
+ }
+
+ @Override
+ protected void doStart() {
+
+ }
+
+ @Override
+ protected void doStop(Runnable callback) {
+
+ }
+
+ @Override
+ public Map<String, Map<MetricName, ? extends Metric>> metrics() {
+ return null;
+ }
+ }
+
+ private AbstractMessageListenerContainer getListenerContainer() {
+ ContainerProperties containerProperties = new ContainerProperties("config");
+ containerProperties.setGroupId("sample");
+ containerProperties.setMessageListener(mock(GenericMessageListener.class));
+ TestAbstractMessageListenerContainer testAbstractMessageListenerContainer = new TestAbstractMessageListenerContainer(
+ containerProperties);
+ return spy(testAbstractMessageListenerContainer);
+ }
+}
diff --git a/netconfsimulator/src/test/java/org/onap/netconfsimulator/websocket/message/NetconfMessageListenerTest.java b/netconfsimulator/src/test/java/org/onap/netconfsimulator/websocket/message/NetconfMessageListenerTest.java
new file mode 100644
index 0000000..bb040d1
--- /dev/null
+++ b/netconfsimulator/src/test/java/org/onap/netconfsimulator/websocket/message/NetconfMessageListenerTest.java
@@ -0,0 +1,73 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Simulator
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.netconfsimulator.websocket.message;
+
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.io.IOException;
+import javax.websocket.EncodeException;
+import javax.websocket.RemoteEndpoint;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.onap.netconfsimulator.kafka.model.KafkaMessage;
+
+
+class NetconfMessageListenerTest {
+
+ private static final ConsumerRecord<String, String> KAFKA_RECORD = new ConsumerRecord<>("sampleTopic", 0, 0,
+ "sampleKey", "sampleValue");
+
+ @Mock
+ private RemoteEndpoint.Basic remoteEndpoint;
+
+ @InjectMocks
+ private NetconfMessageListener netconfMessageListener;
+
+
+ @BeforeEach
+ void setUp() {
+ initMocks(this);
+ }
+
+
+ @Test
+ void shouldProperlyParseAndSendConsumerRecord() throws IOException, EncodeException {
+ netconfMessageListener.onMessage(KAFKA_RECORD);
+
+ verify(remoteEndpoint).sendObject(any(KafkaMessage.class));
+ }
+
+
+
+ @Test
+ void shouldNotPropagateEncodeException() throws IOException, EncodeException {
+ doThrow(new EncodeException("","")).when(remoteEndpoint).sendObject(any(KafkaMessage.class));
+
+ netconfMessageListener.onMessage(KAFKA_RECORD);
+ }
+}
diff --git a/netconfsimulator/src/test/resources/application-it.properties b/netconfsimulator/src/test/resources/application-it.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/netconfsimulator/src/test/resources/application-it.properties
diff --git a/netconfsimulator/src/test/resources/initialConfig.xml b/netconfsimulator/src/test/resources/initialConfig.xml
new file mode 100644
index 0000000..f28a1a0
--- /dev/null
+++ b/netconfsimulator/src/test/resources/initialConfig.xml
@@ -0,0 +1,23 @@
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<config2 xmlns="http://onap.org/newyangmodel">
+ <item1>100</item1>
+</config2>
diff --git a/netconfsimulator/src/test/resources/invalidXmlFile.xml b/netconfsimulator/src/test/resources/invalidXmlFile.xml
new file mode 100644
index 0000000..3debd8c
--- /dev/null
+++ b/netconfsimulator/src/test/resources/invalidXmlFile.xml
@@ -0,0 +1,23 @@
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<config xmlns="http://onap.org/pnf-simulator" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <itemValue1>100</itemValue1>
+<config>
diff --git a/netconfsimulator/src/test/resources/newYangModel.yang b/netconfsimulator/src/test/resources/newYangModel.yang
new file mode 100644
index 0000000..bbe66c3
--- /dev/null
+++ b/netconfsimulator/src/test/resources/newYangModel.yang
@@ -0,0 +1,8 @@
+module newyangmodel {
+ namespace "http://onap.org/newyangmodel";
+ prefix config2;
+ container config2 {
+ config true;
+ leaf item1 {type uint32;}
+ }
+}
diff --git a/netconfsimulator/src/test/resources/updatedConfig.xml b/netconfsimulator/src/test/resources/updatedConfig.xml
new file mode 100644
index 0000000..628a710
--- /dev/null
+++ b/netconfsimulator/src/test/resources/updatedConfig.xml
@@ -0,0 +1,24 @@
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<config xmlns="http://onap.org/pnf-simulator" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <itemValue1>100</itemValue1>
+ <itemValue2>200</itemValue2>
+</config>
diff --git a/netconfsimulator/src/test/resources/updatedConfigForCmHistory.xml b/netconfsimulator/src/test/resources/updatedConfigForCmHistory.xml
new file mode 100644
index 0000000..5bc0e42
--- /dev/null
+++ b/netconfsimulator/src/test/resources/updatedConfigForCmHistory.xml
@@ -0,0 +1,24 @@
+<!--
+ ============LICENSE_START=======================================================
+ Simulator
+ ================================================================================
+ Copyright (C) 2019 Nokia. All rights reserved.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<config xmlns="http://onap.org/pnf-simulator" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <itemValue1>500</itemValue1>
+ <itemValue2>1000</itemValue2>
+</config>
diff --git a/netconfsimulator/ssh/ssh_host_rsa_key b/netconfsimulator/ssh/ssh_host_rsa_key
new file mode 100644
index 0000000..dbf8d76
--- /dev/null
+++ b/netconfsimulator/ssh/ssh_host_rsa_key
@@ -0,0 +1,49 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+-----END OPENSSH PRIVATE KEY-----
diff --git a/netconfsimulator/ssh/ssh_host_rsa_key.pub b/netconfsimulator/ssh/ssh_host_rsa_key.pub
new file mode 100644
index 0000000..8f73911
--- /dev/null
+++ b/netconfsimulator/ssh/ssh_host_rsa_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDQS97W/nxuQtzj/oW9wFhY7yrqs5MmiTtjI4edzpAEkW4KFJNsBpWef4Tyc+DKo3T+EfvjT6qO1alol0qHT6EkzDS9R5fpJDU6lGDJcVsNJsWuOwGMHs3Z66ytEzkXB4PFVb+Z9HsAd6asu6Vs1ge38eZj4d57T0P9rAgKm5WIsos6w+nyJ+ZscbWZdK0s1KA4mAG+ysy9uwpiAmrQd1Y/J+TMV/hJV2MrF56BBuRKiBYapEbvOR+7xuxiihSkgVEUyIm9Z2v22V6ARIC7V+ZTHZbPsS03hCrvGod8BtP5G7Oviu0JFEKx0Si7j2SlNIzvkVoqVeZyFFW0FAqd0WFw9AI76b06RDj7jlVJ4svbfoxO8AUaep8uSz3tM7tUsaKeG0y8kDWBkj3dvBUFI/W5pW6JR3NPw6BSrrGwvHaqDZqhq7ki+Prkd2Py2UiN4Ud4aBR3UZlrjfhfenxYIMVWtAoalF5oNMW21UXDsXb5uIWafrxM7RtvgYTzivyi5phVIbNYbWQTLxS2uJefHoY87OdynwcpNgWfZ63OzkQT0B7rj7WYglNQSdaWnHCuMKoIit6JszPj/sCRJavBzRh4BQRGyj4RJCQJTxHOlxhJHPnRRMJp6tLwrlw8T03ZcHdZhuzOI1OlHnryidAiQv5mVMhs01gGB1U0MuVB5pspKQ== marcin@mandingo
diff --git a/netconfsimulator/tls/ca.crt b/netconfsimulator/tls/ca.crt
new file mode 100644
index 0000000..a72e926
--- /dev/null
+++ b/netconfsimulator/tls/ca.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFlTCCA32gAwIBAgIUYjGBXIujymdkS9A2MzssbetWn9gwDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCUEwxCzAJBgNVBAgMAkRTMRAwDgYDVQQHDAdXcm9jbGF3
+MQ4wDAYDVQQKDAVOb2tpYTENMAsGA1UECwwETWFubzENMAsGA1UEAwwET25hcDAe
+Fw0xOTExMDYxNTExNDBaFw0yOTExMDMxNTExNDBaMFoxCzAJBgNVBAYTAlBMMQsw
+CQYDVQQIDAJEUzEQMA4GA1UEBwwHV3JvY2xhdzEOMAwGA1UECgwFTm9raWExDTAL
+BgNVBAsMBE1hbm8xDTALBgNVBAMMBE9uYXAwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQC7zJGm+qODeBm0citmi+VKOwhVUg1QTyofXsqoib+yeiepYz+c
+MRMXMK20TA2m4ObAG5JoU0Ht1bT08f4K8BAoutq0rwb28YodSKiq8PCtD4Pn81Up
+FdP+Ymsl4YqXhydLEsqv/tpZgVvsCr/a6esxPfGSrScqw2PUf8qrHEXV48lX2Ho9
+19nZWjQTgqDzmOAShyMQabdrfuKII6/A6w/Q8eghzMQrJFsnfOS35RmH6MJyi1vl
+Hn37uEpZWjh0untRA4mtAHB6f3exw36gfpGyQgfMUiCBCbsr8SgJbHCaDpozD9ZE
+Ny+R+jRsqt0RlBoZsSPToNw1o6YjzBZZi1qYyDuNz0bQR/hedQxkwwKcj0Y7tfxF
+IfwUkb1XQ0e4mDia6R6tptphSfrSKLouVFGTVdVtoP5lpGhsw3+KWuTbwB64cZuS
+YuWW1BJ7gM6xj8b9cWRtOakHJ7WKt98Z3U7wDrQqZpnWk893kWoo0Q22X4uTipHX
++ETSdtTq5mVvYQQNub2CcA/4GQyODfdlC0gDkNeT9t6yfMJcOAJUBvW1JMoDAkED
+nWnWm/kS7aflFH6IHfqHbt1ZXUGJG94wTpvGJAHTnaiyX0mL4KK/r+ivdii+Tais
+VFKOqW7EqNZziOvZ0TMfkHtrBY89DgG8+XNwa0KUQIqVIQoJwOfQl4pAjQIDAQAB
+o1MwUTAdBgNVHQ4EFgQUQ9QHKoM/JUSm7ZnO+JvsHMVo6M0wHwYDVR0jBBgwFoAU
+Q9QHKoM/JUSm7ZnO+JvsHMVo6M0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
+AQsFAAOCAgEAK6bcNkkAVY11yUg1Vm3e9QGRWPeWNbKVIHR/uJIvq4R8AthvFcEG
+6GxApE9Zdd0oWpUG9cM/EZ3ZB5drDnpqUYURkQ8YNrIPJXByvqXFeQPej+m7DHOE
+vPtbm780cE+Z6abfctRFdujJX8hQ5s3UEKjpk0HsbZqEX4JbNeLT+LKgH+kwU/SA
+puVFZXN7dTACdX2gg2F1Fg52L54ZTWMlFMNn32b1a8zrKY0HaoLQd+yXS4RfrMpd
+jPsaytuOimU+OFRFb8cvSpuC+gkCEwQFVKb/B+0Rp1No3edEE4N92hpkiDh5/+ML
+/B8kSh0InaGxu6uTC3QzqTRgzbaRpOoWkdVRkDSpU6xNtyQN5jXDExpXtPe4LMP6
+Vk1jGpJ3E6kjSzzSvZFsH9Fko++aF6m5qqAHgG/Yc0j+7bmWCoMZWVqoxE9fHjfc
+mFs8N7R2HYA7G3qKs1fZy5SzAhuMkggys6z3c6ZW2BtD82GClSlXIlPwBu6KpCNy
+6cDeQ245wH1pO7pKfaIu3hbY6m5Z9jkbyOeW4I2Pj2s13345qjF9pRggkJ0MQtgi
+M6vU/13o/WhF0lEVz37pbriJ106412tF60uyyzZvJkAqhBCJyJ0jxqtwKr8GhL9g
+PnTRJkhFAE/5KvAi0ETbOIScRz/B+v+nm08ZGe1nHQ/Vu+NIY5gLp18=
+-----END CERTIFICATE-----
diff --git a/netconfsimulator/tls/ca.key b/netconfsimulator/tls/ca.key
new file mode 100644
index 0000000..1e87b90
--- /dev/null
+++ b/netconfsimulator/tls/ca.key
@@ -0,0 +1,54 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+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
+ETq+BbQtrvmZb6x5IrRU3Kt5QmkS/K/1
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/netconfsimulator/tls/client.crt b/netconfsimulator/tls/client.crt
new file mode 100644
index 0000000..3c0d20c
--- /dev/null
+++ b/netconfsimulator/tls/client.crt
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFKDCCAxACAWUwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCUEwxCzAJBgNV
+BAgMAkRTMRAwDgYDVQQHDAdXcm9jbGF3MQ4wDAYDVQQKDAVOb2tpYTENMAsGA1UE
+CwwETWFubzENMAsGA1UEAwwET25hcDAeFw0xOTExMDYxNTEyMjlaFw0yMDExMDUx
+NTEyMjlaMFoxCzAJBgNVBAYTAlBMMQswCQYDVQQIDAJETjEQMA4GA1UEBwwHV3Jv
+Y2xhdzEOMAwGA1UECgwFTm9raWExDTALBgNVBAsMBE1hbm8xDTALBgNVBAMMBE9u
+YXAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1kEo6cAykAqNE4R53
+8VJn5Rzbs5HDDk9+0HCxgEp/7suRMWLOgeG5/L+nRgrLLcFPscwnIwIHUUE1pzyj
+aDux6cXjpto+1APq/sd1ofVMMl3I/zqcrqojixjFJO1INgN/i1CaAeBRO+mNmIUN
+IJKy2KbTYvTYYJrJbzIRh1NBsUvuIzvj5Wkq3cri4oJmz82lsNDAh4BGedWQT4RU
+mjb/EC7D9WpfOlMhsgLVJgPdG5ESFqN+xx/n4xJUcM/HGqVlfZED5XDcfQjp56Ba
+FBpNhLW41qVy8UoeixYiduLaVvKDoJc2Ea3N/pyE3YU1LBOy7znpULX3UdMb+bX5
+jGn4uauKz77hLZA5nurgb8v1ofbj1GCM28JfaiPRtIuWVtWKI+8RyKjaXjKd/rcG
+W/5T9aM9um/lH2v/DteaTa4i8zebL7k/DJFTXP+DCtJTi8e23Xalkp+O+MWQQrGR
+O07NHdl1gjDmEoc3NMRO51Ux5CDyqCbhF4QdX0ND+vDJmD8GBuxb6A3HjdmT5/iB
+iH5/HO6FRHFhMtR9xABryT01gYkDWJHszTStNLx/ZzW4Dz3i2WyoEvDa2meLkSh5
+edE1skowZriSeoOuGWJlfpjjvn1p4kjk0msg3T8EgJqZu9AiKzuVgILa8u6Kny8/
+p12MN+yltB02hiJZyMlyJDCn3QIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQAf227R
+PWMNcdVKbDz8pKXOYUN5RtvvisabF25CrxVMNzH2+3nQIuICOEg7dFI1qKEEh+DJ
+51dAgwCC7d+levHBHximFaW7ED1rRFZJUoHlF9Ku74KA3eWPcWBJdzIAk2KzcQhu
+GXVQs2RUv5nJJogAPTtt9hFU9e3vqfpb+DNrzO0BTCfTsO3QSSojrtOpLOhH4EBV
+z6TyKDYJH7XCIvhApJjbuWsvVi3WTYFpFtF3dBNTMXjMZaHqMQBmMO4VzoQeu422
+fAcCVb+UUIrdU/Jdm+6tjuUqfQj3lVIZFPhTXrZ0MfBkowHoioos34u+JJ3Hjd+5
+mK27OhNA4SKaYQfi2DQ8jV1Jf/yA+lcmZddAiLZyOgHXPNZpP6glG802/k67i61J
+PIxaoaVmgxccZINzqov4Sn7A6hgt3S68izpsrIu1R9JqKjjgNA5HqtkMjyYqaMoX
+nQ0jIxkd5lkHL8MzAecaR37Y4hibpgsDl2SsuLTtfcO3NQO0bGFMRqYaIHN6DYd0
+4S1vOrsCWc3njvUGW8UcvB8GC4puwTi9en9SPaVcgSEBZ8gsJ4E1XWAdyTQZJWon
+aMn8iAeapfJu7N3UA5nM9L0f7dvODkLDqXOJuLl1IhhNFu6E864Ld+BVH8EQjIrr
+xCvjvlMQ4pnp7kjQdEekswN9ON6e3pxT/W3t7g==
+-----END CERTIFICATE-----
diff --git a/netconfsimulator/tls/client.key b/netconfsimulator/tls/client.key
new file mode 100644
index 0000000..cb80fca
--- /dev/null
+++ b/netconfsimulator/tls/client.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAtZBKOnAMpAKjROEed/FSZ+Uc27ORww5PftBwsYBKf+7LkTFi
+zoHhufy/p0YKyy3BT7HMJyMCB1FBNac8o2g7senF46baPtQD6v7HdaH1TDJdyP86
+nK6qI4sYxSTtSDYDf4tQmgHgUTvpjZiFDSCSstim02L02GCayW8yEYdTQbFL7iM7
+4+VpKt3K4uKCZs/NpbDQwIeARnnVkE+EVJo2/xAuw/VqXzpTIbIC1SYD3RuREhaj
+fscf5+MSVHDPxxqlZX2RA+Vw3H0I6eegWhQaTYS1uNalcvFKHosWInbi2lbyg6CX
+NhGtzf6chN2FNSwTsu856VC191HTG/m1+Yxp+Lmris++4S2QOZ7q4G/L9aH249Rg
+jNvCX2oj0bSLllbViiPvEcio2l4ynf63Blv+U/WjPbpv5R9r/w7Xmk2uIvM3my+5
+PwyRU1z/gwrSU4vHtt12pZKfjvjFkEKxkTtOzR3ZdYIw5hKHNzTETudVMeQg8qgm
+4ReEHV9DQ/rwyZg/BgbsW+gNx43Zk+f4gYh+fxzuhURxYTLUfcQAa8k9NYGJA1iR
+7M00rTS8f2c1uA894tlsqBLw2tpni5EoeXnRNbJKMGa4knqDrhliZX6Y4759aeJI
+5NJrIN0/BICambvQIis7lYCC2vLuip8vP6ddjDfspbQdNoYiWcjJciQwp90CAwEA
+AQKCAgBvwrq+T/yn9Gu12XDc2fIku9ZQsq81xxhN8lZ8HIE/UKmD3Ud54F8nEhqT
+B71bJfYJbWNCb/0ureia8y+TUnT0ZfcG080hwXYL7/qtQR1yYspyEBFvIuDd8iA0
+Mi/RNFZg60uZETCYMG3qWAkGdJd4eX3vPsmCa6y787N5MLiiBlW753pVJQDgfxgF
+IvKsAJwQss88w+vE9ffs1dWaEjdqN/bhbq6koY1Mf3vY4oacUvywirjnMAFGxvKg
+gbSUVaWb3HgoUtJvFpQnDwoYiKlMMDV/6rh7EWXTeUANxOqNBdpQBckuYiF/XIvS
+CV2WxmP1z+Xw5VqtO3Pck5BdZP5RLj/43CAtXt/1JV7QFMXohBOvFbcqBaWjfvl7
+6/CvtTnTjNlp4Z2mciFrClqPTgeLT67dCCiyUwXiqJGWJ50tuao5vpPX3JEbc0By
+FGDsdcnXKat99EhC72celO1NprqfvFGJubHSmF1Kce+faqXEQLWHkda1dJtW2+BY
+AWgtOS9Gaxb/hTZPrD/mkhnUYebLBDmnp9azacKzddlovJvnbRNrpJZ+D+wXkGQR
+XmtfpFAkGyBlT4RyYOmfbEXvas8aSZmOltsPwiFwk0rPoH2owuYCj0XXLTFODuOy
+Rs0zns8sdQsvdpKuykvdh7m4hDDdR8FgEDbzK39DndwGUk7+DQKCAQEA8P82Re74
+Ri02TYK6SaSpxauaJMajgfg6FtXMhZ+Ed0eUmtDI5OMWkoVgaRZGjTLzxGmQxd2I
+h6YV3KsQng0mMySIqnVAPLm2HK3Ahopd1R26XalBBXpE7OwGlLmv7/9hQHNscMyO
+i89WcQWQWuvMUxHc55byeI3cfchgwrR5uqZcBcTmaCu8SQXfyVZnM230532Gz4+P
+DvLicAEI79SCKGDtjgnjWzqGbMgUtvtYDJzOT9py9DjW/h3I7aMIswp0y5PVvzOq
+HIFaDRW2WyfZD6FTqhhtYodQ5Bpu1APdhFo+ELYg7YuqwF8N/+oEU+BtkHlOGgnN
+RXMaBurO9W0MRwKCAQEAwN3iefqYcz+TywUbDXyvWGZNIrU7d8hS2NXZPDgj7ysc
+RecaPAcnEOEv4PgQjj6w+WL4XrIqDk6Nigzq/8nFL8M+odZke7o+zYxOFU9vsj7P
+iqfvcG3Yzkmuw8Rl/9yw25t3tq4ftN3Lw70CeSnWOo8wGrkYmnoHi1Boq1E+PAHr
+co2A6HvJchNxZhrEYPZJk1zg0w2TAhnwGxngZX2dz3bVTDWCu1dU5Me5Xj2ZLrcv
+ypTqF8wGPOkqzrCtV+diLybaYc4pLtuYRKew85KXRd/kNKVRLwtfA9MaLGYIuqaP
++X/fxUTDvWX3d/za7rUC7mgA0wLbzLkg/3i72nnQuwKCAQBwzrXD9IFIsB8sFxZG
+JIrHmXTU5BpjWyMtzzQmFC44e0wnT7XJ1jaZrU8cwa2SqJgMh3GZ/QHMpUS0f2qO
+4Q7Rf7dtOucvxapmMDrff2M+v+SdtP1XqA0dbboC4gxl5f/K4HyRLF9JOGt5Ciax
+OJDzDXh++1xSa+0a/7GzUjFs2jRXCvzjJB3FfLdtEkHKhBzQQyTYgZVwZObzkLhz
+MuEeL4jjJOPEr1d/EgUIRgzIEppMLLAEe5q/ZofIHLmUvT9Aj9nM300wfKf/Wft7
+HeLv+w8jnr2NElQeAfWAzrJhIrDMt8QgDXGHDLeL5CnUbUg8CVwyugXZDXDQbhck
+MMA/AoIBAE/ShY1efgIU6iI4dr3aQOghTwNyZq5opoE/4Kzfv1+ZRBZaU61MeIqv
+6JT/ljqJD83nfEwVCPrq4AGm3wwGgioGcMGibewXZS7FRvvi6KOpA1SrtJmfnP+j
+10UG4ABJYplBewaMgmrk8RSOAZwMCLIr2Qj2Wox0vPKX0Po2ob9MPBopHpUZxoIE
+MgbERUqsJD9tTU+Maq+P9KQCzbCzkMDwuc7UxsqqlmQplTq/28pN3uXFRC2dL2e+
+SQ1KajPbe/Nv7SpVnQ3brOWdUcs9fIZa56QyV6tU1XpLwKVnhW1aynPEzORkVW7p
+6MYsTKBHInNxcAXlRPsyE8ooOfduO0kCggEBAI6yl6Cqv/Yc8wEkews2aIS0oUav
+HF9QGoSWP/yJcX7w4C09L1jemdqJxIzZRJ5dN298PIKdEDuQT59FwC0qgIzNgG9q
+7VI6XZXh3+W9jC7wmp89eUbrj7yPpkREJg3bP9C6d4cFxJQCUACukDfRz896WIUf
+VKJLsXeebs0Ond6+hDX+HKPoCKdpbvIDUZd5FUJkNFh5EoSwkuo5mCAyTTWYkKSl
+JuiUdViT+RkvX3XzxVVBgZsS70mu/Iur/Qbz74rbMeEoReAfOYtXC22rzMD3rQsZ
+/uzCpGWsh32ywI/C+ESWSLAo+HZGL0gbrILQbnBknX2Yq8iUvnkB7W8/igs=
+-----END RSA PRIVATE KEY----- \ No newline at end of file
diff --git a/netconfsimulator/tls/client.req b/netconfsimulator/tls/client.req
new file mode 100644
index 0000000..ace54c0
--- /dev/null
+++ b/netconfsimulator/tls/client.req
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIE0zCCArsCAQAwWjELMAkGA1UEBhMCUEwxCzAJBgNVBAgMAkROMRAwDgYDVQQH
+DAdXcm9jbGF3MQ4wDAYDVQQKDAVOb2tpYTENMAsGA1UECwwETWFubzENMAsGA1UE
+AwwET25hcDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALWQSjpwDKQC
+o0ThHnfxUmflHNuzkcMOT37QcLGASn/uy5ExYs6B4bn8v6dGCsstwU+xzCcjAgdR
+QTWnPKNoO7HpxeOm2j7UA+r+x3Wh9UwyXcj/OpyuqiOLGMUk7Ug2A3+LUJoB4FE7
+6Y2YhQ0gkrLYptNi9NhgmslvMhGHU0GxS+4jO+PlaSrdyuLigmbPzaWw0MCHgEZ5
+1ZBPhFSaNv8QLsP1al86UyGyAtUmA90bkRIWo37HH+fjElRwz8capWV9kQPlcNx9
+COnnoFoUGk2EtbjWpXLxSh6LFiJ24tpW8oOglzYRrc3+nITdhTUsE7LvOelQtfdR
+0xv5tfmMafi5q4rPvuEtkDme6uBvy/Wh9uPUYIzbwl9qI9G0i5ZW1Yoj7xHIqNpe
+Mp3+twZb/lP1oz26b+Ufa/8O15pNriLzN5svuT8MkVNc/4MK0lOLx7bddqWSn474
+xZBCsZE7Ts0d2XWCMOYShzc0xE7nVTHkIPKoJuEXhB1fQ0P68MmYPwYG7FvoDceN
+2ZPn+IGIfn8c7oVEcWEy1H3EAGvJPTWBiQNYkezNNK00vH9nNbgPPeLZbKgS8Nra
+Z4uRKHl50TWySjBmuJJ6g64ZYmV+mOO+fWniSOTSayDdPwSAmpm70CIrO5WAgtry
+7oqfLz+nXYw37KW0HTaGIlnIyXIkMKfdAgMBAAGgNDAYBgkqhkiG9w0BCQIxCwwJ
+Y29sbGVjdG9yMBgGCSqGSIb3DQEJBzELDAljb2xsZWN0b3IwDQYJKoZIhvcNAQEL
+BQADggIBAG4zPJVwotinJdDfBY8sGj6ARcWNtRuCmLj4Xri6pzzvUCaSLFAvIkd1
+Tdm7uIrfUMU+/stvu2E1myWJGnITL8njUahMqrTwI0yZF3Do8bw12kr9lf3wDNLT
+CvPApGpUEegcGQS/pp6ksa7zd4iDkWPLqf4CqnooDKprPQXz2HL2clBCmSrD2u61
+zTVey/G6FCU1tfAXisykmF2pElC8AJpY9EkY/ShSwk31n3ARe0XZAqo9NfcwvgAx
+isqevdXQ+D/FROjVwSKb8CB1rANn36ziZRMz02cyVj3FLbOgSBsrAfunbLNDyQ9z
+C3FYNA/tTtaPlwL8aOz0T5N6iyjmpqcvlFFFB+H5zg1tP0rcYwGnto3d85GgBYBF
+BK+ikAzYgZlpB48VceMQydZk3aMOcUIVSWpzsVhr9GkUYmYE6DxSZlzXGs75FkFf
+aP7/Qqf8Ut8G8fZV2QziOcuMbCnZ2fZl/lLLHO6A4e86bDyUjqPQK/LPylvJGgmY
+CivXpuBVA7j5SZZ4YnwqtWe4xh5vgbxZU7sq9omHnJaLfINHfQepeHcL4mWRo1oP
+xnacKz0ju2rlPNRN2jFciFQnZTUvmWO5KvoipadyjWqd7QzM0J4rdW56+a4tR1LA
+0JiV+yzsq9NB6/wKGGtwjYOEhr5AJOEb3qJPNJ45KI3nag2l3HWj
+-----END CERTIFICATE REQUEST----- \ No newline at end of file
diff --git a/netconfsimulator/tls/server.req b/netconfsimulator/tls/server.req
new file mode 100644
index 0000000..7e3e70a
--- /dev/null
+++ b/netconfsimulator/tls/server.req
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIEuTCCAqECAQAwWjELMAkGA1UEBhMCUEwxCzAJBgNVBAgMAkROMRAwDgYDVQQH
+DAdXcm9jbGF3MQ4wDAYDVQQKDAVOb2tpYTENMAsGA1UECwwETWFubzENMAsGA1UE
+AwwET25hcDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALFl14dsyahU
+bDm8WxD2VPGHOy8CccW77WFyxbUUM0JxfawM8cJC8AZlb2RRVNmwsq5mtQa5XWnp
+rZBx1BaMmN+ek201QSec5m3Nwesz6m7w8xIn+JJqrerdQ6vDqKTsCIINhekHgNAb
+3QdRnIE2dSk8ORZgVTPxNMyLG832U/8lGic2yi+P/jB3j4Ianx33k18GUb851H60
+DSDYkT1QDP97DSw/2k2YThljBcUKaKKuajJpA+IwL2s7vDbaJj1neIuE+tDSKZAS
+TPzUJSS9d0yFk090wI42idZ9szByP+v08hBQDc4Uo0CWUrSxebKJ8Z7k347U3uG6
+MEjY6FqjL7y4oZo670fjiKiOnJVRxFDdj/5DR37hFGjHg1x6BSRt4aJ4SSdYuNu5
+X2LGijqJ6FBeKc+TZfj4g1eH2bLvEI03Z0V1Q2p8qq59jVJAb5U2qwPRU30JRrFY
+R84kPj8xV1xi3tZke0T0nszCLnDa5oCImm1Q31Ncbe1ZruSNNmGhpjGqe+nGaLOg
+jSQaoYfe982i9hhsEIgLOtOLSOYSRsIwd6tWoH5WrsrkotTawby9dMDA7ghkQ8lL
+xkuLkcm/VIKrqCofkP1KNi0NzAa4F3HXhwN8xd/UrbYz5jjjh0s6DOxX5Eu5P1+7
+JIGnuSc2Trd2iHSob2Cf97a5OrQ7/farAgMBAAGgGjAYBgkqhkiG9w0BCQcxCwwJ
+Y29sbGVjdG9yMA0GCSqGSIb3DQEBCwUAA4ICAQCJb4dJS4ddeL/IzuOA6kKpqOz/
+YJUECLCRPSvdf5DCH/ezf5L+UE8WUpcvSkRo36rwBPR/R2hDnshfKUYYZoi5UqzT
++Y7E3Kdrim4K9MGJGVTqtIYqoNS0jgbmX2zw7FDXHBVfP2TQhalzBO4BMshB916o
+IWs8esTRNhXZ7P4hDOkYyWp4qPeOjzdUgqEQosz+OkB+RdVpKfs3sG0eqZRDVnHz
+Hk7cfpn6m9OZf+0aLlAlhs1d9KvvdmUn0vC3QVktPLXZpa1XNTrE7tml3crYNmU0
+JSuP7MBwgjJMBe400aIXIKssqVUJjlAzNARFWo/84Fzb6QuyfVwNp1tMzBAFFDc3
+BOv891fwIKAOKRSSui2Eq3nxt6+8JTTx8YeuppS79V50XNl6PolY5jDEkxED7sst
+ScpU7rOURDfgT2EdqjWzSmW7JzBuuTkAz4T/lg4dAspKacVi9hG28Jt+gX6xlM5Y
+qS43o2YhSze4PfOgjkUIvqSiDa6uORMJPi5iCwGVpomKfMtZCmXrPiCyQg6u8TqJ
+LXA9pm++mehgj79kl+yb8A2eQvIqEb8eOG/CzZjW5IZxLZgKk/AjAcBn0yhhIrVq
+6rBaIlVbXOdxMsxxVgyktziA7DFCt/1TvQylSn9hWaBkpdjVlbFmMyuNU+KHZA41
+8raJhp+mC5IUG/98UQ==
+-----END CERTIFICATE REQUEST-----
diff --git a/netconfsimulator/tls/server_cert.crt b/netconfsimulator/tls/server_cert.crt
new file mode 100644
index 0000000..5909026
--- /dev/null
+++ b/netconfsimulator/tls/server_cert.crt
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFKDCCAxACAWQwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCUEwxCzAJBgNV
+BAgMAkRTMRAwDgYDVQQHDAdXcm9jbGF3MQ4wDAYDVQQKDAVOb2tpYTENMAsGA1UE
+CwwETWFubzENMAsGA1UEAwwET25hcDAeFw0xOTExMDYxNTEzMjJaFw0yMzExMDUx
+NTEzMjJaMFoxCzAJBgNVBAYTAlBMMQswCQYDVQQIDAJETjEQMA4GA1UEBwwHV3Jv
+Y2xhdzEOMAwGA1UECgwFTm9raWExDTALBgNVBAsMBE1hbm8xDTALBgNVBAMMBE9u
+YXAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCxZdeHbMmoVGw5vFsQ
+9lTxhzsvAnHFu+1hcsW1FDNCcX2sDPHCQvAGZW9kUVTZsLKuZrUGuV1p6a2QcdQW
+jJjfnpNtNUEnnOZtzcHrM+pu8PMSJ/iSaq3q3UOrw6ik7AiCDYXpB4DQG90HUZyB
+NnUpPDkWYFUz8TTMixvN9lP/JRonNsovj/4wd4+CGp8d95NfBlG/OdR+tA0g2JE9
+UAz/ew0sP9pNmE4ZYwXFCmiirmoyaQPiMC9rO7w22iY9Z3iLhPrQ0imQEkz81CUk
+vXdMhZNPdMCONonWfbMwcj/r9PIQUA3OFKNAllK0sXmyifGe5N+O1N7hujBI2Oha
+oy+8uKGaOu9H44iojpyVUcRQ3Y/+Q0d+4RRox4NcegUkbeGieEknWLjbuV9ixoo6
+iehQXinPk2X4+INXh9my7xCNN2dFdUNqfKqufY1SQG+VNqsD0VN9CUaxWEfOJD4/
+MVdcYt7WZHtE9J7Mwi5w2uaAiJptUN9TXG3tWa7kjTZhoaYxqnvpxmizoI0kGqGH
+3vfNovYYbBCICzrTi0jmEkbCMHerVqB+Vq7K5KLU2sG8vXTAwO4IZEPJS8ZLi5HJ
+v1SCq6gqH5D9SjYtDcwGuBdx14cDfMXf1K22M+Y444dLOgzsV+RLuT9fuySBp7kn
+Nk63doh0qG9gn/e2uTq0O/32qwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBqW8/0
+u85VTUWA8tpfjqgmMd9WdM5YVty/oRQ3sT+1k6mdPF2u8HgTD5vNR71XEkpQuNbZ
+9UrJ1UOgcbxcodDh2v2Jv5faSyQBnmkSjagyH+7goN/mcM+CtIZQsdp8drzlty/M
+x1CeVW54jVrInk01yS3cATCdxqkel9nhTT0qcPOmHxugTyTwApzwN4dRMf1WHQ9B
+U1K95yWvscZdY2cMKRw3Ps90+AU8eSoAbV1qQEl95qcWani7LPNfc8nii3nCINBR
+KTdbP1uG8hck4BFxOZye6mbNMx086T7O3Buzcve5O/3isCCZQhV6PZIE7zL5+hyY
+RLx7MxUUETy4QFapGCfOGvixK8QM/xXJone2CjnmptiRJjC11Imv84bL3KUziKPR
+DTyX2f4oKEwaVpbfJgyyxMmoNUAB2NaKNAW9NhvYjVqmCUvgrvL7L6WZTe9N5gkX
+H7cQZ7PyAdgGKet0b0Spr8NfBATyZKNI6ZRcGJduO7sOblqAsC6GToq7Jcz9xkYd
+ItNdhoDohZIDwW1XnHuNKgE1tEXm1KAbdlGrUEOTsyuK4bz4kDdVu58/nFT4ZYnC
+2HSyLwsNNeKChwdVFL8d/Rj9ojjVyUBH6wuQHRAcs0Ui2Cpy+OPWcNjDuHjkiw2J
+KEzFqRi6zQqj1y2Sjv1HQpn8dxppKOsV6rjoXQ==
+-----END CERTIFICATE-----
diff --git a/netconfsimulator/tls/server_key.pem b/netconfsimulator/tls/server_key.pem
new file mode 100644
index 0000000..b10a076
--- /dev/null
+++ b/netconfsimulator/tls/server_key.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----