Add configuration-change API
If a configuration file path is defined in an environment variable,
use the Linux kernel's inotify feature to define a watcher on that file.
Xapps that subclass RMRXapp can supply a configuration-change handler
that the framework invokes on write events by polling the watcher.
Xapps that subclass Xapp must invoke a method to poll the watcher.
Bump version to 1.3.0
Issue-ID: RIC-425
Signed-off-by: Lott, Christopher (cl778h) <cl778h@att.com>
Change-Id: I070b36bc7e5a9dcd66c08da0304f7bf9e6a794a1
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 0000000..8eef73b
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,131 @@
+# ==================================================================================
+# Copyright (c) 2020 Nokia
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# 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.
+# ==================================================================================
+
+import time
+import os
+from contextlib import suppress
+from mdclogpy import Logger
+from ricxappframe.xapp_frame import RMRXapp, CONFIG_FILE_ENV
+
+mdc_logger = Logger(name=__name__)
+rmr_xapp_config = None
+rmr_xapp_defconfig = None
+rmr_xapp_noconfig = None
+config_file_path = "/tmp/file.json"
+
+
+def init_config_file():
+ with open(config_file_path, "w") as file:
+ file.write('{ "start" : "value" }')
+
+
+def write_config_file():
+ # generate an inotify/config event
+ with open(config_file_path, "w") as file:
+ file.write('{ "change" : "value2" }')
+
+
+def test_config_no_env(monkeypatch):
+ init_config_file()
+ monkeypatch.delenv(CONFIG_FILE_ENV, raising=False)
+
+ def default_rmr_handler(self, summary, sbuf):
+ pass
+
+ config_event_seen = False
+
+ def config_handler(self, json):
+ nonlocal config_event_seen
+ config_event_seen = True
+
+ global rmr_xapp_noconfig
+ rmr_xapp_noconfig = RMRXapp(default_rmr_handler, config_handler=config_handler, rmr_port=4652, use_fake_sdl=True)
+ # in unit tests we need to thread here or else execution is not returned!
+ rmr_xapp_noconfig.run(thread=True, rmr_timeout=1)
+
+ write_config_file()
+ # give the work loop a chance to timeout on RMR and process the config event
+ time.sleep(3)
+ assert not config_event_seen
+ rmr_xapp_noconfig.stop()
+
+
+def test_default_config_handler(monkeypatch):
+ """Just for coverage"""
+ init_config_file()
+ monkeypatch.setenv(CONFIG_FILE_ENV, config_file_path)
+
+ def default_rmr_handler(self, summary, sbuf):
+ pass
+
+ # listen port is irrelevant, no messages arrive
+ global rmr_xapp_defconfig
+ rmr_xapp_defconfig = RMRXapp(default_rmr_handler, rmr_port=4567, use_fake_sdl=True)
+ # in unit tests we need to thread here or else execution is not returned!
+ rmr_xapp_defconfig.run(thread=True, rmr_timeout=1)
+ write_config_file()
+ # give the work loop a chance to timeout on RMR and process the config event
+ time.sleep(3)
+ rmr_xapp_defconfig.stop()
+
+
+def test_custom_config_handler(monkeypatch):
+ # point watcher at the file
+ init_config_file()
+ monkeypatch.setenv(CONFIG_FILE_ENV, config_file_path)
+
+ def default_handler(self, summary, sbuf):
+ pass
+
+ startup_config_event = False
+ change_config_event = False
+
+ def config_handler(self, json):
+ mdc_logger.info("config_handler: json {}".format(json))
+ nonlocal startup_config_event
+ nonlocal change_config_event
+ if "start" in json:
+ startup_config_event = True
+ if "change" in json:
+ change_config_event = True
+
+ # listen port is irrelevant, no messages arrive
+ global rmr_xapp_config
+ rmr_xapp_config = RMRXapp(default_handler, config_handler=config_handler, rmr_port=4567, use_fake_sdl=True)
+ assert startup_config_event
+ rmr_xapp_config.run(thread=True, rmr_timeout=1) # in unit tests we need to thread here or else execution is not returned!
+ write_config_file()
+ # give the work loop a chance to timeout on RMR and process the config event
+ time.sleep(3)
+ assert change_config_event
+ rmr_xapp_config.stop()
+
+
+def teardown_module():
+ """
+ this is like a "finally"; the name of this function is pytest magic
+ safer to put down here since certain failures above can lead to pytest never returning
+ for example if an exception gets raised before stop is called in any test function above,
+ pytest will hang forever
+ """
+ os.remove(config_file_path)
+ with suppress(Exception):
+ rmr_xapp_config.stop()
+ with suppress(Exception):
+ rmr_xapp_defconfig.stop()
+ with suppress(Exception):
+ rmr_xapp_noconfig.stop()