# 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
:depth: 3
:local:
+[0.1.0] - 3/26/2020
+-------------------
+::
+
+ * Implement the core business logic of the data merge
+
[0.0.2] - 3/25/2020
-------------------
::
--- /dev/null
+"""
+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
+"""
+qpdriver entrypoint module
+"""
# ==================================================================================
# Copyright (c) 2020 AT&T Intellectual Property.
#
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
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)
# 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):
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",
{"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",
}
"MeasPeriodAvailPRB": 20,
"AvailPRBDL": 30,
"AvailPRBUL": 45,
+ "FAKE_BAD_DATA_TEST": "THIS SHOULD GET DELETED",
}
"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},
},
],
}
# 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
--- /dev/null
+# ==================================================================================
+# 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
# 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)
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]