xApp Python Framework

Framework Overview

This package is a framework for writing RAN Intelligent Controller (RIC) Xapps in python. The framework reduces the amount of code required in an Xapp by providing common features needed by all Python-based Xapps including communication with the RIC message router (RMR) and the Shared Data Layer (SDL).

The framework was designed to suport many types of Xapps, including applications that are purely reactive to RMR messages, and general applications that initiate actions according to other criteria.

For complete documentation see the ReadTheDocs site for xapp-frame-py.

Reactive Xapps

A reactive Xapp acts on messages that are delivered (pushed) via RMR. The Xapp only takes action upon receipt of an RMR message. The Xapp never takes action at another time.

This type of application is constructed by creating callback functions and registering them with the framework by message type. When an RMR message arrives, the appropriate callback is invoked based on the message type. An Xapp may define and register a separate callback for each expected message type. Every Xapp must define a default callback function, which is invoked when a message arrives for which no type-specific callback was registered. An analogy of this is AWS Lambda: “execute this code every time an event comes in” (the code to execute can depend on the type of event).

General Xapps

A general Xapp acts according to its own criteria, which may include receipt of RMR messages.

This type of application is constructed by creating a single function that is invoked by the framework after initialization. Typically that function contains a while (something) event loop. When the function returns, the Xapp stops. In this usage, the Xapp must fetch its own data, either from RMR, SDL or other source. The framework does less work for a general application compared to a reactive application; the framework only sets up an RMR thread and an SDL connection before invoking the client-provided function.

Threading in the Framework

RMR interactions are processed in a thread started by the framework. This implementation detail is documented here for transparency, but most users will not have to worry about this.

In both types of Xapp, the framework launches a separate thread whose only job is to read from RMR and deposit all messages (and their summaries) into a thread-safe queue. When the client Xapp reads from RMR using the framework (this read is done by the framework itself in the RMR Xapp, but by the client in a general Xapp), the read is done from the framework-managed queue. The framework is implemented this way so that a long-running client function (e.g., consume) will not block RMR reads. This is important because RMR is not a persistent message bus; if an RMR client does not read fast enough, messages can be lost. So in this framework the client code is not in the same thread as the RMR reads, to ensure that long-running client code will not cause message loss.

In the case of RMR Xapps, there are currently 3 potential threads; the thread that reads from RMR directly, and the user can optionally have the RMR queue read run in a thread, returning execution back to the user thread. The default is only two threads however, where .run does not return back execution and the user code is finished at that point.

Healthchecks

The framework provides a default RMR healthcheck probe handler for reactive Xapps. When an RMR healthcheck message arrives, this handler checks that the RMR thread is healthy (of course the Xapp cannot even reply if the thread is not healthy!), and that the SDL connection is healthy. The handler responds accordingly via RMR. The Xapp can override this probe handler by registering a new callback for the healthcheck message type.

The framework provides no healthcheck handler for general Xapps. Those applications must handle healthcheck probe messages appropriately when they read their RMR mailboxes.

There is no http service in the framework, so there is no support for HTTP-based healthcheck probes, such as what a deployment manager like Kubernetes may use.

Examples

Two sample Xapps using this framework are provided in the examples directory of the git repository. The first, ping, is a general Xapp that defines a main function that reads its RMR mailbox in addition to other work. The second, pong, is a reactive Xapp that only takes action when a message is received.

To run a demonstration, build the Docker images for both examples using the supplied Dockerfiles. Then start the Pong container (the listener) followed by the Ping container (the sender). The Ping application sends a message, the pong application receives the message and use RMR’s return-to-sender feature to reply. Ping then reads its own mailbox and demonstrates other functionality.

Installation Guide

The ricxappframe is available in PyPI . Use pip to install the version required.

Installing the ricxappframe package does NOT install the required RMR system library, a shared object written in C and available for most Linux systems. For information on how to install the RMR system library, see here for DEB/RPM packages and here to install from source. Alternatively, you can use a script (note, this file contains an RMR version that is subject to change!) in the a1 repo.

User Guide

This document explains how to develop an Xapp using the RIC Xapp framework. Information for maintainers of this framework is in the Developer Guide.

Xapp writers should use the public classes and methods from the Xapp Python framework package as documented below.

Class _BaseXapp

Although this base class should not be used directly, it is inherited by the public classes shown below and all of this class’s public methods are available for use by application writers.

Class RMRXapp

Application writers should extend this class to implement a reactive Xapp; also see class Xapp.

Class Xapp

Application writers should extend this class to implement a general Xapp; also see class RMRXapp.

Class SDLWrapper

Application writers may instantiate this class directly to communicate with the SDL service.

class ricxappframe.xapp_sdl.SDLWrapper(use_fake_sdl=False)[source]

Provides convenient wrapper methods for using the SDL Python interface. Optionally uses msgpack for binary (de)serialization: see https://msgpack.org/index.html

Published as a standalone module (and kept separate from the Xapp framework classes) so these features can be used outside Xapps.

set(ns, key, value, usemsgpack=True)[source]

Stores a key-value pair, optionally serializing the value to bytes using msgpack.

TODO: discuss whether usemsgpack should default to True or False here. This seems like a usage statistic question (that we don’t have enough data for yet). Are more uses for an xapp to write/read their own data, or will more xapps end up reading data written by some other thing? I think it’s too early to know.

Parameters:
ns: string

SDL namespace

key: string

SDL key

value:

Object or byte array to store. See the usemsgpack parameter.

usemsgpack: boolean (optional, default is True)

Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes.

set_if(ns, key, old_value, new_value, usemsgpack=True)[source]

Conditionally modify the value of a key if the current value in data storage matches the user’s last known value.

Parameters:
ns: string

SDL namespace

key: string

SDL key

old_value:

Lask known object or byte array. See the usemsgpack parameter.

new_value:

Object or byte array to be written. See the usemsgpack parameter.

usemsgpack: boolean (optional, default is True)

Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes.

Returns:
bool

True for successful modification, false if the user’s last known data did not match the current value in data storage.

set_if_not_exists(ns, key, value, usemsgpack=True)[source]

Write data to SDL storage if key does not exist.

Parameters:
ns: string

SDL namespace

key: string

SDL key

value:

Object or byte array to store. See the usemsgpack parameter.

usemsgpack: boolean (optional, default is True)

Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes.

Returns:
bool

True for successful modification, false if the user’s last known data did not match the current value in data storage.

get(ns, key, usemsgpack=True)[source]

Gets the value for the specified namespace and key, optionally deserializing stored bytes using msgpack.

Parameters:
ns: string

SDL namespace

key: string

SDL key

usemsgpack: boolean (optional, default is True)

If usemsgpack is True, the byte array stored by SDL is deserialized using msgpack to yield the original object that was stored. If usemsgpack is False, the byte array stored by SDL is returned without further processing.

Returns:
Value

See the usemsgpack parameter for an explanation of the returned value type. Answers None if the key is not found.

find_keys(ns, prefix)[source]

Find all keys matching search pattern under the namespace.

Parameters:
ns: string

SDL namespace

prefix: string

Key search pattern

Returns:
keys: list

A list of found keys.

find_and_get(ns, prefix, usemsgpack=True)[source]

Gets all key-value pairs in the specified namespace with keys that start with the specified prefix, optionally deserializing stored bytes using msgpack.

Parameters:
ns: string

SDL namespace

prefix: string

the key prefix

usemsgpack: boolean (optional, default is True)

If usemsgpack is True, every byte array stored by SDL is deserialized using msgpack to yield the original value that was stored. If usemsgpack is False, every byte array stored by SDL is returned without further processing.

Returns:
Dictionary of key-value pairs

Each key has the specified prefix. See the usemsgpack parameter for an explanation of the returned value types. Answers an empty dictionary if no keys matched the prefix.

delete(ns, key)[source]

Deletes the key-value pair with the specified key in the specified namespace.

Parameters:
ns: string

SDL namespace

key: string

SDL key

delete_if(ns, key, value, usemsgpack=True)[source]

Conditionally remove data from SDL storage if the current data value matches the user’s last known value.

Parameters:
ns: string

SDL namespace

key: string

SDL key

value:

Object or byte array to store. See the usemsgpack parameter.

usemsgpack: boolean (optional, default is True)

Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes.

Returns:
bool

True if successful removal, false if the user’s last known data did not match the current value in data storage.

add_member(ns, group, member, usemsgpack=True)[source]

Add new members to a SDL group under the namespace.

Parameters:
ns: string

SDL namespace

group: string

group name

member:

member to be added

usemsgpack: boolean (optional, default is True)

Determines whether the member is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the member to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the member can be anything that is serializable by msgpack. If usemsgpack is False, the member must be bytes.

remove_member(ns, group, member, usemsgpack=True)[source]

Remove members from a SDL group.

Parameters:
ns: string

SDL namespace

group: string

group name

member:

member to be removed

usemsgpack: boolean (optional, default is True)

Determines whether the member is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the member to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the member can be anything that is serializable by msgpack. If usemsgpack is False, the member must be bytes.

remove_group(ns, group)[source]

Remove a SDL group along with its members.

Parameters:
ns: string

SDL namespace

group: string

group name to remove

usemsgpack: boolean (optional, default is True)

Determines whether the member is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the member to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the member can be anything that is serializable by msgpack. If usemsgpack is False, the member must be bytes.

get_members(ns, group, usemsgpack=True)[source]

Get all the members of a SDL group.

Parameters:
ns: string

SDL namespace

group: string

group name to retrive

usemsgpack: boolean (optional, default is True)

Determines whether the member is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the member to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the member can be anything that is serializable by msgpack. If usemsgpack is False, the member must be bytes.

Returns:
Set[str] or Set[bytes]

A set of the members of the group.

None
is_member(ns, group, member, usemsgpack=True)[source]

Validate if a given member is in the SDL group.

Parameters:
ns: string

SDL namespace

group: string

group name

member:

member to validate

usemsgpack: boolean (optional, default is True)

Determines whether the member is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the member to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the member can be anything that is serializable by msgpack. If usemsgpack is False, the member must be bytes.

Returns:
bool

True if member was in the group, false otherwise.

group_size(ns, group)[source]

Return the number of members in a group. If the group does not exist, value 0 is returned.

Parameters:
ns: string

SDL namespace

group: string

group name to retrive size

usemsgpack: boolean (optional, default is True)

Determines whether the member is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the member to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the member can be anything that is serializable by msgpack. If usemsgpack is False, the member must be bytes.

Returns:
int

Number of members in a group.

set_and_publish(ns, channel, event, key, value, usemsgpack=True)[source]

Publish event to channel after writing data.

Parameters:
ns: string

SDL namespace

channel: string

channel to publish event

event: string

published message

key: string

SDL key

value:

Object or byte array to store. See the usemsgpack parameter.

usemsgpack: boolean (optional, default is True)

Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes.

set_if_and_publish(ns, channel, event, key, old_value, new_value, usemsgpack=True)[source]

Publish event to channel after conditionally modifying the value of a key if the current value in data storage matches the user’s last known value.

Parameters:
ns: string

SDL namespace

channel: string

channel to publish event

event: string

published message

key: string

SDL key

old_value:

Lask known object or byte array. See the usemsgpack parameter.

new_value:

Object or byte array to be written. See the usemsgpack parameter.

usemsgpack: boolean (optional, default is True)

Determines whether the old_value & new_value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the old_value & new_value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the old_value & new_value can be anything that is serializable by msgpack. If usemsgpack is False, the old_value & new_value must be bytes.

Returns:
bool

True for successful modification, false if the user’s last known data did not match the current value in data storage.

set_if_not_exists_and_publish(ns, channel, event, key, value, usemsgpack=True)[source]

Publish event to channel after writing data to SDL storage if key does not exist.

Parameters:
ns: string

SDL namespace

channel: string

channel to publish event

event: string

published message

key: string

SDL key

value:

Object or byte array to store. See the usemsgpack parameter.

usemsgpack: boolean (optional, default is True)

Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes.

Returns:
bool

True if key didn’t exist yet and set operation was executed, false if key already existed and thus its value was left untouched.

remove_and_publish(ns, channel, event, key)[source]

Publish event to channel after removing data.

Parameters:
ns: string

SDL namespace

channel: string

channel to publish event

event: string

published message

key: string

SDL key

remove_if_and_publish(ns, channel, event, key, value, usemsgpack=True)[source]

Publish event to channel after removing key and its data from database if the current data value is expected one.

Parameters:
ns: string

SDL namespace

channel: string

channel to publish event

event: string

published message

key: string

SDL key

value:

Object or byte array to store. See the usemsgpack parameter.

usemsgpack: boolean (optional, default is True)

Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function packb is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes.

Returns:
bool

True if successful removal, false if the user’s last known data did not match the current value in data storage.

remove_all_and_publish(ns, channel, event)[source]

Publish event to channel after removing all keys under the namespace.

Parameters:
ns: string

SDL namespace

channel: string

channel to publish event

event: string

published message

subscribe_channel(ns, cb, channel)[source]

Subscribes the client to the specified channels.

Parameters:
ns: string

SDL namespace

cb:

A function that is called when an event on channel is received.

channel: string

channel to subscribe

unsubscribe_channel(ns, channel)[source]

unsubscribe_channel removes subscription from one or several channels.

Parameters:
ns: string

SDL namespace

channel: string

channel to unsubscribe

start_event_listener()[source]

start_event_listener creates an event loop in a separate thread for handling events from subscriptions. The registered callback function will be called when an event is received.

handle_events()[source]

handle_events is a non-blocking function that returns a tuple containing channel name and a list of message(s) received from an event. The registered callback function will still be called when an event is received.

This function is called if SDL user decides to handle notifications in its own event loop. Calling this function after start_event_listener raises an exception. If there are no notifications, these returns None.

Returns:
Tuple:

(channel: str, message: list of str)

healthcheck()[source]

Checks if the sdl connection is healthy.

Returns:
bool

Class Symptomdata

Application writers may instantiate this class directly to communicate with the symptomdata service.

class ricxappframe.xapp_symptomdata.Symptomdata(service='', servicehost='', path='/tmp/', lwsduri=None, timeout=30)[source]
subscribe(args)[source]

internally used subscription function if the dynamic registration has been set

stop()[source]

stops the dynamic service registration/polling

getFileList(regex, fromtime, totime)[source]

internal use only, get the matching files for collect method

collect(zipfiletmpl, fileregexlist, fromtime, totime)[source]
collects the symptomdata based on the file regular expression match and stored the symptomdata. Optionaly

caller can use fromtime and totime to choose only files matching the access time

Parameters:
zipfiletmpl: string

template for zip file name using the strftime format - ex: "symptomdata"+'-%Y-%m-%d-%H-%M-%S.zip'

fileregexlist: string array

array for file matching - ex: ('examples/*.csv',)

fromtime: integer

time value seconds

totime: integer

time value seconds

Returns
——-
string

zipfile name

read()[source]

reads the stored symptomdata file content

Returns:
string

zipfile name

integer

data lenght

bytes

bytes of the file data

Class NewSubscriber

Application writers may instantiate this class directly to communicate REST based subscriptions.

class ricxappframe.xapp_subscribe.NewSubscriber(uri, timeout=None, local_address='0.0.0.0', local_port=8088, rmr_port=4061)[source]
Subscribe(subs_params=None)[source]

subscription request

Parameters:
subs_params: SubscriptionParams

structured subscription data definition defined in subsclient

Returns
——-
SubscriptionResponse

json string of SubscriptionResponse object

UnSubscribe(subs_id=None)[source]

subscription remove

Parameters:
subs_id: int

subscription id returned in SubscriptionResponse

Returns
——-
response.reason: string

http reason

response.status: int

http status code

QuerySubscriptions()[source]

Query all subscriptions

Returns:
response.data: json string

SubscriptionList

response.reason: string

http reason

response.status: int

http status code

ResponseHandler(responseCB=None, server=None)[source]

Starts the response handler and set the callback

Parameters:
responseCB

Set the callback handler, if not set the the default self._responsePostHandler is used

server: xapp_rest.ThreadedHTTPServer

if set then the existing xapp_rest.ThreadedHTTPServer handler is used, otherwise a new will be created

Returns:
status: boolean

True = success, False = failed

Class RestHandler

Application writers may instantiate this class directly to have the xapp REST server service.

class ricxappframe.xapp_rest.RestHandler(request, client_address, server)[source]
add_handler(method=None, name=None, uri=None, callback=None)[source]

Adds the function handler for given uri. The function callback is matched in first matching uri. So prepare your handlers setup in such a way that those won’t override each other. For example you can setup usual xapp handler in this list:

server = ricrest.ThreadedHTTPServer(address, port) server.handler.add_handler(self.server.handler, “GET”, “config”, “/ric/v1/config”, self.configGetHandler) server.handler.add_handler(self.server.handler, “GET”, “healthAlive”, “/ric/v1/health/alive”, self.healthyGetAliveHandler) server.handler.add_handler(self.server.handler, “GET”, “healthReady”, “/ric/v1/health/ready”, self.healthyGetReadyHandler) server.handler.add_handler(self.server.handler, “GET”, “symptomdata”, “/ric/v1/symptomdata”, self.symptomdataGetHandler)

Parameters:
method string

http method GET, POST, DELETE

name string

unique name - used for map name

uri string

http uri part which triggers the callback function

cb function

function to be used for http method processing

RMR Python Bindings

Overview

The xapp python framework package includes a python subpackage called rmr. This subpackage (ricxappframe.rmr) is a CTYPES wrapper around the RMR shared library. Most Xapp users will never use this subpackage natively; however python apps that need access to the low-level RMR API can use it.

Usage of this python package requires that the RMR shared-object library is installed in a system library that is included in the directories found by default, usually something like /usr/local/lib.

The RMR library man pages are available here: RMR Man Pages

RMR API

Wraps all RMR functions, but does not have a reference to the shared library.

ricxappframe.rmr.rmr.RMR_MAX_RCV_BYTES = None

Typical size message to receive; size is not limited

ricxappframe.rmr.rmr.RMRFL_MTCALL = None

Multi-threaded initialization flag

ricxappframe.rmr.rmr.RMRFL_NONE = None

Empty flag

ricxappframe.rmr.rmr.RMR_OK = None

State constant for OK

ricxappframe.rmr.rmr.RMR_ERR_NOENDPT = None

State constant for no endpoint based on msg type

ricxappframe.rmr.rmr.RMR_ERR_RETRY = None

State constant for retry

ricxappframe.rmr.rmr.RMR_ERR_TIMEOUT = None

State constant for timeout

class ricxappframe.rmr.rmr.rmr_mbuf_t[source]

Mirrors public members of type rmr_mbuf_t from RMR header file src/common/include/rmr.h

typedef struct {
int state; // state of processing
int mtype; // message type
int len; // length of data in the payload (send or received)
unsigned char* payload; // transported data
unsigned char* xaction; // pointer to fixed length transaction id bytes
int sub_id; // subscription id
int tp_state; // transport state (a.k.a errno)

these things are off limits to the user application

void* tp_buf; // underlying transport allocated pointer
void* header; // internal message header (whole buffer: header+payload)
unsigned char* id; // if we need an ID in the message separate from the xaction id
int flags; // various MFL (private) flags as needed
int alloc_len; // the length of the allocated space (hdr+payload)
} rmr_mbuf_t;
RE PAYLOADs type below, see the documentation for c_char_p:
class ctypes.c_char_p

Represents the C char * datatype when it points to a zero-terminated string. For a general character pointer that may also point to binary data, POINTER(c_char) must be used. The constructor accepts an integer address, or a bytes object.

ricxappframe.rmr.rmr.rmr_init(uproto_port: c_char_p, max_msg_size: int, flags: int) c_void_p[source]

Prepares the environment for sending and receiving messages. Refer to RMR C documentation for method:

extern void* rmr_init(char* uproto_port, int max_msg_size, int flags)

This function raises an exception if the returned context is None.

Parameters:
uproto_port: c_char_p

Pointer to bytes built from the port number as a string; e.g., b’4550’

max_msg_size: integer

Maximum message size to receive

flags: integer

RMR option flags

Returns:
c_void_p:

Pointer to RMR context

ricxappframe.rmr.rmr.rmr_ready(vctx: c_void_p) int[source]

Checks if a routing table has been received and installed. Refer to RMR C documentation for method:

extern int rmr_ready(void* vctx)
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

Returns:
1 for yes, 0 for no
ricxappframe.rmr.rmr.rmr_close(vctx: c_void_p)[source]

Closes the listen socket. Refer to RMR C documentation for method:

extern void rmr_close(void* vctx)
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

Returns:
None
ricxappframe.rmr.rmr.rmr_set_stimeout(vctx: c_void_p, rloops: int) int[source]

Sets the configuration for how RMR will retry message send operations. Refer to RMR C documentation for method:

extern int rmr_set_stimeout(void* vctx, int rloops)
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

rloops: int

Number of retry loops

Returns:
0 on success, -1 on failure
ricxappframe.rmr.rmr.rmr_alloc_msg(vctx: c_void_p, size: int, payload=None, gen_transaction_id=False, mtype=None, meid=None, sub_id=None, fixed_transaction_id=None)[source]

Allocates and returns a buffer to write and send through the RMR library. Refer to RMR C documentation for method:

extern rmr_mbuf_t* rmr_alloc_msg(void* vctx, int size)

Optionally populates the message from the remaining arguments.

TODO: on next API break, clean up transaction_id ugliness. Kept for now to preserve API.

Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

size: int

How much space to allocate

payload: bytes

if not None, attempts to set the payload

gen_transaction_id: bool

if True, generates and sets a transaction ID. Note, option fixed_transaction_id overrides this option

mtype: bytes

if not None, sets the sbuf’s message type

meid: bytes

if not None, sets the sbuf’s meid

sub_id: bytes

if not None, sets the sbuf’s subscription id

fixed_transaction_id: bytes

if not None, used as the transaction ID. Note, this overrides the option gen_transaction_id

Returns:
c_void_p:

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_realloc_payload(ptr_mbuf: c_void_p, new_len: int, copy=False, clone=False)[source]

Allocates and returns a message buffer large enough for the new length. Refer to RMR C documentation for method:

extern rmr_mbuf_t* rmr_realloc_payload(rmr_mbuf_t*, int, int, int)
Parameters:
ptr_mbuf: c_void_p

Pointer to rmr_mbuf structure

new_len: int

Length

copy: bool

Whether to copy the original paylod

clone: bool

Whether to clone the original buffer

Returns:
c_void_p:

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_free_msg(ptr_mbuf: c_void_p)[source]

Releases the message buffer. Refer to RMR C documentation for method:

extern void rmr_free_msg(rmr_mbuf_t* mbuf )
Parameters:
ptr_mbuf: c_void_p

Pointer to rmr_mbuf structure

Returns:
None
ricxappframe.rmr.rmr.rmr_payload_size(ptr_mbuf: c_void_p) int[source]

Gets the number of bytes available in the payload. Refer to RMR C documentation for method:

extern int rmr_payload_size(rmr_mbuf_t* mbuf)
Parameters:
ptr_mbuf: c_void_p

Pointer to rmr_mbuf structure

Returns:
int:

Number of bytes available

ricxappframe.rmr.rmr.rmr_send_msg(vctx: c_void_p, ptr_mbuf: LP_rmr_mbuf_t) LP_rmr_mbuf_t[source]

Sends the message according to the routing table and returns an empty buffer. Refer to RMR C documentation for method:

extern rmr_mbuf_t* rmr_send_msg(void* vctx, rmr_mbuf_t* mbuf)
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

ptr_mbuf: c_void_p

Pointer to rmr_mbuf structure

Returns:
ctypes c_void_p:

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_rcv_msg(vctx: c_void_p, ptr_mbuf: LP_rmr_mbuf_t) LP_rmr_mbuf_t[source]

Waits for a message to arrive, and returns it. Refer to RMR C documentation for method:

extern rmr_mbuf_t* rmr_rcv_msg(void* vctx, rmr_mbuf_t* old_mbuf)
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

ptr_mbuf: c_void_p

Pointer to rmr_mbuf structure

Returns:
ctypes c_void_p:

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_torcv_msg(vctx: c_void_p, ptr_mbuf: LP_rmr_mbuf_t, ms_to: int) LP_rmr_mbuf_t[source]

Waits up to the timeout value for a message to arrive, and returns it. Refer to RMR C documentation for method:

extern rmr_mbuf_t* rmr_torcv_msg(void* vctx, rmr_mbuf_t* old_mbuf, int ms_to)
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

ptr_mbuf: c_void_p

Pointer to rmr_mbuf structure

ms_to: int

Time out value in milliseconds

Returns:
ctypes c_void_p:

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_rts_msg(vctx: c_void_p, ptr_mbuf: LP_rmr_mbuf_t, payload=None, mtype=None) LP_rmr_mbuf_t[source]

Sends a message to the originating endpoint and returns an empty buffer. Refer to RMR C documentation for method:

extern rmr_mbuf_t* rmr_rts_msg(void* vctx, rmr_mbuf_t* mbuf)
additional features beyond c-rmr:

if payload is not None, attempts to set the payload if mtype is not None, sets the sbuf’s message type

Parameters:
vctx: ctypes c_void_p

Pointer to an RMR context

ptr_mbuf: ctypes c_void_p

Pointer to an RMR message buffer

payload: bytes

Payload

mtype: bytes

Message type

Returns:
ctypes c_void_p:

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_call(vctx: c_void_p, ptr_mbuf: LP_rmr_mbuf_t) LP_rmr_mbuf_t[source]

Sends a message, waits for a response and returns it. Refer to RMR C documentation for method:

extern rmr_mbuf_t* rmr_call(void* vctx, rmr_mbuf_t* mbuf)
Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to an RMR message buffer

Returns:
ctypes c_void_p:

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_set_meid(ptr_mbuf: LP_rmr_mbuf_t, byte_str: bytes) int[source]

Sets the managed entity field in the message and returns the number of bytes copied. Refer to RMR C documentation for method:

extern int rmr_bytes2meid(rmr_mbuf_t* mbuf, unsigned char const* src, int len);

Caution: the meid length supported in an RMR message is 32 bytes, but C applications expect this to be a nil terminated string and thus only 31 bytes are actually available.

Raises: exceptions.MeidSizeOutOfRang

Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to rmr_mbuf structure

byte_tr: bytes

Managed entity ID value

Returns:
int:

number of bytes copied

ricxappframe.rmr.rmr.rmr_get_meid(ptr_mbuf: LP_rmr_mbuf_t) bytes[source]

Gets the managed entity ID (meid) from the message header. This is a python-friendly version of RMR C method:

extern unsigned char* rmr_get_meid(rmr_mbuf_t* mbuf, unsigned char* dest);
Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to rmr_mbuf structure

Returns:
bytes:

Managed entity ID

ricxappframe.rmr.rmr.rmr_get_src(ptr_mbuf: LP_rmr_mbuf_t, dest: c_char_p) c_char_p[source]

Copies the message-source information to the buffer. Refer to RMR C documentation for method:

extern unsigned char* rmr_get_src(rmr_mbuf_t* mbuf, unsigned char* dest);
Parameters:
ptr_mbuf: ctypes POINTER(rmr_mbuf_t)

Pointer to rmr_mbuf structure

dest: ctypes c_char_p

Pointer to a buffer to receive the message source

Returns:
string:

message-source information

ricxappframe.rmr.rmr.rmr_set_vlevel(new_level: c_int)[source]

Sets the verbosity level which determines the messages RMR writes to standard error. Refer to RMR C documentation for method:

void rmr_set_vlevel( int new_level )
Parameters:
new_level: int

New logging verbosity level, an integer in the range 0 (none) to 5 (debug).

ricxappframe.rmr.rmr.rmr_wh_call(vctx: c_void_p, whid: c_int, ptr_mbuf: LP_rmr_mbuf_t, call_id: c_int, max_wait: c_int) LP_rmr_mbuf_t[source]

Sends a message buffer (msg) using a wormhole ID (whid) and waits for a response. Refer to RMR C documentation for method:

rmr_mbuf_t* rmr_wh_call( void* vctx, rmr_whid_t whid, rmr_mbuf_t* mbuf, int call_id, int max_wait )
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

whid: c_int

Wormhole ID returned by rmr_wh_open

ptr_mbuf: ctypes POINTER(rmr_mbuf_t)

Pointer to rmr_mbuf structure

call_id: c_int

number in the range of 2..255 to identify the caller

max_wait: c_int

number of milliseconds to wait for a reply

Returns:
ctypes c_void_p:

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_wh_close(vctx: c_void_p, whid: c_int)[source]

Closes the wormhole associated with the wormhole id. Refer to RMR C documentation for method:

void rmr_close( void* vctx, rmr_whid_t whid )
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

whid: c_int

Wormhole ID returned by rmr_wh_open

ricxappframe.rmr.rmr.rmr_wh_open(vctx: c_void_p, target: c_char_p) c_int[source]

Creates a direct link for sending to another RMR based process. Refer to RMR C documentation for method:

rmr_whid_t rmr_wh_open( void* vctx, char* target )
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

target: str

Pointer to bytes built from the target process host name and port number as a string; e.g., b’localhost:4550’

Returns:
c_int:

Wormhole ID

ricxappframe.rmr.rmr.rmr_wh_send_msg(vctx: c_void_p, whid: c_int, ptr_mbuf: LP_rmr_mbuf_t) LP_rmr_mbuf_t[source]

Sends a message buffer (msg) using a wormhole ID (whid). Refer to RMR C documentation for method:

rmr_mbuf_t* rmr_wh_send_msg( void* vctx, rmr_whid_t id, rmr_mbuf_t* msg )
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

whid: c_int

Wormhole ID returned by rmr_wh_open

ptr_mbuf: ctypes POINTER(rmr_mbuf_t)

Pointer to rmr_mbuf structure

Returns:
ctypes POINTER(rmr_mbuf_t):

Pointer to rmr_mbuf structure

ricxappframe.rmr.rmr.rmr_wh_state(vctx: c_void_p, whid: c_int) c_int[source]

Gets the state of the connection associated with the given wormhole (whid). Refer to RMR C documentation for method:

int rmr_wh_state( void* vctx, rmr_whid_t whid )
Parameters:
vctx: ctypes c_void_p

Pointer to RMR context

whid: c_int

Wormhole ID returned by rmr_wh_open

Returns:
c_int:

State of the connection

ricxappframe.rmr.rmr.get_payload(ptr_mbuf: c_void_p) bytes[source]

Gets the binary payload from the rmr_buf_t*.

Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to an rmr message buffer

Returns:
bytes:

the message payload

ricxappframe.rmr.rmr.get_xaction(ptr_mbuf: c_void_p) bytes[source]

Gets the transaction ID from the rmr_buf_t*.

Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to an rmr message buffer

Returns:
bytes:

the transaction id

ricxappframe.rmr.rmr.message_summary(ptr_mbuf: c_void_p) dict[source]

Builds a dict with the contents of an RMR message.

Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to an RMR message buffer

Returns:
dict:

Message content as key-value pairs; keys are defined as RMR_MS_* constants.

ricxappframe.rmr.rmr.set_payload_and_length(byte_str: bytes, ptr_mbuf: c_void_p)[source]

Sets an rmr payload and content length.

Parameters:
byte_str: bytes

the bytes to set the payload to

ptr_mbuf: ctypes c_void_p

Pointer to an rmr message buffer

Returns:
None
ricxappframe.rmr.rmr.generate_and_set_transaction_id(ptr_mbuf: c_void_p)[source]

Generates a UUID and sets the RMR transaction id to it

Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to an rmr message buffer

ricxappframe.rmr.rmr.set_transaction_id(ptr_mbuf: c_void_p, tid_bytes: bytes)[source]

Sets an RMR transaction id TODO: on next API break, merge these two functions. Not done now to preserve API.

Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to an rmr message buffer

tid_bytes: bytes

bytes of the desired transaction id

ricxappframe.rmr.rmr.get_src(ptr_mbuf: c_void_p) str[source]

Gets the message source (likely host:port)

Parameters:
ptr_mbuf: ctypes c_void_p

Pointer to an rmr message buffer

Returns:
string:

message source

RMR Helper API

ricxappframe.rmr.helpers.rmr_rcvall_msgs(mrc, pass_filter=None, timeout=0)[source]

Assembles an array of all messages which can be received without blocking (but see the timeout parameter). Effectively drains the message queue if RMR is started in mt-call mode, or draining any waiting TCP buffers. If the pass_filter parameter is supplied, it is treated as one or more message types to accept (pass through). Using the default, an empty list, results in capturing all messages. If the timeout parameter is supplied and is not zero, this call may block up to that number of milliseconds waiting for a message to arrive. Using the default, zero, results in non-blocking no-wait behavior.

Parameters:
mrc: ctypes c_void_p

Pointer to the RMR context

pass_filter: list (optional)

The message type(s) to capture.

timeout: int (optional)

The number of milliseconds to wait for a message to arrive.

Returns:
List of message summaries (dict), one for each message received; may be empty.
ricxappframe.rmr.helpers.rmr_rcvall_msgs_raw(mrc, pass_filter=None, timeout=0)[source]

Same as rmr_rcvall_msgs, but answers tuples with the raw sbuf. Useful if return-to-sender (rts) functions are required.

Parameters:
mrc: ctypes c_void_p

Pointer to the RMR context

pass_filter: list (optional)

The message type(s) to capture.

timeout: int (optional)

The number of milliseconds to wait for a message to arrive.

Returns:
list of tuple:

List of tuples [(S, sbuf),…] where S is a message summary (dict), and sbuf is the raw message; may be empty. The caller MUST call rmr.rmr_free_msg(sbuf) when finished with each sbuf to prevent memory leaks!

RIC Alarm API

Overview

The xapp python framework provides an alarm feature in the python subpackage ricxappframe.alarm. This subpackage defines objects and methods for creating, raising and clearing alarms.

The alarm feature reuses the ricxappframe.rmr subpackage for transporting alarm messages. That in turn requires the RMR shared-object library to be available in a system directory that is searched by default, usually something like /usr/local/lib.

The alarm feature opens a direct connection to the alarm manager using the RMR library’s wormhole feature, taking the host name and port number from environment variables defined as the constants ALARM_MGR_SERVICE_NAME_ENV and ALARM_MGR_SERVICE_PORT_ENV in the ricxappframe.alarm.alarm module. The message type is set to constant RIC_ALARM_UPDATE in the ricxappframe.alarm.alarm module, currently 110.

The complete API for the Alarm feature appears below.

Example Usage

Alarms are created, raised and cleared using an AlarmManager as shown below. The manager requires an RMR context at creation time.

from ricxappframe.alarm import alarm
from ricxappframe.rmr import rmr

rmr_context = rmr.rmr_init(b"4562", rmr.RMR_MAX_RCV_BYTES, 0x00)
alarm_mgr = alarm.AlarmManager(rmr_context, "managed-object-id", "application-id")
alarm3 = alarm_mgr.create_alarm(3, alarm.AlarmSeverity.DEFAULT, "identifying", "additional")
success = alarm_mgr.raise_alarm(alarm3)

Alarm API

Provides classes and methods to define, raise, reraise and clear alarms. All actions are implemented by sending RMR messages to the Alarm Adapter. The alarm target host and port are set by environment variables. The alarm message contents comply with the JSON schema in file alarm-schema.json.

class ricxappframe.alarm.alarm.AlarmAction(value)[source]

Action to perform at the Alarm Adapter

class ricxappframe.alarm.alarm.AlarmSeverity(value)[source]

Severity of an alarm

class ricxappframe.alarm.alarm.AlarmDetail(managed_object_id: str, application_id: str, specific_problem: int, perceived_severity: AlarmSeverity, identifying_info: str, additional_info: str = '')[source]

An alarm that can be raised or cleared.

Parameters:
managed_object_id: str

The name of the managed object that is the cause of the fault (required)

application_id: str

The name of the process that raised the alarm (required)

specific_problem: int

The problem that is the cause of the alarm

perceived_severity: AlarmSeverity

The severity of the alarm, a value from the enum.

identifying_info: str

Identifying additional information, which is part of alarm identity

additional_info: str

Additional information given by the application (optional)

class ricxappframe.alarm.alarm.AlarmManager(vctx: c_void_p, managed_object_id: str, application_id: str)[source]

Provides an API for an Xapp to raise and clear alarms by sending messages via RMR directly to an Alarm Adapter. Requires environment variables ALARM_MGR_SERVICE_NAME and ALARM_MGR_SERVICE_PORT with the destination host (service) name and port number; raises an exception if not found.

Parameters:
vctx: ctypes c_void_p

Pointer to RMR context obtained by initializing RMR. The context is used to allocate space and send messages.

managed_object_id: str

The name of the managed object that raises alarms

application_id: str

The name of the process that raises alarms

create_alarm(specific_problem: int, perceived_severity: AlarmSeverity, identifying_info: str, additional_info: str = '')[source]

Convenience method that creates an alarm instance, an AlarmDetail object, using cached values for the managed object ID and application ID.

Parameters:
specific_problem: int

The problem that is the cause of the alarm

perceived_severity: AlarmSeverity

The severity of the alarm, a value from the enum.

identifying_info: str

Identifying additional information, which is part of alarm identity

additional_info: str

Additional information given by the application (optional)

Returns:
AlarmDetail
raise_alarm(detail: AlarmDetail)[source]

Builds and sends a message to the AlarmAdapter to raise an alarm with the specified detail.

Parameters:
detail: AlarmDetail

Alarm to raise

Returns:
bool

True if the send succeeded (possibly with retries), False otherwise

clear_alarm(detail: AlarmDetail)[source]

Builds and sends a message to the AlarmAdapter to clear the alarm with the specified detail.

Parameters:
detail: AlarmDetail

Alarm to clear

Returns:
bool

True if the send succeeded (possibly with retries), False otherwise

reraise_alarm(detail: AlarmDetail)[source]

Builds and sends a message to the AlarmAdapter to clear the alarm with the the specified detail, then builds and sends a message to raise the alarm again.

Parameters:
detail: AlarmDetail

Alarm to clear and raise again.

Returns:
bool

True if the send succeeded (possibly with retries), False otherwise

clear_all_alarms()[source]

Builds and sends a message to the AlarmAdapter to clear all alarms.

Returns:
bool

True if the send succeeded (possibly with retries), False otherwise

Alarm Messages

Alarm messages conform to the following JSON schema.

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "$id": "https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/alarm-go.json",
  "type": "object",
  "title": "Alarm schema",
  "description": "Schema for RIC alarm messages.",
  "default": {},
  "examples": [
    {
      "managedObjectId": "my-pod-lib",
      "applicationId": "my-app",
      "specificProblem": 1234,
      "perceivedSeverity": "MAJOR",
      "additionalInfo": "Some App data",
      "identifyingInfo": "eth 0 1",
      "AlarmAction": "RAISE",
      "AlarmTime": 1591188407505707
    }
  ],
  "required": [
    "managedObjectId",
    "applicationId",
    "specificProblem",
    "perceivedSeverity",
    "identifyingInfo",
    "AlarmAction",
    "AlarmTime"
  ],
  "additionalProperties": true,
  "properties": {
    "managedObjectId": {
      "type": "string",
      "title": "The managedObjectId schema",
      "description": "The name of the managed object that is the cause of the fault.",
      "default": ""
    },
    "applicationId": {
      "type": "string",
      "title": "The applicationId schema",
      "description": "The name of the process that raised the alarm.",
      "default": ""
    },
    "specificProblem": {
      "type": "integer",
      "title": "The specificProblem schema",
      "description": "The problem that is the cause of the alarm.",
      "default": 0
    },
    "perceivedSeverity": {
      "type": "string",
      "enum": [
        "UNSPECIFIED",
        "CRITICAL",
        "MAJOR",
        "MINOR",
        "WARNING",
        "CLEARED",
        "DEFAULT"
      ],
      "title": "The perceivedSeverity schema",
      "description": "The severity of the alarm.",
      "default": ""
    },
    "additionalInfo": {
      "type": "string",
      "title": "The additionalInfo schema",
      "description": "Additional information given by the application (optional).",
      "default": ""
    },
    "identifyingInfo": {
      "type": "string",
      "title": "The identifyingInfo schema",
      "description": "Identifying additional information, which is part of alarm identity.",
      "default": ""
    },
    "AlarmAction": {
      "type": "string",
      "enum": [
        "RAISE",
        "CLEAR",
        "CLEARALL"
      ],
      "title": "The AlarmAction schema",
      "description": "Action to perform on the alarm.",
      "default": ""
    },
    "AlarmTime": {
      "type": "integer",
      "title": "The AlarmTime schema",
      "description": "Current system time in milliseconds since the Epoch.",
      "default": 0
    }
  }
}

RNIB(Radio network information base)

RNIB information is stored in SDL by E2mgr during the E2SETUP process. It is saved as protobuf serialized, see [nodeb-rnib](https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/nodeb-rnib) for .proto definition.

The .proto files defines the message format is located in the rnib repo. The rnib repo is set to submodule.

How to compile .proto files

Prerequisite

  • protoc (v3.6.1)

$ PYTHON_OUT="ricxappframe/entities/rnib"
$ RNIB_PROTO="nodeb-rnib/entities"
$ protoc --python_out="${PYTHON_OUT}" \
         --proto_path="${RNIB_PROTO}" \
         $(find "${RNIB_PROTO}" -iname "*.proto" -printf "%f ")
$ sed -i -E 's/^import.*_pb2/from . \0/' ${PYTHON_OUT}/*_pb2.py

The reason why sed is needed

To use relative imports in generated modules : protobuf issue

MDCLogger

Usage

The library can be used in as shown below.

```python
  from ricappframe.logger.mdclogger import MDCLogger
  my_logger = MDCLogger()
  my_logger.mdclog_format_init(configmap_monitor=True)
  my_logger.error("This is an error log")
```

A program can create several logger instances.

mdclog_format_init() Adds the MDC log format with HostName, PodName, ContainerName, ServiceName,PID,CallbackNotifyforLogFieldChange

Pass configmap_monitor = False in mdclog_format_init() function to stop dynamic log level change based on configmap.

Logging Levels

"""Severity levels of the log messages."""
    DEBUG = 10
    INFO = 20
    WARNING = 30
    ERROR = 40

mdcLogger API’s

  1. Set current logging level

def set_level(self, level: Level):

       Keyword arguments:
       level -- logging level. Log messages with lower severity will be filtered.
  1. Return the current logging level

def get_level(self) -> Level:
  1. Add a logger specific MDC

def add_mdc(self, key: str, value: Value):

       Keyword arguments:
       key -- MDC key
       value -- MDC value
  1. Return logger’s MDC value with the given key or None

def get_mdc(self, key: str) -> Value:
  1. Remove logger’s MDC with the given key

def remove_mdc(self, key: str):
  1. Remove all MDCs of the logger instance.

def clean_mdc(self):
  1. Initialise Logging format:

This api Initialzes mdclog print format using MDC Dictionary by extracting the environment variables in the calling process for “SYSTEM_NAME”, “HOST_NAME”, “SERVICE_NAME”, “CONTAINER_NAME”, “POD_NAME” & “CONFIG_MAP_NAME” mapped to HostName, ServiceName, ContainerName, Podname and Configuration-file-name of the services respectively.

def mdclog_format_init(configmap_monitor=False):

       Keyword arguments:
       configmap_monitor -- Enables/Disables Dynamic log level change based on configmap
                         -- Boolean values True/False can be passed as per requirement.

Developer Guide

This document explains how to maintain the RIC Xapp framework. Information for users of this framework (i.e., Xapp developers) is in the User Guide.

Tech Stack

The framework requires Python version 3.8 or later, and depends on these packages provided by the O-RAN-SC project and third parties:

  • msgpack

  • mdclogpy

  • ricsdl

  • protobuf

Version bumping the framework

This project follows semver. When changes are made, the versions are in:

  1. docs/release-notes.rst

  2. setup.py

Version bumping RMR

These items in this repo must be kept in sync with the RMR version:

  1. Dockerfile-Unit-Test

  2. examples/Dockerfile-Ping

  3. examples/Dockerfile-Pong

  4. rmr-version.yaml controls what version of RMR is installed for unit testing in Jenkins CI

Registration/Deregistartion of Xapp

For registration and deregistration of Xapp following items need to be defined:

  1. CONFIG_FILE_PATH variable as a environment variable in Dockerfile if running Xapp as a docker container or in configmap in case of Xapp as a pod.

  2. Copy the xappConfig.json into the docker image in Dockerfile.

Example Xapp

Director examples has many examples for creating the xapp having features like: * REST subscription interface * symptomdata handler * rmr send/receive * REST healthy and ready response handler * REST config handler

List of xapps: * ping_xapp.py and ping_xapp.py using RMR to send and receive * xapp_symptomdata.py for subscribing the symptomdata event * xapp_test.py for both symptomdata, RMR recveice, k8s healthy service and REST subscription

xapp_test.py

Test xapp can be run by creating the docker container or as standalone process. For testing restserversimu.py has been made to emulate the REST subscription request responses and responding the symptomdata dynamic registration reponse. When running the xapp_test you need to create the RMR static routing file so that the rmr initialization will return (otherwise it will wait for rtmgr connection).

Test xapp has as well the config file in descriptor/config-file.json where your can adjust the local runtime environment for subscriptions and symptomdata lwsd service ip address.

Subsciprion interface url has the submgr service endpoint : http://service-ricplt-submgr-http.ricplt:8088/ Symptomdata registration url set to lwsd service url : http://service-ricplt-lwsd-http.ricplt:8080/ric/v1/lwsd

In case of using restserversimu.py for testing configure your host ip address, for example 192.168.1.122 port 8090:

Subsciprion interface url has the submgr service endpoint : http://192.168.1.122:9000/ Symptomdata registration url set to lwsd service url : http://192.168.1.122:9000/ric/v1/lwsd

Then start the restserversimu:

export PYTHONPATH=./ python3 examples/restserversimu.py -port 9000 -address 192.168.1.122

and then the xapp_test:

export PYTHONPATH=./ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH export RMR_SEED_RT=examples/descriptor/xapp-test.rt export RMR_SRC_ID=”192.168.1.122” python3 examples/xapp_test.py -config examples/descriptor/config-file.json -port 8888 -xapp xapp-test -service xapp-test

If you like to implement the kubernetes healthy service responder, you can follow the example how to use the xapp_rest.py defined rest hander. Basically you can initiate the REST service listener and add the handlers (examples in xapp_tets.py).

Subscription REST interface

api/xapp_rest_api.yaml defines interface and it has been used to generate the swagger api calls.

Generating the swagger client model: docker run –rm -v ${PWD}:/local -u $(id -u ${USER}):$(id -g ${USER}) swaggerapi/swagger-codegen-cli generate -i /local/api/xapp_rest_api.yaml -l python -o /local/out

swagger-codegen-cli generated code result needs to be adjusted to have the ricxappframe prefix. Replace the module name to have the ricxappframe prefix:

find ./out/swagger_client -type f -exec sed -i -e ‘s/swagger_client/ricxappframe.swagger_client/g’ {} ;

Then copy the generated ./out/swagger_client directory to ./ricxappframe/subsclient directory:

cp -r ./out/swagger_client/* ./ricxappframe/subsclient

Unit Testing

Running the unit tests requires the python packages tox and pytest.

The RMR library is also required during unit tests. If running directly from tox (outside a Docker container), install RMR according to its instructions.

Upon completion, view the test coverage like this:

tox
open htmlcov/index.html

Alternatively, if you cannot install RMR locally, you can run the unit tests in Docker. This is somewhat less nice because you don’t get the pretty HTML report on coverage.

docker build  --no-cache -f Dockerfile-Unit-Test .

Release Notes

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[3.2.1] - 2022-12-12

  • small permission fix in python init files

[3.2.0] - 2022-06-16

  • Added REST E2 subscription support

  • rnib enhancements

  • REST service with health and config interface

[3.1.0] - 2022-03-22

  • Added the symptomdata module

[3.0.2] - 2021-12-17

  • Fix caching of error code data not happening (RIC-862)

  • Improves performance of xapp-frame-py by caching the mapping dict of error codes.

[3.0.1] - 2021-12-07

  • Take configuration and rmr unit tests back into use after fixing the tests (RIC-865)

  • Improve memory leak fix done in the version 3.0.0 to free memory also if python exception has been thrown.

[3.0.0] - 2021-12-03

  • Upgrade to RMR version 4.8.0 to fix memory leak in get_constants() function (RIC-858)

  • From xapp-frame-py version 3.0.0 onwards it is required to have RMR version 4.8.0 or newer.

[2.3.0] - 2021-09-15

[2.2.0] - 2021-06-23

[2.1.0] - 2021-06-18

[2.0.0] - 2021-06-14

  • Add Xapp registration/deregistration APIs (RIC-706)

  • Upgrade SDL 3.0.0 version, SDL scaling (RIC-699)

  • Upgrade SDL 3.0.0 version, notification fix (RIC-795)

[1.6.0] - 2020-10-23

[1.5.0] - 2020-07-10

[1.4.0] - 2020-07-06

  • Revise Alarm manager to send via RMR wormhole (RIC-529)

[1.3.0] - 2020-06-24

  • Add configuration-change API (RIC-425)

[1.2.1] - 2020-06-22

  • Revise alarm message type (RIC-514)

[1.2.0] - 2020-06-04

  • Extend RMR module to support wormhole methods

  • Add alarm API (RIC-380)

[1.1.2] - 2020-05-13

  • Extend and publish class and method documentation as user guide in RST

[1.1.1] - 2020-05-07

  • Use timeout on queue get method to avoid 100% CPU usage (RIC-354)

  • Upgrade to RMR version 4.0.5

[1.1.0] - 2020-05-06

  • Use RMR timeout on receive to avoid 100% CPU usage (RIC-354)

  • Publish message-summary dict keys as constants to avoid hardcoding strings

  • Add wrapper and test for RMR method rmr_set_vlevel(int)

[1.0.3] - 2020-04-29

  • Upgrade to RMR version 4.0.2

[1.0.2] - 2020-04-22

  • Upgrade to RMR version 3.8.0

[1.0.1] - 2020-04-10

  • Publish API documentation using Sphinx autodoc, which required changes so Sphinx can run when the RMR .so file is not available, such as during a ReadTheDocs build.

  • Create new subpackage rmr/rmrclib with the C library loaded via ctypes.

  • Extend sphinx configuration to mock the new rmrclib subpackage

  • Add method to get constants from RMR library and detect mock objects to work around a bug in Sphinx 3.0.0.

  • Split test files into test_rmr and test_rmrclib.

  • Add function to define argtype and restype values for library functions

  • Configure intersphinx link for RMR man pages at ReadTheDocs.io

[1.0.0] - 4/6/2020

  • Python rmr has been moved into this repo. The module name has NOT changed in order to make the transition for repos very easy. The only transition needed should be prefixing rmr with ricxappframe in import statements, and to include this rather than rmr in setup.

[0.7.0] - 4/2/2020

  • RMRXapps by default now implement the rmr healthcheck probe; users can also override it with a more complex handler if they wish

  • Fix a bug in the unit tests where a payload mismatch wouldn’t actually fail the test (would now)

[0.6.0] - 3/23/2020

  • Switch to SI95 for rmr

[0.5.0] - 3/18/2020

  • All xapps (via the base class) now have a logger attribute that can be invoked to provide mdc logging. It is a passthrough to the RIC mdc logger for python (untouched, no value in an API on top at the current time).

[0.4.1] - 3/17/2020

  • Switch tox to use py38

  • switch to latest builders

[0.4.0] - 3/13/2020

  • Minor breaking change; switches the default behavior RE threading for RMRXapps. The default is not to return execution, but the caller (in run) can choose to loop in a thread.

  • Add Dockerized examples

[0.3.0] - 3/10/2020

  • Large change to the “feel” of this framework: rather than subclass instantiation, xapps now use initialization and registration functions to register handlers

  • rmr xapps can now register handlers for specific message types (and they must prodive a default callback); if the user does this then “message to function routing” is now handled by the framework itself

  • RMRXapp now runs the polling loop in a thread, and returns execution back to the caller. The user is then free to loop, or do nothing, and call stop() when they want.

  • Raises tox coverage minimum to 70 from 50 (currently at 86)

[0.2.0] - 3/3/2020

  • now allows for RMRXapps to call code before entering the infinite loop

  • stop is now called before throwing NotImplemented in the case where the client fails to provide a must have callback; this ensures there is no dangling rmr thread

  • stop now calls rmr_close to correctly free up any port(s)

  • (breaking) renames loop to entrypoint since the function does not have to contain a loop (though it most likely does)

  • Changes wording around the two types of xapps (docs only)

  • Uses a new version of rmr python that crashes when the rmr mrc fails to init, which prevents an xapp trying to use an unusable rmr

  • more unit test code coverage

  • Adds more fields to setup like long_desc and classifiers so the pypi page looks nicer

  • Removes a bad release file (will be added back in subseq. commit)

[0.1.0] - 2/27/2020

  • Initial commit