* Regexp improvements

* Added check for libxml in configure';
  * Added clixon_util_regexp utility function
* Yang state get improvements
  * Integrated state and config into same tree on retrieval, not separate trees
  * Added cli functions `cli_show_config_state()` and `cli_show_auto_state()` for showing combined config and state info.
  * Added integrated state in the main example: `interface/oper-state`.
  * Added performance tests for getting state, see [test/test_perf_state.sh].
This commit is contained in:
Olof hagsand 2019-05-20 16:03:29 +02:00
parent c17784cfe9
commit bc54f2d04c
26 changed files with 895 additions and 185 deletions

View file

@ -139,6 +139,14 @@
### Minor changes
* Regexp improvements
* Added check for libxml in configure';
* Added clixon_util_regexp utility function
* Yang state get improvements
* Integrated state and config into same tree on retrieval, not separate trees
* Added cli functions `cli_show_config_state()` and `cli_show_auto_state()` for showing combined config and state info.
* Added integrated state in the main example: `interface/oper-state`.
* Added performance tests for getting state, see [test/test_perf_state.sh].
* Improved submodule implementation (as part of [Yang submodule import prefix restrictions #60](https://github.com/clicon/clixon/issues/60)).
* Submodules share same namespace as modules, which means that functions looking for symbols under a module were extended to also look in that module's included submodules, also recursively (submodules can include submodules in Yang 1.0).
* Submodules are no longer merged with modules in the code. This is necessary to have separate local import prefixes, for example.

View file

@ -260,7 +260,10 @@ client_statedata(clicon_handle h,
goto done;
if (ret == 0)
goto fail;
/* Code complex to filter out anything that is outside of xpath */
/* Code complex to filter out anything that is outside of xpath
* Actually this is a safety catch, should realy be done in plugins
* and modules_state functions.
*/
if (xpath_vec(*xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* If vectors are specified then mark the nodes found and

View file

@ -138,6 +138,8 @@ clixon_plugin_statedata(clicon_handle h,
goto done;
if (fn(h, xpath, x) < 0)
goto fail; /* Dont quit here on user callbacks */
if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
goto done;
if (ret == 0)

View file

@ -381,24 +381,27 @@ show_yang(clicon_handle h,
return 0;
}
/*! Generic show configuration CLIGEN callback
* Utility function used by cligen spec file
/*! Show configuration and state internal function
*
* @param[in] h CLICON handle
* @param[in] state If set, show both config and state, otherwise only config
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] argv String vector: <dbname> <format> <xpath> [<varname>]
* Format of argv:
* <dbname> "running"|"candidate"|"startup"
* <dbname> "running"|"candidate"|"startup" # note only running state=1
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression, that may contain one %, eg "/sender[name="%s"]"
* <varname> optional name of variable in cvv. If set, xpath must have a '%s'
* @code
* show config id <n:string>, cli_show_config("running","xml","iface[name="%s"]","n");
* @endcode
* @note if state parameter is set, then db must be running
*/
int
cli_show_config(clicon_handle h,
cvec *cvv,
cvec *argv)
static int
cli_show_config1(clicon_handle h,
int state,
cvec *cvv,
cvec *argv)
{
int retval = -1;
char *db;
@ -462,9 +465,19 @@ cli_show_config(clicon_handle h,
}
else
cprintf(cbxpath, "%s", xpath);
/* Get configuration from database */
if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), &xt) < 0)
goto done;
if (state == 0){ /* Get configuration-only from database */
if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), &xt) < 0)
goto done;
}
else { /* Get configuration and state from database */
if (strcmp(db, "running") != 0){
clicon_err(OE_FATAL, 0, "Show state only for running database, not %s", db);
goto done;
}
if (clicon_rpc_get(h, cbuf_get(cbxpath), &xt) < 0)
goto done;
}
if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
clicon_rpc_generate_error("Get configuration", xerr);
goto done;
@ -518,6 +531,52 @@ done:
return retval;
}
/*! Show configuration and state CLIGEN callback function
*
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] argv String vector: <dbname> <format> <xpath> [<varname>]
* Format of argv:
* <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression, that may contain one %, eg "/sender[name="%s"]"
* <varname> optional name of variable in cvv. If set, xpath must have a '%s'
* @code
* show config id <n:string>, cli_show_config("running","xml","iface[name="%s"]","n");
* @endcode
* @see cli_show_config_state For config and state data (not only config)
*/
int
cli_show_config(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return cli_show_config1(h, 0, cvv, argv);
}
/*! Show configuration and state CLIgen callback function
*
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] argv String vector: <dbname> <format> <xpath> [<varname>]
* Format of argv:
* <dbname> "running"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression, that may contain one %, eg "/sender[name="%s"]"
* <varname> optional name of variable in cvv. If set, xpath must have a '%s'
* @code
* show config id <n:string>, cli_show_config_state("running","xml","iface[name="%s"]","n");
* @endcode
* @see cli_show_config For config-only, no state
*/
int
cli_show_config_state(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return cli_show_config1(h, 1, cvv, argv);
}
/*! Show configuration as text given an xpath
* Utility function used by cligen spec file
* @param[in] h CLICON handle
@ -583,15 +642,21 @@ int cli_show_version(clicon_handle h,
}
/*! Generic show configuration CLIGEN callback using generated CLI syntax
* @param[in] h CLICON handle
* @param[in] state If set, show both config and state, otherwise only config
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] argv String vector: <dbname> <format> <xpath> [<varname>]
* Format of argv:
* <api_path_fmt> Generated API PATH
* <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* @note if state parameter is set, then db must be running
*/
int
cli_show_auto(clicon_handle h,
cvec *cvv,
cvec *argv)
static int
cli_show_auto1(clicon_handle h,
int state,
cvec *cvv,
cvec *argv)
{
int retval = 1;
yang_stmt *yspec;
@ -635,9 +700,19 @@ cli_show_auto(clicon_handle h,
if (xpath[strlen(xpath)-1] == '/')
xpath[strlen(xpath)-1] = '\0';
/* Get configuration from database */
if (clicon_rpc_get_config(h, db, xpath, &xt) < 0)
goto done;
if (state == 0){ /* Get configuration-only from database */
if (clicon_rpc_get_config(h, db, xpath, &xt) < 0)
goto done;
}
else{ /* Get configuration and state from database */
if (strcmp(db, "running") != 0){
clicon_err(OE_FATAL, 0, "Show state only for running database, not %s", db);
goto done;
}
if (clicon_rpc_get(h, xpath, &xt) < 0)
goto done;
}
if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){
clicon_rpc_generate_error("Get configuration", xerr);
goto done;
@ -674,4 +749,33 @@ cli_show_auto(clicon_handle h,
return retval;
}
/*! Generic show configuration CLIgen callback using generated CLI syntax
* Format of argv:
* <api_path_fmt> Generated API PATH
* <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* @see cli_show_auto_state For config and state
*/
int
cli_show_auto(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return cli_show_auto1(h, 0, cvv, argv);
}
/*! Generic show config and state CLIgen callback using generated CLI syntax
* Format of argv:
* <api_path_fmt> Generated API PATH
* <dbname> "running"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* @see cli_show_auto For config only
*/
int
cli_show_auto_state(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return cli_show_auto1(h, 1, cvv, argv);
}

View file

@ -138,9 +138,12 @@ int show_yang(clicon_handle h, cvec *vars, cvec *argv);
int show_conf_xpath(clicon_handle h, cvec *cvv, cvec *argv);
int cli_show_config(clicon_handle h, cvec *cvv, cvec *argv);
int cli_show_config_state(clicon_handle h, cvec *cvv, cvec *argv);
int cli_show_auto(clicon_handle h, cvec *cvv, cvec *argv);
int cli_show_state_auto(clicon_handle h, cvec *cvv, cvec *argv);
#endif /* _CLIXON_CLI_API_H_ */

48
configure vendored
View file

@ -4417,6 +4417,54 @@ _ACEOF
fi
# This is for Libxml2 code which we can use for XSD regexp
# AC_CHECK_HEADERS([libxml2/libxml/xmlexports.h])
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for xmlRegexpCompile in -lxml2" >&5
$as_echo_n "checking for xmlRegexpCompile in -lxml2... " >&6; }
if ${ac_cv_lib_xml2_xmlRegexpCompile+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-lxml2 $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char xmlRegexpCompile ();
int
main ()
{
return xmlRegexpCompile ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_lib_xml2_xmlRegexpCompile=yes
else
ac_cv_lib_xml2_xmlRegexpCompile=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_xml2_xmlRegexpCompile" >&5
$as_echo "$ac_cv_lib_xml2_xmlRegexpCompile" >&6; }
if test "x$ac_cv_lib_xml2_xmlRegexpCompile" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_LIBXML2 1
_ACEOF
LIBS="-lxml2 $LIBS"
fi
for ac_func in inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`

View file

@ -220,6 +220,10 @@ AC_CHECK_LIB(socket, socket)
AC_CHECK_LIB(nsl, xdr_char)
AC_CHECK_LIB(dl, dlopen)
# This is for Libxml2 code which we can use for XSD regexp
# AC_CHECK_HEADERS([libxml2/libxml/xmlexports.h])
AC_CHECK_LIB(xml2, xmlRegexpCompile)
AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort)
# CLIXON_DATADIR is where clixon installs the "system" yang files in yang/Makfile

View file

@ -264,7 +264,7 @@ example_copy_extra(clicon_handle h, /* Clicon handle */
/*! Called to get state data from plugin
* @param[in] h Clicon handle
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] xtop XML tree, <config/> on entry.
* @param[in] xstate XML tree, <config/> on entry.
* @retval 0 OK
* @retval -1 Error
* @see xmldb_get
@ -285,13 +285,34 @@ example_statedata(clicon_handle h,
{
int retval = -1;
cxobj **xvec = NULL;
size_t xlen;
cbuf *cb = cbuf_new();
int i;
cxobj *xt = NULL;
char *name;
if (!_state)
goto ok;
/* Example of (static) statedata, real code would poll state
* Note this state needs to be accomanied by yang snippet
* above
*/
/* Example of statedata, in this case merging state data with
* state information. In this case adding dummy interface operation state
* to configured interfaces.
* Get config according to xpath */
if (xmldb_get(h, "running", xpath, 1, &xt, NULL) < 0)
goto done;
/* From that subset, only get the names */
if (xpath_vec(xt, "/interfaces/interface/name", &xvec, &xlen) < 0)
goto done;
if (xlen){
cprintf(cb, "<interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">");
for (i=0; i<xlen; i++){
name = xml_body(xvec[i]);
cprintf(cb, "<interface><name>%s</name><oper-status>up</oper-status></interface>", name);
}
cprintf(cb, "</interfaces>");
if (xml_parse_string(cbuf_get(cb), NULL, &xstate) < 0)
goto done;
}
if (xml_parse_string("<state xmlns=\"urn:example:clixon\">"
"<op>42</op>"
"<op>41</op>"
@ -301,6 +322,10 @@ example_statedata(clicon_handle h,
ok:
retval = 0;
done:
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
if (xvec)
free(xvec);
return retval;

View file

@ -24,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
}
copy("Copy and create a new object") {
interface("Copy interface"){
<name:string expand_dbvar("candidate","/ietf-interfaces:interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname");
(<name:string>|<name:string expand_dbvar("candidate","/ietf-interfaces:interfaces/interface=%s/name")>("name of interface to copy from")) to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname");
}
}
discard("Discard edits (rollback 0)"), discard_changes();
@ -37,6 +37,11 @@ show("Show a particular state of the system"){
xml("Show comparison in xml"), compare_dbs((int32)0);
text("Show comparison in text"), compare_dbs((int32)1);
}
state("Show configuration and state"), cli_show_config_state("running", "text", "/"){
xml("Show configuration and state as XML"), cli_show_config_state("candidate", "xml", "/");{
@datamodel, cli_show_auto_state("running", "xml");
}
}
configuration("Show configuration"), cli_show_config("candidate", "text", "/");{
xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{
@datamodel, cli_show_auto("candidate", "xml");

View file

@ -57,6 +57,9 @@
/* Define to 1 if you have the `socket' library (-lsocket). */
#undef HAVE_LIBSOCKET
/* Define to 1 if you have the `xml2' library (-lxml2). */
#undef HAVE_LIBXML2
/* Define to 1 if you have the <linux/if_vlan.h> header file. */
#undef HAVE_LINUX_IF_VLAN_H

View file

@ -69,7 +69,6 @@
#include <sys/param.h>
#include <netinet/in.h>
#include <libgen.h>
#include <regex.h>
/* cligen */
#include <cligen/cligen.h>
@ -3323,6 +3322,12 @@ ys_parse_sub(yang_stmt *ys,
cv_uint32_set(ys->ys_cv, minmax);
}
break;
case Y_MODIFIER:
if (strcmp(yang_argument_get(ys), "invert-match")){
clicon_err(OE_YANG, EINVAL, "modifier %s, expected invert-match", yang_argument_get(ys));
goto done;
}
break;
case Y_UNKNOWN: /* XXX This code assumes ymod already loaded
but it may not be */
if (extra == NULL)

View file

@ -75,6 +75,7 @@
#include "clixon_data.h"
#include "clixon_plugin.h"
#include "clixon_netconf_lib.h"
#include "clixon_xml_map.h"
#include "clixon_yang_module.h"
#include "clixon_yang_internal.h" /* internal */
@ -289,11 +290,16 @@ yang_modules_state_get(clicon_handle h,
cxobj *x1;
cbuf *cb = NULL;
int ret;
cxobj **xvec = NULL;
size_t xlen;
int i;
msid = clicon_option_str(h, "CLICON_MODULE_SET_ID");
if ((x = clicon_modst_cache_get(h, brief)) != NULL){
/* x is here: <modules-state>...
* and x is original tree, need to copy */
if ((x = xml_wrap(x, "top")) < 0)
goto done;
if (xpath_first(x, "%s", xpath)){
if ((x1 = xml_dup(x)) == NULL)
goto done;
@ -301,6 +307,9 @@ yang_modules_state_get(clicon_handle h,
}
else
x = NULL;
/* unwrap */
if (x && xml_rootchild(x, 0, &x) < 0)
goto done;
}
else { /* No cache -> build the tree */
if ((cb = cbuf_new()) == NULL){
@ -322,10 +331,21 @@ yang_modules_state_get(clicon_handle h,
if (clicon_modst_cache_set(h, brief, x) < 0) /* move to fn above? */
goto done;
}
if (x){
/* Wrap x (again) with new top-level node "top" which merge wants */
if (x){ /* x is here a copy (This code is ugly and I think wrong) */
/* Wrap x (again) with new top-level node "top" which xpath wants */
if ((x = xml_wrap(x, "top")) < 0)
goto done;
/* extract xpath part of module-state tree */
if (xpath_vec(x, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
if (xvec != NULL){
for (i=0; i<xlen; i++)
xml_flag_set(xvec[i], XML_FLAG_MARK);
}
/* Remove everything that is not marked */
if (xml_tree_prune_flagged_sub(x, XML_FLAG_MARK, 1, NULL) < 0)
goto done;
if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
goto done;
if (ret == 0)
@ -334,6 +354,8 @@ yang_modules_state_get(clicon_handle h,
retval = 1;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (xvec)
free(xvec);
if (cb)
cbuf_free(cb);
if (x)

View file

@ -51,7 +51,8 @@ struct ys_stack{
};
struct clicon_yang_yacc_arg{ /* XXX: mostly unrelevant */
char *yy_name; /* Name of syntax (for error string) */
char *yy_name; /* Name of syntax, typically filename
(for error string) */
int yy_linenum; /* Number of \n in parsed buffer */
char *yy_parse_string; /* original (copy of) parse string */
void *yy_lexbuf; /* internal parse buffer from lex */

View file

@ -49,7 +49,7 @@ cat <<EOF > $cfg
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>scaling</CLICON_YANG_MODULE_MAIN>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
@ -61,26 +61,27 @@ EOF
sudo callgrind_control -i off
new "test params: -f $cfg -y $fyang"
new "test params: -f $cfg
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
start_backend -s init -f $cfg -y $fyang
new "start backend -s init -f $cfg
start_backend -s init -f $cfg
fi
new "kill old restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
new "start restconf daemon"
start_restconf -f $cfg -y $fyang
start_restconf -f $cfg
new "waiting"
sleep $RCWAIT
wait_backend
wait_restconf
new "generate 'large' config with $perfnr list entries"
echo -n "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\">" > $fconfig
@ -91,16 +92,18 @@ echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig
# Now take large config file and write it via netconf to candidate
new "netconf write large config"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Now commit it from candidate to running
new "netconf commit large config"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Zero all event counters
sudo callgrind_control -i on
sudo callgrind_control -z
while [ 1 ] ; do
new "restconf add $perfreq small config"

View file

@ -7,7 +7,6 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
fyang=$dir/ietf-ip.yang
# If set, enable debugging (of backend)
: ${clixon_util_datastore:=clixon_util_datastore}
cat <<EOF > $fyang

View file

@ -33,7 +33,7 @@ cat <<EOF > $cfg
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>$APPNAME</CLICON_YANG_MODULE_MAIN>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
@ -73,52 +73,54 @@ module example{
}
EOF
new "test params: -f $cfg -y $fyang"
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
start_backend -s init -f $cfg -y $fyang
new "start backend -s init -f $cfg"
start_backend -s init -f $cfg
new "waiting"
sleep $RCWAIT
fi
new "cli enabled feature"
expectfn "$clixon_cli -1f $cfg -y $fyang set x foo" 0 ""
expectfn "$clixon_cli -1f $cfg set x foo" 0 ""
new "cli disabled feature"
expectfn "$clixon_cli -1f $cfg -l o -y $fyang set y foo" 255 "CLI syntax error: \"set y foo\": Unknown command"
expectfn "$clixon_cli -1f $cfg -l o set y foo" 255 "CLI syntax error: \"set y foo\": Unknown command"
new "cli enabled feature in other module"
expectfn "$clixon_cli -1f $cfg -y $fyang set routing router-id 1.2.3.4" 0 ""
expectfn "$clixon_cli -1f $cfg set routing router-id 1.2.3.4" 0 ""
new "cli disabled feature in other module"
expectfn "$clixon_cli -1f $cfg -l o -y $fyang set routing ribs rib default-rib false" 255 "CLI syntax error: \"set routing ribs rib default-rib false\": Unknown command"
expectfn "$clixon_cli -1f $cfg -l o set routing ribs rib default-rib false" 255 "CLI syntax error: \"set routing ribs rib default-rib false\": Unknown command"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf enabled feature"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon">foo</x></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon">foo</x></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate enabled feature"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf disabled feature"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><y xmlns="urn:example:clixon">foo</y></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>y</bad-element></error-info><error-severity>error</error-severity><error-message>Unassigned yang spec</error-message></rpc-error></rpc-reply>]]>]]>$'
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><y xmlns="urn:example:clixon">foo</y></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>unknown-element</error-tag><error-info><bad-element>y</bad-element></error-info><error-severity>error</error-severity><error-message>Unassigned yang spec</error-message></rpc-error></rpc-reply>]]>]]>$'
# This test has been broken up into all different modules instead of one large
# reply since the modules change so often
new "netconf schema resource, RFC 7895"
ret=$($clixon_netconf -qf $cfg -y $fyang<<EOF
ret=$($clixon_netconf -qf $cfg<<EOF
<rpc><get><filter type="xpath" select="modules-state/module" xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"/></get></rpc>]]>]]>
EOF
)
)
#echo $ret
new "netconf modules-state header"
expect='^<rpc-reply><data><modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"><module><name>'
match=`echo "$ret" | grep -GZo "$expect"`

View file

@ -29,6 +29,7 @@ cat <<EOF > $cfg
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
@ -151,7 +152,7 @@ new "waiting"
sleep $RCWAIT
new "auth get"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data)" 0 '{"data": {"clixon-example:state": {"op": ["42","41","43"]}}}
'
new "Set x to 0"

View file

@ -42,6 +42,7 @@ cat <<EOF > $cfg
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>

View file

@ -159,10 +159,10 @@ new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit state operation should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><state xmlns="urn:example:clixon"><op>42</op></state></config></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>invalid-value</error-tag>"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>e0</name><oper-status>up</oper-status></interface></interfaces></config></edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>invalid-value</error-tag><error-severity>error</error-severity><error-message>State data not allowed</error-message></rpc-error></rpc-reply>]]>]]>"
new "netconf get state operation"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get><filter type=\"xpath\" select=\"/state\"/></get></rpc>]]>]]>" '^<rpc-reply><data><state xmlns="urn:example:clixon"><op>42</op><op>41</op><op>43</op></state></data></rpc-reply>]]>]]>$'
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><get><filter type=\"xpath\" select=\"/interfaces\"/></get></rpc>]]>]]>" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth1</name><type>ex:eth</type><enabled>true</enabled><oper-status>up</oper-status></interface></interfaces></data></rpc-reply>]]>]]>$'
new "netconf lock/unlock"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]><rpc><unlock><target><candidate/></target></unlock></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><rpc-reply><ok/></rpc-reply>]]>]]>$"

View file

@ -1,5 +1,8 @@
#!/bin/bash
# Scaling/ performance tests
# CLI/Netconf/Restconf
# Lists (and leaf-lists)
# Add, get and delete entries
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -8,7 +11,7 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
: ${format:=xml}
# Number of list/leaf-list entries in file
: ${perfnr:=10000}
: ${perfnr:=5000}
# Number of requests made get/put
: ${perfreq:=100}
@ -20,7 +23,6 @@ fyang=$dir/scaling.yang
fconfig=$dir/large.xml
fconfig2=$dir/large2.xml
cat <<EOF > $fyang
module scaling{
yang-version 1.1;
@ -48,7 +50,7 @@ cat <<EOF > $cfg
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>scaling</CLICON_YANG_MODULE_MAIN>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/example/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
@ -65,89 +67,27 @@ cat <<EOF > $cfg
</clixon-config>
EOF
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
fi
# First generate large XML file
# Use it latter to generate startup-db in xml, tree formats
tmpx=$dir/tmp.xml
new "generate large startup config ($tmpx) with $perfnr entries"
echo -n "<config><x xmlns=\"urn:example:clixon\">" > $tmpx
for (( i=0; i<$perfnr; i++ )); do
echo -n "<y><a>$i</a><b>$i</b></y>" >> $tmpx
done
echo "</x></config>" >> $tmpx
if false; then
# Then generate large JSON file (cant translate namespace - long story)
tmpj=$dir/tmp.json
new "generate large startup config ($tmpj) with $perfnr entries"
echo -n '{"config": {"scaling:x":{"y":[' > $tmpj
for (( i=0; i<$perfnr; i++ )); do
if [ $i -ne 0 ]; then
echo -n ",{\"a\":$i,\"b\":$i}" >> $tmpj
else
echo -n "{\"a\":$i,\"b\":$i}" >> $tmpj
fi
done
echo "]}}}" >> $tmpj
fi
# Loop over mode and format
for mode in startup running; do
file=$dir/${mode}_db
for format in tree xml; do # json - something w namespaces
sudo rm -f $file
sudo touch $file
sudo chmod 666 $file
case $format in
xml)
echo "cp $tmpx $file"
cp $tmpx $file
;;
json)
cp $tmpj $file
;;
tree)
echo "clixon_util_datastore -d ${mode} -f tree -y $fyang -b $dir -x $tmpx put create"
clixon_util_datastore -d ${mode} -f tree -y $fyang -b $dir -x $tmpx put create
;;
esac
new "Startup format: $format mode:$mode"
# echo "time sudo $clixon_backend -F1 -D $DBG -s $mode -f $cfg -y $fyang -o CLICON_XMLDB_FORMAT=$format"
# Cannot use start_backend here due to expected error case
{ time -p sudo $clixon_backend -F1 -D $DBG -s $mode -f $cfg -y $fyang -o CLICON_XMLDB_FORMAT=$format 2> /dev/null; } 2>&1 | awk '/real/ {print $2}'
done
done
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
start_backend -s init -f $cfg -y $fyang
new "start backend -s init -f $cfg -- -s"
start_backend -s init -f $cfg -- -s
fi
new "kill old restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
new "start restconf daemon"
start_restconf -f $cfg -y $fyang
start_restconf -f $cfg
new "waiting"
sleep $RCWAIT
wait_backend
wait_restconf
new "generate 'large' config with $perfnr list entries"
echo -n "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\">" > $fconfig
@ -158,63 +98,106 @@ echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig
# Now take large config file and write it via netconf to candidate
new "netconf write large config"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Here, there are $perfnr entries in candidate
new "netconf write large config again"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Now commit it from candidate to running
new "netconf commit large config"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Now commit it again from candidate (validation takes time when
# comparing to existing)
new "netconf commit large config again"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Having a large db, get and put single entries many times
# Note same entries in the range alreayd there, db has same size
new "netconf add $perfreq small config"
time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
echo "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\"><y><a>$rnd</a><b>$rnd</b></y></x></config></edit-config></rpc>]]>]]>"
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
# Note same entries in the range alreay there, db has same size
# NETCONF get
new "netconf get $perfreq small config"
time -p for (( i=0; i<$perfreq; i++ )); do
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
echo "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/y[a=$rnd][b=$rnd]\" /></get-config></rpc>]]>]]>"
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
# NETCONF add
new "netconf add $perfreq small config"
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
echo "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\"><y><a>$rnd</a><b>$rnd</b></y></x></config></edit-config></rpc>]]>]]>"
done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
# RESTCONF get
new "restconf get $perfreq small config"
time -p for (( i=0; i<$perfreq; i++ )); do
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
curl -sG http://localhost/restconf/data/scaling:x/y=$rnd > /dev/null
done
done } 2>&1 | awk '/real/ {print $2}'
# RESTCONF put
# Reference:
# i686 format=xml perfnr=10000/100 time: 38/29s 20190425 WITH/OUT startup copying
# i686 format=tree perfnr=10000/100 time: 72/64s 20190425 WITH/OUT startup copying
new "restconf add $perfreq small config"
time -p for (( i=0; i<$perfreq; i++ )); do
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
curl -s -X PUT http://localhost/restconf/data/scaling:x/y=$rnd -d '{"scaling:y":{"a":"'$rnd'","b":"'$rnd'"}}'
done
done } 2>&1 | awk '/real/ {print $2}'
# CLI get
new "cli get $perfreq small config"
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
$clixon_cli -1 -f $cfg show conf xml x y $rnd > /dev/null
done } 2>&1 | awk '/real/ {print $2}'
# CLI add
new "cli add $perfreq small config"
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
$clixon_cli -1 -f $cfg set x y $rnd b $rnd
done } 2>&1 | awk '/real/ {print $2}'
# Instead of many small entries, get one large in netconf and restconf
# cli?
new "netconf get large config"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><x xmlns="urn:example:clixon"><y><a>0</a><b>0</b></y><y><a>1</a><b>1</b></y><y><a>2</a><b>2</b></y><y><a>3</a><b>3</b></y>'
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><x xmlns="urn:example:clixon"><y><a>0</a><b>0</b></y><y><a>1</a><b>1</b></y><y><a>2</a><b>2</b></y><y><a>3</a><b>3</b></y>'
new "restconf get large config"
expecteof "/usr/bin/time -f %e curl -sG http://localhost/restconf/data" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^{"data": {"scaling:x": {"y": \[{"a": 0,"b": 0},{ "a": 1,"b": 1},{ "a": 2,"b": 2},{ "a": 3,"b": 3},'
/usr/bin/time -f %e curl -sG http://localhost/restconf/data > /dev/null
new "cli get large config"
/usr/bin/time -f %e $clixon_cli -1f $cfg show config xml> /dev/null
# Delete entries (last since entries are removed from db)
# netconf
new "cli delete $perfreq small config"
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
$clixon_cli -1 -f $cfg delete x y $rnd
done } 2>&1 | awk '/real/ {print $2}'
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf delete $perfreq small config"
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
echo "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\"><y operation=\"delete\"><a>$rnd</a></y></x></config></edit-config></rpc>]]>]]>"
done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "restconf delete $perfreq small config"
time -p for (( i=0; i<$perfreq; i++ )); do
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
curl -s -X DELETE http://localhost/restconf/data/scaling:x/y=$rnd
done
done > /dev/null; } 2>&1 | awk '/real/ {print $2}'
exit
# Now do leaf-lists istead of leafs
new "generate large leaf-list config"
@ -225,25 +208,25 @@ done
echo "</x></config></edit-config></rpc>]]>]]>" >> $fconfig2
new "netconf replace large list-leaf config"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig2" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg" "$fconfig2" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit large leaf-list config"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf add $perfreq small leaf-list config"
time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
echo "<rpc><edit-config><target><candidate/></target><config><x xmlns=\"urn:example:clixon\"><c>$rnd</c></x></config></edit-config></rpc>]]>]]>"
done | $clixon_netconf -qf $cfg -y $fyang > /dev/null
done | $clixon_netconf -qf $cfg > /dev/null
new "netconf add small leaf-list config"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon"><c>x</c></x></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon"><c>x</c></x></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit small leaf-list config"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get large leaf-list config"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><x xmlns="urn:example:clixon"><c>0</c><c>1</c>'
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" '^<rpc-reply><data><x xmlns="urn:example:clixon"><c>0</c><c>1</c>'
new "Kill restconf daemon"
stop_restconf

121
test/test_perf_startup.sh Executable file
View file

@ -0,0 +1,121 @@
#!/bin/bash
# Startup performance tests for different formats and startup modes.
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
# Number of list/leaf-list entries in file
: ${perfnr:=10000}
APPNAME=example
cfg=$dir/scaling-conf.xml
fyang=$dir/scaling.yang
cat <<EOF > $fyang
module scaling{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ip;
container x {
list y {
key "a";
leaf a {
type int32;
}
leaf b {
type int32;
}
}
leaf-list c {
type string;
}
}
}
EOF
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/example/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY>
<CLICON_CLI_MODE>example</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/example/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/example/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_CLI_GENMODEL_TYPE>VARS</CLICON_CLI_GENMODEL_TYPE>
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
</clixon-config>
EOF
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
fi
# First generate large XML file
# Use it latter to generate startup-db in xml, tree formats
tmpx=$dir/tmp.xml
new "generate large startup config ($tmpx) with $perfnr entries"
echo -n "<config><x xmlns=\"urn:example:clixon\">" > $tmpx
for (( i=0; i<$perfnr; i++ )); do
echo -n "<y><a>$i</a><b>$i</b></y>" >> $tmpx
done
echo "</x></config>" >> $tmpx
if false; then # XXX JSON dont work as datastore yet
# Then generate large JSON file (cant translate namespace - long story)
tmpj=$dir/tmp.json
new "generate large startup config ($tmpj) with $perfnr entries"
echo -n '{"config": {"scaling:x":{"y":[' > $tmpj
for (( i=0; i<$perfnr; i++ )); do
if [ $i -ne 0 ]; then
echo -n ",{\"a\":$i,\"b\":$i}" >> $tmpj
else
echo -n "{\"a\":$i,\"b\":$i}" >> $tmpj
fi
done
echo "]}}}" >> $tmpj
fi
# Loop over mode and format
for mode in startup running; do
file=$dir/${mode}_db
for format in tree xml; do # json - something w namespaces
sudo rm -f $file
sudo touch $file
sudo chmod 666 $file
case $format in
xml)
echo "cp $tmpx $file"
cp $tmpx $file
;;
json)
cp $tmpj $file
;;
tree)
echo "clixon_util_datastore -d ${mode} -f tree -y $fyang -b $dir -x $tmpx put create"
clixon_util_datastore -d ${mode} -f tree -y $fyang -b $dir -x $tmpx put create
;;
esac
new "Startup format: $format mode:$mode"
# echo "time sudo $clixon_backend -F1 -D $DBG -s $mode -f $cfg -y $fyang -o CLICON_XMLDB_FORMAT=$format"
# Cannot use start_backend here due to expected error case
{ time -p sudo $clixon_backend -F1 -D $DBG -s $mode -f $cfg -y $fyang -o CLICON_XMLDB_FORMAT=$format 2> /dev/null; } 2>&1 | awk '/real/ {print $2}'
done
done
rm -rf $dir

157
test/test_perf_state.sh Executable file
View file

@ -0,0 +1,157 @@
#!/bin/bash
# Scaling/ performance tests
# Config + state data, only get
# Restconf/Netconf/CLI
# Use mixed ietf-interfaces config+state
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
# Which format to use as datastore format internally
: ${format:=xml}
# Number of list/leaf-list entries in file (cant be less than 2)
: ${perfnr:=1000}
# Number of requests made get/put
: ${perfreq:=100}
APPNAME=example
cfg=$dir/config.xml
fconfig=$dir/large.xml
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>clixon-example</CLICON_YANG_MODULE_MAIN>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/example/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY>
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
<CLICON_CLI_MODE>example</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/example/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/example/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_CLI_GENMODEL_TYPE>VARS</CLICON_CLI_GENMODEL_TYPE>
<CLICON_CLI_LINESCROLLING>0</CLICON_CLI_LINESCROLLING>
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
</clixon-config>
EOF
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -- -s"
start_backend -s init -f $cfg -- -s
fi
new "kill old restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
new "start restconf daemon"
start_restconf -f $cfg
new "waiting"
wait_backend
wait_restconf
new "generate 'large' config with $perfnr list entries"
echo -n "<rpc><edit-config><target><candidate/></target><config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">" > $fconfig
for (( i=0; i<$perfnr; i++ )); do
echo -n "<interface><name>e$i</name><type>ex:eth</type></interface>" >> $fconfig
done
echo "</interfaces></config></edit-config></rpc>]]>]]>" >> $fconfig
# Now take large config file and write it via netconf to candidate
new "netconf write large config"
expecteof_file "/usr/bin/time -f %e $clixon_netconf -qf $cfg" "$fconfig" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Now commit it from candidate to running
new "netconf commit large config"
expecteof "/usr/bin/time -f %e $clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# START actual tests
# Having a large db, get single entries many times
# NETCONF get
new "netconf get test single req"
sel="/ietf-interfaces:interfaces/interface[name='e1']"
msg="<rpc><get><filter type=\"xpath\" select=\"$sel\" /></get></rpc>]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$msg" '^<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>e1</name><type>ex:eth</type><enabled>true</enabled><oper-status>up</oper-status></interface></interfaces></data></rpc-reply>]]>]]>$'
new "netconf get $perfreq single reqs"
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
sel="/ietf-interfaces:interfaces/interface[name='e$rnd']"
echo "<rpc><get><filter type=\"xpath\" select=\"$sel\" /></get></rpc>]]>]]>"
done | $clixon_netconf -qf $cfg > /dev/null; } 2>&1 | awk '/real/ {print $2}'
# RESTCONF get
new "restconf get test single req"
expecteq "$(curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e1)" 0 '{"ietf-interfaces:interface": [{"name": "e1","type": "ex:eth","enabled": true,"oper-status": "up"}]}
'
new "restconf get $perfreq single reqs"
#echo "curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e0"
#curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e67
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces/interface=e$rnd > /dev/null
done } 2>&1 | awk '/real/ {print $2}'
# CLI get
new "cli get test single req"
expectfn "$clixon_cli -1 -1f $cfg -l o show state xml interfaces interface e1" 0 '^<interface>
<name>e1</name>
<type>ex:eth</type>
<enabled>true</enabled>
<oper-status>up</oper-status>
</interface>$'
new "cli get $perfreq single reqs"
{ time -p for (( i=0; i<$perfreq; i++ )); do
rnd=$(( ( RANDOM % $perfnr ) ))
$clixon_cli -1 -f $cfg show state xml interfaces interface e$rnd > /dev/null
done } 2>&1 | awk '/real/ {print $2}'
# Get config in one large get
new "netconf get large config"
/usr/bin/time -f %e echo "<rpc><get> <filter type=\"xpath\" select=\"/ietf-interfaces:interfaces\" /></get></rpc>]]>]]>" | $clixon_netconf -qf $cfg > /tmp/netconf
new "restconf get large config"
/usr/bin/time -f %e curl -sG http://localhost/restconf/data/ietf-interfaces:interfaces | wc
new "cli get large config"
/usr/bin/time -f %e $clixon_cli -1f $cfg show state xml interfaces | wc
new "Kill restconf daemon"
stop_restconf
if [ $BE -eq 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
stop_backend -f $cfg
rm -rf $dir

View file

@ -1,5 +1,6 @@
#!/bin/bash
# Restconf basic functionality
# also uri encoding using eth/0/0
# Assume http server setup, such as nginx described in apps/restconf/README.md
# Magic line must be first in script (see README.md)
@ -35,7 +36,7 @@ EOF
# This is a fixed 'state' implemented in routing_backend. It is assumed to be always there
state='{"clixon-example:state": {"op": ["42","41","43"]}} '
new "test params: -f $cfg"
new "test params: -f $cfg -- -s"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
@ -114,11 +115,7 @@ new "restconf empty rpc with extra args (should fail)"
expecteq "$(curl -s -X POST -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} '
new "restconf get empty config + state json"
expecteq "$(curl -sSG http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}}
'
new "restconf get empty config + state json + module"
expecteq "$(curl -sSG http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}}
expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state": {"op": ["42","41","43"]}}
'
new "restconf get empty config + state json with wrong module name"
@ -136,7 +133,7 @@ new "restconf get data/ json"
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op": ["42","41","43"]}
'
new "restconf get state operation eth0 xml"
new "restconf get state operation"
# Cant get shell macros to work, inline matching from lib.sh
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/clixon-example:state/op=42)
expect='<op xmlns="urn:example:clixon">42</op>'
@ -145,11 +142,11 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf get state operation eth0 type json"
new "restconf get state operation type json"
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op": ["42","41","43"]}
'
new "restconf get state operation eth0 type xml"
new "restconf get state operation type xml"
# Cant get shell macros to work, inline matching from lib.sh
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/clixon-example:state/op=42)
expect='<op xmlns="urn:example:clixon">42</op>'
@ -163,17 +160,18 @@ expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)"
'
# Exact match
new "restconf Add subtree to datastore using POST"
new "restconf Add subtree eth/0/0 to datastore using POST"
expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK'
new "restconf Re-add subtree which should give error"
new "restconf Re-add subtree eth/0/0 which should give error"
expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
# XXX Cant get this to work
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
new "restconf Check interfaces eth/0/0 added"
expectfn "curl -s -G http://localhost/restconf/data" 0 '"ietf-interfaces:interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]}'
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true,"oper-status": "up"}]}}}
'
new "restconf delete interfaces"
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 ""
@ -190,7 +188,7 @@ expectfn 'curl -s -X POST -d {"ietf-interfaces:interface":{"name":"eth/0/0","typ
#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" 0 ""
new "restconf Check eth/0/0 added config"
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}}
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true,"oper-status": "up"}]}}
'
new "restconf Check eth/0/0 added state"
@ -207,7 +205,7 @@ new "Add nothing using POST"
expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:'
new "restconf Check description added"
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}}
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true,"oper-status": "up"}]}}
'
new "restconf delete eth/0/0"
@ -223,7 +221,7 @@ new "restconf Add subtree eth/0/0 using PUT"
expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 ""
new "restconf get subtree"
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}}
expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true,"oper-status": "up"}]}}
'
new "restconf rpc using POST json"

View file

@ -44,6 +44,7 @@ localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
HOST_VENDOR = @host_vendor@
with_restconf = @with_restconf@
HAVE_LIBXML2 = @HAVE_LIBXML2@
SH_SUFFIX = @SH_SUFFIX@
@ -59,7 +60,6 @@ INSTALL_LIB = @INSTALL@
INSTALLFLAGS = @INSTALLFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
CPPFLAGS = @CPPFLAGS@
INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib -I$(top_srcdir)/include
@ -74,6 +74,7 @@ APPSRC += clixon_util_yang.c
APPSRC += clixon_util_xpath.c
APPSRC += clixon_util_datastore.c
APPSRC += clixon_util_insert.c
APPSRC += clixon_util_regexp.c
ifeq ($(with_restconf),yes)
APPSRC += clixon_util_stream.c # Needs curl
endif
@ -102,15 +103,20 @@ clixon_util_yang: clixon_util_yang.c $(LIBDEPS)
clixon_util_xpath: clixon_util_xpath.c $(LIBDEPS)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_stream: clixon_util_stream.c $(LIBDEPS)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@
clixon_util_datastore: clixon_util_datastore.c $(LIBDEPS)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_insert: clixon_util_insert.c $(LIBDEPS)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_regexp: clixon_util_regexp.c $(LIBDEPS)
$(CC) $(INCLUDES) -I /usr/include/libxml2 $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
ifeq ($(with_restconf),yes)
clixon_util_stream: clixon_util_stream.c $(LIBDEPS)
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@
endif
distclean: clean
rm -f Makefile *~ .depend

View file

@ -31,16 +31,8 @@
***** END LICENSE BLOCK *****
See https://www.w3.org/TR/xpath/
* Turn this on to get an xpath test program
* Usage: xpath [<xpath>]
* read xpath on first line and xml on rest of lines from input
* Example compile:
gcc -g -o xpath -I. -I../clixon ./clixon_xsl.c -lclixon -lcligen
* Example run:
echo "a\n<a><b/></a>" | xpath
*/
* Utility for Inserting element in list
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */

214
util/clixon_util_regexp.c Normal file
View file

@ -0,0 +1,214 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
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.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* Utility for compiling regexp and checking validity
* gcc -I /usr/include/libxml2 regex.c -o regex -lxml2
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <unistd.h> /* unistd */
#include <string.h>
#include <regex.h> /* posix regex */
#include <syslog.h>
#ifdef HAVE_LIBXML2 /* Actually it should check for a header file */
#include <libxml/xmlregexp.h>
#endif
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/*! libxml2 regex implementation
* @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028
* @retval -1 Error
* @retval 0 Not match
* @retval 1 Match
*/
static int
regex_libxml2(char *regexp0,
char *content0,
int nr)
{
int retval = -1;
#ifdef HAVE_LIBXML2
xmlChar *regexp = (xmlChar*)regexp0;
xmlChar *content = (xmlChar*)content0;
xmlRegexp *xrp = NULL;
int ret;
int i;
if ((xrp = xmlRegexpCompile(regexp)) == NULL)
goto done;
if (nr==0)
return 1;
for (i=0; i<nr; i++)
if ((ret = xmlRegexpExec(xrp, content)) < 0)
goto done;
return ret;
done:
#endif
return retval;
}
static int
regex_posix(char *regexp,
char *content,
int nr)
{
int retval = -1;
char *posix = NULL;
char pattern[1024];
int status;
regex_t re;
char errbuf[1024];
int len0;
int i;
if (regexp_xsd2posix(regexp, &posix) < 0)
goto done;
len0 = strlen(posix);
if (len0 > sizeof(pattern)-5){
fprintf(stderr, "pattern too long\n");
return -1;
}
strncpy(pattern, "^(", 2);
strncpy(pattern+2, posix, sizeof(pattern)-2);
strncat(pattern, ")$", sizeof(pattern)-len0-1);
if (regcomp(&re, pattern, REG_NOSUB|REG_EXTENDED) != 0)
return(0); /* report error */
if (nr==0)
return 1;
for (i=0; i<nr; i++)
status = regexec(&re, content, (size_t) 0, NULL, 0);
regfree(&re);
if (status != 0) {
regerror(status, &re, errbuf, sizeof(errbuf)); /* XXX error is ignored */
return(0); /* report error */
}
return(1);
done:
if (posix)
free(posix);
return retval;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options] (either one of -p or -x)\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level>\tDebug\n"
"\t-p \txsd->posix translation regexp\n"
"\t-x \tlibxml2 regexp\n"
"\t-n <nr> \tIterate content match (0 means only compile)\n"
"\t-r <regexp> \tregexp (mandatory)\n"
"\t-c <string> \tValue content string(mandatory)\n",
argv0
);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
char *argv0 = argv[0];
int c;
char *regexp = NULL;
char *content = NULL;
int posix = 0;
int libxml2 = 0;
int ret;
int nr = 1;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, "hD:pxn:r:c:")) != -1)
switch (c) {
case 'h':
usage(argv0);
break;
case 'D':
if (sscanf(optarg, "%d", &debug) != 1)
usage(argv0);
break;
case 'p': /* xsd->posix */
posix++;
break;
case 'n': /* Number of iterations */
if ((nr = atoi(optarg)) < 0)
usage(argv0);
break;
case 'x': /* libxml2 */
libxml2++;
break;
case 'r': /* regexp */
regexp = optarg;
break;
case 'c': /* value content string */
content = optarg;
break;
default:
usage(argv[0]);
break;
}
clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR);
if (regexp == NULL || content == NULL)
usage(argv0);
if (posix == libxml2)
usage(argv0);
clicon_debug(1, "regexp:%s", regexp);
clicon_debug(1, "content:%s", content);
if (libxml2){
if ((ret = regex_libxml2(regexp, content, nr)) < 0)
goto done;
}
else if (posix){
if ((ret = regex_posix(regexp, content, nr)) < 0)
goto done;
}
else
goto done;
fprintf(stdout, "%d\n", ret);
exit(ret);
retval = 0;
done:
return retval;
}