X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=docs%2Fuser-guide.rst;h=d9137a3a273da24817d6bd83c6c72b2ae98dc52b;hb=d486a17c04f3d6d865f787168d446f4cfea3be25;hp=f5df1b8f1992d2b864184fc5e511e1c50a4a46fc;hpb=ef3620529c936a8790d6970c57198b4caee09c0d;p=ric-plt%2Fxapp-frame-cpp.git diff --git a/docs/user-guide.rst b/docs/user-guide.rst index f5df1b8..d9137a3 100644 --- a/docs/user-guide.rst +++ b/docs/user-guide.rst @@ -14,16 +14,27 @@ USER'S GUIDE 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 @@ -65,21 +76,37 @@ the object's constructor with two required parameters: :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). @@ -163,49 +190,93 @@ follows: :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. @@ -491,7 +562,7 @@ to the constructor. :: - #include + #include std::string jstring = "{ \\"tag\\": \\"Hello World\\" }"; Jhash* jh; @@ -592,20 +663,35 @@ following default values are returned: :header-rows: 0 :class: borderless + * - **String** + - + An empty string (.e.g ""). + + | + + * - **Value** + - + Zero (e.g 0.0) + + | + + * - **bool** + - + false @@ -753,24 +839,43 @@ The API consists of the following function types: :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. @@ -1083,11 +1188,233 @@ source to be set after construction. +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 + + 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 @@ -1352,7 +1679,7 @@ omitted. The full code is in the framework repository. return 0; } - Figure 23: Simple callback application. + Figure 29: Simple callback application. Callback Receiver @@ -1477,7 +1804,7 @@ genereate metrics with the receipt of each 1000th message. // control should not return } - Figure 24: Simple callback application. + Figure 30: Simple callback application. @@ -1589,131 +1916,224 @@ to keep the example small and to the point. } } - 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 + #include + #include + + #include + #include + + #include "ricxfcpp/xapp.hpp" + #include "ricxfcpp/alarm.hpp" + + extern int main( int argc, char** argv ) { + std::unique_ptr xfw; + std::unique_ptr msg; + xapp::Msg_component payload; // special type of unique pointer to the payload + std::unique_ptr 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, " response timeout set to: %d\\n", response_to ); + fprintf( stderr, " listening on port: %s\\n", port ); + + // get an instance and wait for a route table to be loaded + xfw = std::unique_ptr( 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, " 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 + + #include "ricxfcpp/config.hpp" + #include "ricxfcpp/message.hpp" + #include "ricxfcpp/msg_component.hpp" + #include + #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 cfg; + + // only parameter recognised is the config file name + if( argc > 1 ) { + cfg = std::unique_ptr( new xapp::Config( std::string( argv[1] ) ) ); + } else { + cfg = std::unique_ptr( new xapp::Config( ) ); + } + + // must get a port from the config; no default + auto port = cfg->Get_port( "rmr-data" ); + if( port.empty() ) { + fprintf( stderr, " 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, " listening on port: %s\\n", port.c_str() ); + fprintf( stderr, " 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 - #include - #include - - #include - #include - - #include "ricxfcpp/xapp.hpp" - #include "ricxfcpp/alarm.hpp" - - extern int main( int argc, char** argv ) { - std::unique_ptr xfw; - std::unique_ptr msg; - xapp::Msg_component payload; // special type of unique pointer to the payload - std::unique_ptr 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, " response timeout set to: %d\\n", response_to ); - fprintf( stderr, " listening on port: %s\\n", port ); - - // get an instance and wait for a route table to be loaded - xfw = std::unique_ptr( 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, " 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.