Add endopint for config change 99/119899/5
authorEdyta Krukowska <edyta.krukowska@nokia.com>
Thu, 25 Mar 2021 13:55:24 +0000 (14:55 +0100)
committerEdyta Krukowska <edyta.krukowska@nokia.com>
Fri, 26 Mar 2021 13:36:47 +0000 (14:36 +0100)
Issue-ID: INT-1869
Signed-off-by: Edyta Krukowska <edyta.krukowska@nokia.com>
Change-Id: Ib016779b3539ebaac186d92d3cd95a19f81e45cd

Dockerfile
scripts/run-netconf-server-application.sh
src/python/netconf_change_listener_application.py [moved from src/python/netconf_server_application.py with 85% similarity]
src/python/netconf_rest_application.py [new file with mode: 0644]
src/python/netconf_server/netconf_change_listener.py
src/python/netconf_server/netconf_change_listener_factory.py
src/python/netconf_server/netconf_rest_server.py
src/python/netconf_server/sysrepo_configuration/sysrepo_configuration_manager.py [new file with mode: 0644]
src/python/netconf_server/sysrepo_interface/sysrepo_client.py
src/python/tests/netconf_server/sysrepo_configuration/test_sysrepo_configuration_manager_.py [new file with mode: 0644]
src/python/tests/netconf_server/test_netconf_chang_listener.py [moved from src/python/tests/netconf_server/test_netconf_server.py with 100% similarity]

index 2175b10..d56d20a 100644 (file)
@@ -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
 
index 5cc51f4..b07da8a 100755 (executable)
 
 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
similarity index 85%
rename from src/python/netconf_server_application.py
rename to src/python/netconf_change_listener_application.py
index 09dea83..a4dd8bd 100644 (file)
 # 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 (file)
index 0000000..79f2336
--- /dev/null
@@ -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.")
index 4bb748c..44910d1 100644 (file)
@@ -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):
index 00725dc..fa5e071 100644 (file)
@@ -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):
index 2c44029..dce4f82 100644 (file)
 ###
 
 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/<path:module_name>", 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 (file)
index 0000000..0e8ad94
--- /dev/null
@@ -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)
index fcd29e2..3528747 100644 (file)
@@ -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 (file)
index 0000000..5194218
--- /dev/null
@@ -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 = '''<config xmlns="http://onap.org/pnf-simulator">
+                        <itemValue1>12</itemValue1>
+                        <itemValue2>12</itemValue2>
+                        </config>'''
+        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)