Extend Dockerfile and improve documentation
Bump version to 2.1.8.
No functional changes to A1 behavior.
Signed-off-by: Lott, Christopher (cl778h) <cl778h@att.com>
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 @@
# 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 @@
# 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 @@
# 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 @@
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 @@
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 @@
# 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 @@
# 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 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 @@
# 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 @@
# 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 @@
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 @@
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 @@
::
- 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 @@
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 -dt -p 10000:10000 -v /path/to/localrt:/opt/route/local.rt a1:X.Y.Z -v
+ 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:
+
+::
+
+ 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 @@
: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 @@
* 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 @@
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"]},