Combo-app is a sample netconfd-pro server SIL-SA + DB-API subsystem application that provides functionality to send edits and get requests to the server with help of DB-API and at the same time server SIL-SA libraries. It demonstrates how the SIL-SA, DB-API, and YControl libraries can be used together within an application.


If the --getconfig and --filespec parameters are present then the <get-config> operation will be sent about once per second, which the sil-sa-app is also active and processing SIL-SA requests. Use control-C to exit the program.


Refer to How do I use SIL-SA and sil-sa-app? and How do I use DB-API and db-api-app? on how to use and what API are supported for both sil-sa-app and db-api-app. The combo application uses the same set of CLI parameters.


The following code illustrates a sample code that can be used to run combo-app.



/*
 * Copyright (c) 2008 - 2012, Andy Bierman, All Rights Reserved.
 * Copyright (c) 2012 - 2021, YumaWorks, Inc., All Rights Reserved.
 *
 * 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.
 */

/*  FILE sil_sa_app {main}.c

    Distributed SIL Sub-Agent API Sample Main Program
*
*********************************************************************
*                                                                   *
*                     I N C L U D E    F I L E S                    *
*                                                                   *
*********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#ifdef MEMORY_DEBUG
#ifndef MACOSX
#include <mcheck.h>
#endif
#endif

#include "procdefs.h"
#include "agt_sil_lib.h"
#include "db_api.h"
#include "log.h"
#include "sil_sa.h"
#include "status_enum.h"
#include "ycontrol.h"


/********************************************************************
*                                                                   *
*                       C O N S T A N T S                           *
*                                                                   *
*********************************************************************/
#ifdef DEBUG
// #define COMBO_APP_DEBUG 1
// #define COMBO_APP_NOTIF_TEST 1
// #define COMBO_APP_STATLIB_TEST 1
#endif  // DEBUG

/* uncomment to force a usleep in the main loop */
// #define DO_SLEEP 1


/********************************************************************
*                                                                   *
*                          T Y P E S                                *
*                                                                   *
*********************************************************************/


/********************************************************************
*                                                                   *
*                       V A R I A B L E S                           *
*                                                                   *
*********************************************************************/


/********************************************************************
* FUNCTION send_test_edit
*
* This is an example send edit function.
* The module test.yang needs to be loaded for this to work
*********************************************************************/
static void
    send_test_edit (void)
{
    /* EXAMPLE EDIT:
     * TBD: replace with parameters from command line
     */

    /* set 3 top-level leafs at once with test module */
    const xmlChar *path_str = (const xmlChar *)"/";
    const xmlChar *operation_str = (const xmlChar *)"merge";
    const xmlChar *value_str = (const xmlChar *)
        "<config>"
        "<int8.1 xmlns='http://netconfcentral.org/ns/test'>22</int8.1>"
        "<int16.1 xmlns='http://netconfcentral.org/ns/test'>11</int16.1>"
        "<int32.1 xmlns='http://netconfcentral.org/ns/test'>1000</int32.1>"
        "</config>";

    const xmlChar *patch_id_str = NULL;
    boolean system_edit = FALSE;
    const xmlChar *insert_point = NULL;
    const xmlChar *insert_where = NULL;

    status_t res =
        db_api_send_edit_full(path_str,
                              operation_str,
                              value_str,
                              patch_id_str,
                              system_edit,
                              insert_point,
                              insert_where);
    if (res != NO_ERR) {
        log_error("\nSend test edit failed %s %s = %s (%s)\n",
                  operation_str, path_str, value_str,
                  get_error_string(res));
    } else if (LOGDEBUG) {
        log_debug("\nSend test edit OK  %s %s = %s\n",
                  operation_str, path_str, value_str);
    }

}  /* send_test_edit */


/********************************************************************
* FUNCTION send_test_getconfig
*
* Send a <getconfig> request
*********************************************************************/
static void send_test_getconfig (const char *filespec,
                                 boolean withdef,
                                 boolean with_state,
                                 const char *xpath_filter)
{
    status_t res;

    if (xpath_filter || with_state) {
        res = db_api_send_getfilter((const xmlChar *)filespec,
                                    withdef,
                                    !with_state,
                                    (const xmlChar *)xpath_filter);
    } else {
        res = db_api_send_getconfig((const xmlChar *)filespec, withdef);
    }
    if (res != NO_ERR) {
        log_error("\nSend test getconfig failed (%s)\n",
                  get_error_string(res));
    } else if (LOGDEBUG) {
        log_debug("\nSend test getconfig OK\n");
    }

} /* send_test_getconfig */


/********************************************************************
* FUNCTION print_usage
*
* Print the program usage text
*********************************************************************/
static void
    print_usage (void)
{
    log_info("\nUsage:");
    log_info_append("\n   combo-app [--address=string] [--port=num] "
                    "[--subsys-id=string] [--library=name] [--retry-limit=num]");
    log_info_append("\n  [--getconfig [--withdef] [--with-state] "
                    "[--xpath-filter=str] --filespec=/path/to/output.xml]");
    log_info_append("\nUse the --library parameter to select each library "
                    "instead of all SIL-SA libraries");
    log_info_append("\nExample:");
    log_info_append("\n   combo-app --address=192.168.0.10 "
                    "--port=2088");
    log_info_append("\n   Connect to the main server\n\n");

}  /* print_usage */


/********************************************************************
* FUNCTION get_subsys_parm
*
* Check the CLI parameters for the subsys-id parameter needed first
*
* RETURNS:
*   status code
*********************************************************************/
static status_t
    get_subsys_parm (char **argv,
                     char **subsys)
{
    *subsys = NULL;

    if (argv[0] == NULL) {
        return NO_ERR;   // should not happen
    }

    const char *sub = "--subsys=";
    int sub_len = strlen(sub);

    int i = 1;
    for (; argv[i]; i++) {
        char *str = argv[i];
        if (str == NULL) {
            return NO_ERR;
        }

        int reallen = strlen(str);
        if (!strncmp(str, sub, sub_len)) {
            if (reallen > sub_len) {
                *subsys = &str[sub_len];
            } else {
                return ERR_NCX_INVALID_VALUE;
            }
        }
    }

    return NO_ERR;

}  /* get_subsys_parm */


/********************************************************************
* FUNCTION get_silsa_cli_parms
*
* Check the CLI parameters for the getconfig commands
*
* RETURNS:

*   status code
*********************************************************************/
static status_t
    get_silsa_cli_parms (char **argv,
                         char **address,
                         uint16 *port,
                         uint16 *retry_limit)
{
    *address = NULL;
    *port = 0;
    *retry_limit = 0;

    if (argv[0] == NULL) {
        return NO_ERR;   // should not happen
    }

    const char *ad = "--address=";
    const char *po = "--port=";
    const char *li = "--library=";
    const char *rl = "--retry-limit=";

    int ad_len = strlen(ad);
    int po_len = strlen(po);
    int li_len = strlen(li);
    int rl_len = strlen(rl);

    int i = 1;
    for (; argv[i]; i++) {
        char *str = argv[i];
        if (str == NULL) {
            return NO_ERR;
        }

        int reallen = strlen(str);
        if (!strncmp(str, ad, ad_len)) {
            if (reallen > ad_len) {
                *address = &str[ad_len];
            } else {
                return ERR_NCX_INVALID_VALUE;
            }
        } else if (!strncmp(str, po, po_len)) {
            if (reallen > po_len) {
                char *num = &str[po_len];
                int c = atoi(num);
                if (c > 0 && c <= 65535) {
                    *port = (uint16)c;
                } else {
                    return ERR_NCX_INVALID_VALUE;
                }
            } else {
                return ERR_NCX_INVALID_VALUE;
            }
        } else if (!strncmp(str, li, li_len)) {
            if (reallen > li_len) {
                status_t res =
                    sil_sa_add_library_parm((const xmlChar *)&str[li_len]);
                if (res != NO_ERR) {
                    return res;
                }
            } else {
                return ERR_NCX_INVALID_VALUE;
            }
        } else if (!strncmp(str, rl, rl_len)) {
            if (reallen > rl_len) {
                char *num = &str[rl_len];
                int c = atoi(num);
                if (c > 0 && c <= 65535) {
                    *retry_limit = (uint16)c;
                } else {
                    return ERR_NCX_INVALID_VALUE;
                }
            } else {
                return ERR_NCX_INVALID_VALUE;
            }
        }  /* else ignore because probably an NCX CLI parm */
    }

    return NO_ERR;

}  /* get_silsa_cli_parms */


/********************************************************************
* FUNCTION get_dbapi_cli_parms
*
* Check the CLI parameters for the getconfig commands
*
* RETURNS:

*   status code if error connecting or logging into ncxserver
*********************************************************************/
static status_t
    get_dbapi_cli_parms (char **argv,
                         boolean *getconfig,
                         boolean *withdef,
                         boolean *with_state,
                         const char **filespec,
                         const char **xpath_filter,
                         uint32 *count)
{
    *getconfig = false;
    *withdef = false;
    *with_state = false;
    *filespec = NULL;
    *xpath_filter = NULL;
    *count = 0;

    if (argv[0] == NULL) {
        return NO_ERR;   // should not happen
    }

    const char *gc = "--getconfig";
    const char *wd = "--withdef";
    const char *ws = "--with-state";
    const char *fp = "--filespec=";
    const char *xp = "--xpath-filter=";
    const char *cnt = "--count=";

    int fp_len = strlen(fp);
    int xp_len = strlen(xp);
    int cnt_len = strlen(cnt);

    int i = 1;
    for (; argv[i]; i++) {
        char *str = argv[i];
        if (str == NULL) {
            return NO_ERR;
        }

        if (!strcmp(str, gc)) {
            *getconfig = true;
        } else if (!strcmp(str, wd)) {
            *withdef = true;
        } else if (!strcmp(str, ws)) {
            *with_state = true;
        } else if (!strncmp(str, fp, fp_len)) {
            int reallen = strlen(str);
            if (reallen > fp_len) {
                *filespec = &str[fp_len];
            } else {
                return ERR_NCX_INVALID_VALUE;
            }
        } else if (!strncmp(str, xp, xp_len)) {
            int reallen = strlen(str);
            if (reallen > xp_len) {
                /* check if quotes need to be removed */
                char *p = &str[xp_len];
                if ((*p == '"') || (*p == '\'')) {
                    if (str[reallen - 1] == *p) {
                        p++;
                        str[reallen - 1] = 0;
                    } else {
                        return ERR_NCX_INVALID_VALUE;
                    }
                }
                *xpath_filter = p;
            } else {
                return ERR_NCX_INVALID_VALUE;
            }
        } else if (!strncmp(str, cnt, cnt_len)) {
            int reallen = strlen(str);
            if (reallen > cnt_len) {
                char *num = &str[cnt_len];
                int c = atoi(num);
                if (c > 0 && c <= 10000) {
                    *count = (uint32)c;
                } else {
                    return ERR_NCX_INVALID_VALUE;
                }
            } else {
                return ERR_NCX_INVALID_VALUE;
            }
        }

    }

    return NO_ERR;

}  /* get_dbapi_cli_parms */


#ifdef COMBO_APP_STATLIB_TEST

/* extern definitions for the 3 expected callbacks */
AGT_SIL_LIB_EXTERN(test2)

static status_t static_silsa_init (void)
{
    /* example: module=test2;
     * need to use in Makefile (example)
     * STATIC_SILSA=-L /home/andy/silsa -ltest2_sa
     * The actual library names are not needed in this code
     */
    status_t res =
        agt_sil_lib_register_statlib((const xmlChar *)"test2",
                                     y_test2_init,
                                     y_test2_init2,
                                     y_test2_cleanup);

    return res;
}

#endif  // COMBO_APP_STATLIB_TEST


/********************************************************************
* FUNCTION main
*
* This is an example SIL-SA service main function.
*
* RETURNS:
*   0 if NO_ERR
*   status code if error connecting or logging into ncxserver
*********************************************************************/
int main (int argc, char **argv)
{
#ifdef MEMORY_DEBUG
    mtrace();
#endif

    char *subsys = NULL;
    boolean ycontrol_done = FALSE;

    /* need to check for the subsys-id parm before
     * the system is initialized
     */
    status_t res = get_subsys_parm(argv, &subsys);
    if (res != NO_ERR) {
        print_usage();
    }

    /* 1) setup yumapro messaging service profile */
    if (res == NO_ERR) {
        if (subsys == NULL) {
            res = ycontrol_init(argc, argv,
                                (const xmlChar *)"subsys1");
        } else {
            res = ycontrol_init(argc, argv,
                                (const xmlChar *)subsys);
        }
        ycontrol_done = TRUE;
    }


    char *address = NULL;
    uint16 port = 0;
    uint16 retry_limit = 0;

    if (res == NO_ERR) {
        res = get_silsa_cli_parms(argv, &address, &port, &retry_limit);
        if (res != NO_ERR) {
            print_usage();
        }
    }

    boolean getconfig = false;
    boolean withdef = false;
    boolean with_state = false;
    const char *filespec = NULL;
    const char *xpath_filter = NULL;
    uint32 max_count = 0;
    uint32 wait_count = 0;
    uint32 cnt = 0;

    if (res == NO_ERR) {
        res = get_dbapi_cli_parms(argv,
                                  &getconfig,
                                  &withdef,
                                  &with_state,
                                  &filespec,
                                  &xpath_filter,
                                  &max_count);
        if (res != NO_ERR) {
            print_usage();
        }
    }

    if ((res == NO_ERR) && getconfig && (filespec==NULL)) {
        res = ERR_NCX_MISSING_PARM;
        print_usage();
    }

    /* 2) register services with the control layer */
    if (res == NO_ERR) {
        res = sil_sa_register_service();
    }

    if (res == NO_ERR) {
        res = db_api_register_service();
    }

#ifdef COMBO_STATLIB_TEST
    /* 2B) setup any static SIL-SA libraries */
    if (res == NO_ERR) {
        res = static_silsa_init();
    }
#endif  // COMBO_APP_STATLIB_TEST

    /* set the retry limit if provided */
    if ((res == NO_ERR) && (retry_limit > 0)) {
        ycontrol_set_retry_limit(retry_limit);
    }

    /* It is also possible to set the retry_interval but there is
     * no CLI parameter provided for this purposes
     * if (res == NO_ERR) {
     *     ycontrol_set_retry_interval(retry_int_milliseconds);
     * }
     */

    /* 3) do 2nd stage init of the control manager (connect to server) */
    if (res == NO_ERR) {
        if (address) {
            if (port == 0) {
                port = 2023;
            }
            res = ycontrol_init2_ha("server1", address, port);
        } else {
            res = ycontrol_init2();
        }
    }

    boolean done = FALSE;
    boolean test_done = FALSE;
    const xmlChar *error_msg = NULL;

    /* 4) call ycontrol_check_io periodically from the main program
     * control loop
     */
#ifdef COMBO_APP_DEBUG
    int id = 0;
#endif  // COMBO_APP_DEBUG

#ifdef COMBO_APP_NOTIF_TEST
    uint32 loop_cnt = 0;
#endif // COMBO_APP_NOTIF_TEST

    while (!done && (res == NO_ERR)) {
#ifdef COMBO_APP_DEBUG
        if (LOGDEBUG3) {
            log_debug3("\ncombo-app: checking ycontrol IO %d", id++);
        }
#endif  // COMBO_APP_DEBUG

        res = ycontrol_check_io();

        if (ycontrol_shutdown_now()) {
            // YControl has received a <shutdown-event>
            // from the server subsystem is no longer active
            // could ignore or shut down YControl IO loop
            done = TRUE;
            continue;
        }

        // Using sleep to represent other program work; remove for real
#ifdef DO_SLEEP
        if (!done && (res == NO_ERR)) {
            useconds_t usleep_val = 100000;  // 100K micro-sec == 1/10 sec
            (void)usleep(usleep_val);
        }
#endif  // DO_SLEEP

        if (!done && (res == NO_ERR) && db_api_service_ready()) {
            if (test_done) {
                /* check the test edit */
                res = db_api_check_edit_ex(&error_msg);
                if (res == NO_ERR) {
                    log_info("\nTest %u succeeded\n", cnt+1);

                    if (cnt < max_count) {
                        cnt++;

                        log_info("\nStart send edit %u", cnt);
                        send_test_edit();
                    } else {
                        done = TRUE;
                    }
                } else {
                    if (res == ERR_NCX_SKIPPED) {
                        res = ERR_NCX_OPERATION_FAILED;
                    }

                    log_info("\nTest failed with error: %d '%s'\n\n",
                             res, error_msg ? error_msg :
                             (const xmlChar *)get_error_string(res));

                    done = TRUE;
                }
            } else if (++wait_count == 100) {
                wait_count = 0;
                if (getconfig) {
                    send_test_getconfig(filespec,
                                        withdef,
                                        with_state,
                                        xpath_filter);
                } else {
                    /* send the simple test edit */
                    send_test_edit();
                }

                test_done = TRUE;
            }
        }

#ifdef COMBO_APP_NOTIF_TEST
        loop_cnt++;
        if (loop_cnt == 500) {
            sil_sa_notif_test(10, 20, (const xmlChar *)"this is a test");
            loop_cnt = 0;
        }
#endif // COMBO_APP_NOTIF_TEST
    }

    /* 5) cleanup the control layer before exit */
    if (ycontrol_done) {
        ycontrol_cleanup();
    }

#ifdef MEMORY_DEBUG
    muntrace();
#endif

    return (int)res;

}  /* main */


/* END combo_app {main}.c */