From fdf050451414e1a816e343bcd56f33186a742e49 Mon Sep 17 00:00:00 2001 From: Tommy Carpenter Date: Thu, 18 Jul 2019 20:21:21 +0000 Subject: [PATCH] Implement GET and /healthcheck Change-Id: Ia2804306f91e5d877dd51af21ac02d94622a4089 Signed-off-by: Tommy Carpenter --- Dockerfile | 2 +- a1/controller.py | 66 ++++++++++++++++++---- a1/openapi.yaml | 29 +++++++++- container-tag.yaml | 2 +- docs/developer-guide.rst | 21 ++++--- docs/release-notes.rst | 9 +++ integration_tests/a1mediator/Chart.yaml | 2 +- .../a1mediator/files/rmr_string_int_mapping.txt | 2 + integration_tests/a1mediator/templates/config.yaml | 4 +- .../a1mediator/templates/deployment.yaml | 4 +- integration_tests/receiver.py | 8 ++- integration_tests/test_a1.tavern.yaml | 27 +++++++++ integration_tests/test_local.rt | 4 +- .../testreceiver/templates/config.yaml | 2 + setup.py | 4 +- tests/fixtures/rmr_string_int_mapping.txt | 2 + tests/test_controller.py | 38 ++++++++++++- tests/testing_helpers.py | 6 +- 18 files changed, 195 insertions(+), 37 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0b49ac7..658963f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ COPY . /tmp WORKDIR /tmp # copy NNG out of the CI builder nng -COPY --from=nexus3.o-ran-sc.org:10004/bldr-debian-python-nng:2-py3.7-nng1.1.1 /usr/local/lib/libnng.so /usr/local/lib/libnng.so +COPY --from=nexus3.o-ran-sc.org:10004/bldr-debian-python:3-py3.7-nng1.1.1 /usr/local/lib/libnng.so /usr/local/lib/libnng.so # Installs RMr using debian package hosted at packagecloud.io RUN wget --content-disposition https://packagecloud.io/o-ran-sc/master/packages/debian/stretch/rmr_1.0.36_amd64.deb/download.deb diff --git a/a1/controller.py b/a1/controller.py index b3bbcdb..e6e093f 100644 --- a/a1/controller.py +++ b/a1/controller.py @@ -25,23 +25,40 @@ from a1 import a1rmr, exceptions, utils logger = get_module_logger(__name__) -def _get_needed_policy_info(policyname): - """ - Get the needed info for a policy - """ +def _get_policy_definition(policyname): # Currently we read the manifest on each call, which would seem to allow updating A1 in place. Revisit this? manifest = utils.get_ric_manifest() for m in manifest["controls"]: if m["name"] == policyname: - schema = m["message_receives_payload_schema"] if "message_receives_payload_schema" in m else None - return ( - utils.rmr_string_to_int(m["message_receives_rmr_type"]), - schema, - utils.rmr_string_to_int(m["message_sends_rmr_type"]), - ) + return m raise exceptions.PolicyNotFound() +def _get_needed_policy_info(policyname): + """ + Get the needed info for a policy + """ + m = _get_policy_definition(policyname) + return ( + utils.rmr_string_to_int(m["message_receives_rmr_type"]), + m["message_receives_payload_schema"] if "message_receives_payload_schema" in m else None, + utils.rmr_string_to_int(m["message_sends_rmr_type"]), + ) + + +def _get_needed_policy_fetch_info(policyname): + """ + Get the needed info for fetching a policy state + """ + m = _get_policy_definition(policyname) + req_k = "control_state_request_rmr_type" + ack_k = "control_state_request_reply_rmr_type" + return ( + utils.rmr_string_to_int(m[req_k]) if req_k in m else None, + utils.rmr_string_to_int(m[ack_k]) if ack_k in m else None, + ) + + def _try_func_return(func): """ generic caller that returns the apporp http response if exceptions are raised @@ -99,6 +116,25 @@ def _put_handler(policyname, data): return {"reason": "NO STATUS", "return_payload": rpj}, 502 +def _get_handler(policyname): + """ + Handles policy GET + """ + mtype_send, mtype_return = _get_needed_policy_fetch_info(policyname) + + if not (mtype_send and mtype_return): + return "POLICY DOES NOT SUPPORT FETCHING", 400 + + # send rmr, wait for ACK + return_payload = a1rmr.send_ack_retry("", message_type=mtype_send, expected_ack_message_type=mtype_return) + + # right now it is assumed that xapps respond with JSON payloads + try: + return (json.loads(return_payload), 200) + except json.decoder.JSONDecodeError: + return {"reason": "NOT JSON", "return_payload": return_payload}, 502 + + # Public @@ -114,4 +150,12 @@ def get_handler(policyname): """ Handles policy GET """ - return "", 501 + return _try_func_return(lambda: _get_handler(policyname)) + + +def healthcheck_handler(): + """ + Handles healthcheck GET + Currently, this basically checks the server is alive.a1rmr + """ + return "", 200 diff --git a/a1/openapi.yaml b/a1/openapi.yaml index 1d121d1..81e88d1 100644 --- a/a1/openapi.yaml +++ b/a1/openapi.yaml @@ -16,9 +16,21 @@ # ================================================================================== openapi: 3.0.0 info: - version: 0.8.0 + version: 0.9.0 title: RIC A1 paths: + '/a1-p/healthcheck': + get: + description: > + perform a healthcheck on a1 + tags: + - A1 Mediator + operationId: a1.controller.healthcheck_handler + responses: + 200: + description: > + a1 is healthy. Anything other than a 200 should be considered a1 as failing + '/ric/policies/{policyname}': parameters: - name: policyname @@ -88,6 +100,17 @@ paths: - A1 Mediator operationId: a1.controller.get_handler responses: - '501': + '200': description: > - "future GET support has been pondered, but this is not currently implemented"U + The downstream component responsible for implementing this policy replied with a good response. Check the manifest for response details. + '400': + description: > + The downstream component for implementing this policy does not support policy fetching. + '404': + description: > + there is no policy with this name + '504': + description: > + the downstream component responsible for handling this policy did not respond (in time) + + diff --git a/container-tag.yaml b/container-tag.yaml index d3e22b4..b95acd7 100644 --- a/container-tag.yaml +++ b/container-tag.yaml @@ -1,4 +1,4 @@ # 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.8.4 +tag: 0.9.0 diff --git a/docs/developer-guide.rst b/docs/developer-guide.rst index 5209cd3..2274b6e 100644 --- a/docs/developer-guide.rst +++ b/docs/developer-guide.rst @@ -87,6 +87,9 @@ This script: 4. Barrages the server with apache bench 5. Tears everything down +Unless you're a core A1 developer, you should probably stop here. The below instructions +are for running A1 locally, without docker, and is much more involved (however useful when developing a1). + Running locally =============== @@ -96,7 +99,7 @@ Running locally does this) 3. Create a ``local.rt`` file and copy it into ``/opt/route/local.rt``. - Note, the example one in ``local_tests`` will need to be modified for + Note, the example one in ``integration_tests`` will need to be modified for your scenario and machine. 4. Copy a ric manifest into ``/opt/ricmanifest.json`` and an rmr mapping @@ -105,19 +108,17 @@ Running locally :: - cp tests/fixtures/ricmanifest.json /opt/ricmanifest.json cp - tests/fixtures/rmr_string_int_mapping.txt - /opt/rmr_string_int_mapping.txt + cp tests/fixtures/ricmanifest.json /opt/ricmanifest.json + cp tests/fixtures/rmr_string_int_mapping.txt /opt/rmr_string_int_mapping.txt 5. Then: - sudo pip install –ignore-installed .; set -x LD_LIBRARY_PATH - /usr/local/lib/; set -x RMR_SEED_RT /opt/route/local.rt ; set -x - RMR_RCV_RETRY_INTERVAL 500; set -x RMR_RETRY_TIMES 10; + :: + + sudo pip install -e . + set -x LD_LIBRARY_PATH /usr/local/lib/; set -x RMR_SEED_RT /opt/route/local.rt ; set -x RMR_RCV_RETRY_INTERVAL 500; set -x RMR_RETRY_TIMES 10; /usr/bin/run.py -Testing locally -=============== There are also two test receivers in ``integration_tests`` you can run locally. The first is meant to be used with the ``control_admission`` policy @@ -146,6 +147,8 @@ while it is sleeping, and both responses should be correct. curl -v -X PUT -H "Content-Type: application/json" -d '{}' localhost:10000/ric/policies/test_policy curl -v -X PUT -H "Content-Type: application/json" -d '{ "enforce":true, "window_length":10, "blocking_rate":20, "trigger_threshold":10 }' localhost:10000/ric/policies/admission_control_policy + curl -v localhost:10000/ric/policies/admission_control_policy + curl -v localhost:10000/a1-p/healthcheck Finally, there is a test “bombarder” that will flood A1 with messages with good message types but bad transaction IDs, to test A1’s resilience diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 3008efb..cb50672 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -24,6 +24,15 @@ The format is based on `Keep a Changelog `__ and this project adheres to `Semantic Versioning `__. +[0.9.0] - 7/22/2019 +------------------- + +:: + + * Implement the GET on policies + * Add a new endpoint for healthcheck. NOTE, it has been decided by oran architecture documents that this policy interface should be named a1-p in all URLS. In a future release the existing URLs will be renamed (existing URLs were not changed in this release). + + [0.8.4] - 7/16/2019 ------------------- diff --git a/integration_tests/a1mediator/Chart.yaml b/integration_tests/a1mediator/Chart.yaml index 17253ae..fd9b2a0 100644 --- a/integration_tests/a1mediator/Chart.yaml +++ b/integration_tests/a1mediator/Chart.yaml @@ -1,4 +1,4 @@ apiVersion: v1 description: A1 Helm chart for Kubernetes name: a1mediator -version: 0.8.4 +version: 0.9.0 diff --git a/integration_tests/a1mediator/files/rmr_string_int_mapping.txt b/integration_tests/a1mediator/files/rmr_string_int_mapping.txt index 1f194b8..cd8db8d 100644 --- a/integration_tests/a1mediator/files/rmr_string_int_mapping.txt +++ b/integration_tests/a1mediator/files/rmr_string_int_mapping.txt @@ -1,4 +1,6 @@ DC_ADM_INT_CONTROL:20000 DC_ADM_INT_CONTROL_ACK:20001 +DC_ADM_GET_POLICY: 20002 +DC_ADM_GET_POLICY_ACK: 20003 TEST_REQ:10000 TEST_ACK:10001 diff --git a/integration_tests/a1mediator/templates/config.yaml b/integration_tests/a1mediator/templates/config.yaml index d32e7f1..fa426b6 100644 --- a/integration_tests/a1mediator/templates/config.yaml +++ b/integration_tests/a1mediator/templates/config.yaml @@ -6,9 +6,11 @@ data: local.rt: | newrt|start rte|20000|testreceiverrmrservice:4560 + rte|20001|{{ .Values.rmrservice.name }}:{{ .Values.rmrservice.port }} + rte|20002|testreceiverrmrservice:4560 + rte|20003|{{ .Values.rmrservice.name }}:{{ .Values.rmrservice.port }} rte|10000|delayreceiverrmrservice:4563 rte|10001|{{ .Values.rmrservice.name }}:{{ .Values.rmrservice.port }} - rte|20001|{{ .Values.rmrservice.name }}:{{ .Values.rmrservice.port }} newrt|end rmr_string_int_mapping.txt: {{ tpl (.Files.Get "files/rmr_string_int_mapping.txt") . | quote }} ricmanifest.json: {{ tpl (.Files.Get "files/ricmanifest.json") . | quote }} diff --git a/integration_tests/a1mediator/templates/deployment.yaml b/integration_tests/a1mediator/templates/deployment.yaml index 82360c0..1f3ae51 100644 --- a/integration_tests/a1mediator/templates/deployment.yaml +++ b/integration_tests/a1mediator/templates/deployment.yaml @@ -46,11 +46,11 @@ spec: livenessProbe: httpGet: - path: /ui + path: /healthcheck port: http readinessProbe: httpGet: - path: /ui + path: /healthcheck port: http resources: {{- toYaml .Values.resources | nindent 12 }} diff --git a/integration_tests/receiver.py b/integration_tests/receiver.py index 3a48a8d..a64a30a 100644 --- a/integration_tests/receiver.py +++ b/integration_tests/receiver.py @@ -19,12 +19,13 @@ Test receiver """ import time -from rmr import rmr import json import os +from rmr import rmr PORT = os.environ.get("TEST_RCV_PORT", "4560") RETURN_MINT = int(os.environ.get("TEST_RCV_RETURN_MINT", 20001)) +RETURN_MINT_FETCH = int(os.environ.get("TEST_RCV_RETURN_MINT", 20003)) DELAY = int(os.environ.get("TEST_RCV_SEC_DELAY", 0)) PAYLOAD_RETURNED = json.loads( os.environ.get("TEST_RCV_RETURN_PAYLOAD", '{"ACK_FROM": "ADMISSION_CONTROL", "status": "SUCCESS"}') @@ -48,6 +49,11 @@ while True: else: print("Message received!: {}".format(summary)) + # if this was a policy fetch (request int =20002), override the payload and return int + if summary["message type"] == 20002: + PAYLOAD_RETURNED = {"mock return from FETCH": "pretend policy is here"} + RETURN_MINT = 20003 + val = json.dumps(PAYLOAD_RETURNED).encode("utf-8") rmr.set_payload_and_length(val, sbuf) sbuf.contents.mtype = RETURN_MINT diff --git a/integration_tests/test_a1.tavern.yaml b/integration_tests/test_a1.tavern.yaml index 8409056..6952b9b 100644 --- a/integration_tests/test_a1.tavern.yaml +++ b/integration_tests/test_a1.tavern.yaml @@ -1,5 +1,16 @@ # test_a1.tavern.yaml +test_name: test healthcheck + +stages: + - name: test the a1 healthcheck + request: + url: http://localhost:10000/a1-p/healthcheck + method: GET + response: + status_code: 200 + + --- test_name: test delayed policy @@ -42,6 +53,15 @@ stages: ACK_FROM: ADMISSION_CONTROL status: SUCCESS + - name: test the admission control policy get + request: + url: http://localhost:10000/ric/policies/admission_control_policy + method: GET + response: + status_code: 200 + body: + mock return from FETCH: pretend policy is here + --- test_name: bad_requests @@ -85,3 +105,10 @@ stages: not: "welcome" response: status_code: 400 + + - name: test policy doesnt support fetch + request: + url: http://localhost:10000/ric/policies/test_policy + method: GET + response: + status_code: 400 diff --git a/integration_tests/test_local.rt b/integration_tests/test_local.rt index 92f9ca4..08f2c20 100644 --- a/integration_tests/test_local.rt +++ b/integration_tests/test_local.rt @@ -1,6 +1,8 @@ newrt|start rte|10000|devarchwork:4563 -rte|20000|devarchwork:4560 rte|10001|devarchwork:4562 +rte|20000|devarchwork:4560 +rte|20002|devarchwork:4560 rte|20001|devarchwork:4562 +rte|20003|devarchwork:4562 newrt|end diff --git a/integration_tests/testreceiver/templates/config.yaml b/integration_tests/testreceiver/templates/config.yaml index e958e31..03ee831 100644 --- a/integration_tests/testreceiver/templates/config.yaml +++ b/integration_tests/testreceiver/templates/config.yaml @@ -7,6 +7,8 @@ data: newrt|start rte|20000|{{ .Values.testrmrservice.name }}:{{ .Values.testrmrservice.port }} rte|20001|a1rmrservice:4562 + rte|20002|{{ .Values.testrmrservice.name }}:{{ .Values.testrmrservice.port }} + rte|20003|a1rmrservice:4562 newrt|end --- diff --git a/setup.py b/setup.py index b55ab6c..ad35a1c 100644 --- a/setup.py +++ b/setup.py @@ -18,11 +18,11 @@ from setuptools import setup, find_packages setup( name="a1", - version="0.8.4", + version="0.9.0", packages=find_packages(exclude=["tests.*", "tests"]), author="Tommy Carpenter", description="RIC A1 Mediator for policy/intent changes", - url="", + url="https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/a1", entry_points={"console_scripts": ["run.py=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", "rmr>=0.10.0"], diff --git a/tests/fixtures/rmr_string_int_mapping.txt b/tests/fixtures/rmr_string_int_mapping.txt index 1f194b8..cd8db8d 100644 --- a/tests/fixtures/rmr_string_int_mapping.txt +++ b/tests/fixtures/rmr_string_int_mapping.txt @@ -1,4 +1,6 @@ DC_ADM_INT_CONTROL:20000 DC_ADM_INT_CONTROL_ACK:20001 +DC_ADM_GET_POLICY: 20002 +DC_ADM_GET_POLICY_ACK: 20003 TEST_REQ:10000 TEST_ACK:10001 diff --git a/tests/test_controller.py b/tests/test_controller.py index 5370207..989390b 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -68,9 +68,6 @@ def _fake_dequeue( return f -# Actual Tests - - def _test_put_patch(monkeypatch): testing_helpers.patch_all(monkeypatch) monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(0)) # good sends for this whole batch @@ -90,6 +87,33 @@ def _test_put_patch(monkeypatch): monkeypatch.setattr("rmr.rmr.generate_and_set_transaction_id", fake_set_transactionid) +# Actual Tests + + +def test_policy_get(client, monkeypatch): + """ + test policy GET + """ + _test_put_patch(monkeypatch) + monkeypatch.setattr( + "a1.a1rmr._dequeue_all_waiting_messages", + _fake_dequeue(monkeypatch, msg_payload={"GET ack": "pretend policy is here"}, msg_type=20003), + ) + res = client.get("/ric/policies/admission_control_policy") + assert res.status_code == 200 + assert res.json == {"GET ack": "pretend policy is here"} + + +def test_policy_get_unsupported(client, monkeypatch): + """ + test policy GET + """ + testing_helpers.patch_all(monkeypatch, nofetch=True) + res = client.get("/ric/policies/admission_control_policy") + assert res.status_code == 400 + assert res.data == b'"POLICY DOES NOT SUPPORT FETCHING"\n' + + def test_xapp_put_good(client, monkeypatch): """ test policy put good""" _test_put_patch(monkeypatch) @@ -202,3 +226,11 @@ def test_missing_rmr(client, monkeypatch): res = client.put("/ric/policies/admission_control_policy", json=testing_helpers.good_payload()) assert res.status_code == 500 assert res.data == b'"A1 does not have a mapping for the desired rmr string. report this!"\n' + + +def test_healthcheck(client): + """ + test healthcheck + """ + res = client.get("/a1-p/healthcheck") + assert res.status_code == 200 diff --git a/tests/testing_helpers.py b/tests/testing_helpers.py index b258a85..2c9dda2 100644 --- a/tests/testing_helpers.py +++ b/tests/testing_helpers.py @@ -24,13 +24,17 @@ def _get_fixture_path(name): return "{0}/fixtures/{1}".format(cur_dir, name) -def patch_all(monkeypatch, nonexisting_rmr=False): +def patch_all(monkeypatch, nonexisting_rmr=False, nofetch=False): rmr_mocks.patch_rmr(monkeypatch) # patch manifest man = json.loads(open(_get_fixture_path("ricmanifest.json"), "r").read()) if nonexisting_rmr: man["controls"][0]["message_receives_rmr_type"] = "DARKNESS" + + if nofetch: + del man["controls"][0]["control_state_request_rmr_type"] + monkeypatch.setattr("a1.utils.get_ric_manifest", lambda: man) # patch rmr mapping -- 2.16.6