INTRODUCTION
============
-The C++ framework allows the programmer to create an xApp
-object instance, and to use that instance as the logic base.
-The xApp object provides a message level interface to the RIC
-Message Router (RMR), including the ability to register
-callback functions which the instance will drive as messages
-are received; much in the same way that an X-windows
-application is driven by the window manager for all activity.
-The xApp may also choose to use its own send/receive loop,
-and thus is not required to use the callback driver mechanism
-provided by the framework.
+The C++ framework allows the programmer to create an instance
+of the ``Xapp`` object which then can be used as a foundation
+for the application. The ``Xapp`` object provides a message
+level interface to the RIC Message Router (RMR), including
+the ability to register callback functions which the instance
+will drive as messages are received; much in the same way
+that an X-windows application is driven by the window manager
+for all activity. The xApp may also choose to use its own
+send/receive loop, and thus is not required to use the
+callback driver mechanism provided by the framework.
+
+
+Termonology
+-----------
+
+To avoid confusion the term **xAPP** is used in this document
+to refer to the user's application code which is creating
+``Xapp,`` and related objects provided by the *framework.*
+The use of *framework* should be taken to mean any of the
+classes and/or support functions which are provided by the
+``ricxfcpp`` library.
THE FRAMEWORK API
:header-rows: 0
:class: borderless
+
* - **port**
+
-
+
A C string (pointer to char) which defines the port that
+
RMR will open to listen for connections.
+
+
+
|
+
+
* - **wait**
+
-
+
A Boolean value which indicates whether or not the
+
initialization process should wait for the arrival of a
+
valid route table before completing. When true is
+
supplied, the initialization will not complete until RMR
+
has received a valid route table (or one is located via
+
the ``RMR_SEED_RT`` environment variable).
:header-rows: 0
:class: borderless
+
* - **m**
+
-
+
A reference to the Message that was received.
+
+
+
|
+
+
* - **mtype**
+
-
+
The message type (allows for disambiguation if the
+
callback is registered for multiple message types).
+
+
+
|
+
+
* - **subid**
+
-
+
The subscription ID from the message.
+
+
+
|
+
+
* - **payload len**
+
-
+
The number of bytes which the sender has placed into the
+
payload.
+
+
+
|
+
+
* - **payload**
+
-
+
A direct reference (smart pointer) to the payload. (The
+
smart pointer is wrapped in a special class in order to
+
provide a custom destruction function without burdening
+
the xApp developer with that knowledge.)
+
+
+
|
+
+
* - **user data**
+
-
+
A pointer to user data. This is the pointer that was
+
provided when the function was registered.
::
- #include <ricxfcpp/Jhash>
+ #include <ricxfcpp/Jhash.hpp>
std::string jstring = "{ \\"tag\\": \\"Hello World\\" }";
Jhash* jh;
:header-rows: 0
:class: borderless
+
* - **String**
+
-
+
An empty string (.e.g "").
+
+
|
+
+
* - **Value**
+
-
+
Zero (e.g 0.0)
+
+
|
+
+
* - **bool**
+
-
+
false
:header-rows: 0
:class: borderless
+
* - **Raise**
+
-
+
Cause the alarm to be assigned a severity and and sent via
+
RMR message to the alarm collector process.
+
+
+
|
+
+
* - **Clear**
+
-
+
Cause a clear message to be sent to the alarm collector.
+
+
+
|
+
+
* - **Raise Again**
+
-
+
Cause a clear followed by a raise message to be sent to
+
the alarm collector.
+CONFIGURATION SUPPORT
+=====================
+
+The C++ xAPP framework provides the xAPP with an interface to
+load, parse and receive update notifications on the
+configuration. The configuration, also known as the xAPP
+descriptor, is assumed to be a file containing json with a
+well known structure, with some fields or *objects* used by
+an xAPP for configuration purposes. The following paragraphs
+describe the support that the framework provides to the xAPP
+with respect to the configuration aspects of the descriptor.
+
+
+The Config Object
+-----------------
+
+The xAPP must create an instance of the ``config`` object in
+order to take advantage of the support. This is accomplished
+by using one of two constructors illustrated with code
+samples in figure 23.
+
+
+::
+
+ #include <ricxfcpp/config.hpp>
+
+ auto cfg = new xapp::Config( );
+ auto cfg = new xapp::Config( "/var/myapp/config.json" );
+
+Figure 23: Creating a configuration instance.
+
+The creation of the object causes the file to be found,
+loaded, after which the xAPP can use the instance functions
+to access the information it needs.
+
+
+Available Functions
+-------------------
+
+Once a configuration has been created the following
+capabilities are available:
+
+
+ * Get a control value (numeric, string, or boolean)
+
+ * Get the RMR port for the container with the supplied
+ name
+
+ * Set a notification callback function
+
+ * Get the raw contents of the file
+
+
+
+Control Values
+--------------
+
+The ``controls`` section of the xAPP descriptor is generally
+used to supply a *flat* namespace of key/value pairs which
+the xAPP can use for configuration. These pairs are supplied
+by the xAPP author as a part of development, and thus are
+specific to each xAPP. The framework provides a general set
+of functions which allows a key to be searched for in this
+section and returned to the caller. Data is assumed to be one
+of three types: numeric (double), string, or boolean.
+
+Two methods for each return type are supported with the more
+specific form allowing the xAPP to supply a default value to
+be used should the file not contain the requested field. The
+function prototypes for these are provided in figure 24.
+
+::
+
+ bool Get_control_bool( std::string name, bool defval );
+ bool Get_control_bool( std::string name );
+
+ std::string Get_control_str( std::string name, std::string defval );
+ std::string Get_control_str( std::string name );
+
+ double Get_control_value( std::string name, double defval );
+ double Get_control_value( std::string name );
+
+Figure 24: The various controls section get functions.
+
+If the more generic form of these functions is used, without
+a default value, the return values are false, "", and 0.0 in
+the respective order of the prototypes in figure 24.
+
+
+The RMR Port
+------------
+
+The ``messaging`` section of the xAPP descriptor provides the
+ability to define one or more RMR *listen ports* that apply
+to the xAPP(s) started in a given container. The xAPP may
+read a port value (as a string) using the defined port name
+via the ``Get_port`` function whose prototype is illustrated
+in figure 25 below.
+
+
+::
+
+ std::string Get_port( std::string name );
+
+Figure 25: The get port prototype.
+
+
+
+Raw File Contents
+-----------------
+
+While it is not anticipated to be necessary, the xAPP might
+need direct access to the raw contents of the configuration
+file. As a convenience the framework provides the
+``Get_contents()`` function which reads the entire file into
+a standard library string and returns that to the calling
+function. Parsing and interpreting the raw contents is then
+up to the xAPP.
+
+
+Notification Of Changes
+-----------------------
+
+When desired, the xAPP may register a notification callback
+function with the framework. This callback will be driven any
+time a change to the descriptor is detected. When a change is
+detected, the revised descriptor is read into the existing
+object (overlaying any previous information), before invoking
+the callback. The callback may then retrieve the updated
+values and make any adjustments which are necessary. The
+prototype for the xAPP callback function is described in
+figure 26.
+
+
+::
+
+ void cb_name( xapp::Config& c, void* data )
+
+Figure 26: The prototype which the xAPP configuration notify
+callback must use.
+
+
+
+Enabling The Notifications
+--------------------------
+
+Notifications are enabled by invoking the
+``Set_callback()`` function. Once enabled, the framework will
+monitor the configuration source and invoke the callback upon
+change. This occurs in a separate thread than the main xAPP
+thread; it is up to the xAPP to guard against any potential
+data collisions when evaluating configuration changes. If the
+xAPP does not register a notification function the framework
+will not monitor the configuration for changes and the object
+will have static data. Figure 27 illustrates how the xAPP can
+define and register a notification callback.
+
+
+::
+
+
+ // notification callback; allows verbose level to change on the fly
+ void config_chg( xapp::Config& c, void* vdata ) {
+ app_ctx* ctx; // application context
+
+ ctx = (app_ctx *) vdata;
+ ctx->vlevel = c->Get_value( "verbose_level", ctx->vlevel );
+ }
+
+Figure 27: Small notification callback function allowing on
+the fly verbose level change.
+
+
+The xAPP would register the ``config_chg()`` function as the
+notification callback using the call illustrated in figure
+28.
+
+::
+
+
+ auto conf = new xapp::Config();
+ conf->Set_callback( config_chg );
+
+Figure 28: Setting the notification callback and and
+activating notifications.
+
+
+
+
+xAPP Descriptor Notes
+---------------------
+
+While it is beyond the scope of this document to describe the
+complete contents of an xAPP descriptor file, it is prudent
+to mention several items which are related to the information
+used from the descriptor file. The following paragraphs
+discuss things which the xAPP developer should be aware of,
+and keep in mind when using the configuration class.
+
+
+The RMR Section
+---------------
+
+There is a deprecated section within the xAPP descriptor
+which has the title *rmr.* The *messaging* section provides
+more flexibility, and additional information and has been a
+replacement for the *rmr* section for all applications. The
+information in the *rmr* section should be kept consistent
+with the duplicated information in the *messaging* section as
+long as there are container management and/or platform
+applications (e.g. Route Manager) which are back level and do
+not recognise the *messaging* section. The configuration
+parsing and support provided by the framework will ignore the
+*rmr* section.
+
+
EXAMPLE PROGRAMMES
==================
The following sections contain several example programmes
-which are written on top of the C++ framework.
+which are written on top of the C++ framework. All of these
+examples are available in the code repository RIC xAPP C++
+framework available via the following URL:
+
+.. class:: center
+ ``https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/xapp-frame-cpp``
+
RMR Dump xAPP
return 0;
}
- Figure 23: Simple callback application.
+ Figure 29: Simple callback application.
Callback Receiver
// control should not return
}
- Figure 24: Simple callback application.
+ Figure 30: Simple callback application.
}
}
- Figure 25: Simple looping sender application.
+ Figure 31: Simple looping sender application.
-Alarm Example
--------------
+Alarm Generation
+----------------
+
+This is an extension of a previous example which sends an
+alarm during initialisation and clears the alarm as soon as
+messages are being received. It is unknown if this is the
+type of alarm that is expected at the collector, but
+illustrates how an alarm is allocated, raised and cleared.
+
+
+ ::
+
+
+ #include <stdio.h>
+ #include <string.h>
+ #include <unistd.h>
+
+ #include <iostream>
+ #include <memory>
+
+ #include "ricxfcpp/xapp.hpp"
+ #include "ricxfcpp/alarm.hpp"
+
+ extern int main( int argc, char** argv ) {
+ std::unique_ptr<Xapp> xfw;
+ std::unique_ptr<xapp::Message> msg;
+ xapp::Msg_component payload; // special type of unique pointer to the payload
+ std::unique_ptr<xapp::Alarm> alarm;
+
+ bool received = false; // false until we've received a message
+ int sz;
+ int len;
+ int i;
+ int ai = 1;
+ int response_to = 0; // max timeout wating for a response
+ char* port = (char *) "4555";
+ int mtype = 0;
+ int rmtype; // received message type
+ int delay = 1000000; // mu-sec delay; default 1s
+
+
+ // very simple flag processing (no bounds/error checking)
+ while( ai < argc ) {
+ if( argv[ai][0] != '-' ) {
+ break;
+ }
+
+ // we only support -x so -xy must be -x -y
+ switch( argv[ai][1] ) {
+ // delay between messages (mu-sec)
+ case 'd':
+ delay = atoi( argv[ai+1] );
+ ai++;
+ break;
+
+ case 'p':
+ port = argv[ai+1];
+ ai++;
+ break;
+
+ // timeout in seconds; we need to convert to ms for rmr calls
+ case 't':
+ response_to = atoi( argv[ai+1] ) * 1000;
+ ai++;
+ break;
+ }
+ ai++;
+ }
+
+ fprintf( stderr, "<XAPP> response timeout set to: %d\\n", response_to );
+ fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
+
+ // get an instance and wait for a route table to be loaded
+ xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) );
+ msg = xfw->Alloc_msg( 2048 );
+
+
+ // raise an unavilable alarm which we'll clear on the first recevied message
+ alarm = xfw->Alloc_alarm( "meid-1234" );
+ alarm->Raise( xapp::Alarm::SEV_MINOR, 13, "unavailable", "no data recevied" );
+
+ for( i = 0; i < 100; i++ ) {
+ mtype++;
+ if( mtype > 10 ) {
+ mtype = 0;
+ }
+
+ // we'll reuse a received message; get max size
+ sz = msg->Get_available_size();
+
+ // direct access to payload; add something silly
+ payload = msg->Get_payload();
+ len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i );
+
+ // payload updated in place, prevent copy by passing nil
+ if ( ! msg->Send_msg( mtype, xapp::Message::NO_SUBID, len, NULL )) {
+ fprintf( stderr, "<SNDR> send failed: %d\\n", i );
+ }
+
+ // receive anything that might come back
+ msg = xfw->Receive( response_to );
+ if( msg != NULL ) {
+ if( ! received ) {
+ // clear the alarm on first received message
+ alarm->Clear( xapp::Alarm::SEV_MINOR, 13, "messages flowing", "" );
+ received = true;
+ }
+
+ rmtype = msg->Get_mtype();
+ payload = msg->Get_payload();
+ fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
+ rmtype, (char *) payload.get() );
+ } else {
+ msg = xfw->Alloc_msg( 2048 );
+ }
+
+ if( delay > 0 ) {
+ usleep( delay );
+ }
+ }
+ }
+
+ Figure 32: Simple looping sender application with alarm
+ generation.
+
+
+
+Configuration Interface
+-----------------------
+
+This example is a simple illustration of how the
+configuration file support (xAPP descriptor) can be used to
+suss out configuration parameters before creating the Xapp
+object. The example also illustrates how a notification
+callback can be used to react to changes in the
+configuration.
+
+
+ ::
+
+ #include <stdio.h>
+
+ #include "ricxfcpp/config.hpp"
+ #include "ricxfcpp/message.hpp"
+ #include "ricxfcpp/msg_component.hpp"
+ #include <ricxfcpp/metrics.hpp>
+ #include "ricxfcpp/xapp.hpp"
+
+ int vlevel = 0; // verbose mode set via config
+
+ /*
+ Just print something to the tty when we receive a message
+ and are in verbose mode.
+ */
+ void cb1( xapp::Message& mbuf, int mtype, int subid, int len,
+ xapp::Msg_component payload, void* data ) {
+ if( vlevel > 0 ) {
+ fprintf( stdout, "message received is %d bytes long\\n", len );
+ }
+ }
+
+ /*
+ Driven when the configuration changes. We snarf the verbose
+ level from the new config and update it. If it changed to
+ >0, incoming messages should be recorded with a tty message.
+ If set to 0, then tty output will be disabled.
+ */
+ void config_cb( xapp::Config& c, void* data ) {
+ int* vp;
+
+ if( (vp = (int *) data) != NULL ) {
+ *vp = c.Get_control_value( "verbose_level", *vp );
+ }
+ }
+
+ int main( int argc, char** argv ) {
+ Xapp* x;
+ int nthreads = 1;
+ std::unique_ptr<xapp::Config> cfg;
+
+ // only parameter recognised is the config file name
+ if( argc > 1 ) {
+ cfg = std::unique_ptr<xapp::Config>( new xapp::Config( std::string( argv[1] ) ) );
+ } else {
+ cfg = std::unique_ptr<xapp::Config>( new xapp::Config( ) );
+ }
+
+ // must get a port from the config; no default
+ auto port = cfg->Get_port( "rmr-data" );
+ if( port.empty() ) {
+ fprintf( stderr, "<FAIL> no port in config file\\n" );
+ exit( 1 );
+ }
+
+ // dig other data from the config
+ vlevel = cfg->Get_control_value( "verbose_level", 0 );
+ nthreads = cfg->Get_control_value( "thread_count", 1 );
+ // very simple flag processing (no bounds/error checking)
+
+ if( vlevel > 0 ) {
+ fprintf( stderr, "<XAPP> listening on port: %s\\n", port.c_str() );
+ fprintf( stderr, "<XAPP> starting %d threads\\n", nthreads );
+ }
+
+ // register the config change notification callback
+ cfg->Set_callback( config_cb, (void *) &vlevel );
+
+ x = new Xapp( port.c_str(), true );
+ x->Add_msg_cb( 1, cb1, x ); // register message callback
+
+ x->Run( nthreads ); // let framework drive
+ // control should not return
+ }
- This is an extension of a previous example which sends an
- alarm during initialisation and clears the alarm as soon
- as messages are being received. It is unknown if this is
- the type of alarm that is expected at the collector, but
- illustrates how an alarm is allocated, raised and cleared.
-
-
- ::
-
-
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
-
- #include <iostream>
- #include <memory>
-
- #include "ricxfcpp/xapp.hpp"
- #include "ricxfcpp/alarm.hpp"
-
- extern int main( int argc, char** argv ) {
- std::unique_ptr<Xapp> xfw;
- std::unique_ptr<xapp::Message> msg;
- xapp::Msg_component payload; // special type of unique pointer to the payload
- std::unique_ptr<xapp::Alarm> alarm;
-
- bool received = false; // false until we've received a message
- int sz;
- int len;
- int i;
- int ai = 1;
- int response_to = 0; // max timeout wating for a response
- char* port = (char *) "4555";
- int mtype = 0;
- int rmtype; // received message type
- int delay = 1000000; // mu-sec delay; default 1s
-
-
- // very simple flag processing (no bounds/error checking)
- while( ai < argc ) {
- if( argv[ai][0] != '-' ) {
- break;
- }
-
- // we only support -x so -xy must be -x -y
- switch( argv[ai][1] ) {
- // delay between messages (mu-sec)
- case 'd':
- delay = atoi( argv[ai+1] );
- ai++;
- break;
-
- case 'p':
- port = argv[ai+1];
- ai++;
- break;
-
- // timeout in seconds; we need to convert to ms for rmr calls
- case 't':
- response_to = atoi( argv[ai+1] ) * 1000;
- ai++;
- break;
- }
- ai++;
- }
-
- fprintf( stderr, "<XAPP> response timeout set to: %d\\n", response_to );
- fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
-
- // get an instance and wait for a route table to be loaded
- xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) );
- msg = xfw->Alloc_msg( 2048 );
-
-
- // raise an unavilable alarm which we'll clear on the first recevied message
- alarm = xfw->Alloc_alarm( "meid-1234" );
- alarm->Raise( xapp::Alarm::SEV_MINOR, 13, "unavailable", "no data recevied" );
-
- for( i = 0; i < 100; i++ ) {
- mtype++;
- if( mtype > 10 ) {
- mtype = 0;
- }
-
- // we'll reuse a received message; get max size
- sz = msg->Get_available_size();
-
- // direct access to payload; add something silly
- payload = msg->Get_payload();
- len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i );
-
- // payload updated in place, prevent copy by passing nil
- if ( ! msg->Send_msg( mtype, xapp::Message::NO_SUBID, len, NULL )) {
- fprintf( stderr, "<SNDR> send failed: %d\\n", i );
- }
-
- // receive anything that might come back
- msg = xfw->Receive( response_to );
- if( msg != NULL ) {
- if( ! received ) {
- alarm->Clear( xapp::Alarm::SEV_MINOR, 13, "messages flowing", "" ); // clear the alarm on first received message
- received = true;
- }
-
- rmtype = msg->Get_mtype();
- payload = msg->Get_payload();
- fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
- rmtype, (char *) payload.get() );
- } else {
- msg = xfw->Alloc_msg( 2048 );
- }
-
- if( delay > 0 ) {
- usleep( delay );
- }
- }
- }
-
- Figure 26: Simple looping sender application with alarm
- generation.
+ Figure 33: Simple application making use of the
+ configuration object.