Add simulator improvements. 20/4020/1
authorAlex Stancu <alexandru.stancu@highstreet-technologies.com>
Mon, 8 Jun 2020 11:52:03 +0000 (14:52 +0300)
committerAlex Stancu <alexandru.stancu@highstreet-technologies.com>
Mon, 8 Jun 2020 11:52:26 +0000 (14:52 +0300)
Add manual notification generation support.

Bugfix for IPv6 support.

Add custom naming of simulated devices.

Issue-ID: SIM-20
Change-Id: I82c51c8ca30ae17be66f65110b738eb99cbed5ca
Signed-off-by: Alex Stancu <alexandru.stancu@highstreet-technologies.com>
28 files changed:
ntsimulator/README.md
ntsimulator/deploy/nts-manager/Dockerfile
ntsimulator/deploy/nts-manager/container-tag.yaml
ntsimulator/deploy/o-ran-sc/o-ran-ru/CMakeLists.txt
ntsimulator/deploy/o-ran-sc/o-ran-ru/Dockerfile
ntsimulator/deploy/o-ran-sc/o-ran-ru/container-tag.yaml
ntsimulator/deploy/o-ran-sc/o-ran-ru/supervisord.conf
ntsimulator/deploy/o-ran/ru-fh/CMakeLists.txt
ntsimulator/deploy/o-ran/ru-fh/Dockerfile
ntsimulator/deploy/o-ran/ru-fh/container-tag.yaml
ntsimulator/deploy/o-ran/ru-fh/supervisord.conf
ntsimulator/deploy/tls/enable_connections.sh
ntsimulator/deploy/tls/enable_netconf_call_home.sh [new file with mode: 0755]
ntsimulator/deploy/tls/enable_tls.sh
ntsimulator/deploy/x-ran/CMakeLists.txt
ntsimulator/deploy/x-ran/Dockerfile
ntsimulator/deploy/x-ran/container-tag.yaml
ntsimulator/deploy/x-ran/supervisord.conf
ntsimulator/scripts/docker-compose.yml
ntsimulator/src/generic-notifications/CMakeLists.txt [new file with mode: 0644]
ntsimulator/src/generic-notifications/generic-notifications.c [new file with mode: 0644]
ntsimulator/src/ntsimulator-manager/ntsimulator-manager.c
ntsimulator/src/ntsimulator-manager/simulator-operations.c
ntsimulator/src/ntsimulator-manager/simulator-operations.h
ntsimulator/src/utils/utils.c
ntsimulator/yang/nts-manager/network-topology-simulator.xml
ntsimulator/yang/nts-manager/network-topology-simulator.yang
ntsimulator/yang/sysrepo-configuration-load.sh

index 5ae5319..0e956ed 100644 (file)
@@ -26,21 +26,21 @@ The purpose of the NTS Manager is to ease the utilization of the NTS framework.
 
 ```
 module: network-topology-simulator
-  +--rw simulator-config
+  +--rw simulator-config!
   |  +--rw simulated-devices?      uint32
   |  +--rw mounted-devices?        uint32
-  |  +--rw ssh-connections?        uint32
-  |  +--rw tls-connections?        uint32
+  |  +--rw netconf-call-home?      boolean
   |  +--rw notification-config
   |  |  +--rw fault-notification-delay-period*   uint32
   |  |  +--rw ves-heartbeat-period?              uint32
   |  |  +--rw is-netconf-available?              boolean
   |  |  +--rw is-ves-available?                  boolean
   |  +--rw controller-details
-  |  |  +--rw controller-ip?         inet:ip-address
-  |  |  +--rw controller-port?       inet:port-number
-  |  |  +--rw controller-username?   string
-  |  |  +--rw controller-password?   string
+  |  |  +--rw controller-ip?            inet:ip-address
+  |  |  +--rw controller-port?          inet:port-number
+  |  |  +--rw netconf-call-home-port?   inet:port-number
+  |  |  +--rw controller-username?      string
+  |  |  +--rw controller-password?      string
   |  +--rw ves-endpoint-details
   |     +--rw ves-endpoint-ip?            inet:ip-address
   |     +--rw ves-endpoint-port?          inet:port-number
@@ -53,6 +53,8 @@ module: network-topology-simulator
      +--ro simulation-usage-details
      |  +--ro running-simulated-devices?   uint32
      |  +--ro running-mounted-devices?     uint32
+     |  +--ro ssh-connections?             uint32
+     |  +--ro tls-connections?             uint32
      |  +--ro base-netconf-port?           uint32
      |  +--ro cpu-usage?                   percent
      |  +--ro mem-usage?                   uint32
@@ -92,6 +94,13 @@ module: network-topology-simulator
   rpcs:
     +---x restart-simulation
     +---x add-key-pair-to-odl
+    +---x invoke-notification
+       +---w input
+       |  +---w device-id              string
+       |  +---w yang-module-name       string
+       |  +---w notification-object    string
+       +--ro output
+          +--ro status    enumeration
 ```
 
 #### Detailed information about the YANG attributes
@@ -100,16 +109,16 @@ module: network-topology-simulator
 
 * **simulated-devices** - represents the number of simulated devices. The default value is 0, meaning that when the NTS is started, there are no simulated devices. When this value is increased to **n**, the NTS Manager starts docker containers in order to reach **n** simulated devices. If the value is decreased to **k**, the NTS Manager will remove docker containers, until the number of simulated devices reaches **k**;
 * **mounted-devices** - represents the number of devices to be mounted to an ODL based SDN Controller. The same phylosophy as in the case of the previous leaf applies. If this number is increased, the number of ODL mountpoints increases. Else, the simulated devices are being unmounted from ODL. The number of mounted devices cannot exceed the number of simulated devices. The details about the ODL controller where to mount/unmount are given by the **controller-details** container; **Please note that this cannot be set to a value > 0 if the *ves-registration* leaf is set to 'True'**; For each simulated device, a number of NETCONF endpoints will be mounted, according to the **ssh-connections** and **tls-connections** leafs. See **NETCONF Endpoints** section for more details;
-* **ssh-connections** - represents the number of SSH endpoints to be exposed by each of the simulated devices. **Please note that the total number of SSH and TLS connections cannot exceed 100.** The default value is 1. **The value can only be changed when simulated-devices is 0**.
-* **tls-connectioons** - represents the number of TLS endpoints to be exposed by each of the simulated devices. **Please note that the total number of SSH and TLS connections cannot exceed 100.** The default value is 0. **The value can only be changed when simulated-devices is 0**.
+* **netconf-call-home** - if set to `true`, each simulated device will try to use NETCONF Call Home feature and try to reach the ODL Controller. The default value is `false`.
 *  **notification-config** - this container groups the configuration about fault notification generation of each simulated device;
-* **fault-notification-delay-period** - the amount of seconds between two generated fault notifications. For example, if this has a value of *10*, each simulated device will generate a **random** fault notification every *10* seconds; **when this is set to 0, it will reset the notification counters for the VES and NETCONF notifications, which are exposed in the simulator-status**;
+* **fault-notification-delay-period** - the amount of seconds between two generated fault notifications. For example, if this has a value of *10*, each simulated device will generate a **random** fault notification every *10* seconds; **when this is set to 0, it will reset the notification counters for the VES and NETCONF notifications, which are exposed in the simulator-status**; The type is a leaf-list, such that the user could define a pattern for sending the notifications. E.g.: [10, 3, 5] means that a notification will be sent after 10 seconds, then after another 3 seconds, then after 5 seconds, and then again after 10, 3, 5 etc.
 * **ves-heartbeat-period** - the amount of seconds between VES heartbeat messages that can be generated by each simulated device. The details about the VES connection endpoint are given in the **ves-endpoint-details** container;
 * **is-netconf-available** - if set to 'True', NETCONF notifications will be sent when a random fault notification is generated, The NETCONF notification that is being sent is currently *o-ran-fm:alarm-notif*; if set to 'False', NETCONF notifications are not being sent out;
 * **is-ves-available** - if set to 'True', VES *faultNotification* messages will be sent when a random fault notification is generated; if set to 'False', VES *faultNotification* messages are not generated;
 * **controller-details** - this container groups the configuration related to the ODL based SDN controller that the simulated devices can connect to;
 * **controller-ip** - the IP address of the ODL based SDN controller where the simulated devices can be mounted. Only IPv4 is supported currently;
 * **controller-port** - the port of the ODL based SDN controller;
+* **netconf-call-home-port** - the NETCONF Call Home port of the ODL based SDN controller;
 * **controller-username** - the username to be used when connecting to the ODL based SDN controller;
 * **controller-password** - the password to be used when connecting to the ODL based SDN controller;
 * **ves-endpoint-details** - this container groups the configuration related to the VES endpoint where the VES messages are targeted;
@@ -130,6 +139,8 @@ module: network-topology-simulator
 * **simulation-usage-details** - this container groups the information about the current simulator status;
 * **running-simulated-devices** - the current number of running simulated devices;
 * **running-mounted-devices** - the current number of running simulated devices that have been mounted to the ODL based SDN controller; For each simulated device, 10 NETCONF endpoints will be mounted (7 SSH + 3 TLS). See **NETCONF Endpoints** section for more details.
+* **ssh-connections** - represents the number of SSH endpoints that are exposed by each of the simulated devices. **Please note that the total number of SSH and TLS connections cannot exceed 100.** The default value is 1. **The value can only be changed when the NTS Manager is started, through the SshConnections environment variable**.
+* **tls-connections** - represents the number of TLS endpoints that are exposed by each of the simulated devices. **Please note that the total number of SSH and TLS connections cannot exceed 100.** The default value is 0. **The value can only be changed when the NTS Manager is started, through the SshConnections environment variable**.
 * **base-netconf-port** - the port that was used as a base when craeting simulated devices;
 * **cpu-usage** - the percentage of the CPU used currently by the simulation framework;
 * **mem-usage** - the amount of RAM used (in MB) currently by the simulation framework;
@@ -150,6 +161,13 @@ module: network-topology-simulator
 
 * **add-key-pair-to-odl** - this RPC can be used to trigger the loading of a *keystore* entry in an ODL based SDN controller such that the controller can connect to the simulated devices via **TLS**. A private key, an associated certificate and a trusted certificate are loaded in the *keystore* entry in ODL. The certificate associated with the private key to be used by ODL in the TLS communication is signed by the same CA as the certificates used by the simulated devices, easing the TLS configuration in both the NETCONF Server and the ODL.
 * **restart-simulation** - this RPC is not yet implemented.
+* **invoke-notification** - this RPC is used for forcing a simulated device to send a NETCONF notification, as defined by the user. 
+    - The **input** needed by the RPC:
+        - **device-id** - this is a string containing the name of the simulated device that we want to send the notification. The user is responsible to give a correct name which really exists, otherwise the RPC will fail.
+        - **yang-module-name** - this is a string containing the name of the YANG module which implements the notification that we want to send. E.g.: ***org-openroadm-device*** module defines several notifications.
+        - **notification-object** - this is a string containing the notification object that we are trying to send from the simulated device, in JSON format. **Please note that the user has the responsibility to ensure that the JSON object is valid, according to the definition of the notification in the YANG module.** There is no possibility to see what was wrong when trying to send an incorrect notification. The RPC will only respond with an "ERROR" status in that case, without further information. E.g. of a JSON containing a notification object of type ***otdr-scan-result*** defined in the ***org-openroadm-device*** YANG module: ***{"org-openroadm-device:otdr-scan-result":{"status":"Successful","status-message":"Scan result was successful","result-file":"/home/result-file.txt"}}***. **Please note that the notification object contains also the name of the YANG model defning it, as a namespace, as seen in the example.**
+    - The **output** returned by the RPC:
+        - **status** - if the notification was send successfully by the simulated device, the RPC will return a **SUCCESS** value. Else, the RPC will return a **ERROR** value.
 
 ### Simulated Device
 
@@ -157,11 +175,11 @@ Each simulated device is represented as a docker container, inside which the NET
 
 #### NETCONF Endpoints
 
-Each simulated device exposes a number of NETCONF endpoints which represented by the sum of the **ssh-connections** and **tls-connections** leafs, on consecutive ports. The first simulated device uses the ports starting from the **NETCONF_BASE** environment variable used when starting the NTS Manager, while the next one uses the next ports and so on and so forth. E.g. if the **NETCONF_BASE=50000** and **ssh-connections=5** and **tls-connections=3**, the first simulated device will expose ports from *50000* to *50007*, the second simulated device will expose ports from *5008* to *50015* etc.
+Each simulated device exposes a number of NETCONF endpoints which represented by the sum of the **SshConnections** and **TlsConnections** environment variables, on consecutive ports. The first simulated device uses the ports starting from the **NETCONF_BASE** environment variable used when starting the NTS Manager, while the next one uses the next ports and so on and so forth. E.g. if the **NETCONF_BASE=50000** and **SshConnections=5** and **TlsConnections=3**, the first simulated device will expose ports from *50000* to *50007*, the second simulated device will expose ports from *5008* to *50015* etc.
 
-The first **ssh-connections** ports exposed by a simulated device are **SSH** based. A NETCONF client can connect to the exposed endpoint using one of the SSH ports (e.g. 50000 to 50007, considering the previous example) and the **username/password**: *netconf/netconf*.
+The first **SshConnections** ports exposed by a simulated device are **SSH** based. A NETCONF client can connect to the exposed endpoint using one of the SSH ports (e.g. 50000 to 50007, considering the previous example) and the **username/password**: *netconf/netconf*.
 
-The last **tls-connections** ports exposed by a simulated device are **TLS** based. A NETCONF client can connect to the exposed endpoint using one of the TLS ports (e.g. 50006 to 50008, considering the previous example), using a valid certificate and the **username**: *netconf*. 
+The last **TlsConnections** ports exposed by a simulated device are **TLS** based. A NETCONF client can connect to the exposed endpoint using one of the TLS ports (e.g. 50006 to 50008, considering the previous example), using a valid certificate and the **username**: *netconf*. 
 
 ## Usage
 
@@ -213,6 +231,13 @@ services:
       SshConnections: 1
       TlsConnections: 0
       K8S_DEPLOYMENT: "false"
+      CONTAINER_NAME: "ntsimulator"
+      NetconfCallHome: "true"
+      NetconfCallHomePort: 6666
+      ControllerIp: "10.20.11.121"
+      ControllerPort: 8181
+      ControllerUsername: "admin"
+      ControllerPassword: "admin"
 ```
 
 
@@ -230,7 +255,7 @@ services:
 * Labels - this associates the *NTS-manager* label to the docker container where the NTS runs;
 * Environment variables:
     * **NTS_IP** - this should point to an IP address **from the host**, through which the simulated devices will be accessed;
-    * **NETCONF_BASE** - this is the starting port used to expose NETCONF endpoints. Starting from this, each device will use 10 consecutive ports for its endpoints;
+    * **NETCONF_BASE** - this is the starting port used to expose NETCONF endpoints. Starting from this, each device will use 10 consecutive ports for its endpoints; **Please note that if multiple managers are deployed on the same machine, this environment variable needs to be different for each of the managers!**
     * **DOCKER_ENGINE_VERSION** - this is the version of the *docker engine* installed currently on the host. This can be verified using `docker version` command in the host, and looking to the `API version:      #.##` variable from the Server details.
     * **MODELS_IMAGE** - this represents the name of the docker image that represents the simulated device. The NTS Manager will start containers using this image, when starting simulated devices.
     * **VesHeartbeatPeriod** - this can change the default value of the **ves-heartbeat-period** leaf used by the NTS Manager.
@@ -239,9 +264,16 @@ services:
     * **VesRegistration** - this can change the default value of the **ves-registration** leaf used by the NTS Manager.
     * **VesEndpointPort** - this can change the default value of the **ves-endpoint-port** leaf used by the NTS Manager.
     * **VesEndpointIp** - this can change the default value of the **ves-endpoint-ip** leaf used by the NTS Manager.
-    * **SshConnections** - this can change the default value of the **ssh-connections** leaf used by the NTS Manager.
-    * **TlsConnections** - this can change the default value of the **tls-connections** leaf used by the NTS Manager.
+    * **SshConnections** - **this can change the number of SSH connection endpoints which are exposed by each simulated device.**
+    * **TlsConnections** - **this can change the number of TLS connection endpoints which are exposed by each simulated device.**
     * **K8S_DEPLOYMENT** - this value can be set to `true` when the user wants to the NTS Framework in a Kubernetes deployment. The default is `false`.
+    * **CONTAINER_NAME** - this represents the name to be used by the NTS Manager for asigning to each simulated device, suffixed by a number. The default is `ntsim`. E.g.: the first simulated device will be *ntsim-0*, the second onoe *ntsim-1* and so on. **Please note that if multiple managers are deployed on the same machine, this environment variable needs to be different for each of the managers!**
+    * **ControllerIp** - this can change the default value of the **controller-ip** leaf used by the NTS Manager. The default is `172.17.0.1`.
+    * **ControllerPort** - this can change the default value of the **controller-port** leaf used by the NTS Manager. The default is `8181`.
+    * **NetconfCallHomePort** - this can change the default value of the **netconf-call-home-port** leaf used by the NTS Manager. The default is `6666`.
+    * **ControllerUsername** - this can change the default value of the **controller-username** leaf used by the NTS Manager. The default is `admin`.
+    * **ControllerPassword** - this can change the default value of the **controller-password** leaf used by the NTS Manager. The default is `admin`.
+    * **NetconfCallHome** - this can change the default value of the **netconf-call-home** leaf used by the NTS Manager. The default is `false`.
     
 After modifying the `docker-compose.yml` file with values specific to your host, the NTS Manager can be started by running the command `docker-compose up` from the **scripts** folder.
 
@@ -259,28 +291,28 @@ When the NTS Manager is started, its default configuration looks like this:
 
 ```xml
 <simulator-config xmlns="urn:onf:params:xml:ns:yang:network-topology-simulator">
-       <simulated-devices>0</simulated-devices>
-       <mounted-devices>0</mounted-devices>
-    <ssh-connections>1</ssh-connections>
-    <tls-connections>0</tls-connections>
-       <notification-config>
-               <fault-notification-delay-period>0</fault-notification-delay-period>
-               <ves-heartbeat-period>0</ves-heartbeat-period>
-               <is-netconf-available>true</is-netconf-available>
-               <is-ves-available>true</is-ves-available>
-       </notification-config>
-       <controller-details>
-               <controller-ip>172.17.0.1</controller-ip>
-               <controller-port>8181</controller-port>
-               <controller-username>admin</controller-username>
-               <controller-password>admin</controller-password>
-       </controller-details>
-       <ves-endpoint-details>
-               <ves-endpoint-ip>172.17.0.1</ves-endpoint-ip>
-               <ves-endpoint-port>30007</ves-endpoint-port>
-               <ves-endpoint-auth-method>no-auth</ves-endpoint-auth-method>
-               <ves-registration>false</ves-registration>
-       </ves-endpoint-details>
+    <simulated-devices>0</simulated-devices>
+    <mounted-devices>0</mounted-devices>
+    <netconf-call-home>false</netconf-call-home>
+    <notification-config>
+        <fault-notification-delay-period>0</fault-notification-delay-period>
+        <ves-heartbeat-period>0</ves-heartbeat-period>
+        <is-netconf-available>true</is-netconf-available>
+        <is-ves-available>true</is-ves-available>
+    </notification-config>
+    <controller-details>
+        <controller-ip>172.17.0.1</controller-ip>
+        <controller-port>8181</controller-port>
+        <netconf-call-home-port>6666</netconf-call-home-port>
+        <controller-username>admin</controller-username>
+        <controller-password>admin</controller-password>
+    </controller-details>
+    <ves-endpoint-details>
+        <ves-endpoint-ip>172.17.0.1</ves-endpoint-ip>
+        <ves-endpoint-port>30007</ves-endpoint-port>
+        <ves-endpoint-auth-method>no-auth</ves-endpoint-auth-method>
+        <ves-registration>false</ves-registration>
+    </ves-endpoint-details>
 </simulator-config>
 ```
 
@@ -300,9 +332,6 @@ Example RPC for starting **one** simulated device:
     <config>
       <simulator-config xmlns="urn:onf:params:xml:ns:yang:network-topology-simulator">
         <simulated-devices>1</simulated-devices>
-        <!--We need to delete the ssh-connections and tls-connections leafs when configuring simulated-devices > 0 -->
-        <ssh-connections xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0" xc:operation="delete"/>
-        <tls-connections xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0" xc:operation="delete"/>
       </simulator-config>
     </config>
   </edit-config>
@@ -329,4 +358,24 @@ Example of a result of such a command:
 ntsim_oran_light       latest           57b065de4458     4 days ago     186MB
 ```
 
-This means that `MODELS_IMAGE: "ntsim_oran_light:latest"` can be used as an environment variable when starting the NTS Manager.
\ No newline at end of file
+This means that `MODELS_IMAGE: "ntsim_oran_light:latest"` can be used as an environment variable when starting the NTS Manager.
+
+
+## Release notes
+
+### **version 0.6.1**
+
+Added features:
+* **Manual notification generation** - this enables the user to send a NETCONF notification from a simulated device, using the *invoke-notification* RPC defined in the NTS Manager.
+* **automatic pulling of the simulated device image** - the NTS Manager will automatically try to pull the image of the simulated device (as defined in the **MODELS_IMAGE** environment variable) before starting a simulated device.
+* **custom naming of simulated devices** - the user can now define its own name prefix for simulated devices, through the **CONTAINER_NAME** environment variable defined in the docker-compose.yml. E.g.: if CONTAINER_NAME: "ntsim", the simulated devices will be: *ntsim-0*, *ntsim-1* etc.
+
+Bug fixes and improvements:
+* **ssh-connections** and **tls-connections** are now removed from the simulator-config, and can be set only when the NTS Manager is started, through the **SshConnections** and **TlsConnections** environment variables defined in the docker-compose.yml. The leafs are not moved in the simulator-status, such that the user can check at runting what are the values defined.
+* **fault-notification-delay-period** has now the attribute *ordered-by user*, meaning that the order defined by the user when adding multiple values to the leaf-list is preserved.
+
+### **version 0.5.1**
+
+Added features:
+* **NETCONF Call Home** - this enables each simulated device to use the Call Home feature to connect to the ODL instance, once it boots up. **Please note that when a device is using Call Home, it will no longed expose all the number of *ssh-connections* and *tls-connections*, but a single Call Home endpoint. **
+* **controller-details** configuration leafs are now exposed can now be set at startup time in the docker-compose YAML file, as environment variables.
\ No newline at end of file
index 2396a5d..121627e 100644 (file)
@@ -41,7 +41,7 @@ USER netconf
 RUN \
       mkdir -p /home/netconf/.ssh && \
       ssh-keygen -A && \
-      ssh-keygen -t dsa -P '' -f /home/netconf/.ssh/id_dsa && \
+      ssh-keygen -t rsa -P '' -f /home/netconf/.ssh/id_dsa && \
       cat /home/netconf/.ssh/id_dsa.pub > /home/netconf/.ssh/authorized_keys
 #echo "Host *\n    StrictHostKeyChecking accept-new" >> /home/netconf/.ssh/config
 USER root
@@ -96,7 +96,7 @@ RUN \
       git clone https://github.com/Melacon/Netopeer2.git && \
       cd Netopeer2 && \
       cd keystored && mkdir build && cd build && \
-      cmake -DCMAKE_BUILD_TYPE:String="Release" .. && \
+      cmake -DCMAKE_BUILD_TYPE:String="Release" -DKEYSTORED_KEYS_DIR=/home/netconf/.ssh .. && \
       make -j2 && \
       make install && \
       ldconfig
@@ -143,6 +143,13 @@ ENV SshConnections=1
 ENV TlsConnections=0
 ENV K8S_DEPLOYMENT=false
 ENV CONTAINER_NAME="ntsim"
+ENV ControllerIp="172.17.0.1"
+ENV ControllerPort=8181
+ENV NetconfCallHomePort=6666
+ENV ControllerUsername="admin"
+ENV ControllerPassword="admin"
+ENV NetconfCallHome=false
+ENV Ipv6Enabled=false
 
 RUN \
       apt-get update && apt-get install -y supervisor bc
@@ -207,8 +214,8 @@ COPY --from=builder /lib/x86_64-linux-gnu/libkeyutils.so.1 /lib/x86_64-linux-gnu
 COPY --from=builder /etc/sysrepo /etc/sysrepo
 COPY --from=builder /var/run /var/run
 
-COPY --from=builder /usr/local/etc/keystored/keys/ssh_host_rsa_key.pem /usr/local/etc/keystored/keys/ssh_host_rsa_key.pem
-COPY --from=builder /usr/local/etc/keystored/keys/ssh_host_rsa_key.pem.pub /usr/local/etc/keystored/keys/ssh_host_rsa_key.pem.pub
+#COPY --from=builder /usr/local/etc/keystored/keys/ssh_host_rsa_key.pem /usr/local/etc/keystored/keys/ssh_host_rsa_key.pem
+#COPY --from=builder /usr/local/etc/keystored/keys/ssh_host_rsa_key.pem.pub /usr/local/etc/keystored/keys/ssh_host_rsa_key.pem.pub
 COPY --from=builder /usr/local/share/libnetconf2 /usr/local/share/libnetconf2
 
 RUN ldconfig
@@ -216,6 +223,10 @@ RUN ldconfig
 COPY ./deploy/nts-manager/supervisord.conf /etc/supervisord.conf
 
 COPY ./deploy/nts-manager/docker_stats.sh /opt/dev/docker_stats.sh
+# Fix for the NETCONF notifications
+RUN \
+    echo "Fixing the NETCONF notifications..." && \
+    mkdir -p /var/run/sysrepo-subscriptions/ietf-crypto-types
          
 ARG BUILD_DATE
 LABEL build-date=$BUILD_DATE
index 3eec7d7..8eabadd 100644 (file)
@@ -5,6 +5,8 @@ include_directories(
     "/usr/include"
 )
 
+add_subdirectory(generic-notifications)
+
 # sources
 set (UTILS_SOURCES
     ${UTILS_DIR}/utils.c
index ff4c008..7129884 100644 (file)
@@ -202,9 +202,11 @@ COPY --from=builder /usr/local/share/libnetconf2 /usr/local/share/libnetconf2
 RUN ldconfig
 
 WORKDIR /tmp
-RUN apt-get install -yqq wget \
+RUN \
+    apt-get update && apt-get install -yqq wget \
     openssh-client \
-    jq
+    jq \
+    curl
 
 RUN mkdir py_install && cd py_install && \
     wget https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz && \
index e4b99fa..05ab36d 100644 (file)
@@ -21,9 +21,9 @@ autorestart=true
 redirect_stderr=true
 priority=3
 
-[program:enable-ssh]
+[program:enable-netconf-call-home]
 directory=/home/netconf/.ssh
-command=/home/netconf/.ssh/enable_ssh_key.sh
+command=/home/netconf/.ssh/enable_netconf_call_home.sh
 startsecs=0
 autorestart=false
 redirect_stderr=false
@@ -37,6 +37,14 @@ autorestart=false
 redirect_stderr=false
 priority=5
 
+[program:set-nts-ip-script]
+directory=/home/netconf/.ssh
+command=/home/netconf/.ssh/set_NTS_IP.sh
+startsecs=0
+autorestart=false
+redirect_stderr=false
+priority=5
+
 [program:sysrepo-config-load]
 directory=/opt/dev/yang
 command=/opt/dev/yang/sysrepo-configuration-load.sh
index 3294ef2..3cf2ed0 100644 (file)
@@ -8,6 +8,7 @@ include_directories(
 add_subdirectory(o-ran-notifications)
 add_subdirectory(ves-messages)
 add_subdirectory(software-management-oran)
+add_subdirectory(generic-notifications)
 
 # sources
 set (UTILS_SOURCES
index e711258..d33d1d2 100644 (file)
@@ -202,9 +202,11 @@ COPY --from=builder /usr/local/share/libnetconf2 /usr/local/share/libnetconf2
 RUN ldconfig
 
 WORKDIR /tmp
-RUN apt-get install -yqq wget \
+RUN \
+    apt-get update && apt-get install -yqq wget \
     openssh-client \
-    jq
+    jq \
+    curl
 
 RUN mkdir py_install && cd py_install && \
     wget https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz && \
index e9f3e2f..1dcba35 100644 (file)
@@ -21,9 +21,9 @@ autorestart=true
 redirect_stderr=true
 priority=3
 
-[program:enable-ssh]
+[program:enable-netconf-call-home]
 directory=/home/netconf/.ssh
-command=/home/netconf/.ssh/enable_ssh_key.sh
+command=/home/netconf/.ssh/enable_netconf_call_home.sh
 startsecs=0
 autorestart=false
 redirect_stderr=false
index ff51e27..a321f8b 100755 (executable)
@@ -41,17 +41,23 @@ fi
 
 netconf_port=830
 
+if [ $IPv6Enabled = "true" ]; then
+    localhost_address="::"
+else
+    localhost_address="0.0.0.0"
+fi
+
 echo '<netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server"><listen>' > connections.xml
 
 for (( ssh_counter=0; ssh_counter<$ssh_conn; ssh_counter++ ))
 do
-  echo "<endpoint><name>MNG$ssh_counter</name><ssh><address>::</address><port>$netconf_port</port><host-keys><host-key><name>imported SSH key</name><public-key>ssh_host_rsa_key</public-key></host-key></host-keys></ssh></endpoint>" >> connections.xml
+  echo "<endpoint><name>MNG$ssh_counter</name><ssh><address>$localhost_address</address><port>$netconf_port</port><host-keys><host-key><name>imported SSH key</name><public-key>ssh_host_rsa_key</public-key></host-key><host-key><name>Melacon Server key</name><public-key>melacon_server_key</public-key></host-key></host-keys></ssh></endpoint>" >> connections.xml
   ((netconf_port++))
 done
 
 for (( tls_counter=0; tls_counter<$tls_conn; tls_counter++ ))
 do
-  echo "<endpoint><name>MNGTLS$tls_counter</name><tls><address>::</address><port>$netconf_port</port><certificates><certificate><name>melacon_server_cert</name></certificate></certificates><client-auth><trusted-ca-certs>trusted_ca_list</trusted-ca-certs><cert-maps><cert-to-name><id>1</id><fingerprint>02:E9:38:1F:F6:8B:62:DE:0A:0B:C5:03:81:A8:03:49:A0:00:7F:8B:F3</fingerprint><map-type xmlns:x509c2n=\"urn:ietf:params:xml:ns:yang:ietf-x509-cert-to-name\">x509c2n:specified</map-type><name>netconf</name></cert-to-name></cert-maps></client-auth></tls></endpoint>" >> connections.xml
+  echo "<endpoint><name>MNGTLS$tls_counter</name><tls><address>$localhost_address</address><port>$netconf_port</port><certificates><certificate><name>melacon_server_cert</name></certificate></certificates><client-auth><trusted-ca-certs>trusted_ca_list</trusted-ca-certs><cert-maps><cert-to-name><id>1</id><fingerprint>02:E9:38:1F:F6:8B:62:DE:0A:0B:C5:03:81:A8:03:49:A0:00:7F:8B:F3</fingerprint><map-type xmlns:x509c2n=\"urn:ietf:params:xml:ns:yang:ietf-x509-cert-to-name\">x509c2n:specified</map-type><name>netconf</name></cert-to-name></cert-maps></client-auth></tls></endpoint>" >> connections.xml
   ((netconf_port++))
 done
 
diff --git a/ntsimulator/deploy/tls/enable_netconf_call_home.sh b/ntsimulator/deploy/tls/enable_netconf_call_home.sh
new file mode 100755 (executable)
index 0000000..5d453ea
--- /dev/null
@@ -0,0 +1,94 @@
+#! /bin/bash
+################################################################################
+#
+# Copyright 2020 highstreet technologies GmbH and others
+#
+# 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.
+
+################################################################################
+
+sleep 20
+
+netconf_call_home=`jq '.["netconf-call-home"]' /opt/dev/scripts/configuration.json`
+
+
+if [ $netconf_call_home = "false" ] ; then
+   echo "NETCONF Call Home is disabled, nothing to do..."
+   exit 0
+else
+   echo "Enabling NETCONF Call Home in device..."
+fi
+
+controller_ip=`jq '.["controller-details"] ."controller-ip"' /opt/dev/scripts/configuration.json`
+controller_username=`jq '.["controller-details"] ."controller-username"' /opt/dev/scripts/configuration.json`
+controller_password=`jq '.["controller-details"] ."controller-password"' /opt/dev/scripts/configuration.json`
+controller_port=`jq '.["controller-details"] ."controller-port"' /opt/dev/scripts/configuration.json`
+netconf_call_home_port=`jq '.["controller-details"] ."netconf-call-home-port"' /opt/dev/scripts/configuration.json`
+
+SSH_PUB_KEY_MELACON="$(cat /home/netconf/.ssh/melacon.server.key.pub | awk '{print $2}')"
+
+payload='{
+      "odl-netconf-callhome-server:device": [
+        {
+          "odl-netconf-callhome-server:unique-id": "'$HOSTNAME'",
+          "odl-netconf-callhome-server:ssh-host-key": "'$SSH_PUB_KEY_MELACON'",
+          "odl-netconf-callhome-server:credentials": {
+            "odl-netconf-callhome-server:username": "netconf",
+            "odl-netconf-callhome-server:passwords": [
+              "netconf"
+            ]
+          }
+        }
+      ]
+}'
+
+odl_ip=`sed -e 's/^"//' -e 's/"$//' <<<"$controller_ip"`
+odl_username=`sed -e 's/^"//' -e 's/"$//' <<<"$controller_username"`
+odl_password=`sed -e 's/^"//' -e 's/"$//' <<<"$controller_password"`
+
+echo "Payload: $payload"
+
+curl -v -H 'Content-Type: application/json' -X PUT -u $odl_username:$odl_password \
+-d "$payload" http://$odl_ip:$controller_port/restconf/config/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices/device/$HOSTNAME
+
+echo '<netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
+        <call-home>
+          <netconf-client>
+            <name>test_ssh_ch_client</name>
+            <ssh>
+              <endpoints>
+                <endpoint>
+                  <name>test_ssh_ch_endpt</name>
+                  <address>'$odl_ip'</address>
+                  <port>'$netconf_call_home_port'</port>
+                </endpoint>
+              </endpoints>
+              <host-keys>
+                <host-key>
+                  <name>melacon server key</name>
+                  <public-key>melacon_server_key</public-key>
+                </host-key>
+              </host-keys>
+            </ssh>
+            <connection-type>
+              <persistent/>
+            </connection-type>
+          </netconf-client>
+        </call-home>
+    </netconf-server>' > connections.xml
+
+sysrepocfg --merge=connections.xml --format=xml ietf-netconf-server
+rm connections.xml
+
+echo 'Done'
+exit 0
\ No newline at end of file
index 11fbb3d..97ba4c2 100755 (executable)
@@ -24,6 +24,24 @@ openssl genrsa -out melacon.server.key 2048
 openssl req -new -sha256 -key melacon.server.key -subj "/C=US/ST=CA/O=MeLaCon, Inc./CN=melacon.com" -out melacon.server.csr
 openssl x509 -req -in melacon.server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out melacon.server.crt -days 500 -sha256
 rm melacon.server.csr
+ssh-keygen -y -f melacon.server.key > melacon.server.key.pub
+
+SSH_PUB_KEY="$(cat /home/netconf/.ssh/id_dsa.pub| awk '{print $2}')"
+SSH_PUB_KEY_MELACON="$(cat melacon.server.key.pub | awk '{print $2}')"
+
+echo '<system xmlns="urn:ietf:params:xml:ns:yang:ietf-system"><authentication><user><name>netconf</name>'  >> load_auth_pubkey.xml
+echo '<authorized-key><name>ssh_key</name><algorithm>ssh-dss</algorithm>' >> load_auth_pubkey.xml
+echo '<key-data>'"$SSH_PUB_KEY"'</key-data></authorized-key>' >> load_auth_pubkey.xml
+echo '<authorized-key><name>melacon_server_key</name><algorithm>ssh-rsa</algorithm>' >> load_auth_pubkey.xml
+echo '<key-data>'"$SSH_PUB_KEY_MELACON"'</key-data></authorized-key></user></authentication></system>' >> load_auth_pubkey.xml
+
+sysrepocfg --merge=load_auth_pubkey.xml --format=xml ietf-system
+rm load_auth_pubkey.xml
+
+if [ $IPv6Enabled = "true" ]; then
+   ssh-keyscan -p 830 :: >> ~/.ssh/known_hosts
+fi
+ssh-keyscan -p 830 127.0.0.1 >> /root/.ssh/known_hosts
 
 MELACON_SERVER_KEY="$(sed '1d;$d' melacon.server.key)"
 
index e21a060..e8d6bf1 100644 (file)
@@ -7,6 +7,7 @@ include_directories(
 
 add_subdirectory(ves-messages)
 add_subdirectory(software-management-xran)
+add_subdirectory(generic-notifications)
 
 # sources
 set (UTILS_SOURCES
index b1577d7..355ae78 100644 (file)
@@ -202,9 +202,11 @@ COPY --from=builder /usr/local/share/libnetconf2 /usr/local/share/libnetconf2
 RUN ldconfig
 
 WORKDIR /tmp
-RUN apt-get install -yqq wget \
+RUN \
+    apt-get update && apt-get install -yqq wget \
     openssh-client \
-    jq
+    jq \
+    curl
 
 RUN mkdir py_install && cd py_install && \
     wget https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz && \
index 3b37a89..fbc35d5 100644 (file)
@@ -21,9 +21,9 @@ autorestart=true
 redirect_stderr=true
 priority=3
 
-[program:enable-ssh]
+[program:enable-netconf-call-home]
 directory=/home/netconf/.ssh
-command=/home/netconf/.ssh/enable_ssh_key.sh
+command=/home/netconf/.ssh/enable_netconf_call_home.sh
 startsecs=0
 autorestart=false
 redirect_stderr=false
@@ -37,6 +37,14 @@ autorestart=false
 redirect_stderr=false
 priority=5
 
+[program:set-nts-ip-script]
+directory=/home/netconf/.ssh
+command=/home/netconf/.ssh/set_NTS_IP.sh
+startsecs=0
+autorestart=false
+redirect_stderr=false
+priority=5
+
 [program:sysrepo-config-load]
 directory=/opt/dev/yang
 command=/opt/dev/yang/sysrepo-configuration-load.sh
index 7594ba9..0756327 100644 (file)
@@ -1,19 +1,51 @@
-version: '3'
+version: '2.4'
 services:
-  ntsimulator:
-    image: "o-ran-sc_ntsim_manager:latest"
-    container_name: NTS_Manager
+  ntsimulator-o-ran-sc:
+    image: "o-ran-sc/ntsim-manager:latest"
+    #image: "10.20.6.10:30000/hightec/ntsim_manager:0.4.2-SNAPSHOT-latest"
+    container_name: NTS-Manager-o-ran-sc
     ports:
-     - "8300:830"
+     - "::8300:830"
     volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
-     - "/home/parallels/workspace/highstreet/o-ran-sc-repositories/o1-interface/ntsimulator/scripts:/opt/dev/scripts"
+     - "/var/tmp/NTS-Manager-o-ran-sc:/opt/dev/scripts"
      - "/usr/bin/docker:/usr/bin/docker"
     labels:
       "NTS-manager": ""
     environment:
-      NTS_IP: "172.17.0.1"
+      #NTS_IP: "2001:db8:1::1"
+      NTS_IP: "10.20.11.121"
       NETCONF_BASE: 50000
       DOCKER_ENGINE_VERSION: "1.40"
-      MODELS_IMAGE: "o-ran-sc_ntsim_oran"
-
+      #MODELS_IMAGE: "ntsim_onf_core_model_1_2"
+      MODELS_IMAGE: "o-ran-sc/ntsim-o-ran-ru-fh:latest"      
+      VesHeartbeatPeriod: 0
+      IsVesAvailable: "true"
+      IsNetconfAvailable: "true"
+      VesRegistration: "false"
+      VesEndpointPort: 8080
+      VesEndpointIp: "172.17.0.1"
+      SshConnections: 4
+      TlsConnections: 0
+      K8S_DEPLOYMENT: "false"
+      CONTAINER_NAME: "nts-o-ran-sc"
+      NetconfCallHome: "false"
+      NetconfCallHomePort: 6666
+      ControllerIp: "10.20.11.121"
+      ControllerPort: 8181
+      ControllerUsername: "admin"
+      ControllerPassword: "admin"
+      IPv6Enabled: "true"
+    networks:
+      app_net:
+        ipv6_address: 2001:3200:3000::40
+  
+networks:
+  app_net:
+    enable_ipv6: true
+    driver: bridge
+    ipam:
+      driver: default
+      config:
+        - subnet: 2001:3200:3000::/64
+          gateway: 2001:3200:3000::1
\ No newline at end of file
diff --git a/ntsimulator/src/generic-notifications/CMakeLists.txt b/ntsimulator/src/generic-notifications/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c87e843
--- /dev/null
@@ -0,0 +1,19 @@
+set (NOTIFICATIONS_SOURCES
+    generic-notifications.c
+)
+
+include_directories(
+    "/usr/include/x86_64-linux-gnu"
+    "/usr/local/include/cjson"
+    "/usr/local/include/libyang"
+)
+
+add_executable(generic-notifications ${NOTIFICATIONS_SOURCES})
+target_link_libraries(generic-notifications utils)
+target_link_libraries(generic-notifications sysrepo)
+target_link_libraries(generic-notifications m)
+target_link_libraries(generic-notifications curl)
+target_link_libraries(generic-notifications cjson)
+target_link_libraries(generic-notifications yang)
+
+install(TARGETS generic-notifications DESTINATION ${CMAKE_INSTALL_BINDIR})
\ No newline at end of file
diff --git a/ntsimulator/src/generic-notifications/generic-notifications.c b/ntsimulator/src/generic-notifications/generic-notifications.c
new file mode 100644 (file)
index 0000000..b6b5459
--- /dev/null
@@ -0,0 +1,357 @@
+/*************************************************************************
+*
+* Copyright 2019 highstreet technologies GmbH and others
+*
+* 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.
+***************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <inttypes.h>
+#include <time.h>
+#include <math.h>
+#include <sys/time.h>
+#include <cjson/cJSON.h>
+#include <string.h>
+#include <assert.h>
+
+#include "sysrepo.h"
+#include "sysrepo/values.h"
+#include "libyang/libyang.h"
+
+#include "utils.h"
+
+#define EMEM printf("Memory allocation failed (%s:%d)", __FILE__, __LINE__);
+
+static int op_set_srval(struct lyd_node *node, char *path, int dup, sr_val_t *val, char **val_buf)
+{
+    uint32_t i;
+    struct lyd_node_leaf_list *leaf;
+    const char *str;
+
+    if (!dup) {
+        assert(val_buf);
+        (*val_buf) = NULL;
+    }
+
+    if (!dup) {
+        val->xpath = path;
+    } else {
+        sr_val_set_xpath(val, path);
+    }
+    val->dflt = 0;
+    val->data.int64_val = 0;
+
+    switch (node->schema->nodetype) {
+    case LYS_CONTAINER:
+        val->type = ((struct lys_node_container *)node->schema)->presence ? SR_CONTAINER_PRESENCE_T : SR_CONTAINER_T;
+        break;
+    case LYS_LIST:
+        val->type = SR_LIST_T;
+        break;
+    case LYS_LEAF:
+    case LYS_LEAFLIST:
+        leaf = (struct lyd_node_leaf_list *)node;
+settype:
+        switch (leaf->value_type) {
+        case LY_TYPE_BINARY:
+            val->type = SR_BINARY_T;
+            str = leaf->value.binary;
+            if (dup) {
+                sr_val_set_str_data(val, val->type, str);
+            } else {
+                val->data.string_val = (char *)str;
+            }
+            if (NULL == val->data.binary_val) {
+                EMEM;
+                return -1;
+            }
+            break;
+        case LY_TYPE_BITS:
+            val->type = SR_BITS_T;
+            str = leaf->value_str;
+            if (dup) {
+                sr_val_set_str_data(val, val->type, str);
+            } else {
+                val->data.string_val = (char *)str;
+            }
+            break;
+        case LY_TYPE_BOOL:
+            val->type = SR_BOOL_T;
+            val->data.bool_val = leaf->value.bln;
+            break;
+        case LY_TYPE_DEC64:
+            val->type = SR_DECIMAL64_T;
+            val->data.decimal64_val = (double)leaf->value.dec64;
+            for (i = 0; i < ((struct lys_node_leaf *)leaf->schema)->type.info.dec64.dig; i++) {
+                /* shift decimal point */
+                val->data.decimal64_val *= 0.1;
+            }
+            break;
+        case LY_TYPE_EMPTY:
+            val->type = SR_LEAF_EMPTY_T;
+            break;
+        case LY_TYPE_ENUM:
+            val->type = SR_ENUM_T;
+            str = leaf->value.enm->name;
+            if (dup) {
+                sr_val_set_str_data(val, val->type, str);
+            } else {
+                val->data.string_val = (char *)str;
+            }
+            if (NULL == val->data.enum_val) {
+                EMEM;
+                return -1;
+            }
+            break;
+        case LY_TYPE_IDENT:
+            val->type = SR_IDENTITYREF_T;
+
+            str = malloc(strlen(lys_main_module(leaf->value.ident->module)->name) + 1 + strlen(leaf->value.ident->name) + 1);
+            if (NULL == str) {
+                EMEM;
+                return -1;
+            }
+            sprintf((char *)str, "%s:%s", lys_main_module(leaf->value.ident->module)->name, leaf->value.ident->name);
+            val->data.identityref_val = (char *)str;
+            if (!dup) {
+                (*val_buf) = (char *)str;
+            }
+            break;
+        case LY_TYPE_INST:
+            val->type = SR_INSTANCEID_T;
+            if (dup) {
+                sr_val_set_str_data(val, val->type, leaf->value_str);
+            } else {
+                val->data.string_val = (char *)leaf->value_str;
+            }
+            break;
+        case LY_TYPE_STRING:
+            val->type = SR_STRING_T;
+            str = leaf->value.string;
+            if (dup) {
+                sr_val_set_str_data(val, val->type, str);
+            } else {
+                val->data.string_val = (char *)str;
+            }
+            if (NULL == val->data.string_val) {
+                EMEM;
+                return -1;
+            }
+            break;
+        case LY_TYPE_INT8:
+            val->type = SR_INT8_T;
+            val->data.int8_val = leaf->value.int8;
+            break;
+        case LY_TYPE_UINT8:
+            val->type = SR_UINT8_T;
+            val->data.uint8_val = leaf->value.uint8;
+            break;
+        case LY_TYPE_INT16:
+            val->type = SR_INT16_T;
+            val->data.int16_val = leaf->value.int16;
+            break;
+        case LY_TYPE_UINT16:
+            val->type = SR_UINT16_T;
+            val->data.uint16_val = leaf->value.uint16;
+            break;
+        case LY_TYPE_INT32:
+            val->type = SR_INT32_T;
+            val->data.int32_val = leaf->value.int32;
+            break;
+        case LY_TYPE_UINT32:
+            val->type = SR_UINT32_T;
+            val->data.uint32_val = leaf->value.uint32;
+            break;
+        case LY_TYPE_INT64:
+            val->type = SR_INT64_T;
+            val->data.int64_val = leaf->value.int64;
+            break;
+        case LY_TYPE_UINT64:
+            val->type = SR_UINT64_T;
+            val->data.uint64_val = leaf->value.uint64;
+            break;
+        case LY_TYPE_LEAFREF:
+            leaf = (struct lyd_node_leaf_list *)leaf->value.leafref;
+            goto settype;
+        default:
+            //LY_DERIVED, LY_UNION
+            val->type = SR_UNKNOWN_T;
+            break;
+        }
+        break;
+    default:
+        val->type = SR_UNKNOWN_T;
+        break;
+    }
+
+    return 0;
+}
+
+static int op_add_srval(sr_val_t **values, size_t *values_cnt, struct lyd_node *node)
+{
+    char *path, *buf = NULL;
+    int ret;
+
+    if (sr_realloc_values(*values_cnt, *values_cnt + 1, values) != SR_ERR_OK) {
+        return -1;
+    }
+    ++(*values_cnt);
+
+    path = lyd_path(node);
+    ret = op_set_srval(node, path, 1, &(*values)[*values_cnt - 1], &buf);
+    free(path);
+    free(buf);
+
+    return ret;
+}
+
+
+static int send_dummy_notif(sr_session_ctx_t *sess, const char *module_name, const char *notif_object)
+{
+       int rc = SR_ERR_OK;
+
+    struct ly_ctx *ctx = NULL;
+    struct lyd_node *data = NULL, *iter = NULL;
+    sr_schema_t *schemas = NULL;
+    size_t schema_cnt = 0;
+    sr_val_t *vnotif = NULL;
+       size_t num_values= 0;
+
+    ctx = ly_ctx_new("/etc/sysrepo/yang", LY_CTX_ALLIMPLEMENTED);
+    if (!ctx) 
+    {
+       printf("Creating context failed...\n");
+       return SR_ERR_OPERATION_FAILED;
+    }
+
+    rc = sr_list_schemas(sess, &schemas, &schema_cnt);
+    if (rc != SR_ERR_OK)
+    {
+        printf("Could not list the schemas from the sysrepo session...\n");
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+    const char *schema_path = NULL;
+
+    for (size_t i = 0; i < schema_cnt; i++) 
+    {
+        schema_path = schemas[i].revision.file_path_yang;
+
+        if (NULL != schema_path && 0 == strcmp(module_name, schemas[i].module_name)) {
+            printf("Trying to install schema: %s\n", schema_path);
+            if (NULL == lys_parse_path(ctx, schema_path, LYS_IN_YANG)) 
+            {
+                fprintf(stderr, "Failed to parse schema file '%s': %s (%s)",
+                        schema_path, ly_errmsg(ctx), ly_errpath(ctx));
+                return SR_ERR_OPERATION_FAILED;
+                // continue;
+            }
+            break;
+        }
+    }
+
+    data = lyd_parse_mem(ctx, notif_object, LYD_JSON, LYD_OPT_NOTIF);
+    if (data == NULL)
+    {
+        printf("Could not create JSON object, not valid!\n");
+        return SR_ERR_VALIDATION_FAILED;
+    }
+
+    LY_TREE_FOR(data->child, iter) {
+        if (op_add_srval(&vnotif, &num_values, iter)) {
+            printf("Could not transform libyang into sysrepo values...\n");
+            return SR_ERR_OPERATION_FAILED;
+        }
+    }
+
+    if (num_values == 0)
+    {
+        printf("Could not generate objects for sending inside the notif...\n");
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+    rc = sr_event_notif_send(sess, lyd_path(data), vnotif, num_values, SR_EV_NOTIF_DEFAULT);
+    if (rc != SR_ERR_OK)
+    {
+        printf("Error: could not send notification...\n");
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+       return rc;
+}
+
+int
+main(int argc, char **argv)
+{
+    sr_conn_ctx_t *connection = NULL;
+    sr_session_ctx_t *session = NULL;
+    sr_subscription_ctx_t *subscription = NULL;
+    int rc = SR_ERR_OK;
+
+    setbuf(stdout, NULL);
+    setbuf(stderr, NULL);
+
+    const char *notif_object = NULL;
+    const char *module_name = NULL;
+
+    if (argc != 3) {
+        printf("%s <module_name> <notification-object>\n", argv[0]);
+        return EXIT_FAILURE;
+    }
+    module_name = argv[1];
+    notif_object = argv[2];
+
+    /* connect to sysrepo */
+    rc = sr_connect("generic_notifications", SR_CONN_DEFAULT, &connection);
+    if (SR_ERR_OK != rc) {
+        fprintf(stderr, "Error by sr_connect: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    /* start session */
+    rc = sr_session_start(connection, SR_DS_RUNNING, SR_SESS_DEFAULT, &session);
+    if (SR_ERR_OK != rc) {
+        fprintf(stderr, "Error by sr_session_start: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = send_dummy_notif(session, module_name, notif_object);
+    if (SR_ERR_OK != rc) {
+        fprintf(stderr, "Error by send_dummy_notif: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+    printf("Application exit reached, exiting.\n");
+
+    return 0;
+
+cleanup:
+    if (NULL != subscription) {
+        sr_unsubscribe(session, subscription);
+    }
+    if (NULL != session) {
+        sr_session_stop(session);
+    }
+    if (NULL != connection) {
+        sr_disconnect(connection);
+    }
+    printf("Error encountered. Exiting...");
+    return rc;
+}
+
+
+
+
index 165e7bb..4d8c2fb 100644 (file)
@@ -340,69 +340,103 @@ simulator_config_change_cb(sr_session_ctx_t *session, const char *module_name, s
        sr_free_val(val);
        val = NULL;
 
-       /* get the value from sysrepo, we do not care if the value did not change in our case */
-       rc = sr_get_item(session, "/network-topology-simulator:simulator-config/notification-config/is-ves-available", &val);
-       if (rc != SR_ERR_OK) {
-               goto sr_error;
-       }
+    /* get the value from sysrepo, we do not care if the value did not change in our case */
+    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/notification-config/is-ves-available", &val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
 
-       rc = is_ves_available_changed(val->data.bool_val);
-       if (rc != SR_ERR_OK) {
-               goto sr_error;
-       }
+    rc = is_ves_available_changed(val->data.bool_val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
 
-       sr_free_val(val);
-       val = NULL;
+    sr_free_val(val);
+    val = NULL;
 
     /* get the value from sysrepo, we do not care if the value did not change in our case */
-    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/ssh-connections", &val);
+    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/netconf-call-home", &val);
     if (rc != SR_ERR_OK) {
-        printf("NTS Manager /network-topology-simulator:simulator-config/ssh-connections object not available, ignoring..");
+        goto sr_error;
     }
-    else
-    {
-        rc = ssh_connections_changed(val->data.uint32_val);
-        if (rc != SR_ERR_OK) {
-            goto sr_error;
-        }
 
-        if (strcmp(getenv("K8S_DEPLOYMENT"), "true") == 0)
-        {
-            rc = send_k8s_extend_port();
-            if (rc != SR_ERR_OK)
-            {
-                printf("Could not send the extended port to k8s cluster.\n");
-            }
-        }
+    rc = netconf_call_home_changed(val->data.bool_val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
     }
 
     sr_free_val(val);
-       val = NULL;
+    val = NULL;
 
     /* get the value from sysrepo, we do not care if the value did not change in our case */
-    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/tls-connections", &val);
+    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/controller-details/controller-ip", &val);
     if (rc != SR_ERR_OK) {
-        printf("NTS Manager /network-topology-simulator:simulator-config/tls-connections object not available, ignoring..");
+        goto sr_error;
     }
-    else
-    {
-        rc = tls_connections_changed(val->data.uint32_val);
-        if (rc != SR_ERR_OK) {
-            goto sr_error;
-        }
 
-        if (strcmp(getenv("K8S_DEPLOYMENT"), "true") == 0)
-        {
-            rc = send_k8s_extend_port();
-            if (rc != SR_ERR_OK)
-            {
-                printf("Could not send the extended port to k8s cluster.\n");
-            }
-        }
+    rc = controller_ip_changed(val->data.string_val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
     }
 
     sr_free_val(val);
-       val = NULL;
+    val = NULL;
+
+    /* get the value from sysrepo, we do not care if the value did not change in our case */
+    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/controller-details/controller-port", &val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
+
+    rc = controller_port_changed(val->data.uint16_val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
+
+    sr_free_val(val);
+    val = NULL;
+
+    /* get the value from sysrepo, we do not care if the value did not change in our case */
+    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/controller-details/netconf-call-home-port", &val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
+
+    rc = controller_netconf_call_home_port_changed(val->data.uint16_val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
+
+    sr_free_val(val);
+    val = NULL;
+
+    /* get the value from sysrepo, we do not care if the value did not change in our case */
+    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/controller-details/controller-username", &val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
+
+    rc = controller_username_changed(val->data.string_val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
+
+    sr_free_val(val);
+    val = NULL;
+
+    /* get the value from sysrepo, we do not care if the value did not change in our case */
+    rc = sr_get_item(session, "/network-topology-simulator:simulator-config/controller-details/controller-password", &val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
+
+    rc = controller_password_changed(val->data.string_val);
+    if (rc != SR_ERR_OK) {
+        goto sr_error;
+    }
+
+    sr_free_val(val);
+    val = NULL;
 
     return SR_ERR_OK;
 
@@ -444,12 +478,12 @@ simulator_status_cb(const char *xpath, sr_val_t **values, size_t *values_cnt,
                        return SR_ERR_OK;
                }
 
-               rc = get_docker_containers_operational_state_curl(device_list);
-               if (rc != SR_ERR_OK)
-               {
-                       printf("Could not get the operational state for the devices simulated.\n");
-                       return SR_ERR_OPERATION_FAILED;
-               }
+        rc = get_docker_containers_operational_state_curl(device_list);
+        if (rc != SR_ERR_OK)
+        {
+            printf("Could not get the operational state for the devices simulated.\n");
+            return SR_ERR_OPERATION_FAILED;
+        }
 
                device_t *current_device = device_list->head;
 
@@ -605,6 +639,18 @@ simulator_status_cb(const char *xpath, sr_val_t **values, size_t *values_cnt,
                v[current_num_of_values - 1].type = SR_UINT32_T;
                v[current_num_of_values - 1].data.uint32_val = (int)mem_usage;
 
+        CREATE_NEW_VALUE(rc, v, current_num_of_values);
+
+        sr_val_build_xpath(&v[current_num_of_values - 1], "%s/%s", xpath, "ssh-connections");
+        v[current_num_of_values - 1].type = SR_UINT32_T;
+        v[current_num_of_values - 1].data.uint32_val = getSshConnectionsFromConfigJson();
+
+        CREATE_NEW_VALUE(rc, v, current_num_of_values);
+
+        sr_val_build_xpath(&v[current_num_of_values - 1], "%s/%s", xpath, "tls-connections");
+        v[current_num_of_values - 1].type = SR_UINT32_T;
+        v[current_num_of_values - 1].data.uint32_val = getTlsConnectionsFromConfigJson();
+
                //return the values that we have just created
                *values = v;
                *values_cnt = current_num_of_values;
@@ -693,29 +739,70 @@ simulator_status_cb(const char *xpath, sr_val_t **values, size_t *values_cnt,
     return SR_ERR_OK;
 }
 
-int odl_add_key_pair_cb(const char *xpath, const sr_val_t *input, const size_t input_cnt,
-               sr_val_t **output, size_t *output_cnt, void *private_ctx)
+static int odl_add_key_pair_cb(const char *xpath, const sr_val_t *input, const size_t input_cnt,
+      sr_val_t **output, size_t *output_cnt, void *private_ctx)
 {
-       int rc = SR_ERR_OK;
-       controller_t controller_list[CONTROLLER_LIST_MAX_LEN];
-       int controller_list_size = 0;
+    int rc = SR_ERR_OK;
+    controller_t controller_list[CONTROLLER_LIST_MAX_LEN];
+    int controller_list_size = 0;
 
-       controller_list[0] = controller_details;
-       controller_list_size++;
+    controller_list[0] = controller_details;
+    controller_list_size++;
 
-       for (int i = 0; i < controller_list_size; ++i)
-       {
-               printf("%d iteration: Got back url=%s and credentials=%s\n", i, controller_list[i].url, controller_list[i].credentials);
-       }
+    for (int i = 0; i < controller_list_size; ++i)
+    {
+        printf("%d iteration: Got back url=%s and credentials=%s\n", i, controller_list[i].url, controller_list[i].credentials);
+    }
 
-       rc = add_key_pair_to_odl(controller_list, controller_list_size);
-       if (rc != SR_ERR_OK)
-       {
-               printf("Failed to add key pair to ODL.\n");
-               return SR_ERR_OPERATION_FAILED;
-       }
+    rc = add_key_pair_to_odl(controller_list, controller_list_size);
+    if (rc != SR_ERR_OK)
+    {
+        printf("Failed to add key pair to ODL.\n");
+        return SR_ERR_OPERATION_FAILED;
+    }
 
-       return rc;
+    return rc;
+}
+
+static int invoke_notification_cb(const char *xpath, const sr_val_t *input, const size_t input_cnt,
+      sr_val_t **output, size_t *output_cnt, void *private_ctx)
+{
+    int rc = SR_ERR_OK;
+
+    char *device_name = NULL, *module_name = NULL, *notification_object = NULL;
+    
+    /* print input values */
+    printf("\n\n ========== RECEIVED RPC REQUEST ==========\n\n");
+    printf(">>> RPC Input:\n\n");
+    
+    device_name = strdup(input[0].data.string_val);
+    module_name = strdup(input[1].data.string_val);
+    notification_object = strdup(input[2].data.string_val);
+
+    rc = sr_new_values(1, output);
+    if (SR_ERR_OK != rc) {
+        return rc;
+    }
+
+    /* set 'output/step-count' leaf */
+    rc = sr_val_set_xpath(&(*output)[0], "/network-topology-simulator:invoke-notification/status");
+    if (SR_ERR_OK != rc) {
+        return rc;
+    }
+
+    rc = invoke_device_notification(device_name, module_name, notification_object);
+    
+    if (rc != SR_ERR_OK)
+    {
+        sr_val_build_str_data(&(*output)[0], SR_ENUM_T, "%s", "ERROR");
+    }
+    else
+    {
+        sr_val_build_str_data(&(*output)[0], SR_ENUM_T, "%s", "SUCCESS");
+    }
+    *output_cnt = 1;
+
+    return SR_ERR_OK;
 }
 
 
@@ -887,51 +974,154 @@ main(int argc, char **argv)
 
     int sshConnections = getIntFromString(getenv("SshConnections"), 1);
 
+    rc = ssh_connections_changed(sshConnections);
+    if (SR_ERR_OK != rc) {
+        printf("Error by ssh_connections_changed: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    // setting the values that come in an ENV variable as defaults - tls-connections
+
+    int tlsConnections = getIntFromString(getenv("TlsConnections"), 0);
+
+    rc = tls_connections_changed(tlsConnections);
+    if (SR_ERR_OK != rc) {
+        printf("Error by tls_connections_changed: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    if (strcmp(getenv("K8S_DEPLOYMENT"), "true") == 0)
+    {
+        rc = send_k8s_extend_port();
+        if (rc != SR_ERR_OK)
+        {
+            printf("Could not send the number of ports to k8s cluster\n");
+        }
+    }
+
+    // setting the values that come in an ENV variable as defaults - controller-ip
+
     value = (const sr_val_t) { 0 };
-    value.type = SR_UINT32_T;
-    value.data.uint32_val = sshConnections;
-    rc = sr_set_item(session, "/network-topology-simulator:simulator-config/ssh-connections",
+    value.type = SR_STRING_T;
+    value.data.string_val = getenv("ControllerIp");
+    rc = sr_set_item(session, "/network-topology-simulator:simulator-config/controller-details/controller-ip", 
             &value, SR_EDIT_DEFAULT);
     if (SR_ERR_OK != rc) {
         printf("Error by sr_set_item: %s\n", sr_strerror(rc));
         goto cleanup;
     }
 
-    rc = ssh_connections_changed(sshConnections);
+    rc = controller_ip_changed(getenv("ControllerIp"));
     if (SR_ERR_OK != rc) {
-        printf("Error by ssh_connections_changed: %s\n", sr_strerror(rc));
+        printf("Error by controller_ip_changed: %s\n", sr_strerror(rc));
         goto cleanup;
     }
 
-    // setting the values that come in an ENV variable as defaults - tls-connections
+    // setting the values that come in an ENV variable as defaults - controller-port
 
-    int tlsConnections = getIntFromString(getenv("TlsConnections"), 0);
+    int controllerPort = getIntFromString(getenv("ControllerPort"), 8181);
 
     value = (const sr_val_t) { 0 };
-    value.type = SR_UINT32_T;
-    value.data.uint32_val = tlsConnections;
-    rc = sr_set_item(session, "/network-topology-simulator:simulator-config/tls-connections",
+    value.type = SR_UINT16_T;
+    value.data.uint16_val = controllerPort;
+    rc = sr_set_item(session, "/network-topology-simulator:simulator-config/controller-details/controller-port", 
             &value, SR_EDIT_DEFAULT);
     if (SR_ERR_OK != rc) {
         printf("Error by sr_set_item: %s\n", sr_strerror(rc));
         goto cleanup;
     }
 
-    rc = tls_connections_changed(tlsConnections);
+    rc = controller_port_changed(controllerPort);
     if (SR_ERR_OK != rc) {
-        printf("Error by tls_connections_changed: %s\n", sr_strerror(rc));
+        printf("Error by controller_port_changed: %s\n", sr_strerror(rc));
         goto cleanup;
     }
 
-    if (strcmp(getenv("K8S_DEPLOYMENT"), "true") == 0)
+    // setting the values that come in an ENV variable as defaults - netconf-call-home-port
+
+    int netconfCallHomePort = getIntFromString(getenv("NetconfCallHomePort"), 6666);
+
+    value = (const sr_val_t) { 0 };
+    value.type = SR_UINT16_T;
+    value.data.uint16_val = netconfCallHomePort;
+    rc = sr_set_item(session, "/network-topology-simulator:simulator-config/controller-details/netconf-call-home-port", 
+            &value, SR_EDIT_DEFAULT);
+    if (SR_ERR_OK != rc) {
+        printf("Error by sr_set_item: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = controller_netconf_call_home_port_changed(netconfCallHomePort);
+    if (SR_ERR_OK != rc) {
+        printf("Error by controller_netconf_call_home_port_changed: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    // setting the values that come in an ENV variable as defaults - controller-username
+
+    value = (const sr_val_t) { 0 };
+    value.type = SR_STRING_T;
+    value.data.string_val = getenv("ControllerUsername");
+    rc = sr_set_item(session, "/network-topology-simulator:simulator-config/controller-details/controller-username", 
+            &value, SR_EDIT_DEFAULT);
+    if (SR_ERR_OK != rc) {
+        printf("Error by sr_set_item: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = controller_username_changed(getenv("ControllerUsername"));
+    if (SR_ERR_OK != rc) {
+        printf("Error by controller_username_changed: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    // setting the values that come in an ENV variable as defaults - controller-password
+
+    value = (const sr_val_t) { 0 };
+    value.type = SR_STRING_T;
+    value.data.string_val = getenv("ControllerPassword");
+    rc = sr_set_item(session, "/network-topology-simulator:simulator-config/controller-details/controller-password", 
+            &value, SR_EDIT_DEFAULT);
+    if (SR_ERR_OK != rc) {
+        printf("Error by sr_set_item: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = controller_password_changed(getenv("ControllerPassword"));
+    if (SR_ERR_OK != rc) {
+        printf("Error by controller_password_changed: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    // setting the values that come in an ENV variable as defaults - netconf-call-home
+
+    int netconfCallHome = 1;
+
+    char *netconfCallHomeString = getenv("NetconfCallHome");
+    if (netconfCallHomeString != NULL)
     {
-        rc = send_k8s_extend_port();
-        if (rc != SR_ERR_OK)
+        if (strcmp(netconfCallHomeString, "false") == 0)
         {
-            printf("Could not send the number of ports to k8s cluster\n");
+            netconfCallHome = 0;
         }
     }
 
+    value = (const sr_val_t) { 0 };
+    value.type = SR_BOOL_T;
+    value.data.bool_val = netconfCallHome;
+    rc = sr_set_item(session, "/network-topology-simulator:simulator-config/netconf-call-home", 
+            &value, SR_EDIT_DEFAULT);
+    if (SR_ERR_OK != rc) {
+        printf("Error by sr_set_item: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = netconf_call_home_changed(netconfCallHome);
+    if (SR_ERR_OK != rc) {
+        printf("Error by netconf_call_home_changed: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
     //commit the changes that we have done until now
     rc = sr_commit(session);
     if (SR_ERR_OK != rc) {
@@ -981,6 +1171,15 @@ main(int argc, char **argv)
         fprintf(stderr, "Could not initialize status JSON file: %s\n", sr_strerror(rc));
     }
 
+    rc = sr_rpc_subscribe(session, "/network-topology-simulator:invoke-notification", invoke_notification_cb,
+            (void *)session, SR_SUBSCR_DEFAULT, &subscription);
+    if (SR_ERR_OK != rc) {
+        fprintf(stderr, "Error by sr_rpc_subscribe: %s\n", sr_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = pull_docker_image_of_simulated_device();
+
     /* loop until ctrl-c is pressed / SIGINT is received */
     signal(SIGINT, sigint_handler);
     signal(SIGTERM, sigint_handler);
index 81736c3..928ace0 100644 (file)
@@ -21,6 +21,7 @@
 #include <string.h>
 #include <math.h>
 #include <linux/limits.h>
+#include <unistd.h>
 
 #include "utils.h"
 
@@ -231,7 +232,7 @@ static cJSON* get_docker_container_network_node(void)
     return NULL;
 }
 
-static char* create_docker_container_curl(int base_netconf_port, cJSON* managerBinds, cJSON* networkMode)
+static char* create_docker_container_curl(int base_netconf_port, cJSON* managerBinds, cJSON* networkMode, int device_number)
 {
     if (managerBinds == NULL)
     {
@@ -270,26 +271,35 @@ static char* create_docker_container_curl(int base_netconf_port, cJSON* managerB
     cJSON *postDataJson = cJSON_CreateObject();
 
     if (cJSON_AddStringToObject(postDataJson, "Image", models_var) == NULL)
-       {
-       printf("Could not create JSON object: Image\n");
-               return NULL;
-       }
+    {
+        printf("Could not create JSON object: Image\n");
+        return NULL;
+    }
+
+    char device_name[100];
+    sprintf(device_name, "%s-%d", getenv("CONTAINER_NAME"), device_number);
+
+    if (cJSON_AddStringToObject(postDataJson, "Hostname", device_name) == NULL)
+    {
+        printf("Could not create JSON object: Hostname\n");
+        return NULL;
+    }    
 
     cJSON *hostConfig = cJSON_CreateObject();
     if (hostConfig == NULL)
-       {
-       printf("Could not create JSON object: HostConfig\n");
-               return NULL;
-       }
+    {
+        printf("Could not create JSON object: HostConfig\n");
+        return NULL;
+    }
 
     cJSON_AddItemToObject(postDataJson, "HostConfig", hostConfig);
 
     cJSON *portBindings = cJSON_CreateObject();
     if (portBindings == NULL)
-       {
-       printf("Could not create JSON object: PortBindings\n");
-               return NULL;
-       }
+    {
+        printf("Could not create JSON object: PortBindings\n");
+        return NULL;
+    }
 
     cJSON_AddItemToObject(hostConfig, "PortBindings", portBindings);
 
@@ -400,6 +410,16 @@ static char* create_docker_container_curl(int base_netconf_port, cJSON* managerB
     }
     cJSON_AddItemToArray(env_variables_array, env_var_obj_4);
 
+    char ipv6_enabled[50];
+    sprintf(ipv6_enabled, "IPv6Enabled=%s", getenv("IPv6Enabled"));
+    cJSON *env_var_obj_5 = cJSON_CreateString(ipv6_enabled);
+    if (env_var_obj_5 == NULL)
+    {
+        printf("Could not create JSON object: Env array object IPv6Enabled\n");
+        return NULL;
+    }
+    cJSON_AddItemToArray(env_variables_array, env_var_obj_5);
+
     cJSON_AddItemToObject(hostConfig, "Binds", binds);
 
     cJSON_AddItemToObject(hostConfig, "NetworkMode", netMode);
@@ -725,7 +745,6 @@ static int send_unmount_device_instance(char *url, char *credentials, char *devi
                return SR_ERR_OPERATION_FAILED;
        }
 
-
        return SR_ERR_OK;
 }
 
@@ -736,11 +755,16 @@ static int send_mount_device(device_t *current_device, controller_t controller_d
        bool is_mounted = true;
     int port = 0;
 
+    char device_name[100];
+    sprintf(device_name, "%s-%d", getenv("CONTAINER_NAME"), current_device->device_number);
+
        //This is where we hardcoded: 7 devices will have SSH connections and 3 devices will have TLS connections
        for (int i = 0; i < SSH_CONNECTIONS_PER_DEVICE; ++port, ++i)
        {
+        
+
                rc = send_mount_device_instance_ssh(controller_details.url, controller_details.credentials,
-                               current_device->device_id, current_device->netconf_port + port);
+                               device_name, current_device->netconf_port + port);
                if (rc != SR_ERR_OK)
                {
                        is_mounted = false;
@@ -749,7 +773,7 @@ static int send_mount_device(device_t *current_device, controller_t controller_d
        for (int i = 0; i < TLS_CONNECTIONS_PER_DEVICE; ++port, ++i)
        {
                rc = send_mount_device_instance_tls(controller_details.url, controller_details.credentials,
-                               current_device->device_id, current_device->netconf_port + port);
+                               device_name, current_device->netconf_port + port);
                if (rc != SR_ERR_OK)
                {
                        is_mounted = false;
@@ -764,15 +788,17 @@ static int send_mount_device(device_t *current_device, controller_t controller_d
 static int send_unmount_device(device_t *current_device, controller_t controller_details)
 {
        int rc = SR_ERR_OK;
+    char device_name[100];
+    sprintf(device_name, "%s-%d", getenv("CONTAINER_NAME"), current_device->device_number);
 
        for (int port = 0; port < NETCONF_CONNECTIONS_PER_DEVICE; ++port)
        {
                rc = send_unmount_device_instance(controller_details.url, controller_details.credentials,
-                               current_device->device_id, current_device->netconf_port + port);
+                               device_name, current_device->netconf_port + port);
                if (rc != SR_ERR_OK)
                {
                        printf("Could not send unmount for ODL with url=\"%s\", for device=\"%s\" and port=%d\n",
-                                       controller_details.url, current_device->device_id, current_device->netconf_port);
+                                       controller_details.url, device_name, current_device->netconf_port);
                }
        }
        current_device->is_mounted = false;
@@ -997,7 +1023,7 @@ int start_device(device_stack_t *theStack)
        int netconf_base = get_netconf_port_next(theStack);
     int device_number = get_device_number_next(theStack);
 
-       char *dev_id = create_docker_container_curl(netconf_base, managerBindings, networkMode);
+       char *dev_id = create_docker_container_curl(netconf_base, managerBindings, networkMode, device_number);
     if (dev_id == NULL)
     {
         printf("ERROR: Could not create docker container!\n");
@@ -2124,5 +2150,661 @@ int send_k8s_scale(int number_of_devices)
         return SR_ERR_OPERATION_FAILED;
     }
 
+    return SR_ERR_OK;
+}
+
+int controller_ip_changed(char *new_ip)
+{
+       char *stringConfiguration = readConfigFileInString();
+
+       if (stringConfiguration == NULL)
+       {
+               printf("Could not read configuration file!\n");
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *jsonConfig = cJSON_Parse(stringConfiguration);
+       if (jsonConfig == NULL)
+       {
+               free(stringConfiguration);
+               const char *error_ptr = cJSON_GetErrorPtr();
+               if (error_ptr != NULL)
+               {
+                       fprintf(stderr, "Could not parse JSON configuration! Error before: %s\n", error_ptr);
+               }
+               return SR_ERR_OPERATION_FAILED;
+       }
+       //we don't need the string anymore
+       free(stringConfiguration);
+       stringConfiguration = NULL;
+
+       cJSON *controllerDetails = cJSON_GetObjectItemCaseSensitive(jsonConfig, "controller-details");
+       if (!cJSON_IsObject(controllerDetails))
+       {
+               printf("Configuration JSON is not as expected: controller-details is not an object");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *controllerIp = cJSON_GetObjectItemCaseSensitive(controllerDetails, "controller-ip");
+       if (!cJSON_IsString(controllerIp))
+       {
+               printf("Configuration JSON is not as expected: controller-ip is not a string");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       //we set the value of the fault-notification-delay-period object
+       cJSON_ReplaceItemInObject(controllerDetails, "controller-ip", cJSON_CreateString(new_ip));
+
+       //writing the new JSON to the configuration file
+       stringConfiguration = cJSON_Print(jsonConfig);
+       writeConfigFile(stringConfiguration);
+
+    if (stringConfiguration != NULL)
+    {
+        free(stringConfiguration);
+        stringConfiguration = NULL;
+    }
+
+       cJSON_Delete(jsonConfig);
+
+       return SR_ERR_OK;
+}
+
+int controller_port_changed(int new_port)
+{
+       char *stringConfiguration = readConfigFileInString();
+
+       if (stringConfiguration == NULL)
+       {
+               printf("Could not read configuration file!\n");
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *jsonConfig = cJSON_Parse(stringConfiguration);
+       if (jsonConfig == NULL)
+       {
+               free(stringConfiguration);
+               const char *error_ptr = cJSON_GetErrorPtr();
+               if (error_ptr != NULL)
+               {
+                       fprintf(stderr, "Could not parse JSON configuration! Error before: %s\n", error_ptr);
+               }
+               return SR_ERR_OPERATION_FAILED;
+       }
+       //we don't need the string anymore
+       free(stringConfiguration);
+       stringConfiguration = NULL;
+
+       cJSON *controllerDetails = cJSON_GetObjectItemCaseSensitive(jsonConfig, "controller-details");
+       if (!cJSON_IsObject(controllerDetails))
+       {
+               printf("Configuration JSON is not as expected: controller-details is not an object");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *controllerPort = cJSON_GetObjectItemCaseSensitive(controllerDetails, "controller-port");
+       if (!cJSON_IsNumber(controllerPort))
+       {
+               printf("Configuration JSON is not as expected: controller-port is not a number.");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       //we set the value of the fault-notification-delay-period object
+       cJSON_SetNumberValue(controllerPort, new_port);
+
+       //writing the new JSON to the configuration file
+       stringConfiguration = cJSON_Print(jsonConfig);
+       writeConfigFile(stringConfiguration);
+
+    if (stringConfiguration != NULL)
+    {
+        free(stringConfiguration);
+        stringConfiguration = NULL;
+    }
+
+       cJSON_Delete(jsonConfig);
+
+       return SR_ERR_OK;
+}
+
+int controller_netconf_call_home_port_changed(int new_port)
+{
+       char *stringConfiguration = readConfigFileInString();
+
+       if (stringConfiguration == NULL)
+       {
+               printf("Could not read configuration file!\n");
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *jsonConfig = cJSON_Parse(stringConfiguration);
+       if (jsonConfig == NULL)
+       {
+               free(stringConfiguration);
+               const char *error_ptr = cJSON_GetErrorPtr();
+               if (error_ptr != NULL)
+               {
+                       fprintf(stderr, "Could not parse JSON configuration! Error before: %s\n", error_ptr);
+               }
+               return SR_ERR_OPERATION_FAILED;
+       }
+       //we don't need the string anymore
+       free(stringConfiguration);
+       stringConfiguration = NULL;
+
+       cJSON *controllerDetails = cJSON_GetObjectItemCaseSensitive(jsonConfig, "controller-details");
+       if (!cJSON_IsObject(controllerDetails))
+       {
+               printf("Configuration JSON is not as expected: controller-details is not an object");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *netconfCallHomePort = cJSON_GetObjectItemCaseSensitive(controllerDetails, "netconf-call-home-port");
+       if (!cJSON_IsNumber(netconfCallHomePort))
+       {
+               printf("Configuration JSON is not as expected: netconf-call-home-port is not a number.");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       //we set the value of the fault-notification-delay-period object
+       cJSON_SetNumberValue(netconfCallHomePort, new_port);
+
+       //writing the new JSON to the configuration file
+       stringConfiguration = cJSON_Print(jsonConfig);
+       writeConfigFile(stringConfiguration);
+
+    if (stringConfiguration != NULL)
+    {
+        free(stringConfiguration);
+        stringConfiguration = NULL;
+    }
+
+       cJSON_Delete(jsonConfig);
+
+       return SR_ERR_OK;
+}
+
+int controller_username_changed(char *new_username)
+{
+       char *stringConfiguration = readConfigFileInString();
+
+       if (stringConfiguration == NULL)
+       {
+               printf("Could not read configuration file!\n");
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *jsonConfig = cJSON_Parse(stringConfiguration);
+       if (jsonConfig == NULL)
+       {
+               free(stringConfiguration);
+               const char *error_ptr = cJSON_GetErrorPtr();
+               if (error_ptr != NULL)
+               {
+                       fprintf(stderr, "Could not parse JSON configuration! Error before: %s\n", error_ptr);
+               }
+               return SR_ERR_OPERATION_FAILED;
+       }
+       //we don't need the string anymore
+       free(stringConfiguration);
+       stringConfiguration = NULL;
+
+       cJSON *controllerDetails = cJSON_GetObjectItemCaseSensitive(jsonConfig, "controller-details");
+       if (!cJSON_IsObject(controllerDetails))
+       {
+               printf("Configuration JSON is not as expected: controller-details is not an object");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *controllerUsername = cJSON_GetObjectItemCaseSensitive(controllerDetails, "controller-username");
+       if (!cJSON_IsString(controllerUsername))
+       {
+               printf("Configuration JSON is not as expected: controller-username is not a string");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       //we set the value of the fault-notification-delay-period object
+       cJSON_ReplaceItemInObject(controllerDetails, "controller-username", cJSON_CreateString(new_username));
+
+       //writing the new JSON to the configuration file
+       stringConfiguration = cJSON_Print(jsonConfig);
+       writeConfigFile(stringConfiguration);
+
+    if (stringConfiguration != NULL)
+    {
+        free(stringConfiguration);
+        stringConfiguration = NULL;
+    }
+
+       cJSON_Delete(jsonConfig);
+
+       return SR_ERR_OK;
+}
+
+int controller_password_changed(char *new_password)
+{
+       char *stringConfiguration = readConfigFileInString();
+
+       if (stringConfiguration == NULL)
+       {
+               printf("Could not read configuration file!\n");
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *jsonConfig = cJSON_Parse(stringConfiguration);
+       if (jsonConfig == NULL)
+       {
+               free(stringConfiguration);
+               const char *error_ptr = cJSON_GetErrorPtr();
+               if (error_ptr != NULL)
+               {
+                       fprintf(stderr, "Could not parse JSON configuration! Error before: %s\n", error_ptr);
+               }
+               return SR_ERR_OPERATION_FAILED;
+       }
+       //we don't need the string anymore
+       free(stringConfiguration);
+       stringConfiguration = NULL;
+
+       cJSON *controllerDetails = cJSON_GetObjectItemCaseSensitive(jsonConfig, "controller-details");
+       if (!cJSON_IsObject(controllerDetails))
+       {
+               printf("Configuration JSON is not as expected: controller-details is not an object");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *controllerPassword = cJSON_GetObjectItemCaseSensitive(controllerDetails, "controller-password");
+       if (!cJSON_IsString(controllerPassword))
+       {
+               printf("Configuration JSON is not as expected: controller-password is not a string");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       //we set the value of the fault-notification-delay-period object
+       cJSON_ReplaceItemInObject(controllerDetails, "controller-password", cJSON_CreateString(new_password));
+
+       //writing the new JSON to the configuration file
+       stringConfiguration = cJSON_Print(jsonConfig);
+       writeConfigFile(stringConfiguration);
+
+    if (stringConfiguration != NULL)
+    {
+        free(stringConfiguration);
+        stringConfiguration = NULL;
+    }
+
+       cJSON_Delete(jsonConfig);
+
+       return SR_ERR_OK;
+}
+
+int netconf_call_home_changed(cJSON_bool new_bool)
+{
+       char *stringConfiguration = readConfigFileInString();
+
+       if (stringConfiguration == NULL)
+       {
+               printf("Could not read configuration file!\n");
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       cJSON *jsonConfig = cJSON_Parse(stringConfiguration);
+       if (jsonConfig == NULL)
+       {
+               free(stringConfiguration);
+               const char *error_ptr = cJSON_GetErrorPtr();
+               if (error_ptr != NULL)
+               {
+                       fprintf(stderr, "Could not parse JSON configuration! Error before: %s\n", error_ptr);
+               }
+               return SR_ERR_OPERATION_FAILED;
+       }
+       //we don't need the string anymore
+       free(stringConfiguration);
+       stringConfiguration = NULL;
+
+       cJSON *netconfCallHome = cJSON_GetObjectItemCaseSensitive(jsonConfig, "netconf-call-home");
+       if (!cJSON_IsBool(netconfCallHome))
+       {
+               printf("Configuration JSON is not as expected: netconf-call-home is not a bool.");
+               cJSON_Delete(jsonConfig);
+               return SR_ERR_OPERATION_FAILED;
+       }
+
+       //we set the value of the ves-registration object
+       cJSON_ReplaceItemInObject(jsonConfig, "netconf-call-home", cJSON_CreateBool(new_bool));
+
+       //writing the new JSON to the configuration file
+       stringConfiguration = cJSON_Print(jsonConfig);
+       writeConfigFile(stringConfiguration);
+
+    if (stringConfiguration != NULL)
+    {
+        free(stringConfiguration);
+        stringConfiguration = NULL;
+    }
+
+       cJSON_Delete(jsonConfig);
+
+       return SR_ERR_OK;
+}
+
+static int start_device_notification(char *exec_id)
+{
+    struct MemoryStruct curl_response_mem;
+
+    curl_response_mem.memory = malloc(1);  /* will be grown as needed by the realloc above */
+    curl_response_mem.size = 0;    /* no data at this point */
+
+    CURLcode res;
+
+    curl_easy_reset(curl);
+    set_curl_common_info();
+
+    char url[100];
+    sprintf(url, "http:/v%s/exec/%s/start", getenv("DOCKER_ENGINE_VERSION"), exec_id);
+
+    curl_easy_setopt(curl, CURLOPT_URL, url);
+
+    cJSON *postDataJson = cJSON_CreateObject();
+
+    if (cJSON_AddFalseToObject(postDataJson, "Detach") == NULL)
+    {
+        printf("Could not create JSON object: Detach\n");
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+    if (cJSON_AddFalseToObject(postDataJson, "Tty") == NULL)
+    {
+        printf("Could not create JSON object: Tty\n");
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+    char *post_data_string = NULL;
+
+    post_data_string = cJSON_PrintUnformatted(postDataJson);
+
+    printf("Post data JSON:\n%s\n", post_data_string);
+
+    if (postDataJson != NULL)
+    {
+        cJSON_Delete(postDataJson);
+    }
+
+    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data_string);
+
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&curl_response_mem);
+
+    res = curl_easy_perform(curl);
+
+    if (post_data_string != NULL)
+    {
+        free(post_data_string);
+    }
+
+    if (res != CURLE_OK)
+    {
+        return SR_ERR_OPERATION_FAILED;
+    }
+    else
+    {
+        cJSON *json_response = cJSON_Parse(curl_response_mem.memory);
+        const cJSON *message = NULL;
+
+        printf("%lu bytes retrieved\n", (unsigned long)curl_response_mem.size);
+
+        message = cJSON_GetObjectItemCaseSensitive(json_response, "message");
+
+        if (cJSON_IsString(message) && (message->valuestring != NULL))
+        {
+            printf("Message: \"%s\"\n", message->valuestring);
+        }
+
+        cJSON_Delete(json_response);
+    }
+
+    return SR_ERR_OK;
+}
+
+static int inspect_device_notification_execution(char *exec_id)
+{
+    int rc = SR_ERR_OK;
+
+    struct MemoryStruct curl_response_mem;
+
+    curl_response_mem.memory = malloc(1);  /* will be grown as needed by the realloc above */
+    curl_response_mem.size = 0;    /* no data at this point */
+
+    CURLcode res;
+
+    curl_easy_reset(curl);
+    set_curl_common_info();
+
+    char url[100];
+    sprintf(url, "http:/v%s/exec/%s/json", getenv("DOCKER_ENGINE_VERSION"), exec_id);
+
+    curl_easy_setopt(curl, CURLOPT_URL, url);
+
+    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
+    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
+
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&curl_response_mem);
+
+    res = curl_easy_perform(curl);
+
+    if (res != CURLE_OK)
+    {
+        rc = SR_ERR_OPERATION_FAILED;
+    }
+    else
+    {
+        cJSON *json_response = cJSON_Parse(curl_response_mem.memory);
+        const cJSON *exit_code = NULL;
+
+        exit_code = cJSON_GetObjectItemCaseSensitive(json_response, "ExitCode");
+
+        if (cJSON_IsNumber(exit_code))
+        {
+            rc = exit_code->valueint;
+        }
+        else
+        {
+            printf("Exit code is not a number!\n");
+            rc = SR_ERR_OPERATION_FAILED;
+        }
+        
+        cJSON_Delete(json_response);
+    }
+
+    return rc;
+}
+
+int invoke_device_notification(char *device_id, char *module_name, char *notification_string)
+{
+    int rc = SR_ERR_OK;
+
+    printf("Device-name = %s\nModule-name = %s\nNotification-object = %s\n", device_id, module_name, notification_string);
+
+    struct MemoryStruct curl_response_mem;
+
+    curl_response_mem.memory = malloc(1);  /* will be grown as needed by the realloc above */
+    curl_response_mem.size = 0;    /* no data at this point */
+
+    CURLcode res;
+
+    curl_easy_reset(curl);
+    set_curl_common_info();
+
+    char url[100];
+    sprintf(url, "http:/v%s/containers/%s/exec", getenv("DOCKER_ENGINE_VERSION"), device_id);
+
+    curl_easy_setopt(curl, CURLOPT_URL, url);
+
+    cJSON *postDataJson = cJSON_CreateObject();
+
+    if (cJSON_AddFalseToObject(postDataJson, "AtttachStdin") == NULL)
+    {
+        printf("Could not create JSON object: AtttachStdin\n");
+        rc = SR_ERR_OPERATION_FAILED;
+        goto cleanup;
+    }
+
+    if (cJSON_AddTrueToObject(postDataJson, "AtttachStdout") == NULL)
+    {
+        printf("Could not create JSON object: AtttachStdout\n");
+        rc = SR_ERR_OPERATION_FAILED;
+        goto cleanup;
+    }
+
+    if (cJSON_AddTrueToObject(postDataJson, "AtttachStderr") == NULL)
+    {
+        printf("Could not create JSON object: AtttachStderr\n");
+        rc = SR_ERR_OPERATION_FAILED;
+        goto cleanup;
+    }
+
+    if (cJSON_AddTrueToObject(postDataJson, "Privileged") == NULL)
+    {
+        printf("Could not create JSON object: Privileged\n");
+        rc = SR_ERR_OPERATION_FAILED;
+        goto cleanup;
+    }
+
+    if (cJSON_AddStringToObject(postDataJson, "User", "root") == NULL)
+    {
+        printf("Could not create JSON object: User\n");
+        rc = SR_ERR_OPERATION_FAILED;
+        goto cleanup;
+    }
+
+    cJSON *cmd_array = cJSON_CreateArray();
+    if (cmd_array == NULL)
+    {
+        printf("Could not create JSON object: Cmd array\n");
+        rc = SR_ERR_OPERATION_FAILED;
+        goto cleanup;
+    }
+
+    cJSON_AddItemToObject(postDataJson, "Cmd", cmd_array);
+
+    cJSON *cmd_string_1 = cJSON_CreateString("sh");
+    cJSON_AddItemToArray(cmd_array, cmd_string_1);
+
+    cJSON *cmd_string_2 = cJSON_CreateString("-c");
+    cJSON_AddItemToArray(cmd_array, cmd_string_2);
+
+    char string_command[500];
+    sprintf(string_command, "/usr/local/bin/generic-notifications %s '%s'", module_name, notification_string);
+
+    cJSON *cmd_string_3 = cJSON_CreateString(string_command);
+    cJSON_AddItemToArray(cmd_array, cmd_string_3);
+
+    char *post_data_string = NULL;
+
+    post_data_string = cJSON_PrintUnformatted(postDataJson);
+
+    printf("Post data JSON:\n%s\n", post_data_string);
+
+    if (postDataJson != NULL)
+    {
+        cJSON_Delete(postDataJson);
+    }
+
+    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data_string);
+
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&curl_response_mem);
+
+    res = curl_easy_perform(curl);
+
+    if (post_data_string != NULL)
+    {
+        free(post_data_string);
+    }
+
+    if (res != CURLE_OK)
+    {
+        rc = SR_ERR_OPERATION_FAILED;
+        goto cleanup;
+    }
+    else
+    {
+        cJSON *json_response = cJSON_Parse(curl_response_mem.memory);
+        const cJSON *exec_id = NULL;
+
+        exec_id = cJSON_GetObjectItemCaseSensitive(json_response, "Id");
+
+        if (cJSON_IsString(exec_id) && (exec_id->valuestring != NULL))
+        {
+            printf("Exec id: \"%s\"\n", exec_id->valuestring);
+
+            rc = start_device_notification(exec_id->valuestring);
+            if (rc != SR_ERR_OK)
+            {
+                printf("Could not start the execution of the notification...\n");
+            }
+
+            sleep(1);
+
+            rc = inspect_device_notification_execution(exec_id->valuestring);
+        }
+
+        cJSON_Delete(json_response);
+    }
+
+cleanup:
+    if (device_id != NULL)
+    {
+        free(device_id);
+    }
+    if (module_name != NULL)
+    {
+        free(module_name);
+    }
+    if (notification_string != NULL)
+    {
+        free(notification_string);
+    }
+
+    return rc;
+}
+
+int pull_docker_image_of_simulated_device()
+{
+    struct MemoryStruct curl_response_mem;
+
+    curl_response_mem.memory = malloc(1);  /* will be grown as needed by the realloc above */
+    curl_response_mem.size = 0;    /* no data at this point */
+
+    CURLcode res;
+
+    curl_easy_reset(curl);
+    set_curl_common_info();
+
+    char url[100];
+    sprintf(url, "http:/v%s/images/create?fromImage=%s", getenv("DOCKER_ENGINE_VERSION"), getenv("MODELS_IMAGE"));
+
+    curl_easy_setopt(curl, CURLOPT_URL, url);
+
+    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
+
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&curl_response_mem);
+
+    res = curl_easy_perform(curl);
+
+    if (res != CURLE_OK)
+    {
+        return SR_ERR_OPERATION_FAILED;
+    }
+
     return SR_ERR_OK;
 }
\ No newline at end of file
index 3540eb3..364633c 100644 (file)
@@ -111,10 +111,21 @@ int is_ves_available_changed(cJSON_bool new_bool);
 int ssh_connections_changed(int number);
 int tls_connections_changed(int number);
 
+int controller_ip_changed(char *new_ip);
+int controller_port_changed(int new_port);
+int controller_netconf_call_home_port_changed(int new_port);
+int controller_username_changed(char *new_username);
+int controller_password_changed(char *new_password);
+int netconf_call_home_changed(cJSON_bool new_bool);
+
 
 int add_key_pair_to_odl(controller_t *controller_list, int controller_list_size);
 
 int send_k8s_scale(int number_of_devices);
 int send_k8s_extend_port(void);
 
+int invoke_device_notification(char *device_id, char *module_name, char *notification_string);
+
+int pull_docker_image_of_simulated_device(void);
+
 #endif /* SRC_NTSIMULATOR_MANAGER_SIMULATOR_OPERATIONS_H_ */
index d98dac0..7c7f56c 100644 (file)
@@ -2044,6 +2044,50 @@ int writeSkeletonConfigFile()
         return SR_ERR_OPERATION_FAILED;
     }
 
+    cJSON *controllerDetails = cJSON_CreateObject();
+    if (controllerDetails == NULL)
+    {
+        printf("Could not create JSON object: controllerDetails\n");
+        cJSON_Delete(configObject);
+        return SR_ERR_OPERATION_FAILED;
+    }
+    cJSON_AddItemToObject(configObject, "controller-details", controllerDetails);
+
+    if (cJSON_AddStringToObject(controllerDetails, "controller-ip", "172.17.0.1") == NULL)
+    {
+        printf("Could not create JSON object: controller-ip\n");
+        cJSON_Delete(configObject);
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+    if (cJSON_AddNumberToObject(controllerDetails, "controller-port", 8181) == NULL)
+    {
+        printf("Could not create JSON object: controller-port\n");
+        cJSON_Delete(configObject);
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+    if (cJSON_AddNumberToObject(controllerDetails, "netconf-call-home-port", 6666) == NULL)
+    {
+        printf("Could not create JSON object: netconf-call-home-port\n");
+        cJSON_Delete(configObject);
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+    if (cJSON_AddStringToObject(controllerDetails, "controller-username", "admin") == NULL)
+    {
+        printf("Could not create JSON object: controller-username\n");
+        cJSON_Delete(configObject);
+        return SR_ERR_OPERATION_FAILED;
+    }
+
+    if (cJSON_AddStringToObject(controllerDetails, "controller-password", "admin") == NULL)
+    {
+        printf("Could not create JSON object: controller-password\n");
+        cJSON_Delete(configObject);
+        return SR_ERR_OPERATION_FAILED;
+    }
+
     if (cJSON_AddNumberToObject(configObject, "ssh-connections", 1) == NULL)
     {
         printf("Could not create JSON object: ssh-connections\n");
@@ -2058,6 +2102,13 @@ int writeSkeletonConfigFile()
         return SR_ERR_OPERATION_FAILED;
     }
 
+    if (cJSON_AddFalseToObject(configObject, "netconf-call-home") == NULL)
+    {
+        printf("Could not create JSON object: netconf-call-home\n");
+        cJSON_Delete(configObject);
+        return SR_ERR_OPERATION_FAILED;
+    }
+
     char *config_string = NULL;
 
     config_string = cJSON_PrintUnformatted(configObject);
index 46fa4de..157d8ef 100644 (file)
@@ -1,24 +1,24 @@
 <simulator-config xmlns="urn:onf:params:xml:ns:yang:network-topology-simulator">
-       <simulated-devices>0</simulated-devices>
-       <mounted-devices>0</mounted-devices>
-    <ssh-connections>1</ssh-connections>
-    <tls-connections>0</tls-connections>
-       <notification-config>
-               <fault-notification-delay-period>0</fault-notification-delay-period>
-               <ves-heartbeat-period>0</ves-heartbeat-period>
-               <is-netconf-available>true</is-netconf-available>
-               <is-ves-available>true</is-ves-available>
-       </notification-config>
-       <controller-details>
-               <controller-ip>172.17.0.1</controller-ip>
-               <controller-port>8181</controller-port>
-               <controller-username>admin</controller-username>
-               <controller-password>admin</controller-password>
-       </controller-details>
-       <ves-endpoint-details>
-               <ves-endpoint-ip>172.17.0.1</ves-endpoint-ip>
-               <ves-endpoint-port>30007</ves-endpoint-port>
-               <ves-endpoint-auth-method>no-auth</ves-endpoint-auth-method>
-               <ves-registration>false</ves-registration>
-       </ves-endpoint-details>
+    <simulated-devices>0</simulated-devices>
+    <mounted-devices>0</mounted-devices>
+    <netconf-call-home>false</netconf-call-home>
+    <notification-config>
+        <fault-notification-delay-period>0</fault-notification-delay-period>
+        <ves-heartbeat-period>0</ves-heartbeat-period>
+        <is-netconf-available>true</is-netconf-available>
+        <is-ves-available>true</is-ves-available>
+    </notification-config>
+    <controller-details>
+        <controller-ip>172.17.0.1</controller-ip>
+        <controller-port>8181</controller-port>
+        <netconf-call-home-port>6666</netconf-call-home-port>
+        <controller-username>admin</controller-username>
+        <controller-password>admin</controller-password>
+    </controller-details>
+    <ves-endpoint-details>
+        <ves-endpoint-ip>172.17.0.1</ves-endpoint-ip>
+        <ves-endpoint-port>30007</ves-endpoint-port>
+        <ves-endpoint-auth-method>no-auth</ves-endpoint-auth-method>
+        <ves-registration>false</ves-registration>
+    </ves-endpoint-details>
 </simulator-config>
\ No newline at end of file
index 766a3ef..203c931 100644 (file)
@@ -16,6 +16,12 @@ module network-topology-simulator {
   description
     "This module contains a collection of YANG definitions for managing the Network Topology Simulator.";
 
+revision 2020-05-14 {
+    description
+      "New RPC for generic NETCONF notification generation.";
+    reference
+      "O-RAN SC SIM project";
+  }
   revision 2020-04-22 {
     description
       "Add configuration for number of SSH and TLS connections exposed by each simulated device.";
@@ -119,6 +125,11 @@ module network-topology-simulator {
       description
         "The Port of the SDN Controller.";
     }
+    leaf netconf-call-home-port {
+      type inet:port-number;
+      description
+        "The NETCONF Call Home Port of the SDN Controller.";      
+    }
     leaf controller-username {
       type string;
       description
@@ -194,10 +205,7 @@ module network-topology-simulator {
   }
 
   container simulator-config {
-    must "./ssh-connections + ./tls-connections <= 100" {
-      error-message "The total number of connections (SSH + TLS) needs to be less than or equal with 100.";
-    }
-    // presence "Enables NTS configuration.";
+    presence "Enables NTS configuration.";
     description
       "Configuration container of the simulator.";
     leaf simulated-devices {
@@ -215,25 +223,18 @@ module network-topology-simulator {
       description
         "The number of devices to be mounted in ODL. The configured number should not exceed the number of mounted devices.";
     }
-    leaf ssh-connections {
-      when "../simulated-devices = 0";
-      type uint32;
-      default "1";
-      description
-        "The number of SSH connections to be exposed by each simulated device.";
-    }
-    leaf tls-connections {
-    when "../simulated-devices = 0";
-      type uint32;
-      default "0";
-      description
-        "The number of TLS connections to be exposed by each simulated device.";
+    leaf netconf-call-home {
+        type boolean;
+        default "false";
+        description
+          "Configuration of NETCONF Call Home feature. If set to 'true', the device will use the SDN controller details and try to connect to it via Call Home.";
     }
     container notification-config {
       leaf-list fault-notification-delay-period {
         type uint32;
         min-elements 1;
         max-elements 99;
+        ordered-by user;
         description
           "Defines a pattern of generating notifications, represented by the amount of time between two generated fault notifications. The pattern is circular, it restarts at the beginning after all the leaf-list items are being used.
           E.g.: if the leaf-list contains three items: 4, 8 and 10, then a fault-notification will be generated, then after 4 seconds a new notification will be generaed, then after 8 seconds, then after 10 seconds, then again after 4 seconds etc.";
@@ -326,6 +327,16 @@ module network-topology-simulator {
         description
           "The current number of running simulated devices that are mounted in ODL.";
       }
+      leaf ssh-connections {
+        type uint32;
+        description
+          "The number of SSH connections that are exposed by each simulated device. This can be set through the ENV variable SshConnections when starting the NTS Manager.";
+      }
+      leaf tls-connections {
+        type uint32;
+        description
+          "The number of TLS connections that are exposed by each simulated device. This can be set through the ENV variable TlsConnections when starting the NTS Manager.";
+      }
       leaf base-netconf-port {
         type uint32;
         default "30000";
@@ -392,6 +403,29 @@ module network-topology-simulator {
       "State data container of the simulator.";
   }
 
+  grouping notification-job-g {
+      leaf device-id {
+        type string;
+        mandatory true;
+        description
+          "Device to invoke the NETCONF notification. If it is not present, all simulated devices would invoke the notification.";
+      }
+      leaf yang-module-name {
+        type string;
+        mandatory true;
+        description
+          "Name of the YANG module containing the notification to be invoked.";
+      }
+      leaf notification-object {
+          type string;
+          mandatory true;
+          description
+            "A JSON string containing the notification object to be sent by the device.";
+      }
+      description
+        "Notification job object class.";
+  }
+
   rpc restart-simulation {
     description
       "Operation to restart all the simulated devices with the new configuration of the simulator.";
@@ -401,4 +435,29 @@ module network-topology-simulator {
     description
       "Operation to add a key pair signed by the NTS to OpenDaylight, such that it can connect to devices using TLS.";
   }
+
+  rpc invoke-notification {
+    description
+      "Operation to generate a NETCONF notification from a device.";
+    input {
+      uses notification-job-g;
+    }
+    output {
+      leaf status {
+          type enumeration {
+            enum SUCCESS {
+            description
+                "This means that the RPC was successfully invoked.";
+            }
+            enum ERROR {
+            description
+                "This means that an error was encountered when invoking the notification.";
+            }
+          }
+        mandatory true;
+        description
+          "The status of the RPC.";
+      }
+    }
+  }
 }
index 71ba5d4..379ea70 100755 (executable)
@@ -20,9 +20,21 @@ sleep 20
 
 echo "Loading data into sysrepo..."
 
+result=$(netopeer2-cli <<-END
+       knownhosts --del 0
+    knownhosts --del 0
+END
+)
+
+SSH_PUB_KEY_MELACON="$(cat /home/netconf/.ssh/melacon.server.key.pub)"
+
+echo "Writing the public key to the known_hosts..."
+echo "[127.0.0.1]:830 $SSH_PUB_KEY_MELACON" > /root/.ssh/known_hosts
+
 pyang -f sample-xml-skeleton --sample-xml-list-entries 2 *.yang
 
 result=$(netopeer2-cli <<-END
+    auth keys add /home/netconf/.ssh/melacon.server.key.pub /home/netconf/.ssh/melacon.server.key
        connect --host 127.0.0.1 --login netconf
        user-rpc --content=/opt/dev/yang/edit_config_operation.xml
        disconnect
@@ -48,6 +60,4 @@ echo "Finished loading data into sysrepo. Removing edit-config XML..."
 rm -f /opt/dev/yang/edit_config_operation.xml
 
 echo "Done..."
-
-
 exit 0
\ No newline at end of file