Fixes and enhancements: 37/2637/4
authorTommy Carpenter <tc677g@att.com>
Tue, 3 Mar 2020 15:21:24 +0000 (10:21 -0500)
committerTommy Carpenter <tc677g@att.com>
Wed, 4 Mar 2020 12:52:14 +0000 (07:52 -0500)
    * now allows for RMR Xapps to call code before entering the infinite loop
    * stop is now called before throwing NotImplemented in the case where the client fails to provide a must have callback; this ensures there is no dangling rmr thread
    * stop now calls rmr_close to correctly free up any port(s)
    * (breaking) renames `loop` to `entrypoint` since the function does not have to contain a loop (though it most likely does)
    * Changes wording around the two types of xapps (docs only)
    * Uses a new version of rmr python that crashes when the rmr mrc fails to init, which prevents an xapp trying to use an unusable rmr
    * more unit test code coverage
    * Adds more fields to setup like long_desc and classifiers so the pypi page looks nicer
    * Removes a bad release file (will be added back in subseq. commit)

Issue-ID: RIC-228
Change-Id: I2fa0fcce61f54d4ce1d6176ae71eb2139b1005a5
Signed-off-by: Tommy Carpenter <tc677g@att.com>
LICENSE.txt [new file with mode: 0644]
container-release-ric-plt-xapp-frame-py.yaml [deleted file]
docs/overview.rst
docs/release-notes.rst
examples/ping_xapp.py
examples/pong_xapp.py
ricxappframe/xapp_frame.py
ricxappframe/xapp_rmr.py
setup.py
tests/test_xapp.py

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644 (file)
index 0000000..69a2cef
--- /dev/null
@@ -0,0 +1,29 @@
+
+       Unless otherwise specified, all software contained herein is licensed
+       under the Apache License, Version 2.0 (the "Software License");
+       you may not use this software except in compliance with the Software
+       License. You may obtain a copy of the Software License at
+
+               http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing, software
+       distributed under the Software License is distributed on an "AS IS" BASIS,
+       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+       See the Software License for the specific language governing permissions
+       and limitations under the Software License.
+
+
+
+       Unless otherwise specified, all documentation contained herein is licensed
+       under the Creative Commons License, Attribution 4.0 Intl. (the
+       "Documentation License"); you may not use this documentation except in
+       compliance with the Documentation License. You may obtain a copy of the
+       Documentation License at
+
+               https://creativecommons.org/licenses/by/4.0/
+
+       Unless required by applicable law or agreed to in writing, documentation
+       distributed under the Documentation License is distributed on an "AS IS"
+       BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+       implied. See the Documentation License for the specific language governing
+       permissions and limitations under the Documentation License.
diff --git a/container-release-ric-plt-xapp-frame-py.yaml b/container-release-ric-plt-xapp-frame-py.yaml
deleted file mode 100644 (file)
index a17e7a9..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
----
-pypi_project: ricxappframe
-python_version: '3.7'
-version: 0.1.0
-log_dir: ric-plt-xapp-frame-py-pypi-merge-master/2
index eca1990..43d9ab2 100644 (file)
@@ -8,7 +8,7 @@ xapp-frame-py Overview
 This library is a framework for writing Xapps in python.
 There may or may not be many Xapps written in python; however rmr, sdl, and logging libraries all exist for python, and this framework brings them together.
 
-There are (at the time of writing) two "kinds" of Xapps one can instantiate with this framework that model "push" (RMR Xapps) and "pull" (Loop Xapps), as described below.
+There are (at the time of writing) two "kinds" of Xapps one can instantiate with this framework that model "push" (RMR Xapps) and "pull" (General Xapps), as described below.
 
 RMR Xapps
 ---------
@@ -17,8 +17,8 @@ That is, every time the Xapp receives an rmr message, they do something, then wa
 This is represented by a very simple callback `consume` that is invoked every time an rmr message arrives (note, this is subject to change, with more callbacks for specific messages like `A1_POLICY_REQUEST`).
 An analogy of this is AWS Lambda: "execute this code every time an event comes in" (the code to execute can depend on the type of event).
 
-Loop Xapps
-----------
+General Xapps
+-------------
 In this class of Xapp the user simply provides a function that gets invoked, and typically that function has a `while (something)` in it.
 If the function returns, the Xapp will stop.
 In this type of Xapp, the Xapp must "pull" it's own data, typically from SDL, rmr (ie query another component for data), or other sources.
@@ -31,14 +31,18 @@ NOTE: this is an implementation detail!
 We expose this for transparency but most users will not have to worry about this.
 
 In both types of Xapp, the framework launches a seperate thread whose only job is to read from rmr and deposit all messages (and their summaries) into a thread safe queue.
-When the client Xapp reads using the framework (this read is done by the framework itself in the RMR Xapp, but by the client in a Loop Xapp), the read is done from the queue.
+When the client Xapp reads using the framework (this read is done by the framework itself in the RMR Xapp, but by the client in a general Xapp), the read is done from the queue.
 The framework is implemented this way so that a long running client function (e.g., consume) cannot block rmr reads.
 This is important because rmr is *not* a persistent message bus, if any rmr client does not read "fast enough", messages can be lost.
 So in this framework the client code is not in the same thread as the rmr reads, so that long running client code can never lead to lost messages.
 
 Examples
 --------
-There are two examples in the `examples` directory; `ping` which is a Loop Xapp, and `pong` which is an RMR Xapp. Ping sends a message, pong receives the message and use rts to reply. Ping then reads it's own mailbox and demonstrates other functionality. The highlight to note is that `pong` is purely reactive, it only does anything when a message is received. Ping is a general loop that also happens to read it's rmr mailbox inside.
+There are two examples in the `examples` directory; `ping` which is a general Xapp, and `pong` which is an RMR Xapp.
+Ping sends a message, pong receives the message and use rts to reply.
+Ping then reads it's own mailbox and demonstrates other functionality.
+The highlight to note is that `pong` is purely reactive, it only does anything when a message is received.
+Ping uses a general that also happens to read it's rmr mailbox inside.
 
 Current gaps
 ------------
index 37e7c70..1caf93a 100644 (file)
@@ -14,6 +14,20 @@ and this project adheres to `Semantic Versioning <http://semver.org/>`__.
    :depth: 3
    :local:
 
+[0.2.0] - 3/3/2020
+-------------------
+::
+
+    * now allows for RMR Xapps to call code before entering the infinite loop
+    * stop is now called before throwing NotImplemented in the case where the client fails to provide a must have callback; this ensures there is no dangling rmr thread
+    * stop now calls rmr_close to correctly free up any port(s)
+    * (breaking) renames `loop` to `entrypoint` since the function does not have to contain a loop (though it most likely does)
+    * Changes wording around the two types of xapps (docs only)
+    * Uses a new version of rmr python that crashes when the rmr mrc fails to init, which prevents an xapp trying to use an unusable rmr
+    * more unit test code coverage
+    * Adds more fields to setup like long_desc and classifiers so the pypi page looks nicer
+    * Removes a bad release file (will be added back in subseq. commit)
+
 [0.1.0] - 2/27/2020
 -------------------
 ::
index 5ad3154..d7066e9 100644 (file)
@@ -27,7 +27,7 @@ from ricxappframe.xapp_frame import Xapp
 
 # Now we use the framework to echo back the acks
 class MyXapp(Xapp):
-    def loop(self):
+    def entrypoint(self):
         my_ns = "myxapp"
         number = 0
         while True:
index 496c72e..7542c90 100644 (file)
@@ -31,6 +31,9 @@ from ricxappframe.xapp_frame import RMRXapp
 
 
 class MyXapp(RMRXapp):
+    def post_init(self):
+        print("ping xapp could do some useful stuff here!")
+
     def consume(self, summary, sbuf):
         """callbnack called for each new message"""
         print(summary)
index 94a3a3e..dba5ad9 100644 (file)
@@ -62,6 +62,19 @@ class _BaseXapp:
         # SDL
         self._sdl = SDLWrapper(use_fake_sdl)
 
+        # run the optionally provided user post init
+        self.post_init()
+
+    # Public methods to be implemented by the client
+    def post_init(self):
+        """
+        this method can optionally be implemented by the client to run code immediately after the xall initialized (but before the xapp starts it's processing loop)
+        the base method here does nothing (ie nothing is executed prior to starting if the client does not implement this)
+        """
+        pass
+
+    # Public rmr methods
+
     def rmr_get_messages(self):
         """
         returns a generator iterable over all current messages in the queue that have not yet been read by the client xapp
@@ -258,6 +271,7 @@ class RMRXapp(_BaseXapp):
         sbuf: ctypes c_void_p
             Pointer to an rmr message buffer. The user must call free on this when done.
         """
+        self.stop()
         raise NotImplementedError()
 
     def run(self):
@@ -278,15 +292,15 @@ class Xapp(_BaseXapp):
     Represents an xapp where the client provides a generic function to call, which is mostly likely a loop-forever loop
     """
 
-    def loop(self):
+    def entrypoint(self):
         """
-        This function is to be implemented by the client and is called
+        This function is to be implemented by the client and is called after post_init
         """
+        self.stop()
         raise NotImplementedError()
 
     def run(self):
         """
-        This function should be called when the client xapp is ready to start their loop
-        This is simple and the client could just call self.loop(), however this gives a consistent interface as the other xapps
+        This function should be called when the client xapp is ready to start their code
         """
-        self.loop()
+        self.entrypoint()
index ab13369..601e65a 100644 (file)
@@ -66,14 +66,16 @@ class RmrLoop:
                 time.sleep(0.1)
 
         # Private
-        self._keep_going = True
-        self._last_ran = time.time()
+        self._keep_going = True  # used to tell this thread to stop it's work
+        self._last_ran = time.time()  # used for healthcheck
+        self._loop_is_running = False  # used in stop to know when it's safe to kill the mrc
 
         # start the work loop
         mdc_logger.debug("Starting loop thread")
 
         def loop():
             mdc_logger.debug("Work loop starting")
+            self._loop_is_running = True
             while self._keep_going:
 
                 # read our mailbox
@@ -86,6 +88,8 @@ class RmrLoop:
 
                 self._last_ran = time.time()
 
+            self._loop_is_running = False
+
         self._thread = Thread(target=loop)
         self._thread.start()
 
@@ -94,7 +98,15 @@ class RmrLoop:
         sets a flag that will cleanly stop the thread
         note, this does not yet have a use yet for xapps to call, however this is very handy during unit testing.
         """
+        mdc_logger.debug("Stopping rmr thread. Waiting for last iteration to finish..")
         self._keep_going = False
+        # wait until the current batch of messages is done, then kill the rmr connection
+        # note; I debated putting this in "loop" however if the while loop was still going setting mrc to close here would blow up any processing still currently happening
+        # probably more polite to at least finish the current batch and then close. So here we wait until the current batch is done, then we kill the mrc
+        while self._loop_is_running:
+            pass
+        mdc_logger.debug("Closing rmr connection")
+        rmr.rmr_close(self.mrc)
 
     def healthcheck(self, seconds=30):
         """
index f4ba164..ee76260 100644 (file)
--- a/setup.py
+++ b/setup.py
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 # ==================================================================================
+from os.path import dirname, abspath, join as path_join
 from setuptools import setup, find_packages
 
+SETUP_DIR = abspath(dirname(__file__))
+
+
+def _long_descr():
+    """Yields the content of documentation files for the long description"""
+    try:
+        doc_path = path_join(SETUP_DIR, "docs/overview.rst")
+        with open(doc_path) as f:
+            return f.read()
+    except FileNotFoundError:  # this happens during unit testing, we don't need it
+        return ""
+
+
 setup(
     name="ricxappframe",
-    version="0.1.0",
+    version="0.2.0",
     packages=find_packages(exclude=["tests.*", "tests"]),
     author="Tommy Carpenter",
     description="Xapp framework for python",
     url="https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/xapp-frame-py",
-    install_requires=["msgpack", "rmr>=2.2.0, <3.0.0", "mdclogpy", "ricsdl>=2.0.3,<3.0.0"],
+    install_requires=["msgpack", "rmr>=2.2.1, <3.0.0", "mdclogpy", "ricsdl>=2.0.3,<3.0.0"],
+    classifiers=[
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Telecommunications Industry",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.7",
+        "License :: OSI Approved :: Apache Software License",
+        "Operating System :: POSIX :: Linux",
+        "Topic :: Communications",
+    ],
+    python_requires=">=3.7",
+    keywords="RIC xapp",
+    license="Apache 2.0",
+    data_files=[("", ["LICENSE.txt"])],
+    long_description=_long_descr(),
+    long_description_content_type="text/x-rst",
 )
index 97448af..def7904 100644 (file)
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 # ==================================================================================
+import pytest
+from rmr.exceptions import InitFailed
 from ricxappframe.xapp_frame import Xapp
 
 gen_xapp = None
 rmr_xapp = None
 
 
+def test_bad_gen_xapp():
+    """test that an xapp that does not implement entrypoint blows up"""
+
+    class MyXapp(Xapp):
+        def post_init(self):
+            pass
+
+    with pytest.raises(NotImplementedError):
+        # missing entrypoint
+        bad_xapp = MyXapp(rmr_wait_for_ready=False, use_fake_sdl=True)
+        bad_xapp.run()
+
+
+def test_bad_init():
+    """test that an xapp whose rmr fails to init blows up"""
+
+    class MyXapp(Xapp):
+        def entrypoint(self):
+            pass
+
+    with pytest.raises(InitFailed):
+        bad_xapp = MyXapp(rmr_port=-1)
+        bad_xapp.run()  # we wont get here
+
+
 def test_init_general_xapp():
     class MyXapp(Xapp):
-        # TODO: obviouslly a lot more is needed here. For now this tests that the class is instantiable.
-        def loop(self):
-            print("ok")
+        def post_init(self):
+            self.sdl_set("testns", "mykey", 6)
+
+        def entrypoint(self):
+            assert self.sdl_get("testns", "mykey") == 6
+            assert self.sdl_find_and_get("testns", "myk") == {"mykey": 6}
+            assert self.healthcheck()
+            # normally we would have some kind of loop here
+            print("bye")
 
     global gen_xapp
-    gen_xapp = MyXapp(rmr_wait_for_ready=False)
+    gen_xapp = MyXapp(rmr_wait_for_ready=False, use_fake_sdl=True)
     gen_xapp.run()