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)
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
- 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
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 processingint mtype; // message typeint len; // length of data in the payload (send or received)unsigned char* payload; // transported dataunsigned char* xaction; // pointer to fixed length transaction id bytesint sub_id; // subscription idint tp_state; // transport state (a.k.a errno)these things are off limits to the user applicationvoid* tp_buf; // underlying transport allocated pointervoid* header; // internal message header (whole buffer: header+payload)unsigned char* id; // if we need an ID in the message separate from the xaction idint flags; // various MFL (private) flags as neededint 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
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.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
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¶
Set current logging level
def set_level(self, level: Level):
Keyword arguments:
level -- logging level. Log messages with lower severity will be filtered.
Return the current logging level
def get_level(self) -> Level:
Add a logger specific MDC
def add_mdc(self, key: str, value: Value):
Keyword arguments:
key -- MDC key
value -- MDC value
Return logger’s MDC value with the given key or None
def get_mdc(self, key: str) -> Value:
Remove logger’s MDC with the given key
def remove_mdc(self, key: str):
Remove all MDCs of the logger instance.
def clean_mdc(self):
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:
docs/release-notes.rst
setup.py
Version bumping RMR¶
These items in this repo must be kept in sync with the RMR version:
Dockerfile-Unit-Test
examples/Dockerfile-Ping
examples/Dockerfile-Pong
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:
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.
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¶
Add Xapp Registration (RIC-706)
Integrate pylog (https://gerrit.o-ran-sc.org/r/admin/repos/com/pylog) with xapp-frame-py (RIC-330)
[2.2.0] - 2021-06-23¶
Add E2AP package (RIC-664)
[2.1.0] - 2021-06-18¶
[2.0.0] - 2021-06-14¶
[1.6.0] - 2020-10-23¶
Add SDL wrapping API (RIC-659)
[1.5.0] - 2020-07-10¶
Add Metrics API (RIC-381)
[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