Version 0.0.1 of an xApplication based on the Python xApp framework.
This mockup listens for a QoE prediction request (RMR message type
30001) as sent by the QP Driver xApp. Reacts by sending a fixed QoE
prediction (RMR message type 30002), which should be routed to the
Traffic Steering xApp.
Includes a static RMR routing table with this entry for TS:
rte|30002|service-ricxapp-trafficxapp-rmr.ricxapp.svc.cluster.local:4560
Issue-ID: RICAPP-107
Signed-off-by: Lott, Christopher (cl778h) <cl778h@att.com>
Change-Id: Ie6a6a851dff87c23a0bcaf23d9936e35d0cb3dd9
--- /dev/null
+# https://help.github.com/articles/dealing-with-line-endings/
+
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized
+# and converted to native line endings on checkout.
+*.css text
+*.htm text diff=html
+*.html text diff=html
+*.java text diff=java
+*.js text
+*.jsp text
+*.less text
+*.properties text
+*.sql text
+*.xml text
+
+# Denote all files that are truly binary and should not be modified.
+*.png binary
+*.jpg binary
--- /dev/null
+# misc cruft
+*.log
+log.txt
+rmr/*
+docs_and_diagrams/
+
+# documentation
+.tox
+docs/_build/
+
+# standard python ignore template
+.pytest_cache/
+xunit-results.xml
+.DS_Store
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+venv-tox/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# IPython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# dotenv
+.env
+
+# virtualenv
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+
+# Rope project settings
+.ropeproject
+
+# Test report
+xunit-reports
+coverage-reports
+
+# Eclipse
+.project
+.pydevproject
+.settings
-
- [gerrit]
- host=gerrit.o-ran-sc.org
- port=29418
- project=ric-app/qp
- defaultbranch=master
-
\ No newline at end of file
+[gerrit]
+host=gerrit.o-ran-sc.org
+port=29418
+project=ric-app/qp
+defaultbranch=master
+defaultremote=origin
--- /dev/null
+---
+# .readthedocs.yml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+# Required
+version: 2
+
+formats:
+ - htmlzip
+
+build:
+ image: latest
+
+python:
+ version: 3.7
+ install:
+ - requirements: docs/requirements-docs.txt
+
+sphinx:
+ configuration: docs/conf.py
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
+FROM python:3.8-alpine
+
+# RMR setup
+RUN mkdir -p /opt/route/
+# copy rmr files 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.5 /usr/local/lib64/librmr* /usr/local/lib64/
+# rmr_probe replaced health_ck
+COPY --from=nexus3.o-ran-sc.org:10002/o-ran-sc/bldr-alpine3-rmr:4.0.5 /usr/local/bin/rmr* /usr/local/bin/
+ENV LD_LIBRARY_PATH /usr/local/lib/:/usr/local/lib64
+COPY tests/fixtures/local.rt /opt/route/local.rt
+ENV RMR_SEED_RT /opt/route/local.rt
+
+# sdl needs gcc
+RUN apk update && apk add gcc musl-dev
+
+# Install
+COPY setup.py /tmp
+COPY LICENSE.txt /tmp/
+COPY qp/ /tmp/qp
+RUN pip install /tmp
+
+# Run
+ENV PYTHONUNBUFFERED 1
+CMD run-qp.py
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
+FROM python:3.8-alpine
+
+# sdl uses hiredis which needs gcc
+RUN apk update && apk add gcc musl-dev
+
+# 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.5 /usr/local/lib64/librmr* /usr/local/lib64/
+
+# Upgrade pip, install tox
+RUN pip install --upgrade pip && pip install tox
+
+# copies
+COPY setup.py tox.ini LICENSE.txt /tmp/
+COPY qp/ /tmp/qp
+COPY tests/ /tmp/tests
+RUN pip install /tmp
+
+# Run the unit tests
+WORKDIR /tmp
+RUN tox -e code,flake8
--- /dev/null
+
+ 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.
--- /dev/null
+# The Jenkins job uses this string for the tag in the image name
+# for example nexus3.o-ran-sc.org:10004/my-image-name:my-tag
+---
+tag: 0.0.1
--- /dev/null
+from docs_conf.conf import *
+
+linkcheck_ignore = ["http://localhost.*", "http://127.0.0.1.*", "https://gerrit.o-ran-sc.org.*"]
--- /dev/null
+---
+project_cfg: oran
+project: ric-app-qp
--- /dev/null
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2020 AT&T Intellectual Property
+
+
+Developers Guide
+=================
+
+.. contents::
+ :depth: 3
+ :local:
+
+
+Version bumping the Xapp
+------------------------
+
+This project follows semver. When changes are made, update the version strings in:
+
+#. ``container-tag.yaml``
+#. ``docs/release-notes.rst``
+#. ``setup.py``
+
+
+Testing RMR Healthcheck
+-----------------------
+The following instructions should deploy the QP container in bare docker, and allow you
+to test that the RMR healthcheck is working.
+
+::
+
+ docker build -t qpd:latest -f Dockerfile .
+ docker run -d --net=host -e USE_FAKE_SDL=1 qpd:latest
+ docker exec -it CONTAINER_ID /usr/local/bin/rmr_probe -h 127.0.0.1:4562
+
+Unit Testing
+------------
+
+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 according to its instructions.
+
+Upon completion, view the test coverage like this:
+
+::
+
+ tox
+ open htmlcov/index.html
+
+Alternatively, if you cannot install RMR locally, you can run the unit
+tests in Docker. This is somewhat less nice because you don't get the
+pretty HTML report on coverage.
+
+::
+
+ docker build --no-cache -f Dockerfile-Unit-Test .
--- /dev/null
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2020 AT&T Intellectual Property
+
+
+QoE Predictor xApp
+==================
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ overview.rst
+ developers-guide.rst
+ release-notes.rst
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
--- /dev/null
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2020 AT&T Intellectual Property
+
+QoE Predictor Overview
+======================
+
+QoE Predictor (QP) is an Xapp in the Traffic Steering O-RAN use case,
+which uses the following Xapps:
+
+#. Traffic Steering, which sends prediction requests to QP Driver.
+#. QP Driver, which fetches data from SDL on behalf of traffic steering,
+ both UE Data and Cell Data, merges that data together, then sends off
+ the data to the QoE Predictor.
+#. QoE Predictor, which predicts and sends that prediction back to Traffic Steering
+#. KPIMONN, which populates SDL in the first place.
+
+Expected Input
+--------------
+
+The QP Xapp expects a prediction-request JSON message via RMR with the following structure::
+
+ {
+ "predictionUE": "UEId1",
+ "ueMeasurements" :
+ { "servingCellId" : "CID2",
+ "measTimestampUePrbUsage" : TS1,
+ "measPeriodUePrbUsage" : Int,
+ "uePrbUsageDL" : Int,
+ "uePrbUsageUL" : Int,
+ "measTimestampUePdcpBytes" : TS2,
+ "measPeriodUePdcpByes" : Int,
+ "uePdcpBytesDL": Int,
+ "uePdcpBytesUL" : Int
+ },
+ "cellMeasurements" : [
+ {
+ "cellId" : "CID2",
+ "measTimestampPrbAvailable" : TS,
+ "measPeriodPrbAvailable" : Int,
+ "prbAvailableDL" : Int,
+ "prbAvailableUL" : Int,
+ "measTimestampPdcpBytes" : TS,
+ "measPeriodPdcpBytes" : Int,
+ "pdcpBytesDL" : 30000000,
+ "pdcpBytesUL" : 5000000,
+ "measTimestampRf" : TS,
+ "measPeriodRf" : Int,
+ "rfMeasurements" : {
+ "rsrp": Int,
+ "rsrq": Int,
+ "rsSinr": Int
+ }
+ },
+ {
+ "cellId" : "CID1",
+ "measTimestampPrbAvailable" : TS,
+ "measPeriodPrbAvailable" : Int,
+ "prbAvailableDL" : Int,
+ "prbAvailableUL" : Int,
+ "measTimestampPdcpBytes" : TS,
+ "measPeriodPdcpBytes" : Int,
+ "pdcpBytesDL" : 10000000,
+ "pdcpBytesUL" : 2000000,
+ "measTimestampRf" : TS,
+ "measPeriodRf" : Int,
+ "rfMeasurements" : {
+ "rsrp": Int,
+ "rsrq": Int,
+ "rsSinr": Int
+ }
+ },
+ {
+ "cellId" : "CID3",
+ "measTimestampPrbAvailable" : TS,
+ "measPeriodPrbAvailable" : Int,
+ "prbAvailableDL" : Int,
+ "prbAvailableUL" : Int,
+ "measTimestampPdcpBytes" : TS,
+ "measPeriodPdcpBytes" : Int,
+ "pdcpBytesDL" : 50000000,
+ "pdcpBytesUL" : 4000000,
+ "measTimestampRf" : TS,
+ "measPeriodRf" : Int,
+ "rfMeasurements" : {
+ "rsrp": Int,
+ "rsrq": Int,
+ "rsSinr": Int
+ }
+ }
+ ]
+ }
+
+
+Expected Output
+---------------
+
+The QP Xapp should send a prediction for both downlink and uplink throughput
+as a JSON message via RMR with the following structure::
+
+ {
+ "UEId1": {
+ "CID1" : [10000000,2000000],
+ "CID2" : [30000000,5000000],
+ "CID3" : [50000000,4000000]
+ }
+ }
+
+
--- /dev/null
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2020 AT&T Intellectual Property
+
+Release Notes
+===============
+
+All notable changes to this project will be documented in this file.
+
+The format is based on `Keep a Changelog <http://keepachangelog.com/>`__
+and this project adheres to `Semantic Versioning <http://semver.org/>`__.
+
+[0.0.1] - 2020-05-21
+--------------------
+* Initial mock version (`RICAPP-107 <https://jira.o-ran-sc.org/browse/RICAPP-107>`_)
--- /dev/null
+sphinx
+sphinx-rtd-theme
+sphinxcontrib-httpdomain
+recommonmark
+lfdocs-conf
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
+"""
+mock qp module
+
+RMR Messages:
+ #define TS_QOE_PRED_REQ 30001
+ #define TS_QOE_PREDICTION 30002
+30001 is the message type QP receives from the driver;
+sends out type 30002 which should be routed to TS.
+
+"""
+
+
+import os
+from mdclogpy import Logger
+from ricxappframe.xapp_frame import RMRXapp, rmr
+
+
+qp_xapp = None
+logger = Logger(name=__name__)
+
+
+def post_init(self):
+ self.predict_requests = 0
+ logger.debug("QP xApp started")
+
+
+def qp_default_handler(self, summary, sbuf):
+ logger.debug("default handler received message type {}".format(summary[rmr.RMR_MS_MSG_TYPE]))
+ # we don't use rts here; free this
+ self.rmr_free(sbuf)
+
+
+def qp_predict_handler(self, summary, sbuf):
+ logger.debug("predict handler received message type {}".format(summary[rmr.RMR_MS_MSG_TYPE]))
+ self.predict_requests += 1
+ # we don't use rts here; free this
+ self.rmr_free(sbuf)
+ # send a mock message
+ mock_msg = '{ "12345" : { "310-680-200-555001" : [ 2000000 , 1200000 ] , "310-680-200-555002" : [ 800000 , 400000 ] , "310-680-200-555003" : [ 800000 , 400000 ] } }'
+ ok = self.rmr_send(mock_msg.encode(), 30002)
+ if ok:
+ logger.debug("predict handler: sent message successfully")
+ else:
+ logger.warn("predict handler: failed to send message")
+
+
+def start(thread=False):
+ """
+ This is a convenience function that allows this xapp to run in Docker
+ for "real" (no thread, real SDL), but also easily modified for unit testing
+ (e.g., use_fake_sdl). The defaults for this function are for the Dockerized xapp.
+ """
+ logger.debug("QP xApp starting")
+ global qp_xapp
+ fake_sdl = os.environ.get("USE_FAKE_SDL", None)
+ qp_xapp = RMRXapp(qp_default_handler, post_init=post_init, use_fake_sdl=True if fake_sdl else False)
+ qp_xapp.register_callback(qp_predict_handler, 30001)
+ qp_xapp.run(thread)
+
+
+def stop():
+ """
+ can only be called if thread=True when started
+ TODO: could we register a signal handler for Docker SIGTERM that calls this?
+ """
+ qp_xapp.stop()
+
+
+def get_stats():
+ # hacky for now, will evolve
+ return {"PredictRequests": qp_xapp.predict_requests}
--- /dev/null
+# CI script installs RMR from PackageCloud using this version
+---
+version: 4.0.5
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
+from setuptools import setup, find_packages
+
+setup(
+ name="qp",
+ version="0.0.1",
+ packages=find_packages(exclude=["tests.*", "tests"]),
+ description="Quality-of-Service Predictor Xapp for Traffic Steering",
+ url="https://gerrit.o-ran-sc.org/r/admin/repos/ric-app/qp",
+ install_requires=["ricxappframe>=1.1.1,<2.0.0"],
+ entry_points={"console_scripts": ["run-qp.py=qp.main:start"]}, # adds a magical entrypoint for Docker
+ license="Apache 2.0",
+ data_files=[("", ["LICENSE.txt"])],
+)
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
+
+
+import pytest
+
+
+@pytest.fixture
+def qp_prediction():
+ return {
+ "12345": {
+ "310-680-200-555001": [2000000, 1200000],
+ "310-680-200-555002": [800000, 400000],
+ "310-680-200-555003": [800000, 400000]
+ }
+ }
+
+
+@pytest.fixture
+def qpd_to_qp():
+ return {
+ "PredictionUE": "12345",
+ "UEMeasurements": {
+ "ServingCellID": "310-680-200-555002",
+ "MeasTimestampUEPDCPBytes": "2020-03-18 02:23:18.220",
+ "MeasPeriodUEPDCPBytes": 20,
+ "UEPDCPBytesDL": 250000,
+ "UEPDCPBytesUL": 100000,
+ "MeasTimestampUEPRBUsage": "2020-03-18 02:23:18.220",
+ "MeasPeriodUEPRBUsage": 20,
+ "UEPRBUsageDL": 10,
+ "UEPRBUsageUL": 30,
+ },
+ "CellMeasurements": [
+ {
+ "CellID": "310-680-200-555001",
+ "MeasTimestampPDCPBytes": "2020-03-18 02:23:18.220",
+ "MeasPeriodPDCPBytes": 20,
+ "PDCPBytesDL": 2000000,
+ "PDCPBytesUL": 1200000,
+ "MeasTimestampAvailPRB": "2020-03-18 02:23:18.220",
+ "MeasPeriodAvailPRB": 20,
+ "AvailPRBDL": 30,
+ "AvailPRBUL": 50,
+ "MeasTimestampRF": "2020-03-18 02:23:18.210",
+ "MeasPeriodRF": 40,
+ "RFMeasurements": {"RSRP": -90, "RSRQ": -13, "RSSINR": -2.5},
+ },
+ {
+ "CellID": "310-680-200-555003",
+ "MeasTimestampPDCPBytes": "2020-03-18 02:23:18.220",
+ "MeasPeriodPDCPBytes": 20,
+ "PDCPBytesDL": 1900000,
+ "PDCPBytesUL": 1000000,
+ "MeasTimestampAvailPRB": "2020-03-18 02:23:18.220",
+ "MeasPeriodAvailPRB": 20,
+ "AvailPRBDL": 60,
+ "AvailPRBUL": 80,
+ "MeasTimestampRF": "2020-03-18 02:23:18.210",
+ "MeasPeriodRF": 40,
+ "RFMeasurements": {"RSRP": -140, "RSRQ": -17, "RSSINR": -6},
+ },
+ {
+ "CellID": "310-680-200-555002",
+ "MeasTimestampPDCPBytes": "2020-03-18 02:23:18.220",
+ "MeasPeriodPDCPBytes": 20,
+ "PDCPBytesDL": 800000,
+ "PDCPBytesUL": 400000,
+ "MeasTimestampAvailPRB": "2020-03-18 02:23:18.220",
+ "MeasPeriodAvailPRB": 20,
+ "AvailPRBDL": 30,
+ "AvailPRBUL": 45,
+ "MeasTimestampRF": "2020-03-18 02:23:18.210",
+ "MeasPeriodRF": 40,
+ "RFMeasurements": {"RSRP": -115, "RSRQ": -16, "RSSINR": -5},
+ },
+ ],
+ }
--- /dev/null
+# static route table to direct messages sent by mock QP xApp
+newrt|start
+rte|30002|service-ricxapp-trafficxapp-rmr.ricxapp.svc.cluster.local:4560
+newrt|end
--- /dev/null
+# do NOT use localhost, seems unresolved on jenkins VMs
+newrt|start
+mse| 30001 | -1 | 127.0.0.1:4562 # prediction request from QPD to QP
+mse| 60001 | -1 | 127.0.0.1:4562 # other message from QPD to QP
+mse| 30002 | -1 | 127.0.0.1:4563 # prediction response from QP to TS
+newrt|end
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
+import json
+import time
+from contextlib import suppress
+from qp import main
+from ricxappframe.xapp_frame import Xapp, RMRXapp
+
+mock_qpd_xapp = None
+mock_ts_xapp = None
+
+"""
+ these tests are not currently parallelizable (do not use this tox flag)
+ I would use setup_module, however that can't take monkeypatch fixtures
+ Currently looking for the best way to make this better:
+ https://stackoverflow.com/questions/60886013/python-monkeypatch-in-pytest-setup-module
+"""
+
+
+def test_init_xapp(monkeypatch):
+
+ # monkeypatch post_init to set the data we want
+ def fake_post_init(self):
+ self.predict_requests = 0
+
+ # patch
+ monkeypatch.setattr("qp.main.post_init", fake_post_init)
+
+ # start qp
+ main.start(thread=True)
+
+
+def test_rmr_flow(monkeypatch, qpd_to_qp, qp_prediction):
+ """
+ this flow mocks out the xapps on both sides of QP.
+ It first stands up a mock ts, then it starts up a mock qp-driver
+ which will immediately send requests to the running qp.
+ """
+
+ expected_result = {}
+
+ # define a mock traffic steering xapp
+ def mock_ts_default_handler(self, summary, sbuf):
+ pass
+
+ def mock_ts_prediction_handler(self, summary, sbuf):
+ nonlocal expected_result # closures ftw
+ pay = json.loads(summary["payload"])
+ expected_result = pay
+
+ global mock_ts_xapp
+ mock_ts_xapp = RMRXapp(mock_ts_default_handler, rmr_port=4563, use_fake_sdl=True)
+ mock_ts_xapp.register_callback(mock_ts_prediction_handler, 30002)
+ mock_ts_xapp.run(thread=True)
+
+ time.sleep(1)
+
+ # define a mock qp driver xapp that sends a message to QP under test
+ def mock_qpd_entry(self):
+
+ # good traffic steering request
+ val = json.dumps(qpd_to_qp).encode()
+ self.rmr_send(val, 30001)
+
+ # should trigger the default handler and do nothing
+ val = json.dumps({"test send 60001": 2}).encode()
+ self.rmr_send(val, 60001)
+
+ global mock_qpd_xapp
+ mock_qpd_xapp = Xapp(entrypoint=mock_qpd_entry, rmr_port=4666, use_fake_sdl=True)
+ mock_qpd_xapp.run() # this will return since entry isn't a loop
+
+ time.sleep(1)
+
+ assert main.get_stats() == {"PredictRequests": 1}
+ assert expected_result == qp_prediction
+
+
+def teardown_module():
+ """
+ this is like a "finally"; the name of this function is pytest magic
+ safer to put down here since certain failures above can lead to pytest never returning
+ for example if an exception gets raised before stop is called in any test function above,
+ pytest will hang forever
+ """
+ with suppress(Exception):
+ mock_ts_xapp.stop()
+ with suppress(Exception):
+ mock_qpd_xapp.stop()
+ with suppress(Exception):
+ main.stop()
--- /dev/null
+# ==================================================================================
+# Copyright (c) 2020 AT&T Intellectual Property.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==================================================================================
+[tox]
+envlist = code,flake8,docs,docs-linkcheck
+minversion = 2.0
+
+[testenv:code]
+basepython = python3.8
+deps=
+ pytest
+ coverage
+ pytest-cov
+setenv =
+ LD_LIBRARY_PATH = /usr/local/lib/:/usr/local/lib64
+ RMR_SEED_RT = tests/fixtures/test_local.rt
+ RMR_ASYNC_CONN = 0
+ USE_FAKE_SDL = 1
+
+commands =
+ pytest -v --cov qp --cov-report xml --cov-report term-missing --cov-report html --cov-fail-under=70
+ coverage xml -i
+
+[testenv:flake8]
+basepython = python3.8
+skip_install = true
+deps = flake8
+commands = flake8 setup.py qp tests
+
+[flake8]
+extend-ignore = E501,E741,E731
+
+[testenv:clm]
+# use pip to gather dependencies with versions for CLM analysis
+whitelist_externals = sh
+commands = sh -c 'pip freeze > requirements.txt'
+
+# doc jobs
+[testenv:docs]
+whitelist_externals = echo
+skipsdist = true
+basepython = python3.8
+deps =
+ sphinx
+ sphinx-rtd-theme
+ sphinxcontrib-httpdomain
+ recommonmark
+ lfdocs-conf
+commands =
+ sphinx-build -W -b html -n -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/html
+ echo "Generated docs available in {toxinidir}/docs/_build/html"
+
+[testenv:docs-linkcheck]
+skipsdist = true
+basepython = python3.8
+deps = sphinx
+ sphinx-rtd-theme
+ sphinxcontrib-httpdomain
+ recommonmark
+ lfdocs-conf
+commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/linkcheck
--- /dev/null
+{
+ "xapp_name": "qp",
+ "version": "0.0.1",
+ "containers": [
+ {
+ "name": "qp",
+ "image": {
+ "registry": "nexus3.o-ran-sc.org:10002",
+ "name": "o-ran-sc/ric-app-qp",
+ "tag": "0.0.1"
+ }
+ }
+ ],
+ "messaging": {
+ "ports": [
+ {
+ "name": "rmr-data-in",
+ "container": "qp",
+ "port": 4562,
+ "rxMessages": ["TS_QOE_PRED_REQ"],
+ "txMessages": ["TS_QOE_PREDICTION"],
+ "policies": [],
+ "description": "rmr receive data port for qp"
+ },
+ {
+ "name": "rmr-route",
+ "container": "qp",
+ "port": 4561,
+ "description": "rmr route port for qp"
+ }
+ ]
+ },
+ "rmr": {
+ "protPort": "tcp:4562",
+ "maxSize": 2072,
+ "numWorkers": 1,
+ "rxMessages": ["TS_QOE_PRED_REQ"],
+ "txMessages": ["TS_QOE_PREDICTION"],
+ "policies": []
+ }
+}