diff options
Diffstat (limited to 'test')
23 files changed, 872 insertions, 0 deletions
diff --git a/test/mocks/netconf-pnp-simulator/README.md b/test/mocks/netconf-pnp-simulator/README.md new file mode 100644 index 000000000..df3211844 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/README.md @@ -0,0 +1,9 @@ +# NETCONF Plug-and-Play Simulator + +Instead of a single docker image aggregating all Yang models and simulation logic, this simulator uses a modular +approach that is reflected on this directory structure: + +- engine: Contains only the core NETCONF engine and files required to build the + docker image; +- modules: The modules containing the Yang models and its corresponding + applications goes here. 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 <mislav.novakovic@sartura.hr>" +__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 Binary files differnew file mode 100644 index 000000000..da95c9142 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/docs/images/Architecture.png diff --git a/test/mocks/netconf-pnp-simulator/engine/Dockerfile b/test/mocks/netconf-pnp-simulator/engine/Dockerfile new file mode 100644 index 000000000..5432b646a --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/Dockerfile @@ -0,0 +1,179 @@ +#- +# ============LICENSE_START======================================================= +# Copyright (C) 2020 Nordix Foundation. +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +FROM python:3.7.6-alpine3.11 as build + +ARG libyang_version=v1.0-r5 +ARG sysrepo_version=v0.7.9 +ARG libnetconf2_version=v0.12-r2 +ARG netopeer2_version=v0.7-r2 + +WORKDIR /usr/src + +RUN set -eux \ + && apk add \ + autoconf \ + bash \ + build-base \ + cmake \ + curl-dev \ + file \ + git \ + libev-dev \ + openssh-keygen \ + openssl \ + openssl-dev \ + pcre-dev \ + pkgconfig \ + protobuf-c-dev \ + swig \ + # for troubleshooting + the_silver_searcher \ + vim \ + # v0.9.3 has somes bugs as warned in libnetconf2/CMakeLists.txt:237 + && apk add --repository http://dl-cdn.alpinelinux.org/alpine/v3.10/main libssh-dev==0.8.8-r0 + +RUN git config --global advice.detachedHead false + +ENV PKG_CONFIG_PATH=/opt/lib64/pkgconfig +ENV LD_LIBRARY_PATH=/opt/lib:/opt/lib64 + + +# libyang +COPY patches/libyang/ ./patches/libyang/ +RUN set -eux \ + && git clone --branch $libyang_version --depth 1 https://github.com/CESNET/libyang.git \ + && cd libyang \ + && for p in ../patches/libyang/*.patch; do patch -p1 -i $p; done \ + && mkdir build && cd build \ + && cmake -DCMAKE_BUILD_TYPE:String="Release" -DENABLE_BUILD_TESTS=OFF \ + -DCMAKE_INSTALL_PREFIX:PATH=/opt \ + -DGEN_LANGUAGE_BINDINGS=ON \ + -DPYTHON_MODULE_PATH:PATH=/opt/lib/python3.7/site-packages \ + .. \ + && make -j2 \ + && make install + +RUN set -eux \ + && git clone --depth 1 https://github.com/sysrepo/libredblack.git \ + && cd libredblack \ + && ./configure --prefix=/opt --without-rbgen \ + && make \ + && make install + +# sysrepo +COPY patches/sysrepo/ ./patches/sysrepo/ +RUN set -eux \ + && git clone --branch $sysrepo_version --depth 1 https://github.com/sysrepo/sysrepo.git \ + && cd sysrepo \ + && for p in ../patches/sysrepo/*.patch; do patch -p1 -i $p; done \ + && mkdir build && cd build \ + && cmake -DCMAKE_BUILD_TYPE:String="Release" -DENABLE_TESTS=OFF \ + -DREPOSITORY_LOC:PATH=/opt/etc/sysrepo \ + -DCMAKE_INSTALL_PREFIX:PATH=/opt \ + -DGEN_PYTHON_VERSION=3 \ + -DPYTHON_MODULE_PATH:PATH=/opt/lib/python3.7/site-packages \ + .. \ + && make -j2 \ + && make install + +# libnetconf2 +COPY patches/libnetconf2/ ./patches/libnetconf2/ +RUN set -eux \ + && git clone --branch $libnetconf2_version --depth 1 https://github.com/CESNET/libnetconf2.git \ + && cd libnetconf2 \ + && for p in ../patches/libnetconf2/*.patch; do patch -p1 -i $p; done \ + && mkdir build && cd build \ + && cmake -DCMAKE_BUILD_TYPE:String="Release" -DENABLE_BUILD_TESTS=OFF \ + -DCMAKE_INSTALL_PREFIX:PATH=/opt \ + -DENABLE_PYTHON=ON \ + -DPYTHON_MODULE_PATH:PATH=/opt/lib/python3.7/site-packages \ + .. \ + && make \ + && make install + +# keystore +COPY patches/Netopeer2/ ./patches/Netopeer2/ +RUN set -eux \ + && git clone --branch $netopeer2_version --depth 1 https://github.com/CESNET/Netopeer2.git \ + && cd Netopeer2 \ + && for p in ../patches/Netopeer2/*.patch; do patch -p1 -i $p; done \ + && cd keystored \ + && mkdir build && cd build \ + && cmake -DCMAKE_BUILD_TYPE:String="Release" \ + -DCMAKE_INSTALL_PREFIX:PATH=/opt \ + .. \ + && make -j2 \ + && make install + +# netopeer2 +RUN set -eux \ + && cd Netopeer2/server \ + && mkdir build && cd build \ + && cmake -DCMAKE_BUILD_TYPE:String="Release" \ + -DCMAKE_INSTALL_PREFIX:PATH=/opt \ + .. \ + && make -j2 \ + && make install + +FROM python:3.7.6-alpine3.11 +LABEL authors="eliezio.oliveira@est.tech" + +RUN set -eux \ + && pip install supervisor \ + && apk update \ + && apk upgrade -a \ + && apk add \ + libcurl \ + libev \ + openssh-keygen \ + pcre \ + protobuf-c \ + # v0.9.3 has somes bugs as warned in libnetconf2/CMakeLists.txt:237 + && apk add --repository http://dl-cdn.alpinelinux.org/alpine/v3.10/main libssh==0.8.8-r0 \ + && rm -rf /var/cache/apk/* + +COPY --from=build /opt/ /opt/ + +ENV LD_LIBRARY_PATH=/opt/lib:/opt/lib64 + +COPY config/ /config +VOLUME /config + +# finish setup and add netconf user +RUN adduser --system --disabled-password --gecos 'Netconf User' netconf + +ENV HOME=/home/netconf +VOLUME $HOME/.local/share/virtualenvs + +# generate ssh keys for netconf user +RUN set -eux \ + && mkdir -p $HOME/.cache \ + && mkdir -p $HOME/.ssh \ + && ssh-keygen -t dsa -P '' -f $HOME/.ssh/id_dsa \ + && cat $HOME/.ssh/id_dsa.pub > $HOME/.ssh/authorized_keys + +EXPOSE 830 + +COPY supervisord.conf /etc/supervisord.conf +RUN mkdir /etc/supervisord.d + +COPY entrypoint.sh /opt/bin/ + +CMD /opt/bin/entrypoint.sh diff --git a/test/mocks/netconf-pnp-simulator/engine/LICENSE b/test/mocks/netconf-pnp-simulator/engine/LICENSE new file mode 100644 index 000000000..c6aae559e --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/LICENSE @@ -0,0 +1,13 @@ +Copyright (C) 2020 Nordix Foundation + +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. diff --git a/test/mocks/netconf-pnp-simulator/engine/config/modules/.gitkeep b/test/mocks/netconf-pnp-simulator/engine/config/modules/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/config/modules/.gitkeep diff --git a/test/mocks/netconf-pnp-simulator/engine/config/tls/load_server_certs.xml b/test/mocks/netconf-pnp-simulator/engine/config/tls/load_server_certs.xml new file mode 100644 index 000000000..8872a8edb --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/config/tls/load_server_certs.xml @@ -0,0 +1,62 @@ +<keystore xmlns="urn:ietf:params:xml:ns:yang:ietf-keystore"> + <private-keys> + <private-key> + <name>server_key</name> + <certificate-chains> + <certificate-chain> + <name>server_cert</name> + <certificate>MIIECTCCAvGgAwIBAgIBCDANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCQ1ox +FjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoM +BkNFU05FVDEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJ +KoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTE1MDczMDA3MjU1MFoX +DTM1MDcyNTA3MjU1MFowgYUxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBN +b3JhdmlhMQ8wDQYDVQQKDAZDRVNORVQxDDAKBgNVBAsMA1RNQzEXMBUGA1UEAwwO +ZXhhbXBsZSBzZXJ2ZXIxJjAkBgkqhkiG9w0BCQEWF2V4YW1wbGVzZXJ2ZXJAbG9j +YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdI1TBjzX1Pg +QXFuPCw5/kQwU7qkrhirMcFAXhI8EoXepPa9fKAVuMjHW32P6nNzDpnhFe0YGdNl +oIEN3hJJ87cVOqj4o7zZMbq3zVG2L8As7MTA8tYXm2fSC/0rIxxRRemcGUXM0q+4 +LEACjZj2pOKonaivF5VbhgNjPCO1Jj/TamUc0aViE577C9L9EiObGM+bGbabWk/K +WKLsvxUc+sKZXaJ7psTVgpggJAkUszlmwOQgFiMSR53E9/CAkQYhzGVCmH44Vs6H +zs3RZjOTbce4wr4ongiA5LbPeSNSCFjy9loKpaE1rtOjkNBVdiNPCQTmLuODXUTK +gkeL+9v/OwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu +U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU83qEtQDFzDvLoaII +vqiU6k7j1uswHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gwDQYJKoZI +hvcNAQELBQADggEBAJ+QOLi4gPWGofMkLTqSsbv5xRvTw0xa/sJnEeiejtygAu3o +McAsyevSH9EYVPCANxzISPzd9SFaO56HxWgcxLn9vi8ZNvo2wIp9zucNu285ced1 +K/2nDZfBmvBxXnj/n7spwqOyuoIc8sR7P7YyI806Qsfhk3ybNZE5UHJFZKDRQKvR +J1t4nk9saeo87kIuNEDfYNdwYZzRfXoGJ5qIJQK+uJJv9noaIhfFowDW/G14Ji5p +Vh/YtvnOPh7aBjOj8jmzk8MqzK+TZgT7GWu48Nd/NaV8g/DNg9hlN047LaNsJly3 +NX3+VBlpMnA4rKwl1OnmYSirIVh9RJqNwqe6k/k=</certificate> + </certificate-chain> + </certificate-chains> + </private-key> + </private-keys> + <trusted-certificates> + <name>trusted_ca_list</name> + <trusted-certificate> + <name>ca</name> + <certificate>MIID7TCCAtWgAwIBAgIJAMtE1NGAR5KoMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD +VQQGEwJDWjEWMBQGA1UECAwNU291dGggTW9yYXZpYTENMAsGA1UEBwwEQnJubzEP +MA0GA1UECgwGQ0VTTkVUMQwwCgYDVQQLDANUTUMxEzARBgNVBAMMCmV4YW1wbGUg +Q0ExIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVjYUBsb2NhbGhvc3QwHhcNMTQwNzI0 +MTQxOTAyWhcNMjQwNzIxMTQxOTAyWjCBjDELMAkGA1UEBhMCQ1oxFjAUBgNVBAgM +DVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoMBkNFU05FVDEM +MAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJKoZIhvcNAQkB +FhNleGFtcGxlY2FAbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArD3TDHPAMT2Z84orK4lMlarbgooIUCcRZyLe+QM+8KY8Hn+mGaxPEOTS +L3ywszqefB/Utm2hPKLHX684iRC14ID9WDGHxPjvoPArhgFhfV+qnPfxKTgxZC12 +uOj4u1V9y+SkTCocFbRfXVBGpojrBuDHXkDMDEWNvr8/52YCv7bGaiBwUHolcLCU +bmtKILCG0RNJyTaJpXQdAeq5Z1SJotpbfYFFtAXB32hVoLug1dzl2tjG9sb1wq3Q +aDExcbC5w6P65qOkNoyym9ne6QlQagCqVDyFn3vcqkRaTjvZmxauCeUxXgJoXkyW +cm0lM1KMHdoTArmchw2Dz0yHHSyDAQIDAQABo1AwTjAdBgNVHQ4EFgQUc1YQIqjZ +sHVwlea0AB4N+ilNI2gwHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAI/1KH60qnw9Xs2RGfi0/ +IKf5EynXt4bQX8EIyVKwSkYKe04zZxYfLIl/Q2HOPYoFmm3daj5ddr0ZS1i4p4fT +UhstjsYWvXs3W/HhVmFUslakkn3PrswhP77fCk6eEJLxdfyJ1C7Uudq2m1isZbKi +h+XF0mG1LxJaDMocSz4eAya7M5brwjy8DoOmA1TnLQFCVcpn+sCr7VC4wE/JqxyV +hBCk/MuGqqM3B1j90bGFZ112ZOecyE0EDSr6IbiRBtmeNbEwOFjKXhNLYdxpBZ9D +8A/368OckZkCrVLGuJNxK9UwCVTe8IhotHUqU9EqFDmxdV8oIdU/OzUwwNPA/Bd/ +9g==</certificate> + </trusted-certificate> + </trusted-certificates> +</keystore> diff --git a/test/mocks/netconf-pnp-simulator/engine/config/tls/server_key.pem b/test/mocks/netconf-pnp-simulator/engine/config/tls/server_key.pem new file mode 100644 index 000000000..d61c77bdf --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/config/tls/server_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsdI1TBjzX1PgQXFuPCw5/kQwU7qkrhirMcFAXhI8EoXepPa9 +fKAVuMjHW32P6nNzDpnhFe0YGdNloIEN3hJJ87cVOqj4o7zZMbq3zVG2L8As7MTA +8tYXm2fSC/0rIxxRRemcGUXM0q+4LEACjZj2pOKonaivF5VbhgNjPCO1Jj/TamUc +0aViE577C9L9EiObGM+bGbabWk/KWKLsvxUc+sKZXaJ7psTVgpggJAkUszlmwOQg +FiMSR53E9/CAkQYhzGVCmH44Vs6Hzs3RZjOTbce4wr4ongiA5LbPeSNSCFjy9loK +paE1rtOjkNBVdiNPCQTmLuODXUTKgkeL+9v/OwIDAQABAoIBAG/4MG1JbL4C/7vV +pBcpth7Aaznd1eJ2UB4VVOWnT8JOH2L6p1h5KRRhAP9AMkXsCnAQPyZiVAG3FlAZ +01SZaY2YJDr6uQ3JVW4155TWtgSdWux//Ass+lJ17lJ0SRxjsV13ez6CsDWeRjc+ +2xy0S+KJgqk71XzhJG9fZLYyuddp3U/i3xFPUAcQM9xXKxcaD7g6LJf+a9pt6rim +Eqq/pjJxDgTsRLARsazYuxrlOB445mvnLiYhOf2/MvI80jIUKaj8BeAhg49UIg/k +mIh0xdevkcxBFer/BjBjscWaFjx14D6nkFMw7vtCum5KfalLN2edZKAzByOudGD4 +5KnRp3ECgYEA6vnSoNGg9Do80JOpXRGYWhcR1lIDO5yRW5rVagncCcW5Pn/GMtNd +x2q6k1ks8mXKR9CxZrxZGqeYObZ9a/5SLih7ZkpiVWXG8ZiBIPhP6lnwm5OeIqLa +hr0BYWcRfrGg1phj5uySZgsVBE+D8jH42O9ccdvrWv1OiryAHfKIcwMCgYEAwbs+ +HfQtvHOQXSYNhtOeA7IetkGy3cKVg2oILNcROvI96hS0MZKt1Rko0UAapx96eCIr +el7vfdT0eUzNqt2wTKp1zmiG+SnX3fMDJNzMwu/jb/b4wQ20IHWNDnqcqTUVRUnL +iksLFoHbTxsN5NpEQExcSt/zzP4qi1W2Bmo18WkCgYEAnhrk16LVux9ohiulHONW +8N9u+BeM51JtGAcxrDzgGo85Gs2czdwc0K6GxdiN/rfxCKtqgqcfCWlVaxfYgo7I +OxiwF17blXx7BVrJICcUlqpX1Ebac5HCmkCYqjJQuj/I6jv1lI7/3rt8M79RF+j5 ++PXt7Qq97SZd78nwJrZni4MCgYAiPjZ8lOyAouyhilhZvI3xmUpUbMhw6jQDRnqr +clhZUvgeqAoxuPuA7zGHywzq/WVoVqHYv28Vjs6noiu4R/chlf+8vD0fTYYadRnZ +Ki4HRt+sqrrNZN6x3hVQudt3DSr1VFXl293Z3JonIWETUoE93EFz+qHdWg+rETtb +ZuqiAQKBgD+HI/syLECyO8UynuEaDD7qPl87PJ/CmZLMxa2/ZZUjhaXAW7CJMaS6 +9PIzsLk33y3O4Qer0wx/tEdfnxMTBJrgGt/lFFdAKhSJroZ45l5apiavg1oZYp89 +jSd0lVxWSmrBjBZLnqOl336gzaBVkBD5ND+XUPdR1UuVQExJlem4 +-----END RSA PRIVATE KEY----- diff --git a/test/mocks/netconf-pnp-simulator/engine/config/tls/server_key.pem.pub b/test/mocks/netconf-pnp-simulator/engine/config/tls/server_key.pem.pub new file mode 100644 index 000000000..9ccec4a0c --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/config/tls/server_key.pem.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdI1TBjzX1PgQXFuPCw5 +/kQwU7qkrhirMcFAXhI8EoXepPa9fKAVuMjHW32P6nNzDpnhFe0YGdNloIEN3hJJ +87cVOqj4o7zZMbq3zVG2L8As7MTA8tYXm2fSC/0rIxxRRemcGUXM0q+4LEACjZj2 +pOKonaivF5VbhgNjPCO1Jj/TamUc0aViE577C9L9EiObGM+bGbabWk/KWKLsvxUc ++sKZXaJ7psTVgpggJAkUszlmwOQgFiMSR53E9/CAkQYhzGVCmH44Vs6Hzs3RZjOT +bce4wr4ongiA5LbPeSNSCFjy9loKpaE1rtOjkNBVdiNPCQTmLuODXUTKgkeL+9v/ +OwIDAQAB +-----END PUBLIC KEY----- diff --git a/test/mocks/netconf-pnp-simulator/engine/config/tls/tls_listen.xml b/test/mocks/netconf-pnp-simulator/engine/config/tls/tls_listen.xml new file mode 100644 index 000000000..852f3d0f6 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/config/tls/tls_listen.xml @@ -0,0 +1,27 @@ +<netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server"> + <listen> + <endpoint> + <name>tls_listen_endpt</name> + <tls> + <address>0.0.0.0</address> + <port>6513</port> + <certificates> + <certificate> + <name>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> + </listen> +</netconf-server> diff --git a/test/mocks/netconf-pnp-simulator/engine/container-tag.yaml b/test/mocks/netconf-pnp-simulator/engine/container-tag.yaml new file mode 100644 index 000000000..f705e1e02 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/container-tag.yaml @@ -0,0 +1 @@ +tag: "2.6.0" diff --git a/test/mocks/netconf-pnp-simulator/engine/entrypoint.sh b/test/mocks/netconf-pnp-simulator/engine/entrypoint.sh new file mode 100755 index 000000000..951ca474b --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/entrypoint.sh @@ -0,0 +1,132 @@ +#!/bin/sh +# shellcheck disable=SC2086 + +#- +# ============LICENSE_START======================================================= +# Copyright (C) 2020 Nordix Foundation. +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +export PATH=/opt/bin:/usr/local/bin:/usr/bin:/bin + +CONFIG=/config +TLS_CONFIG=$CONFIG/tls +MODELS_CONFIG=$CONFIG/modules +KEY_PATH=/opt/etc/keystored/keys +BASE_VIRTUALENVS=$HOME/.local/share/virtualenvs + +find_file() { + local dir=$1 + shift + for prog in "$@"; do + if [ -f $dir/$prog ]; then + echo -n $dir/$prog + break + fi + done +} + +find_executable() { + local dir=$1 + shift + for prog in "$@"; do + if [ -x $dir/$prog ]; then + echo -n $dir/$prog + break + fi + done +} + +configure_tls() +{ + cp $TLS_CONFIG/server_key.pem $KEY_PATH + cp $TLS_CONFIG/server_key.pem.pub $KEY_PATH + sysrepocfg --datastore=startup --format=xml ietf-keystore --merge=$TLS_CONFIG/load_server_certs.xml + sysrepocfg --datastore=startup --format=xml ietf-netconf-server --merge=$TLS_CONFIG/tls_listen.xml +} + +configure_modules() +{ + for dir in "$MODELS_CONFIG"/*; do + if [ -d $dir ]; then + model=${dir##*/} + install_and_configure_yang_model $dir $model + prog=$(find_executable $dir subscriber.py) + if [ -n "$prog" ]; then + configure_subscriber_execution $dir $model $prog + fi + fi + done +} + +install_and_configure_yang_model() +{ + local dir=$1 + local model=$2 + + yang=$(find_file $dir $model.yang model.yang) + sysrepoctl --install --yang=$yang + data=$(find_file $dir startup.json startup.xml data.json data.xml) + if [ -n "$data" ]; then + sysrepocfg --datastore=startup --import=$data $model + fi +} + +configure_subscriber_execution() +{ + local dir=$1 + local model=$2 + local prog=$3 + + PROG_PATH=$PATH + if [ -r "$dir/requirements.txt" ]; then + env_dir=$(create_python_venv $dir) + PROG_PATH=$env_dir/bin:$PROG_PATH + fi + cat > /etc/supervisord.d/$model.conf <<EOF +[program:subs-$model] +command=$prog $model +redirect_stderr=true +autorestart=true +environment=PATH=$PROG_PATH,PYTHONPATH=/opt/lib/python3.7/site-packages,PYTHONUNBUFFERED="1" +EOF +} + +create_python_venv() +{ + local dir=$1 + + mkdir -p $BASE_VIRTUALENVS + env_dir=$BASE_VIRTUALENVS/$model + ( + python3 -m venv --system-site-packages $env_dir + cd $env_dir + . ./bin/activate + pip install --upgrade pip + pip install -r "$dir"/requirements.txt + ) 1>&2 + echo $env_dir +} + +configure_tls +configure_modules + +exec /usr/local/bin/supervisord -c /etc/supervisord.conf diff --git a/test/mocks/netconf-pnp-simulator/engine/patches/Netopeer2/01-fix-grep-count.patch b/test/mocks/netconf-pnp-simulator/engine/patches/Netopeer2/01-fix-grep-count.patch new file mode 100644 index 000000000..00bc93085 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/patches/Netopeer2/01-fix-grep-count.patch @@ -0,0 +1,35 @@ +diff --git a/keystored/scripts/model-install.sh b/keystored/scripts/model-install.sh +index a350950..671dd16 100755 +--- a/keystored/scripts/model-install.sh ++++ b/keystored/scripts/model-install.sh +@@ -13,7 +13,7 @@ local_path=$(dirname $0) + is_yang_module_installed() { + module=$1 + +- $SYSREPOCTL -l | grep --count "^$module [^|]*|[^|]*| Installed .*$" > /dev/null ++ $SYSREPOCTL -l | grep -c "^$module [^|]*|[^|]*| Installed .*$" > /dev/null + } + + install_yang_module() { +diff --git a/server/scripts/model-install.sh.in b/server/scripts/model-install.sh.in +index 589d639..760ce42 100755 +--- a/server/scripts/model-install.sh.in ++++ b/server/scripts/model-install.sh.in +@@ -13,7 +13,7 @@ shopt -s failglob + is_yang_module_installed() { + module=$1 + +- $SYSREPOCTL -l | grep --count "^$module [^|]*|[^|]*| Installed .*$" > /dev/null ++ $SYSREPOCTL -l | grep -c "^$module [^|]*|[^|]*| Installed .*$" > /dev/null + } + + install_yang_module() { +@@ -31,7 +31,7 @@ enable_yang_module_feature() { + module=$1 + feature=$2 + +- if ! $SYSREPOCTL -l | grep --count "^$module [^|]*|[^|]*|[^|]*|[^|]*|[^|]*|[^|]*|.* $feature.*$" > /dev/null; then ++ if ! $SYSREPOCTL -l | grep -c "^$module [^|]*|[^|]*|[^|]*|[^|]*|[^|]*|[^|]*|.* $feature.*$" > /dev/null; then + echo "- Enabling feature $feature in $module..." + $SYSREPOCTL -m $module -e $feature + else diff --git a/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/01-configurable-PYTHON_MODULE_PATH.patch b/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/01-configurable-PYTHON_MODULE_PATH.patch new file mode 100644 index 000000000..3deb95c29 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/01-configurable-PYTHON_MODULE_PATH.patch @@ -0,0 +1,14 @@ +--- a/python/CMakeLists.txt 2020-02-19 12:25:07.000000000 +0000 ++++ b/python/CMakeLists.txt 2020-02-20 14:56:26.810463000 +0000 +@@ -22,7 +22,9 @@ + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docs/Makefile.in ${CMAKE_CURRENT_SOURCE_DIR}/docs/Makefile) + add_custom_target(pyapi ALL COMMAND ${PYTHON} ${SETUP_PY} build -b ${PYAPI_BUILD_DIR} ${DEBUG}) + add_custom_target(pyapidoc COMMAND make -f ${CMAKE_CURRENT_SOURCE_DIR}/docs/Makefile html) +- execute_process(COMMAND ${PYTHON} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True))" +- OUTPUT_VARIABLE PYTHON_MODULE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) ++ if(NOT DEFINED PYTHON_MODULE_PATH) ++ execute_process(COMMAND ${PYTHON} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True))" ++ OUTPUT_VARIABLE PYTHON_MODULE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) ++ endif() + install(CODE "execute_process(COMMAND ${PYTHON} ${SETUP_PY} build -b ${PYAPI_BUILD_DIR} install --install-lib=\$ENV{DESTDIR}/${PYTHON_MODULE_PATH})") + endif() diff --git a/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/02-fix-missing-include-dir.patch b/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/02-fix-missing-include-dir.patch new file mode 100644 index 000000000..556b9fd84 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/02-fix-missing-include-dir.patch @@ -0,0 +1,11 @@ +--- a/python/setup.py.in 2020-02-20 20:04:33.000000000 +0000 ++++ b/python/setup.py.in 2020-02-20 20:04:57.000000000 +0000 +@@ -13,7 +13,7 @@ + "${CMAKE_CURRENT_COURCE_DIR}/rpc.h" + ], + libraries=["netconf2"], +- extra_compile_args=["-Wall", "-I${CMAKE_CURRENT_BINARY_DIR}" @SSH_DEFINE@ @TLS_DEFINE@], ++ extra_compile_args=["-Wall", "-I${CMAKE_CURRENT_BINARY_DIR}", "-I${LIBYANG_INCLUDE_DIR}", "-I${LIBSSH_INCLUDE_DIR}" @SSH_DEFINE@ @TLS_DEFINE@], + extra_link_args=["-L${CMAKE_CURRENT_BINARY_DIR}/.."], + ) + diff --git a/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/03-fix-missing-pthread_rwlockattr_setkind_np.patch b/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/03-fix-missing-pthread_rwlockattr_setkind_np.patch new file mode 100644 index 000000000..65537a017 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/03-fix-missing-pthread_rwlockattr_setkind_np.patch @@ -0,0 +1,20 @@ +diff --git a/src/session_server.c b/src/session_server.c +index 636b1a2..57f2854 100644 +--- a/src/session_server.c ++++ b/src/session_server.c +@@ -560,6 +560,7 @@ nc_server_init(struct ly_ctx *ctx) + errno=0; + + if (pthread_rwlockattr_init(&attr) == 0) { ++#ifdef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP + if (pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP) == 0) { + if (pthread_rwlock_init(&server_opts.endpt_lock, &attr) != 0) { + ERR("%s: failed to init rwlock(%s).", __FUNCTION__, strerror(errno)); +@@ -570,6 +571,7 @@ nc_server_init(struct ly_ctx *ctx) + } else { + ERR("%s: failed set attribute (%s).", __FUNCTION__, strerror(errno)); + } ++#endif + pthread_rwlockattr_destroy(&attr); + } else { + ERR("%s: failed init attribute (%s).", __FUNCTION__, strerror(errno)); diff --git a/test/mocks/netconf-pnp-simulator/engine/patches/libyang/01-configurable-PYTHON_MODULE_PATH.patch b/test/mocks/netconf-pnp-simulator/engine/patches/libyang/01-configurable-PYTHON_MODULE_PATH.patch new file mode 100644 index 000000000..167297f06 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/patches/libyang/01-configurable-PYTHON_MODULE_PATH.patch @@ -0,0 +1,17 @@ +--- a/swig/python/CMakeLists.txt 2020-02-19 12:24:05.000000000 +0000 ++++ b/swig/python/CMakeLists.txt 2020-02-20 14:54:59.279634000 +0000 +@@ -20,9 +20,11 @@ + + file(COPY "examples" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +-execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True))" +- OUTPUT_VARIABLE PYTHON_MODULE_PATH +- OUTPUT_STRIP_TRAILING_WHITESPACE ) ++if(NOT DEFINED PYTHON_MODULE_PATH) ++ execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True))" ++ OUTPUT_VARIABLE PYTHON_MODULE_PATH ++ OUTPUT_STRIP_TRAILING_WHITESPACE ) ++endif() + + install( TARGETS _${PYTHON_SWIG_BINDING} DESTINATION ${PYTHON_MODULE_PATH}) + install( FILES "${CMAKE_CURRENT_BINARY_DIR}/${PYTHON_SWIG_BINDING}.py" DESTINATION ${PYTHON_MODULE_PATH}) diff --git a/test/mocks/netconf-pnp-simulator/engine/patches/sysrepo/01-configurable-PYTHON_MODULE_PATH.patch b/test/mocks/netconf-pnp-simulator/engine/patches/sysrepo/01-configurable-PYTHON_MODULE_PATH.patch new file mode 100644 index 000000000..3c6fa7b87 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/patches/sysrepo/01-configurable-PYTHON_MODULE_PATH.patch @@ -0,0 +1,21 @@ +diff --git a/swig/python/CMakeLists.txt b/swig/python/CMakeLists.txt +index 7d00a8b7..dc06da00 100644 +--- a/swig/python/CMakeLists.txt ++++ b/swig/python/CMakeLists.txt +@@ -24,10 +24,12 @@ swig_link_libraries(${PYTHON_SWIG_BINDING} ${PYTHON_LIBRARIES} Sysrepo-cpp) + + file(COPY "examples" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + +-execute_process(COMMAND +- ${PYTHON_EXECUTABLE} -c +- "from distutils.sysconfig import get_python_lib; print(get_python_lib())" +-OUTPUT_VARIABLE PYTHON_MODULE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) ++if(NOT DEFINED PYTHON_MODULE_PATH) ++ execute_process(COMMAND ++ ${PYTHON_EXECUTABLE} -c ++ "from distutils.sysconfig import get_python_lib; print(get_python_lib())" ++ OUTPUT_VARIABLE PYTHON_MODULE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) ++endif() + + install( FILES "${CMAKE_CURRENT_BINARY_DIR}/_${PYTHON_SWIG_BINDING}.so" DESTINATION ${PYTHON_MODULE_PATH} ) + install( FILES "${CMAKE_CURRENT_BINARY_DIR}/${PYTHON_SWIG_BINDING}.py" DESTINATION ${PYTHON_MODULE_PATH} ) diff --git a/test/mocks/netconf-pnp-simulator/engine/supervisord.conf b/test/mocks/netconf-pnp-simulator/engine/supervisord.conf new file mode 100644 index 000000000..9e6fd4282 --- /dev/null +++ b/test/mocks/netconf-pnp-simulator/engine/supervisord.conf @@ -0,0 +1,45 @@ +#- +# ============LICENSE_START======================================================= +# Copyright (C) 2020 Nordix Foundation. +# ================================================================================ +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +[supervisord] +nodaemon=true +logfile=/dev/null +logfile_maxbytes=0 +loglevel=debug + +[program:sysrepod] +command=/opt/bin/sysrepod -d -l3 +autorestart=true +redirect_stderr=true +priority=1 + +[program:sysrepo-plugind] +command=/opt/bin/sysrepo-plugind -d -l3 +autorestart=true +redirect_stderr=true +priority=2 + +[program:netopeer2-server] +command=/opt/bin/netopeer2-server -d -v3 +autorestart=true +redirect_stderr=true +priority=3 + +[include] +files=/etc/supervisord.d/*.conf |