From: Tommy Carpenter Date: Thu, 26 Mar 2020 12:48:44 +0000 (-0400) Subject: Implement the core business logic of the data merge X-Git-Tag: 1.0.1~4 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=d2f16bf2801a52f199edb6fd1ecbf1ed0670e0bc;p=ric-app%2Fqp-driver.git Implement the core business logic of the data merge Issue-ID: RICAPP-92 Change-Id: I1e5e59247f2f28f66140da65bf64f39669e5af67 Signed-off-by: Tommy Carpenter --- diff --git a/container-tag.yaml b/container-tag.yaml index 48c5b97..61e4b76 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.0.1 +tag: 0.1.0 diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 9d95fe2..4aeb5b0 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -14,6 +14,12 @@ and this project adheres to `Semantic Versioning `__. :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 index 0000000..0e18dd5 --- /dev/null +++ b/qpdriver/data.py @@ -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 diff --git a/qpdriver/main.py b/qpdriver/main.py index 3cfe42b..decf7aa 100644 --- a/qpdriver/main.py +++ b/qpdriver/main.py @@ -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): diff --git a/setup.py b/setup.py index e96f2c6..7d22ec7 100644 --- 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", diff --git a/tests/conftest.py b/tests/conftest.py index 4861ff0..8ba1cd4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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}, }, ], } diff --git a/tests/fixtures/test_local.rt b/tests/fixtures/test_local.rt index 2d12e18..72cb461 100644 --- a/tests/fixtures/test_local.rt +++ b/tests/fixtures/test_local.rt @@ -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 index 0000000..4a207f8 --- /dev/null +++ b/tests/test_data.py @@ -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 diff --git a/tests/test_qpd.py b/tests/test_qpd.py index 1d70821..96d4dd1 100644 --- a/tests/test_qpd.py +++ b/tests/test_qpd.py @@ -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 --- 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]