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 */