From: Lott, Christopher (cl778h) Date: Wed, 29 Apr 2020 19:23:28 +0000 (-0400) Subject: Extend Dockerfile and improve documentation X-Git-Tag: 2.1.8~3 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=fe30c1726c397b1ee100cd3d3795758e7d21b6bf;p=ric-plt%2Fa1.git Extend Dockerfile and improve documentation Bump version to 2.1.8. No functional changes to A1 behavior. Signed-off-by: Lott, Christopher (cl778h) Change-Id: I4486b5bec68017a3c94f13c7d1a53977a0ef9940 --- diff --git a/Dockerfile b/Dockerfile index c8ef420..e5eeb1b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,16 +18,19 @@ # 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 diff --git a/Dockerfile-Unit-Test b/Dockerfile-Unit-Test index 8aba74f..a9ea22a 100644 --- a/Dockerfile-Unit-Test +++ b/Dockerfile-Unit-Test @@ -16,18 +16,15 @@ # ================================================================================== 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 diff --git a/a1/__init__.py b/a1/__init__.py index 818ed1a..76a9dac 100644 --- a/a1/__init__.py +++ b/a1/__init__.py @@ -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 diff --git a/a1/a1rmr.py b/a1/a1rmr.py index 3d0ff95..d6bd685 100644 --- a/a1/a1rmr.py +++ b/a1/a1rmr.py @@ -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: diff --git a/a1/controller.py b/a1/controller.py index 5daa8a5..23ef804 100644 --- a/a1/controller.py +++ b/a1/controller.py @@ -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 diff --git a/a1/data.py b/a1/data.py index dfe9dd2..ca0acb3 100644 --- a/a1/data.py +++ b/a1/data.py @@ -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)) diff --git a/a1/messages.py b/a1/messages.py index cbcea08..fc22bcb 100644 --- a/a1/messages.py +++ b/a1/messages.py @@ -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): diff --git a/a1/run.py b/a1/run.py index feaada3..122684a 100644 --- 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() diff --git a/docs/developer-guide.rst b/docs/developer-guide.rst index 4c0d38d..d9a3a8a 100644 --- a/docs/developer-guide.rst +++ b/docs/developer-guide.rst @@ -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 ------------------- diff --git a/docs/installation-guide.rst b/docs/installation-guide.rst index ff12eca..abada0f 100644 --- a/docs/installation-guide.rst +++ b/docs/installation-guide.rst @@ -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 diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 14d0612..f793d02 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -14,6 +14,17 @@ and this project adheres to `Semantic Versioning `__. :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 `__. * 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 index 0000000..02816b5 --- /dev/null +++ b/local.rt @@ -0,0 +1,4 @@ +# Trivial RMR route table +newrt | start +rte | 1 | app10:4560 +newrt | end diff --git a/setup.py b/setup.py index 93728b2..cec529d 100644 --- 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"]},