Cell Congestion Optimization rApp
The TOSSI Open RAN O1 Stack is a complete, standards-based environment for building and testing O1-driven, closed-loop applications on a live 5G network. It brings together a 5G RAN, an O1 adapter, and an O1 NETCONF server, and integrates with a Non-RT RIC application to close a control loop over the O-RAN O1 interface.
The stack is designed to interoperate with the platforms the community already runs, the O-RAN Software Community Non-RT RIC and an ONAP-based SMO, so that an independent rApp can observe the network, decide, and act, entirely through standard interfaces. The reference application is the open-source Cell Congestion Optimization rApp published by coRAN LABS, which detects Physical Resource Block congestion from O1 performance data and relieves it with signal-aware inter-gNB handovers.
Architecture
The stack has four cooperating components.
OCUDU RAN. A monolithic 5G gNB connected to O-RUs over Open Fronthaul 7.2x and to a 5G core over N2 and N3. It exports per-cell and per-user performance, configured neighbour relations, and serving and neighbour radio measurements, and executes inter-gNB handovers over Xn.
OCUDU O1 Adaptor. Consumes the RAN metrics stream and translates it into 3GPP TS 28.554 performance-management envelopes, streaming them to the collector defined by a provisioned PerfMetricJob.
OCUDU NETCONF. Presents the RAN as a 3GPP network resource model over the O1 NETCONF interface, and holds the PerfMetricJob that controls performance streaming. It is compatible with OpenDaylight-based controllers, so an SMO can mount the network element and manage it.
Cell Congestion Optimization rApp. The open-source Non-RT RIC application published by coRAN LABS. It ingests the O1 performance-management stream, detects Physical Resource Block congestion at the cell level, and selects a relief neighbour the user can actually hear by gating the target on reported RSRP. It actuates the resulting inter-gNB handover over O1 through the SMO SDNR controller, exposes an operator dashboard and a runtime policy API, and is onboarded onto the O-RAN SC Non-RT RIC over the R1 interface.
Prerequisites
- A Linux host for the RAN, with a DPDK-capable fronthaul NIC and access to O-RUs and a 5G core.
- Python 3 for the O1 adapter.
- Docker Engine 24.0 or newer, for the NETCONF server and container image builds.
- For the rApp, an O-RAN SC Non-RT RIC with rApp Manager is the primary deployment target (R1-based onboarding). A Kubernetes cluster and Helm are optional, only for a standalone manual deployment of the rApp that is unrelated to R1.
sudo apt install python3 python3-venv python3-pip
Deployment
Clone the three TOSSI components into a working directory (the adapter, NETCONF, and RAN side by side):
git clone https://github.com/TOSSI-Foundation/OCUDU-RAN.git
git clone https://github.com/TOSSI-Foundation/OCUDU-Netconf.git
git clone https://github.com/TOSSI-Foundation/OCUDU-O1-Adaptor.git
NETCONF server (start first)
cd OCUDU-Netconf
docker build -t ocudu-netconf/ocudu-netconf:latest .
docker run -it --name ocudu-netconf -p 830:830 ocudu-netconf/ocudu-netconf:latest --config gnb
cd ..
The server is ready when it logs that it is listening for connections on port 830.
RAN (gNB)
The Xn inter-gNB handover support and the paired example configurations live on the xnap_handover branch. Check it out, then build (install the build dependencies listed in the OCUDU-RAN README first, including DPDK):
cd OCUDU-RAN
git checkout xnap_handover
mkdir -p build && cd build
sudo cmake -DDU_SPLIT_TYPE=SPLIT_7_2 -DENABLE_DPDK=True -DASSERT_LEVEL=MINIMAL -DENABLE_UHD=OFF ../
make -j "$(nproc)"
cd ../..
Inter-gNB handover requires two gNBs. The repository ships a paired set of example configurations under OCUDU-RAN/configs/, one for each gNB:
gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb1.ymlfor the first gNBgnb_ru_liteon_tdd_n78_100mhz_xnap_gnb2.ymlfor the second gNB
Edit each configuration for your site (core/AMF address, the fronthaul NIC and O-RU parameters, and the CPU affinities), keeping the cell identity and neighbour relations that pair the two gNBs. Then run each on its host:
# first gNB host
sudo ./OCUDU-RAN/build/apps/gnb/gnb -c OCUDU-RAN/configs/gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb1.yml
# second gNB host
sudo ./OCUDU-RAN/build/apps/gnb/gnb -c OCUDU-RAN/configs/gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb2.yml
O1 adapter
cd OCUDU-O1-Adaptor
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
./venv/bin/python src/o1_adapter.py \
--netconf_host localhost --netconf_port 830 \
--netconf_username root --netconf_password root \
--profile gnb --ws_host localhost --ws_port 8001
cd ..
Provision O1 performance streaming
The NETCONF server re-seeds its factory config on every start, so run this once after it comes up (and again after any restart) to point the gNB O1 performance stream at your collector. Set STREAM_TARGET to your endpoint (for example a Non-RT RIC rApp /pm). It is an idempotent merge, safe to re-run.
STREAM_TARGET="http://<collector-host>:<port>/pm" ./OCUDU-O1-Adaptor/venv/bin/python - <<'PY'
import os
from ncclient import manager
t = os.environ["STREAM_TARGET"]
cfg = f"""<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ManagedElement xmlns="urn:3gpp:sa5:_3gpp-common-managed-element"><id>ran1</id><GNBCUCPFunction xmlns="urn:3gpp:sa5:_3gpp-nr-nrm-gnbcucpfunction"><id>cucp1</id><PerfMetricJob><id>congestion-rapp</id><attributes><administrativeState>UNLOCKED</administrativeState><performanceMetrics>DRB.MaxActiveUeDl</performanceMetrics><granularityPeriod>5</granularityPeriod><streamTarget>{t}</streamTarget></attributes></PerfMetricJob></GNBCUCPFunction></ManagedElement></config>"""
with manager.connect(host="localhost", port=830, username="root", password="root", hostkey_verify=False, allow_agent=False, look_for_keys=False, timeout=20) as m:
m.edit_config(target="running", config=cfg, default_operation="merge")
print("PerfMetricJob:", "PROVISIONED" if "congestion-rapp" in m.get_config(source="running").data_xml else "FAILED")
PY
Prints PerfMetricJob: PROVISIONED on success. The adapter picks up the job over its NETCONF subscription automatically; no restart is required.
Cell Congestion Optimization rApp (coRAN LABS)
git clone https://github.com/coranlabs/cell_congestion_optimization_rapp.git
cd cell_congestion_optimization_rapp
Primary: R1-based deployment (O-RAN SC Non-RT RIC rApp Manager). Prerequisites: build and push the rApp image to a registry your cluster can pull from; bake your configuration and secrets into the Helm chart values (SDNR endpoint and credentials, managed-element id, InfluxDB token, dashboard accounts), because the R1 and ACM path ignores instance values; and upload the charts to the Non-RT RIC chart repository. Then onboard, prime, create an instance, and deploy:
RM=$(kubectl get svc rappmanager -n <namespace> -o jsonpath='{.spec.clusterIP}')
# build the CSAR package
bash scripts/create-csar.sh
# onboard
curl -X POST http://$RM:8080/rapps/congestion-rapp \
-F file=@rapp-package/dist/congestion-rapp-1.1.0.csar
# prime
curl -X PUT http://$RM:8080/rapps/congestion-rapp \
-H 'Content-Type: application/json' -d '{"primeOrder":"PRIME"}'
# create instance (ACM + SME + DME), capture the instance id
IID=$(curl -s -X POST http://$RM:8080/rapps/congestion-rapp/instance \
-H 'Content-Type: application/json' \
-d '{"acm":{"instance":"congestion-instance"},"sme":{"providerFunction":"congestion-provider","serviceApis":"congestion-api","invokers":"ics-invoker"},"dme":{"infoTypesProducer":["cell-congestion-state"],"infoProducer":"congestion-rapp-producer"}}' \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["rappInstanceId"])')
echo "instance: $IID"
# deploy
curl -X PUT http://$RM:8080/rapps/congestion-rapp/instance/$IID \
-H 'Content-Type: application/json' -d '{"deployOrder":"DEPLOY"}'
Optional: manual Helm deployment (standalone, no R1).
docker build -t congestion-rapp:2.1.7 .
helm install congestion-rapp deploy/helm/congestion -n <namespace> \
--set image.repository=congestion-rapp --set image.tag=2.1.7 \
--set o1HandoverEnabled=true \
--set sdnr.endpoint=<sdnr-restconf-url> \
--set sdnr.username=<user> --set secrets.sdnrPassword=<password> \
--set nodeId=<managed-element-id>
Point the adapter STREAM_TARGET (Provision O1 performance streaming) at the rApp PM ingest endpoint. For trends and handover history, deploy the companion InfluxDB chart under deploy/helm/.
References
- OCUDU RAN: TOSSI-Foundation/OCUDU-RAN
- OCUDU NETCONF: TOSSI-Foundation/OCUDU-Netconf
- OCUDU O1 Adaptor: TOSSI-Foundation/OCUDU-O1-Adaptor
- Cell Congestion Optimization rApp: coranlabs/cell_congestion_optimization_rapp