Bump version to 2.1.8.
No functional changes to A1 behavior.
Signed-off-by: Lott, Christopher (cl778h) <cl778h@att.com>
Change-Id: I4486b5bec68017a3c94f13c7d1a53977a0ef9940
# 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
# 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
# ==================================================================================
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
-"""
-contains the app; broken out here for ease of unit testing
-"""
# ==================================================================================
# Copyright (c) 2019 Nokia
# Copyright (c) 2018-2019 AT&T Intellectual Property.
# 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
-"""
-a1s rmr functionality
-"""
# ==================================================================================
# Copyright (c) 2019-2020 Nokia
# Copyright (c) 2018-2020 AT&T Intellectual Property.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==================================================================================
+"""
+A1 RMR functionality
+"""
import os
import queue
import time
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()
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:
-"""
-Main a1 controller
-"""
# ==================================================================================
# Copyright (c) 2019-2020 Nokia
# Copyright (c) 2018-2020 AT&T Intellectual Property.
# 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
-"""
-Represents A1s database and database access functions.
-"""
# ==================================================================================
# Copyright (c) 2019-2020 Nokia
# Copyright (c) 2018-2020 AT&T Intellectual Property.
# 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
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))
-"""
-rmr messages
-"""
# ==================================================================================
# Copyright (c) 2019 Nokia
# Copyright (c) 2018-2019 AT&T Intellectual Property.
# 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):
-"""
-A1 entrypoint
-"""
# ==================================================================================
# Copyright (c) 2019 Nokia
# Copyright (c) 2018-2019 AT&T Intellectual Property.
# 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
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()
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:
::
::
- docker build --no-cache -t a1test:latest -f Dockerfile-Unit-Test
+ docker build --no-cache -f Dockerfile-Unit-Test .
Integration testing
-------------------
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
: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
--------------------
* Ensure that policy type ID on path matches ID in object
* Add OpenAPI spec to RST documentation
+
[2.1.6] - 4/7/2020
-------------------
::
--- /dev/null
+# Trivial RMR route table
+newrt | start
+rte | 1 | app10:4560
+newrt | end
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"]},