Extend Dockerfile and improve documentation 36/3536/4
authorLott, Christopher (cl778h) <cl778h@att.com>
Wed, 29 Apr 2020 19:23:28 +0000 (15:23 -0400)
committerLott, Christopher (cl778h) <cl778h@att.com>
Wed, 29 Apr 2020 20:02:06 +0000 (16:02 -0400)
Bump version to 2.1.8.
No functional changes to A1 behavior.

Signed-off-by: Lott, Christopher (cl778h) <cl778h@att.com>
Change-Id: I4486b5bec68017a3c94f13c7d1a53977a0ef9940

13 files changed:
Dockerfile
Dockerfile-Unit-Test
a1/__init__.py
a1/a1rmr.py
a1/controller.py
a1/data.py
a1/messages.py
a1/run.py
docs/developer-guide.rst
docs/installation-guide.rst
docs/release-notes.rst
local.rt [new file with mode: 0644]
setup.py

index c8ef420..e5eeb1b 100644 (file)
 # This container uses a 2 stage build!
 # Tips and tricks were learned from: https://pythonspeed.com/articles/multi-stage-docker-python/
 FROM python:3.8-alpine AS compile-image
+# upgrade pip as root
+RUN pip install --upgrade pip
 # Gevent needs gcc, make, file, ffi
 RUN apk update && apk add gcc musl-dev make file libffi-dev
-
-# Switch to a non-root user for security reasons
-# This is only really needed in stage 2 however this makes the copying easier and straitforward! --user doesn't do the same thing if run as root!
+# create a non-root user.  Only really needed in stage 2,
+# however this makes the copying easier and straighttforward;
+# pip option --user doesn't do the same thing if run as root
 RUN addgroup -S a1user && adduser -S -G a1user a1user
+# switch to the non-root user for installing site packages
 USER a1user
-
-# Speed hack; we install gevent FIRST because when building repeatedly (eg during dev) and only changing a1 code, we do not need to keep compiling gevent which takes forever
-RUN pip install --upgrade pip && pip install --user gevent
+# Speed hack; we install gevent before a1 because when building repeatedly (eg during dev)
+# and only changing a1 code, we do not need to keep compiling gevent which takes forever
+RUN pip install --user gevent
 COPY setup.py /home/a1user/
 COPY a1/ /home/a1user/a1
 RUN pip install --user /home/a1user
@@ -39,21 +42,23 @@ FROM python:3.8-alpine
 # copy rmr libraries from builder image in lieu of an Alpine package
 COPY --from=nexus3.o-ran-sc.org:10002/o-ran-sc/bldr-alpine3-rmr:4.0.2 /usr/local/lib64/librmr* /usr/local/lib64/
 
-# dir that rmr routing file temp goes into
-RUN mkdir -p /opt/route/
-# python copy; this basically makes the 2 stage python build work
+# copy python modules; this makes the 2 stage python build work
 COPY --from=compile-image /home/a1user/.local /home/a1user/.local
-
-# Switch to a non-root user for security reasons. a1 does not currently write into any dirs so no chowns are needed at this time.
+# create mount point for dir with rmr routing file as named below
+RUN mkdir -p /opt/route/
+# create a non-root user
 RUN addgroup -S a1user && adduser -S -G a1user a1user
+# ensure the non-root user can read python files
+RUN chown -R a1user:a1user /home/a1user/.local
+# switch to the non-root user for security reasons
 USER a1user
 # misc setups
 EXPOSE 10000
 ENV LD_LIBRARY_PATH /usr/local/lib/:/usr/local/lib64
 ENV RMR_SEED_RT /opt/route/local.rt
 ENV PYTHONUNBUFFERED 1
-# This step is critical
+# pip installs console script to ~/.local/bin so PATH is critical
 ENV PATH=/home/a1user/.local/bin:$PATH
 
 # Run!
-CMD run.py
+CMD run-a1
index 8aba74f..a9ea22a 100644 (file)
 # ==================================================================================
 FROM python:3.8-alpine
 
-# copy rmr libraries from builder image in lieu of an Alpine package
-COPY --from=nexus3.o-ran-sc.org:10002/o-ran-sc/bldr-alpine3-rmr:4.0.2 /usr/local/lib64/librmr* /usr/local/lib64/
-
-# dir that rmr routing file temp goes into
-RUN mkdir -p /opt/route/
-
 # Gevent needs gcc, make, file, ffi
 RUN apk update && apk add gcc musl-dev make file libffi-dev
 
 # Upgrade pip, install tox (gevent is installed as a speed hack in local dev where tox is run many times)
 RUN pip install --upgrade pip && pip install tox gevent
 
+# copy rmr libraries from builder image in lieu of an Alpine package
+COPY --from=nexus3.o-ran-sc.org:10002/o-ran-sc/bldr-alpine3-rmr:4.0.2 /usr/local/lib64/librmr* /usr/local/lib64/
+
 # copies
 COPY a1/ /tmp/a1
 COPY tests/ /tmp/tests
index 818ed1a..76a9dac 100644 (file)
@@ -1,6 +1,3 @@
-"""
-contains the app; broken out here for ease of unit testing
-"""
 # ==================================================================================
 #       Copyright (c) 2019 Nokia
 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
@@ -17,6 +14,9 @@ contains the app; broken out here for ease of unit testing
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 # ==================================================================================
+"""
+contains the app; broken out here for ease of unit testing
+"""
 import connexion
 
 
index 3d0ff95..d6bd685 100644 (file)
@@ -1,6 +1,3 @@
-"""
-a1s rmr functionality
-"""
 # ==================================================================================
 #       Copyright (c) 2019-2020 Nokia
 #       Copyright (c) 2018-2020 AT&T Intellectual Property.
@@ -17,6 +14,9 @@ a1s rmr functionality
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 # ==================================================================================
+"""
+A1 RMR functionality
+"""
 import os
 import queue
 import time
@@ -43,13 +43,27 @@ __RMR_LOOP__ = None
 
 class _RmrLoop:
     """
-    class represents an rmr loop that constantly reads from rmr and performs operations based on waiting messages
-    this launches a thread, it should probably only be called once; the public facing method to access these ensures this
+    Class represents an rmr loop that constantly reads from rmr and performs operations
+    based on waiting messages.  This launches a thread, it should probably only be called
+    once; the public facing method to access these ensures this.
 
     TODO: the xapp frame has a version of this looping structure. See if A1 can switch to that.
     """
 
     def __init__(self, init_func_override=None, rcv_func_override=None):
+        """
+        Init
+
+        Parameters
+        ----------
+        init_func_override: function (optional)
+            Function that initializes RMR and answers an RMR context.
+            Supply an empty function to skip initializing RMR.
+
+        rcv_func_override: function (optional)
+            Function that receives messages from RMR and answers a list.
+            Supply a trivial function to skip reading from RMR.
+        """
         self.keep_going = True
         self.rcv_func = None
         self.last_ran = time.time()
@@ -199,6 +213,16 @@ class _RmrLoop:
 def start_rmr_thread(init_func_override=None, rcv_func_override=None):
     """
     Start a1s rmr thread
+
+    Parameters
+    ----------
+    init_func_override: function (optional)
+        Function that initializes RMR and answers an RMR context.
+        Supply an empty function to skip initializing RMR.
+
+    rcv_func_override: function (optional)
+        Function that receives messages from RMR and answers a list.
+        Supply a trivial function to skip reading from RMR.
     """
     global __RMR_LOOP__
     if __RMR_LOOP__ is None:
index 5daa8a5..23ef804 100644 (file)
@@ -1,6 +1,3 @@
-"""
-Main a1 controller
-"""
 # ==================================================================================
 #       Copyright (c) 2019-2020 Nokia
 #       Copyright (c) 2018-2020 AT&T Intellectual Property.
@@ -17,6 +14,9 @@ Main a1 controller
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 # ==================================================================================
+"""
+Main a1 controller
+"""
 from jsonschema import validate
 from jsonschema.exceptions import ValidationError
 import connexion
index dfe9dd2..ca0acb3 100644 (file)
@@ -1,6 +1,3 @@
-"""
-Represents A1s database and database access functions.
-"""
 # ==================================================================================
 #       Copyright (c) 2019-2020 Nokia
 #       Copyright (c) 2018-2020 AT&T Intellectual Property.
@@ -17,6 +14,9 @@ Represents A1s database and database access functions.
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 # ==================================================================================
+"""
+Represents A1s database and database access functions.
+"""
 import os
 import time
 from threading import Thread
@@ -24,7 +24,6 @@ from mdclogpy import Logger
 from ricxappframe.xapp_sdl import SDLWrapper
 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, CantDeleteNonEmptyType
 
-
 # constants
 INSTANCE_DELETE_NO_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_NO_RESP_TTL", 5))
 INSTANCE_DELETE_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_RESP_TTL", 5))
index cbcea08..fc22bcb 100644 (file)
@@ -1,6 +1,3 @@
-"""
-rmr messages
-"""
 # ==================================================================================
 #       Copyright (c) 2019 Nokia
 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
@@ -17,6 +14,9 @@ rmr messages
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 # ==================================================================================
+"""
+rmr messages
+"""
 
 
 def a1_to_handler(operation, policy_type_id, policy_instance_id, payload=None):
index feaada3..122684a 100644 (file)
--- a/a1/run.py
+++ b/a1/run.py
@@ -1,6 +1,3 @@
-"""
-A1 entrypoint
-"""
 # ==================================================================================
 #       Copyright (c) 2019 Nokia
 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
@@ -17,6 +14,10 @@ A1 entrypoint
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 # ==================================================================================
+"""
+A1 entrypoint
+"""
+from os import environ
 from gevent.pywsgi import WSGIServer
 from mdclogpy import Logger
 from a1 import app
@@ -28,11 +29,14 @@ mdc_logger = Logger(name=__name__)
 
 def main():
     """Entrypoint"""
+    mdc_logger.debug("A1Mediator starts")
     # start rmr thread
-    mdc_logger.debug("Initializing rmr thread. A1s webserver will not start until rmr initialization is complete.")
+    mdc_logger.debug("Starting RMR thread with RMR_RTG_SVC {0}, RMR_SEED_RT {1}".format(environ.get('RMR_RTG_SVC'), environ.get('RMR_SEED_RT')))
+    mdc_logger.debug("RMR initialization must complete before webserver can start")
     a1rmr.start_rmr_thread()
-
+    mdc_logger.debug("RMR initialization complete")
     # start webserver
-    mdc_logger.debug("Starting gevent server")
-    http_server = WSGIServer(("", 10000), app)
+    port = 10000
+    mdc_logger.debug("Starting gevent webserver on port {0}".format(port))
+    http_server = WSGIServer(("", port), app)
     http_server.serve_forever()
index 4c0d38d..d9a3a8a 100644 (file)
@@ -60,10 +60,13 @@ If you want to update the version of python itself (ie just done from 37 to 38):
 Unit Testing
 ------------
 
-Note, before this will work, for the first time on the machine running
-the tests, run ``./install_deps.sh``. This is only needed once on the
-machine.  Also, this requires the python packages ``tox`` and
-``pytest``.
+Running the unit tests requires the python packages ``tox`` and ``pytest``.
+
+The RMR library is also required during unit tests. If running directly from tox
+(outside a Docker container), install RMR using the script in the integration_tests
+directory: ``install_rmr.sh``.
+
+Upon completion, view the test coverage like this:
 
 ::
 
@@ -75,7 +78,7 @@ less nice because you don't get the pretty HTML)
 
 ::
 
-   docker build  --no-cache -t a1test:latest -f Dockerfile-Unit-Test
+   docker build  --no-cache -f Dockerfile-Unit-Test .
 
 Integration testing
 -------------------
index ff12eca..abada0f 100644 (file)
@@ -27,18 +27,38 @@ The "real" helm chart for A1 is in the LF it/dep repo. That repo holds all of th
 Local Docker
 -------------
 
-building
-~~~~~~~~
+Build the image
+~~~~~~~~~~~~~~~
 ::
 
-   docker build --no-cache -t a1:X.Y.Z .
+   docker build --no-cache -t a1:latest .
 
 .. _running-1:
 
-running
-~~~~~~~
+Start the container
+~~~~~~~~~~~~~~~~~~~
+
+A sample RMR routing table is supplied here in file `local.rt` for mounting as a volume:
+
+::
+
+   docker run -p 10000:10000 -v /path/to/local.rt:/opt/route/local.rt a1:latest
+
+View container API
+~~~~~~~~~~~~~~~~~~
+
+A web user interface generated from the OpenAPI specification can be accessed at this URL:
 
 ::
 
-   docker run -dt -p 10000:10000 -v /path/to/localrt:/opt/route/local.rt a1:X.Y.Z -v
+    http://docker-host-name-or-ip:10000/ui
+
+Check container health
+~~~~~~~~~~~~~~~~~~~~~~
+
+The following command requests the container health.  This requires a Storage Data Layer
+(SDL) service; expect internal server error if that service is not available/running.
+
+::
 
+    curl docker-host-name-or-ip:10000/a1-p/healthcheck
index 14d0612..f793d02 100644 (file)
@@ -14,6 +14,17 @@ and this project adheres to `Semantic Versioning <http://semver.org/>`__.
    :depth: 3
    :local:
 
+[2.1.8] - 2020-04-29
+--------------------
+
+* Revise Dockerfile to set user as owner of .local dir with a1 package
+* Rename console shell start script to run-a1 from run.py 
+* Extend start script to report webserver listening port
+* Add tiny RMR routing table for use in demo and test
+* Extend documentation for running a container locally
+* Add documentation of start/init parameters to _RmrLoop class
+
+
 [2.1.7] - 2020-04-28
 --------------------
 
@@ -23,6 +34,7 @@ and this project adheres to `Semantic Versioning <http://semver.org/>`__.
 * Ensure that policy type ID on path matches ID in object
 * Add OpenAPI spec to RST documentation
 
+
 [2.1.6] - 4/7/2020
 -------------------
 ::
diff --git a/local.rt b/local.rt
new file mode 100644 (file)
index 0000000..02816b5
--- /dev/null
+++ b/local.rt
@@ -0,0 +1,4 @@
+# Trivial RMR route table
+newrt | start
+rte   | 1 | app10:4560
+newrt | end
index 93728b2..cec529d 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -18,12 +18,12 @@ from setuptools import setup, find_packages
 
 setup(
     name="a1",
-    version="2.1.7",
+    version="2.1.8",
     packages=find_packages(exclude=["tests.*", "tests"]),
     author="Tommy Carpenter",
     description="RIC A1 Mediator for policy/intent changes",
     url="https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/a1",
-    entry_points={"console_scripts": ["run.py=a1.run:main"]},
+    entry_points={"console_scripts": ["run-a1=a1.run:main"]},
     # we require jsonschema, should be in that list, but connexion already requires a specific version of it
     install_requires=["requests", "Flask", "connexion[swagger-ui]", "gevent", "mdclogpy", "ricxappframe>=1.0.0,<2.0.0"],
     package_data={"a1": ["openapi.yaml"]},