From 3ecb7b39473c5ea6835fcbdd57b91acb74781a52 Mon Sep 17 00:00:00 2001 From: ebo Date: Thu, 27 Feb 2020 14:04:23 +0000 Subject: Add NETCONF PNF Simulator Engine Issue-ID: INT-1124 Signed-off-by: ebo Change-Id: Ifb50a749992cbd662d579e1cb861bd8f55b3f808 --- test/mocks/netconf-pnp-simulator/docs/README.md | 63 ++++++++++ .../docs/examples/mynetconf/data.json | 10 ++ .../docs/examples/mynetconf/docker-compose.yml | 12 ++ .../docs/examples/mynetconf/model.yang | 29 +++++ .../docs/examples/mynetconf/subscriber.py | 136 +++++++++++++++++++++ .../docs/images/Architecture.png | Bin 0 -> 58061 bytes 6 files changed, 250 insertions(+) create mode 100644 test/mocks/netconf-pnp-simulator/docs/README.md create mode 100644 test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/data.json create mode 100644 test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/docker-compose.yml create mode 100644 test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/model.yang create mode 100755 test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/subscriber.py create mode 100644 test/mocks/netconf-pnp-simulator/docs/images/Architecture.png (limited to 'test/mocks/netconf-pnp-simulator/docs') diff --git a/test/mocks/netconf-pnp-simulator/docs/README.md b/test/mocks/netconf-pnp-simulator/docs/README.md new file mode 100644 index 000000000..8aa24504f --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/docs/README.md @@ -0,0 +1,63 @@ +# NETCONF Plug-and-Play Simulator + +[![GitHub Tag][gh-tag-badge]]() +[![Docker Automated Build][dockerhub-badge]][dockerhub] + +## Overview + +This project builds a modular engine that allows the creation of NETCONF-enabled devices simulators, +either physical (PNF) and virtual (VNF). + +Basically it's a docker container running Sysrepo and Netopeer2 servers enhanced with a plugger script that, at +start-time, performs the following actions: + +1. Configures TLS and SSH secure accesses to the Netopeer2 server; +2. Installs multiple YANG models into sysrepo datastore; +3. Launches the corresponding subscriber applications. + +The picture below unveils the architecture of this solution. + +![Architecture](images/Architecture.png) + +A YANG module contains the following files: + +| Filename | Purpose +| -------- | ------- +|`model.yang` | The YANG model specified according to [RFC-6020][yang-rfc]. Alternatively, you can use your model's name as a basename for this file. Example: `mynetconf.yang`. +|`data.json` or `data.xml` | An optional data file used to initialize your model. +|`subscriber.py` | The Python 3 application that implements the behavioral aspects of the YANG model. +|`requirements.txt` | [Optional] Additional Python packages specified in the [Requirements File Format][py-requirements]. + +## Application + +The `subscriber` is free to implement any wanted passive or active behaviour: + +**Passive Behaviour**: The subscriber will receive an event for each modification externally applied to the YANG model. + +**Active Behaviour**: At any point in time the subscriber can proactively change its own YANG model. + +## Runtime Configuration + +### Customizing TLS and SSH accesses + +The distributed docker image comes with a sample configuration for TLS and SSH, that can be found at +`/config/tls` and `/home/netconf/.ssh` directories respectively. The user can replace one or both configurations +by mounting a custom directory under the respective TLS or SSH mounting point. + +### Python Virtual Environment Support + +Python programs usually use additional packages not included in the standard Python distribution, +like the `requests` package, for example. +We support this scenario by creating isolated Python environments for each custom-provided module whenever +a `requirements.txt` file is present in the module directory. + +## Example Module + +The directory `examples/mynetconf` contains an example YANG model and its subscriber along with a +Docker Compose configuration file to launch a basic simulator. + +[dockerhub]: https://hub.docker.com/r/blueonap/netconf-pnp-simulator/ +[dockerhub-badge]: https://img.shields.io/docker/cloud/automated/blueonap/netconf-pnp-simulator +[gh-tag-badge]: https://img.shields.io/github/v/tag/blue-onap/netconf-pnp-simulator?label=Release +[py-requirements]: https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format +[yang-rfc]: https://tools.ietf.org/html/rfc6020 diff --git a/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/data.json b/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/data.json new file mode 100644 index 000000000..63872eef9 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/data.json @@ -0,0 +1,10 @@ +{ + "mynetconf:netconflist": { + "netconf": [ + { + "netconf-id": 3, + "netconf-param": 3 + } + ] + } +} diff --git a/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/docker-compose.yml b/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/docker-compose.yml new file mode 100644 index 000000000..ee70c4fd9 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3' + +services: + netopeer2: + image: nexus3.onap.org:10001/onap/integration/simulators/netconf-pnp-simulator:2.6.0 + container_name: mynetconf + restart: always + ports: + - "830:830" + - "6513:6513" + volumes: + - ./:/config/modules/mynetconf diff --git a/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/model.yang b/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/model.yang new file mode 100644 index 000000000..6c8c36ab0 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/model.yang @@ -0,0 +1,29 @@ +module mynetconf { + yang-version 1.1; + namespace "urn:mynetconf:test"; + + prefix nft; + + organization + "mynetconf"; + contact + "my netconf address"; + description + "yang model for mynetconf"; + revision "2019-03-01" { + description + "initial version"; + } + + container netconflist { + list netconf { + key netconf-id; + leaf netconf-id { + type uint16; + } + leaf netconf-param { + type uint32; + } + } + } +} diff --git a/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/subscriber.py b/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/subscriber.py new file mode 100755 index 000000000..612729675 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/docs/examples/mynetconf/subscriber.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 + +__author__ = "Mislav Novakovic " +__copyright__ = "Copyright 2018, Deutsche Telekom AG" +__license__ = "Apache 2.0" + +# 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. + +# This sample application demonstrates use of Python programming language bindings for sysrepo library. +# Original c application was rewritten in Python to show similarities and differences +# between the two. +# +# Most notable difference is in the very different nature of languages, c is weakly statically typed language +# while Python is strongly dynamically typed. Python code is much easier to read and logic easier to comprehend +# for smaller scripts. Memory safety is not an issue but lower performance can be expected. +# +# The original c implementation is also available in the source, so one can refer to it to evaluate trade-offs. + +import sysrepo as sr +import sys + + +# Helper function for printing changes given operation, old and new value. +def print_change(op, old_val, new_val): + if op == sr.SR_OP_CREATED: + print(f"CREATED: {new_val.to_string()}") + elif op == sr.SR_OP_DELETED: + print(f"DELETED: {old_val.to_string()}") + elif op == sr.SR_OP_MODIFIED: + print(f"MODIFIED: {old_val.to_string()} to {new_val.to_string()}") + elif op == sr.SR_OP_MOVED: + print(f"MOVED: {new_val.xpath()} after {old_val.xpath()}") + + +# Helper function for printing events. +def ev_to_str(ev): + if ev == sr.SR_EV_VERIFY: + return "verify" + elif ev == sr.SR_EV_APPLY: + return "apply" + elif ev == sr.SR_EV_ABORT: + return "abort" + else: + return "unknown" + + +# Function to print current configuration state. +# It does so by loading all the items of a session and printing them out. +def print_current_config(session, module_name): + select_xpath = f"/{module_name}:*//*" + + values = session.get_items(select_xpath) + + if values is not None: + print("========== BEGIN CONFIG ==========") + for i in range(values.val_cnt()): + print(values.val(i).to_string(), end='') + print("=========== END CONFIG ===========") + + +# Function to be called for subscribed client of given session whenever configuration changes. +def module_change_cb(sess, module_name, event, private_ctx): + try: + print("========== Notification " + ev_to_str(event) + " =============================================") + if event == sr.SR_EV_APPLY: + print_current_config(sess, module_name) + + print("========== CHANGES: =============================================") + + change_path = f"/{module_name}:*" + + it = sess.get_changes_iter(change_path) + + while True: + change = sess.get_change_next(it) + if change is None: + break + print_change(change.oper(), change.old_val(), change.new_val()) + + print("========== END OF CHANGES =======================================") + except Exception as e: + print(e) + + return sr.SR_ERR_OK + + +def main(): + # Notable difference between c implementation is using exception mechanism for open handling unexpected events. + # Here it is useful because `Connection`, `Session` and `Subscribe` could throw an exception. + try: + module_name = "ietf-interfaces" + if len(sys.argv) > 1: + module_name = sys.argv[1] + else: + print("\nYou can pass the module name to be subscribed as the first argument") + + print(f"Application will watch for changes in {module_name}") + + # connect to sysrepo + conn = sr.Connection(module_name) + + # start session + sess = sr.Session(conn) + + # subscribe for changes in running config */ + subscribe = sr.Subscribe(sess) + + subscribe.module_change_subscribe(module_name, module_change_cb) + + try: + print_current_config(sess, module_name) + except Exception as e: + print(e) + + print("========== STARTUP CONFIG APPLIED AS RUNNING ==========") + + sr.global_loop() + + print("Application exit requested, exiting.") + + except Exception as e: + print(e) + + +if __name__ == '__main__': + main() diff --git a/test/mocks/netconf-pnp-simulator/docs/images/Architecture.png b/test/mocks/netconf-pnp-simulator/docs/images/Architecture.png new file mode 100644 index 000000000..da95c9142 Binary files /dev/null and b/test/mocks/netconf-pnp-simulator/docs/images/Architecture.png differ -- cgit 1.2.3-korg