From 3ecb7b39473c5ea6835fcbdd57b91acb74781a52 Mon Sep 17 00:00:00 2001 From: ebo Date: Thu, 27 Feb 2020 14:04:23 +0000 Subject: [PATCH] Add NETCONF PNF Simulator Engine Issue-ID: INT-1124 Signed-off-by: ebo Change-Id: Ifb50a749992cbd662d579e1cb861bd8f55b3f808 --- test/mocks/netconf-pnp-simulator/README.md | 9 ++ 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 test/mocks/netconf-pnp-simulator/engine/Dockerfile | 179 +++++++++++++++++++++ test/mocks/netconf-pnp-simulator/engine/LICENSE | 13 ++ .../engine/config/modules/.gitkeep | 0 .../engine/config/tls/load_server_certs.xml | 62 +++++++ .../engine/config/tls/server_key.pem | 27 ++++ .../engine/config/tls/server_key.pem.pub | 9 ++ .../engine/config/tls/tls_listen.xml | 27 ++++ .../engine/container-tag.yaml | 1 + .../netconf-pnp-simulator/engine/entrypoint.sh | 132 +++++++++++++++ .../patches/Netopeer2/01-fix-grep-count.patch | 35 ++++ .../01-configurable-PYTHON_MODULE_PATH.patch | 14 ++ .../libnetconf2/02-fix-missing-include-dir.patch | 11 ++ ...fix-missing-pthread_rwlockattr_setkind_np.patch | 20 +++ .../01-configurable-PYTHON_MODULE_PATH.patch | 17 ++ .../01-configurable-PYTHON_MODULE_PATH.patch | 21 +++ .../netconf-pnp-simulator/engine/supervisord.conf | 45 ++++++ 23 files changed, 872 insertions(+) create mode 100644 test/mocks/netconf-pnp-simulator/README.md 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 create mode 100644 test/mocks/netconf-pnp-simulator/engine/Dockerfile create mode 100644 test/mocks/netconf-pnp-simulator/engine/LICENSE create mode 100644 test/mocks/netconf-pnp-simulator/engine/config/modules/.gitkeep create mode 100644 test/mocks/netconf-pnp-simulator/engine/config/tls/load_server_certs.xml create mode 100644 test/mocks/netconf-pnp-simulator/engine/config/tls/server_key.pem create mode 100644 test/mocks/netconf-pnp-simulator/engine/config/tls/server_key.pem.pub create mode 100644 test/mocks/netconf-pnp-simulator/engine/config/tls/tls_listen.xml create mode 100644 test/mocks/netconf-pnp-simulator/engine/container-tag.yaml create mode 100755 test/mocks/netconf-pnp-simulator/engine/entrypoint.sh create mode 100644 test/mocks/netconf-pnp-simulator/engine/patches/Netopeer2/01-fix-grep-count.patch create mode 100644 test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/01-configurable-PYTHON_MODULE_PATH.patch create mode 100644 test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/02-fix-missing-include-dir.patch create mode 100644 test/mocks/netconf-pnp-simulator/engine/patches/libnetconf2/03-fix-missing-pthread_rwlockattr_setkind_np.patch create mode 100644 test/mocks/netconf-pnp-simulator/engine/patches/libyang/01-configurable-PYTHON_MODULE_PATH.patch create mode 100644 test/mocks/netconf-pnp-simulator/engine/patches/sysrepo/01-configurable-PYTHON_MODULE_PATH.patch create mode 100644 test/mocks/netconf-pnp-simulator/engine/supervisord.conf 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 " +__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 0000000000000000000000000000000000000000..da95c9142d86fdb920a1bc5c5122ac5af3015854 GIT binary patch literal 58061 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 V+z+;QL@odyT}?xcPipqB{vSrA3-JH| literal 0 HcmV?d00001 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 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 @@ + + + + server_key + + + server_cert + 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= + + + + + + trusted_ca_list + + ca + 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== + + + 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 @@ + + + + tls_listen_endpt + +
0.0.0.0
+ 6513 + + + server_cert + + + + trusted_ca_list + + + 1 + 02:E9:38:1F:F6:8B:62:DE:0A:0B:C5:03:81:A8:03:49:A0:00:7F:8B:F3 + x509c2n:specified + netconf + + + +
+
+
+
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 <&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 -- 2.16.6