Add support for config file parsing and watching 87/4487/4 2.2.0
authorE. Scott Daniels <daniels@research.att.com>
Wed, 29 Jul 2020 16:39:54 +0000 (12:39 -0400)
committerE. Scott Daniels <daniels@research.att.com>
Fri, 31 Jul 2020 15:03:24 +0000 (11:03 -0400)
This change introduces the ability to load and parse
the json xapp descriptor (config) file. It also provides
the ability for the xAPP to register a callback function
which is executed when the descriptor is changed.

Issue-ID: RIC-428

Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: I7a1147aa8055599ef4b36ab17960e32ccb5b741f

30 files changed:
CHANGES
CMakeLists.txt
doc/src/lib/setup.im
doc/src/user/Makefile
doc/src/user/config.im [new file with mode: 0644]
doc/src/user/cpp_frame.im
doc/src/user/example1.im
doc/src/user/example2.im
doc/src/user/example3.im
doc/src/user/example4.im
doc/src/user/example5.im [new file with mode: 0644]
doc/src/user/jhash.im
doc/src/user/user_guide.xfm
docs/rel-notes.rst
docs/user-guide.rst
examples/Makefile
examples/xapp_t3.cpp [new file with mode: 0644]
src/config/CMakeLists.txt [new file with mode: 0644]
src/config/config.cpp [new file with mode: 0644]
src/config/config.hpp [new file with mode: 0644]
src/config/config_cb.cpp [new file with mode: 0644]
src/config/config_cb.hpp [new file with mode: 0644]
src/json/jhash.cpp
src/json/jwrapper.c
test/Makefile
test/config1.json [new file with mode: 0644]
test/config_test.cpp [new file with mode: 0644]
test/metrics_test.cpp
test/unit_test.cpp
test/unit_test.sh

diff --git a/CHANGES b/CHANGES
index 8663e5a..2e7cbff 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -6,6 +6,9 @@
 # squished to one.
 
 release = Cherry
+2020 July 30; version 2.2.0
+       Add support for configuration file reading and change notification.
+
 2020 July 22; version 2.1.0
        Added metrics support (RIC-381).
 
index 308c524..cd2e3a1 100644 (file)
@@ -29,7 +29,7 @@ project( ricxfcpp )
 cmake_minimum_required( VERSION 3.5 )
 
 set( major_version "2" )               # should be automatically populated from git tag later, but until CI process sets a tag we use this
-set( minor_version "1" )
+set( minor_version "2" )
 set( patch_level "0" )
 
 set( install_root "${CMAKE_INSTALL_PREFIX}" )
@@ -156,7 +156,7 @@ set ( srcd "${CMAKE_CURRENT_SOURCE_DIR}" )
 # but Cmake insists on having these exist when we add them to include directories to
 # enable code to find them after we build them.
 #
-include_directories( "${srcd}/src/messaging;${srcd}/src/json;${srcd}/src/alarm;${srcd}/src/metrics;${srcd}/ext/jsmn" )
+include_directories( "${srcd}/src/messaging;${srcd}/src/json;${srcd}/src/alarm;${srcd}/src/metrics;${srcd}/src/config;${srcd}/ext/jsmn" )
 
 # Compiler flags
 #
@@ -187,12 +187,13 @@ add_subdirectory( src/messaging )
 add_subdirectory( src/xapp )
 add_subdirectory( src/alarm )
 add_subdirectory( src/metrics )
+add_subdirectory( src/config )
 #add_subdirectory( doc )                               # this will auto skip if {X}fm is not available
 
 # shared and static libraries are built from the same object files.
 #
 add_library( ricxfcpp_shared SHARED
-       "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:json_objects>;$<TARGET_OBJECTS:alarm_objects>;$<TARGET_OBJECTS:metrics_objects>;$<TARGET_OBJECTS:xapp_objects>"
+       "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:json_objects>;$<TARGET_OBJECTS:alarm_objects>;$<TARGET_OBJECTS:metrics_objects>;$<TARGET_OBJECTS:config_objects>;$<TARGET_OBJECTS:xapp_objects>"
 )
 set_target_properties( ricxfcpp_shared
        PROPERTIES
@@ -205,7 +206,7 @@ set_target_properties( ricxfcpp_shared
 # we only build/export the static archive (.a) if generating a dev package
 if( DEV_PKG )
        add_library( ricxfcpp_static STATIC
-               "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:json_objects>;$<TARGET_OBJECTS:alarm_objects>;$<TARGET_OBJECTS:metrics_objects>;$<TARGET_OBJECTS:xapp_objects>"
+               "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:json_objects>;$<TARGET_OBJECTS:alarm_objects>;$<TARGET_OBJECTS:config_objects>;$<TARGET_OBJECTS:metrics_objects>;$<TARGET_OBJECTS:xapp_objects>"
        )
        set_target_properties( ricxfcpp_static
                PROPERTIES
index 6d51a00..0a41d38 100644 (file)
@@ -41,6 +41,7 @@
        .dv col_width 6.5i
        .dv textsize 10
        .dv textfont Helvetica
+       .dv boldfont Helvetica-Bold
        .hn off
        .tm .75i
 
        .gv e OUTPUT_TYPE ot
        .dv output_type &{ot!txt}
 
+
        .** {X}fm package provided common macro definitions
        .im cmd_master.im
 
+       .** because of the current RTD "template" we MUST force double spaceing
+       .** in definition lists else the bloody thing runs them all onto one line.
+       .** the following will do this and is harmless for other output types.
+       &force_diterm_breaks
+
        .gv e XFM_PASS pass
        .dv pass &{pass!1}
 
@@ -82,8 +89,8 @@
                .ca expand extend _fref.ca
                .dv &vname &vval
                .ca end
-.** this ca end MUST be in col 0
 .ca end
+.** the previous 'ca end' MUST be in col 0
        .dv export_var .dv vname $1 ^: .dv vval $2 ^: .dv $1 $2 ^: .im _fref_sub.ca
        .dv export_fig  .dv vname $1 ^: .gv fig .ev ^`.dv vval ^^&_fig ^: ^`  .im _fref_sub.ca
        .dv export_tab  .dv vname $1 ^: .gv table .ev ^`.dv vval ^^&_table ^` ^: .im _fref_sub.ca
index a300d6c..9a1b395 100644 (file)
@@ -19,7 +19,7 @@
 
 docs = user_guide
 src = user_guide.xfm
-imbed_src = cpp_frame.im example1.im example2.im example3.im jhash.im example4.im metrics.im
+imbed_src = cpp_frame.im example1.im example2.im example3.im jhash.im example4.im metrics.im config.im
 desired_out = rst ps md
 
 include ../master.mk
diff --git a/doc/src/user/config.im b/doc/src/user/config.im
new file mode 100644 (file)
index 0000000..e14fb80
--- /dev/null
@@ -0,0 +1,236 @@
+.** vim: sw=4 ts=4 et :
+.if false
+==================================================================================
+    Copyright (c) 2020 Nokia
+    Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+.fi
+
+
+.if false
+       This imbed file contains the portion of the document that describes the
+    configuration file and config notification update interface.
+.fi
+
+
+&h1(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
+&ital( 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.
+
+&h2( The Config Object )
+The xAPP must create an instance of the &cw(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 &conf_const_fig.
+&space
+
+
+.ca start conf_const.ca
+&ex_start
+    #include <ricxfcpp/config.hpp>
+
+    auto cfg = new xapp::Config( );
+    auto cfg = new xapp::Config( "/var/myapp/config.json"  );
+&ex_end
+&export_fig(conf_const_fig)
+&fig_cen(Creating a configuration instance.)
+&space
+.ca end
+.gv remain
+&ifroom( 1.5 : conf_const.ca )
+
+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.
+
+&h2( Available Functions )
+Once a configuration has been created the following capabilities are
+available:
+&space
+
+&indent
+&beg_list( &lic1 )
+&li Get a control value (numeric, string, or boolean)
+&half_space
+&li Get the RMR port for the container with the supplied name
+&half_space
+&li Set a notification callback function
+&half_space
+&li Get the raw contents of the file
+&end_list
+&uindent
+
+&h3(Control Values)
+The &cw(controls) section of the xAPP descriptor is generally used to supply a
+&ital(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.
+
+&space
+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 &config_proto1_fig.
+
+
+.ca start conf_proto1.ca
+&ex_start
+  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 );
+&ex_end
+&export_fig(config_proto1_fig)
+&fig_cen(The various controls section get functions.)
+&space
+.ca end
+.gv remain
+&ifroom( 1.5 : conf_proto1.ca )
+
+
+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 &config_proto1_fig.
+
+
+&h3(The RMR Port)
+The &cw(messaging) section of the xAPP descriptor provides the ability to define
+one or more RMR &ital(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 &cw(Get_port) function whose prototype is illustrated in figure &get_port_fig below.
+&space
+
+.ca start git_port.ca
+&ex_start
+  std::string Get_port( std::string name );
+&ex_end
+&export_fig(get_port_fig)
+&fig_cen(The get port prototype.)
+&space
+.ca end
+.gv remain
+&ifroom( 0.5 : git_port.ca )
+
+
+&h3(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 &cw(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.
+
+&h2(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 &callback_proto_fig.
+&space
+
+
+
+.ca start callbac_proto.ca
+&ex_start
+   void cb_name( xapp::Config& c, void* data )
+&ex_end
+&export_fig(callback_proto_fig)
+&fig_cen(The prototype which the xAPP configuration notify callback must use.)
+&space
+.ca end
+.gv remain
+&ifroom( 0.75 : callbac_proto.ca )
+
+&h3(Enabling The Notifications)
+Notifications are enabled by invoking the &cw(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 &callback_fig illustrates how the xAPP can define and register a notification
+callback.
+&space
+
+.ca start callback.ca
+&ex_start
+
+  //  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 );
+  }
+&ex_end
+&export_fig(callback_fig)
+&fig_cen(Small notification callback function allowing on the fly verbose level change.)
+&space
+.ca end
+.gv remain
+&ifroom( 1.75 : callback.ca )
+
+&space
+The xAPP would register the &cw(config_chg()) function as the notification callback
+using the call illustrated in figure &config_set_cb_fig.
+
+.ca start set_callback.ca
+&ex_start
+
+   auto conf = new xapp::Config();
+   conf->Set_callback( config_chg );
+&ex_end
+&export_fig(config_set_cb_fig)
+&fig_cen(Setting the notification callback and and activating notifications.)
+&mult_space( 2.5 )
+.ca end
+.gv remain
+&ifroom( 0.75 : set_callback.ca )
+
+&h2(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.
+
+&h3(The RMR Section)
+There is a deprecated section within the xAPP descriptor which has the title
+&ital(rmr.)
+The &ital(messaging) section provides more flexibility, and additional information
+and has been a replacement for the &ital(rmr) section for all applications.
+The information in the &ital(rmr) section should be kept consistent with the
+duplicated information in the &ital(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 &ital(messaging) section.
+The configuration parsing and support provided by the framework will ignore the
+&ital(rmr) section.
+
index f9e3963..5271301 100644 (file)
 .fi
 
 &h1(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
+The C++ framework allows the programmer to create an instance of the &cw(Xapp) object which then
+can be used as a foundation for the application.
+The &cw(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.
 
-&h1(The Framework API)
+&h2( Termonology )
+To avoid confusion the term &bold( xAPP ) is used in this document to refer to the user's application
+code which is creating &cw( Xapp, ) and related objects provided by the &ital( framework. )
+The use of &ital( framework ) should be taken to mean any of the classes and/or support functions
+which are provided by the &cw( ricxfcpp ) library.
+
+&h1( The Framework API )
 The C++ framework API consists of the creation of the xApp object, and invoking desired functions via
 the instance of the object.
 The following paragraphs cover the various steps involved to create an xApp instance, wait for a route
index 8ca90b6..c9eb267 100644 (file)
@@ -52,6 +52,6 @@ the framework, have been omitted. The full code is in the framework repository.
 &ex_start
 .im  j=start-example ../../../examples/rmr_dump.cpp
 &ex_end
-&fig( Simple callback application. )
+&fig_cen( Simple callback application. )
 &uindent
 
index b78c6db..6ede415 100644 (file)
@@ -46,6 +46,6 @@ the receipt of each 1000th message.
 &ex_start
 .im  j=start-example ../../../examples/xapp_t1.cpp
 &ex_end
-&fig( Simple callback application. )
+&fig_cen( Simple callback application. )
 &uindent
 &space
index 09d2117..e9c3625 100644 (file)
@@ -30,6 +30,7 @@ been made in order to keep the example small and to the point.
 &ex_start
 .im  j=start-example ../../../examples/xapp_t2.cpp
 &ex_end
-&fig( Simple looping sender application. )
+&fig_cen( Simple looping sender application. )
+&uindent
 &space
 
index a1968bf..5d6759b 100644 (file)
@@ -17,7 +17,7 @@
 ==================================================================================
 .fi
 
-&h2(Alarm Example)
+&h2(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.
@@ -32,6 +32,7 @@ cleared.
 &ex_start
 .im  j=start-example ../../../examples/xapp_alarm.cpp
 &ex_end
-&fig( Simple looping sender application with alarm generation. )
+&fig_cen( Simple looping sender application with alarm generation. )
+&uindent
 &space
 
diff --git a/doc/src/user/example5.im b/doc/src/user/example5.im
new file mode 100644 (file)
index 0000000..635cd10
--- /dev/null
@@ -0,0 +1,38 @@
+
+.if false
+==================================================================================
+    Copyright (c) 2020 Nokia
+    Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+.fi
+
+&h2(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.
+
+
+&space
+&indent
+.** pull in the code from the example directory
+&ex_start
+.im  j=start-example ../../../examples/xapp_t3.cpp
+&ex_end
+&fig_cen( Simple application making use of the configuration object.)
+&uindent
+&space
+
index 66bbb57..3b59548 100644 (file)
@@ -37,9 +37,9 @@ resulting hash.
 &h2(Creating The Jhash Object)
 The Jhash object is created simply by passing a json string to the constructor.
 
-.cc 10l
+.ca start jhash_obj.ca
 &ex_start
-    #include <ricxfcpp/Jhash>
+    #include <ricxfcpp/Jhash.hpp>
 
     std::string jstring = "{ \"tag\": \"Hello World\" }";
     Jhash*  jh;
@@ -48,6 +48,8 @@ The Jhash object is created simply by passing a json string to the constructor.
 &ex_end
 &fig_cen(The creation of the Jhash object.)
 &space
+.ca end
+&ifroom( 2.5 : jhash_obj.ca )
 
 
 Once the Jhash object has been created any of the methods described in the following
@@ -232,7 +234,7 @@ values from the json in figure &array_blob_json_fig.
 &space
 
 
-.cc 18l
+.ca start blobs.ca
 &ex_start
     std::string mname;
     float mnum;
@@ -252,4 +254,6 @@ values from the json in figure &array_blob_json_fig.
 &export_fig(array_blob_json_fig )
 &fig_cen(Code to process the array of blobs.)
 &space
+.ca end
+&ifroom( 3 : blobs.ca )
 
index 9ba1d4a..75434a0 100644 (file)
 .im jhash.im
 .im alarm.im
 .im metrics.im
+.im config.im
 
 &h1(Example Programmes)
 The following sections contain several example programmes 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:
+&space
+
+.** indent gets round a space bug in RST
+&center_start
+&indent
+&cw( https^://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/xapp-frame-cpp )
+&uindent
+&center_end
 
 .im example1.im
 .im example2.im
 .im example3.im
 .im example4.im
+.im example5.im
index 4841bcb..13e3542 100644 (file)
@@ -18,6 +18,12 @@ xAPP Framework.
 Cherry Release
 ==============
 
+2020 July 30; version 2.2.0
+---------------------------
+Add support for configuration file reading and change
+notification.
+
+
 2020 July 22; version 2.1.0
 ---------------------------
 Added metrics support (RIC-381).
index f5df1b8..d9137a3 100644 (file)
@@ -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 <ricxfcpp/Jhash>
+      #include <ricxfcpp/Jhash.hpp>
 
       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 <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
@@ -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 <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.
 
index 2c4ded5..aa9c5a7 100644 (file)
@@ -27,5 +27,6 @@
 % :: %.cpp
        #C_INCLUDE_PATH=/tmp/usr/include:/usr/local/include g++ $< -g -o $@  -lricxfcpp -lrmr_si -lpthread -lm
        g++ $< -g -o $@  -lricxfcpp -lrmr_si -lpthread -lm
+       #g++ $< -g -o $@  /usr/local/lib/libricxfcpp.a -lrmr_si -lpthread -lm
 
 all:: xapp_t1 xapp_t2 rmr_dump
diff --git a/examples/xapp_t3.cpp b/examples/xapp_t3.cpp
new file mode 100644 (file)
index 0000000..eb58a1c
--- /dev/null
@@ -0,0 +1,108 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+       Mnemonic:       xapp_t3.cpp
+       Abstract:       This is a simple xAPP which includes using the config support
+                               in the framework.  It is based on the xapp_t1 example and doesn't
+                               do much more than that.
+
+       Date:           30 July 2020
+       Author:         E. Scott Daniels
+
+       Caution:        This example code is pulled directly into one or more of the documents
+                               (starting from the "start-example" tag below.  Use caution with
+                               line lengths and contents because of the requirement that this
+                               be documentation as well as working code.
+*/
+// start-example
+#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
+}
diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d56b24d
--- /dev/null
@@ -0,0 +1,42 @@
+# vim: sw=4 ts=4 noet:
+#
+#==================================================================================
+#   Copyright (c) 2020 Nokia
+#   Copyright (c) 2020 AT&T Intellectual Property.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#==================================================================================
+#
+
+
+# For clarity: this generates object, not a lib as the CM command implies.
+#
+add_library( config_objects OBJECT
+       config.cpp
+       config_cb.cpp
+)
+
+target_include_directories (config_objects PUBLIC
+       $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+       $<INSTALL_INTERFACE:include>
+       PRIVATE src)
+
+# header files should go into .../include/xfcpp/
+if( DEV_PKG )
+       install( FILES
+               config.hpp
+               config_cb.hpp
+               DESTINATION ${install_inc}
+       )
+endif()
+
diff --git a/src/config/config.cpp b/src/config/config.cpp
new file mode 100644 (file)
index 0000000..16eb421
--- /dev/null
@@ -0,0 +1,371 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+    Copyright (c) 2020 AT&T Intellectual Property.
+    Copyright (c) 2020 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+    Mnemonic:  config.cpp
+    Abstract:  Support for reading config json.
+
+                               The config structure allows simplified parsing of the json and
+                               easy access to the things we belive all xAPPs will use (port
+                               digging form the named "interface" and control settings). This
+                               also supports the watching on the file and driving the user
+                               callback when the config file appears to have changed (write
+                               close on the file).
+
+                               Accessing information from the json is serialised (mutex) as
+                               with the random nature of file updates, we must ensure that
+                               we don't change the json as we're trying to read from it. Locking
+                               should be transparent to the user xAPP.
+
+    Date:       27 July 2020
+    Author:     E. Scott Daniels
+*/
+
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <thread>
+#include <memory>
+
+#include "jhash.hpp"
+#include "config.hpp"
+#include "config_cb.hpp"
+
+namespace xapp {
+
+
+// ----- private things --------
+/*
+       Notification listener. This function is started in a thread and listens for
+       changes to the config file. When it sees an interesting change (write close)
+       to the file, then it will read the new set of json, parse it, and drive the
+       user callback.
+
+       We must watch the directory containing the file as if the file is edited and
+       replaced it's likely saved with a different referencing inode. We'd see the
+       first change, but not any subsequent changes as the inotify is based on inodes
+       and not directory entries.
+*/
+void xapp::Config::Listener( ) {
+       struct inotify_event*   ie;             // event that popped
+       int ifd;                                                // the inotify file des
+       int     wfd;                                            // the watched file des
+       int     n;
+       char    rbuf[4096];                             // large read buffer as the event is var len
+       char*   dname;                                  // directory name
+       char*   bname;                                  // basename
+       char*   tok;
+
+       ifd = inotify_init1( 0 );               // initialise watcher setting blocking read (no option)
+       if( ifd < 0 ) {
+               fprintf( stderr, "<XFCPP> ### ERR ### unable to initialise file watch %s\n", strerror( errno ) );
+               return;
+       }
+
+       dname = strdup( fname.c_str() );                                        // defrock the file name into dir and basename
+       if( (tok = strrchr( dname, '/' )) != NULL ) {
+               *tok = 0;
+               bname = strdup( tok+1 );
+       } else {
+               free( dname );
+               dname = strdup( "." );
+               bname = strdup( fname.c_str() );
+       }
+
+       wfd = inotify_add_watch( ifd, (char *) dname, IN_MOVED_TO | IN_CLOSE_WRITE );           // we only care about close write changes
+
+       if( wfd < 0 ) {
+               fprintf( stderr, "<XFCPP> ### ERR ### unable to add watch on config file %s: %s\n", fname.c_str(), strerror( errno ) );
+               return;
+       }
+
+       while( true ) {
+               n = read( ifd, rbuf, sizeof( rbuf ) );                          // read the event
+               if( n < 0  ) {
+                       if( errno == EAGAIN ) {
+                               continue;
+                       } else {
+                               fprintf( stderr, "<XFCPP ### CRIT ### config listener read err: %s\n", strerror( errno ) );
+                               return;
+                       }
+               }
+
+               ie = (inotify_event *) rbuf;
+               if( ie->len > 0 && strcmp( bname, ie->name ) == 0  ) {
+                       // TODO: lock
+                       auto njh = jparse( fname );                                                     // reparse the file
+                       // TODO: unlock
+
+                       if( njh != NULL && cb != NULL ) {                               // good parse, save and drive user callback
+                               jh = njh;
+                               cb->Drive_cb( *this, user_cb_data );
+                       }
+               }
+       }
+}
+
+
+/*
+       Read a file containing json and parse into a framework Jhash.
+
+       Using C i/o will speed this up, but I can't imagine that we need
+       speed reading the config file once in a while.
+       The file read comes from a stack overflow example:
+               stackoverflow.com/questions/2912520/read-file-contents-into-a-string-in-c
+*/
+std::shared_ptr<xapp::Jhash> xapp::Config::jparse( std::string ufname ) {
+       fname = ufname;
+
+       std::ifstream ifs( fname );
+       std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
+
+       auto new_jh = std::shared_ptr<xapp::Jhash>( new xapp::Jhash( st.c_str() ) );
+       return  new_jh->Parse_errors() ? NULL : new_jh;
+}
+
+/*
+       Read the configuration file from what we find as the filename in the environment (assumed
+       to be referenced by $XAPP_DESCRIPTOR_PATH/config-file.json. If the env var is not
+       defined we assume ./.  The data is then parsed with the assumption that it's json.
+
+       The actual meaning of the environment variable is confusing. The name is "path" which
+       should mean that this is the directory in which the config file lives, but the examples
+       elsewhere suggest that this is a filename (either fully qualified or relative). For now
+       we will assume that it's a file name, though we could add some intelligence to determine
+       if it's a directory name or not if it comes to it.
+*/
+std::shared_ptr<xapp::Jhash> xapp::Config::jparse( ) {
+       char*   data;
+
+       if( (data = getenv( (char *) "XAPP_DESCRIPTOR_PATH" )) == NULL ) {
+               data =  (char *) "./config-file.json";
+       }
+
+       return jparse( std::string( data ) );
+}
+
+// --------------------- construction, destruction -------------------------------------------
+
+/*
+       By default expect to find XAPP_DESCRIPTOR_PATH in the environment and assume that it
+       is the directory name where we can find config-file.json. The build process will
+       read and parse the json allowing the user xAPP to invoke the supplied  "obvious"
+       functions to retrieve data.  If there is something in an xAPP's config that isn't
+       standard, it can get the raw Jhash object and go at it directly. The idea is that
+       the common things should be fairly painless to extract from the json goop.
+*/
+xapp::Config::Config() :
+       jh( jparse() ),
+       listener( NULL )
+{ /* empty body */ }
+
+/*
+       Similar, except that it allows the xAPP to supply the filename (testing?)
+*/
+xapp::Config::Config( std::string fname) :
+       jh( jparse( fname ) ),
+       listener( NULL )
+{ /* empty body */ }
+
+
+/*
+       Read and return the raw file blob as a single string. User can parse, or do
+       whatever they need (allows non-json things if necessary).
+*/
+std::string xapp::Config::Get_contents( ) {
+       std::string rv = "";
+
+       if( ! fname.empty() ) {
+               std::ifstream ifs( fname );
+               std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
+               rv = st;
+       }
+
+       return rv;
+}
+
+// ----- convience function for things we think an xAPP will likely need to pull from the config
+
+
+/*
+       Suss out the port for the named "interface". The interface is likely the application
+       name.
+*/
+std::string xapp::Config::Get_port( std::string name ) {
+       int i;
+       int     nele = 0;
+       double value;
+       std::string rv = "";            // result value
+       std::string pname;                      // element port name in the json
+
+       if( jh == NULL ) {
+               return rv;
+       }
+
+       jh->Unset_blob();
+       if( jh->Set_blob( (char *) "messaging" ) ) {
+               nele = jh->Array_len( (char *) "ports" );
+               for( i = 0; i < nele; i++ ) {
+                       if( jh->Set_blob_ele( (char *) "ports", i ) ) {
+                               pname = jh->String( (char *) "name" );
+                               if( pname.compare( name ) == 0 ) {                              // this element matches the name passed in
+                                       value = jh->Value( (char *) "port" );
+                                       rv = std::to_string( (int) value );
+                                       jh->Unset_blob( );                                                      // leave hash in a known state
+                                       return rv;
+                               }
+                       }
+
+                       jh->Unset_blob( );                                                              // Jhash requires bump to root, and array reselct to move to next ele
+                       jh->Set_blob( (char *) "messaging" );
+               }
+       }
+
+       jh->Unset_blob();
+       return rv;
+}
+
+/*
+       Suss out the named string from the controls object. If the resulting value is
+       missing or "", then the default is returned.
+*/
+std::string xapp::Config::Get_control_str( std::string name, std::string defval ) {
+       std::string value;
+       std::string rv;                         // result value
+
+       rv = defval;
+       if( jh == NULL ) {
+               return rv;
+       }
+
+       jh->Unset_blob();
+       if( jh->Set_blob( (char *) "controls" ) ) {
+               if( jh->Exists( name.c_str() ) )  {
+                       value = jh->String( name.c_str() );
+                       if( value.compare( "" ) != 0 ) {
+                               rv = value;
+                       }
+               }
+       }
+
+       jh->Unset_blob();
+       return rv;
+}
+
+/*
+       Convenience funciton without default. "" returned if not found.
+       No default value; returns "" if not set.
+*/
+std::string xapp::Config::Get_control_str( std::string name ) {
+       return Get_control_str( name, "" );
+}
+
+/*
+       Suss out the named field from the controls object with the assumption that it is a boolean.
+       If the resulting value is missing then the defval is used.
+*/
+bool xapp::Config::Get_control_bool( std::string name, bool defval ) {
+       bool value;
+       bool rv;                                // result value
+
+       rv = defval;
+       if( jh == NULL ) {
+               return rv;
+       }
+
+       jh->Unset_blob();
+       if( jh->Set_blob( (char *) "controls" ) ) {
+               if( jh->Exists( name.c_str() ) )  {
+                       rv = jh->Bool( name.c_str() );
+               }
+       }
+
+       jh->Unset_blob();
+       return rv;
+}
+
+
+/*
+       Convenience function without default.
+*/
+bool xapp::Config::Get_control_bool( std::string name ) {
+       return Get_control_bool( name, false );
+}
+
+
+/*
+       Suss out the named field from the controls object with the assumption that it is a value (float/int).
+       If the resulting value is missing then the defval is used.
+*/
+double xapp::Config::Get_control_value( std::string name, double defval ) {
+       double value;
+
+       auto rv = defval;                               // return value; set to default
+       if( jh == NULL ) {
+               return rv;
+       }
+
+       jh->Unset_blob();
+       if( jh->Set_blob( (char *) "controls" ) ) {
+               if( jh->Exists( name.c_str() ) )  {
+                       rv = jh->Value( name.c_str() );
+               }
+       }
+
+       jh->Unset_blob();
+       return rv;
+}
+
+
+/*
+       Convenience function. If value is undefined, then 0 is returned.
+*/
+double xapp::Config::Get_control_value( std::string name ) {
+       return Get_control_value( name, 0.0 );
+}
+
+
+// ---- notification support ---------------------------------------------------------------
+
+
+/*
+       Accept the user's notification function, and data that it needs (pointer to
+       something unknown), and stash that as a callback.
+
+       The fact that the user xAPP registers a callback also triggers the creation
+       of a thread to listen for changes on the config file.
+*/
+void xapp::Config::Set_callback( notify_callback usr_func, void* usr_data ) {
+       cb = std::unique_ptr<Config_cb>( new Config_cb( usr_func, usr_data ) );
+       user_cb_data = usr_data;
+
+       if( listener == NULL ) {                                // start thread if needed
+               listener = new std::thread( &xapp::Config::Listener, this );
+       }
+}
+
+} // namespace
diff --git a/src/config/config.hpp b/src/config/config.hpp
new file mode 100644 (file)
index 0000000..5dac6a3
--- /dev/null
@@ -0,0 +1,83 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+    Copyright (c) 2020 AT&T Intellectual Property.
+    Copyright (c) 2020 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+    Mnemonic:  config.
+    Abstract:  Header for the config reader/watcher class.
+
+    Date:       27 July 2020
+    Author:     E. Scott Daniels
+*/
+
+#ifndef XAPP_CONFIG_HPP
+#define XAPP_CONFIG_HPP
+
+
+#include <iostream>
+#include <thread>
+
+#include "jhash.hpp"
+#include "config.hpp"
+#include "config_cb.hpp"
+
+namespace xapp {
+
+#define MAX_PFNAME     (4096 + 256)            // max path name and max filname + nil for buffer allocation
+
+class Config {
+       std::string     fname;                          // the file name that we'll listen to
+       std::thread* listener;                  // listener thread info
+
+       std::shared_ptr<Jhash>  jh;             // the currently parsed json from the config
+       std::unique_ptr<Config_cb> cb;  // info needed to drive user code when config change noticed
+       void*   user_cb_data;                   // data that the caller wants passed on notification callback
+
+       // -----------------------------------------------------------------------
+       private:
+               std::shared_ptr<xapp::Jhash>  jparse( std::string fname );
+               std::shared_ptr<xapp::Jhash>  jparse( );
+               void Listener( );
+
+       public:
+               Config();                                               // builders
+               Config( std::string fname);
+
+               bool Get_control_bool( std::string name, bool defval );
+               bool Get_control_bool( std::string name );
+
+               std::string Get_contents( );
+
+               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 );
+
+               std::string Get_port( std::string name );
+
+               void Set_callback( notify_callback usr_func, void* usr_data );
+};
+
+
+} // namespace
+
+
+
+#endif
diff --git a/src/config/config_cb.cpp b/src/config/config_cb.cpp
new file mode 100644 (file)
index 0000000..309ddba
--- /dev/null
@@ -0,0 +1,62 @@
+
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+       Mnemonic:       notify_cb.cpp
+       Abstract:       Notification drivers.
+
+       Date:           27 July 2020
+       Author:         E. Scott Daniels
+*/
+
+//#include <cstdlib>
+
+#include "config.hpp"
+#include "config_cb.hpp"
+
+namespace xapp {
+
+/*
+       Builder.
+*/
+Config_cb::Config_cb( notify_callback ufun, void* udata ) :
+               user_fun( ufun ),
+               udata( udata )
+{ /* empty body */  }
+
+
+/*
+       there is nothing to be done from a destruction perspective, so no
+       destruction function needed at the moment.
+*/
+
+/*
+       Drive_cb will invoke the callback and pass along the stuff passed here.
+*/
+void xapp::Config_cb::Drive_cb( xapp::Config& c, void* udata ) {
+       if( user_fun != NULL ) {
+               user_fun( c, udata );
+       }
+}
+
+
+
+} // namespace
diff --git a/src/config/config_cb.hpp b/src/config/config_cb.hpp
new file mode 100644 (file)
index 0000000..f94d129
--- /dev/null
@@ -0,0 +1,64 @@
+
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+       Mnemonic:       callback.hpp
+       Abstract:       Manages config notification callback data and such.
+                               This is a bit of over kill because, unlike the message
+                               receipt callbacks, there is only one potential callback
+                               for the config. We could manage this inside of the conf
+                               class, but if there is ever the need to have multiple
+                               callbacks, the base is set.
+
+       Date:           27 July 2020
+       Author:         E. Scott Daniels
+*/
+
+
+#ifndef _XAPP_CONF_CB_HPP
+#define _XAPP_CONF_CB_HPP
+
+#include <memory>
+
+namespace xapp {
+
+class Config;
+
+/*
+       Describes the user function that we will invoke
+*/
+typedef void(*notify_callback)( xapp::Config& c, void* user_data );
+
+class Config_cb {
+
+       private:
+               notify_callback user_fun;
+               void*   udata;                                                                  // user data
+
+       public:
+               Config_cb( notify_callback cbfun, void* user_data );            // builder
+               void Drive_cb( xapp::Config& c, void* udata );                          // invoker
+};
+
+} // namespace
+
+#endif
+
index df95e13..d81c617 100644 (file)
@@ -222,6 +222,7 @@ bool xapp::Jhash::Is_missing( const char* name ) {
 bool xapp::Jhash::Bool( const char* name ) {
        int v;
        v = (int) jw_value( st, name );
+
        return v == 1;
 }
 
index f6ce014..85a2c24 100644 (file)
@@ -1,4 +1,3 @@
-
 // vi: ts=4 sw=4 noet:
 /*
 ==================================================================================
index b7c843b..cc47990 100644 (file)
@@ -1,11 +1,11 @@
 
 coverage_opts = -ftest-coverage -fprofile-arcs
 
-binaries = unit_test
+binaries = unit_test jhash_test config_test metrics_test
+include = -I ../src/xapp -I ../src/alarm -I ../src/messaging  -I  ../src/config -I ../ext/jsmn  -I  ../src/json -I ../src/metrics
+ld_path = LD_LIBRARY_PATH=../src/.build
 
-tests::        unit_test jhash_test metrics_test
-
-include_list = -I ../src/metrics -I ../src/alarm -I ../src/messaging
+tests::        $(binaries)
 
 # RMR emulation
 rmr_em.o::     rmr_em.c
@@ -15,7 +15,7 @@ rmr_em.o::    rmr_em.c
 # emulate (and don't need to)
 #
 unit_test:: unit_test.cpp rmr_em.o
-       g++ -g $(coverage_opts) $(include_list) unit_test.cpp -o unit_test -L../.build -lricxfcpp rmr_em.o  -lrmr_si -lpthread
+       g++ -g $(coverage_opts) $(include) unit_test.cpp -o unit_test -L../.build -lricxfcpp rmr_em.o  -lrmr_si -lpthread
 
 # build a special jwrapper object with coverage settings
 jwrapper_test.o:: ../src/json/jwrapper.c ../src/json/jwrapper.h
@@ -26,8 +26,10 @@ jhash_test:: jwrapper_test.o jhash_test.cpp
        g++ -g $(coverage_opts) -I ../src/json -I ../ext/jsmn jhash_test.cpp -o jhash_test jwrapper_test.o -lrmr_si -lpthread
 
 metrics_test:: metrics_test.cpp rmr_em.o
-       # do NOT link the xapp lib; we include all modules in the test programme
-       g++ -g $(coverage_opts) $(include_list) metrics_test.cpp -o metrics_test -L../.build -lricxfcpp rmr_em.o -l rmr_si -lpthread
+       g++ -g $(coverage_opts) $(include) metrics_test.cpp -o metrics_test -L../.build -lricxfcpp rmr_em.o -l rmr_si -lpthread
+
+config_test:: config_test.cpp jwrapper_test.o
+       g++ -g $(coverage_opts) $(include) config_test.cpp -o config_test jwrapper_test.o -lricxfcpp -lrmr_si -lpthread
 
 # prune gcov files generated by system include files
 clean::
diff --git a/test/config1.json b/test/config1.json
new file mode 100644 (file)
index 0000000..8a09e5c
--- /dev/null
@@ -0,0 +1,103 @@
+{
+   "comment":  "test config; this one is complete and legit",
+   "xapp_name": "mcxapp",
+   "version": "1.0.11",
+   "containers": [
+        {
+            "name": "mcxapp",
+            "image": {
+                "registry": "ranco-dev-tools.eastus.cloudapp.azure.com:10001",
+                "name": "ric-app-mc",
+                "tag": "1.1.3"
+            },
+            "command": [ "/bin/bash", "-c", "--" ],
+        "args": [ "/playpen/bin/container_start.sh" ]
+        }
+    ],
+    "messaging": {
+        "ports": [
+            {
+                "name": "rmr-data-in",
+                "container": "mcxapp",
+                "port": 4560,
+                "rxMessages": [
+                    "RIC_UE_CONTEXT_RELEASE",
+                    "RIC_SGNB_ADDITION_REQ",
+                    "RIC_SGNB_ADDITION_ACK",
+                    "RIC_SGNB_ADDITION_REJECT",
+                    "RIC_SGNB_RECONF_COMPLETE",
+                    "RIC_RRC_TRANSFER",
+                    "RIC_SGNB_MOD_REQUEST",
+                    "RIC_SGNB_MOD_REQUEST_ACK",
+                    "RIC_SGNB_MOD_REQUEST_REJ",
+                    "RIC_SGNB_MOD_REQUIRED",
+                    "RIC_SGNB_MOD_CONFIRM",
+                    "RIC_SGNB_MOD_REFUSE",
+                    "RIC_SGNB_RELEASE_REQUEST",
+                    "RIC_SGNB_RELEASE_REQUEST_ACK",
+                    "RIC_SGNB_RELEASE_REQUIRED",
+                    "RIC_SGNB_RELEASE_CONFIRM",
+                    "RIC_SECONDARY_RAT_DATA_USAGE_REPORT"
+                ],
+                "txMessages": [],
+                "policies": [],
+                "description": "rmr receive data port for mcxapp"
+            },
+            {
+                "name": "rmr-data-out",
+                "container": "mcxapp",
+                "port": 4562,
+                "txMessages": [
+                    "MC_REPORT"
+                ],
+                "rxMessages": [],
+                "policies": [],
+                "description": "rmr send data port for mcxapp"
+            },
+            {
+                "name": "rmr-route",
+                "container": "mcxapp",
+                "port": 4561,
+                "description": "rmr route port for mcxapp"
+            }
+        ]
+    },
+
+    "controls": {
+        "ves_collector_address": "10.53.183.214:8888",
+        "measurement_interval": 1000,
+        "debug_mode": true,
+        "tbool": true,
+        "fbool": false,
+        "simulator_mode": true
+    },
+
+    "rmr": {
+        "protPort": "tcp:4560",
+        "maxSize": 2072,
+        "numWorkers": 1,
+        "txMessages": [
+            "MC_REPORT"
+        ],
+        "rxMessages": [
+            "RIC_UE_CONTEXT_RELEASE",
+            "RIC_SGNB_ADDITION_REQ",
+            "RIC_SGNB_ADDITION_ACK",
+            "RIC_SGNB_ADDITION_REJECT",
+            "RIC_SGNB_RECONF_COMPLETE",
+            "RIC_RRC_TRANSFER",
+            "RIC_SGNB_MOD_REQUEST",
+            "RIC_SGNB_MOD_REQUEST_ACK",
+            "RIC_SGNB_MOD_REQUEST_REJ",
+            "RIC_SGNB_MOD_REQUIRED",
+            "RIC_SGNB_MOD_CONFIRM",
+            "RIC_SGNB_MOD_REFUSE",
+            "RIC_SGNB_RELEASE_REQUEST",
+            "RIC_SGNB_RELEASE_REQUEST_ACK",
+            "RIC_SGNB_RELEASE_REQUIRED",
+            "RIC_SGNB_RELEASE_CONFIRM",
+            "RIC_SECONDARY_RAT_DATA_USAGE_REPORT"
+        ]
+    }
+}
+
diff --git a/test/config_test.cpp b/test/config_test.cpp
new file mode 100644 (file)
index 0000000..68506c1
--- /dev/null
@@ -0,0 +1,186 @@
+// vim: ts=4 sw=4 noet :
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+       Mnemonic:       config_test.cpp
+       Abstract:       Unit test to drive the config functions.
+
+       Date:           28 July 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+
+#include <string>
+#include <memory>
+
+#include "../src/xapp/xapp.hpp"
+
+//#include "../src/json/jhash.hpp"
+//#include "../src/json/jhash.cpp"
+
+#include "../src/config/config.hpp"            // include things directly under test
+#include "../src/config/config_cb.hpp"
+#include "../src/config/config.cpp"
+#include "../src/config/config_cb.cpp"
+
+#include "ut_support.cpp"
+
+char* data = (char *) "some data to validate in callback";
+bool   callback_driven = false;
+int            cb_errors = 0;
+
+
+/*
+       Notification callback to see that it is driven on change.
+*/
+void ncb( xapp::Config& c, void* data ) {
+       cb_errors += fail_if( data == NULL, "callback function did not get a good pointer" );
+
+       auto v = c.Get_control_value( "measurement_interval" );
+       cb_errors += fail_if( v != 1000, "measurement value in new file wasn't different" );
+
+       callback_driven = true;
+}
+
+int main( int argc, char** argv ) {
+       int             errors = 0;
+
+       set_test_name( "config_test" );
+
+
+       auto x = new Xapp( (char*) "43086", false );
+       if(     fail_if( x == NULL, "could not allocate xapp" )  > 0 ) {
+               announce_results( 1 );
+               exit( 1 );
+       }
+
+       fprintf( stderr, "<INFO> sussing info from config1.json\n" );
+       auto c = new xapp::Config( "config1.json" );
+       errors += fail_if( c == NULL, "unable to allocate a config with alternate name" );
+
+       auto s = c->Get_control_str( "ves_collector_address" );
+       errors += fail_if( s.empty(), "expected control string not found" );
+       fprintf( stderr, "<INFO> collector address string var: %s\n", s.c_str() );
+
+       s = c->Get_port( "rmr-data-out" );
+       errors += fail_if( s.empty(), "expected port string not found" );
+       fprintf( stderr, "<INFO> port string var: %s\n", s.c_str() );
+
+       s = c->Get_port( "no-interface" );
+       errors += fail_if( ! s.empty(), "did not return empty when get port given an invalid name" );
+
+       s = c->Get_control_str( "no-such-control" );
+       errors += fail_if( ! s.empty(), "expected empty string for missing control got a string" );
+       if( ! s.empty() ) {
+               fprintf( stderr, "<INFO> unexpected string for no such control name:  %s\n", s.c_str() );
+       }
+
+       auto v = c->Get_control_value( "measurement_interval" );
+       errors += fail_if( v == 0.0, "epxected measurement interval control value not found" );
+
+       auto b = c->Get_control_bool( "debug_mode" );
+       errors += fail_if( b == false, "epxected debug mode control boolean not found or had wrong value" );
+
+
+       // ----- test sussing path and using default name ----------------------------------
+       fprintf( stderr, "<INFO> sussing info from default (no name)\n" );
+       c = new xapp::Config(  );                                                       // drive for coverage
+
+       fprintf( stderr, "<INFO> sussing info from default (env var == ./config1.json)\n" );
+       setenv( (char *) "XAPP_DESCRIPTOR_PATH", "./config1.json", 1 );                         // this var name is bad; it's not a path, but fname
+       c = new xapp::Config(  );
+
+       s = c->Get_control_str( "ves_collector_address" );
+       errors += fail_if( s.empty(), "expected collector address control string not found" );
+       fprintf( stderr, "<INFO> string var: %s\n", s.c_str() );
+
+       v = c->Get_control_value( "measurement_interval" );
+       errors += fail_if( v == 0.0, "expected measurement interval control value not found" );
+
+       b = c->Get_control_bool( "debug_mode" );
+       errors += fail_if( b == false, "expected debug mode control boolean not found" );
+
+
+       auto cs = c->Get_contents();
+       if( fail_if( cs.empty(), "get contents returned an empty string" ) == 0 ) {
+               fprintf( stderr, "<INFO> contents from file: %s\n", cs.c_str() );
+               fprintf( stderr, "<INFO> ---------------------\n" );
+       } else {
+               errors++;
+       }
+
+
+       // -------------- force callback to drive and test ---------------------------------
+
+       fprintf( stderr, "<INFO> load config-file.json for listener coverage testing\n" );
+       c = new xapp::Config( "config-file.json" );                     // set filname with out leading path
+       c->Set_callback( ncb, data );                                           // for coverage in listener
+
+       fprintf( stderr, "<INFO> load ./config-file.json for callback testing\n" );
+       c = new xapp::Config( "./config-file.json" );
+       c->Set_callback( ncb, data );
+
+       fprintf( stderr, "<INFO> sleeping to give callback time to be initialsed\n" );
+       sleep( 4 );
+       if( rename( (char *) "./config-file.json", (char *) "./tmp-config.json" ) == 0 ) {              // rename (should not cause callback)
+               fprintf( stderr, "<INFO> file moved; sleeping a bit\n" );
+               sleep( 3 );
+               errors += fail_if( callback_driven, "callback was driven when file was deleted/moved away" );
+
+               if( rename( (char *) "./tmp-config.json", (char *) "./config-file.json" ) == 0 ) {              // put it back to drive callback
+                       fprintf( stderr, "<INFO> sleeping to give callback time to be driven\n" );
+                       sleep( 3 );
+
+                       errors += fail_if( ! callback_driven, "callback was never executed" );
+               } else {
+                       fprintf( stderr, "<WARN> attempt to move config file back failed: %s\n", strerror( errno ) );
+               }
+       } else {
+               fprintf( stderr, "<WARN> attempt to move config file away failed: %s\n", strerror( errno ) );
+       }
+
+
+       // ----- force errors where we can -----------------------------------------
+       fprintf( stderr, "<INFO> json parse errors expected to be reported now\n" );
+       c = new xapp::Config( "not-there.json" );                                               // json parse will fail
+
+       v = c->Get_control_value( "measurement_interval", 999 );
+       errors += fail_if( v != 999.0, "value from non-existant file wasn't default" );
+
+       s = c->Get_control_str( "ves_collector_address", "no-value" );
+       errors += fail_if( s.compare( "no-value" ) != 0, "string from non-existant file wasn't default" );
+
+       b = c->Get_control_bool( "debug_mode", false );
+       errors += fail_if( b, "boolean from non-existant file wasn't default" );
+
+       s = c->Get_port( "rmr-data-out" );
+       errors += fail_if( !s.empty(), "get port from bad jsonfile returned value" );
+
+       // ---------------------------- end housekeeping ---------------------------
+       announce_results( cb_errors + errors );
+       return !!errors;
+}
index 3b939e3..120bbda 100644 (file)
@@ -45,6 +45,7 @@
 #include "../src/xapp/xapp.hpp"
 
 #include "../src/metrics/metrics.hpp"          // overtly pull the code under test to get coverage opts
+#include "../src/messaging/messenger.cpp"
 #include "../src/metrics/metrics.cpp"
 
 #include "ut_support.cpp"
@@ -54,7 +55,7 @@ int main( int argc, char** argv ) {
        std::shared_ptr<Xapp> x;
        std::shared_ptr<xapp::Metrics> m;
 
-       set_test_name( "jhash_test" );
+       set_test_name( "metrics_test" );
 
        x = std::shared_ptr<Xapp>( new Xapp( "4560", true ) );
        if( x == NULL ) {
index 3e3c5bc..6c9cf31 100644 (file)
@@ -365,7 +365,6 @@ int main( int argc, char** argv ) {
        b = std::move( c );                                             // move = operator
        xapp::Alarm d = std::move( b );                 // move constructor
 
-
        // ------ minimal metrics to prove coverage ------------------------------------------------------------
        metrics( x );
 
index 3e06a53..c845553 100755 (executable)
@@ -1,6 +1,5 @@
 #!/usr/bin/env bash
 # vim: ts=4 sw=4 noet:
-
 #==================================================================================
 #       Copyright (c) 2020 Nokia
 #       Copyright (c) 2020 AT&T Intellectual Property.
@@ -99,6 +98,8 @@ echo "## INFO ##"
 export LD_LIBRARY_PATH=$build_dir:/usr/local/lib:$LD_LIBRARY_PATH
 export LIBRARY_PATH=$build_dir:/usr/local/lib:$LIBRARY_PATH
 
+cp config1.json config-file.json                                               # ensure default named file is there too
+
 make nuke >/dev/null
 make tests >/tmp/PID$$.log 2>&1
 abort_if_error $? "unable to make"
@@ -106,10 +107,18 @@ echo "tests successfully built" >&2
 
 spew="cat"
 
-for x in unit_test jhash_test metrics_test
+#run everything, then generate coverage stats after all have run
+for x in metrics_test jhash_test config_test  unit_test
 do
        ./$x >/tmp/PID$$.log 2>&1
        abort_if_error $? "test failed: $x"
+done
+
+# it seems that we loose coverage reporting if metrics_test's gcov file is generated
+# after unit test.  Very strange. To be safe, run unit_test last.
+#
+for x in metrics_test jhash_test config_test unit_test
+do
        gcov $x.c >/dev/null 2>&1
 done