diff options
Diffstat (limited to 'test/mocks/pnfsimulator/netconfsimulator/netconf')
11 files changed, 673 insertions, 0 deletions
diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/__init__.py b/test/mocks/pnfsimulator/netconfsimulator/netconf/__init__.py new file mode 100644 index 000000000..0f144c21e --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/__init__.py @@ -0,0 +1,19 @@ +### +# ============LICENSE_START======================================================= +# Simulator +# ================================================================================ +# Copyright (C) 2019 Nokia. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +### diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/initialize_netopeer.sh b/test/mocks/pnfsimulator/netconfsimulator/netconf/initialize_netopeer.sh new file mode 100755 index 000000000..71c1f215b --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/initialize_netopeer.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +### +# ============LICENSE_START======================================================= +# Simulator +# ================================================================================ +# Copyright (C) 2019 Nokia. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +### + +cat > /etc/apt/apt.conf << EOF +Acquire::http { + No-Cache "true"; + No-Store "true"; + Pipeline-Depth "0"; +}; +EOF + +NETOPEER_CHANGE_SAVER=netopeer-change-saver + +cp /tls/* /usr/local/etc/keystored/keys/ +cp /netconf/*.xml /tmp/ + +chmod +x /netconf/set-up-xmls.py +/netconf/set-up-xmls.py /tls ca.crt server_cert.crt server_key.pem /tmp/load_server_certs.xml /tmp/tls_listen.xml + +/usr/bin/supervisord -c /etc/supervisord.conf & +sysrepoctl --install --yang=/netconf/pnf-simulator.yang --owner=netconf:nogroup --permissions=777 +sysrepocfg --import=/netconf/pnf-simulator.data.xml --datastore=startup --format=xml --level=3 pnf-simulator +sysrepocfg --merge=/tmp/load_server_certs.xml --format=xml --datastore=startup ietf-keystore +sysrepocfg --merge=/tmp/tls_listen.xml --format=xml --datastore=startup ietf-netconf-server + +apt-get update +apt-get install -y python3 python3-pip librdkafka-dev +pip3 install flask flask_restful +nohup python3 /netconf/yang_loader_server.py & + +/bin/cp -R /$NETOPEER_CHANGE_SAVER /opt/dev/ +ln -s /opt/dev/sysrepo/build/src/libsysrepo.so /$NETOPEER_CHANGE_SAVER/libsysrepo.so +cd /opt/dev/$NETOPEER_CHANGE_SAVER && cmake . +cd /opt/dev/$NETOPEER_CHANGE_SAVER && make +/opt/dev/$NETOPEER_CHANGE_SAVER/bin/netopeer-change-saver pnf-simulator kafka1 config diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/load_server_certs.xml b/test/mocks/pnfsimulator/netconfsimulator/netconf/load_server_certs.xml new file mode 100644 index 000000000..2524e08b0 --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/load_server_certs.xml @@ -0,0 +1,40 @@ +<!-- + ============LICENSE_START======================================================= + Simulator + ================================================================================ + Copyright (C) 2019 Nokia. All rights reserved. + ================================================================================ + 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. + ============LICENSE_END========================================================= + --> + +<keystore xmlns="urn:ietf:params:xml:ns:yang:ietf-keystore"> + <private-keys> + <private-key> + <name>SERVER_KEY_NAME</name> + <certificate-chains> + <certificate-chain> + <name>SERVER_CERT_NAME</name> + <certificate>SERVER_CERTIFICATE_HERE</certificate> + </certificate-chain> + </certificate-chains> + </private-key> + </private-keys> + <trusted-certificates> + <name>test_trusted_ca_list</name> + <trusted-certificate> + <name>CA_CERT_NAME</name> + <certificate>CA_CERTIFICATE_HERE</certificate> + </trusted-certificate> + </trusted-certificates> +</keystore> diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/newmodel.xml b/test/mocks/pnfsimulator/netconfsimulator/netconf/newmodel.xml new file mode 100644 index 000000000..90a3451d4 --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/newmodel.xml @@ -0,0 +1,24 @@ +<!-- + ============LICENSE_START======================================================= + Simulator + ================================================================================ + Copyright (C) 2019 Nokia. All rights reserved. + ================================================================================ + 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. + ============LICENSE_END========================================================= + --> + +<config2 xmlns="http://onap.org/pnf-simulator2"> + <item1>500</item1> + <item2>1000</item2> +</config2> diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/newmodel.yang b/test/mocks/pnfsimulator/netconfsimulator/netconf/newmodel.yang new file mode 100644 index 000000000..544f46725 --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/newmodel.yang @@ -0,0 +1,9 @@ +module newmodel { + namespace "http://onap.org/pnf-simulator2"; + prefix config2; + container config2 { + config true; + leaf item1 {type uint32;} + leaf item2 {type uint32;} + } +} diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/pnf-simulator.data.xml b/test/mocks/pnfsimulator/netconfsimulator/netconf/pnf-simulator.data.xml new file mode 100644 index 000000000..c235f6405 --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/pnf-simulator.data.xml @@ -0,0 +1,24 @@ +<!-- + ============LICENSE_START======================================================= + Simulator + ================================================================================ + Copyright (C) 2019 Nokia. All rights reserved. + ================================================================================ + 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. + ============LICENSE_END========================================================= + --> + +<config xmlns="http://onap.org/pnf-simulator"> + <itemValue1>42</itemValue1> + <itemValue2>35</itemValue2> +</config> diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/pnf-simulator.yang b/test/mocks/pnfsimulator/netconfsimulator/netconf/pnf-simulator.yang new file mode 100644 index 000000000..ba1158560 --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/pnf-simulator.yang @@ -0,0 +1,9 @@ +module pnf-simulator { + namespace "http://onap.org/pnf-simulator"; + prefix config; + container config { + config true; + leaf itemValue1 {type uint32;} + leaf itemValue2 {type uint32;} + } +} diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/set-up-xmls.py b/test/mocks/pnfsimulator/netconfsimulator/netconf/set-up-xmls.py new file mode 100755 index 000000000..d46ff91f9 --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/set-up-xmls.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +### +# ============LICENSE_START======================================================= +# Simulator +# ================================================================================ +# Copyright (C) 2019 Nokia. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +### + +import os +import sys +import logging +import logging.config + +logging.basicConfig() +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# Placeholders definition - this needs to match placeholders in +# load_server_certs_xml_file and tls_listen_xml_file +SERVER_KEY_NAME = "SERVER_KEY_NAME" +SERVER_CERT_NAME = "SERVER_CERT_NAME" +SERVER_CERTIFICATE_HERE = "SERVER_CERTIFICATE_HERE" +CA_CERT_NAME = "CA_CERT_NAME" +CA_CERTIFICATE_HERE = "CA_CERTIFICATE_HERE" +CA_FINGERPRINT_HERE = "CA_FINGERPRINT_HERE" +CA_FINGERPRINT_ENV = "CA_FINGERPRINT" +SERVER_CERTIFICATE_ENV = "SERVER_CERTIFICATE_ENV" +CA_CERTIFICATE_ENV = "CA_CERTIFICATE_ENV" + + +class FileHelper(object): + @classmethod + def get_file_contents(cls, filename): + with open(filename, "r") as f: + return f.read() + + @classmethod + def write_file_contents(cls, filename, data): + with open(filename, "w+") as f: + f.write(data) + + +class CertHelper(object): + @classmethod + def get_pem_content_stripped(cls, pem_dir, pem_filename): + cmd = "cat {}/{} | grep -v '^-'".format(pem_dir, pem_filename) + content = CertHelper.system(cmd) + return content + + @classmethod + def get_cert_fingerprint(cls, directory, cert_filename): + cmd = "openssl x509 -fingerprint -noout -in {}/{} | sed -e " \ + "'s/SHA1 Fingerprint//; s/=//; s/=//p'" \ + .format(directory, cert_filename) + fingerprint = CertHelper.system(cmd) + return fingerprint + + @classmethod + def print_certs_info(cls, ca_cert, ca_fingerprint, server_cert): + logger.info("Will use server certificate: " + server_cert) + logger.info("Will use CA certificate: " + ca_cert) + logger.info("CA certificate fingerprint: " + ca_fingerprint) + + @classmethod + def system(cls, cmd): + return os.popen(cmd).read().replace("\n", "") + + +class App(object): + @classmethod + def patch_server_certs(cls, data, server_key_filename_noext, + server_cert_filename_noext, ca_cert_filename_noext, + server_cert, ca_cert): + data = data.replace(SERVER_KEY_NAME, server_key_filename_noext) + data = data.replace(SERVER_CERT_NAME, server_cert_filename_noext) + data = data.replace(CA_CERT_NAME, ca_cert_filename_noext) + data = data.replace(SERVER_CERTIFICATE_HERE, server_cert) + data = data.replace(CA_CERTIFICATE_HERE, ca_cert) + return data + + @classmethod + def patch_tls_listen(cls, data, server_cert_filename_noext, ca_fingerprint, + server_cert, ca_cert): + data = data.replace(SERVER_CERT_NAME, server_cert_filename_noext) + data = data.replace(CA_FINGERPRINT_HERE, ca_fingerprint) + data = data.replace(SERVER_CERTIFICATE_HERE, server_cert) + data = data.replace(CA_CERTIFICATE_HERE, ca_cert) + return data + + @classmethod + def run(cls): + # name things + cert_dir = sys.argv[1] + ca_cert_filename = sys.argv[2] + server_cert_filename = sys.argv[3] + server_key_filename = sys.argv[4] + load_server_certs_xml_file = sys.argv[5] + tls_listen_xml_file = sys.argv[6] + + # strip extensions + ca_cert_filename_noext = ca_cert_filename.replace(".crt", "") + server_cert_filename_noext = server_cert_filename.replace(".crt", "") + server_key_filename_noext = server_key_filename.replace(".pem", "") + + # get certificates from files + server_cert = CertHelper.get_pem_content_stripped(cert_dir, + server_cert_filename) + ca_cert = CertHelper.get_pem_content_stripped(cert_dir, + ca_cert_filename) + ca_fingerprint = CertHelper.get_cert_fingerprint(cert_dir, + ca_cert_filename) + CertHelper.print_certs_info(ca_cert, ca_fingerprint, server_cert) + + # patch TLS configuration files + data_srv = FileHelper.get_file_contents(load_server_certs_xml_file) + patched_srv = App.patch_server_certs(data_srv, server_key_filename_noext, + server_cert_filename_noext, + ca_cert_filename_noext, + server_cert, ca_cert) + FileHelper.write_file_contents(load_server_certs_xml_file, patched_srv) + + data_tls = FileHelper.get_file_contents(tls_listen_xml_file) + patched_tls = App.patch_tls_listen(data_tls, server_cert_filename_noext, + ca_fingerprint, server_cert, ca_cert) + FileHelper.write_file_contents(tls_listen_xml_file, patched_tls) + + +def main(): + if len(sys.argv) is not 7: + print("Usage: {1} <cert_dir> <ca_cert_filename> <server_cert_filename> " + "<server_key_filename> <load_server_certs_xml_full_path> " + "<tls_listen_full_path>", sys.argv[0]) + return 1 + App.run() + logger.info("XML files patched successfully") + + +if __name__ == '__main__': + main() diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/test_yang_loader_server.py b/test/mocks/pnfsimulator/netconfsimulator/netconf/test_yang_loader_server.py new file mode 100644 index 000000000..f282517b2 --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/test_yang_loader_server.py @@ -0,0 +1,121 @@ +### +# ============LICENSE_START======================================================= +# Simulator +# ================================================================================ +# Copyright (C) 2019 Nokia. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +### + +import unittest + +from unittest import mock +from werkzeug.datastructures import FileStorage + +from yang_loader_server import YangLoaderHelper, YangModelServer + + +class TestYangLoaderHelper(unittest.TestCase): + + def test_should_save_file_and_return_path(self): + helper = YangLoaderHelper() + mocked_file = mock.Mock(FileStorage) + mocked_file.filename = "sample" + + path = helper.save_file(mocked_file) + + self.assertEqual(path, "/tmp/sample") + mocked_file.save.assert_called_once_with("/tmp/sample") + + @mock.patch('yang_loader_server.check_output') + def test_should_install_new_yang_model(self, mocked_output): + helper = YangLoaderHelper() + + helper.install_new_model("path") + + mocked_output.assert_called_with( + ['sysrepoctl', '--install', '--yang=path', + '--owner=netconf:nogroup', '--permissions=777'], + stderr=-2, universal_newlines=True) + + @mock.patch('yang_loader_server.check_output') + def test_should_delete_yang_model(self, mocked_output): + helper = YangLoaderHelper() + + helper.uninstall_a_model("modelName") + + mocked_output.assert_called_with( + ['sysrepoctl', '--uninstall', '--module=modelName'], + stderr=-2, universal_newlines=True) + + @mock.patch('yang_loader_server.check_output') + def test_should_set_default_configuration(self, mocked_output): + helper = YangLoaderHelper() + + helper.set_default_configuration("samplePath", "sampleModuleName") + + mocked_output.assert_called_with( + ['sysrepocfg', '--import=samplePath', '--datastore=startup', + '--format=xml', '--level=3', 'sampleModuleName'], + stderr=-2, universal_newlines=True) + + @mock.patch('yang_loader_server.subprocess.Popen') + @mock.patch('yang_loader_server.check_output') + def test_should_verify_change_listener_for_model_properly(self, mocked_output, mocked_popen): + helper = YangLoaderHelper() + + helper.start_change_listener_for_model("sampleModule") + + mocked_output.assert_called_with( + ['pgrep', '-f', '/opt/dev/netopeer-change-saver/bin/netopeer-change-saver sampleModule kafka1 config'], + stderr=-2, universal_newlines=True) + + @mock.patch('yang_loader_server.check_output') + def test_should_raise_exception_when_error_occurred_in_output(self, + mocked_output): + helper = YangLoaderHelper() + mocked_output.return_value = "abcd ERR" + with self.assertRaises(RuntimeError) as context: + helper._run_bash_command("sample command") + + self.assertEqual('abcd ERR', str(context.exception)) + + +class TestYangModelServer(unittest.TestCase): + + def __init__(self, methodName='runTest'): + super().__init__(methodName) + self._mocked_file = mock.Mock(FileStorage) + + def test_should_properly_apply_and_start_new_model(self): + with mock.patch.object(YangModelServer, '_parse_request', + new=self._mock_request): + helper = mock.Mock(YangLoaderHelper) + helper.save_file.return_value = "sampleFile" + server = YangModelServer(helper) + + server.post() + + self.assertEqual(helper.save_file.call_count, 2) + helper.install_new_model.assert_called_once_with('sampleFile') + helper.set_default_configuration.assert_called_once_with( + 'sampleFile', 'sampleModuleName') + helper.start_change_listener_for_model.assert_called_once_with('sampleModuleName') + + def _mock_request(self): + return { + 'yangModel': self._mocked_file, + 'initialConfig': self._mocked_file, + 'moduleName': "sampleModuleName" + } diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/tls_listen.xml b/test/mocks/pnfsimulator/netconfsimulator/netconf/tls_listen.xml new file mode 100644 index 000000000..4f45b28a2 --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/tls_listen.xml @@ -0,0 +1,48 @@ +<!-- + ============LICENSE_START======================================================= + Simulator + ================================================================================ + Copyright (C) 2019 Nokia. All rights reserved. + ================================================================================ + 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. + ============LICENSE_END========================================================= + --> + +<netconf-server xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server"> + <listen> + <endpoint> + <name>test_tls_listen_endpt</name> + <tls> + <address>0.0.0.0</address> + <port>6513</port> + <certificates> + <certificate> + <name>SERVER_CERT_NAME</name> + </certificate> + </certificates> + <client-auth> + <trusted-ca-certs>test_trusted_ca_list</trusted-ca-certs> + <cert-maps> + <cert-to-name> + <id>1</id> + <!-- This is not a typo - 0x02 should stay there --> + <fingerprint>02:CA_FINGERPRINT_HERE</fingerprint> + <map-type xmlns:x509c2n="urn:ietf:params:xml:ns:yang:ietf-x509-cert-to-name">x509c2n:specified</map-type> + <name>test</name> + </cert-to-name> + </cert-maps> + </client-auth> + </tls> + </endpoint> + </listen> +</netconf-server> diff --git a/test/mocks/pnfsimulator/netconfsimulator/netconf/yang_loader_server.py b/test/mocks/pnfsimulator/netconfsimulator/netconf/yang_loader_server.py new file mode 100644 index 000000000..716d0712e --- /dev/null +++ b/test/mocks/pnfsimulator/netconfsimulator/netconf/yang_loader_server.py @@ -0,0 +1,172 @@ +### +# ============LICENSE_START======================================================= +# Simulator +# ================================================================================ +# Copyright (C) 2019 Nokia. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +### + +import logging +import subprocess +import os +from subprocess import check_output, CalledProcessError +from flask import Flask +from flask_restful import Resource, Api, reqparse +from werkzeug.datastructures import FileStorage +import time + +app = Flask(__name__) +api = Api(app) +logger = logging.getLogger("yang-loader") +logger.addHandler(logging.StreamHandler()) +KAFKA_BROKER_NAME="kafka1" +KAFKA_TOPIC_NAME="config" + + +class YangLoaderHelper(object): + + @classmethod + def save_file(cls, yang_model_file: FileStorage) -> str: + path = "/tmp/" + yang_model_file.filename + yang_model_file.save(path) + return path + + @classmethod + def install_new_model(cls, yang_model_path: str): + logger.info("Installing new model: %s", yang_model_path) + command = "sysrepoctl --install --yang={} --owner=netconf:nogroup --permissions=777" \ + .format(yang_model_path) + cls._run_bash_command(command) + + @classmethod + def uninstall_a_model(cls, yang_model_name: str): + logger.info("Uninstalling a model: %s", yang_model_name) + command = "sysrepoctl --uninstall --module={}" \ + .format(yang_model_name) + cls._run_bash_command(command) + + + @classmethod + def set_default_configuration(cls, init_conf_path: str, module_name: str): + logger.info("Attempting to set default configuration %s for module %s", init_conf_path, module_name) + command = "sysrepocfg --import={} --datastore=startup --format=xml --level=3 {}" \ + .format(init_conf_path, module_name) + cls._run_bash_command(command) + + @classmethod + def start_change_listener_for_model(cls, module_name: str): + logger.info("Starting listener for model: %s", module_name) + command = "/opt/dev/netopeer-change-saver/bin/netopeer-change-saver {} {} {}" \ + .format(module_name, KAFKA_BROKER_NAME, KAFKA_TOPIC_NAME) + try: + check_output(["pgrep", "-f" , command], stderr=subprocess.STDOUT, universal_newlines=True) + logger.info("Change listener for {} already exist.".format(module_name)) + except CalledProcessError: + subprocess.Popen(command.split(), stdout=subprocess.PIPE) + + @classmethod + def stop_change_listener_for_model(cls, model_name): + logger.info("Stopping listener for model %s", model_name) + pid = cls.get_pid_by_name(model_name) + logger.info("pid is %s", pid) + command = "kill -2 {}".format(pid) + cls._run_bash_command(command) + + @classmethod + def _run_bash_command(cls, command: str): + try: + logger.info("Attempts to invoke %s", command) + output = check_output(command.split(), stderr=subprocess.STDOUT, + universal_newlines=True) + logger.info("Output: %s", output) + if "ERR" in output: + raise RuntimeError(str(output)) + except subprocess.CalledProcessError as e: + raise RuntimeError(e, str(e.stdout)) + + @classmethod + def get_pid_by_name(cls, name): + for dirname in os.listdir('/proc'): + if not dirname.isdigit(): + continue + try: + with open('/proc/{}/cmdline'.format(dirname), mode='rb') as fd: + content = fd.read().decode().split('\x00') + except Exception as e: + print(e) + continue + + if name in content: + return dirname + + +class YangModelServer(Resource): + logger = logging.getLogger('YangModelServer') + + def __init__(self, yang_loader_helper: YangLoaderHelper = YangLoaderHelper()): + self._yang_loader_helper = yang_loader_helper + + def post(self): + args = self._parse_request() + yang_model_file = args['yangModel'] + initial_config_file = args['initialConfig'] + module_name = args['moduleName'] + model_path = self._yang_loader_helper.save_file(yang_model_file) + conf_path = self._yang_loader_helper.save_file(initial_config_file) + + try: + self._yang_loader_helper.install_new_model(model_path) + self._yang_loader_helper.set_default_configuration(conf_path, + module_name) + self._yang_loader_helper.start_change_listener_for_model(module_name) + except RuntimeError as e: + self.logger.error(e.args, exc_info=True) + return str(e.args), 400 + return "Successfully started" + + def delete(self): + args = self._parse_request() + yang_model_name = args['yangModelName'] + + try: + self._yang_loader_helper.stop_change_listener_for_model(yang_model_name) + time.sleep(5) + self._yang_loader_helper.uninstall_a_model(yang_model_name) + except RuntimeError as e: + self.logger.error(e.args, exc_info=True) + return str(e.args), 400 + return "Successfully deleted" + + @classmethod + def _parse_request(cls) -> reqparse.Namespace: + parse = reqparse.RequestParser() + parse.add_argument('yangModel', + type=FileStorage, + location='files') + parse.add_argument('initialConfig', + type=FileStorage, + location='files') + parse.add_argument('moduleName', type=str) + parse.add_argument('yangModelName', type=str) + return parse.parse_args() + + +api.add_resource(YangModelServer, '/model') + +if __name__ == '__main__': + logging.basicConfig(filename=os.path.dirname(__file__) + "/yang_loader.log", + filemode="w", + level=logging.DEBUG) + app.run(host='0.0.0.0', port='5002') |