Implement GET and /healthcheck 39/539/3
authorTommy Carpenter <tommy@research.att.com>
Thu, 18 Jul 2019 20:21:21 +0000 (20:21 +0000)
committerTommy Carpenter <tommy@research.att.com>
Tue, 23 Jul 2019 14:00:34 +0000 (14:00 +0000)
Change-Id: Ia2804306f91e5d877dd51af21ac02d94622a4089
Signed-off-by: Tommy Carpenter <tommy@research.att.com>
18 files changed:
Dockerfile
a1/controller.py
a1/openapi.yaml
container-tag.yaml
docs/developer-guide.rst
docs/release-notes.rst
integration_tests/a1mediator/Chart.yaml
integration_tests/a1mediator/files/rmr_string_int_mapping.txt
integration_tests/a1mediator/templates/config.yaml
integration_tests/a1mediator/templates/deployment.yaml
integration_tests/receiver.py
integration_tests/test_a1.tavern.yaml
integration_tests/test_local.rt
integration_tests/testreceiver/templates/config.yaml
setup.py
tests/fixtures/rmr_string_int_mapping.txt
tests/test_controller.py
tests/testing_helpers.py

index 0b49ac7..658963f 100644 (file)
@@ -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
index b3bbcdb..e6e093f 100644 (file)
@@ -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
index 1d121d1..81e88d1 100644 (file)
 # ==================================================================================
 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)
+
+
index d3e22b4..b95acd7 100644 (file)
@@ -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
index 5209cd3..2274b6e 100644 (file)
@@ -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
index 3008efb..cb50672 100644 (file)
@@ -24,6 +24,15 @@ The format is based on `Keep a Changelog <http://keepachangelog.com/>`__
 and this project adheres to `Semantic
 Versioning <http://semver.org/>`__.
 
+[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
 -------------------
 
index 17253ae..fd9b2a0 100644 (file)
@@ -1,4 +1,4 @@
 apiVersion: v1
 description: A1 Helm chart for Kubernetes
 name: a1mediator
-version: 0.8.4
+version: 0.9.0
index 1f194b8..cd8db8d 100644 (file)
@@ -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
index d32e7f1..fa426b6 100644 (file)
@@ -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 }}
index 82360c0..1f3ae51 100644 (file)
@@ -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 }}
index 3a48a8d..a64a30a 100644 (file)
@@ -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
index 8409056..6952b9b 100644 (file)
@@ -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
index 92f9ca4..08f2c20 100644 (file)
@@ -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
index e958e31..03ee831 100644 (file)
@@ -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
 
 ---
index b55ab6c..ad35a1c 100644 (file)
--- 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"],
index 1f194b8..cd8db8d 100644 (file)
@@ -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
index 5370207..989390b 100644 (file)
@@ -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
index b258a85..2c9dda2 100644 (file)
@@ -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