Implement the core business logic of the data merge 94/2994/3
authorTommy Carpenter <tc677g@att.com>
Thu, 26 Mar 2020 12:48:44 +0000 (08:48 -0400)
committerTommy Carpenter <tc677g@att.com>
Thu, 26 Mar 2020 14:59:47 +0000 (10:59 -0400)
Issue-ID: RICAPP-92
Change-Id: I1e5e59247f2f28f66140da65bf64f39669e5af67
Signed-off-by: Tommy Carpenter <tc677g@att.com>
container-tag.yaml
docs/release-notes.rst
qpdriver/data.py [new file with mode: 0644]
qpdriver/main.py
setup.py
tests/conftest.py
tests/fixtures/test_local.rt
tests/test_data.py [new file with mode: 0644]
tests/test_qpd.py
tox.ini

index 48c5b97..61e4b76 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.0.1
+tag: 0.1.0
index 9d95fe2..4aeb5b0 100644 (file)
@@ -14,6 +14,12 @@ and this project adheres to `Semantic Versioning <http://semver.org/>`__.
    :depth: 3
    :local:
 
+[0.1.0] - 3/26/2020
+-------------------
+::
+
+    * Implement the core business logic of the data merge
+
 [0.0.2] - 3/25/2020
 -------------------
 ::
diff --git a/qpdriver/data.py b/qpdriver/data.py
new file mode 100644 (file)
index 0000000..0e18dd5
--- /dev/null
@@ -0,0 +1,105 @@
+"""
+qpdriver module responsible for SDL queries and data merging
+"""
+# ==================================================================================
+#       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.
+# ==================================================================================
+
+# list of keys in ue metrics that we want to carry over to qp pred
+UE_KEY_LIST = set(
+    [
+        "ServingCellID",
+        "MeasTimestampUEPDCPBytes",
+        "MeasPeriodUEPDCPBytes",
+        "UEPDCPBytesDL",
+        "UEPDCPBytesUL",
+        "MeasTimestampUEPRBUsage",
+        "MeasPeriodUEPRBUsage",
+        "UEPRBUsageDL",
+        "UEPRBUsageUL",
+    ]
+)
+
+# list of keys in cell metrics we want to carry
+CELL_KEY_LIST = set(
+    [
+        "CellID",
+        "MeasTimestampPDCPBytes",
+        "MeasPeriodPDCPBytes",
+        "PDCPBytesDL",
+        "PDCPBytesUL",
+        "MeasTimestampAvailPRB",
+        "MeasPeriodAvailPRB",
+        "AvailPRBDL",
+        "AvailPRBUL",
+    ]
+)
+
+
+def _fetch_ue_metrics(ueid):
+    """fetch ue metrics for ueid"""
+    return {}
+
+
+def _fetch_cell_metrics(cellid):
+    """fetch cell metrics for a cellid"""
+    return {}
+
+
+def form_qp_pred_req(ueid):
+    """
+    this function takes in a single ueid and:
+        - fetches the current ue data
+        - for the serving cell id, and for each neighboring cell id, fetches the cell data for those cells
+        - returns the message that should be sent to the QP Predictor
+    Note that a single request to qp driver may have many UEs in a list, however since a new message needs to be sent for each one,
+    the calling function iterates over that list, rather than doing it here.
+    """
+    ue_data = _fetch_ue_metrics(ueid)
+
+    serving_cid = ue_data["ServingCellID"]
+
+    # a dict is better than a list for what we need to do here
+    n_cell_info = {}
+    for ncell in ue_data["NeighborCellRF"]:
+        n_cell_info[ncell["CID"]] = ncell["CellRF"]
+
+    # form the cell_id list
+    # qp prediction team does not have a preference as to order; we deterministically put the serving cell last
+    cell_ids = list(n_cell_info.keys())
+    cell_ids.append(serving_cid)
+
+    # form the qp req
+    qp_pred_req = {"PredictionUE": ueid}  # top level key
+    qp_pred_req["UEMeasurements"] = {k: ue_data[k] for k in UE_KEY_LIST}  # take ue keys we want
+    qp_pred_req["CellMeasurements"] = []
+
+    # form the CellMeasurements
+    for cid in cell_ids:
+        cellm = _fetch_cell_metrics(cid)
+        # if we were really under performance strain here we could delete from the orig instead of copying but this code is far simpler
+        cell_data = {k: cellm[k] for k in CELL_KEY_LIST}
+
+        # these keys get dropped into *each* cell
+        cell_data["MeasTimestampRF"] = ue_data["MeasTimestampRF"]
+        cell_data["MeasPeriodRF"] = ue_data["MeasPeriodRF"]
+
+        # add the RF
+        cell_data["RFMeasurements"] = ue_data["ServingCellRF"] if cid == serving_cid else n_cell_info[cid]
+
+        # add to our array
+        qp_pred_req["CellMeasurements"].append(cell_data)
+
+    return qp_pred_req
index 3cfe42b..decf7aa 100644 (file)
@@ -1,3 +1,6 @@
+"""
+qpdriver entrypoint module
+"""
 # ==================================================================================
 #       Copyright (c) 2020 AT&T Intellectual Property.
 #
@@ -21,6 +24,13 @@ This is only a stencil for now, will be filled in!
 What is currently here was only for initial skeleton and test creation.
 """
 
+"""
+RMR Messages
+ #define TS_UE_LIST 30000
+ #define TS_QOE_PRED_REQ 30001
+30000 is the message QPD receives, sends out 30001 to QP
+"""
+
 
 def post_init(self):
     self.def_hand_called = 0
@@ -34,6 +44,14 @@ def default_handler(self, summary, sbuf):
 
 
 def steering_req_handler(self, summary, sbuf):
+    """
+    This is the main handler for this xapp, which handles the traffic steering requests.
+    Traffic steering requests predictions on a set of UEs.
+    QP Driver (this) fetches a set of data, merges it together in a deterministic way, then sends a new message out to the QP predictor Xapp.
+
+    The incoming message that this function handles looks like:
+        {“UEPredictionSet” : [“UEId1”,”UEId2”,”UEId3”]}
+    """
     self.traffic_steering_requests += 1
     print(summary)
     self.rmr_free(sbuf)
@@ -41,7 +59,7 @@ def steering_req_handler(self, summary, sbuf):
 
 # obv some of these flags have to change
 rmr_xapp = RMRXapp(default_handler, post_init=post_init, rmr_port=4562, use_fake_sdl=True)
-rmr_xapp.register_callback(steering_req_handler, 60000)  # no idea (yet) what the real int is here
+rmr_xapp.register_callback(steering_req_handler, 30000)
 
 
 def start(thread=False):
index e96f2c6..7d22ec7 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@ from setuptools import setup, find_packages
 
 setup(
     name="qpdriver",
-    version="0.0.2",
+    version="0.1.0",
     packages=find_packages(exclude=["tests.*", "tests"]),
     author="Tommy Carpenter",
     description="QP Driver Xapp for traffic steering",
index 4861ff0..8ba1cd4 100644 (file)
@@ -21,6 +21,7 @@ def ue_metrics():
             {"CID": "310-680-200-555001", "CellRF": {"RSRP": -90, "RSRQ": -13, "RSSINR": -2.5}},
             {"CID": "310-680-200-555003", "CellRF": {"RSRP": -140, "RSRQ": -17, "RSSINR": -6}},
         ],
+        "FAKE_BAD_DATA_TEST": "THIS SHOULD GET DELETED",
     }
 
 
@@ -51,6 +52,7 @@ def cell_metrics_2():
         "MeasPeriodAvailPRB": 20,
         "AvailPRBDL": 30,
         "AvailPRBUL": 45,
+        "FAKE_BAD_DATA_TEST": "THIS SHOULD GET DELETED",
     }
 
 
@@ -100,32 +102,32 @@ def qpd_to_qp():
                 "RFMeasurements": {"RSRP": -90, "RSRQ": -13, "RSSINR": -2.5},
             },
             {
-                "CellID": "310-680-200-555002",
+                "CellID": "310-680-200-555003",
                 "MeasTimestampPDCPBytes": "2020-03-18 02:23:18.220",
                 "MeasPeriodPDCPBytes": 20,
-                "PDCPBytesDL": 800000,
-                "PDCPBytesUL": 400000,
+                "PDCPBytesDL": 1900000,
+                "PDCPBytesUL": 1000000,
                 "MeasTimestampAvailPRB": "2020-03-18 02:23:18.220",
                 "MeasPeriodAvailPRB": 20,
-                "AvailPRBDL": 30,
-                "AvailPRBUL": 45,
+                "AvailPRBDL": 60,
+                "AvailPRBUL": 80,
                 "MeasTimestampRF": "2020-03-18 02:23:18.210",
                 "MeasPeriodRF": 40,
-                "RFMeasurements": {"RSRP": -115, "RSRQ": -16, "RSSINR": -5},
+                "RFMeasurements": {"RSRP": -140, "RSRQ": -17, "RSSINR": -6},
             },
             {
-                "CellID": "310-680-200-555003",
+                "CellID": "310-680-200-555002",
                 "MeasTimestampPDCPBytes": "2020-03-18 02:23:18.220",
                 "MeasPeriodPDCPBytes": 20,
-                "PDCPBytesDL": 1900000,
-                "PDCPBytesUL": 1000000,
+                "PDCPBytesDL": 800000,
+                "PDCPBytesUL": 400000,
                 "MeasTimestampAvailPRB": "2020-03-18 02:23:18.220",
                 "MeasPeriodAvailPRB": 20,
-                "AvailPRBDL": 60,
-                "AvailPRBUL": 80,
+                "AvailPRBDL": 30,
+                "AvailPRBUL": 45,
                 "MeasTimestampRF": "2020-03-18 02:23:18.210",
                 "MeasPeriodRF": 40,
-                "RFMeasurements": {"RSRP": -140, "RSRQ": -17, "RSSINR": -6},
+                "RFMeasurements": {"RSRP": -115, "RSRQ": -16, "RSSINR": -5},
             },
         ],
     }
index 2d12e18..72cb461 100644 (file)
@@ -1,5 +1,5 @@
 # do NOT use localhost, seems unresolved on jenkins VMs
 newrt|start
-mse| 60000 | -1 | 127.0.0.1:4562
+mse| 30000 | -1 | 127.0.0.1:4562
 mse| 60001 | -1 | 127.0.0.1:4562
 newrt|end
diff --git a/tests/test_data.py b/tests/test_data.py
new file mode 100644 (file)
index 0000000..4a207f8
--- /dev/null
@@ -0,0 +1,37 @@
+# ==================================================================================
+#       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 qpdriver import data
+
+
+def test_merge(monkeypatch, ue_metrics, cell_metrics_1, cell_metrics_2, cell_metrics_3, qpd_to_qp):
+
+    # monkeypatch
+    def fake_ue(ueid):
+        if ueid == 12345:
+            return ue_metrics
+
+    def fake_cell(cellid):
+        if cellid == "310-680-200-555001":
+            return cell_metrics_1
+        if cellid == "310-680-200-555002":
+            return cell_metrics_2
+        if cellid == "310-680-200-555003":
+            return cell_metrics_3
+
+    monkeypatch.setattr("qpdriver.data._fetch_ue_metrics", fake_ue)
+    monkeypatch.setattr("qpdriver.data._fetch_cell_metrics", fake_cell)
+
+    assert data.form_qp_pred_req(12345) == qpd_to_qp
index 1d70821..96d4dd1 100644 (file)
@@ -33,8 +33,8 @@ def test_flow():
     # define a test sender
     def entry(self):
 
-        val = json.dumps({"test send 60000": 1}).encode()
-        self.rmr_send(val, 60000)
+        val = json.dumps({"test send 30000": 1}).encode()
+        self.rmr_send(val, 30000)
 
         val = json.dumps({"test send 60001": 2}).encode()
         self.rmr_send(val, 60001)
diff --git a/tox.ini b/tox.ini
index fde992f..e9fe0d0 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -29,8 +29,7 @@ setenv =
     RMR_ASYNC_CONN = 0
 
 commands =
-    pytest --cov qpdriver --cov-report xml --cov-report term-missing --cov-report html
-# --cov-fail-under=70
+    pytest -v --cov qpdriver --cov-report xml --cov-report term-missing --cov-report html --cov-fail-under=70
     coverage xml -i
 
 [testenv:flake8]