From 480e3de63f475477138c45f551def992ba8d8b9d Mon Sep 17 00:00:00 2001 From: Edyta Krukowska Date: Thu, 25 Mar 2021 14:55:24 +0100 Subject: [PATCH] Add endopint for config change Issue-ID: INT-1869 Signed-off-by: Edyta Krukowska Change-Id: Ib016779b3539ebaac186d92d3cd95a19f81e45cd --- Dockerfile | 3 +- scripts/run-netconf-server-application.sh | 15 ++++-- ...n.py => netconf_change_listener_application.py} | 17 +++---- src/python/netconf_rest_application.py | 53 ++++++++++++++++++++++ .../netconf_server/netconf_change_listener.py | 2 +- .../netconf_change_listener_factory.py | 2 +- src/python/netconf_server/netconf_rest_server.py | 15 ++++-- .../sysrepo_configuration_manager.py | 41 +++++++++++++++++ .../sysrepo_interface/sysrepo_client.py | 2 +- .../test_sysrepo_configuration_manager_.py | 31 +++++++++++++ ...nf_server.py => test_netconf_chang_listener.py} | 0 11 files changed, 161 insertions(+), 20 deletions(-) rename src/python/{netconf_server_application.py => netconf_change_listener_application.py} (85%) create mode 100644 src/python/netconf_rest_application.py create mode 100644 src/python/netconf_server/sysrepo_configuration/sysrepo_configuration_manager.py create mode 100644 src/python/tests/netconf_server/sysrepo_configuration/test_sysrepo_configuration_manager_.py rename src/python/tests/netconf_server/{test_netconf_server.py => test_netconf_chang_listener.py} (100%) diff --git a/Dockerfile b/Dockerfile index 2175b10..d56d20a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,8 @@ RUN mkdir /logs COPY ./models /resources/models COPY ./scripts ./scripts COPY ./src/python/netconf_server ./application/netconf_server -COPY ./src/python/netconf_server_application.py ./application/netconf_server_application.py +COPY src/python/netconf_change_listener_application.py ./application/netconf_change_listener_application.py +COPY ./src/python/netconf_rest_application.py ./application/netconf_rest_application.py COPY ./src/python/requirements.txt ./application/requirements.txt COPY ./src/python/setup.py ./application/setup.py diff --git a/scripts/run-netconf-server-application.sh b/scripts/run-netconf-server-application.sh index 5cc51f4..b07da8a 100755 --- a/scripts/run-netconf-server-application.sh +++ b/scripts/run-netconf-server-application.sh @@ -21,9 +21,18 @@ if [ "$#" -eq 2 ]; then - echo "Starting NETCONF server" - python3 ./application/netconf_server_application.py $1/$2 & + ## Set up variable + MODELS_CONFIG_PATH=$1 + MODELS_CONFIG_NAME=$2 + + echo "Starting NETCONF Change listener" + python3 ./application/netconf_change_listener_application.py $MODELS_CONFIG_PATH/$MODELS_CONFIG_NAME & + + + echo "Starting NETCONF Rest server" + python3 ./application/netconf_rest_application.py $MODELS_CONFIG_PATH/$MODELS_CONFIG_NAME & + else - echo "Missing argument: path to file with models to subscribe to." + echo "[ERROR] Invalid number of arguments. Please provide all required arguments." fi diff --git a/src/python/netconf_server_application.py b/src/python/netconf_change_listener_application.py similarity index 85% rename from src/python/netconf_server_application.py rename to src/python/netconf_change_listener_application.py index 09dea83..a4dd8bd 100644 --- a/src/python/netconf_server_application.py +++ b/src/python/netconf_change_listener_application.py @@ -17,9 +17,11 @@ # limitations under the License. # ============LICENSE_END========================================================= ### +import asyncio import sys import logging from netconf_server.netconf_rest_server import NetconfRestServer +from netconf_server.sysrepo_configuration.sysrepo_configuration_manager import SysrepoConfigurationManager from netconf_server.netconf_change_listener import NetconfChangeListener from netconf_server.netconf_change_listener_factory import NetconfChangeListenerFactory @@ -28,15 +30,15 @@ from netconf_server.sysrepo_configuration.sysrepo_configuration_loader import Sy from netconf_server.sysrepo_interface.sysrepo_client import SysrepoClient logging.basicConfig( - handlers=[logging.StreamHandler(), logging.FileHandler("/logs/netconf_server.log")], + handlers=[logging.StreamHandler(), logging.FileHandler("/logs/netconf_change_listener.log")], level=logging.DEBUG ) -logger = logging.getLogger("netconf_server") +logger = logging.getLogger("netconf_change_listener") -def run_server_forever(session, change_listener: NetconfChangeListener, server_rest: NetconfRestServer): +def run_server_forever(session, connection, change_listener: NetconfChangeListener): change_listener.run(session) - server_rest.start() + asyncio.get_event_loop().run_forever() def create_change_listener() -> NetconfChangeListener: @@ -44,16 +46,11 @@ def create_change_listener() -> NetconfChangeListener: return NetconfChangeListenerFactory(configuration.models_to_subscribe_to).create() -def create_rest_server() -> NetconfRestServer: - return NetconfRestServer() - - if __name__ == "__main__": if len(sys.argv) >= 2: try: netconf_change_listener = create_change_listener() - rest_server = create_rest_server() - SysrepoClient().run_in_session(run_server_forever, netconf_change_listener, rest_server) + SysrepoClient().run_in_session(run_server_forever, netconf_change_listener) except ConfigLoadingException: logger.error("File to load configuration from file %s" % sys.argv[1]) else: diff --git a/src/python/netconf_rest_application.py b/src/python/netconf_rest_application.py new file mode 100644 index 0000000..79f2336 --- /dev/null +++ b/src/python/netconf_rest_application.py @@ -0,0 +1,53 @@ +### +# ============LICENSE_START======================================================= +# Netconf Server +# ================================================================================ +# Copyright (C) 2021 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 sys +import logging +from netconf_server.netconf_rest_server import NetconfRestServer +from netconf_server.sysrepo_configuration.sysrepo_configuration_manager import SysrepoConfigurationManager + +from netconf_server.sysrepo_configuration.sysrepo_configuration_loader import ConfigLoadingException +from netconf_server.sysrepo_interface.sysrepo_client import SysrepoClient + +logging.basicConfig( + handlers=[logging.StreamHandler(), logging.FileHandler("/logs/netconf_rest_application.log")], + level=logging.DEBUG +) +logger = logging.getLogger("netconf_rest_application") + + +def start_rest_server(session, connection, server_rest: NetconfRestServer): + sysrepo_cfg_manager = create_conf_manager(session, connection) + server_rest.start(sysrepo_cfg_manager) + + +def create_rest_server() -> NetconfRestServer: + return NetconfRestServer() + + +def create_conf_manager(session, connection) -> SysrepoConfigurationManager: + return SysrepoConfigurationManager(session, connection) + + +if __name__ == "__main__": + if len(sys.argv) >= 2: + rest_server = create_rest_server() + SysrepoClient().run_in_session(start_rest_server, rest_server) + else: + logger.error("Missing path to file with configuration argument required to start netconf server.") diff --git a/src/python/netconf_server/netconf_change_listener.py b/src/python/netconf_server/netconf_change_listener.py index 4bb748c..44910d1 100644 --- a/src/python/netconf_server/netconf_change_listener.py +++ b/src/python/netconf_server/netconf_change_listener.py @@ -21,7 +21,7 @@ import logging from netconf_server.sysrepo_interface.config_change_data import ConfigChangeData -logger = logging.getLogger("netconf_saver") +logger = logging.getLogger(__name__) class NetconfChangeListener(object): diff --git a/src/python/netconf_server/netconf_change_listener_factory.py b/src/python/netconf_server/netconf_change_listener_factory.py index 00725dc..fa5e071 100644 --- a/src/python/netconf_server/netconf_change_listener_factory.py +++ b/src/python/netconf_server/netconf_change_listener_factory.py @@ -22,7 +22,7 @@ import logging from netconf_server.netconf_change_listener import NetconfChangeListener from netconf_server.sysrepo_interface.config_change_subscriber import ConfigChangeSubscriber -logger = logging.getLogger("netconf_saver") +logger = logging.getLogger(__name__) class NetconfChangeListenerFactory(object): diff --git a/src/python/netconf_server/netconf_rest_server.py b/src/python/netconf_server/netconf_rest_server.py index 2c44029..dce4f82 100644 --- a/src/python/netconf_server/netconf_rest_server.py +++ b/src/python/netconf_server/netconf_rest_server.py @@ -19,19 +19,21 @@ ### import logging as sys_logging -from flask import Flask, logging, make_response, Response +from flask import Flask, logging, make_response, Response, request +from netconf_server.sysrepo_configuration.sysrepo_configuration_manager import SysrepoConfigurationManager class NetconfRestServer: _rest_server: Flask = Flask("server") - sys_logging.basicConfig(level=sys_logging.DEBUG) logger = logging.create_logger(_rest_server) + _configuration_manager: SysrepoConfigurationManager def __init__(self, host='0.0.0.0', port=6555): self._host = host self._port = port - def start(self): + def start(self, configuration_manager: SysrepoConfigurationManager): + NetconfRestServer._configuration_manager = configuration_manager Flask.run( NetconfRestServer._rest_server, host=self._host, @@ -43,6 +45,13 @@ class NetconfRestServer: def __health_check(): return "UP" + @staticmethod + @_rest_server.route("/change_config/", methods=['POST']) + def __change_config(module_name): + config_data = request.data.decode("utf-8") + NetconfRestServer._configuration_manager.change_configuration(config_data, module_name) + return NetconfRestServer.__create_http_response(202, "Accepted") + @staticmethod def __create_http_response(code, message): return make_response( diff --git a/src/python/netconf_server/sysrepo_configuration/sysrepo_configuration_manager.py b/src/python/netconf_server/sysrepo_configuration/sysrepo_configuration_manager.py new file mode 100644 index 0000000..0e8ad94 --- /dev/null +++ b/src/python/netconf_server/sysrepo_configuration/sysrepo_configuration_manager.py @@ -0,0 +1,41 @@ +# ================================================================================ +# Copyright (C) 2021 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 + + +class SysrepoConfigurationManager(object): + logger = logging.getLogger(__name__) + + def __init__(self, session, connection): + self._connection = connection + self._session = session + + def __parse_config_data(self, config_data): + self.logger.debug(config_data) + ctx = self._connection.get_ly_ctx() + data = ctx.parse_data_mem( + config_data, + "xml", + config=True, + strict=False, + ) + return data + + def change_configuration(self, config_data: str, module_name: str): + data = self.__parse_config_data(config_data) + self._session.replace_config_ly(data, module_name) diff --git a/src/python/netconf_server/sysrepo_interface/sysrepo_client.py b/src/python/netconf_server/sysrepo_interface/sysrepo_client.py index fcd29e2..3528747 100644 --- a/src/python/netconf_server/sysrepo_interface/sysrepo_client.py +++ b/src/python/netconf_server/sysrepo_interface/sysrepo_client.py @@ -26,4 +26,4 @@ class SysrepoClient(object): def run_in_session(method_to_run: callable, *extra_args): with sysrepo.SysrepoConnection() as connection: with connection.start_session() as session: - method_to_run(session, *extra_args) + method_to_run(session, connection, *extra_args) diff --git a/src/python/tests/netconf_server/sysrepo_configuration/test_sysrepo_configuration_manager_.py b/src/python/tests/netconf_server/sysrepo_configuration/test_sysrepo_configuration_manager_.py new file mode 100644 index 0000000..5194218 --- /dev/null +++ b/src/python/tests/netconf_server/sysrepo_configuration/test_sysrepo_configuration_manager_.py @@ -0,0 +1,31 @@ +import unittest +from unittest.mock import MagicMock +from netconf_server.sysrepo_configuration.sysrepo_configuration_manager import SysrepoConfigurationManager + + +class TestSysrepoConfigurationManager(unittest.TestCase): + + def test_should_change_configuration(self): + # given + expected_parse_data = "parse_data" + + ctx = MagicMock() + ctx.parse_data_mem = MagicMock(return_value=expected_parse_data) + connection = MagicMock() + connection.get_ly_ctx = MagicMock(return_value=ctx) + session = MagicMock() + session.replace_config_ly = MagicMock() + + config_data = ''' + 12 + 12 + ''' + module_name = "pnf-simulator" + + # when + config_manager = SysrepoConfigurationManager(session=session, connection=connection) + config_manager.change_configuration(config_data=config_data, module_name=module_name) + + # then + ctx.parse_data_mem.assert_called_with(config_data, "xml", config=True, strict=False) + session.replace_config_ly.assert_called_with(expected_parse_data, module_name) diff --git a/src/python/tests/netconf_server/test_netconf_server.py b/src/python/tests/netconf_server/test_netconf_chang_listener.py similarity index 100% rename from src/python/tests/netconf_server/test_netconf_server.py rename to src/python/tests/netconf_server/test_netconf_chang_listener.py -- 2.16.6