Add NETCONF PNF Simulator Engine

Issue-ID: INT-1124
Signed-off-by: ebo <eliezio.oliveira@est.tech>
Change-Id: Ifb50a749992cbd662d579e1cb861bd8f55b3f808
diff --git a/test/mocks/netconf-pnp-simulator/README.md b/test/mocks/netconf-pnp-simulator/README.md
new file mode 100644
index 0000000..df32118
--- /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 0000000..8aa2450
--- /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 0000000..63872ee
--- /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 0000000..ee70c4f
--- /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 0000000..6c8c36a
--- /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 0000000..6127296
--- /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
new file mode 100644
index 0000000..da95c91
--- /dev/null
+++ b/test/mocks/netconf-pnp-simulator/docs/images/Architecture.png
Binary files differ
diff --git a/test/mocks/netconf-pnp-simulator/engine/Dockerfile b/test/mocks/netconf-pnp-simulator/engine/Dockerfile
new file mode 100644
index 0000000..5432b64
--- /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 0000000..c6aae55
--- /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 0000000..e69de29
--- /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 0000000..8872a8e
--- /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 0000000..d61c77b
--- /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 0000000..9ccec4a
--- /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 0000000..852f3d0
--- /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 0000000..f705e1e
--- /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 0000000..951ca47
--- /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 0000000..00bc930
--- /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 0000000..3deb95c
--- /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 0000000..556b9fd
--- /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 0000000..65537a0
--- /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 0000000..167297f
--- /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 0000000..3c6fa7b
--- /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 0000000..9e6fd42
--- /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