Lab 6: Advanced RAN Slicing
This experiment is to deploy a 5G Standalone (SA) network using OpenAirInterface (OAI) RF Simulator gNB and OAI minimal 5GC. We also deploy FlexRIC as the Near-RT RIC. Finally a KPM+RC xApp for RAN Slicing will be deployed on bare-metal connecting to the RIC.
apiVersion: athena.trirematics.io/v1
kind: Network
metadata:
name: bubbleran
namespace: trirematics
spec:
slices:
- plmn: "00101"
dnn: "internet"
network-mode: "IPv4"
service-type: eMBB
differentiator: 0x000000
ipv4-range: "12.1.1.0/24"
ipv6-range: "2001:db8:1::/64"
access:
- name: oai-gnb
stack: 5g-sa
model: oai-ran/monolithic-gnb
identity:
an-id: 50
radio:
device: rf-sim
cells:
- band: n78
arfcn: 641280
bandwidth: 40MHz
subcarrier-spacing: 30kHz
tdd-config:
period: 5ms
dl-slots: 7
dl-symbols: 6
ul-slots: 2
ul-symbols: 4
controller: flexric.bubbleran
core-networks:
- minimal.bubbleran
core:
- name: minimal
stack: 5g-sa
model: oai-cn/minimal
identity:
region: 0
cn-group: 4
cn-id: 5
dns:
ipv4:
default: 8.8.8.8
secondary: 8.8.4.4
edge:
- name: flexric
stack: 5g-sa
model: mosaic5g/flexric
---
apiVersion: athena.trirematics.io/v1
kind: Terminal
metadata:
name: ue1
namespace: trirematics
spec:
vendor: oai
stack: 5g-sa
model: terminal/nr-rfsim
preferred-access: oai-gnb.bubbleran
target-cores:
- minimal.bubbleran
identity:
imsi: "001010000000001"
pin: "1234"
opc: "0xc42449363bbad02b66d16bc975d77cc1"
key: "0xfec86ba6eb707ed08905757b1bb44b8f"
sqn: "0xff9bb4000001"
slice:
dnn: "internet"
network-mode: "IPv4"
service-type: eMBB
differentiator: 0x000000
radio:
bands:
- n78
readiness-check:
method: ping
target: google-ip
interface-name: oaitun_ue0
---
apiVersion: athena.trirematics.io/v1
kind: Terminal
metadata:
name: ue2
namespace: trirematics
spec:
vendor: oai
stack: 5g-sa
model: terminal/nr-rfsim
preferred-access: oai-gnb.bubbleran
target-cores:
- minimal.bubbleran
identity:
imsi: "001010000000002"
pin: "1234"
opc: "0xc42449363bbad02b66d16bc975d77cc1"
key: "0xfec86ba6eb707ed08905757b1bb44b8f"
sqn: "0xff9bb4000001"
slice:
dnn: "internet"
network-mode: "IPv4"
service-type: eMBB
differentiator: 0x000000
radio:
bands:
- n78
readiness-check:
method: ping
target: google-ip
interface-name: oaitun_ue0
Deployment​
Use the command brc install network open-ran.yaml
to deploy the network.
It should finish without errors and printout the three Kubernetes resource names that were created.
Check for the status of the deployment using the command brc observe
.
Wait until all the Elements other than the UE are in the STATUS
set to
1/1 Y
state.
After deploying the network you should update the xApp configuration file with the IP of the deployed Near RT-RIC and the local source IP in the cluster subnet.
To do so you may deploy the update_conf.sh
script which automatically extract the IPs from the cluster and updated the specified configuration file:
cd /path/to/xapp-source/conf_files
./update_conf.sh default.conf
The script will prompt the user for selecting which Near RT-RIC the xApp should connect to (in case of multiple RICs and/or networks currently deployed). After selecting a RIC to connect an output as the follwing is obtained:
Select the RIC to configure (enter number):
1) flexric.flexric.bubbleran (10.244.0.251)
Choice [1-1]: 1
Selected RIC: flexric.flexric.bubbleran with IP 10.244.0.251
Local IP for xApp: 10.244.0.218
Config file 'default.conf' updated successfully.
Once the configuration file has been updated accordingly, now the Hello world xApp can be run.
KPM+RC RAN Slicing xApp​
#/*
#Copyright (C) 2021-2025 BubbleRAN SAS
#
#External application
#Last Changed: 2025-05-02
#Project: MX-XAPP
#Full License: https://bubbleran.com/resources/files/BubbleRAN_Licence-Agreement-1.3.pdf)
#*/
import time
import os
import json
import pdb
import csv
import sys
import signal
cur_dir = os.path.dirname(os.path.abspath(__file__))
sdk_path = cur_dir + "/../xapp_sdk/"
sys.path.append(sdk_path)
import xapp_sdk as ric
def sig_handler(signum, frame):
print("Ctrl-C Detect")
### End
ric.rm_report_slice_sm(hndlr)
# Avoid deadlock.
while ric.try_stop == 0:
time.sleep(1)
print("Test finished")
exit(1)
####################
#### SLICE INDICATION MSG TO JSON
####################
assoc_rnti = 0
def slice_ind_to_dict_json(ind):
slice_stats = {
"RAN" : {
"dl" : {},
"ul" : {}
},
"UE" : {}
}
# RAN - dl
dl_dict = slice_stats["RAN"]["dl"]
if ind.slice_stats.dl.len_slices <= 0:
dl_dict["num_of_slices"] = ind.slice_stats.dl.len_slices
dl_dict["slice_sched_algo"] = "null"
dl_dict["ue_sched_algo"] = ind.slice_stats.dl.sched_name[0]
else:
dl_dict["num_of_slices"] = ind.slice_stats.dl.len_slices
dl_dict["slice_sched_algo"] = "null"
dl_dict["slices"] = []
slice_algo = ""
for s in ind.slice_stats.dl.slices:
if s.params.type == 6:
slice_algo = "PR"
else:
slice_algo = "unknown"
dl_dict.update({"slice_sched_algo" : slice_algo})
slices_dict = {
"index" : s.id,
"label" : s.label[0],
"ue_sched_algo" : s.sched[0],
}
if dl_dict["slice_sched_algo"] == "PR":
slices_dict["slice_algo_params"] = {
"max_ratio" : s.params.u.pr.max_ratio,
"min_ratio" : s.params.u.pr.min_ratio,
"dedicated_ratio" : s.params.u.pr.dedicated_ratio
}
else:
print("unknown slice algorithm, cannot handle params")
dl_dict["slices"].append(slices_dict)
# RAN - ul
ul_dict = slice_stats["RAN"]["ul"]
if ind.slice_stats.ul.len_slices <= 0:
ul_dict["num_of_slices"] = ind.slice_stats.ul.len_slices
ul_dict["slice_sched_algo"] = "null"
ul_dict["ue_sched_algo"] = ind.slice_stats.ul.sched_name[0]
else:
ul_dict["num_of_slices"] = ind.slice_stats.ul.len_slices
ul_dict["slice_sched_algo"] = "null"
ul_dict["slices"] = []
slice_algo = ""
for s in ind.slice_stats.ul.slices:
if s.params.type == 6:
slice_algo = "PR"
else:
slice_algo = "unknown"
ul_dict.update({"slice_sched_algo" : slice_algo})
slices_dict = {
"index" : s.id,
"label" : s.label[0],
"ue_sched_algo" : s.sched[0],
}
if ul_dict["slice_sched_algo"] == "PR":
slices_dict["slice_algo_params"] = {
"max_ratio" : s.params.u.pr.max_ratio,
"min_ratio" : s.params.u.pr.min_ratio,
"dedicated_ratio" : s.params.u.pr.dedicated_ratio
}
else:
print("unknown slice algorithm, cannot handle params")
ul_dict["slices"].append(slices_dict)
# UE
global assoc_rnti
ue_dict = slice_stats["UE"]
if ind.ue_slice_stats.len_ue_slice <= 0:
ue_dict["num_of_ues"] = ind.ue_slice_stats.len_ue_slice
else:
ue_dict["num_of_ues"] = ind.ue_slice_stats.len_ue_slice
ue_dict["ues"] = []
for u in ind.ue_slice_stats.ues:
ues_dict = {}
dl_id = "null"
ul_id = "null"
if u.dl_id >= 0 and dl_dict["num_of_slices"] > 0:
dl_id = u.dl_id
if u.ul_id >= 0 and ul_dict["num_of_slices"] > 0:
ul_id = u.ul_id
ues_dict = {
"rnti" : hex(u.rnti),
"assoc_dl_slice_id" : dl_id,
"assoc_ul_slice_id" : ul_id
}
ue_dict["ues"].append(ues_dict)
assoc_rnti = u.rnti
ind_dict = slice_stats
ind_json = json.dumps(ind_dict)
with open("rt_slice_stats.json", "w") as outfile:
outfile.write(ind_json)
return json
####################
#### SLICE INDICATION CALLBACK
####################
class SLICECallback(ric.slice_cb):
# Define Python class 'constructor'
def __init__(self):
# Call C++ base class constructor
ric.slice_cb.__init__(self)
# Override C++ method: virtual void handle(swig_slice_ind_msg_t a) = 0;
def handle(self, ind):
slice_ind_to_dict_json(ind)
####################
#### SLICE CONTROL FUNCS
####################
def create_slice(slice_params, slice_sched_algo):
s = ric.fr_slice_t()
s.id = slice_params["id"]
s.label = slice_params["label"]
s.len_label = len(slice_params["label"])
s.sched = slice_params["ue_sched_algo"]
s.len_sched = len(slice_params["ue_sched_algo"])
if slice_sched_algo == "PR":
s.params.type = ric.SLICE_ALG_SM_V0_PR
s.params.u.pr.max_ratio = slice_params["slice_algo_params"]["max_ratio"]
s.params.u.pr.min_ratio = slice_params["slice_algo_params"]["min_ratio"]
s.params.u.pr.dedicated_ratio = slice_params["slice_algo_params"]["dedicated_ratio"]
else:
print("Unkown slice algo type")
return s
####################
#### SLICE CONTROL PARAMETER EXAMPLE - ADD SLICE
####################
slice_0_ratio = 90
slice_1_ratio = 10
add_pr_slices = {
"num_slices" : 2,
"slice_sched_algo" : "PR",
"slices" : [
{
"id" : 0,
"label" : "s1",
"ue_sched_algo" : "PF",
"slice_algo_params" : {"max_ratio" : slice_0_ratio , "min_ratio" : slice_0_ratio, "dedicated_ratio" : slice_0_ratio},
},
{
"id" : 1,
"label" : "s2",
"ue_sched_algo" : "PF",
"slice_algo_params" : {"max_ratio" : slice_1_ratio, "min_ratio" : slice_1_ratio, "dedicated_ratio" : slice_1_ratio},
}
]
}
####################
#### SLICE CONTROL PARAMETER EXAMPLE - ASSOC UE SLICE
####################
assoc_ue_slice = {
"num_ues" : 1,
"ues" : [
{
"rnti" : assoc_rnti,
"assoc_dl_slice_id" : 1, # if set to -1, gNB will not perform DL slice association.
"assoc_ul_slice_id" : -1 # if set to -1, gNB will not perform UL slice association.
}
]
}
def fill_slice_ctrl_msg(ctrl_type, ctrl_msg):
msg = ric.slice_ctrl_msg_t()
if (ctrl_type == "ADDMOD"):
msg.type = ric.SLICE_CTRL_SM_V0_ADD
dl = ric.ul_dl_slice_conf_t()
ul = ric.ul_dl_slice_conf_t()
# DL SLICE CTRL ADD
if "dl_conf" in ctrl_msg:
dl_conf = ctrl_msg["dl_conf"]
slice_sched_algo = "NULL"
if "slice_sched_algo" in dl_conf:
slice_sched_algo = dl_conf["slice_sched_algo"]
dl.sched_name = slice_sched_algo
dl.len_sched_name = len(slice_sched_algo)
dl.len_slices = dl_conf["num_slices"]
slices = ric.slice_array(dl_conf["num_slices"])
for i in range(0, dl_conf["num_slices"]):
slices[i] = create_slice(dl_conf["slices"][i], dl_conf["slice_sched_algo"])
dl.slices = slices
msg.u.add_mod_slice.dl = dl
# UL SLICE CTRL ADD
if "ul_conf" in ctrl_msg:
ul_conf = ctrl_msg["ul_conf"]
slice_sched_algo = "NULL"
if "slice_sched_algo" in ul_conf:
slice_sched_algo = ul_conf["slice_sched_algo"]
ul.sched_name = slice_sched_algo
ul.len_sched_name = len(slice_sched_algo)
ul.len_slices = ul_conf["num_slices"]
slices = ric.slice_array(ul_conf["num_slices"])
for i in range(0, ul_conf["num_slices"]):
slices[i] = create_slice(ul_conf["slices"][i], ul_conf["slice_sched_algo"])
ul.slices = slices
msg.u.add_mod_slice.ul = ul
elif (ctrl_type == "ASSOC_UE_SLICE"):
msg.type = ric.SLICE_CTRL_SM_V0_UE_SLICE_ASSOC
msg.u.ue_slice.len_ue_slice = ctrl_msg["num_ues"]
assoc = ric.ue_slice_assoc_array(ctrl_msg["num_ues"])
for i in range(ctrl_msg["num_ues"]):
a = ric.ue_slice_assoc_t()
a.rnti = ctrl_msg["ues"][i]["rnti"]
a.dl_id = ctrl_msg["ues"][i]["assoc_dl_slice_id"]
a.ul_id = ctrl_msg["ues"][i]["assoc_ul_slice_id"]
assoc[i] = a
msg.u.ue_slice.ues = assoc
return msg
####################
#### GENERAL
####################
ric.init(sys.argv)
signal.signal(signal.SIGINT, sig_handler)
conn = ric.conn_e2_nodes()
assert(len(conn) > 0)
node_idx = -1
for i in range(0, len(conn)):
if (conn[i].id.type == ric.e2ap_ngran_gNB or conn[i].id.type == ric.e2ap_ngran_gNB_DU):
# Assuming this xApp only support 1 node
node_idx = i
break
assert(node_idx > -1 and "Node type should be DU or gNB")
####################
#### SLICE INDICATION
####################
slice_cb = SLICECallback()
hndlr = ric.report_slice_sm(conn[node_idx].id, ric.Interval_ms_1000, slice_cb)
time.sleep(5)
####################
#### SLICE CTRL ADD
####################
ul_dl_conf = {
"dl_conf": add_pr_slices,
}
msg = fill_slice_ctrl_msg("ADDMOD", ul_dl_conf)
ric.control_slice_sm(conn[node_idx].id, msg)
time.sleep(5)
####################
#### SLICE CTRL ASSOC
####################
while(assoc_rnti == 0):
time.sleep(1)
assoc_ue_slice["ues"][0]["rnti"] = assoc_rnti
assoc_ue_slice["ues"][0]["assoc_dl_slice_id"] = 1
msg = fill_slice_ctrl_msg("ASSOC_UE_SLICE", assoc_ue_slice)
ric.control_slice_sm(conn[node_idx].id, msg)
with open("rt_slice_stats.json", "w") as outfile:
outfile.write(json.dumps({}))
mod_pr_slices = add_pr_slices
direction = 1 # 1: increasing and -1: decreasing
while True:
mod_pr_slices["slices"][0]["slice_algo_params"]["max_ratio"] -= direction * 10
mod_pr_slices["slices"][0]["slice_algo_params"]["min_ratio"] -= direction * 10
mod_pr_slices["slices"][0]["slice_algo_params"]["dedicated_ratio"] -= direction * 10
mod_pr_slices["slices"][1]["slice_algo_params"]["max_ratio"] += direction * 10
mod_pr_slices["slices"][1]["slice_algo_params"]["min_ratio"] += direction * 10
mod_pr_slices["slices"][1]["slice_algo_params"]["dedicated_ratio"] += direction * 10
### modify PR slice config
if mod_pr_slices["slices"][1]["slice_algo_params"]["dedicated_ratio"] <= slice_1_ratio:
direction = 1
elif mod_pr_slices["slices"][1]["slice_algo_params"]["dedicated_ratio"] >= slice_0_ratio:
direction = -1
ul_dl_conf = {
"dl_conf": mod_pr_slices,
}
msg = fill_slice_ctrl_msg("ADDMOD", ul_dl_conf)
ric.control_slice_sm(conn[node_idx].id, msg)
assoc_ue_slice["ues"][0]["rnti"] = assoc_rnti
assoc_ue_slice["ues"][0]["assoc_dl_slice_id"] = 1
msg = fill_slice_ctrl_msg("ASSOC_UE_SLICE", assoc_ue_slice)
ric.control_slice_sm(conn[node_idx].id, msg)
time.sleep(10)
In order to compile this xApp you should run:
cd /path/to/xapp-source/build
cmake ..
make -j
sudo make install
ldconfig
Then in order to run it:
./build/src/dev/c/lab6 -c ../conf_files/default.conf
If run successfully, the output should be as follows:
br@mate:~/LABS/xapp-source/build$ python3 xapp/dev_python/xapp_sdk/xapp_slice_moni_ctrl.py -c ../conf_files/default.conf
10:20:07.723592 [INFO]: conf_file.c:443 Config file path ../conf_files/default.conf. To change the path add -c path_to_file e.g., python3 mntr.py -c default.conf
10:20:07.723641 [INFO]: e42_xapp_api.c:81 Git SHA1 e7a9fa37e4f056ffd8ca49533e30acf2d4371ce0
10:20:07.723645 [INFO]: e42_xapp.c:191 NearRT-RIC Server IP Address = 10.244.2.155, PORT = 36422
10:20:07.724449 [INFO]: emb_sm_ag.c:93 Loaded SM(s) 10, custom SMs true
10:20:07.724576 [INFO]: emb_sm_ric.c:93 Loaded SM(s) 10, custom SMs true
10:20:07.725617 [INFO]: msg_handler_xapp.c:533 E42 SETUP-REQUEST tx
10:20:07.725831 [INFO]: msg_handler_xapp.c:374 E42 SETUP-RESPONSE rx xApp ID 9
10:20:07.725837 [INFO]: msg_handler_xapp.c:390 Connected E2 Node(s) 1
10:20:07.726143 [INFO]: msg_handler_xapp.c:568 RIC_SUBSCRIPTION_REQUEST tx RAN_FUNC_ID 145 RIC_REQ_ID 1
10:20:07.726429 [INFO]: msg_handler_xapp.c:155 RIC_SUBSCRIPTION_RESPONSE rx RAN_FUNC_ID 145 RIC_REQ_ID 1
ADD PR DL SLICE: id 0, label s1, max_ratio 90, min_ratio 90, dedicated_ratio 90
ADD PR DL SLICE: id 1, label s2, max_ratio 10, min_ratio 10, dedicated_ratio 10
RESET UL SLICE, algo = NONE
10:20:12.730912 [INFO]: msg_handler_xapp.c:613 E42_RIC_CONTROL_REQUEST RAN_FUNC_ID 145 RIC_REQ_ID 2 tx
10:20:12.731207 [INFO]: msg_handler_xapp.c:306 CONTROL ACK rx RIC_REQ_ID 2
ASSOC DL SLICE: rnti e0f1, to slice id 1
10:20:17.732309 [INFO]: msg_handler_xapp.c:613 E42_RIC_CONTROL_REQUEST RAN_FUNC_ID 145 RIC_REQ_ID 3 tx
10:20:17.732662 [INFO]: msg_handler_xapp.c:306 CONTROL ACK rx RIC_REQ_ID 3
ADD PR DL SLICE: id 0, label s1, max_ratio 50, min_ratio 50, dedicated_ratio 50
ADD PR DL SLICE: id 1, label s2, max_ratio 14, min_ratio 14, dedicated_ratio 14
RESET UL SLICE, algo = NONE
10:20:17.735474 [INFO]: msg_handler_xapp.c:613 E42_RIC_CONTROL_REQUEST RAN_FUNC_ID 145 RIC_REQ_ID 4 tx
10:20:17.735783 [INFO]: msg_handler_xapp.c:306 CONTROL ACK rx RIC_REQ_ID 4
ASSOC DL SLICE: rnti e0f1, to slice id 1
10:20:17.735817 [INFO]: msg_handler_xapp.c:613 E42_RIC_CONTROL_REQUEST RAN_FUNC_ID 145 RIC_REQ_ID 5 tx
10:20:17.735962 [INFO]: msg_handler_xapp.c:306 CONTROL ACK rx RIC_REQ_ID 5
ADD PR DL SLICE: id 0, label s1, max_ratio 46, min_ratio 46, dedicated_ratio 46
ADD PR DL SLICE: id 1, label s2, max_ratio 1e, min_ratio 1e, dedicated_ratio 1e
RESET UL SLICE, algo = NONE
Uninstall​
To uninstall the network, use the following command:
brc remove network open-ran.yaml
Checking via the brc observe
command, you should see that all the elements are removed.
Advanced (optional)
This lab can also be performed leveraging an over-the-air deployment. In this section, we provide a sample deployment with a LITEON RU. Similarly to the previous deployment, this includes a 5G Standalone (SA) network with OpenAirInterface (OAI) gNB using a LITEON RU device, and Open5GS core network.
apiVersion: athena.trirematics.io/v1
kind: Network
metadata:
name: bubbleran
namespace: trirematics
spec:
slices:
- plmn: "00101"
dnn: internet
network-mode: IPv4
service-type: eMBB
differentiator: 0x000000
ipv4-range: 12.1.1.0/24
ipv6-range: 2001:db8:1::/64
access:
- name: oai-gnb
stack: 5g-sa
model: oai-ran/monolithic-gnb-ru #cu-du-ru
identity:
an-id: 30
tracking-area: 1
radio:
device: oran-7.2
antenna:
formation: 4x4
scheduling:
nodeName: bubble2 # Change with any other CU-DU node
annotations:
# PCI IDs obtained by running `dpdk-devbind.py --status` in the CU-DU node
extras.t9s.io/pci-ids: '["0000:01:11.0", "0000:01:11.1"]'
# VF MACs obtained for dpdk0 interface by running `ip link show` in CU-DU node
extras.t9s.io/du-macs: '["02:11:22:33:44:66", "02:11:22:33:44:67"]'
# RU MAC (repeated) obtained by logging into RU and running `show eth-info`
extras.t9s.io/ru-macs: '["e8:c7:4f:25:80:f9", "e8:c7:4f:25:80:f9"]'
extras.t9s.io/mtu: '9000'
# RU IP used for logging into it
extras.t9s.io/o1-remote-ipv4: 192.168.1.40
# Flag for reboot, after first use, it can be set to `false` if values in 'cells' is not changed
extras.t9s.io/liteon-ru-reboot: 'true' #'false'
cells:
- band: n78
arfcn: 643296
bandwidth: 100MHz
subcarrier-spacing: 30kHz
tdd-config:
period: 2.5ms
dl-slots: 3
dl-symbols: 6
ul-slots: 1
ul-symbols: 4
controller: flexric.bubbleran
core-networks:
- ogscore.bubbleran
core:
- name: ogscore
stack: 5g-sa
model: open5gs/5gc
profiles:
- debug
identity:
region: 128
cn-group: 4
cn-id: 5
edge:
- name: flexric
stack: 5g-sa
model: mosaic5g/flexric
dns:
ipv4:
default: 8.8.8.8
secondary: 8.8.4.4
Deployment​
Use the command brc install network open-ran-liteon.yaml
to deploy the network.
It should finish without errors and printout the three Kubernetes resource names that were created.
Check for the status of the deployment using the command brc observe
.
Wait until all the Elements other than the UE are in the STATUS
set to
1/1 Y
state.
After the network has been successfully deployed, the monitoring xApp can be deployed following the exact same steps previously detailed.
Before deploying this network first make sure:
- The CU-DU node name specified in the deployment file (in this case
bubble2
on the sectionscheduling
) is correct. - The CU-DU node is able to reach the RU IP specified in the deployment file. If not sure about the IP, please check the Google Sheet (provided to all customers) with all IPs of your MX-PDK.
To do so you may try to ssh into the RU from the node with the command
ssh user@RU-IP
replacingRU-IP
with the actual IP. - The CU-DU node is properly synchronized with the PTP grandmaster switch. To do so you may check the status with
sudo systemctl status ptp4l.service
sudo systemctl status phc2sys.service
- The CU-DU node's DPDK has been setup with
sudo dpdk-setup