Skip to content

1. xApp Library In-depth

1.1 xApp Library Overview

The xApp Library is a library written in Python which is used by developers to implement their own new functionalities and/or services on the dRAX RIC. The xApp Library abstracts the interfaces towards the dRAX RIC, allowing developers to use these interfaces in an easy-to-use way. The xApp Library connects multiple components using its interfaces such as:

  1. The logic as implemented by the developer
  2. The kafka and nats connectors
  3. The REST API
  4. The configuration and settings

1.2 Overview of the xApp interactions with the RIC

Once an xApp is deployed into dRAX it can interact with all the components present in the system. Currently, there are two main ways to achieve this: via the dRAX Databus or via the REST APIs. Depending on the use case, a developer can choose which communication method fits their need best. The use of the dRAX Databus is encouraged in scenarios where xApps need to access RAN data, exchange real-time data or issue commands via the Action Taker. The use of the REST APIs is best for non-real-time information gathering or for utilizing the Netconf functionalities exposed by the dRAX REST API Gateway. The xAPP interaction with the cells can happen either via the REST API or via direct Netconf sessions to the corresponding Netconf servers.

1.3 The xApp Structure and Conventions

When creating a new Python xApp, we recommend the following structure:

root/
  setup.cfg
  README.md
  requirements.txt
  core/
    restapi.py
    xapp_main.py
    xapp_metadata.json
  config/
    xapp_endpoints.json
    xapp_config.json

This folder structure splits the xApp in three parts:

  • The root folder contains necessary information about the Python packages the xApp is and needs. Since Python usually expects those to be in the base of the tree we recommend leaving them there. This includes the README.md documentation file and requirements.txt to specify python package dependencies.
  • The core folder contains the xApp code (which can be spread over multiple files and subfolders). Here, we also expect the xapp_metadata.json file which is used by the xApp itself and contains the configuration details of the xApp including the default values. As an example, we also include here the restapi.py which contains custom API endpoints exposed by the xApp. Do note the directory of the file building the xapp is used as base by the builder.
  • The config folder contains various configuration files meant to be provided at runtime. This means the folder will usually not exist and not be added to the version tracking system, as its contents are created by the xApp Helm Chart.

An example of this can be found in the example subfolder of the repository, along with some basic usage in example/core/xapp_main.py.

1.4 The xApp Builder

For convenience an xApp Builder class has been added. An example of how its used can be found in the _example/core/xapp_main/.py _example xApp in the xApp Framework repository:

builder = xapp_lib.XAppBuilder("..", absolute=False)
    builder.metadata("core/xapp_metadata.json")
    builder.endpoints("config/xapp_endpoints.json")
    builder.config("config/xapp_config.json")
    builder.readme("README.md")
    builder.restapi(
        [
            ("/api/", restapi.MainApiHandler),
            ("/api/actions", restapi.ActionsHandler),
            ("/api/request", restapi.RequestHandler),
        ]
    )
    xapp = builder.build()

This code, assuming it is written in root/core/xapp_main.py, will create an xApp builder using root/ as base (hence the “..” as its first argument). After this, the metadata, which is located in root/core/xapp_metadata.json, will be loaded by the xApp itself. The xapp_metadata.json is a mandatory configuration file of the xApp.

Next, the xapp_endpoints.json and xapp-config.json files can be loaded. Note that these files are created by the xApp Helm Chart as on deployment time configuration options, hence they are located in the root/config folder.

The xApp Builder then can load the README.md documentation file. Finally, if the xApp developer has created custom API endpoints in the root/core/restapi.py file, the APIs can be initiated by the xApp Builder as described above in the example code (providing their API endpoint and callback function).

Do note that the builder only registers paths and configuration, and thus any errors about missing files etc will only be thrown during the builder.build() call.

1.5 dRAX RIC Databus

The data from the dRAX RIC Databus comes in through the Kafka object, which automatically takes care of properly setting up and configuring a Kafka client. Note that this object is a singleton created and owned by the xApp library object itself, and calling the .kafka() method multiple times will result in the same object being returned every time, which is why the following code works:

xapp.kafka().subscribe(["topic"])
(topic, data) = xapp.kafka().recv_data()

The URL of the dRAX RIC Databus (Kafka in this case) is automatically generated in the xapp_endpoints.json file by the xApp Helm Chart. As seen, this config file is loaded into the xapp using:

builder.endpoints("config/xapp_endpoints.json")

By default, kafka will use the value associated with the “KAFKA_URL” key inside the xapp_endpoints.json. However, the developer can choose to add additional keys in the xapp_endpoints.json file through the xApp Helm Chart, which is discussed in the xApp Endpoints section. The xApp itself can then decide to use a different key by calling:

(topic, data) = xapp.kafka(name="KAFKA_URL_2").recv_data()

A simple way to check what messages are received is to simply log everything coming in from the kafka object. This is shown in Example 1 in the example/core/xapp_main.py from the xApp Framework repository and in the test_kafka_producer unittest.

### Example 1: Just logging all the messages received from the dRAX Databus
logging.info("Received message from dRAX Databus!")
logging.debug("dRAX Databus message on {topic}: {data}".format(topic=topic, data=data))

The data on the dRAX RIC Databus (Kafka) uses the JSON format. In order to decode json messages the xApp Library provides a decorator created with .json() that automatically transforms serialized data into a dictionary.

(topic, data) = xapp.kafka().json().recv_message()

The 4G and 5G RAN data that is available on the dRAX Databus is described in a separate document that can be accessed via the following link:

https://docs.google.com/document/u/1/d/e/2PACX-1vTmZjYiThXivi03Td_1LXjQVldAO_7AXqnVwc0Qe-uFCT16tKH6EBF2OTOhiZ0SFw/pub

1.6 dRAX NATS Databus

Connecting to the dRAX NATS Databus is nearly identical to connecting to the dRAX RIC Databus. The only difference is the xApp method call and the use of NATS_URL instead of KAFKA_URL:

xapp.nats().subscribe(["topic"])
(topic, data) = xapp.nats().recv_data()

1.7 dRAX Commands

1.7.1 Handover

From the xApp you can issue two types of handover commands on the dRAX NATS Databus:

  1. 4G LTE Handover command

  2. 5G NR Handover command

These are both defined inside the xapp_lib.actions module.

NOTE: As the commands are sent over the dRAX NATS Databus, the NATS client should be set up in the xApp as described in the dRAX NATS Databus section.

1.7.1.1 4G LTE Handover command

dRAX enables you to issue a handover command for a set of UEs in an LTE environment from source cells to target cells. In the example/core/xapp_main.py example of the xApp Framework we show this in Example 4a.

### Example 4a: Send handover command (LTE)
handover_list = [
    {'ueIdx': 'ueRicId_to_handover_1', 'targetCell': 'Cell_1', 'sourceCell': 'Cell_2'},
    {'ueIdx': 'ueRicId_to_handover_2', 'targetCell': 'Cell_2', 'sourceCell': 'Cell_1'}
]
actions.trigger_handover(xapp.nats(), handover_list)

The actions.trigger_handover function abstracts the lower-level details of how a handover command message is generated and sent.

The function takes a list of handovers that are required. Each handover consists of the following information :

  • 'ueIdx': This is the ueDraxId of the UE that should be handed over;
  • 'targetCell': The target cell to which the UE should get handed over to;
  • 'servingCell': The UEs’ current serving cell.

The ueIdx and servingCell values are determined from the UE Measurement messages received on the accelleran.drax.4g.ric.raw.ue_measurements Kafka Topic, and the relevant portion is shown below.

...
"Rc4gUeMeasurement": {
    "UniqueRicId": <string>,
    "CellId": <string>,
    "AttachedCellId": <string>,
    "Rsrp": <uint>,
    "Rsrq": <uint>,
...

ueIdx is UniqueRicId and the servingCell is AttachedCellId

The potential target cells should be collected from L2 Stats messages also received on the accelleran.drax.4g.ric.raw.ue_measurements Kafka Topic. These messages are in the format :

{
  "ENB": <string>,
  "CELL": <string>,
  "UE": <string>,
  "ueDraxId": <string>,
  "ueRicId": <string>,
  "EnbStatsL2StatsValues": {
    "Report": {
      "DschThroughput": <uint>,
      "UschThroughput": <uint>,
      "DownlinkBler": <uint>,
      "UplinkBler": <uint>,
      "TimingAdvance": <uint>,
      "UlSinr": <uint>
    }
  },
  "tlpublishTime__": <string>,
  "timestamp": <int>
}

The UniqueRicId field from a UEMeasurement can be matched with the ueDraxId field from a L2 Stats message. Then the cell along with its information relating to the UE can be matched using the CELL field from the corresponding L2 Stats message.

1.7.1.2 5G NR Handover command and response

Supported CU Versions

This command is supported since CU 4.2, used by default from RIC 6.5.0.

dRAX also enables you to issue a handover command of a specific UE in an NR environment to a target cell. It is also possible to check the response from the handover command. In the example/core/xapp_main.py example of the xApp Framework we show this in Examples 4b and 4c:

### Example 4b: Send handover command (5G NR)
ue_id = 7
target_cell = actions.Cell(id=456, plmn='654321')
cucp_id = "cucp-1"

transaction_id = actions.trigger_handover_5g(xapp=xapp,
                                      ue_id=ue_id,
                                      target_cell=target_cell,
                                      cucp_id=cucp_id)

The actions.trigger_handover_5g function abstracts the lower-level details of how a handover command message is generated and sent.

The parameters required are as follows :

  • 'xapp': This a reference to the xapp triggering the handover
  • 'ue_id': This is the Id of the UE that should be handed over;
  • 'target_cell': the target cell to which the UE should be handed over to (the cell is composed of the cell id and the plmn)
  • 'cucp_id': the cucp instance id that is responsible for coordinating the handover procedure and supervising the control of the serving cell

Both the ue_id, target_cell and cucp_id are determined from UE Measurement messages recieved on the accelleran.drax.5g.ric.raw.ue_measurement Kafka Topic. The cucp_id can be extracted from the "topic" field of the message by parsing the string and identifying the first segment before the first dot (e.g. "cucp_id".5G_MEAS_INFO...).

The ue_id is taken from the GnbCuCpUeId field in the RrcMeasurementReportResult portion of a UE Measurement message as shown below:

...
"RrcMeasurementReportResultInfo": {
    "GnbCuCpUeId": <uint>,
    "Timestamp": <string>,
    "ServingCellInfo": {
      "NrCgi": {
        "NrCellId": <string>,
        "PlmnId": {
          "items": {
            "0": <uint>
          }
        }
      }
    ...
  }
},
"topic":"<string>.5G_MEAS_INFO.ENB=<string>.DU=<string>.CELL=<uint>.UE=<string>"

It is the responsibility of the xApp application to determine the target_cell required for the handover. Potential target cells for a UE can be determined from the Neighbour Cells listed in the following portion of a UE Measurement message.

...
"NumberOfIncludedCells": <uint>,
    "CellInfo": {
      "0": {
        "PhyCellId": <uint>,
        "NeighbourCellInfo": {
          "NrCgi": {
            "NrCellId": <string>,
            "PlmnId": {
              "items": {
                "0": <uint>
              }
            }
          },
...

Handover responses are received on the Kafka topic accelleran.drax.5g.ric.raw.ran_control_response', therefore you will also need to subscribe to this Kafka topic in the subscribe_to_topics function. The reponse will be in the following format:

{
  "RIC_REQUESTOR": <string>,
  "RIC_INSTANCE": <string>,
  "E2apRanControlRsp": {
    "RicRequestorId": <uint>,
    "RicInstanceId": <uint>,
    "RicControlActionId": <uint>,
    "Timestamp": <string>
  },
  "tlpublishTime__": <string>,
  "spanContext__": <string>
}

Example 4c shows a handover response being received, and the data being parsed using the actions.parse_ran_control_response(data) function.

### Example 4c: Receive handover response
(topic, data) = xapp.kafka().json().recv_message()
if data and 'accelleran.drax.5g.ric.raw.ran_control_response' in topic:
    timestamp, transaction_id, requestor_id = actions.parse_ran_control_response(data)

The actions.parse_ran_control_response function will return the time the message was generated, the requestor_id (which is the xapp.id), and also the transaction_id so the response can be matched with the transaction_id returned by the actions.trigger_handover function from Example 4b.

1.7.2 UE Admission Control

Supported CU Versions

This command is supported since CU 4.3 used by default from RIC 7.0.0.

The UE Admission Control grants dRAX RIC the authority to allow or reject a User Equipment (UE) from accessing the 5G network.

Based on certain criteria and by evaluating factors like network load and available resources, dRAX RIC can determine what decision to take about the UE's connection request.

If a UE is accepted by dRAX RIC, the dRAX RIC will be notified when the UE information is updated or when the UE state changes.

This proactive management helps prevent network congestion and ensures optimal resource allocation.

Hence, it plays a crucial role in maintaining network performance, quality of service, and resource efficiency within the radio access network.

1.7.2.1 Prerequisite

To be able to activate this feature within dRAX RIC, some prerequisite configuration must be performed.

UE Admission Control prerequisite

To allow dRAX RIC to have control over UE admission, the CU-CP must be configured properly.

To do so, the CU-CP provides the ricUeAdmissionControlEnabled configuration flag that must be set to true.

The ricUeAdmissionControlEnabled flag can be set using the dRAX Dashboard in the CU-CP feature configuration section or via the dRAX API.

Once activated, the incoming UE admission request will be forwarded from the CU to the dRAX RIC and thus, will allow the dRAX RIC to have control over UE admission decisions.

1.7.2.2 Receive messages on NATS

From the xApp point-of-view, the application should listen the following subjects on NATS:

  • *.UE-ADMISSION-REQUEST subject is used to be notified when a CU requests admission of a UE to the dRAX RIC
  • *.UE-RELEASE-INDICATION subject is used to notify dRAX RIC when a UE context is released (triggered by the UE or forced by dRAX RIC)
  • *.UE-INFO-UPDATE-INDICATION subject is used to notify dRAX RIC when the information state of a UE is updated

Info

A NATS subject is a character string used as an identifier to direct messages to the relevant subscribers within the NATS messaging system.

The following example shows how to subscribe to the NATS subjects from an xApp to be able to receive all the necessary messages for the UE Admission Control:

Example

# NATS subscription for the UE admission example
xapp.nats(endpoint="NATS_URL_5G").subscribe([
  '*.UE-ADMISSION-REQUEST',
  '*.UE-RELEASE-INDICATION',
  '*.UE-INFO-UPDATE-INDICATION'
])

Then, the xApp can read the UE Admission Control messages from the dRAX RIC NATS Databus:

(topic, data) = xapp.nats().recv_data()

1.7.2.3 UE Admission Request messages

When a new message is received on NATS, it is necessary to parse the message with the help of the topic field.

Depending on which topic the message arrives, the processing will have to be different.

Example

if "UE-ADMISSION-REQUEST" in topic:
    ue_adm_req = nr.ue_admission_request_message(encoded_data=data)
    logging.info("UE-ADMISSION-REQUEST received: {}".format(ue_adm_req))

The ue_admission_request_message function is used to decode encoded messages received on the UE-ADMISSION-REQUEST subject.

It takes as argument the encoded message and will return a decoded object.

Here is an example of the returned object:

CuCpName: "cucp-1"
DuId: 1
NrCgi {
  NrCellId: 513
  PlmnId {
    Data: 153
    Data: 249
    Data: 16
  }
}
CRnti: 42003
CuCpUeId: 14
DuUeId: 1
1.7.2.3.1 Accept UE Admission

The Accept UE Admission command is the decision made by dRAX RIC to allow a specific UE to access the 5G network.

Once the ue_admission_request_message has been received on NATS and decoded, the CuCpName and the CuCpUeId can be used to accept the new UE.

Example

err = actions.ue_admission_accept(xapp=xapp, cucp_name=ue_adm_req.CuCpName, cucp_ue_id=ue_adm_req.CuCpUeId)
if err == 0:
  logging.info(f"Admission of UE {ue_adm_req.CuCpUeId} on CU {ue_adm_req.CuCpName} succeeded")
else:
  logging.info(f"Admission of UE {ue_adm_req.CuCpUeId} on CU {ue_adm_req.CuCpName} failed")

Note

After a UE has been accepted, it is the responsibility of the xApp to keep track about the list of accepted / rejected UEs

1.7.2.3.2 Reject UE Admission

The Reject UE Admission command is the decision made by dRAX RIC to deny a specific UE to access the 5G network.

Once the ue_admission_request_message has been received on NATS and decoded, the CuCpName and the CuCpUeId can be used to reject the new UE.

Example

err = actions.ue_admission_reject(xapp=xapp, cucp_name=ue_adm_req.CuCpName, cucp_ue_id=ue_adm_req.CuCpUeId)
if err == 0:
  logging.info(f"Rejection of UE {ue_adm_req.CuCpUeId} on CU {ue_adm_req.CuCpName} succeeded")
else:
  logging.info(f"Rejection of UE {ue_adm_req.CuCpUeId} on CU {ue_adm_req.CuCpName} failed")

1.7.2.4 Control Admitted UE

Once the UE is admitted by dRAX RIC, the xApp will be able to receive notifications about the admitted UEs state.

1.7.2.4.1 UE Update Indication

The UE Update Indication message is a notification received by dRAX RIC when the state of an admitted UE changes or is updated.

Example

# Show ue update information
if "UE-INFO-UPDATE-INDICATION" in topic:
  ue_info_update = nr.ue_info_update_indication_message(encoded_data=data)
  logging.info("UE-INFO-UPDATE-INDICATION received: {}".format(ue_info_update))

The ue_info_update_indication_message function is used to decode encoded messages received on the UE-INFO-UPDATE-INDICATION subject.

It takes as argument the encoded message and will return a decoded object.

Here is an example of the returned object:

CuCpName: "cucp-1"
UeId: 1
DuUeId: 42002
CRnti: 42002
DuId: 1
DuGuid: "Kf1ap_f1_ap_0"
AmfGuid: "Kngap_ng_ap_0"
Guami {
  PlmnIdentity {
    Data: 0
    Data: 241
    Data: 16
  }
  AmfRegionId: 2
  AmfSetId: 1
  AmfPointer: 0
}
NrCgiData {
  NrCellId: 17
  PlmnId {
    Data: 153
    Data: 249
    Data: 16
  }
}
1.7.2.4.2 UE Release Indication

The UE Release Indication message is a notification received by dRAX RIC when the communication with a UE terminates.

Example

# Show ue release information
if "UE-RELEASE-INDICATION" in topic:
  ue_release_ind = nr.ue_release_indication_message(encoded_data=data)
  logging.info("UE-RELEASE-INDICATION received: {}".format(ue_release_ind))

The ue_release_indication_message function is used to decode encoded messages received on the UE-RELEASE-INDICATION subject.

It takes as argument the encoded message and will return a decoded object.

Here is an example of the returned object:

CuCpName: "cucp-1"
CuCpUeId: 24
RanUeId: 1 # Available if known
1.7.2.4.3 UE Context release

The UE Context Release command is the decision made by dRAX RIC to terminate the session of a specific UE within the 5G network.

Example

# Release an accepted ue
err = actions.ue_context_release_with_cucp_ue_id(xapp=xapp, cucp_name=ue_adm_req.CuCpName, cucp_ue_id=ue_adm_req.CuCpUeId)
if err == 0:
  logging.info(f"Release context of UE {ue_adm_req.CuCpUeId} on CU {ue_adm_req.CuCpName} succeeded")
else:
  logging.info(f"Release context of UE {ue_adm_req.CuCpUeId} on CU {ue_adm_req.CuCpName} failed")

1.7.3 PDU Admission Control

Supported CU Versions

This command is supported since CU 4.3 used by default from RIC 7.0.0.

The PDU Admission Control grants dRAX RIC the authority to allow or reject a PDU session.

A PDU session is, in 5G, a bidirectional communication link between the user equipment (UE) and the network, encapsulating and managing the data transmission.

It plays a crucial role in maintaining data integrity and supporting diverse services within the network.

Thus, based on certain criteria and by evaluating factors like network load and available resources, dRAX RIC can determine what decision to take about the traffic load and the data flows.

This dynamic control allows for efficient utilization of network resources and ensures that the network can handle the traffic in a way that meets the specified performance criteria.

The PDU admission feature is essential for maintaining a balance between network efficiency and quality of service, especially in environments with varying traffic loads and changing network conditions.

Hence, the PDU admission control allows the dRAX RIC to adapt in real-time to the demands of different services and applications, providing an intelligent and flexible approach to managing data flows in the O-RAN architecture.

1.7.3.1 Prerequisite

To be able to activate this feature within dRAX RIC, some prerequisite configuration must be performed.

PDU Admission Control prerequisite

To allow dRAX RIC to have control over PDU admission, the UE Admission control feature must be activated and its prerequisite fulfilled.

Reference: UE Admission Control section.

:warning: The PDU Admission control cannot work without the UE Admission control turned on.

Both the ricUeAdmissionControlEnabled and ricPduAdmissionControlEnabled flags can be set using the dRAX Dashboard in the CU-CP feature configuration section or via the dRAX API.

Once activated, the incoming UE admission request and PDU admission request will be forwarded from the CU to the dRAX RIC and thus, will allow the dRAX RIC to have control over UE admission decisions and PDU admission decisions.

1.7.3.2 Receive messages on NATS

From the xApp point-of-view, the application should listen the following subjects on NATS:

  • *.PDU-SESSION-ADM-REQ subject is used to be notified when an accepted UE requests permission from the dRAX RIC to establish a PDU session
  • *.PDU-SESSION-MOD-REQ subject is used to be notified when an accepted UE requests permission from the dRAX RIC to modify its PDU session
  • *.PDU-SESSION-RELEASE-IND subject is used to notify dRAX RIC when a PDU session is released

Info

A NATS subject is a character string used as an identifier to direct messages to the relevant subscribers within the NATS messaging system.

The following example shows how to subscribe to the NATS subjects from an xApp to be able to receive all the necessary messages for the PDU Admission Control:

Example

# NATS subscription for the PDU session admission / modification example
xapp.nats(endpoint="NATS_URL_5G").subscribe([
  '*.PDU-SESSION-ADM-REQ',
  '*.PDU-SESSION-MOD-REQ',
  '*.PDU-SESSION-RELEASE-IND'
])

Then, the xApp can read the PDU Admission Control messages from the dRAX RIC NATS Databus:

(topic, data) = xapp.nats().recv_data()

1.7.3.3 PDU Session Admission Request messages

When a new message is received on NATS, it is necessary to parse the message with the help of the topic field.

Depending on which topic the message arrives, the processing will have to be different.

Example

if "PDU-SESSION-ADM-REQ" in topic:
  pdu_session_adm_req = nr.pdu_session_admission_request_message(decoded_data=data)
  logging.info("PDU-SESSION-ADM-REQ received: {}".format(pdu_session_adm_req))

The pdu_session_admission_request_message function is used to decode encoded messages received on the PDU-SESSION-ADM-REQ subject.

It takes as argument the encoded message and will return a decoded object.

Here is an example of the returned object:

CuCpName: "cucp-1"
CuCpUeId: 14
PduSessionResourceSetupRequestList {
  items {
    Id: 1
    PduSessionNasPdu {
      items: 126
      items: 2
      items: 178
      ...
      items: 1
    }
    SNssai {
      SST: 1
      SD {
        Data: 0
        Data: 0
        Data: 0
      }
    }
    ResSetupReqTransfer {
      PduSessAggMaxBitrate {
        Present: true
        Ul: 1073741824
        Dl: 1073741824
      }
      UlNgUpTnlInfo {
        TunnelAddress: false
        ...
        TunnelAddress: true
        TunnelId: 0
        TunnelId: 0
        TunnelId: 55
        TunnelId: 191
      }
      PduSessionType: PDU_SESSION_TYPE_IPv4
      QosFlowSetupReqList {
        items {
          Id: 1
          QosFlowLevelQosParameters {
            Fiveqi: 9
            AllocationAndRetentionPriority {
              PriorityLevel: 8
              PreEmptionCapability: REEMPTION_CAPA_SHALL_NOT_TRIGGER
              PreEmptionVulnerability: REEMPTION_VUL_NOT_PREEMPTABLE
            }
          }
        }
      }
    }
  }
}
1.7.3.3.1 Accept PDU Session Admission

The Accept PDU Session Admission command is the decision made by dRAX RIC to allow a specific PDU session to be set.

Once the pdu_session_admission_request_message has been received on NATS and decoded, the CuCpName and the CuCpUeId can be used to accept the new PDU session.

Example

err = actions.pdu_session_admission_accept(xapp=xapp, cucp_name=pdu_session_adm_req.CuCpName, cucp_ue_id=pdu_session_adm_req.CuCpUeId)
if err == 0:
  logging.info(f"Admission of PDU session {pdu_session_adm_req.CuCpUeId} on CU {pdu_session_adm_req.CuCpName} succeeded")
else:
  logging.info(f"Admission of PDU session {pdu_session_adm_req.CuCpUeId} on CU {pdu_session_adm_req.CuCpName} failed")
1.7.3.3.2 Reject PDU Session Admission

The Reject PDU Session Admission command is the decision made by dRAX RIC to deny a specific PDU session to be set.

Once the pdu_session_admission_request_message has been received on NATS and decoded, the CuCpName and the CuCpUeId can be used to reject the new PDU session.

Example

err = actions.pdu_session_admission_reject(xapp=xapp, cucp_name=pdu_session_adm_req.CuCpName, cucp_ue_id=pdu_session_adm_req.CuCpUeId)
if err == 0:
  logging.info(f"Rejection of PDU session {pdu_session_adm_req.CuCpUeId} on CU {pdu_session_adm_req.CuCpName} succeeded")
else:
  logging.info(f"Rejection of PDU session {pdu_session_adm_req.CuCpUeId} on CU {pdu_session_adm_req.CuCpName} failed")

1.7.3.4 PDU Session Modification messages

When a new message is received on NATS, it is necessary to parse the message with the help of the topic field.

Depending on which topic the message arrives, the processing will have to be different.

Example

if "PDU-SESSION-MOD-REQ" in topic:
  pdu_session_mod_req = nr.pdu_session_modification_request_message(encoded_data=data)
  logging.info("PDU-SESSION-MOD-REQ received: {}".format(pdu_session_mod_req))

The pdu_session_modification_request_message function is used to decode encoded messages received on the PDU-SESSION-MOD-REQ subject.

It takes as argument the encoded message and will return a decoded object.

1.7.3.4.1 Accept PDU Session Modification

The Accept PDU Session Modification command is the decision made by dRAX RIC to accept a specific PDU session to be set.

Once the pdu_session_modification_request_message has been received on NATS and decoded, the CuCpName and the CuCpUeId can be used to accept the new PDU session.

Example

err = actions.pdu_session_modification_accept(xapp=xapp, cucp_name=pdu_session_mod_req.CuCpName, cucp_ue_id=pdu_session_mod_req.CuCpUeId)
if err == 0:
  logging.info(f"Admission of PDU session modification {pdu_session_mod_req.CuCpUeId} on CU {pdu_session_mod_req.CuCpName} succeeded")
else:
  logging.info(f"Admission of PDU session modification {pdu_session_mod_req.CuCpUeId} on CU {pdu_session_mod_req.CuCpName} failed")
1.7.3.4.2 Reject PDU Session Modification

The Reject PDU Session Modification command is the decision made by dRAX RIC to deny a specific PDU session to be set.

Once the pdu_session_modification_request_message has been received on NATS and decoded, the CuCpName and the CuCpUeId can be used to reject the new PDU session.

Example

err = actions.pdu_session_admission_reject(xapp=xapp, cucp_name=pdu_session_adm_req.CuCpName, cucp_ue_id=pdu_session_adm_req.CuCpUeId)
if err == 0:
  logging.info(f"Rejection of PDU session {pdu_session_adm_req.CuCpUeId} on CU {pdu_session_adm_req.CuCpName} succeeded")
else:
  logging.info(f"Rejection of PDU session {pdu_session_adm_req.CuCpUeId} on CU {pdu_session_adm_req.CuCpName} failed")

1.7.3.5 Control Admitted PDU Session

Once the PDU session is admitted by dRAX RIC, the xApp will be able to receive notifications about the PDU session state.

1.7.3.5.1 PDU Session Release

The PDU Session Release Indication message is a notification received by dRAX RIC when an admitted PDU session is released.

Here is an example about how to read a PDU Session Release Indication message

Example

# Show pdu session release information
if "PDU-SESSION-RELEASE-IND" in topic:
  pdu_session_release_ind = nr.pdu_session_release_indication_message(encoded_data=data)
  logging.info("PDU-SESSION-RELEASE-IND received: {}".format(pdu_session_release_ind))

The pdu_session_release_indication_message function is used to decode encoded messages received on the PDU-SESSION-RELEASE-IND subject.

It takes as argument the encoded message and will return a decoded object.

Here is an example of the returned object:

CuCpName: "cucp-1"
CuCpUeId: 1
PduSessionIdList {
  items: 1
}

1.8 Publish to dRAX RIC Databus

From your xApp, you can publish data on the dRAX RIC Databus for other xApps to use as well.

1.8.1 Event-based publishing

You can publish certain event-based data to the dRAX RIC Databus. This is shown in Example 2 in the processor.py of the xApp Core:

1.8.2 Example 2: Publishing one time or event-based data on the dRAX Databus

We will publish on topic "my_test_topic", and just republish the "data" data

xapp.kafka().json().send_message("my_test_topic", data)

We have created an abstraction which takes the following parameters:

  1. "my_test_topic": This is the topic on the dRAX RIC Databus to which the data should be published
  2. data: the actual data that needs to be published, it should be of a type that can be serialized as the dRAX RIC Databus expects JSON format messages (hence the xapp.kafka().json()... decorator).

1.8.3 Periodic publishing

If one wants to periodically publish information, they can spawn a separate thread using the standard Python threading.Thread function

data = {"key": "data"}
def periodic_publish(xapp):
    while True:
        xapp.kafka().json().send_message("my_test_topic", data)
        sleep(1)

publish_thread = threading.Thread(name="PeriodicPublish", target=periodic_publish, args=(xapp,))
publish_thread.start()

1.8.4 Publish to the dRAX NATS Databus

Publishing to the dRAX NATS Databus is equally simple:

data = {"key": "data"}
def periodic_publish(xapp):
    while True:
        xapp.nats().json().send_message("my_test_topic", data)
        sleep(1)

publish_thread = threading.Thread(name="PeriodicPublish", target=periodic_publish, args=(xapp,))
publish_thread.start()

1.9 Use the dRAX RIC API Gateway

The dRAX RIC exposes a number of APIs through the dRAX RIC API Gateway. The full documentation on all of the endpoints is available on the dRAX RIC API Gateway Swagerhub, which is exposed on the following URL:

http://<kubernets_ip>:31315/api/v1/docs

Just substitute with the advertise address of your Kubernetes cluster.

Inside the actions module of the xApp Library, we have already implemented the code for making API calls, and abstracted the details on how to connect to the API. That information is stored in the xapp_endpoints.json file (see also section on xApp Configuration In-depth under API_GATEWAY_URL. We created a few examples of how this works in the xApp Example, while the SwagerHub contains detailed information and description of all the endpoints.

In summary, through the dRAX RIC API Gateway you can:

  • xApps
    • Get a list of deployed xApps
    • Get/Modify the configuration of another xApp
    • Check if the xApp is healthy
    • Deploy or delete other xApps
  • 4G Radio Controller
    • Check the status of the 4GRC
    • Configure the 4GRC
  • 4G Cells
    • Configure basic parameters of a cell using a JSON
    • Send a full NetConf RPC through the API to configure the cell
    • Upload a pre-existing configuration
    • Auto-configure cells
    • Reboot cells
  • 5G CU
    • Programmatically deploy the 5G CU-CP and CU-UP
    • Configure the 5G CU-CP and CU-UP

1.9.1 How to use the dRAX RIC API Gateway in general

Example 6 shows how to call the API gateway on the following endpoint:

/discover/services/netconf

This endpoint, as described in the SwagerHub, returns the list of NetConf services deployed on dRAX. Since the Accelleran 4G small cells and the 5G CUs have NetConf servers running in them to be able to use NetConf to configure them, this way we can get that information.

### Example 6: How to use the dRAX RIC API Gateway
endpoint = '/discover/services/netconf'
api_response = requests.get(actions.create_api_url(xapp, endpoint))

if api_response.status_code == 200:
  try:
    logging.info(api_response.json())
  except:
    logging.info('Failed to load JSON, showing raw content of API response:')
    logging.info(api_response.text)

1.9.2 How to select only the port at which NetConf is exposed

Example 7 shows how to parse the retrieved information from the dRAX RIC API Gateway. Because the information retrieved can be parsed as JSON, in python we can simply use it as a Python dictionary:

### Example 7: Get the ports where the netconf servers of cells are exposed
        endpoint = '/discover/services/netconf'
        api_response = requests.get(actions.create_api_url(xapp, endpoint))

        for cell, cell_info in api_response.json().items():
            for port in cell_info['spec']['ports']:
                if port['name'] == 'netconf-port':
                    logging.info('Cell [{cell}] has NETCONF exposed on port [{port}]'.format(
                        cell=cell,
                        port=port['node_port'])
                    )

This would log the following example data:

[netconf-dageraadplats] has NETCONF exposed on port [30023]
[netconf-parking] has NETCONF exposed on port [32634]