Extend controller with counters to measure activity
Add /a1-p/metrics endpoint to expose measures in Prometheus format
Document new environment variable prometheus_multiproc_dir
Issue-ID: RIC-353
Signed-off-by: Lott, Christopher (cl778h) <cl778h@att.com>
Change-Id: I44bc5346b627b27ba5b344187223dd0323b32a2c
14 files changed:
commit = False
tag = False
commit = False
tag = False
ENV USE_FAKE_SDL False
ENV PYTHONUNBUFFERED 1
# pip installs console script to ~/.local/bin so PATH is critical
ENV USE_FAKE_SDL False
ENV PYTHONUNBUFFERED 1
# pip installs console script to ~/.local/bin so PATH is critical
-ENV PATH=/home/a1user/.local/bin:$PATH
+ENV PATH /home/a1user/.local/bin:$PATH
+# prometheus client gathers data here
+ENV prometheus_multiproc_dir /tmp
contains the app; broken out here for ease of unit testing
"""
import connexion
contains the app; broken out here for ease of unit testing
"""
import connexion
+from prometheus_client import CollectorRegistry, generate_latest, multiprocess
app = connexion.App(__name__, specification_dir=".")
app.add_api("openapi.yaml", arguments={"title": "My Title"})
app = connexion.App(__name__, specification_dir=".")
app.add_api("openapi.yaml", arguments={"title": "My Title"})
+
+
+# python decorators feel like black magic to me
+@app.app.route('/a1-p/metrics', methods=['GET'])
+def metrics(): # pylint: disable=unused-variable
+ # /metrics API shouldn't be visible in the API documentation,
+ # hence it's added here in the create_app step
+ # requires environment variable prometheus_multiproc_dir
+ registry = CollectorRegistry()
+ multiprocess.MultiProcessCollector(registry)
+ return generate_latest(registry)
from jsonschema import validate
from jsonschema.exceptions import ValidationError
import connexion
from jsonschema import validate
from jsonschema.exceptions import ValidationError
import connexion
+from prometheus_client import Counter
from mdclogpy import Logger
from ricsdl.exceptions import RejectedByBackend, NotConnected, BackendError
from a1 import a1rmr, exceptions, data
mdc_logger = Logger(name=__name__)
from mdclogpy import Logger
from ricsdl.exceptions import RejectedByBackend, NotConnected, BackendError
from a1 import a1rmr, exceptions, data
mdc_logger = Logger(name=__name__)
+request_counter = Counter('policy_requests', 'Policy type and instance requests', ['action', 'target'])
def _log_build_http_resp(exception, http_resp_code):
def _log_build_http_resp(exception, http_resp_code):
"""
Handles PUT /a1-p/policytypes/policy_type_id
"""
"""
Handles PUT /a1-p/policytypes/policy_type_id
"""
+ request_counter.labels(action='create', target='policy_type').inc()
def put_type_handler():
data.store_policy_type(policy_type_id, body)
def put_type_handler():
data.store_policy_type(policy_type_id, body)
"""
Handles DELETE /a1-p/policytypes/policy_type_id
"""
"""
Handles DELETE /a1-p/policytypes/policy_type_id
"""
+ request_counter.labels(action='delete', target='policy_type').inc()
def delete_policy_type_handler():
data.delete_policy_type(policy_type_id)
def delete_policy_type_handler():
data.delete_policy_type(policy_type_id)
"""
Handles PUT /a1-p/policytypes/polidyid/policies/policy_instance_id
"""
"""
Handles PUT /a1-p/policytypes/polidyid/policies/policy_instance_id
"""
+ request_counter.labels(action='create', target='policy_inst').inc()
instance = connexion.request.json
def put_instance_handler():
instance = connexion.request.json
def put_instance_handler():
"""
Handles DELETE /a1-p/policytypes/polidyid/policies/policy_instance_id
"""
"""
Handles DELETE /a1-p/policytypes/polidyid/policies/policy_instance_id
"""
+ request_counter.labels(action='delete', target='policy_inst').inc()
def delete_instance_handler():
data.delete_policy_instance(policy_type_id, policy_instance_id)
def delete_instance_handler():
data.delete_policy_instance(policy_type_id, policy_instance_id)
# 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
---
# 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
---
- Connexion
- Flask with Gevent serving
- Swagger
- Connexion
- Flask with Gevent serving
- Swagger
Version bumping A1
------------------
Version bumping A1
------------------
#. ``integration_tests/install_rmr.sh`` is a useful script for a
variety of local testing.
#. ``integration_tests/install_rmr.sh`` is a useful script for a
variety of local testing.
Version bumping Python
----------------------
Version bumping Python
----------------------
#. ``Dockerfile-Unit-Test``
#. ``tox.ini``
#. ``Dockerfile-Unit-Test``
#. ``tox.ini``
+
+Running A1 Standalone
+---------------------
+
+The A1 container can be run standalone, which means using an in-memory mock
+version of SDL and a static route table. The host machine must have the RMR
+library and the environment must define the variable `prometheus_multiproc_dir`
+with a value like /tmp. Alternately, use the following command to run A1 as
+a Docker container, using a route table mounted as a file from this git
+repository and exposing the server's HTTP port on the Docker host::
+
+ docker run -e USE_FAKE_SDL=True -p 10000:10000 -v `pwd`:/opt/route [DOCKER_IMAGE_ID_HERE]
+
+Then test the server with an invocation such as this::
+
+ curl localhost:10000/a1-p/healthcheck
+
+
Unit Testing
------------
Unit Testing
------------
docker build --no-cache -f Dockerfile-Unit-Test .
docker build --no-cache -f Dockerfile-Unit-Test .
Integration testing
-------------------
Integration testing
-------------------
-Optional ENV Variables
-----------------------
+Environment Variables
+---------------------
You can set the following environment variables when launching a container to change the A1 behavior:
You can set the following environment variables when launching a container to change the A1 behavior:
4. ``USE_FAKE_SDL``: This allows testing of the A1 feature without a DBaaS SDL container. The default is False.
4. ``USE_FAKE_SDL``: This allows testing of the A1 feature without a DBaaS SDL container. The default is False.
-K8S
----
-The "real" helm chart for A1 is in the LF it/dep repo. That repo holds all of the helm charts for the RIC platform. There is a helm chart in `integration_tests` here for running the integration tests as discussed above.
+5. ``prometheus_multiproc_dir``: The directory where Prometheus gathers metrics. The default is /tmp.
-Local Docker
--------------
+
+Kubernetes Deployment
+---------------------
+The official Helm chart for the A1 Mediator is in a deployment repository, which holds all of the Helm charts
+for the RIC platform. There is a helm chart in `integration_tests` here for running the integration tests as
+discussed above.
+
+Local Deployment
+----------------
+
+Build and run the A1 mediator locally using the docker CLI as follows.
Build the image
~~~~~~~~~~~~~~~
Build the image
~~~~~~~~~~~~~~~
+.. contents::
+ :depth: 3
+ :local:
+
The RAN Intelligent Controller (RIC) Platform's A1 Mediator component
listens for policy type and policy instance requests sent via HTTP
(the "northbound" interface), and publishes those requests to running
The RAN Intelligent Controller (RIC) Platform's A1 Mediator component
listens for policy type and policy instance requests sent via HTTP
(the "northbound" interface), and publishes those requests to running
and this project adheres to `Semantic Versioning <http://semver.org/>`__.
and this project adheres to `Semantic Versioning <http://semver.org/>`__.
+[2.2.0] - 2020-05-28
+--------------------
+
+* Add counters of create/update/delete actions on policy types and instances
+* Add Prometheus /metrics endpoint to report counter data
+
+
[2.1.9] - 2020-05-26
--------------------
[2.1.9] - 2020-05-26
--------------------
User Guide and APIs
===================
User Guide and APIs
===================
+.. contents::
+ :depth: 3
+ :local:
+
This document explains how to communicate with the A1 Mediator.
Information for maintainers of this platform component is in the Developer Guide.
This document explains how to communicate with the A1 Mediator.
Information for maintainers of this platform component is in the Developer Guide.
+For example, if you put the JSON above into a file called "create.json" you can use
+the curl command-line tool to send the request::
+
+ curl -X PUT --header "Content-Type: application/json" --data-raw @create.json http://localhost/a1-p/policytypes/20008
+
+
Send the following JSON to create an instance of policy type 20008:
.. code-block:: yaml
Send the following JSON to create an instance of policy type 20008:
.. code-block:: yaml
+For example, you can use the curl command-line tool to send this request::
+
+ curl -X PUT --header "Content-Type: application/json" --data '{"threshold" : 5}' http://localhost/a1-p/policytypes/20008/policies/tsapolicy145
+
+
Integrating Xapps with A1
-------------------------
Integrating Xapps with A1
-------------------------
apiVersion: v1
description: A1 Helm chart for Kubernetes
name: a1mediator
apiVersion: v1
description: A1 Helm chart for Kubernetes
name: a1mediator
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-a1=a1.run:main"]},
# we require jsonschema, should be in that list, but connexion already requires a specific version of it
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-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"],
+ install_requires=["requests", "Flask", "connexion[swagger-ui]", "gevent", "prometheus-client", "mdclogpy", "ricxappframe>=1.0.0,<2.0.0"],
package_data={"a1": ["openapi.yaml"]},
)
package_data={"a1": ["openapi.yaml"]},
)
assert res.status_code == 200
assert res.status_code == 200
+def test_metrics(client):
+ """
+ test Prometheus metrics
+ """
+ res = client.get("/a1-p/metrics")
+ assert res.status_code == 200
+
+
def teardown_module():
"""module teardown"""
a1rmr.stop_rmr_thread()
def teardown_module():
"""module teardown"""
a1rmr.stop_rmr_thread()
A1_RMR_RETRY_TIMES = 2
INSTANCE_DELETE_NO_RESP_TTL = 3
INSTANCE_DELETE_RESP_TTL = 3
A1_RMR_RETRY_TIMES = 2
INSTANCE_DELETE_NO_RESP_TTL = 3
INSTANCE_DELETE_RESP_TTL = 3
+ prometheus_multiproc_dir = /tmp
# Note, before this will work, for the first time on that machine, run ./install_deps.sh
commands =
# Note, before this will work, for the first time on that machine, run ./install_deps.sh
commands =