This commit is contained in:
Olof Hagsand 2020-06-16 07:29:27 +00:00
commit c18c40434f
71 changed files with 2181 additions and 713 deletions

View file

@ -27,6 +27,11 @@ Expected: July 2020
### Major New features ### Major New features
* Auto-CLI enhancements
* A generated clispec including state (default @datanodestate) also generated along with the config clispec tree (default @datanode)
* New mode `GT_HIDE` set by option `CLICON_CLI_GENMODEL_TYPE` to collapse non-presence containers that only contain a single list
* Added a prefix for cli_show_config/cli_show_auto so that it can produce parseable output
* Thanks dcornejo@netgate.com for trying it out and suggestions
* Embedding restconf into the existing [libevhtp](https://github.com/criticalstack/libevhtp) embedded web server. Experimental. * Embedding restconf into the existing [libevhtp](https://github.com/criticalstack/libevhtp) embedded web server. Experimental.
* The existing FCGI restconf solution will continue to be supported for NGINX and other reverese proxies with an fast CGI API. * The existing FCGI restconf solution will continue to be supported for NGINX and other reverese proxies with an fast CGI API.
* The restconf code has been refactored to support both modes. Hopefully, it should be straightforward to add another embedded server, such as GNU microhttpd. * The restconf code has been refactored to support both modes. Hopefully, it should be straightforward to add another embedded server, such as GNU microhttpd.
@ -35,9 +40,10 @@ Expected: July 2020
* `--with-restconf=evhtp Integrate restconf with libevhtp server` * `--with-restconf=evhtp Integrate restconf with libevhtp server`
* `--without-restconf Disable restconf altogether` * `--without-restconf Disable restconf altogether`
### C/CLI-API changes on existing features (For developers)
### C-API changes on existing features (For developers) * Added prefix for cli_show_config/cli_show_auto so that it can produce parseable output
* Replaced the global variable `debug` with access function: `clicon_debug_get()`.
* Due to name collision with libevent, all clixon event functions prepended with `clixon_`. You need to rename your event functions as follows: * Due to name collision with libevent, all clixon event functions prepended with `clixon_`. You need to rename your event functions as follows:
* event_reg_fd() -> clixon_event_reg_fd() * event_reg_fd() -> clixon_event_reg_fd()
* event_unreg_fd() -> clixon_event_unreg_fd() * event_unreg_fd() -> clixon_event_unreg_fd()
@ -53,6 +59,10 @@ Expected: July 2020
* Added new function `clicon_xml2str()` to complement xml_print and others that returns a malloced string. * Added new function `clicon_xml2str()` to complement xml_print and others that returns a malloced string.
* Added new function `xml_child_index_each()` to iterate over the children of an XML node according to the order defined by an explicit index variable. This is a complement to `xml_child_each()` which iterates using the default order. * Added new function `xml_child_index_each()` to iterate over the children of an XML node according to the order defined by an explicit index variable. This is a complement to `xml_child_each()` which iterates using the default order.
### Corrected Bugs
* Fixed: The module `clixon-rfc5277` was always enabled, but should only be enabled when `CLICON_STREAM_DISCOVERY_RFC5277` is enabled.
## 4.5.0 ## 4.5.0
12 May 2020 12 May 2020

View file

@ -1066,7 +1066,7 @@ from_client_get(clicon_handle h,
(ret = xml_yang_validate_add(h, xret, &xerr)) < 0) (ret = xml_yang_validate_add(h, xret, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ if (ret == 0){
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE"); clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE");
if (clixon_netconf_internal_error(xerr, if (clixon_netconf_internal_error(xerr,
". Internal error, state callback returned invalid XML", ". Internal error, state callback returned invalid XML",
@ -1362,7 +1362,7 @@ from_client_debug(clicon_handle h,
clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */ clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */
setlogmask(LOG_UPTO(level?LOG_DEBUG:LOG_INFO)); /* for syslog */ setlogmask(LOG_UPTO(level?LOG_DEBUG:LOG_INFO)); /* for syslog */
clicon_log(LOG_NOTICE, "%s debug:%d", __FUNCTION__, debug); clicon_log(LOG_NOTICE, "%s debug:%d", __FUNCTION__, clicon_debug_get());
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>"); cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok: ok:
retval = 0; retval = 0;

View file

@ -453,7 +453,7 @@ from_validate_common(clicon_handle h,
&td->td_tcvec, /* changed: wanted values */ &td->td_tcvec, /* changed: wanted values */
&td->td_clen) < 0) &td->td_clen) < 0)
goto done; goto done;
if (debug>1) if (clicon_debug_get()>1)
transaction_print(stderr, td); transaction_print(stderr, td);
/* Mark as changed in tree */ /* Mark as changed in tree */
for (i=0; i<td->td_dlen; i++){ /* Also down */ for (i=0; i<td->td_dlen; i++){ /* Also down */

View file

@ -297,7 +297,7 @@ check_drop_priv(clicon_handle h,
clicon_err(OE_DAEMON, EPERM, "Privileges can only be dropped from root user (uid is %u)\n", uid); clicon_err(OE_DAEMON, EPERM, "Privileges can only be dropped from root user (uid is %u)\n", uid);
goto done; goto done;
} }
/* When dropping priveleges, datastores are created if they do not exist. /* When dropping privileges, datastores are created if they do not exist.
* But when drops are not made, datastores are created on demand. * But when drops are not made, datastores are created on demand.
* XXX: move the creation to top-level so they are always created at init? * XXX: move the creation to top-level so they are always created at init?
*/ */
@ -461,6 +461,7 @@ main(int argc,
cvec *nsctx_global = NULL; /* Global namespace context */ cvec *nsctx_global = NULL; /* Global namespace context */
size_t cligen_buflen; size_t cligen_buflen;
size_t cligen_bufthreshold; size_t cligen_bufthreshold;
int dbg;
/* In the startup, logs to stderr & syslog and debug flag set later */ /* In the startup, logs to stderr & syslog and debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst); clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -472,6 +473,7 @@ main(int argc,
once = 0; once = 0;
zap = 0; zap = 0;
extraxml_file = NULL; extraxml_file = NULL;
dbg = 0;
/* /*
* Command-line options for help, debug, and config-file * Command-line options for help, debug, and config-file
@ -489,7 +491,7 @@ main(int argc,
help = 1; help = 1;
break; break;
case 'D' : /* debug */ case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv[0]); usage(h, argv[0]);
break; break;
case 'f': /* config file */ case 'f': /* config file */
@ -513,8 +515,8 @@ main(int argc,
* XXX: if started in a start-daemon script, there will be irritating * XXX: if started in a start-daemon script, there will be irritating
* double syslogs until fork below. * double syslogs until fork below.
*/ */
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL); clicon_debug_init(dbg, NULL);
/* Find and read configfile */ /* Find and read configfile */
if (clicon_options_main(h) < 0){ if (clicon_options_main(h) < 0){
@ -622,7 +624,7 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */ /* Access the remaining argv/argc options (after --) w clicon-argv_get() */
clicon_argv_set(h, argv0, argc, argv); clicon_argv_set(h, argv0, argc, argv);
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
/* Defer: Wait to the last minute to print help message */ /* Defer: Wait to the last minute to print help message */
if (help) if (help)
@ -880,7 +882,7 @@ main(int argc,
demonized errors OK. Before this stage, errors are logged on stderr demonized errors OK. Before this stage, errors are logged on stderr
also */ also */
if (foreground==0){ if (foreground==0){
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO,
logdst==CLICON_LOG_FILE?CLICON_LOG_FILE:CLICON_LOG_SYSLOG); logdst==CLICON_LOG_FILE?CLICON_LOG_FILE:CLICON_LOG_SYSLOG);
if (daemon(0, 0) < 0){ if (daemon(0, 0) < 0){
fprintf(stderr, "config: daemon"); fprintf(stderr, "config: daemon");
@ -911,8 +913,8 @@ main(int argc,
goto done; goto done;
if (clicon_socket_set(h, ss) < 0) if (clicon_socket_set(h, ss) < 0)
goto done; goto done;
if (debug) if (dbg)
clicon_option_dump(h, debug); clicon_option_dump(h, dbg);
/* Depending on configure setting, privileges may be dropped here after /* Depending on configure setting, privileges may be dropped here after
* initializations */ * initializations */
if (check_drop_priv(h, gid) < 0) if (check_drop_priv(h, gid) < 0)

View file

@ -246,7 +246,7 @@ clixon_plugin_statedata_all(clicon_handle h,
continue; continue;
} }
#if 1 #if 1
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, x, "%s STATE:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, x, "%s STATE:", __FUNCTION__);
#endif #endif
/* XXX: ret == 0 invalid yang binding should be handled as internal error */ /* XXX: ret == 0 invalid yang binding should be handled as internal error */

View file

@ -2,7 +2,8 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON. This file is part of CLIXON.
@ -62,9 +63,9 @@
#include "cli_plugin.h" #include "cli_plugin.h"
#include "cli_generate.h" #include "cli_generate.h"
/* This is the default callback function. But this is typically overwritten */ /*
#define GENERATE_CALLBACK "overwrite_me" * Constants
*/
/* variable expand function */ /* variable expand function */
#define GENERATE_EXPAND_XMLDB "expand_dbvar" #define GENERATE_EXPAND_XMLDB "expand_dbvar"
@ -340,8 +341,8 @@ yang2cli_var_pattern(clicon_handle h,
} }
/* Forward */ /* Forward */
static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, enum genmodel_type gt,
enum genmodel_type gt, int level, cbuf *cb); int level, int state, cbuf *cb);
static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
yang_stmt *ytype, char *helptext, cbuf *cb); yang_stmt *ytype, char *helptext, cbuf *cb);
@ -657,7 +658,7 @@ yang2cli_leaf(clicon_handle h,
*s = '\0'; *s = '\0';
} }
cprintf(cb, "%*s", level*3, ""); cprintf(cb, "%*s", level*3, "");
if (gt == GT_VARS|| gt == GT_ALL){ if (gt == GT_VARS|| gt == GT_ALL || gt == GT_HIDE){
cprintf(cb, "%s", yang_argument_get(ys)); cprintf(cb, "%s", yang_argument_get(ys));
if (helptext) if (helptext)
cprintf(cb, "(\"%s\")", helptext); cprintf(cb, "(\"%s\")", helptext);
@ -686,6 +687,7 @@ yang2cli_leaf(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
*/ */
static int static int
@ -693,6 +695,7 @@ yang2cli_container(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level, int level,
int state,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
@ -700,26 +703,34 @@ yang2cli_container(clicon_handle h,
int retval = -1; int retval = -1;
char *helptext = NULL; char *helptext = NULL;
char *s; char *s;
int hide = 0;
cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys)); /* If non-presence container && HIDE mode && only child is
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){ * a list, then skip container keyword
if ((helptext = strdup(yang_argument_get(yd))) == NULL){ * See also xml2cli
clicon_err(OE_UNIX, errno, "strdup"); */
goto done; if ((hide = yang_container_cli_hide(ys, gt)) == 0){
cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys));
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
if ((helptext = strdup(yang_argument_get(yd))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
if ((s = strstr(helptext, "\n\n")) != NULL)
*s = '\0';
cprintf(cb, "(\"%s\")", helptext);
} }
if ((s = strstr(helptext, "\n\n")) != NULL) if (cli_callback_generate(h, ys, cb) < 0)
*s = '\0'; goto done;
cprintf(cb, "(\"%s\")", helptext); cprintf(cb, ";{\n");
} }
if (cli_callback_generate(h, ys, cb) < 0)
goto done;
cprintf(cb, ";{\n");
yc = NULL; yc = NULL;
while ((yc = yn_each(ys, yc)) != NULL) while ((yc = yn_each(ys, yc)) != NULL)
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0)
goto done; goto done;
cprintf(cb, "%*s}\n", level*3, ""); if (hide == 0)
cprintf(cb, "%*s}\n", level*3, "");
retval = 0; retval = 0;
done: done:
if (helptext) if (helptext)
@ -732,6 +743,7 @@ yang2cli_container(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
*/ */
static int static int
@ -739,6 +751,7 @@ yang2cli_list(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level, int level,
int state,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
@ -775,7 +788,8 @@ yang2cli_list(clicon_handle h,
/* Print key variable now, and skip it in loop below /* Print key variable now, and skip it in loop below
Note, only print callback on last statement Note, only print callback on last statement
*/ */
if (yang2cli_leaf(h, yleaf, gt==GT_VARS?GT_NONE:gt, level+1, if (yang2cli_leaf(h, yleaf,
(gt==GT_VARS||gt==GT_HIDE)?GT_NONE:gt, level+1,
cvec_next(cvk, cvi)?0:1, cb) < 0) cvec_next(cvk, cvi)?0:1, cb) < 0)
goto done; goto done;
} }
@ -794,7 +808,7 @@ yang2cli_list(clicon_handle h,
} }
if (cvi != NULL) if (cvi != NULL)
continue; continue;
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0)
goto done; goto done;
} }
cprintf(cb, "%*s}\n", level*3, ""); cprintf(cb, "%*s}\n", level*3, "");
@ -811,6 +825,7 @@ yang2cli_list(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
@example @example
choice interface-type { choice interface-type {
@ -826,6 +841,7 @@ yang2cli_choice(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level, int level,
int state,
cbuf *cb) cbuf *cb)
{ {
int retval = -1; int retval = -1;
@ -835,7 +851,7 @@ yang2cli_choice(clicon_handle h,
while ((yc = yn_each(ys, yc)) != NULL) { while ((yc = yn_each(ys, yc)) != NULL) {
switch (yang_keyword_get(yc)){ switch (yang_keyword_get(yc)){
case Y_CASE: case Y_CASE:
if (yang2cli_stmt(h, yc, gt, level+2, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+2, state, cb) < 0)
goto done; goto done;
break; break;
case Y_CONTAINER: case Y_CONTAINER:
@ -843,45 +859,47 @@ yang2cli_choice(clicon_handle h,
case Y_LEAF_LIST: case Y_LEAF_LIST:
case Y_LIST: case Y_LIST:
default: default:
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0)
goto done; goto done;
break; break;
} }
} }
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
/*! Generate CLI code for Yang statement /*! Generate CLI code for Yang statement
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[out] cb Buffer where cligen code is written
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[out] cb Buffer where cligen code is written
*/ */
static int static int
yang2cli_stmt(clicon_handle h, yang2cli_stmt(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level, /* indentation level for pretty-print */ int level,
int state,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
int retval = -1; int retval = -1;
if (yang_config(ys)){ if (state || yang_config(ys)){
switch (yang_keyword_get(ys)){ switch (yang_keyword_get(ys)){
case Y_CONTAINER: case Y_CONTAINER:
if (yang2cli_container(h, ys, gt, level, cb) < 0) if (yang2cli_container(h, ys, gt, level, state, cb) < 0)
goto done; goto done;
break; break;
case Y_LIST: case Y_LIST:
if (yang2cli_list(h, ys, gt, level, cb) < 0) if (yang2cli_list(h, ys, gt, level, state, cb) < 0)
goto done; goto done;
break; break;
case Y_CHOICE: case Y_CHOICE:
if (yang2cli_choice(h, ys, gt, level, cb) < 0) if (yang2cli_choice(h, ys, gt, level, state, cb) < 0)
goto done; goto done;
break; break;
case Y_LEAF_LIST: case Y_LEAF_LIST:
@ -894,7 +912,7 @@ yang2cli_stmt(clicon_handle h,
case Y_MODULE: case Y_MODULE:
yc = NULL; yc = NULL;
while ((yc = yn_each(ys, yc)) != NULL) while ((yc = yn_each(ys, yc)) != NULL)
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0)
goto done; goto done;
break; break;
default: /* skip */ default: /* skip */
@ -902,7 +920,7 @@ yang2cli_stmt(clicon_handle h,
} }
} }
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
@ -911,6 +929,7 @@ yang2cli_stmt(clicon_handle h,
* @param[in] yspec Yang specification * @param[in] yspec Yang specification
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] printgen Log generated CLIgen syntax * @param[in] printgen Log generated CLIgen syntax
* @param[in] state Also include state syntax
* @param[out] ptnew CLIgen parse-tree * @param[out] ptnew CLIgen parse-tree
* *
* Code generation styles: * Code generation styles:
@ -922,6 +941,7 @@ yang2cli(clicon_handle h,
yang_stmt *yspec, yang_stmt *yspec,
enum genmodel_type gt, enum genmodel_type gt,
int printgen, int printgen,
int state,
parse_tree *ptnew) parse_tree *ptnew)
{ {
cbuf *cb = NULL; cbuf *cb = NULL;
@ -936,7 +956,7 @@ yang2cli(clicon_handle h,
/* Traverse YANG, loop through all modules and generate CLI */ /* Traverse YANG, loop through all modules and generate CLI */
ymod = NULL; ymod = NULL;
while ((ymod = yn_each(yspec, ymod)) != NULL) while ((ymod = yn_each(yspec, ymod)) != NULL)
if (yang2cli_stmt(h, ymod, gt, 0, cb) < 0) if (yang2cli_stmt(h, ymod, gt, 0, state, cb) < 0)
goto done; goto done;
if (printgen) if (printgen)
clicon_log(LOG_NOTICE, "%s: Generated CLI spec:\n%s", __FUNCTION__, cbuf_get(cb)); clicon_log(LOG_NOTICE, "%s: Generated CLI spec:\n%s", __FUNCTION__, cbuf_get(cb));
@ -951,10 +971,10 @@ yang2cli(clicon_handle h,
goto done; goto done;
cvec_free(globals); cvec_free(globals);
/* Resolve the expand callback functions in the generated syntax. /* Resolve the expand callback functions in the generated syntax.
This "should" only be GENERATE_EXPAND_XMLDB * This "should" only be GENERATE_EXPAND_XMLDB
handle=NULL for global namespace, this means expand callbacks must be in * handle=NULL for global namespace, this means expand callbacks must be in
CLICON namespace, not in a cli frontend plugin. * CLICON namespace, not in a cli frontend plugin.
*/ */
if (cligen_expandv_str2fn(ptnew, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0) if (cligen_expandv_str2fn(ptnew, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0)
goto done; goto done;

View file

@ -2,7 +2,8 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON. This file is part of CLIXON.
@ -36,10 +37,22 @@
#ifndef _CLI_GENERATE_H_ #ifndef _CLI_GENERATE_H_
#define _CLI_GENERATE_H_ #define _CLI_GENERATE_H_
/*
* Constants
*/
/* This is the default "virtual" callback function of the auto-cli. It should be overwritten by
* a callback specified in a clispec, such as:
* @code
* set @datamodel, cli_set();
* @endcode
* where the virtual callback (overwrite_me) is overwritten by cli_set.
*/
#define GENERATE_CALLBACK "overwrite_me"
/* /*
* Prototypes * Prototypes
*/ */
int yang2cli(clicon_handle h, yang_stmt *yspec, enum genmodel_type gt, int yang2cli(clicon_handle h, yang_stmt *yspec, enum genmodel_type gt,
int printgen, parse_tree *ptnew); int printgen, int state, parse_tree *ptnew);
#endif /* _CLI_GENERATE_H_ */ #endif /* _CLI_GENERATE_H_ */

View file

@ -236,6 +236,103 @@ cli_interactive(clicon_handle h)
return retval; return retval;
} }
/*! Generate one autocli clispec tree
*
* @param[in] h Clixon handle
* @param[in] name Name of tree
* @param[in] gt genmodel-type, ie HOW to generate the CLI
* @param[in] printgen Print CLI syntax to stderr
*
* Generate clispec (datamodel) from YANG dataspec and add to the set of cligen trees
* (as a separate mode)
* This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR) using the "tree reference"
* syntax, ie @datamodel
* @param[in] h Clixon handle
* @param[in] printgen Print CLI syntax generated from dbspec
* @retval 0 OK
* @retval -1 Error
*
* @note that yang2cli generates syntax for ALL modules under the loaded yangspec.
*/
static int
autocli_tree(clicon_handle h,
char *name,
enum genmodel_type gt,
int state,
int printgen)
{
int retval = -1;
parse_tree *pt = NULL; /* cli parse tree */
yang_stmt *yspec;
if ((pt = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new");
goto done;
}
yspec = clicon_dbspec_yang(h);
/* Generate tree (this is where the action is) */
if (yang2cli(h, yspec, gt, printgen, state, pt) < 0)
goto done;
/* Append cligen tree and name it */
if (cligen_tree_add(cli_cligen(h), name, pt) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Generate autocli, ie if enabled, generate clispec from YANG and add to cligen parse-trees
*
* Generate clispec (datamodel) from YANG dataspec and add to the set of cligen trees
* (as a separate mode)
* This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR) using the "tree reference"
* syntax, ie @datamodel
* Also (if enabled) generate a second "state" tree called @datamodelstate
*
* @param[in] h Clixon handle
* @param[in] printgen Print CLI syntax generated from dbspec
* @retval 0 OK
* @retval -1 Error
*/
static int
autocli_start(clicon_handle h,
int printgen)
{
int retval = -1;
int autocli_model = 0;
cbuf *treename = NULL;
enum genmodel_type gt;
/* If autocli disabled quit */
if ((autocli_model = clicon_cli_genmodel(h)) == 0)
goto ok;
/* Get the autocli type, ie HOW the cli is generated (could be much more here) */
gt = clicon_cli_genmodel_type(h);
/* Create treename cbuf */
if ((treename = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* The tree name is by default @datamodel but can be changed by option (why would one do that?) */
cprintf(treename, "%s", clicon_cli_model_treename(h));
if (autocli_tree(h, cbuf_get(treename), gt, 0, printgen) < 0)
goto done;
/* Create a tree for config+state. This tree's name has appended "state" to @datamodel (XXX)
*/
if (autocli_model > 1){
cprintf(treename, "state");
if (autocli_tree(h, cbuf_get(treename), gt, 1, printgen) < 0)
goto done;
}
ok:
retval = 0;
done:
if (treename)
cbuf_free(treename);
return retval;
}
static void static void
usage(clicon_handle h, usage(clicon_handle h,
char *argv0) char *argv0)
@ -294,6 +391,7 @@ main(int argc,
cvec *nsctx_global = NULL; /* Global namespace context */ cvec *nsctx_global = NULL; /* Global namespace context */
size_t cligen_buflen; size_t cligen_buflen;
size_t cligen_bufthreshold; size_t cligen_bufthreshold;
int dbg=0;
/* Defaults */ /* Defaults */
once = 0; once = 0;
@ -332,7 +430,7 @@ main(int argc,
help = 1; help = 1;
break; break;
case 'D' : /* debug */ case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv[0]); usage(h, argv[0]);
break; break;
case 'f': /* config file */ case 'f': /* config file */
@ -352,9 +450,9 @@ main(int argc,
/* /*
* Logs, error and debug to stderr or syslog, set debug level * Logs, error and debug to stderr or syslog, set debug level
*/ */
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL); clicon_debug_init(dbg, NULL);
/* Find, read and parse configfile */ /* Find, read and parse configfile */
if (clicon_options_main(h) < 0){ if (clicon_options_main(h) < 0){
@ -535,29 +633,9 @@ main(int argc,
if (clicon_nsctx_global_set(h, nsctx_global) < 0) if (clicon_nsctx_global_set(h, nsctx_global) < 0)
goto done; goto done;
/* Create tree generated from dataspec. If no other trees exists, this is /* Create autocli from YANG */
* the only one. if (autocli_start(h, printgen) < 0)
* The following code creates the tree @datamodel goto done;
* This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR)
* using the "tree reference"
* syntax, ie @datamodel
* But note that yang2cli generates syntax for ALL modules, not just for
* <module>.
*/
if (clicon_cli_genmodel(h)){
parse_tree *pt = NULL; /* cli parse tree */
char *treeref;
if ((pt = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new");
goto done;
}
treeref = clicon_cli_model_treename(h);
/* Create cli command tree from dbspec */
if (yang2cli(h, yspec, clicon_cli_genmodel_type(h), printgen, pt) < 0)
goto done;
cligen_tree_add(cli_cligen(h), treeref, pt);
}
/* Initialize cli syntax */ /* Initialize cli syntax */
if (cli_syntax_load(h) < 0) if (cli_syntax_load(h) < 0)
@ -588,8 +666,8 @@ main(int argc,
if (logclisyntax) if (logclisyntax)
cli_logsyntax_set(h, logclisyntax); cli_logsyntax_set(h, logclisyntax);
if (debug) if (dbg)
clicon_option_dump(h, debug); clicon_option_dump(h, dbg);
/* Join rest of argv to a single command */ /* Join rest of argv to a single command */
restarg = clicon_strjoin(argc, argv, " "); restarg = clicon_strjoin(argc, argv, " ");

View file

@ -66,6 +66,7 @@
#include "clixon_cli_api.h" #include "clixon_cli_api.h"
#include "cli_plugin.h" #include "cli_plugin.h"
#include "cli_handle.h" #include "cli_handle.h"
#include "cli_generate.h"
/* /*
* *
@ -177,7 +178,7 @@ cli_syntax_unload(clicon_handle h)
* @param[in] handle Handle to plugin .so module as returned by dlopen * @param[in] handle Handle to plugin .so module as returned by dlopen
* @param[out] error Static error string, if set indicates error * @param[out] error Static error string, if set indicates error
* @retval fn Function pointer * @retval fn Function pointer
* @retval NULL FUnction not found or symbol NULL (check error for proper handling) * @retval NULL Function not found or symbol NULL (check error for proper handling)
* @see see cli_plugin_load where (optional) handle opened * @see see cli_plugin_load where (optional) handle opened
* @note the returned function is not type-checked which may result in segv at runtime * @note the returned function is not type-checked which may result in segv at runtime
*/ */
@ -188,8 +189,15 @@ clixon_str2fn(char *name,
{ {
void *fn = NULL; void *fn = NULL;
/* Reset error */ /* Reset error */
*error = NULL; *error = NULL;
/* Special check for auto-cli. If the virtual callback is used, it should be overwritten later
* by a callback given in the clispec, eg: set @datamodel, cli_set();
*/
if (strcmp(name, GENERATE_CALLBACK) == 0)
return NULL;
/* First check given plugin if any */ /* First check given plugin if any */
if (handle) { if (handle) {

View file

@ -421,10 +421,12 @@ show_yang(clicon_handle h,
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression, that may contain one %, eg "/sender[name='foo']" * <xpath> xpath expression, that may contain one %, eg "/sender[name='foo']"
* <namespace> If xpath set, the namespace the symbols in xpath belong to (optional) * <namespace> If xpath set, the namespace the symbols in xpath belong to (optional)
* <prefix> to print before cli syntax output (optional)
* @code * @code
* show config id <n:string>, cli_show_config("running","xml","iface[name='foo']","urn:example:example"); * show config id <n:string>, cli_show_config("running","xml","iface[name='foo']","urn:example:example");
* @endcode * @endcode
* @note if state parameter is set, then db must be running * @note if state parameter is set, then db must be running
* @see cli_show_auto1
*/ */
static int static int
cli_show_config1(clicon_handle h, cli_show_config1(clicon_handle h,
@ -446,9 +448,10 @@ cli_show_config1(clicon_handle h,
yang_stmt *yspec; yang_stmt *yspec;
char *namespace = NULL; char *namespace = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
char *prefix = NULL;
if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ if (cvec_len(argv) < 3 || cvec_len(argv) > 5){
clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<format>,<xpath>[,<attr>]", cvec_len(argv)); clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<format>,<xpath>[,<namespace>, [<prefix>]]", cvec_len(argv));
goto done; goto done;
} }
@ -474,11 +477,14 @@ cli_show_config1(clicon_handle h,
} }
cprintf(cbxpath, "%s", xpath); cprintf(cbxpath, "%s", xpath);
/* Fourth argument is namespace */ /* Fourth argument is namespace */
if (cvec_len(argv) == 4){ if (cvec_len(argv) > 3){
namespace = cv_string_get(cvec_i(argv, 3)); namespace = cv_string_get(cvec_i(argv, 3));
if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
goto done; goto done;
} }
if (cvec_len(argv) > 4){
prefix = cv_string_get(cvec_i(argv, 4));
}
if (state == 0){ /* Get configuration-only from database */ if (state == 0){ /* Get configuration-only from database */
if (clicon_rpc_get_config(h, NULL, db, cbuf_get(cbxpath), nsc, &xt) < 0) if (clicon_rpc_get_config(h, NULL, db, cbuf_get(cbxpath), nsc, &xt) < 0)
goto done; goto done;
@ -516,7 +522,7 @@ cli_show_config1(clicon_handle h,
goto done; goto done;
xc = NULL; /* Dont print xt itself */ xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL)
xml2cli(stdout, xc, NULL, gt); /* cli syntax */ xml2cli(stdout, xc, prefix, gt); /* cli syntax */
break; break;
case FORMAT_NETCONF: case FORMAT_NETCONF:
fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n"); fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n");
@ -549,6 +555,7 @@ done:
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression, that may contain one %, eg "/sender[name="%s"]" * <xpath> xpath expression, that may contain one %, eg "/sender[name="%s"]"
* <namespace> If xpath set, the namespace the symbols in xpath belong to (optional) * <namespace> If xpath set, the namespace the symbols in xpath belong to (optional)
* <prefix> to print before cli syntax output
* @code * @code
* show config id <n:string>, cli_show_config("running","xml","iface[name='foo']","urn:example:example"); * show config id <n:string>, cli_show_config("running","xml","iface[name='foo']","urn:example:example");
* @endcode * @endcode
@ -675,8 +682,10 @@ int cli_show_version(clicon_handle h,
* <api_path_fmt> Generated API PATH * <api_path_fmt> Generated API PATH
* <dbname> "running"|"candidate"|"startup" * <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <prefix> to print before cli syntax output
* @note if state parameter is set, then db must be running * @note if state parameter is set, then db must be running
* @note that first argument is generated by code. * @note that first argument is generated by code.
* @see cli_show_config1
*/ */
static int static int
cli_show_auto1(clicon_handle h, cli_show_auto1(clicon_handle h,
@ -687,7 +696,6 @@ cli_show_auto1(clicon_handle h,
int retval = 1; int retval = 1;
yang_stmt *yspec; yang_stmt *yspec;
char *api_path_fmt; /* xml key format */ char *api_path_fmt; /* xml key format */
// char *api_path = NULL; /* xml key */
char *db; char *db;
char *xpath = NULL; char *xpath = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
@ -698,9 +706,10 @@ cli_show_auto1(clicon_handle h,
cxobj *xerr; cxobj *xerr;
enum genmodel_type gt; enum genmodel_type gt;
char *api_path = NULL; char *api_path = NULL;
char *prefix = NULL;
if (cvec_len(argv) != 3){ if (cvec_len(argv) < 3 || cvec_len(argv) > 4){
clicon_err(OE_PLUGIN, 0, "Usage: <api-path-fmt>* <database> <format>. (*) generated."); clicon_err(OE_PLUGIN, 0, "Usage: <api-path-fmt>* <database> <format> <prefix>. (*) generated.");
goto done; goto done;
} }
/* First argv argument: API_path format */ /* First argv argument: API_path format */
@ -709,6 +718,10 @@ cli_show_auto1(clicon_handle h,
db = cv_string_get(cvec_i(argv, 1)); db = cv_string_get(cvec_i(argv, 1));
/* Third format: output format */ /* Third format: output format */
formatstr = cv_string_get(cvec_i(argv, 2)); formatstr = cv_string_get(cvec_i(argv, 2));
if (cvec_len(argv) > 3){
/* Fourth format: prefix to print before cli syntax */
prefix = cv_string_get(cvec_i(argv, 3));
}
if ((int)(format = format_str2int(formatstr)) < 0){ if ((int)(format = format_str2int(formatstr)) < 0){
clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr); clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr);
goto done; goto done;
@ -758,7 +771,7 @@ cli_show_auto1(clicon_handle h,
case FORMAT_CLI: case FORMAT_CLI:
if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR)
goto done; goto done;
xml2cli(stdout, xp, NULL, gt); /* cli syntax */ xml2cli(stdout, xp, prefix, gt); /* cli syntax */
break; break;
case FORMAT_NETCONF: case FORMAT_NETCONF:
fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n"); fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n");
@ -786,7 +799,9 @@ cli_show_auto1(clicon_handle h,
* <api_path_fmt> Generated API PATH * <api_path_fmt> Generated API PATH
* <dbname> "running"|"candidate"|"startup" * <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <prefix> to print before cli syntax outptu
* @see cli_show_auto_state For config and state * @see cli_show_auto_state For config and state
* @note SHOULD be used: ... @datamodel, cli_show_auto(<dbname>,...) to get correct #args
*/ */
int int
cli_show_auto(clicon_handle h, cli_show_auto(clicon_handle h,
@ -801,7 +816,9 @@ cli_show_auto(clicon_handle h,
* <api_path_fmt> Generated API PATH * <api_path_fmt> Generated API PATH
* <dbname> "running" * <dbname> "running"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <prefix> to print before cli syntax output
* @see cli_show_auto For config only * @see cli_show_auto For config only
* @see cli_show_config_state Not auto-generated
*/ */
int int
cli_show_auto_state(clicon_handle h, cli_show_auto_state(clicon_handle h,

View file

@ -69,54 +69,44 @@ int cli_notification_register(clicon_handle h, char *stream, enum format_enum fo
/* cli_common.c: CLIgen new vector callbacks */ /* cli_common.c: CLIgen new vector callbacks */
int cli_set(clicon_handle h, cvec *vars, cvec *argv); int cli_set(clicon_handle h, cvec *vars, cvec *argv);
int cli_merge(clicon_handle h, cvec *vars, cvec *argv); int cli_merge(clicon_handle h, cvec *vars, cvec *argv);
int cli_create(clicon_handle h, cvec *vars, cvec *argv); int cli_create(clicon_handle h, cvec *vars, cvec *argv);
int cli_remove(clicon_handle h, cvec *vars, cvec *argv); int cli_remove(clicon_handle h, cvec *vars, cvec *argv);
int cli_del(clicon_handle h, cvec *vars, cvec *argv); int cli_del(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_cli(clicon_handle h, cvec *vars, cvec *argv); int cli_debug_cli(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_backend(clicon_handle h, cvec *vars, cvec *argv); int cli_debug_backend(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_restconf(clicon_handle h, cvec *vars, cvec *argv); int cli_debug_restconf(clicon_handle h, cvec *vars, cvec *argv);
int cli_set_mode(clicon_handle h, cvec *vars, cvec *argv); int cli_set_mode(clicon_handle h, cvec *vars, cvec *argv);
int cli_start_shell(clicon_handle h, cvec *vars, cvec *argv); int cli_start_shell(clicon_handle h, cvec *vars, cvec *argv);
int cli_quit(clicon_handle h, cvec *vars, cvec *argv); int cli_quit(clicon_handle h, cvec *vars, cvec *argv);
int cli_commit(clicon_handle h, cvec *vars, cvec *argv); int cli_commit(clicon_handle h, cvec *vars, cvec *argv);
int cli_validate(clicon_handle h, cvec *vars, cvec *argv); int cli_validate(clicon_handle h, cvec *vars, cvec *argv);
int compare_dbs(clicon_handle h, cvec *vars, cvec *argv); int compare_dbs(clicon_handle h, cvec *vars, cvec *argv);
int load_config_file(clicon_handle h, cvec *vars, cvec *argv); int load_config_file(clicon_handle h, cvec *vars, cvec *argv);
int save_config_file(clicon_handle h, cvec *vars, cvec *argv); int save_config_file(clicon_handle h, cvec *vars, cvec *argv);
int delete_all(clicon_handle h, cvec *vars, cvec *argv); int delete_all(clicon_handle h, cvec *vars, cvec *argv);
int discard_changes(clicon_handle h, cvec *vars, cvec *argv); int discard_changes(clicon_handle h, cvec *vars, cvec *argv);
int cli_notify(clicon_handle h, cvec *cvv, cvec *argv); int cli_notify(clicon_handle h, cvec *cvv, cvec *argv);
int db_copy(clicon_handle h, cvec *cvv, cvec *argv); int db_copy(clicon_handle h, cvec *cvv, cvec *argv);
int cli_lock(clicon_handle h, cvec *cvv, cvec *argv); int cli_lock(clicon_handle h, cvec *cvv, cvec *argv);
@ -133,7 +123,6 @@ int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv,
/* cli_show.c: CLIgen new vector arg callbacks */ /* cli_show.c: CLIgen new vector arg callbacks */
int show_yang(clicon_handle h, cvec *vars, cvec *argv); int show_yang(clicon_handle h, cvec *vars, cvec *argv);
int show_conf_xpath(clicon_handle h, cvec *cvv, 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(clicon_handle h, cvec *cvv, cvec *argv);

View file

@ -197,7 +197,7 @@ netconf_output(int s,
int retval = -1; int retval = -1;
clicon_debug(1, "SEND %s", msg); clicon_debug(1, "SEND %s", msg);
if (debug > 1){ /* XXX: below only works to stderr, clicon_debug may log to syslog */ if (clicon_debug_get() > 1){ /* XXX: below only works to stderr, clicon_debug may log to syslog */
cxobj *xt = NULL; cxobj *xt = NULL;
if (clixon_xml_parse_string(buf, YB_NONE, NULL, &xt, NULL) == 0){ if (clixon_xml_parse_string(buf, YB_NONE, NULL, &xt, NULL) == 0){
clicon_xml2file(stderr, xml_child_i(xt, 0), 0, 0); clicon_xml2file(stderr, xml_child_i(xt, 0), 0, 0);

View file

@ -401,6 +401,7 @@ main(int argc,
cvec *nsctx_global = NULL; /* Global namespace context */ cvec *nsctx_global = NULL; /* Global namespace context */
size_t cligen_buflen; size_t cligen_buflen;
size_t cligen_bufthreshold; size_t cligen_bufthreshold;
int dbg = 0;
/* Create handle */ /* Create handle */
if ((h = clicon_handle_init()) == NULL) if ((h = clicon_handle_init()) == NULL)
@ -421,7 +422,7 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
break; break;
case 'D' : /* debug */ case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv[0]); usage(h, argv[0]);
break; break;
case 'f': /* override config file */ case 'f': /* override config file */
@ -442,8 +443,8 @@ main(int argc,
/* /*
* Logs, error and debug to stderr or syslog, set debug level * Logs, error and debug to stderr or syslog, set debug level
*/ */
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL); clicon_debug_init(dbg, NULL);
/* Find, read and parse configfile */ /* Find, read and parse configfile */
if (clicon_options_main(h) < 0) if (clicon_options_main(h) < 0)
@ -592,8 +593,8 @@ main(int argc,
send_hello(h, 1, id); send_hello(h, 1, id);
if (clixon_event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0) if (clixon_event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0)
goto done; goto done;
if (debug) if (dbg)
clicon_option_dump(h, debug); clicon_option_dump(h, dbg);
if (tv.tv_sec || tv.tv_usec){ if (tv.tv_sec || tv.tv_usec){
struct timeval t; struct timeval t;
gettimeofday(&t, NULL); gettimeofday(&t, NULL);

View file

@ -78,35 +78,33 @@ CPPFLAGS = @CPPFLAGS@ -fPIC
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
# Applications # Application
ifeq ($(with_restconf),fcgi) APPL = clixon_restconf
APPL = clixon_restconf # fcgi / nginx
else
APPL = clixon_restconf_$(with_restconf)
endif
# Common source - not accessible from plugin - independent of restconf package (fcgi|evhtp) # Common source - not accessible from plugin - independent of restconf package (fcgi|evhtp)
#APPSRC = restconf_lib.c APPSRC =
APPSRC += restconf_api.c # maybe empty
APPSRC += restconf_api_$(with_restconf).c # cant be .so since libevhtp is a.
APPSRC += restconf_root.c
APPSRC += restconf_$(with_restconf)_main.c
# Fcgi-specific source including main # Fcgi-specific source including main
ifeq ($(with_restconf),fcgi) ifeq ($(with_restconf),fcgi)
APPSRC = restconf_fcgi_lib.c APPSRC += restconf_fcgi_lib.c # Most of these should be made generic
APPSRC += restconf_fcgi_main.c APPSRC += restconf_methods.c
APPSRC += restconf_methods.c # These should be moved ^
APPSRC += restconf_methods_post.c APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c APPSRC += restconf_methods_get.c
APPSRC += restconf_stream.c APPSRC += restconf_stream.c
endif endif
# Evhtp-specific source including main
ifeq ($(with_restconf),evhtp)
APPSRC = restconf_evhtp_main.c
endif
APPOBJ = $(APPSRC:.c=.o) APPOBJ = $(APPSRC:.c=.o)
# Accessible from plugin # Accessible from plugin
# XXX actually this does not work properly, there are functions in lib
# (eg restconf_method_notallowed) that uses symbols in restconf_api that
# are not in the lib.
LIBSRC = restconf_lib.c LIBSRC = restconf_lib.c
LIBOBJ = $(LIBSRC:.c=.o) LIBOBJ = $(LIBSRC:.c=.o)
# This lib is very small but used for clixon restconf applications to access clixon restconf lib # This lib is very small but used for clixon restconf applications to access clixon restconf lib

View file

@ -0,0 +1,66 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
* Generic restconf API functions
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <libgen.h>
#include <sys/stat.h> /* chmod */
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h"
#include "restconf_api.h"

View file

@ -0,0 +1,54 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
*
* Virtual clixon restconf API functions.
*/
#ifndef _RESTCONF_API_H_
#define _RESTCONF_API_H_
/*
* Prototypes
*/
int restconf_reply_status_code(void *req, int code);
#if defined(__GNUC__) && __GNUC__ >= 3
int restconf_reply_header_add(void *req, char *name, char *vfmt, ...) __attribute__ ((format (printf, 3, 4)));
#else
int restconf_reply_header_add(FCGX_Request *req, char *name, char *vfmt, ...);
#endif
int restconf_reply_send(void *req, cbuf *cb);
#endif /* _RESTCONF_API_H_ */

View file

@ -0,0 +1,183 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
* Concrete functions for libevhtp of the
* Virtual clixon restconf API functions.
* @see restconf_api.h for virtual API
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <dlfcn.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/wait.h>
/* evhtp */
#include <evhtp/evhtp.h>
#include <evhtp/sslutils.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "restconf_lib.h"
#include "restconf_api.h" /* Virtual api */
/*! Add HTTP header field name and value to reply, evhtp specific
* @param[in] req Evhtp http request handle
* @param[in] code HTTP status code
* @see eg RFC 7230
*/
int
restconf_reply_status_code(void *req0,
int code)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
req->status = code;
return 0;
}
/*! Add HTTP header field name and value to reply, evhtp specific
* @param[in] req Evhtp http request handle
* @param[in] name HTTP header field name
* @param[in] vfmt HTTP header field value format string w variable parameter
* @see eg RFC 7230
*/
int
restconf_reply_header_add(void *req0,
char *name,
char *vfmt,
...)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
int retval = -1;
size_t vlen;
char *value = NULL;
va_list ap;
evhtp_header_t *evhdr;
if (req == NULL || name == NULL || vfmt == NULL){
clicon_err(OE_CFG, EINVAL, "req, name or value is NULL");
return -1;
}
va_start(ap, vfmt);
vlen = vsnprintf(NULL, 0, vfmt, ap);
va_end(ap);
/* allocate value string exactly fitting */
if ((value = malloc(vlen+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
/* second round: compute actual value */
va_start(ap, vfmt);
if (vsnprintf(value, vlen+1, vfmt, ap) < 0){
clicon_err(OE_UNIX, errno, "vsnprintf");
va_end(ap);
goto done;
}
va_end(ap);
if ((evhdr = evhtp_header_new(name, value, 0, 1)) == NULL){ /* 1: free after use */
clicon_err(OE_CFG, errno, "evhttp_header_new");
goto done;
}
value = NULL; /* freed by evhtp */
evhtp_headers_add_header(req->headers_out, evhdr);
retval = 0;
done:
if (value)
free(value);
return retval;
}
/*! Send HTTP reply with potential message body
* @param[in] req Evhtp http request handle
* @param[in] cb Body as a cbuf, send if
*
* Prerequisites: status code set, headers given, body if wanted set
*/
int
restconf_reply_send(void *req0,
cbuf *cb)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
int retval = -1;
evhtp_connection_t *conn;
struct evbuffer *eb = NULL;
#if 1 /* Optional? */
if ((conn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
goto done;
}
htp_sslutil_add_xheaders(req->headers_out, conn->ssl, HTP_SSLUTILS_XHDR_ALL);
#endif
/* create evbuffer* : bufferevent_write_buffer/ drain,
ie send everything , except body */
evhtp_send_reply_start(req, req->status);
/* Write a body if cbuf is nonzero */
if (cb != NULL && cbuf_len(cb)){
/* Suboptimal, copy from cbuf to evbuffer */
if ((eb = evbuffer_new()) == NULL){
clicon_err(OE_CFG, errno, "evbuffer_new");
goto done;
}
if (evbuffer_add(eb, cbuf_get(cb), cbuf_len(cb)) < 0){
clicon_err(OE_CFG, errno, "evbuffer_add");
goto done;
}
evhtp_send_reply_body(req, eb); /* conn->bev = eb, body is different */
}
evhtp_send_reply_end(req); /* just flag finished */
retval = 0;
done:
if (eb)
evhtp_safe_free(eb, evbuffer_free);
return retval;
}

View file

@ -0,0 +1,217 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
* Concrete functions for FCGI of the
* Virtual clixon restconf API functions.
* @see restconf_api.h for virtual API
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <dlfcn.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_api.h" /* Virtual api */
/*! Add HTTP header field name and value to reply, fcgi specific
* @param[in] req Fastcgi request handle
* @param[in] code HTTP status code
* @see eg RFC 7230
*/
int
restconf_reply_status_code(void *req0,
int code)
{
FCGX_Request *req = (FCGX_Request *)req0;
FCGX_SetExitStatus(code, req->out);
return 0;
}
/*! HTTP headers done, if there is a message body coming next
* @param[in] req Fastcgi request handle
* @retval body Handle for body handling (in fcgi same as req)
*
* HTTP-message = start-line *( header-field CRLF ) CRLF [ message-body ]
* @see eg RFC 7230
* XXX may be unecessary (or body start or something)
*/
FCGX_Request *
restconf_reply_body_start(void *req0)
{
FCGX_Request *req = (FCGX_Request *)req0;
FCGX_FPrintF(req->out, "\r\n");
return req;
}
/*! Add HTTP header field name and value to reply, fcgi specific
* @param[in] req Fastcgi request handle
* @param[in] name HTTP header field name
* @param[in] vfmt HTTP header field value format string w variable parameter
* @see eg RFC 7230
*/
int
restconf_reply_header_add(void *req0,
char *name,
char *vfmt,
...)
{
FCGX_Request *req = (FCGX_Request *)req0;
int retval = -1;
size_t vlen;
char *value = NULL;
va_list ap;
if (req == NULL || name == NULL || vfmt == NULL){
clicon_err(OE_CFG, EINVAL, "req, name or value is NULL");
return -1;
}
va_start(ap, vfmt);
vlen = vsnprintf(NULL, 0, vfmt, ap);
va_end(ap);
/* allocate value string exactly fitting */
if ((value = malloc(vlen+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
/* second round: compute actual value */
va_start(ap, vfmt);
if (vsnprintf(value, vlen+1, vfmt, ap) < 0){
clicon_err(OE_UNIX, errno, "vsnprintf");
va_end(ap);
goto done;
}
va_end(ap);
FCGX_FPrintF(req->out, "%s: %s\r\n", name, value);
retval = 0;
done:
if (value)
free(value);
return retval;
}
/*! Add HTTP message body to reply, fcgi specific
* @param[in] req Fastcgi request handle
* @param[in,out] content_len This is for Content-Length header
* @param[in] bfmt HTTP message body format string w variable parameter
* @see eg RFC 7230
*/
int
restconf_reply_body_add(void *req0,
size_t *content_len,
char *bfmt,
...)
{
FCGX_Request *req = (FCGX_Request *)req0;
int retval = -1;
size_t sz;
size_t blen;
char *body = NULL;
va_list ap;
if (req == NULL || bfmt == NULL){
clicon_err(OE_CFG, EINVAL, "req or body is NULL");
return -1;
}
va_start(ap, bfmt);
blen = vsnprintf(NULL, 0, bfmt, ap);
va_end(ap);
/* allocate body string exactly fitting */
if ((body = malloc(blen+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
/* second round: compute actual body */
va_start(ap, bfmt);
if (vsnprintf(body, blen+1, bfmt, ap) < 0){
clicon_err(OE_UNIX, errno, "vsnprintf");
va_end(ap);
goto done;
}
va_end(ap);
FCGX_FPrintF(req->out, "%s", body);
/* Increment in/out Content-Length parameter */
if (content_len){
sz = strlen(body);
*content_len += sz;
}
retval = 0;
done:
if (body)
free(body);
return retval;
}
/*! Send HTTP reply with potential message body
* @param[in] req Fastcgi request handle
* @param[in] cb Body as a cbuf, send if
*
* Prerequisites: status code set, headers given, body if wanted set
*/
int
restconf_reply_send(void *req0,
cbuf *cb)
{
FCGX_Request *req = (FCGX_Request *)req0;
int retval = -1;
/* Write a body if cbuf is nonzero */
if (cb != NULL && cbuf_len(cb)){
FCGX_FPrintF(req->out, "\r\n");
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
}
retval = 0;
return retval;
}

View file

@ -31,7 +31,8 @@
the terms of any one of the Apache License version 2 or the GPL. the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* libevhtp code
*/ */
/* XXX temp constant should go away, */ /* XXX temp constant should go away, */
@ -43,7 +44,7 @@
/* compilation withotu threading support /* compilation withotu threading support
* XXX: could be disabled already in configure? * XXX: could be disabled already in configure?
*/ */
#define EVHTP_DISABLE_EVTHR //#define EVHTP_DISABLE_EVTHR
#define EVHTP_DISABLE_REGEX #define EVHTP_DISABLE_REGEX
#include <stdlib.h> #include <stdlib.h>
@ -73,13 +74,9 @@
/* restconf */ /* restconf */
#include "restconf_lib.h" #include "restconf_lib.h" /* generic shared with plugins */
#if 0 /* These are all dependent on FCGX */ #include "restconf_api.h" /* generic not shared with plugins */
#include "restconf_methods.h" #include "restconf_root.h"
#include "restconf_methods_get.h"
#include "restconf_methods_post.h"
#include "restconf_stream.h"
#endif
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:P:c:k:" #define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:P:c:k:"
@ -122,6 +119,178 @@ restconf_sig_child(int arg)
} }
} }
static char*
evhtp_method2str(enum htp_method m)
{
switch (m){
case htp_method_GET:
return "GET";
break;
case htp_method_HEAD:
return "HEAD";
break;
case htp_method_POST:
return "POST";
break;
case htp_method_PUT:
return "PUT";
break;
case htp_method_DELETE:
return "DELETE";
break;
case htp_method_PATCH:
return "PATCH";
break;
default:
return "XXX";
break;
}
}
static int
query_iterator(evhtp_header_t *hdr,
void *arg)
{
cvec *qvec = (cvec *)arg;
char *key;
char *val;
char *valu = NULL; /* unescaped value */
cg_var *cv;
key = hdr->key;
val = hdr->val;
if (uri_percent_decode(val, &valu) < 0)
return -1;
if ((cv = cvec_add(qvec, CGV_STRING)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_add");
return -1;
}
cv_name_set(cv, key);
cv_string_set(cv, valu);
if (valu)
free(valu);
return 0;
}
/*! Map from evhtp information to "fcgi" type parameters used in clixon code
*
* While all these params come via one call in fcgi, the information must be taken from
* several different places in evhtp
* @param[in] h Clicon handle
* @param[in] req Evhtp request struct
* @retval 0 OK
* @retval -1 Error
* The following parameters are set:
* QUERY_STRING
* REQUEST_METHOD
* REQUEST_URI
* HTTPS
* HTTP_HOST
* HTTP_ACCEPT
* HTTP_CONTENT_TYPE
* @note there may be more used by an application plugin
*/
static int
evhtp_params_set(clicon_handle h,
evhtp_request_t *req,
cvec *qvec)
{
int retval = -1;
htp_method meth;
evhtp_uri_t *uri;
evhtp_path_t *path;
evhtp_header_t *hdr;
if ((uri = req->uri) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No uri");
goto done;
}
if ((path = uri->path) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No path");
goto done;
}
meth = evhtp_request_get_method(req);
/* QUERY_STRING */
if (qvec && uri->query)
if (evhtp_kvs_for_each(uri->query, query_iterator, qvec) < 0){
clicon_err(OE_CFG, errno, "evhtp_kvs_for_each");
goto done;
}
if (clixon_restconf_param_set(h, "REQUEST_METHOD", evhtp_method2str(meth)) < 0)
goto done;
if (clixon_restconf_param_set(h, "REQUEST_URI", path->full) < 0)
goto done;
if (clixon_restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */
goto done;
if ((hdr = evhtp_headers_find_header(req->headers_in, "Host")) != NULL){
if (clixon_restconf_param_set(h, "HTTP_HOST", hdr->val) < 0)
goto done;
}
if ((hdr = evhtp_headers_find_header(req->headers_in, "Accept")) != NULL){
if (clixon_restconf_param_set(h, "HTTP_ACCEPT", hdr->val) < 0)
goto done;
}
if ((hdr = evhtp_headers_find_header(req->headers_in, "Content-Type")) != NULL){
if (clixon_restconf_param_set(h, "HTTP_CONTENT_TYPE", hdr->val) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
static int
evhtp_params_clear(clicon_handle h)
{
int retval = -1;
char *params[] = {"QUERY_STRING", "REQUEST_METHOD", "REQUEST_URI",
"HTTPS", "HTTP_HOST", "HTTP_ACCEPT", "HTTP_CONTENT_TYPE", NULL};
char *param;
int i=0;
while((param=params[i]) != NULL)
if (clixon_restconf_param_del(h, param) < 0)
goto done;
retval = 0;
done:
return retval;
}
static int
print_header(evhtp_header_t *header,
void *arg)
{
// clicon_handle h = (clicon_handle)arg;
clicon_debug(1, "%s %s %s",
__FUNCTION__, header->key, header->val);
return 0;
}
static evhtp_res
cx_pre_accept(evhtp_connection_t *conn,
void *arg)
{
// clicon_handle h = (clicon_handle)arg;
clicon_debug(1, "%s", __FUNCTION__);
return EVHTP_RES_OK;
}
static evhtp_res
cx_post_accept(evhtp_connection_t *conn,
void *arg)
{
// clicon_handle h = (clicon_handle)arg;
clicon_debug(1, "%s", __FUNCTION__);
return EVHTP_RES_OK;
}
/*! Generic callback called if no other callbacks are matched
*/
static void static void
cx_gencb(evhtp_request_t *req, cx_gencb(evhtp_request_t *req,
void *arg) void *arg)
@ -129,7 +298,7 @@ cx_gencb(evhtp_request_t *req,
evhtp_connection_t *conn; evhtp_connection_t *conn;
// clicon_handle h = arg; // clicon_handle h = arg;
fprintf(stderr, "%s\n", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){ if (req == NULL){
errno = EINVAL; errno = EINVAL;
return; return;
@ -145,67 +314,105 @@ cx_gencb(evhtp_request_t *req,
return; /* void */ return; /* void */
} }
static evhtp_res /*! /.well-known callback
cx_pre_accept(evhtp_connection_t *conn, * @see cx_genb
void *arg) */
static void
cx_path_wellknown(evhtp_request_t *req,
void *arg)
{ {
fprintf(stderr, "%s\n", __FUNCTION__); clicon_handle h = arg;
return EVHTP_RES_OK;
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, print_header, h);
/* get accepted connection */
/* set fcgi-like paramaters (ignore query vector) */
if (evhtp_params_set(h, req, NULL) < 0)
goto done;
/* call generic function */
if (api_well_known(h, req) < 0)
goto done;
/* Clear (fcgi) paramaters from this request */
if (evhtp_params_clear(h) < 0)
goto done;
done:
return; /* void */
} }
static evhtp_res /*! /restconf callback
cx_post_accept(evhtp_connection_t *conn, * @see cx_genb
void *arg)
{
fprintf(stderr, "%s\n", __FUNCTION__);
return EVHTP_RES_OK;
}
static int
print_header_(evhtp_header_t * header, void * arg) {
fprintf(stderr, "%s: %s\n", header->key, header->val);
return 0;
}
/*! Generic callback called if no other callbacks are matched
*/ */
static void static void
cx_path_restconf(evhtp_request_t *req, cx_path_restconf(evhtp_request_t *req,
void *arg) void *arg)
{ {
evhtp_connection_t *conn; evhtp_connection_t *conn;
// clicon_handle h = arg; clicon_handle h = arg;
struct evbuffer *b = NULL; struct evbuffer *b = NULL;
htp_method meth; cvec *qvec = NULL;
size_t len = 0;
cbuf *cblen = NULL;
fprintf(stderr, "%s\n", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){ if (req == NULL){
errno = EINVAL; errno = EINVAL;
goto done; goto done;
} }
if ((conn = evhtp_request_get_connection(req)) == NULL) /* input debug */
goto done; if (clicon_debug_get())
meth = evhtp_request_get_method(req); evhtp_headers_for_each(req->headers_in, print_header, h);
fprintf(stderr, "%s method:%d\n", __FUNCTION__, meth);
evhtp_headers_for_each(req->headers_in, print_header_, NULL);
if ((b = evbuffer_new()) == NULL){ if ((cblen = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
htp_sslutil_add_xheaders( /* Query vector, ie the ?a=x&b=y stuff */
req->headers_out, if ((qvec = cvec_new(0)) ==NULL){
conn->ssl, clicon_err(OE_UNIX, errno, "cvec_new");
HTP_SSLUTILS_XHDR_ALL); goto done;
}
/* get accepted connection */
if ((conn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
goto done;
}
/* Get all parameters from this request (resembling fcgi) */
if (evhtp_params_set(h, req, qvec) < 0)
goto done;
/* 1. create body */
if ((b = evbuffer_new()) == NULL){
clicon_err(OE_DAEMON, errno, "evbuffer_new");
goto done;
}
cprintf(cblen, "%lu", len);
/* 2. add headers (can mix with body) */
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Cache-Control", "no-cache", 0, 0));
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Type", "application/xrd+xml", 0, 0));
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Length", cbuf_get(cblen), 0, 0));
/* Optional? */
htp_sslutil_add_xheaders(req->headers_out, conn->ssl, HTP_SSLUTILS_XHDR_ALL);
/* 3. send reply */
evhtp_send_reply_start(req, EVHTP_RES_OK); evhtp_send_reply_start(req, EVHTP_RES_OK);
evbuffer_add(b, "hej\n", strlen("hej\n\n"));
evhtp_send_reply_body(req, b); evhtp_send_reply_body(req, b);
evhtp_send_reply_end(req); evhtp_send_reply_end(req);
// evhtp_headers_add_header(request->headers_out, evhtp_header_new("Host", "localhost", 0, 0)); evhtp_headers_add_headers(request->headers_out, headers); /* Clear (fcgi)paramaters */
if (evhtp_params_clear(h) < 0)
goto done;
done: done:
if (qvec)
cvec_free(qvec);
if (cblen)
cbuf_free(cblen);
if (b)
evhtp_safe_free(b, evbuffer_free);
return; /* void */ return; /* void */
} }
@ -246,26 +453,27 @@ int
main(int argc, main(int argc,
char **argv) char **argv)
{ {
int retval = -1; int retval = -1;
char *argv0 = argv[0]; char *argv0 = argv[0];
int c; int c;
clicon_handle h; clicon_handle h;
char *dir; char *dir;
int logdst = CLICON_LOG_SYSLOG; int logdst = CLICON_LOG_SYSLOG;
yang_stmt *yspec = NULL; yang_stmt *yspec = NULL;
char *str; char *str;
clixon_plugin *cp = NULL; clixon_plugin *cp = NULL;
cvec *nsctx_global = NULL; /* Global namespace context */ cvec *nsctx_global = NULL; /* Global namespace context */
size_t cligen_buflen; size_t cligen_buflen;
size_t cligen_bufthreshold; size_t cligen_bufthreshold;
uint16_t port = 443; uint16_t port = 443;
#ifdef _EVHTP_NYI #ifdef _EVHTP_NYI
char *stream_path; char *stream_path;
#endif #endif
evhtp_t *htp = NULL; evhtp_t *htp = NULL;
struct event_base *evbase = NULL; struct event_base *evbase = NULL;
evhtp_ssl_cfg_t *ssl_config = NULL; evhtp_ssl_cfg_t *ssl_config = NULL;
struct stat f_stat; struct stat f_stat;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst); clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -282,7 +490,7 @@ main(int argc,
usage(h, argv0); usage(h, argv0);
break; break;
case 'D' : /* debug */ case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv0); usage(h, argv0);
break; break;
case 'f': /* override config file */ case 'f': /* override config file */
@ -303,9 +511,9 @@ main(int argc,
/* /*
* Logs, error and debug to stderr or syslog, set debug level * Logs, error and debug to stderr or syslog, set debug level
*/ */
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL); clicon_debug_init(dbg, NULL);
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid()); clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){ if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){
clicon_err(OE_DAEMON, errno, "Setting signal"); clicon_err(OE_DAEMON, errno, "Setting signal");
@ -390,7 +598,6 @@ main(int argc,
} }
argc -= optind; argc -= optind;
argv += optind; argv += optind;
/* Check ssl mandatory options */ /* Check ssl mandatory options */
if (ssl_config->pemfile == NULL || ssl_config->privfile == NULL) if (ssl_config->pemfile == NULL || ssl_config->privfile == NULL)
usage(h, argv0); usage(h, argv0);
@ -412,6 +619,53 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */ /* Access the remaining argv/argc options (after --) w clicon-argv_get() */
clicon_argv_set(h, argv0, argc, argv); clicon_argv_set(h, argv0, argc, argv);
/* Init evhtp */
if ((evbase = event_base_new()) == NULL){
clicon_err(OE_UNIX, errno, "event_base_new");
goto done;
}
/* create a new evhtp_t instance */
if ((htp = evhtp_new(evbase, NULL)) == NULL){
clicon_err(OE_UNIX, errno, "evhtp_new");
goto done;
}
if (evhtp_ssl_init(htp, ssl_config) < 0){
clicon_err(OE_UNIX, errno, "evhtp_new");
goto done;
}
#ifndef EVHTP_DISABLE_EVTHR
evhtp_use_threads_wexit(htp, NULL, NULL, 4, NULL);
#endif
/* Callback before the connection is accepted. */
evhtp_set_pre_accept_cb(htp, cx_pre_accept, h);
/* Callback right after a connection is accepted. */
evhtp_set_post_accept_cb(htp, cx_post_accept, h);
/* Callback to be executed for all /restconf api calls */
if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
goto done;
}
/* Callback to be executed for all /restconf api calls */
if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
goto done;
}
/* Generic callback called if no other callbacks are matched */
evhtp_set_gencb(htp, cx_gencb, h);
/* bind to a socket, optionally with specific protocol support formatting
* If port is proteced must be done as root?
*/
if (evhtp_bind_socket(htp, "127.0.0.1", port, 128) < 0){
clicon_err(OE_UNIX, errno, "evhtp_bind_socket");
goto done;
}
if (restconf_drop_privileges(h, WWWUSER) < 0)
goto done;
/* Init cligen buffers */ /* Init cligen buffers */
cligen_buflen = clicon_option_int(h, "CLICON_CLI_BUF_START"); cligen_buflen = clicon_option_int(h, "CLICON_CLI_BUF_START");
cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD"); cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD");
@ -422,7 +676,6 @@ main(int argc,
*/ */
if (netconf_module_features(h) < 0) if (netconf_module_features(h) < 0)
goto done; goto done;
/* Create top-level yang spec and store as option */ /* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;
@ -491,8 +744,8 @@ main(int argc,
goto done; goto done;
/* Dump configuration options on debug */ /* Dump configuration options on debug */
if (debug) if (dbg)
clicon_option_dump(h, debug); clicon_option_dump(h, dbg);
/* Call start function in all plugins before we go interactive /* Call start function in all plugins before we go interactive
*/ */
@ -503,40 +756,6 @@ main(int argc,
if (clicon_options_main(h) < 0) if (clicon_options_main(h) < 0)
goto done; goto done;
/* Init evhtp */
if ((evbase = event_base_new()) == NULL){
clicon_err(OE_UNIX, errno, "event_base_new");
goto done;
}
/* create a new evhtp_t instance */
if ((htp = evhtp_new(evbase, NULL)) == NULL){
clicon_err(OE_UNIX, errno, "evhtp_new");
goto done;
}
if (evhtp_ssl_init(htp, ssl_config) < 0){
clicon_err(OE_UNIX, errno, "evhtp_new");
goto done;
}
/* Generic callback called if no other callbacks are matched */
evhtp_set_gencb(htp, cx_gencb, h);
/* Callback before the connection is accepted. */
evhtp_set_pre_accept_cb(htp, cx_pre_accept, h);
/* Callback right after a connection is accepted. */
evhtp_set_post_accept_cb(htp, cx_post_accept, h);
/* Callback to be executed on a specific path */
if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
goto done;
}
/* bind to a socket, optionally with specific protocol support formatting */
if (evhtp_bind_socket(htp, "127.0.0.1", port, 128) < 0){
clicon_err(OE_UNIX, errno, "evhtp_bind_socket");
goto done;
}
event_base_loop(evbase, 0); event_base_loop(evbase, 0);

View file

@ -65,166 +65,166 @@
#include "restconf_fcgi_lib.h" #include "restconf_fcgi_lib.h"
/*! HTTP error 400 /*! HTTP error 400
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_badrequest(clicon_handle h, restconf_badrequest(clicon_handle h,
FCGX_Request *r) FCGX_Request *req)
{ {
char *path; char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI"); path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(400, r->out); FCGX_SetExitStatus(400, req->out);
FCGX_FPrintF(r->out, "Status: 400 Bad Request\r\n"); /* 400 bad request */ FCGX_FPrintF(req->out, "Status: 400 Bad Request\r\n"); /* 400 bad request */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Clixon Bad request/h1>\n"); FCGX_FPrintF(req->out, "<h1>Clixon Bad request/h1>\n");
FCGX_FPrintF(r->out, "The requested URL %s or data is in some way badly formed.\n", FCGX_FPrintF(req->out, "The requested URL %s or data is in some way badly formed.\n",
path); path);
return 0; return 0;
} }
/*! HTTP error 401 /*! HTTP error 401
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_unauthorized(clicon_handle h, restconf_unauthorized(clicon_handle h,
FCGX_Request *r) FCGX_Request *req)
{ {
char *path; char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI"); path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(401, r->out); FCGX_SetExitStatus(401, req->out);
FCGX_FPrintF(r->out, "Status: 401 Unauthorized\r\n"); /* 401 unauthorized */ FCGX_FPrintF(req->out, "Status: 401 Unauthorized\r\n"); /* 401 unauthorized */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<error-tag>access-denied</error-tag>\n"); FCGX_FPrintF(req->out, "<error-tag>access-denied</error-tag>\n");
FCGX_FPrintF(r->out, "The requested URL %s was unauthorized.\n", path); FCGX_FPrintF(req->out, "The requested URL %s was unauthorized.\n", path);
return 0; return 0;
} }
/*! HTTP error 403 /*! HTTP error 403
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_forbidden(clicon_handle h, restconf_forbidden(clicon_handle h,
FCGX_Request *r) FCGX_Request *req)
{ {
char *path; char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI"); path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(403, r->out); FCGX_SetExitStatus(403, req->out);
FCGX_FPrintF(r->out, "Status: 403 Forbidden\r\n"); /* 403 forbidden */ FCGX_FPrintF(req->out, "Status: 403 Forbidden\r\n"); /* 403 forbidden */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Forbidden</h1>\n"); FCGX_FPrintF(req->out, "<h1>Forbidden</h1>\n");
FCGX_FPrintF(r->out, "The requested URL %s was forbidden.\n", path); FCGX_FPrintF(req->out, "The requested URL %s was forbidden.\n", path);
return 0; return 0;
} }
/*! HTTP error 404 /*! HTTP error 404
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_notfound(clicon_handle h, restconf_notfound(clicon_handle h,
FCGX_Request *r) FCGX_Request *req)
{ {
char *path; char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI"); path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(404, r->out); FCGX_SetExitStatus(404, req->out);
FCGX_FPrintF(r->out, "Status: 404 Not Found\r\n"); /* 404 not found */ FCGX_FPrintF(req->out, "Status: 404 Not Found\r\n"); /* 404 not found */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Not Found</h1>\n"); FCGX_FPrintF(req->out, "<h1>Not Found</h1>\n");
FCGX_FPrintF(r->out, "Not Found\n"); FCGX_FPrintF(req->out, "Not Found\n");
FCGX_FPrintF(r->out, "The requested URL %s was not found on this server.\n", FCGX_FPrintF(req->out, "The requested URL %s was not found on this server.\n",
path); path);
return 0; return 0;
} }
/*! HTTP error 406 Not acceptable /*! HTTP error 406 Not acceptable
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_notacceptable(clicon_handle h, restconf_notacceptable(clicon_handle h,
FCGX_Request *r) FCGX_Request *req)
{ {
char *path; char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI"); path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(406, r->out); FCGX_SetExitStatus(406, req->out);
FCGX_FPrintF(r->out, "Status: 406 Not Acceptable\r\n"); /* 406 not acceptible */ FCGX_FPrintF(req->out, "Status: 406 Not Acceptable\r\n"); /* 406 not acceptible */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Not Acceptable</h1>\n"); FCGX_FPrintF(req->out, "<h1>Not Acceptable</h1>\n");
FCGX_FPrintF(r->out, "Not Acceptable\n"); FCGX_FPrintF(req->out, "Not Acceptable\n");
FCGX_FPrintF(r->out, "The target resource does not have a current representation that would be acceptable to the user agent.\n", FCGX_FPrintF(req->out, "The target resource does not have a current representation that would be acceptable to the user agent.\n",
path); path);
return 0; return 0;
} }
/*! HTTP error 409 /*! HTTP error 409
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_conflict(FCGX_Request *r) restconf_conflict(FCGX_Request *req)
{ {
FCGX_SetExitStatus(409, r->out); FCGX_SetExitStatus(409, req->out);
FCGX_FPrintF(r->out, "Status: 409 Conflict\r\n"); /* 409 Conflict */ FCGX_FPrintF(req->out, "Status: 409 Conflict\r\n"); /* 409 Conflict */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Data resource already exists</h1>\n"); FCGX_FPrintF(req->out, "<h1>Data resource already exists</h1>\n");
return 0; return 0;
} }
/*! HTTP error 409 /*! HTTP error 409
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_unsupported_media(FCGX_Request *r) restconf_unsupported_media(FCGX_Request *req)
{ {
FCGX_SetExitStatus(415, r->out); FCGX_SetExitStatus(415, req->out);
FCGX_FPrintF(r->out, "Status: 415 Unsupported Media Type\r\n"); FCGX_FPrintF(req->out, "Status: 415 Unsupported Media Type\r\n");
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Unsupported Media Type</h1>\n"); FCGX_FPrintF(req->out, "<h1>Unsupported Media Type</h1>\n");
return 0; return 0;
} }
/*! HTTP error 500 /*! HTTP error 500
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_internal_server_error(clicon_handle h, restconf_internal_server_error(clicon_handle h,
FCGX_Request *r) FCGX_Request *req)
{ {
char *path; char *path;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
path = clixon_restconf_param_get(h, "REQUEST_URI"); path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_FPrintF(r->out, "Status: 500 Internal Server Error\r\n"); /* 500 internal server error */ FCGX_FPrintF(req->out, "Status: 500 Internal Server Error\r\n"); /* 500 internal server error */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Internal server error when accessing %s</h1>\n", path); FCGX_FPrintF(req->out, "<h1>Internal server error when accessing %s</h1>\n", path);
return 0; return 0;
} }
/*! HTTP error 501 /*! HTTP error 501
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
int int
restconf_notimplemented(FCGX_Request *r) restconf_notimplemented(FCGX_Request *req)
{ {
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Status: 501 Not Implemented\r\n"); FCGX_FPrintF(req->out, "Status: 501 Not Implemented\r\n");
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Not Implemented/h1>\n"); FCGX_FPrintF(req->out, "<h1>Not Implemented/h1>\n");
return 0; return 0;
} }
/*! Print all FCGI headers /*! Print all FCGI headers
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/ */
int int
restconf_test(FCGX_Request *r, restconf_test(FCGX_Request *req,
int dbg) int dbg)
{ {
char **environ = r->envp; char **environ = req->envp;
int i; int i;
clicon_debug(1, "All environment vars:"); clicon_debug(1, "All environment vars:");
@ -303,24 +303,24 @@ clixon_restconf_params_clear(clicon_handle h,
} }
/*! /*!
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
*/ */
cbuf * cbuf *
readdata(FCGX_Request *r) readdata(FCGX_Request *req)
{ {
int c; int c;
cbuf *cb; cbuf *cb;
if ((cb = cbuf_new()) == NULL) if ((cb = cbuf_new()) == NULL)
return NULL; return NULL;
while ((c = FCGX_GetChar(r->in)) != -1) while ((c = FCGX_GetChar(req->in)) != -1)
cprintf(cb, "%c", c); cprintf(cb, "%c", c);
return cb; return cb;
} }
/*! Return restconf error on get/head request /*! Return restconf error on get/head request
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
* @param[in] xerr XML error message from backend * @param[in] xerr XML error message from backend
* @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media Output media * @param[in] media Output media
@ -329,7 +329,7 @@ readdata(FCGX_Request *r)
*/ */
int int
api_return_err(clicon_handle h, api_return_err(clicon_handle h,
FCGX_Request *r, FCGX_Request *req,
cxobj *xerr, cxobj *xerr,
int pretty, int pretty,
restconf_media media, restconf_media media,
@ -381,23 +381,23 @@ api_return_err(clicon_handle h,
} }
if ((reason_phrase = restconf_code2reason(code)) == NULL) if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase=""; reason_phrase="";
FCGX_SetExitStatus(code, r->out); /* Created */ FCGX_SetExitStatus(code, req->out); /* Created */
FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); FCGX_FPrintF(req->out, "Status: %d %s\r\n", code, reason_phrase);
FCGX_FPrintF(r->out, "Content-Type: %s\r\n\r\n", restconf_media_int2str(media)); FCGX_FPrintF(req->out, "Content-Type: %s\r\n\r\n", restconf_media_int2str(media));
switch (media){ switch (media){
case YANG_DATA_XML: case YANG_DATA_XML:
if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0) if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
goto done; goto done;
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb)); clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){ if (pretty){
FCGX_FPrintF(r->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb)); FCGX_FPrintF(req->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb));
FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(r->out, " </errors>\r\n"); FCGX_FPrintF(req->out, " </errors>\r\n");
} }
else { else {
FCGX_FPrintF(r->out, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">", cbuf_get(cb)); FCGX_FPrintF(req->out, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">", cbuf_get(cb));
FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(r->out, "</errors>\r\n"); FCGX_FPrintF(req->out, "</errors>\r\n");
} }
break; break;
case YANG_DATA_JSON: case YANG_DATA_JSON:
@ -405,16 +405,16 @@ api_return_err(clicon_handle h,
goto done; goto done;
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb)); clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){ if (pretty){
FCGX_FPrintF(r->out, "{\n"); FCGX_FPrintF(req->out, "{\n");
FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n", FCGX_FPrintF(req->out, " \"ietf-restconf:errors\" : %s\n",
cbuf_get(cb)); cbuf_get(cb));
FCGX_FPrintF(r->out, "}\r\n"); FCGX_FPrintF(req->out, "}\r\n");
} }
else{ else{
FCGX_FPrintF(r->out, "{"); FCGX_FPrintF(req->out, "{");
FCGX_FPrintF(r->out, "\"ietf-restconf:errors\":"); FCGX_FPrintF(req->out, "\"ietf-restconf:errors\":");
FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(r->out, "}\r\n"); FCGX_FPrintF(req->out, "}\r\n");
} }
break; break;
default: default:
@ -434,14 +434,14 @@ api_return_err(clicon_handle h,
} }
/*! Print location header from FCGI environment /*! Print location header from FCGI environment
* @param[in] r Fastcgi request handle * @param[in] req Fastcgi request handle
* @param[in] xobj If set (eg POST) add to api-path * @param[in] xobj If set (eg POST) add to api-path
* $https on if connection operates in SSL mode, or an empty string otherwise * $https on if connection operates in SSL mode, or an empty string otherwise
* @note ports are ignored * @note ports are ignored
*/ */
int int
http_location(clicon_handle h, http_location(clicon_handle h,
FCGX_Request *r, FCGX_Request *req,
cxobj *xobj) cxobj *xobj)
{ {
int retval = -1; int retval = -1;
@ -460,14 +460,14 @@ http_location(clicon_handle h,
} }
if (xml2api_path_1(xobj, cb) < 0) if (xml2api_path_1(xobj, cb) < 0)
goto done; goto done;
FCGX_FPrintF(r->out, "Location: http%s://%s%s%s\r\n", FCGX_FPrintF(req->out, "Location: http%s://%s%s%s\r\n",
https?"s":"", https?"s":"",
host, host,
request_uri, request_uri,
cbuf_get(cb)); cbuf_get(cb));
} }
else else
FCGX_FPrintF(r->out, "Location: http%s://%s%s\r\n", FCGX_FPrintF(req->out, "Location: http%s://%s%s\r\n",
https?"s":"", https?"s":"",
host, host,
request_uri); request_uri);
@ -478,4 +478,3 @@ http_location(clicon_handle h,
return retval; return retval;
} }

View file

@ -49,12 +49,12 @@ int restconf_conflict(FCGX_Request *r);
int restconf_unsupported_media(FCGX_Request *r); int restconf_unsupported_media(FCGX_Request *r);
int restconf_internal_server_error(clicon_handle h, FCGX_Request *r); int restconf_internal_server_error(clicon_handle h, FCGX_Request *r);
int restconf_notimplemented(FCGX_Request *r); int restconf_notimplemented(FCGX_Request *r);
int restconf_test(FCGX_Request *r, int dbg); int restconf_test(FCGX_Request *r, int dbg);
int clixon_restconf_params_set(clicon_handle h, char **envp); int clixon_restconf_params_set(clicon_handle h,
char **envp);
int clixon_restconf_params_clear(clicon_handle h, char **envp); int clixon_restconf_params_clear(clicon_handle h, char **envp);
cbuf *readdata(FCGX_Request *r); cbuf *readdata(FCGX_Request *r);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int pretty, restconf_media media, int code0);
int pretty, enum restconf_media media, int code);
int http_location(clicon_handle h, FCGX_Request *r, cxobj *xobj); int http_location(clicon_handle h, FCGX_Request *r, cxobj *xobj);
#endif /* _RESTCONF_FCGI_LIB_H_ */ #endif /* _RESTCONF_FCGI_LIB_H_ */

View file

@ -77,9 +77,12 @@
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */ #include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
/* restconf */ /* restconf */
#include "restconf_lib.h" #include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_fcgi_lib.h" #include "restconf_api.h" /* generic not shared with plugins */
#include "restconf_methods.h"
#include "restconf_root.h" /* generic not shared with plugins */
#include "restconf_fcgi_lib.h" /* fcgi specific */
#include "restconf_methods.h" /* fcgi specific */
#include "restconf_methods_get.h" #include "restconf_methods_get.h"
#include "restconf_methods_post.h" #include "restconf_methods_post.h"
#include "restconf_stream.h" #include "restconf_stream.h"
@ -101,7 +104,7 @@
*/ */
static int static int
api_data(clicon_handle h, api_data(clicon_handle h,
FCGX_Request *r, FCGX_Request *req,
char *api_path, char *api_path,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
@ -117,21 +120,21 @@ api_data(clicon_handle h,
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD"); request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
if (strcmp(request_method, "OPTIONS")==0) if (strcmp(request_method, "OPTIONS")==0)
retval = api_data_options(h, r); retval = api_data_options(h, req);
else if (strcmp(request_method, "HEAD")==0) else if (strcmp(request_method, "HEAD")==0)
retval = api_data_head(h, r, api_path, pcvec, pi, qvec, pretty, media_out); retval = api_data_head(h, req, api_path, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "GET")==0) else if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, r, api_path, pcvec, pi, qvec, pretty, media_out); retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "POST")==0) else if (strcmp(request_method, "POST")==0)
retval = api_data_post(h, r, api_path, pi, qvec, data, pretty, media_out); retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PUT")==0) else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out); retval = api_data_put(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PATCH")==0) else if (strcmp(request_method, "PATCH")==0)
retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, pretty, media_out); retval = api_data_patch(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "DELETE")==0) else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, r, api_path, pi, pretty, media_out); retval = api_data_delete(h, req, api_path, pi, pretty, media_out);
else else
retval = restconf_notfound(h, r); retval = restconf_notfound(h, req);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval; return retval;
} }
@ -181,27 +184,44 @@ api_operations(clicon_handle h,
* In line with the best practices defined by [RFC7320], RESTCONF * In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located. * enables deployments to specify where the RESTCONF API is located.
*/ */
#if 0
static int static int
api_well_known(clicon_handle h, api_well_known(clicon_handle h,
FCGX_Request *r) FCGX_Request *req)
{ {
clicon_debug(1, "%s", __FUNCTION__); char *request_method;
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n"); FCGX_Request *body;
FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
FCGX_FPrintF(r->out, "\r\n"); /* call generic function */
FCGX_SetExitStatus(200, r->out); /* OK */ if (api_well_known(h, req) < 0)
FCGX_FPrintF(r->out, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\n"); goto done;
FCGX_FPrintF(r->out, " <Link rel='restconf' href='/restconf'/>\n");
FCGX_FPrintF(r->out, "</XRD>\r\n");
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
goto done;
}
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
if (strcmp(request_method, "GET") !=0 )
return restconf_method_notallowed(req, "GET");
restconf_reply_status_code(req, 200); /* OK */
restconf_reply_header_add(req, "Cache-Control", "no-cache");
restconf_reply_header_add(req, "Content-Type", "application/xrd+xml");
body = restconf_reply_body_start(req);
restconf_reply_body_add(body, NULL, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\n");
restconf_reply_body_add(body, NULL, " <Link rel='restconf' href='/restconf'/>\n");
restconf_reply_body_add(body, NULL, "</XRD>\r\n");
done:
return 0; return 0;
} }
#endif
/*! Retrieve the Top-Level API Resource /*! Retrieve the Top-Level API Resource
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
* @note Only returns null for operations and data,... * @note Only returns null for operations and data,...
* See RFC8040 3.3 * See RFC8040 3.3
* XXX doesnt check method
*/ */
static int static int
api_root(clicon_handle h, api_root(clicon_handle h,
@ -318,11 +338,11 @@ api_yang_library_version(clicon_handle h,
*/ */
static int static int
api_restconf(clicon_handle h, api_restconf(clicon_handle h,
FCGX_Request *r) FCGX_Request *req)
{ {
int retval = -1; int retval = -1;
char *path; char *path;
char *query; char *query = NULL;
char *method; char *method;
char **pvec = NULL; char **pvec = NULL;
int pn; int pn;
@ -357,7 +377,7 @@ api_restconf(clicon_handle h,
if (strcmp(media_str, "*/*") == 0) /* catch-all */ if (strcmp(media_str, "*/*") == 0) /* catch-all */
media_out = YANG_DATA_JSON; media_out = YANG_DATA_JSON;
else{ else{
retval = restconf_unsupported_media(r); retval = restconf_unsupported_media(req);
goto done; goto done;
} }
} }
@ -367,34 +387,35 @@ api_restconf(clicon_handle h,
goto done; goto done;
/* Sanity check of path. Should be /restconf/ */ /* Sanity check of path. Should be /restconf/ */
if (pn < 2){ if (pn < 2){
restconf_notfound(h, r); restconf_notfound(h, req);
goto ok; goto ok;
} }
if (strlen(pvec[0]) != 0){ if (strlen(pvec[0]) != 0){
retval = restconf_notfound(h, r); retval = restconf_notfound(h, req);
goto done; goto done;
} }
if (strcmp(pvec[1], RESTCONF_API)){ if (strcmp(pvec[1], RESTCONF_API)){
retval = restconf_notfound(h, r); retval = restconf_notfound(h, req);
goto done; goto done;
} }
restconf_test(r, 1); restconf_test(req, 1);
if (pn == 2){ if (pn == 2){
retval = api_root(h, r, pretty, media_out); retval = api_root(h, req, pretty, media_out);
goto done; goto done;
} }
if ((method = pvec[2]) == NULL){ if ((method = pvec[2]) == NULL){
retval = restconf_notfound(h, r); retval = restconf_notfound(h, req);
goto done; goto done;
} }
clicon_debug(1, "%s: method=%s", __FUNCTION__, method); clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
if (str2cvec(query, '&', '=', &qvec) < 0) if (query != NULL && strlen(query))
goto done; if (str2cvec(query, '&', '=', &qvec) < 0)
goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done; goto done;
/* data */ /* data */
if ((cb = readdata(r)) == NULL) if ((cb = readdata(req)) == NULL)
goto done; goto done;
data = cbuf_get(cb); data = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); clicon_debug(1, "%s DATA=%s", __FUNCTION__, data);
@ -404,7 +425,7 @@ api_restconf(clicon_handle h,
/* If present, check credentials. See "plugin_credentials" in plugin /* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5 * See RFC 8040 section 2.5
*/ */
if ((authenticated = clixon_plugin_auth_all(h, r)) < 0) if ((authenticated = clixon_plugin_auth_all(h, req)) < 0)
goto done; goto done;
clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
@ -417,7 +438,7 @@ api_restconf(clicon_handle h,
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
goto done; goto done;
if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xerr, pretty, media_out, 0) < 0) if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -425,23 +446,23 @@ api_restconf(clicon_handle h,
} }
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
if (strcmp(method, "yang-library-version")==0){ if (strcmp(method, "yang-library-version")==0){
if (api_yang_library_version(h, r, pretty, media_out) < 0) if (api_yang_library_version(h, req, pretty, media_out) < 0)
goto done; goto done;
} }
else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */ else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */
if (api_data(h, r, path, pcvec, 2, qvec, data, if (api_data(h, req, path, pcvec, 2, qvec, data,
pretty, media_out) < 0) pretty, media_out) < 0)
goto done; goto done;
} }
else if (strcmp(method, "operations") == 0){ /* rpc */ else if (strcmp(method, "operations") == 0){ /* rpc */
if (api_operations(h, r, path, pcvec, 2, qvec, data, if (api_operations(h, req, path, pcvec, 2, qvec, data,
pretty, media_out) < 0) pretty, media_out) < 0)
goto done; goto done;
} }
else if (strcmp(method, "test") == 0) else if (strcmp(method, "test") == 0)
restconf_test(r, 0); restconf_test(req, 0);
else else
restconf_notfound(h, r); restconf_notfound(h, req);
ok: ok:
retval = 0; retval = 0;
done: done:
@ -534,7 +555,7 @@ main(int argc,
int sock; int sock;
char *argv0 = argv[0]; char *argv0 = argv[0];
FCGX_Request request; FCGX_Request request;
FCGX_Request *r = &request; FCGX_Request *req = &request;
int c; int c;
char *sockpath; char *sockpath;
char *path; char *path;
@ -551,6 +572,7 @@ main(int argc,
cvec *nsctx_global = NULL; /* Global namespace context */ cvec *nsctx_global = NULL; /* Global namespace context */
size_t cligen_buflen; size_t cligen_buflen;
size_t cligen_bufthreshold; size_t cligen_bufthreshold;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst); clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -567,7 +589,7 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
break; break;
case 'D' : /* debug */ case 'D' : /* debug */
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(h, argv[0]); usage(h, argv[0]);
break; break;
case 'f': /* override config file */ case 'f': /* override config file */
@ -587,9 +609,9 @@ main(int argc,
/* /*
* Logs, error and debug to stderr or syslog, set debug level * Logs, error and debug to stderr or syslog, set debug level
*/ */
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL); clicon_debug_init(dbg, NULL);
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid()); clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){ if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){
clicon_err(OE_DAEMON, errno, "Setting signal"); clicon_err(OE_DAEMON, errno, "Setting signal");
@ -663,6 +685,7 @@ main(int argc,
cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD"); cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD");
cbuf_alloc_set(cligen_buflen, cligen_bufthreshold); cbuf_alloc_set(cligen_buflen, cligen_bufthreshold);
/* Add (hardcoded) netconf features in case ietf-netconf loaded here /* Add (hardcoded) netconf features in case ietf-netconf loaded here
* Otherwise it is loaded in netconf_module_load below * Otherwise it is loaded in netconf_module_load below
*/ */
@ -737,8 +760,8 @@ main(int argc,
goto done; goto done;
/* Dump configuration options on debug */ /* Dump configuration options on debug */
if (debug) if (dbg)
clicon_option_dump(h, debug); clicon_option_dump(h, dbg);
/* Call start function in all plugins before we go interactive /* Call start function in all plugins before we go interactive
*/ */
@ -758,7 +781,21 @@ main(int argc,
clicon_err(OE_CFG, errno, "FCGX_OpenSocket"); clicon_err(OE_CFG, errno, "FCGX_OpenSocket");
goto done; goto done;
} }
#if 1
{
/* Change group of fcgi sock fronting reverse proxy to WWWUSER, the effective group is clicon
* which is backend. */
gid_t wgid = -1;
if (group_name2gid(WWWUSER, &wgid) < 0){
clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.", WWWUSER);
goto done;
}
if (chown(sockpath, -1, wgid) < 0){
clicon_err(OE_CFG, errno, "chown");
goto done;
}
}
#endif
if (clicon_socket_set(h, sock) < 0) if (clicon_socket_set(h, sock) < 0)
goto done; goto done;
/* umask settings may interfer: we want group to write: this is 774 */ /* umask settings may interfer: we want group to write: this is 774 */
@ -766,14 +803,18 @@ main(int argc,
clicon_err(OE_UNIX, errno, "chmod"); clicon_err(OE_UNIX, errno, "chmod");
goto done; goto done;
} }
if (FCGX_InitRequest(r, sock, 0) != 0){ #if 1
if (restconf_drop_privileges(h, WWWUSER) < 0)
goto done;
#endif
if (FCGX_InitRequest(req, sock, 0) != 0){
clicon_err(OE_CFG, errno, "FCGX_InitRequest"); clicon_err(OE_CFG, errno, "FCGX_InitRequest");
goto done; goto done;
} }
while (1) { while (1) {
finish = 1; /* If zero, dont finish request, initiate new */ finish = 1; /* If zero, dont finish request, initiate new */
if (FCGX_Accept_r(r) < 0) { if (FCGX_Accept_r(req) < 0) {
clicon_err(OE_CFG, errno, "FCGX_Accept_r"); clicon_err(OE_CFG, errno, "FCGX_Accept_r");
goto done; goto done;
} }
@ -793,32 +834,32 @@ main(int argc,
/* Translate from FCGI parameter form to Clixon runtime data /* Translate from FCGI parameter form to Clixon runtime data
* XXX: potential name collision? * XXX: potential name collision?
*/ */
if (clixon_restconf_params_set(h, r->envp) < 0) if (clixon_restconf_params_set(h, req->envp) < 0)
goto done; goto done;
if ((path = clixon_restconf_param_get(h, "REQUEST_URI")) != NULL){ if ((path = clixon_restconf_param_get(h, "REQUEST_URI")) != NULL){
clicon_debug(1, "path: %s", path); clicon_debug(1, "path: %s", path);
if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0) if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0)
api_restconf(h, r); /* This is the function */ api_restconf(h, req); /* This is the function */
else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) { else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
api_stream(h, r, stream_path, &finish); api_stream(h, req, stream_path, &finish);
} }
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) { else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
api_well_known(h, r); /* */ api_well_known(h, req); /* */
} }
else{ else{
clicon_debug(1, "top-level %s not found", path); clicon_debug(1, "top-level %s not found", path);
restconf_notfound(h, r); restconf_notfound(h, req);
} }
} }
else else
clicon_debug(1, "NULL URI"); clicon_debug(1, "NULL URI");
if (clixon_restconf_params_clear(h, r->envp) < 0) if (clixon_restconf_params_clear(h, req->envp) < 0)
goto done; goto done;
if (finish) if (finish)
FCGX_Finish_r(r); FCGX_Finish_r(req);
else{ /* A handler is forked so we initiate a new request after instead else{ /* A handler is forked so we initiate a new request after instead
of finnishing the old */ of finnishing the old */
if (FCGX_InitRequest(r, sock, 0) != 0){ if (FCGX_InitRequest(req, sock, 0) != 0){
clicon_err(OE_CFG, errno, "FCGX_InitRequest"); clicon_err(OE_CFG, errno, "FCGX_InitRequest");
goto done; goto done;
} }

View file

@ -59,6 +59,7 @@
/* clicon */ /* clicon */
#include <clixon/clixon.h> #include <clixon/clixon.h>
#include "restconf_api.h"
#include "restconf_lib.h" #include "restconf_lib.h"
/* See RFC 8040 Section 7: Mapping from NETCONF<error-tag> to Status Code /* See RFC 8040 Section 7: Mapping from NETCONF<error-tag> to Status Code
@ -443,6 +444,13 @@ clixon_restconf_param_set(clicon_handle h,
return clicon_data_set(h, param, val); return clicon_data_set(h, param, val);
} }
/*! Delete restconf http parameter
* @param[in] h Clicon handle
* @param[in] name Data name
* @retval 0 OK
* @retval -1 Error
* Currently using clixon runtime data but there is risk for colliding names
*/
int int
clixon_restconf_param_del(clicon_handle h, clixon_restconf_param_del(clicon_handle h,
char *param) char *param)
@ -469,3 +477,78 @@ restconf_uripath(clicon_handle h)
*q = '\0'; *q = '\0';
return path; return path;
} }
/*! Drop privileges from root to user (or already at user)
* @param[in] h Clicon handle
* @param[in] user Drop to this level
* Group set to clicon to communicate with backend
*/
int
restconf_drop_privileges(clicon_handle h,
char *user)
{
int retval = -1;
uid_t newuid = -1;
uid_t uid;
char *group;
gid_t gid = -1;
clicon_debug(1, "%s", __FUNCTION__);
/* Sanity check: backend group exists */
if ((group = clicon_sock_group(h)) == NULL){
clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
return -1;
}
if (group_name2gid(group, &gid) < 0){
clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n" /* \n required here due to multi-line log */
"The config demon requires a valid group to create a server UNIX socket\n"
"Define a valid CLICON_SOCK_GROUP in %s or via the -g option\n"
"or create the group and add the user to it. Check documentation for how to do this on your platform",
group,
clicon_configfile(h));
goto done;
}
/* Get (wanted) new www user id */
if (name2uid(user, &newuid) < 0){
clicon_err(OE_DAEMON, errno, "'%s' is not a valid user .\n", user);
goto done;
}
/* get current backend userid, if already at this level OK */
if ((uid = getuid()) == newuid)
goto ok;
if (uid != 0){
clicon_err(OE_DAEMON, EPERM, "Privileges can only be dropped from root user (uid is %u)\n", uid);
goto done;
}
if (setgid(gid) == -1) {
clicon_err(OE_DAEMON, errno, "setgid %d", gid);
goto done;
}
if (drop_priv_perm(newuid) < 0)
goto done;
/* Verify you cannot regain root privileges */
if (setuid(0) != -1){
clicon_err(OE_DAEMON, EPERM, "Could regain root privilieges");
goto done;
}
clicon_debug(1, "%s dropped privileges from root to %s(%d)",
__FUNCTION__, user, newuid);
ok:
retval = 0;
done:
return retval;
}
/*! HTTP error 405
* @param[in] req Generic Www handle
* @param[in] allow Which methods are allowed
*/
int
restconf_method_notallowed(void *req,
char *allow)
{
restconf_reply_status_code(req, 405);
restconf_reply_header_add(req, "Allow", "%s", allow);
return 0;
}

View file

@ -38,21 +38,8 @@
#define _RESTCONF_LIB_H_ #define _RESTCONF_LIB_H_
/* /*
* Constants * Types
*/ */
#define RESTCONF_API "restconf"
/* RESTCONF enables deployments to specify where the RESTCONF API is
located. The client discovers this by getting the "/.well-known/host-meta"
resource
*/
#define RESTCONF_WELL_KNOWN "/.well-known/host-meta"
/*
* Variables
*/
/*! RESTCONF media types /*! RESTCONF media types
* @see http_media_map * @see http_media_map
* (also in clixon_restconf.h) * (also in clixon_restconf.h)
@ -81,5 +68,7 @@ char *clixon_restconf_param_get(clicon_handle h, char *param);
int clixon_restconf_param_set(clicon_handle h, char *param, char *val); int clixon_restconf_param_set(clicon_handle h, char *param, char *val);
int clixon_restconf_param_del(clicon_handle h, char *param); int clixon_restconf_param_del(clicon_handle h, char *param);
char *restconf_uripath(clicon_handle h); char *restconf_uripath(clicon_handle h);
int restconf_drop_privileges(clicon_handle h, char *user);
int restconf_method_notallowed(void *req, char *allow);
#endif /* _RESTCONF_LIB_H_ */ #endif /* _RESTCONF_LIB_H_ */

View file

@ -306,7 +306,7 @@ api_data_write(clicon_handle h,
} }
#if 0 #if 0
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "%s xret:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xret, "%s xret:", __FUNCTION__);
#endif #endif
if (xml_child_nr(xret) == 0){ /* Object does not exist */ if (xml_child_nr(xret) == 0){ /* Object does not exist */

View file

@ -221,7 +221,7 @@ api_data_get2(clicon_handle h,
* We need to cut that tree to only the object. * We need to cut that tree to only the object.
*/ */
#if 0 /* DEBUG */ #if 0 /* DEBUG */
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "%s xret:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xret, "%s xret:", __FUNCTION__);
#endif #endif
/* Check if error return */ /* Check if error return */

View file

@ -307,7 +307,7 @@ api_data_post(clicon_handle h,
if (restconf_insert_attributes(xdata, qvec) < 0) if (restconf_insert_attributes(xdata, qvec) < 0)
goto done; goto done;
#if 1 #if 1
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xdata, "%s xdata:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xdata, "%s xdata:", __FUNCTION__);
#endif #endif
@ -512,7 +512,7 @@ api_operations_post_input(clicon_handle h,
* <data><input xmlns="urn:example:clixon">...</input></data> * <data><input xmlns="urn:example:clixon">...</input></data>
*/ */
#if 1 #if 1
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xdata, "%s xdata:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xdata, "%s xdata:", __FUNCTION__);
#endif #endif
/* Validate that exactly only <input> tag */ /* Validate that exactly only <input> tag */
@ -614,7 +614,7 @@ api_operations_post_output(clicon_handle h,
xml_name_set(xoutput, "output"); xml_name_set(xoutput, "output");
/* xoutput should now look: <output><x xmlns="uri">0</x></output> */ /* xoutput should now look: <output><x xmlns="uri">0</x></output> */
#if 1 #if 1
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xoutput, "%s xoutput:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xoutput, "%s xoutput:", __FUNCTION__);
#endif #endif
@ -843,7 +843,7 @@ api_operations_post(clicon_handle h,
/* Here xtop is: /* Here xtop is:
<rpc username="foo"><myfn xmlns="uri"><x>42</x></myfn></rpc> */ <rpc username="foo"><myfn xmlns="uri"><x>42</x></myfn></rpc> */
#if 1 #if 1
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xtop, "%s 5. Translate input args:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xtop, "%s 5. Translate input args:", __FUNCTION__);
#endif #endif
/* 6. Validate outgoing RPC and fill in defaults */ /* 6. Validate outgoing RPC and fill in defaults */
@ -874,7 +874,7 @@ api_operations_post(clicon_handle h,
* <rpc username="foo"><myfn xmlns="uri"><x>42</x><y>99</y></myfn></rpc> * <rpc username="foo"><myfn xmlns="uri"><x>42</x><y>99</y></myfn></rpc>
*/ */
#if 0 #if 0
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xtop, "%s 6. Validate and defaults:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xtop, "%s 6. Validate and defaults:", __FUNCTION__);
#endif #endif
/* 7. Send to RPC handler, either local or backend /* 7. Send to RPC handler, either local or backend
@ -909,7 +909,7 @@ api_operations_post(clicon_handle h,
* <rpc-reply><x xmlns="uri">0</x></rpc-reply> * <rpc-reply><x xmlns="uri">0</x></rpc-reply>
*/ */
#if 1 #if 1
if (debug) if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "%s Receive reply:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xret, "%s Receive reply:", __FUNCTION__);
#endif #endif
youtput = yang_find(yrpc, Y_OUTPUT, NULL); youtput = yang_find(yrpc, Y_OUTPUT, NULL);

View file

@ -0,0 +1,118 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
* Generic restconf root handlers eg for /restconf /.well-known, etc
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <libgen.h>
#include <sys/stat.h> /* chmod */
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h"
#include "restconf_api.h"
#include "restconf_root.h"
/*! Determine the root of the RESTCONF API
* @param[in] h Clicon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] cb Body buffer
* @see RFC8040 3.1 and RFC7320
* In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located.
*/
int
api_well_known(clicon_handle h,
void *req)
{
int retval = -1;
char *request_method;
cbuf *cb = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
goto done;
}
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
if (strcmp(request_method, "GET") != 0){
restconf_method_notallowed(req, "GET");
goto ok;
}
restconf_reply_status_code(req, 200); /* OK */
restconf_reply_header_add(req, "Cache-Control", "no-cache");
restconf_reply_header_add(req, "Content-Type", "application/xrd+xml");
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\n");
cprintf(cb, " <Link rel='restconf' href='/restconf'/>\n");
cprintf(cb, "</XRD>\r\n");
/* Must be after body */
restconf_reply_header_add(req, "Content-Length", "%d", cbuf_len(cb));
if (restconf_reply_send(req, cb) < 0)
goto done;
ok:
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -0,0 +1,57 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
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 *****
*
* Generic restconf root handlers eg for /restconf /.well-known, etc
*/
#ifndef _RESTCONF_ROOT_H_
#define _RESTCONF_ROOT_H_
/*
* Constants
*/
#define RESTCONF_API "restconf"
/* RESTCONF enables deployments to specify where the RESTCONF API is
located. The client discovers this by getting the "/.well-known/host-meta"
resource
*/
#define RESTCONF_WELL_KNOWN "/.well-known/host-meta"
/*
* Prototypes
*/
int api_well_known(clicon_handle h, void *req);
#endif /* _RESTCONF_ROOT_H_ */

View file

@ -235,6 +235,7 @@ AC_ARG_WITH([restconf],
# Common actions for all restconf packages # Common actions for all restconf packages
if test "x${with_restconf}" != "x"; then if test "x${with_restconf}" != "x"; then
# This is for changing web user default www-data # This is for changing web user default www-data
# Should this be a runtime option?
AC_ARG_WITH([wwwuser], AC_ARG_WITH([wwwuser],
[AS_HELP_STRING([--with-wwwuser=<user>],[Set www user different from www-data])]) [AS_HELP_STRING([--with-wwwuser=<user>],[Set www user different from www-data])])
if test "${with_wwwuser}"; then if test "${with_wwwuser}"; then

View file

@ -68,11 +68,11 @@ Clixon is written in C. The plugins are written in C. The CLI
specification uses [CLIgen](http://github.com/olofhagsand/cligen) specification uses [CLIgen](http://github.com/olofhagsand/cligen)
## How to best understand Clixon? ## How to best understand Clixon?
Run the Clixon example, in the [example](../example) directory. Run the Clixon main example, in the [example](../example) directory or [examples repo](https://github.com/clicon/clixon-examples), or [main documentation](https://clixon-docs.readthedocs.io)
## Hello world? ## Hello world?
One of the examples is [a hello world example](../example/hello). Please start with that. One of the examples is [a hello world example](https://github.com/clicon/clixon-examples/hello). Please start with that.
## How do you build and install Clixon? ## How do you build and install Clixon?
Clixon: Clixon:
@ -128,8 +128,7 @@ clicon:x:1001:<user>,www-data
## How do I use the CLI? ## How do I use the CLI?
The easiest way to use Clixon is via the CLI. Once the backend is started The easiest way to use Clixon is via the CLI. In the main example, once the backend is started you can start the auto-cli. Example:
Example:
``` ```
clixon_cli -f /usr/local/etc/example.xml clixon_cli -f /usr/local/etc/example.xml
cli> set interfaces interface eth9 ? cli> set interfaces interface eth9 ?

View file

@ -159,7 +159,7 @@ main_commit(clicon_handle h,
/* Get all added i/fs */ /* Get all added i/fs */
if (xpath_vec_flag(target, nsc, "//interface", XML_FLAG_ADD, &vec, &len) < 0) if (xpath_vec_flag(target, nsc, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
return -1; return -1;
if (debug) if (clicon_debug_get())
for (i=0; i<len; i++) /* Loop over added i/fs */ for (i=0; i<len; i++) /* Loop over added i/fs */
xml_print(stdout, vec[i]); /* Print the added interface */ xml_print(stdout, vec[i]); /* Print the added interface */
done: done:

View file

@ -78,8 +78,8 @@ show("Show a particular state of the system"){
xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{ xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{
@datamodel, cli_show_auto("candidate", "xml"); @datamodel, cli_show_auto("candidate", "xml");
} }
cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{ cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/", 0, "set ");{
@datamodel, cli_show_auto("candidate", "cli"); @datamodel, cli_show_auto("candidate", "cli", "set ");
} }
netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{ netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{
@datamodel, cli_show_auto("candidate", "netconf"); @datamodel, cli_show_auto("candidate", "netconf");

View file

@ -46,11 +46,6 @@
#define CLICON_LOG_STDOUT 4 /* print logs on stdout */ #define CLICON_LOG_STDOUT 4 /* print logs on stdout */
#define CLICON_LOG_FILE 8 /* print logs on clicon_log_filename */ #define CLICON_LOG_FILE 8 /* print logs on clicon_log_filename */
/*
* Variables
*/
extern int debug;
/* /*
* Prototypes * Prototypes
*/ */
@ -67,6 +62,8 @@ int clicon_log(int level, char *format, ...);
int clicon_debug(int dbglevel, char *format, ...); int clicon_debug(int dbglevel, char *format, ...);
#endif #endif
int clicon_debug_init(int dbglevel, FILE *f); int clicon_debug_init(int dbglevel, FILE *f);
int clicon_debug_get(void);
char *mon2name(int md); char *mon2name(int md);
#endif /* _CLIXON_LOG_H_ */ #endif /* _CLIXON_LOG_H_ */

View file

@ -58,20 +58,24 @@
*/ */
/*! Controls how keywords a generated in CLI syntax / prints from object model /*! Controls how keywords a generated in CLI syntax / prints from object model
* Example YANG: * Example YANG:
* container c{
* list a { * list a {
* key x; * key x;
* leaf x; * leaf x;
* leaf y; * leaf y;
* } * }
* NONE: a <x> <y>; * }
* VARS: a <x> y <y>; * NONE: c a <x> <y>;
* ALL: a x <x> y <y>; * VARS: c a <x> y <y>;
* ALL: c a x <x> y <y>;
* HIDE: a x <x> y <y>;
*/ */
enum genmodel_type{ enum genmodel_type{
GT_ERR =-1, /* Error */ GT_ERR =-1, /* Error */
GT_NONE=0, /* No extra keywords */ GT_NONE=0, /* No extra keywords */
GT_VARS, /* Keywords on non-key variables */ GT_VARS, /* Keywords on non-key variables */
GT_ALL, /* Keywords on all variables */ GT_ALL, /* Keywords on all variables */
GT_HIDE, /* Keywords on all variables and hide container around lists */
}; };
/*! See clixon-config.yang type startup_mode */ /*! See clixon-config.yang type startup_mode */

View file

@ -245,6 +245,7 @@ int yang_config(yang_stmt *ys);
int yang_config_ancestor(yang_stmt *ys); int yang_config_ancestor(yang_stmt *ys);
int yang_features(clicon_handle h, yang_stmt *yt); int yang_features(clicon_handle h, yang_stmt *yt);
cvec *yang_arg2cvec(yang_stmt *ys, char *delimi); cvec *yang_arg2cvec(yang_stmt *ys, char *delimi);
int yang_container_cli_hide(yang_stmt *ys, int gt);
int yang_key_match(yang_stmt *yn, char *name); int yang_key_match(yang_stmt *yn, char *name);
int yang_type_cache_regexp_set(yang_stmt *ytype, int rxmode, cvec *regexps); int yang_type_cache_regexp_set(yang_stmt *ytype, int rxmode, cvec *regexps);

View file

@ -457,7 +457,7 @@ xmldb_get_nocache(clicon_handle h,
if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__); clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__);
#endif #endif
if (debug>1) if (clicon_debug_get()>1)
clicon_xml2file(stderr, xt, 0, 1); clicon_xml2file(stderr, xt, 0, 1);
*xtop = xt; *xtop = xt;
xt = NULL; xt = NULL;
@ -565,7 +565,7 @@ xmldb_get_cache(clicon_handle h,
/* Copy the matching parts of the (relevant) XML tree. /* Copy the matching parts of the (relevant) XML tree.
* If cache was empty, also update to datastore cache * If cache was empty, also update to datastore cache
*/ */
if (debug>1) if (clicon_debug_get()>1)
clicon_xml2file(stderr, x1t, 0, 1); clicon_xml2file(stderr, x1t, 0, 1);
*xtop = x1t; *xtop = x1t;
retval = 0; retval = 0;
@ -639,7 +639,7 @@ xmldb_get_zerocopy(clicon_handle h,
/* Apply default values (removed in clear function) */ /* Apply default values (removed in clear function) */
if (xml_default_recurse(x0t) < 0) if (xml_default_recurse(x0t) < 0)
goto done; goto done;
if (debug>1) if (clicon_debug_get()>1)
clicon_xml2file(stderr, x0t, 0, 1); clicon_xml2file(stderr, x0t, 0, 1);
*xtop = x0t; *xtop = x0t;
retval = 0; retval = 0;

View file

@ -58,8 +58,12 @@
#include "clixon_err.h" #include "clixon_err.h"
#include "clixon_log.h" #include "clixon_log.h"
/* The global debug level. 0 means no debug */ /* The global debug level. 0 means no debug
int debug = 0; * @note There are pros and cons in having the debug state as a global variable. The
* alternative to bind it to the clicon handle (h) was considered but it limits its
* usefulness, since not all functions have h
*/
static int _clixon_debug = 0;
/* Bitmask whether to log to syslog or stderr: CLICON_LOG_STDERR | CLICON_LOG_SYSLOG */ /* Bitmask whether to log to syslog or stderr: CLICON_LOG_STDERR | CLICON_LOG_SYSLOG */
static int _logflags = 0x0; static int _logflags = 0x0;
@ -67,6 +71,7 @@ static int _logflags = 0x0;
/* Set to open file to write debug messages directly to file */ /* Set to open file to write debug messages directly to file */
static FILE *_logfile = NULL; static FILE *_logfile = NULL;
/*! Initialize system logger. /*! Initialize system logger.
* *
* Make syslog(3) calls with specified ident and gates calls of level upto specified level (upto). * Make syslog(3) calls with specified ident and gates calls of level upto specified level (upto).
@ -217,7 +222,7 @@ clicon_log_str(int level,
/* syslog makes own filtering, we do it here: /* syslog makes own filtering, we do it here:
* if normal (not debug) then filter loglevels >= debug * if normal (not debug) then filter loglevels >= debug
*/ */
if (debug == 0 && level >= LOG_DEBUG) if (_clixon_debug == 0 && level >= LOG_DEBUG)
goto done; goto done;
if (_logflags & CLICON_LOG_STDERR){ if (_logflags & CLICON_LOG_STDERR){
flogtime(stderr); flogtime(stderr);
@ -231,6 +236,7 @@ clicon_log_str(int level,
flogtime(_logfile); flogtime(_logfile);
fprintf(_logfile, "%s\n", msg); fprintf(_logfile, "%s\n", msg);
fflush(_logfile); fflush(_logfile);
} }
/* Enable this if you want syslog in a stream. But there are problems with /* Enable this if you want syslog in a stream. But there are problems with
@ -288,7 +294,6 @@ clicon_log(int level,
return retval; return retval;
} }
/*! Initialize debug messages. Set debug level. /*! Initialize debug messages. Set debug level.
* *
* Initialize debug module. The level is used together with clicon_debug(dbglevel) calls as follows: * Initialize debug module. The level is used together with clicon_debug(dbglevel) calls as follows:
@ -309,10 +314,16 @@ int
clicon_debug_init(int dbglevel, clicon_debug_init(int dbglevel,
FILE *f) FILE *f)
{ {
debug = dbglevel; /* Global variable */ _clixon_debug = dbglevel; /* Global variable */
return 0; return 0;
} }
int
clicon_debug_get(void)
{
return _clixon_debug;
}
/*! Print a debug message with debug-level. Settings determine where msg appears. /*! Print a debug message with debug-level. Settings determine where msg appears.
* *
* If the dbglevel passed in the function is equal to or lower than the one set by * If the dbglevel passed in the function is equal to or lower than the one set by
@ -335,7 +346,7 @@ clicon_debug(int dbglevel,
char *msg = NULL; char *msg = NULL;
int retval = -1; int retval = -1;
if (dbglevel > debug) /* debug mask */ if (dbglevel > _clixon_debug) /* compare debug mask with global variable */
return 0; return 0;
/* first round: compute length of debug message */ /* first round: compute length of debug message */
va_start(args, format); va_start(args, format);

View file

@ -1305,8 +1305,9 @@ netconf_module_load(clicon_handle h)
/* Load yang spec */ /* Load yang spec */
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0) if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
goto done; goto done;
if (yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0) if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277"))
goto done; if (yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
goto done;
/* YANG module revision change management */ /* YANG module revision change management */
if (clicon_option_bool(h, "CLICON_XML_CHANGELOG")) if (clicon_option_bool(h, "CLICON_XML_CHANGELOG"))
if (yang_spec_parse_module(h, "clixon-xml-changelog", NULL, yspec)< 0) if (yang_spec_parse_module(h, "clixon-xml-changelog", NULL, yspec)< 0)

View file

@ -85,6 +85,7 @@ static const map_str2int cli_genmodel_map[] = {
{"NONE", GT_NONE}, {"NONE", GT_NONE},
{"VARS", GT_VARS}, {"VARS", GT_VARS},
{"ALL", GT_ALL}, {"ALL", GT_ALL},
{"HIDE", GT_HIDE},
{NULL, -1} {NULL, -1}
}; };
@ -612,7 +613,7 @@ clicon_option_del(clicon_handle h,
* But sometimes there are type conversions, etc which makes it more * But sometimes there are type conversions, etc which makes it more
* convenient to make wrapper functions. Or not? * convenient to make wrapper functions. Or not?
*-----------------------------------------------------------------*/ *-----------------------------------------------------------------*/
/*! Wether to generate CLIgen syntax from datamodel or not (0 or 1) /*! Whether to generate CLIgen syntax from datamodel or not (0, 1 or 2)
* Must be used with a previous clicon_option_exists(). * Must be used with a previous clicon_option_exists().
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @retval flag If set, generate CLI code from yang model, otherwise not * @retval flag If set, generate CLI code from yang model, otherwise not

View file

@ -1559,7 +1559,7 @@ clixon_xml_find_api_path(cxobj *xt,
/* Parse api-path string to structured clixon-path data */ /* Parse api-path string to structured clixon-path data */
if (api_path_parse(api_path, &cplist) < 0) if (api_path_parse(api_path, &cplist) < 0)
goto done; goto done;
if (debug) if (clicon_debug_get())
clixon_path_print(stderr, cplist); clixon_path_print(stderr, cplist);
/* Resolve module:name to yang-stmt, fail if not successful */ /* Resolve module:name to yang-stmt, fail if not successful */
if ((ret = api_path_resolve(cplist, yt)) < 0) if ((ret = api_path_resolve(cplist, yt)) < 0)
@ -1652,7 +1652,7 @@ clixon_xml_find_instance_id(cxobj *xt,
va_end(ap); va_end(ap);
if (instance_id_parse(path, &cplist) < 0) if (instance_id_parse(path, &cplist) < 0)
goto done; goto done;
if (debug) if (clicon_debug_get())
clixon_path_print(stderr, cplist); clixon_path_print(stderr, cplist);
/* Resolve module:name to pointer to yang-stmt, fail if not successful */ /* Resolve module:name to pointer to yang-stmt, fail if not successful */
if ((ret = instance_id_resolve(cplist, yt)) < 0) if ((ret = instance_id_resolve(cplist, yt)) < 0)

View file

@ -326,7 +326,7 @@ clicon_msg_send(int s,
clicon_debug(2, "%s: send msg len=%d", clicon_debug(2, "%s: send msg len=%d",
__FUNCTION__, ntohl(msg->op_len)); __FUNCTION__, ntohl(msg->op_len));
if (debug > 2) if (clicon_debug_get() > 2)
msg_dump(msg); msg_dump(msg);
if (atomicio((ssize_t (*)(int, void *, size_t))write, if (atomicio((ssize_t (*)(int, void *, size_t))write,
s, msg, ntohl(msg->op_len)) < 0){ s, msg, ntohl(msg->op_len)) < 0){
@ -400,7 +400,7 @@ clicon_msg_rcv(int s,
clicon_err(OE_CFG, errno, "body too short"); clicon_err(OE_CFG, errno, "body too short");
goto done; goto done;
} }
if (debug > 1) if (clicon_debug_get() > 1)
msg_dump(*msg); msg_dump(*msg);
retval = 0; retval = 0;
done: done:

View file

@ -927,7 +927,7 @@ url_post(char *url,
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(postfields)); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(postfields));
if (debug) if (clicon_debug_get())
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
if ((errcode = curl_easy_perform(curl)) != CURLE_OK){ if ((errcode = curl_easy_perform(curl)) != CURLE_OK){
clicon_debug(1, "%s: curl: %s(%d)", __FUNCTION__, err, errcode); clicon_debug(1, "%s: curl: %s(%d)", __FUNCTION__, err, errcode);

View file

@ -206,7 +206,7 @@ xml2cli(FILE *f,
if (yang_keyword_get(ys) == Y_LEAF || yang_keyword_get(ys) == Y_LEAF_LIST){ if (yang_keyword_get(ys) == Y_LEAF || yang_keyword_get(ys) == Y_LEAF_LIST){
if (prepend0) if (prepend0)
fprintf(f, "%s", prepend0); fprintf(f, "%s", prepend0);
if (gt == GT_ALL || gt == GT_VARS) if (gt == GT_ALL || gt == GT_VARS || gt == GT_HIDE)
fprintf(f, "%s ", xml_name(x)); fprintf(f, "%s ", xml_name(x));
if ((body = xml_body(x)) != NULL){ if ((body = xml_body(x)) != NULL){
if (index(body, ' ')) if (index(body, ' '))
@ -224,7 +224,12 @@ xml2cli(FILE *f,
} }
if (prepend0) if (prepend0)
cprintf(cbpre, "%s", prepend0); cprintf(cbpre, "%s", prepend0);
cprintf(cbpre, "%s ", xml_name(x));
/* If non-presence container && HIDE mode && only child is
* a list, then skip container keyword
* See also yang2cli_container */
if (yang_container_cli_hide(ys, gt) == 0)
cprintf(cbpre, "%s ", xml_name(x));
if (yang_keyword_get(ys) == Y_LIST){ if (yang_keyword_get(ys) == Y_LIST){
/* If list then first loop through keys */ /* If list then first loop through keys */
@ -358,7 +363,7 @@ xml2cvec(cxobj *xt,
} }
} }
} }
if (debug > 1){ if (clicon_debug_get() > 1){
clicon_debug(2, "%s cvv:\n", __FUNCTION__); clicon_debug(2, "%s cvv:\n", __FUNCTION__);
cvec_print(stderr, cvv); cvec_print(stderr, cvv);
} }

View file

@ -504,7 +504,7 @@ xpath_parse(char *xpath,
clicon_err(OE_XML, 0, "XPATH parser error with no error code (should not happen)"); clicon_err(OE_XML, 0, "XPATH parser error with no error code (should not happen)");
goto done; goto done;
} }
if (debug > 1){ if (clicon_debug_get() > 1){
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;

View file

@ -940,7 +940,7 @@ xp_eval(xp_ctx *xc,
xp_ctx *xr2 = NULL; xp_ctx *xr2 = NULL;
int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */ int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */
if (debug > 1) if (clicon_debug_get() > 1)
ctx_print(stderr, xc, xpath_tree_int2str(xs->xs_type)); ctx_print(stderr, xc, xpath_tree_int2str(xs->xs_type));
/* Pre-actions before check first child c0 /* Pre-actions before check first child c0
*/ */
@ -1096,7 +1096,7 @@ xp_eval(xp_ctx *xc,
xr0 = NULL; xr0 = NULL;
} }
ok: ok:
if (debug>1) if (clicon_debug_get() > 1)
ctx_print(stderr, *xrp, xpath_tree_int2str(xs->xs_type)); ctx_print(stderr, *xrp, xpath_tree_int2str(xs->xs_type));
retval = 0; retval = 0;
done: done:

View file

@ -2537,6 +2537,54 @@ yang_arg2cvec(yang_stmt *ys,
return cvv; return cvv;
} }
/*! Check if yang is subject to generated cli GT_HIDE boolean
* The yang should be:
* 1) a non-presence container
* 2) parent of a (single) list XXX: or could multiple lists work?
* 3) no other data node children
* @retval 0 No, does not satisfy the GT_HIDE condition
* @retval 1 Yes, satisfies the GT_HIDE condition
* @see clixon-config.yang HIDE enumeration type
*/
int
yang_container_cli_hide(yang_stmt *ys,
enum genmodel_type gt)
{
yang_stmt *yc = NULL;
int i;
enum rfc_6020 keyw;
keyw = yang_keyword_get(ys);
/* HIDE mode */
if (gt != GT_HIDE)
return 0;
/* A container */
if (yang_keyword_get(ys) != Y_CONTAINER)
return 0;
/* Non-presence */
if (yang_find(ys, Y_PRESENCE, NULL) != NULL)
return 0;
/* Ensure a single list child and no other data nodes */
i = 0; /* Number of list nodes */
while ((yc = yn_each(ys, yc)) != NULL) {
keyw = yang_keyword_get(yc);
/* case/choice could hide anything so disqualify those */
if (keyw == Y_CASE || keyw == Y_CHOICE)
break;
if (!yang_datanode(yc)) /* Allowed, check next */
continue;
if (keyw != Y_LIST) /* Another datanode than list */
break;
if (i++>0) /* More than one list (or could this work?) */
break;
}
if (yc != NULL) /* break from loop */
return 0;
if (i != 1) /* List found */
return 0;
return 1; /* Passed all tests: yes you can hide this keyword */
}
/*! Check if yang node yn has key-stmt as child which matches name /*! Check if yang node yn has key-stmt as child which matches name
* *
* The function looks at the LIST argument string (not actual children) * The function looks at the LIST argument string (not actual children)

View file

@ -195,13 +195,6 @@
extern int clixon_yang_parseget_lineno (void); extern int clixon_yang_parseget_lineno (void);
int
clicon_yang_debug(int d)
{
debug = d;
return 0;
}
/* /*
clixon_yang_parseerror clixon_yang_parseerror
also called from yacc generated code * also called from yacc generated code *

View file

@ -90,9 +90,6 @@ fi
# RESTCONF protocol, eg http or https # RESTCONF protocol, eg http or https
: ${RCPROTO:=http} : ${RCPROTO:=http}
# RESTCONF port
: ${RCPORT:=80}
# RESTCONF error message (if not up) # RESTCONF error message (if not up)
: ${RCERROR:="HTTP/1.1 502 Bad Gateway"} : ${RCERROR:="HTTP/1.1 502 Bad Gateway"}
@ -229,8 +226,11 @@ wait_backend(){
done done
} }
# Start restconf daemon
# @see wait_restconf
start_restconf(){ start_restconf(){
# Start in background # Start in background
# echo "sudo -u $wwwuser -s $clixon_restconf $RCLOG -D $DBG $*"
sudo -u $wwwuser -s $clixon_restconf $RCLOG -D $DBG $* & sudo -u $wwwuser -s $clixon_restconf $RCLOG -D $DBG $* &
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
@ -246,12 +246,16 @@ stop_restconf(){
} }
# Wait for restconf to stop sending 502 Bad Gateway # Wait for restconf to stop sending 502 Bad Gateway
# @see start_restconf
wait_restconf(){ wait_restconf(){
hdr=$(curl --head -sS $RCPROTO://localhost:$RCPORT/restconf) # echo "curl -kis $RCPROTO://localhost/restconf"
hdr=$(curl -kis $RCPROTO://localhost/restconf)
# echo "hdr:\"$hdr\""
let i=0; let i=0;
while [[ $hdr == "$RCERROR"* ]]; do while [[ $hdr != *"200 OK"* ]]; do
sleep 1 sleep 1
hdr=$(curl --head -sS http://localhost/restconf) hdr=$(curl -kis $RCPROTO://localhost/restconf)
# echo "hdr:\"$hdr\""
let i++; let i++;
# echo "wait_restconf $i" # echo "wait_restconf $i"
if [ $i -ge $RCWAIT ]; then if [ $i -ge $RCWAIT ]; then
@ -366,6 +370,8 @@ expecteq(){
# - expected stdout outcome* # - expected stdout outcome*
# - the token "--not--" # - the token "--not--"
# - not expected stdout outcome* # - not expected stdout outcome*
# Example:
# expectpart "$(a-shell-cmd arg)" 0 'expected match 1' 'expected match 2' --not-- 'not expected 1'
# @note need to escape \[\] # @note need to escape \[\]
expectpart(){ expectpart(){
r=$? r=$?

View file

@ -189,13 +189,13 @@ new "cli set protocol udp"
expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$" expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$"
new "cli get protocol udp" new "cli get protocol udp"
expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^system protocol udp$" expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^set system protocol udp$"
new "cli change protocol to tcp" new "cli change protocol to tcp"
expectfn "$clixon_cli -1 -f $cfg -l o set system protocol tcp" 0 "^$" expectfn "$clixon_cli -1 -f $cfg -l o set system protocol tcp" 0 "^$"
new "cli get protocol tcp" new "cli get protocol tcp"
expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^system protocol tcp$" expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^set system protocol tcp$"
new "cli delete all" new "cli delete all"
expectfn "$clixon_cli -1 -f $cfg -l o delete all" 0 "^$" expectfn "$clixon_cli -1 -f $cfg -l o delete all" 0 "^$"

View file

@ -49,24 +49,25 @@ if [ $BE -ne 0 ]; then
fi fi
new "cli configure top" new "cli configure top"
expectfn "$clixon_cli -1 -f $cfg set interfaces" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg set interfaces)" 0 "^$"
new "cli show configuration top (no presence)" new "cli show configuration top (no presence)"
expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg show conf cli)" 0 "^$"
new "cli configure delete top" new "cli configure delete top"
expectfn "$clixon_cli -1 -f $cfg delete interfaces" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg delete interfaces)" 0 "^$"
new "cli show configuration delete top" new "cli show configuration delete top"
expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg show conf cli)" 0 "^$"
new "cli configure set interfaces" new "cli configure set interfaces"
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg set interfaces interface eth/0/0)" 0 "^$"
new "cli show configuration" new "cli show configuration"
expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 '^interfaces interface eth/0/0 interfaces interface eth/0/0 enabled true' expectpart "$($clixon_cli -1 -f $cfg show conf cli)" 0 "^set interfaces interface eth/0/0" "^set interfaces interface eth/0/0 enabled true"
new "cli configure using encoded chars data <&" new "cli configure using encoded chars data <&"
# problems in changing to expectpart with escapes
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" 0 "" expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" 0 ""
new "cli configure using encoded chars name <&" new "cli configure using encoded chars name <&"
@ -76,53 +77,53 @@ new "cli failed validate"
expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Validate failed. Edit and try again or discard changes: application missing-element Mandatory variable <bad-element>type</bad-element>" expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Validate failed. Edit and try again or discard changes: application missing-element Mandatory variable <bad-element>type</bad-element>"
new "cli configure ip addr" new "cli configure ip addr"
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24)" 0 "^$"
new "cli configure ip descr" new "cli configure ip descr"
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description mydesc" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description mydesc)" 0 "^$"
new "cli configure ip type" new "cli configure ip type"
expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 type ex:eth" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg set interfaces interface eth/0/0 type ex:eth)" 0 "^$"
new "cli show xpath description" new "cli show xpath description"
expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces" 0 "<description>mydesc</description>" expectpart "$($clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces)" 0 "<description>mydesc</description>"
new "cli delete description" new "cli delete description"
expectfn "$clixon_cli -1 -f $cfg -l o delete interfaces interface eth/0/0 description mydesc" 0 "" expectpart "$($clixon_cli -1 -f $cfg -l o delete interfaces interface eth/0/0 description mydesc)" 0 ""
new "cli show xpath no description" new "cli show xpath no description"
expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces)" 0 "^$"
new "cli copy interface" new "cli copy interface"
expectfn "$clixon_cli -1 -f $cfg copy interface eth/0/0 to eth99" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg copy interface eth/0/0 to eth99)" 0 "^$"
new "cli success validate" new "cli success validate"
expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 "^$"
new "cli commit" new "cli commit"
expectfn "$clixon_cli -1 -f $cfg -l o commit" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$"
new "cli save" new "cli save"
expectfn "$clixon_cli -1 -f $cfg -l o save /tmp/foo" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o save /tmp/foo)" 0 "^$"
new "cli delete all" new "cli delete all"
expectfn "$clixon_cli -1 -f $cfg -l o delete all" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o delete all)" 0 "^$"
new "cli load" new "cli load"
expectfn "$clixon_cli -1 -f $cfg -l o load /tmp/foo" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o load /tmp/foo)" 0 "^$"
new "cli check load" new "cli check load"
expectfn "$clixon_cli -1 -f $cfg -l o show conf cli" 0 "interfaces interface eth/0/0 ipv4 enabled true" expectpart "$($clixon_cli -1 -f $cfg -l o show conf cli)" 0 "interfaces interface eth/0/0 ipv4 enabled true"
new "cli debug set" new "cli debug set"
expectfn "$clixon_cli -1 -f $cfg -l o debug level 1" 0 "^$" expectpart "$($clixon_cli -1 -f $cfg -l o debug level 1)" 0 "^$"
# How to test this? # How to test this?
new "cli debug reset" new "cli debug reset"
expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$"
new "cli rpc" new "cli rpc"
expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 '<rpc-reply><x xmlns="urn:example:clixon">ipv4</x><y xmlns="urn:example:clixon">42</y></rpc-reply>' expectpart "$($clixon_cli -1 -f $cfg -l o rpc ipv4)" 0 '<rpc-reply><x xmlns="urn:example:clixon">ipv4</x><y xmlns="urn:example:clixon">42</y></rpc-reply>'
if [ $BE -eq 0 ]; then if [ $BE -eq 0 ]; then
exit # BE exit # BE

204
test/test_cli_gen.sh Executable file
View file

@ -0,0 +1,204 @@
#!/usr/bin/env bash
# Tests for using the generated cli.
# In particular setting a config, displaying as cli commands and reconfigure it
# Tests:
# Make a config in CLI. Show output as CLI, save it and ensure it is the same
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example
# include err() and new() functions and creates $dir
cfg=$dir/conf_yang.xml
fyang=$dir/$APPNAME.yang
fstate=$dir/state.xml
clidir=$dir/cli
if [ -d $clidir ]; then
rm -rf $clidir/*
else
mkdir $clidir
fi
# Use yang in example
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLISPEC_DIR>$clidir</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_GENMODEL>2</CLICON_CLI_GENMODEL>
<CLICON_CLI_GENMODEL_TYPE>VARS</CLICON_CLI_GENMODEL_TYPE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
</clixon-config>
EOF
cat <<EOF > $fyang
module $APPNAME {
namespace "urn:example:clixon";
prefix ex;
container table{
list parameter{
key name;
leaf name{
type string;
}
leaf value{
type string;
}
}
}
container exstate{
config false;
list sender{
key ref;
leaf ref{
type string;
}
}
}
}
EOF
# This is state data written to file that backend reads from (on request)
cat <<EOF > $fstate
<exstate xmlns="urn:example:clixon">
<sender>
<ref>x</ref>
</sender>
</exstate>
EOF
cat <<EOF > $clidir/ex.cli
CLICON_MODE="example";
CLICON_PROMPT="%U@%H> ";
set @datamodel, cli_set();
merge @datamodel, cli_merge();
create @datamodel, cli_create();
delete @datamodel, cli_del();
show config, cli_show_config("candidate", "cli", "/", 0, "set ");
show config @datamodel, cli_show_auto("candidate", "cli", "set ");
show state, cli_show_config_state("running", "cli", "/", "set ");
show state @datamodelstate, cli_show_auto_state("running", "cli", "set ");
show xml, cli_show_config("candidate", "xml", "/");
show xml @datamodel, cli_show_auto("candidate", "xml");
commit, cli_commit();
discard, discard_changes();
EOF
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -- -sS $fstate"
start_backend -s init -f $cfg -- -sS $fstate
new "waiting"
wait_backend
fi
# Simple run trying setting a config,
# then deleting it, and reloading it
# 1. mode - either VARS Keywords on non-key variables: a <x> y <y> or
# ALL Keywords on all variables: a x <x> y <y>
testrun()
{
mode=$1
if [ $mode = ALL ]; then
table=" table"
name=" name"
elif [ $mode = HIDE ]; then
table=
name=
else
table=" table"
name=
fi
new "set a"
echo "$clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg set$table parameter$name a value x)" 0 ""
new "set b"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg set$table parameter$name b value y)" 0 ""
new "reset b"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg set$table parameter$name b value z)" 0 ""
new "show match a & b"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)" 0 "set$table parameter$name a" "set$table parameter$name a value x" "set$table parameter$name b" "set$table parameter$name b value z" --not-- "set$table parameter$name b value y"
SAVED=$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)
# awkward having pretty-printed xml in matching strings
new "show match a & b xml"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show xml)" 0 "<table xmlns=\"urn:example:clixon\">" "<parameter>" "<name>a</name>" "<value>x</value>" "</parameter>" "<parameter>" "<name>b</name>" "<value>z</value>" "</parameter>" "</table>"
new "delete a"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg delete$table parameter$name a)" 0 ""
new "show match b"
echo "$clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)" 0 "$table parameter$name b" "$table parameter$name b value z" --not-- "$table parameter$name a" "$table parameter$name a value x" "$table parameter$name b value y"
new "discard"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg discard)" 0 ""
new "show match empty"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)" 0 --not-- "$table parameter$name b" "$table parameter$name b value z" "$table parameter$name a" "$table parameter$name a value x" "$table parameter$name b value y"
new "load saved cli config"
expectpart "$(echo "$SAVED" | $clixon_cli -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg)" 0 ""
new "show saved a & b"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)" 0 "set$table parameter$name a" "set$table parameter$name a value x" "set$table parameter$name b" "set$table parameter$name b value z" --not-- "set$table parameter$name b value y"
new "discard"
expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg discard)" 0 ""
} # testrun
new "keywords=HIDE"
testrun HIDE
new "keywords=ALL"
testrun ALL
new "keywords=VARS"
testrun VARS
# show state
new "set a"
expectpart "$($clixon_cli -1 -f $cfg set$table parameter a value x)" 0 ""
new "commit"
expectpart "$($clixon_cli -1 -f $cfg commit)" 0 ""
new "show state"
expectpart "$($clixon_cli -1 -f $cfg show state)" 0 "exstate sender x" "table parameter a" "table parameter a value x"
new "show state exstate"
expectpart "$($clixon_cli -1 -f $cfg show state exstate)" 0 "state sender x" --not-- "table parameter a" "table parameter a value x"
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

@ -65,7 +65,7 @@ module leafref{
} }
EOF EOF
# This is state data writte to file that backend reads from (on request) # This is state data written to file that backend reads from (on request)
cat <<EOF > $fstate cat <<EOF > $fstate
<sender-state xmlns="urn:example:example"> <sender-state xmlns="urn:example:example">
<ref>x</ref> <ref>x</ref>

View file

@ -57,37 +57,37 @@ if [ $RC -ne 0 ]; then
new "start restconf daemon" new "start restconf daemon"
start_restconf -f $cfg start_restconf -f $cfg
new "waiting"
wait_restconf
fi fi
new "waiting"
wait_restconf
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
expectpart "$(curl -si -X GET http://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl -sik -X GET $RCPROTO://localhost/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
new "restconf get restconf resource. RFC 8040 3.3 (json)" new "restconf get restconf resource. RFC 8040 3.3 (json)"
expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" http://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}' expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}'
new "restconf get restconf resource. RFC 8040 3.3 (xml)" new "restconf get restconf resource. RFC 8040 3.3 (xml)"
# Get XML instead of JSON? # Get XML instead of JSON?
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf)" 0 'HTTP/1.1 200 OK' '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 'HTTP/1.1 200 OK' '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>'
# Should be alphabetically ordered # Should be alphabetically ordered
new "restconf get restconf/operations. RFC8040 3.3.2 (json)" new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
expectpart "$(curl -si -X GET http://localhost/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\],"clixon-rfc5277:create-subscription":\[null\]}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 200 OK' '{"operations":{"clixon-example:client-rpc":\[null\],"clixon-example:empty":\[null\],"clixon-example:optional":\[null\],"clixon-example:example":\[null\],"clixon-lib:debug":\[null\],"clixon-lib:ping":\[null\],"clixon-lib:stats":\[null\],"clixon-lib:restart-plugin":\[null\],"ietf-netconf:get-config":\[null\],"ietf-netconf:edit-config":\[null\],"ietf-netconf:copy-config":\[null\],"ietf-netconf:delete-config":\[null\],"ietf-netconf:lock":\[null\],"ietf-netconf:unlock":\[null\],"ietf-netconf:get":\[null\],"ietf-netconf:close-session":\[null\],"ietf-netconf:kill-session":\[null\],"ietf-netconf:commit":\[null\],"ietf-netconf:discard-changes":\[null\],"ietf-netconf:validate":\[null\]}}'
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" http://localhost/restconf/operations) ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/operations)
expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><stats xmlns="http://clicon.org/lib"/><restart-plugin xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"/></operations>' expect='<operations><client-rpc xmlns="urn:example:clixon"/><empty xmlns="urn:example:clixon"/><optional xmlns="urn:example:clixon"/><example xmlns="urn:example:clixon"/><debug xmlns="http://clicon.org/lib"/><ping xmlns="http://clicon.org/lib"/><stats xmlns="http://clicon.org/lib"/><restart-plugin xmlns="http://clicon.org/lib"/><get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><discard-changes xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/><validate xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/></operations>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
err "$expect" "$ret" err "$expect" "$ret"
fi fi
new "restconf get restconf/yang-library-version. RFC8040 3.3.3" new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
expectpart "$(curl -si -X GET http://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2016-06-21"}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/yang-library-version)" 0 'HTTP/1.1 200 OK' '{"yang-library-version":"2016-06-21"}'
new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)"
ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" http://localhost/restconf/yang-library-version) ret=$(curl -s -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/yang-library-version)
expect="<yang-library-version>2016-06-21</yang-library-version>" expect="<yang-library-version>2016-06-21</yang-library-version>"
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -95,46 +95,46 @@ if [ -z "$match" ]; then
fi fi
new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" new "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-interfaces,2018-02-20)" 0 'HTTP/1.1 200 OK' '{"ietf-yang-library:module":\[{"name":"ietf-interfaces","revision":"2018-02-20","namespace":"urn:ietf:params:xml:ns:yang:ietf-interfaces","conformance-type":"implement"}\]}'
new "restconf options. RFC 8040 4.1" new "restconf options. RFC 8040 4.1"
expectpart "$(curl -is -X OPTIONS http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" expectpart "$(curl -is -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE"
# -I means HEAD # -I means HEAD
new "restconf HEAD. RFC 8040 4.2" new "restconf HEAD. RFC 8040 4.2"
expectpart "$(curl -si -I -H "Accept: application/yang-data+json" http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json" expectpart "$(curl -si -I -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+json"
new "restconf empty rpc JSON" new "restconf empty rpc JSON"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
new "restconf empty rpc XML" new "restconf empty rpc XML"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+xml" -d '<input xmlns="urn:example:clixon"></input>' http://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+xml" -d '<input xmlns="urn:example:clixon"></input>' $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 "HTTP/1.1 204 No Content"
new "restconf empty rpc, default media type should fail" new "restconf empty rpc, default media type should fail"
expectpart "$(curl -si -X POST -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' expectpart "$(curl -si -X POST -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
new "restconf empty rpc, default media type should fail (JSON)" new "restconf empty rpc, default media type should fail (JSON)"
expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
new "restconf empty rpc with extra args (should fail)" new "restconf empty rpc with extra args (should fail)"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}' expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} $RCPROTO://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}'
# Irritiating to get debugs on the terminal # Irritiating to get debugs on the terminal
#new "restconf debug rpc" #new "restconf debug rpc"
#expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} http://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content" #expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} $RCPROTO://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
new "restconf get empty config + state json" new "restconf get empty config + state json"
expecteq "$(curl -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} expecteq "$(curl -sS -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}}
' '
new "restconf get empty config + state json with wrong module name" new "restconf get empty config + state json with wrong module name"
expectpart "$(curl -siSG http://localhost/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}' expectpart "$(curl -siSG $RCPROTO://localhost/restconf/data/badmodule:state)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"badmodule"},"error-severity":"error","error-message":"No such yang module prefix"}}}'
#'HTTP/1.1 404 Not Found' #'HTTP/1.1 404 Not Found'
#'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}' #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"No such yang module: badmodule"}}}'
new "restconf get empty config + state xml" new "restconf get empty config + state xml"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/clixon-example:state) ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)
expect='<state xmlns="urn:example:clixon"><op>41</op><op>42</op><op>43</op></state>' expect='<state xmlns="urn:example:clixon"><op>41</op><op>42</op><op>43</op></state>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -142,12 +142,12 @@ if [ -z "$match" ]; then
fi fi
new "restconf get data type json" new "restconf get data type json"
expecteq "$(curl -s -G http://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"} expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}
' '
new "restconf get state operation" new "restconf get state operation"
# Cant get shell macros to work, inline matching from lib.sh # 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) ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)
expect='<op xmlns="urn:example:clixon">42</op>' expect='<op xmlns="urn:example:clixon">42</op>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -155,12 +155,12 @@ if [ -z "$match" ]; then
fi fi
new "restconf get state operation 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"} expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)" 0 '{"clixon-example:op":"42"}
' '
new "restconf get state operation type xml" new "restconf get state operation type xml"
# Cant get shell macros to work, inline matching from lib.sh # 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) ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/clixon-example:state/op=42)
expect='<op xmlns="urn:example:clixon">42</op>' expect='<op xmlns="urn:example:clixon">42</op>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -168,94 +168,94 @@ if [ -z "$match" ]; then
fi fi
new "restconf GET datastore" new "restconf GET datastore"
expecteq "$(curl -s -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}}
' '
# Exact match # Exact match
new "restconf Add subtree eth/0/0 to datastore using POST" new "restconf Add subtree eth/0/0 to datastore using POST"
expectpart "$(curl -s -i -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' http://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces' expectpart "$(curl -s -i -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' 'Location: http://localhost/restconf/data/ietf-interfaces:interfaces'
new "restconf Re-add subtree eth/0/0 which should give error" new "restconf Re-add subtree eth/0/0 which should give error"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example: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"}}}' expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://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" new "restconf Check interfaces eth/0/0 added"
expectfn "curl -s -X GET http://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}} expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces" 0 '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up"}\]}}}
' '
new "restconf delete interfaces" new "restconf delete interfaces"
expectpart "$(curl -si -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/1.1 204 No Content"
new "restconf Check empty config" new "restconf Check empty config"
expectfn "curl -sG http://localhost/restconf/data/clixon-example:state" 0 "$state expectfn "curl -sG $RCPROTO://localhost/restconf/data/clixon-example:state" 0 "$state
" "
new "restconf Add interfaces subtree eth/0/0 using POST" new "restconf Add interfaces subtree eth/0/0 using POST"
expectpart "$(curl -si -X POST http://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X POST $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}')" 0 "HTTP/1.1 201 Created"
new "restconf Check eth/0/0 added config" new "restconf Check eth/0/0 added config"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
new "restconf Check eth/0/0 GET augmented state level 1" new "restconf Check eth/0/0 GET augmented state level 1"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 200 OK' '{"ietf-interfaces:interface":\[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}'
new "restconf Check eth/0/0 GET augmented state level 2" new "restconf Check eth/0/0 GET augmented state level 2"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0/clixon-example:my-status)" 0 'HTTP/1.1 200 OK' '{"clixon-example:my-status":{"int":42,"str":"foo"}}'
new "restconf Check eth/0/0 added state XXXXXXX" new "restconf Check eth/0/0 added state XXXXXXX"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}'
new "restconf Re-post eth/0/0 which should generate error" new "restconf Re-post eth/0/0 which should generate error"
expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' expectpart "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 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 "Add leaf description using POST" new "Add leaf description using POST"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
new "Add nothing using POST (expect fail)" new "Add nothing using POST (expect fail)"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
new "restconf Check description added" 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":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}} expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}}
' '
new "restconf delete eth/0/0" new "restconf delete eth/0/0"
expectpart "$(curl -si -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 204 No Content"
new "Check deleted eth/0/0" new "Check deleted eth/0/0"
expectfn 'curl -s -G http://localhost/restconf/data' 0 "$state" expectfn "curl -s -X GET http://localhost/restconf/data" 0 "$state"
new "restconf Re-Delete eth/0/0 using none should generate error" new "restconf Re-Delete eth/0/0 using none should generate error"
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}} ' expecteq "$(curl -s -X DELETE $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-missing","error-severity":"error","error-message":"Data does not exist; cannot delete resource"}}} '
new "restconf Add subtree eth/0/0 using PUT" new "restconf Add subtree eth/0/0 using PUT"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/1.1 201 Created"
new "restconf get subtree" 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":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}} expecteq "$(curl -s -X GET $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces)" 0 '{"ietf-interfaces:interfaces":{"interface":[{"name":"eth/0/0","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}]}}
' '
new "restconf rpc using POST json" new "restconf rpc using POST json"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}} expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"42","y":"42"}}
' '
if ! $YANG_UNKNOWN_ANYDATA ; then if ! $YANG_UNKNOWN_ANYDATA ; then
new "restconf rpc using POST json wrong" new "restconf rpc using POST json wrong"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}' expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}'
fi fi
new "restconf rpc non-existing rpc without namespace" new "restconf rpc non-existing rpc without namespace"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
new "restconf rpc non-existing rpc" new "restconf rpc non-existing rpc"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/clixon-example:kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
new "restconf rpc missing name" new "restconf rpc missing name"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}'
new "restconf rpc missing input" new "restconf rpc missing input"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' $RCPROTO://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}'
new "restconf rpc using POST xml" new "restconf rpc using POST xml"
ret=$(curl -s -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example) ret=$(curl -s -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' $RCPROTO://localhost/restconf/operations/clixon-example:example)
expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>' expect='<output xmlns="urn:example:clixon"><x>42</x><y>42</y></output>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -263,10 +263,10 @@ if [ -z "$match" ]; then
fi fi
new "restconf rpc using wrong prefix" new "restconf rpc using wrong prefix"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}} ' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' $RCPROTO://localhost/restconf/operations/wrong:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"yang module not found"}}} '
new "restconf local client rpc using POST xml" new "restconf local client rpc using POST xml"
ret=$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' http://localhost/restconf/operations/clixon-example:client-rpc) ret=$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":"example"}}' $RCPROTO://localhost/restconf/operations/clixon-example:client-rpc)
expect='<output xmlns="urn:example:clixon"><x>example</x></output>' expect='<output xmlns="urn:example:clixon"><x>example</x></output>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -274,10 +274,10 @@ if [ -z "$match" ]; then
fi fi
new "restconf Add subtree without key (expected error)" new "restconf Add subtree without key (expected error)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =interface, expected'
new "restconf Add subtree with too many keys (expected error)" new "restconf Add subtree with too many keys (expected error)"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} ' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}' $RCPROTO://localhost/restconf/data/ietf-interfaces:interfaces/interface=a,b)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key interface length mismatch"}}} '
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"

View file

@ -94,22 +94,22 @@ if [ $RC -ne 0 ]; then
fi fi
new "restconf POST tree without key" new "restconf POST tree without key"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} '
new "restconf POST initial tree" new "restconf POST initial tree"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
new "restconf POST top without namespace" new "restconf POST top without namespace"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}} ' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object cont1 is not qualified with namespace which is a MUST according to RFC 7951"}}} '
new "restconf GET datastore initial" new "restconf GET datastore initial"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
new "restconf GET interface subtree" new "restconf GET interface subtree"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"regular"}\]}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"regular"}\]}'
new "restconf GET interface subtree xml" new "restconf GET interface subtree xml"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:cont1/interface=local0) ret=$(curl -s -H "Accept: application/yang-data+xml" -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)
expect='<interface xmlns="urn:example:clixon"><name>local0</name><type>regular</type></interface>' expect='<interface xmlns="urn:example:clixon"><name>local0</name><type>regular</type></interface>'
match=`echo $ret | grep --null -Eo "$expect"` match=`echo $ret | grep --null -Eo "$expect"`
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -117,83 +117,83 @@ if [ -z "$match" ]; then
fi fi
new "restconf GET if-type" new "restconf GET if-type"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type":"regular"}'
new "restconf POST interface without mandatory type" new "restconf POST interface without mandatory type"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:cont1 -d '{"example:interface":{"name":"TEST"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"type"},"error-severity":"error","error-message":"Mandatory variable"}}} ' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"name":"TEST"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"type"},"error-severity":"error","error-message":"Mandatory variable"}}} '
new "restconf POST interface without mandatory key" new "restconf POST interface without mandatory key"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} ' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:cont1 -d '{"example:interface":{"type":"regular"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"name"},"error-severity":"error","error-message":"Mandatory key"}}} '
new "restconf POST interface" new "restconf POST interface"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 "HTTP/1.1 201 Created"
new "restconf POST interface without namespace" new "restconf POST interface without namespace"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object interface is not qualified with namespace which is a MUST according to RFC 7951"}}} ' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"interface":{"name":"TEST2","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object interface is not qualified with namespace which is a MUST according to RFC 7951"}}} '
new "restconf POST again" new "restconf POST again"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}} ' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1)" 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 POST from top" new "restconf POST from top"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' 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"}}} ' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' $RCPROTO://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 DELETE" new "restconf DELETE"
expectfn 'curl -si -X DELETE http://localhost/restconf/data/example:cont1' 0 "HTTP/1.1 204 No Content" expectfn "curl -si -X DELETE $RCPROTO://localhost/restconf/data/example:cont1" 0 "HTTP/1.1 204 No Content"
new "restconf GET null datastore" new "restconf GET null datastore"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}'
new "restconf POST initial tree" new "restconf POST initial tree"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' http://localhost/restconf/data)" 0 "" expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}' $RCPROTO://localhost/restconf/data)" 0 ""
new "restconf GET initial tree" new "restconf GET initial tree"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
new "restconf DELETE whole datastore" new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 ""
new "restconf GET null datastore" new "restconf GET null datastore"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}'
new "restconf PUT initial datastore" new "restconf PUT initial datastore"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
new "restconf GET datastore" new "restconf GET datastore"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"regular"}\]}}'
new "restconf PUT replace datastore" new "restconf PUT replace datastore"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont2":{"name":"foo"}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
new "restconf GET replaced datastore" new "restconf GET replaced datastore"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont2" 0 '{"example:cont2":{"name":"foo"}}'
new "restconf PUT initial datastore again" new "restconf PUT initial datastore again"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-restconf:data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}}' $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
new "restconf PUT change interface" new "restconf PUT change interface"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' http://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"local0","type":"atm0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0)" 0 "HTTP/1.1 204 No Content"
new "restconf GET datastore atm" new "restconf GET datastore atm"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1" 0 '{"example:cont1":{"interface":\[{"name":"local0","type":"atm0"}\]}}'
new "restconf PUT add interface" new "restconf PUT add interface"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"TEST","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 "HTTP/1.1 201 Created"
new "restconf PUT change key error" new "restconf PUT change key error"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' http://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:interface":{"name":"ALPHA","type":"eth0"}}' $RCPROTO://localhost/restconf/data/example:cont1/interface=TEST)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} '
new "restconf PUT change type to eth0 (non-key sub-element to list)" new "restconf PUT change type to eth0 (non-key sub-element to list)"
expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' http://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X PUT -H "Content-Type: application/yang-data+json" -d '{"example:type":"eth0"}' $RCPROTO://localhost/restconf/data/example:cont1/interface=local0/type)" 0 "HTTP/1.1 204 No Content"
new "restconf GET datastore eth" new "restconf GET datastore eth"
expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface":\[{"name":"local0","type":"eth0"}\]}'
#--------------- json type tests #--------------- json type tests
new "restconf POST type x3" new "restconf POST type x3"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' http://localhost/restconf/data)" 0 '' expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' $RCPROTO://localhost/restconf/data)" 0 ''
new "restconf POST type x3" new "restconf POST type x3"
expectfn 'curl -s -X GET http://localhost/restconf/data/example:types' 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}' expectfn "curl -s -X GET $RCPROTO://localhost/restconf/data/example:types" 0 '{"example:types":{"tint":42,"tdec64":42.123,"tbool":false,"tstr":"str"}}'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"

View file

@ -169,54 +169,54 @@ if [ $RC -ne 0 ]; then
fi fi
new "restconf POST initial tree" new "restconf POST initial tree"
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" http://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created'
new "restconf GET initial datastore" new "restconf GET initial datastore"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/example:a=0)" 0 'HTTP/1.1 200 OK' "$XML" expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 'HTTP/1.1 200 OK' "$XML"
new "restconf GET non-qualified list" new "restconf GET non-qualified list"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a)" 0 'HTTP/1.1 400 Bad Request' "{\"ietf-restconf:errors\":{\"error\":{\"error-type\":\"rpc\",\"error-tag\":\"malformed-message\",\"error-severity\":\"error\",\"error-message\":\"malformed key =example:a, expected '=restval'\"}}}" expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:a)" 0 'HTTP/1.1 400 Bad Request' "{\"ietf-restconf:errors\":{\"error\":{\"error-type\":\"rpc\",\"error-tag\":\"malformed-message\",\"error-severity\":\"error\",\"error-message\":\"malformed key =example:a, expected '=restval'\"}}}"
new "restconf GET non-qualified list subelements" new "restconf GET non-qualified list subelements"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a/k)" 0 'HTTP/1.1 400 Bad Request' "^{\"ietf-restconf:errors\":{\"error\":{\"error-type\":\"rpc\",\"error-tag\":\"malformed-message\",\"error-severity\":\"error\",\"error-message\":\"malformed key =example:a, expected '=restval'\"}}}" expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:a/k)" 0 'HTTP/1.1 400 Bad Request' "^{\"ietf-restconf:errors\":{\"error\":{\"error-type\":\"rpc\",\"error-tag\":\"malformed-message\",\"error-severity\":\"error\",\"error-message\":\"malformed key =example:a, expected '=restval'\"}}}"
new "restconf GET non-existent container body" new "restconf GET non-existent container body"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a=0/c)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:a=0/c)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}'
new "restconf GET invalid (no yang) container body" new "restconf GET invalid (no yang) container body"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example:a=0/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:a=0/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}'
new "restconf GET invalid (no yang) element" new "restconf GET invalid (no yang) element"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example:xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example:xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}'
new "restconf POST non-existent (no yang) element" new "restconf POST non-existent (no yang) element"
# should be invalid element # should be invalid element
expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" http://localhost/restconf/data/example:a=23/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}' expectpart "$(curl -is -X POST -H 'Content-Type: application/yang-data+xml' -d "$XML" $RCPROTO://localhost/restconf/data/example:a=23/xxx)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"xxx"},"error-severity":"error","error-message":"Unknown element"}}}'
# Test for multi-module path where an augment stretches across modules # Test for multi-module path where an augment stretches across modules
new "restconf POST augment multi-namespace path" new "restconf POST augment multi-namespace path"
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' -d '<route-config xmlns="urn:example:aug"><dynamic><ospf xmlns="urn:example:clixon"><reference-bandwidth>23</reference-bandwidth></ospf></dynamic></route-config>' http://localhost/restconf/data)" 0 'HTTP/1.1 201 Created' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' -d '<route-config xmlns="urn:example:aug"><dynamic><ospf xmlns="urn:example:clixon"><reference-bandwidth>23</reference-bandwidth></ospf></dynamic></route-config>' $RCPROTO://localhost/restconf/data)" 0 'HTTP/1.1 201 Created'
new "restconf GET augment multi-namespace top" new "restconf GET augment multi-namespace top"
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config)" 0 'HTTP/1.1 200 OK' '{"augment:route-config":{"dynamic":{"example:ospf":{"reference-bandwidth":23}}}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config)" 0 'HTTP/1.1 200 OK' '{"augment:route-config":{"dynamic":{"example:ospf":{"reference-bandwidth":23}}}}'
new "restconf GET augment multi-namespace level 1" new "restconf GET augment multi-namespace level 1"
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic)" 0 'HTTP/1.1 200 OK' '{"augment:dynamic":{"example:ospf":{"reference-bandwidth":23}}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic)" 0 'HTTP/1.1 200 OK' '{"augment:dynamic":{"example:ospf":{"reference-bandwidth":23}}}'
new "restconf GET augment multi-namespace cross" new "restconf GET augment multi-namespace cross"
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/example:ospf)" 0 'HTTP/1.1 200 OK' '{"example:ospf":{"reference-bandwidth":23}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/example:ospf)" 0 'HTTP/1.1 200 OK' '{"example:ospf":{"reference-bandwidth":23}}'
new "restconf GET augment multi-namespace cross level 2" new "restconf GET augment multi-namespace cross level 2"
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example:reference-bandwidth":23}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/example:ospf/reference-bandwidth)" 0 'HTTP/1.1 200 OK' '{"example:reference-bandwidth":23}'
# XXX actually no such element # XXX actually no such element
new "restconf GET augment multi-namespace, no 2nd module in api-path, fail" new "restconf GET augment multi-namespace, no 2nd module in api-path, fail"
expectpart "$(curl -si -X GET http://localhost/restconf/data/augment:route-config/dynamic/ospf)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/augment:route-config/dynamic/ospf)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}'
#---------------------------------------------- #----------------------------------------------
# Also generate an invalid state XML. This should generate an "Internal" error and the name of the # Also generate an invalid state XML. This should generate an "Internal" error and the name of the
new "restconf GET failed state" new "restconf GET failed state"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data?content=nonconfig)" 0 '412 Precondition Failed' '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-info><bad-element>mystate</bad-element></error-info><error-severity>error</error-severity><error-message>No such yang module. Internal error, state callback returned invalid XML: example_backend</error-message></error></errors>' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 '412 Precondition Failed' '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>application</error-type><error-tag>operation-failed</error-tag><error-info><bad-element>mystate</bad-element></error-info><error-severity>error</error-severity><error-message>No such yang module. Internal error, state callback returned invalid XML: example_backend</error-message></error></errors>'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"

View file

@ -89,160 +89,160 @@ if [ $RC -ne 0 ]; then
fi fi
new "B.1.1. Retrieve the Top-Level API Resource root" new "B.1.1. Retrieve the Top-Level API Resource root"
expectpart "$(curl -si -X GET -H 'Accept: application/xrd+xml' http://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>" expectpart "$(curl -si -X GET -H 'Accept: application/xrd+xml' $RCPROTO://localhost/.well-known/host-meta)" 0 "HTTP/1.1 200 OK" "Content-Type: application/xrd+xml" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
d='{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}' d='{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}'
new "B.1.1. Retrieve the Top-Level API Resource /restconf json" new "B.1.1. Retrieve the Top-Level API Resource /restconf json"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "$d" expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" "$d"
new "B.1.1. Retrieve the Top-Level API Resource /restconf xml (not in RFC)" new "B.1.1. Retrieve the Top-Level API Resource /restconf xml (not in RFC)"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+xml" '<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>'
# This just catches the header and the jukebox module, the RFC has foo and bar which # This just catches the header and the jukebox module, the RFC has foo and bar which
# seems wrong to recreate # seems wrong to recreate
new "B.1.2. Retrieve the Server Module Information" new "B.1.2. Retrieve the Server Module Information"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-lib","revision":"2020-04-23","namespace":"http://clicon.org/lib","conformance-type":"implement"},{"name":"clixon-rfc5277","revision":"2008-07-01","namespace":"urn:ietf:params:xml:ns:netmod:notification","conformance-type":"implement"},{"name":"example-events","revision":"","namespace":"urn:example:events","conformance-type":"implement"},{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"},{"name":"example-system","revision":"","namespace":"http://example.com/ns/example-system","conformance-type":"implement"},{"name":"ietf-inet-types","revision":"2013-07-15","namespace":"urn:ietf:params:xml:ns:yang:ietf-inet-types","conformance-type":"implement"},' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/ietf-yang-library:modules-state)" 0 "HTTP/1.1 200 OK" 'Cache-Control: no-cache' "Content-Type: application/yang-data+json" '{"ietf-yang-library:modules-state":{"module-set-id":"0","module":\[{"name":"clixon-lib","revision":"2020-04-23","namespace":"http://clicon.org/lib","conformance-type":"implement"},{"name":"example-events","revision":"","namespace":"urn:example:events","conformance-type":"implement"},{"name":"example-jukebox","revision":"2016-08-15","namespace":"http://example.com/ns/example-jukebox","conformance-type":"implement"},{"name":"example-system","revision":"","namespace":"http://example.com/ns/example-system","conformance-type":"implement"},{"name":"ietf-inet-types","revision":"2013-07-15","namespace":"urn:ietf:params:xml:ns:yang:ietf-inet-types","conformance-type":"implement"},'
new "B.1.3. Retrieve the Server Capability Information" new "B.1.3. Retrieve the Server Capability Information"
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability><capability>urn:ietf:params:restconf:capability:depth</capability> expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/capabilities)" 0 "HTTP/1.1 200 OK" "Content-Type: application/yang-data+xml" 'Cache-Control: no-cache' '<capabilities xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf-monitoring"><capability>urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit</capability><capability>urn:ietf:params:restconf:capability:depth</capability>
</capabilities>' </capabilities>'
new "B.2.1. Create New Data Resources (artist+json)" new "B.2.1. Create New Data Resources (artist+json)"
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters" expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library -d '{"example-jukebox:artist":[{"name":"Foo Fighters"}]}')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters"
new "B.2.1. Create New Data Resources (album+xml)" new "B.2.1. Create New Data Resources (album+xml)"
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light" expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 201 Created" "Location: http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light"
new "B.2.1. Add Data Resources again (conflict - not in RFC)" new "B.2.1. Add Data Resources again (conflict - not in RFC)"
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 409 Conflict" expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters -d '<album xmlns="http://example.com/ns/example-jukebox"><name>Wasting Light</name><year>2011</year></album>')" 0 "HTTP/1.1 409 Conflict"
new "4.5. PUT replace content (xml encoding)" new "4.5. PUT replace content (xml encoding)"
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '<album xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album>')" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '<album xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album>')" 0 "HTTP/1.1 204 No Content"
new "4.5. PUT create new identity" new "4.5. PUT create new identity"
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":[{"name":"London Calling","year":1979}]}')" 0 "HTTP/1.1 201 Created"
new "4.5. Check jukebox content: 1 Clash and 1 Foo fighters album" new "4.5. Check jukebox content: 1 Clash and 1 Foo fighters album"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><year>1979</year></album></artist><artist><name>Foo Fighters</name><album xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album></artist></library></jukebox>' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><year>1979</year></album></artist><artist><name>Foo Fighters</name><album xmlns:jbox="http://example.com/ns/example-jukebox"><name>Wasting Light</name><genre>jbox:alternative</genre><year>2011</year></album></artist></library></jukebox>'
new "B.2.2. Added genre (preamble to actual test)" new "B.2.2. Added genre (preamble to actual test)"
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light -d '{"example-jukebox:album":[{"name":"Wasting Light","genre":"example-jukebox:alternative","year":2011}]}')" 0 "HTTP/1.1 204 No Content"
# First use of PATCH # First use of PATCH
new "B.2.2. Detect Datastore Resource Entity-Tag Change (XXX if-unmodified)" new "B.2.2. Detect Datastore Resource Entity-Tag Change (XXX if-unmodified)"
expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light/genre -d '{"example-jukebox:genre":"example-jukebox:alternative"}')" 0 'HTTP/1.1 204 No Content' expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Foo%20Fighters/album=Wasting%20Light/genre -d '{"example-jukebox:genre":"example-jukebox:alternative"}')" 0 'HTTP/1.1 204 No Content'
new "B.2.3. Edit a Datastore Resource (Add 1 Foo fighter and Nick cave album)" new "B.2.3. Edit a Datastore Resource (Add 1 Foo fighter and Nick cave album)"
expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d '<data xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><system xmlns="http://example.com/ns/example-system"><enable-jukebox-streaming>true</enable-jukebox-streaming></system><jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox></data>')" 0 'HTTP/1.1 204 No Content' expectpart "$(curl -si -X PATCH -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data -d '<data xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><system xmlns="http://example.com/ns/example-system"><enable-jukebox-streaming>true</enable-jukebox-streaming></system><jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox></data>')" 0 'HTTP/1.1 204 No Content'
new "B.2.3. Check patch system" new "B.2.3. Check patch system"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<system xmlns="http://example.com/ns/example-system"><enable-jukebox-streaming>true</enable-jukebox-streaming></system>' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-system:system -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<system xmlns="http://example.com/ns/example-system"><enable-jukebox-streaming>true</enable-jukebox-streaming></system>'
new "B.2.3. Check jukebox: 1 Clash, 2 Foo Fighters, 1 Nick Cave" new "B.2.3. Check jukebox: 1 Clash, 2 Foo Fighters, 1 Nick Cave"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><year>1979</year></album></artist><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album><album><name>Wasting Light</name><genre>alternative</genre><year>2011</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox>' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><year>1979</year></album></artist><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album><album><name>Wasting Light</name><genre>alternative</genre><year>2011</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox>'
new "B.2.4. Replace a Datastore Resource" new "B.2.4. Replace a Datastore Resource"
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data -d '<data xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox></data>')" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data -d '<data xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox></data>')" 0 "HTTP/1.1 204 No Content"
new "B.2.4. Check replace" new "B.2.4. Check replace"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox>' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Foo Fighters</name><album><name>One by One</name><year>2012</year></album></artist><artist><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album></artist></library></jukebox>'
new "B.2.5. Edit a Data Resource (add Nick cave album The good son)" new "B.2.5. Edit a Data Resource (add Nick cave album The good son)"
expectpart "$(curl -si -X PATCH http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Content-Type: application/yang-data+xml' -d '<artist xmlns="http://example.com/ns/example-jukebox"><name>Nick Cave and the Bad Seeds</name><album><name>The Good Son</name><year>1990</year></album></artist>')" 0 'HTTP/1.1 204 No Content' expectpart "$(curl -si -X PATCH $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Content-Type: application/yang-data+xml' -d '<artist xmlns="http://example.com/ns/example-jukebox"><name>Nick Cave and the Bad Seeds</name><album><name>The Good Son</name><year>1990</year></album></artist>')" 0 'HTTP/1.1 204 No Content'
new "B.2.5. Check edit" new "B.2.5. Check edit"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<artist xmlns="http://example.com/ns/example-jukebox"><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album><album><name>The Good Son</name><year>1990</year></album></artist>' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Nick%20Cave%20and%20the%20Bad%20Seeds -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<artist xmlns="http://example.com/ns/example-jukebox"><name>Nick Cave and the Bad Seeds</name><album><name>Tender Prey</name><year>1988</year></album><album><name>The Good Son</name><year>1990</year></album></artist>'
# note reverse order of down/up as it is ordered by system and down is before up # note reverse order of down/up as it is ordered by system and down is before up
new 'B.3.1. "content" Parameter (preamble, add content)' new 'B.3.1. "content" Parameter (preamble, add content)'
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-events:events -d '{"example-events:events":{"event":[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}]}}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-events:events -d '{"example-events:events":{"event":[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}]}}')" 0 "HTTP/1.1 201 Created"
new 'B.3.1. "content" Parameter (wrong content)' new 'B.3.1. "content" Parameter (wrong content)'
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=kalle -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-attribute","error-info":{"bad-attribute":"content"},"error-severity":"error","error-message":"Unrecognized value of content attribute"}}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=kalle -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-attribute","error-info":{"bad-attribute":"content"},"error-severity":"error","error-message":"Unrecognized value of content attribute"}}}'
new 'B.3.1. "content" Parameter example 1: content=all' new 'B.3.1. "content" Parameter example 1: content=all'
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=all -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count","event-count":90},{"name":"interface-up","description":"Interface up notification count","event-count":77}\]}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=all -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count","event-count":90},{"name":"interface-up","description":"Interface up notification count","event-count":77}\]}}'
new 'B.3.1. "content" Parameter example 2: content=config' new 'B.3.1. "content" Parameter example 2: content=config'
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=config -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}\]}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=config -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","description":"Interface down notification count"},{"name":"interface-up","description":"Interface up notification count"}\]}}'
new 'B.3.1. "content" Parameter example 3: content=nonconfig' new 'B.3.1. "content" Parameter example 3: content=nonconfig'
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-events:events?content=nonconfig -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","event-count":90},{"name":"interface-up","event-count":77}\]}}' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-events:events?content=nonconfig -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-events:events":{"event":\[{"name":"interface-down","event-count":90},{"name":"interface-up","event-count":77}\]}}'
#new "restconf DELETE whole datastore" #new "restconf DELETE whole datastore"
#expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" #expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 ""
new 'B.3.2. "depth" Parameter example 1 unbound' new 'B.3.2. "depth" Parameter example 1 unbound'
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox?depth=unbounded)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Foo Fighters","album":\[{"name":"One by One","year":2012}\]},{"name":"Nick Cave and the Bad Seeds","album":\[{"name":"Tender Prey","year":1988},{"name":"The Good Son","year":1990}\]}\]}}}' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=unbounded)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Foo Fighters","album":\[{"name":"One by One","year":2012}\]},{"name":"Nick Cave and the Bad Seeds","album":\[{"name":"Tender Prey","year":1988},{"name":"The Good Son","year":1990}\]}\]}}}'
new 'B.3.2. "depth" Parameter example 2 depth=1' new 'B.3.2. "depth" Parameter example 2 depth=1'
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox?depth=1)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{}}' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=1)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{}}'
new 'B.3.2. "depth" Parameter depth=2' new 'B.3.2. "depth" Parameter depth=2'
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox?depth=2)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"library":{}}}' expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=2)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"library":{}}}'
# Maybe this is not correct w [null,null]but I have no good examples # Maybe this is not correct w [null,null]but I have no good examples
new 'B.3.2. "depth" Parameter depth=3' new 'B.3.2. "depth" Parameter depth=3'
expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox?depth=3)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"artist":\[null,null\]}}} expectpart "$(curl -si -X GET -H 'Accept: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox?depth=3)" 0 "HTTP/1.1 200 OK" '{"example-jukebox:jukebox":{"artist":\[null,null\]}}}
' '
new "restconf DELETE whole datastore" new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 ""
#new 'B.3.3. "fields" Parameter' #new 'B.3.3. "fields" Parameter'
new 'B.3.4. "insert" Parameter' new 'B.3.4. "insert" Parameter'
JSON="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}" JSON="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}"
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1'
new 'B.3.4. "insert" Parameter first (RFC example says after)' new 'B.3.4. "insert" Parameter first (RFC example says after)'
JSON="{\"example-jukebox:song\":[{\"index\":0,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}" JSON="{\"example-jukebox:song\":[{\"index\":0,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0'
new 'B.3.4. "insert" Parameter check order' new 'B.3.4. "insert" Parameter check order'
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>" RES="<playlist xmlns=\"$RCPROTO://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES" expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new 'B.3.5. "point" Parameter (before for more interesting order: 0,2,1)' new 'B.3.5. "point" Parameter (before for more interesting order: 0,2,1)'
JSON="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}" JSON="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
new 'B.3.5. "point" check order (0,2,1)' new 'B.3.5. "point" check order (0,2,1)'
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>" RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES" expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new 'B.3.5. "point" Parameter 3 after 2 (using PUT)' new 'B.3.5. "point" Parameter 3 after 2 (using PUT)'
JSON="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Something else']\"}]}" JSON="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Something else']\"}]}"
expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created" expectpart "$(curl -si -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created"
new 'B.3.5. "point" check order (0,2,3,1)' new 'B.3.5. "point" check order (0,2,3,1)'
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>3</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Something else'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>" RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>3</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Something else'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES" expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
new "restconf DELETE whole datastore" new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" expectfn "curl -s -X DELETE $RCPROTO://localhost/restconf/data" 0 ""
new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)' new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)'
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3'
new 'B.3.4. "insert/point" leaf-list 2 first' new 'B.3.4. "insert/point" leaf-list 2 first'
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2'
new 'B.3.4. "insert/point" leaf-list 1 last' new 'B.3.4. "insert/point" leaf-list 1 last'
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
#new 'B.3.4. "insert/point" move leaf-list 1 last' #new 'B.3.4. "insert/point" move leaf-list 1 last'
#- restconf cannot move a leaf-list(list?) item #- restconf cannot move a leaf-list(list?) item
#expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1' #expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
new 'B.3.5. "insert/point" leaf-list check order (2,3,1)' new 'B.3.5. "insert/point" leaf-list check order (2,3,1)'
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
new 'B.3.5. "point" Parameter leaf-list 4 before 3' new 'B.3.5. "point" Parameter leaf-list 4 before 3'
expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' $RCPROTO://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4'
new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)' new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)'
expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox">4</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>' expectpart "$(curl -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox" xmlns:jbox="http://example.com/ns/example-jukebox">4</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed
new 'B.3.3. "fields" Parameter' new 'B.3.3. "fields" Parameter'

View file

@ -92,78 +92,78 @@ if [ $RC -ne 0 ]; then
fi fi
new "restconf PUT add whole list entry" new "restconf PUT add whole list entry"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"0"}}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"0"}}')" 0 ''
# GETs to ensure you get list [] in JSON # GETs to ensure you get list [] in JSON
new "restconf GET whole list entry" new "restconf GET whole list entry"
expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" http://localhost/restconf/data/list:c/)" 0 '{"list:c":{"a":[{"b":"x","c":"y","nonkey":"0"}]}} expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/)" 0 '{"list:c":{"a":[{"b":"x","c":"y","nonkey":"0"}]}}
' '
new "restconf GET list entry itself (should fail)" new "restconf GET list entry itself (should fail)"
expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" http://localhost/restconf/data/list:c/a)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected ' expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected '
new "restconf GET list entry" new "restconf GET list entry"
expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y)" 0 '{"list:a":[{"b":"x","c":"y","nonkey":"0"}]} expecteq "$(curl -s -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y)" 0 '{"list:a":[{"b":"x","c":"y","nonkey":"0"}]}
' '
new "restconf PUT add whole list entry XML" new "restconf PUT add whole list entry XML"
expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xx</b><c>xy</c><nonkey>0</nonkey></a>' http://localhost/restconf/data/list:c/a=xx,xy)" 0 '' expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xx</b><c>xy</c><nonkey>0</nonkey></a>' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 ''
expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" http://localhost/restconf/data/list:c/a/b)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected ' expectpart "$(curl -si -X GET -H "Accept: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a/b)" 0 'HTTP/1.1 400 Bad Request' '^{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"malformed key =a, expected '
new "restconf PUT change whole list entry (same keys)" new "restconf PUT change whole list entry (same keys)"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"x","c":"y","nonkey":"z"}}')" 0 ''
new "restconf PUT change whole list entry (no namespace)(expect fail)" new "restconf PUT change whole list entry (no namespace)(expect fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}} ' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"a":{"b":"x","c":"y","nonkey":"z"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"Top-level JSON object a is not qualified with namespace which is a MUST according to RFC 7951"}}} '
new "restconf PUT change list entry (wrong keys)(expect fail)" new "restconf PUT change list entry (wrong keys)(expect fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y -d '{"list:a":{"b":"y","c":"x"}}')" 0 '412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} '
new "restconf PUT change list entry (wrong keys)(expect fail) XML" new "restconf PUT change list entry (wrong keys)(expect fail) XML"
expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xy</b><c>xz</c><nonkey>0</nonkey></a>' http://localhost/restconf/data/list:c/a=xx,xy)" 0 '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>api-path keys do not match data keys</error-message></error></errors> ' expecteq "$(curl -s -X PUT -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '<a xmlns="urn:example:clixon"><b>xy</b><c>xz</c><nonkey>0</nonkey></a>' $RCPROTO://localhost/restconf/data/list:c/a=xx,xy)" 0 '<errors xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"><error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>api-path keys do not match data keys</error-message></error></errors> '
new "restconf PUT change list entry (just one key)(expect fail)" new "restconf PUT change list entry (just one key)(expect fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} ' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x -d '{"list:a":{"b":"x"}}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} '
new "restconf PUT sub non-key" new "restconf PUT sub non-key"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/nonkey -d '{"list:nonkey":"u"}')" 0 ''
new "restconf PUT sub key same value" new "restconf PUT sub key same value"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/b -d '{"list:b":"x"}')" 0 ''
new "restconf PUT just key other value (should fail)" new "restconf PUT just key other value (should fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/b -d '{"b":"y"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}}'
new "restconf PUT add leaf-list entry" new "restconf PUT add leaf-list entry"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"x"}')" 0 ''
new "restconf PUT change leaf-list entry (expect fail)" new "restconf PUT change leaf-list entry (expect fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/d=x -d '{"list:d":"y"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
new "restconf PUT list-list" new "restconf PUT list-list"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"z","nonkey":"0"}}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"z","nonkey":"0"}}')" 0 ''
new "restconf PUT change list-lst entry (wrong keys)(expect fail)" new "restconf PUT change list-lst entry (wrong keys)(expect fail)"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"wrong","nonkey":"0"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z -d '{"list:e":{"f":"wrong","nonkey":"0"}}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} '
new "restconf PUT list-list sub non-key" new "restconf PUT list-list sub non-key"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d '{"list:nonkey":"u"}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/nonkey -d '{"list:nonkey":"u"}')" 0 ''
new "restconf PUT list-list single first key" new "restconf PUT list-list single first key"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} ' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x/e=z/f -d '{"f":"z"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"List key a length mismatch"}}} '
new "restconf PUT list-list just key ok" new "restconf PUT list-list just key ok"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"z"}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"z"}')" 0 ''
new "restconf PUT list-list just key just key wrong value (should fail)" new "restconf PUT list-list just key just key wrong value (should fail)"
expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"wrong"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} ' expectpart "$(curl -is -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/e=z/f -d '{"list:f":"wrong"}')" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}} '
new "restconf PUT add list+leaf-list entry" new "restconf PUT add list+leaf-list entry"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"u"}')" 0 '' expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"u"}')" 0 ''
new "restconf PUT change list+leaf-list entry (expect fail)" new "restconf PUT change list+leaf-list entry (expect fail)"
expectpart "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}' expectpart "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/list:c/a=x,y/f=u -d '{"list:f":"w"}')" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"api-path keys do not match data keys"}}}'
if [ $RC -ne 0 ]; then if [ $RC -ne 0 ]; then
new "Kill restconf daemon" new "Kill restconf daemon"

View file

@ -119,80 +119,80 @@ wait_restconf
# also in test_restconf.sh # also in test_restconf.sh
new "MUST support the PATCH method for a plain patch" new "MUST support the PATCH method for a plain patch"
expectpart "$(curl -u andy:bar -is -X OPTIONS http://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" "Accept-Patch: application/yang-data+xml,application/yang-data+json" expectpart "$(curl -u andy:bar -is -X OPTIONS $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 200 OK" "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE" "Accept-Patch: application/yang-data+xml,application/yang-data+json"
new "If the target resource instance does not exist, the server MUST NOT create it." new "If the target resource instance does not exist, the server MUST NOT create it."
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 400 Bad Request" expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 400 Bad Request"
new "Create it with PUT instead" new "Create it with PUT instead"
expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":null}')" 0 "HTTP/1.1 201 Created"
new "THEN change it with PATCH" new "THEN change it with PATCH"
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":{"library":{"artist":{"name":"Clash"}}}}')" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -d '{"example-jukebox:jukebox":{"library":{"artist":{"name":"Clash"}}}}')" 0 "HTTP/1.1 204 No Content"
new "Check content (json)" new "Check content (json)"
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Clash"}\]}}}' expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:jukebox":{"library":{"artist":\[{"name":"Clash"}\]}}}'
new "Check content (xml)" new "Check content (xml)"
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name></artist></library></jukebox>' expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name></artist></library></jukebox>'
new 'If the user is not authorized, "403 Forbidden" SHOULD be returned.' new 'If the user is not authorized, "403 Forbidden" SHOULD be returned.'
expectpart "$(curl -u wilma:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' expectpart "$(curl -u wilma:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}'
new 'user is authorized' new 'user is authorized'
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash -d '{"example-jukebox:artist":{"name":"Clash","album":{"name":"London Calling"}}}')" 0 "HTTP/1.1 204 No Content"
# 4.6.1. Plain Patch # 4.6.1. Plain Patch
new "restconf DELETE whole datastore" new "restconf DELETE whole datastore"
expectpart "$(curl -u andy:bar -is -X DELETE http://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -u andy:bar -is -X DELETE $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
new "Create album London Calling with PUT" new "Create album London Calling with PUT"
expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling"}}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling"}}')" 0 "HTTP/1.1 201 Created"
new "The message-body for a plain patch MUST be present" new "The message-body for a plain patch MUST be present"
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Beatles -d '')" 0 "HTTP/1.1 400 Bad Request" expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Beatles -d '')" 0 "HTTP/1.1 400 Bad Request"
# Plain patch can be used to create or update, but not delete, a child # Plain patch can be used to create or update, but not delete, a child
# resource within the target resource. # resource within the target resource.
new "Create a child resource (genre and year)" new "Create a child resource (genre and year)"
expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","genre":"example-jukebox:rock","year":"2129"}}')" 0 'HTTP/1.1 204 No Content' expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","genre":"example-jukebox:rock","year":"2129"}}')" 0 'HTTP/1.1 204 No Content'
new "Update a child resource (year)" new "Update a child resource (year)"
expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","year":"1979"}}')" 0 'HTTP/1.1 204 No Content' expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"London Calling","year":"1979"}}')" 0 'HTTP/1.1 204 No Content'
new "Check content xml" new "Check content xml"
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>rock</genre><year>1979</year></album>' expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>rock</genre><year>1979</year></album>'
new "Check content json" new "Check content json"
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:album":\[{"name":"London Calling","genre":"rock","year":1979}\]}' expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:album":\[{"name":"London Calling","genre":"rock","year":1979}\]}'
new "The message-body MUST be represented by the media type application/yang-data+xml (or +json ^)" new "The message-body MUST be represented by the media type application/yang-data+xml (or +json ^)"
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 204 No Content" expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 204 No Content"
new "Check content (xml)" new "Check content (xml)"
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><genre>jazz</genre><year>1979</year></album></artist></library></jukebox>' expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:jukebox -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<jukebox xmlns="http://example.com/ns/example-jukebox"><library><artist><name>Clash</name><album><name>London Calling</name><genre>jazz</genre><year>1979</year></album></artist></library></jukebox>'
new "not implemented media type" new "not implemented media type"
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-patch+xml' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 501 Not Implemented" expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-patch+xml' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 501 Not Implemented"
new "wrong media type" new "wrong media type"
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: text/html' http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 415 Unsupported Media Type" expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: text/html' $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '<album xmlns="http://example.com/ns/example-jukebox"><name>London Calling</name><genre>jazz</genre></album>')" 0 "HTTP/1.1 415 Unsupported Media Type"
# If the target resource represents a YANG leaf-list, then the PATCH # If the target resource represents a YANG leaf-list, then the PATCH
# method MUST NOT change the value of the leaf-list instance. # method MUST NOT change the value of the leaf-list instance.
# leaf-list extra{ # leaf-list extra{
new "Create leaf-list a" new "Create leaf-list a"
expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"a"}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"a"}')" 0 "HTTP/1.1 201 Created"
new "Create leaf-list b" new "Create leaf-list b"
expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=b -d '{"example-jukebox:extra":"b"}')" 0 "HTTP/1.1 201 Created" expectpart "$(curl -u andy:bar -si -X PUT -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=b -d '{"example-jukebox:extra":"b"}')" 0 "HTTP/1.1 201 Created"
new "Check content" new "Check content"
expectpart "$(curl -u andy:bar -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:extra":\["a","b"\]}' expectpart "$(curl -u andy:bar -si -X GET $RCPROTO://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+json')" 0 'HTTP/1.1 200 OK' '{"example-jukebox:extra":\["a","b"\]}'
new "MUST NOT change the value of the leaf-list instance" new "MUST NOT change the value of the leaf-list instance"
expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"b"}')" 0 'HTTP/1.1 412 Precondition Failed' expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-data+json' $RCPROTO://localhost/restconf/data/example-jukebox:extra=a -d '{"example-jukebox:extra":"b"}')" 0 'HTTP/1.1 412 Precondition Failed'
# If the target resource represents a YANG list instance, then the key # If the target resource represents a YANG list instance, then the key
# leaf values, in message-body representation, MUST be the same as the # leaf values, in message-body representation, MUST be the same as the
@ -200,7 +200,7 @@ expectpart "$(curl -u andy:bar -si -X PATCH -H 'Content-Type: application/yang-d
# used to change the key leaf values for a data resource instance. # used to change the key leaf values for a data resource instance.
new "The key leaf values MUST be the same as the key leaf values in the request" new "The key leaf values MUST be the same as the key leaf values in the request"
expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"The Clash"}}')" 0 'HTTP/1.1 412 Precondition Failed' expectpart "$(curl -u andy:bar -si -X PATCH -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example-jukebox:jukebox/library/artist=Clash/album=London%20Calling -d '{"example-jukebox:album":{"name":"The Clash"}}')" 0 'HTTP/1.1 412 Precondition Failed'
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -74,16 +74,16 @@ testrun(){
wait_restconf wait_restconf
new "restconf put 42" new "restconf put 42"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 "" expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=42 -d '{"example:y":{"a":"42","b":"42"}}')" 0 ""
new "restconf put 99" new "restconf put 99"
expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 "" expecteq "$(curl -s -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x/y=99 -d '{"example:y":{"a":"99","b":"99"}}')" 0 ""
new "restconf post 123" new "restconf post 123"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 "" expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/example:x -d '{"example:y":{"a":"123","b":"123"}}')" 0 ""
new "restconf delete 42" new "restconf delete 42"
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/example:x/y=42)" 0 "" expecteq "$(curl -s -X DELETE $RCPROTO://localhost/restconf/data/example:x/y=42)" 0 ""
new "Kill restconf daemon" new "Kill restconf daemon"
stop_restconf stop_restconf

View file

@ -118,6 +118,7 @@ main(int argc, char **argv)
int i; int i;
char *xpath; char *xpath;
cbuf *cbret = NULL; cbuf *cbret = NULL;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
@ -135,7 +136,7 @@ main(int argc, char **argv)
usage(argv0); usage(argv0);
break; break;
case 'D' : /* debug */ case 'D' : /* debug */
debug = 1; dbg = 1;
break; break;
case 'd': /* db symbolic: running|candidate|startup */ case 'd': /* db symbolic: running|candidate|startup */
if (!optarg) if (!optarg)
@ -166,8 +167,8 @@ main(int argc, char **argv)
/* /*
* Logs, error and debug to stderr, set debug level * Logs, error and debug to stderr, set debug level
*/ */
clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR); clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR);
clicon_debug_init(debug, NULL); clicon_debug_init(dbg, NULL);
argc -= optind; argc -= optind;
argv += optind; argv += optind;

View file

@ -96,6 +96,7 @@ main(int argc,
cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xerr = NULL; /* malloced must be freed */
int ret; int ret;
int pretty = 0; int pretty = 0;
int dbg = 0;
optind = 1; optind = 1;
opterr = 0; opterr = 0;
@ -105,7 +106,7 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
break; break;
case 'D': case 'D':
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]); usage(argv[0]);
break; break;
case 'j': case 'j':
@ -125,7 +126,9 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
break; break;
} }
clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(dbg, NULL);
if (yang_filename){ if (yang_filename){
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;

View file

@ -109,6 +109,7 @@ main(int argc,
cxobj *xcfg = NULL; cxobj *xcfg = NULL;
cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xerr = NULL; /* malloced must be freed */
int nr = 1; int nr = 1;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init("api-path", LOG_DEBUG, CLICON_LOG_STDERR); clicon_log_init("api-path", LOG_DEBUG, CLICON_LOG_STDERR);
@ -129,7 +130,7 @@ main(int argc,
usage(argv0); usage(argv0);
break; break;
case 'D': case 'D':
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0); usage(argv0);
break; break;
case 'f': /* XML file */ case 'f': /* XML file */
@ -159,6 +160,8 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
break; break;
} }
clicon_debug_init(dbg, NULL);
/* Parse yang */ /* Parse yang */
if (yang_file_dir){ if (yang_file_dir){
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)

View file

@ -163,6 +163,7 @@ main(int argc,
int ret = 0; int ret = 0;
int nr = 1; int nr = 1;
int mode = 0; /* 0 is posix, 1 is libxml */ int mode = 0; /* 0 is posix, 1 is libxml */
int dbg = 0;
optind = 1; optind = 1;
opterr = 0; opterr = 0;
@ -172,7 +173,7 @@ main(int argc,
usage(argv0); usage(argv0);
break; break;
case 'D': case 'D':
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0); usage(argv0);
break; break;
case 'p': /* xsd->posix */ case 'p': /* xsd->posix */
@ -195,7 +196,9 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
break; break;
} }
clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR); clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR);
clicon_debug_init(dbg, NULL);
if (regexp == NULL){ if (regexp == NULL){
fprintf(stderr, "-r mandatory\n"); fprintf(stderr, "-r mandatory\n");
usage(argv0); usage(argv0);
@ -211,12 +214,12 @@ main(int argc,
clicon_debug(1, "regexp:%s", regexp); clicon_debug(1, "regexp:%s", regexp);
clicon_debug(1, "content:%s", content); clicon_debug(1, "content:%s", content);
if (mode == 0){ if (mode == 0){
if ((ret = regex_posix(regexp, content, nr, debug)) < 0) if ((ret = regex_posix(regexp, content, nr, dbg)) < 0)
goto done; goto done;
} }
else if (mode == 1){ else if (mode == 1){
if ((ret = regex_libxml2(regexp, content, nr, debug)) < 0) if ((ret = regex_libxml2(regexp, content, nr, dbg)) < 0)
goto done; goto done;
} }
else else

View file

@ -95,6 +95,7 @@ main(int argc,
int ret; int ret;
cbuf *cb = cbuf_new(); cbuf *cb = cbuf_new();
clicon_handle h; clicon_handle h;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
@ -110,7 +111,7 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
break; break;
case 'D': case 'D':
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]); usage(argv[0]);
break; break;
case 's': case 's':
@ -129,7 +130,9 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
break; break;
} }
clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(dbg, NULL);
if (sockpath == NULL){ if (sockpath == NULL){
fprintf(stderr, "Mandatory option missing: -s <sockpath>\n"); fprintf(stderr, "Mandatory option missing: -s <sockpath>\n");
usage(argv[0]); usage(argv[0]);

View file

@ -219,6 +219,7 @@ main(int argc, char **argv)
int c; int c;
char *argv0 = argv[0]; char *argv0 = argv[0];
struct timeval now; struct timeval now;
int dbg = 0;
clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR); clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR);
gettimeofday(&now, NULL); gettimeofday(&now, NULL);
@ -230,7 +231,7 @@ main(int argc, char **argv)
usage(argv0); usage(argv0);
break; break;
case 'D': case 'D':
debug++; dbg = 1;
break; break;
case 'u': /* URL */ case 'u': /* URL */
url = optarg; url = optarg;
@ -264,6 +265,7 @@ main(int argc, char **argv)
usage(argv[0]); usage(argv[0]);
break; break;
} }
clicon_debug_init(dbg, NULL);
if (url == NULL) if (url == NULL)
usage(argv[0]); usage(argv[0]);
curl_global_init(0); curl_global_init(0);

View file

@ -163,6 +163,7 @@ main(int argc,
cxobj *xbot; /* Place in xtop where base cxobj is parsed */ cxobj *xbot; /* Place in xtop where base cxobj is parsed */
cvec *nsc = NULL; cvec *nsc = NULL;
yang_bind yb; yang_bind yb;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
@ -184,7 +185,7 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
break; break;
case 'D': case 'D':
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]); usage(argv[0]);
break; break;
case 'f': case 'f':
@ -239,7 +240,9 @@ main(int argc,
fprintf(stderr, "-t requires -T\n"); fprintf(stderr, "-t requires -T\n");
usage(argv[0]); usage(argv[0]);
} }
clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(dbg, NULL);
/* 1. Parse yang */ /* 1. Parse yang */
if (yang_file_dir){ if (yang_file_dir){
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
@ -319,7 +322,7 @@ main(int argc,
} }
/* Dump data structures (for debug) */ /* Dump data structures (for debug) */
if (debug){ if (clicon_debug_get()){
cbuf_reset(cb); cbuf_reset(cb);
xmltree2cbuf(cb, xt, 0); xmltree2cbuf(cb, xt, 0);
fprintf(stderr, "%s\n", cbuf_get(cb)); fprintf(stderr, "%s\n", cbuf_get(cb));

View file

@ -129,6 +129,7 @@ main(int argc, char **argv)
clicon_handle h; clicon_handle h;
enum opx opx = OPX_ERROR; enum opx opx = OPX_ERROR;
char *reason = NULL; char *reason = NULL;
int dbg = 0;
clicon_log_init("clixon_insert", LOG_DEBUG, CLICON_LOG_STDERR); clicon_log_init("clixon_insert", LOG_DEBUG, CLICON_LOG_STDERR);
if ((h = clicon_handle_init()) == NULL) if ((h = clicon_handle_init()) == NULL)
@ -141,7 +142,7 @@ main(int argc, char **argv)
usage(argv0); usage(argv0);
break; break;
case 'D': case 'D':
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0); usage(argv0);
break; break;
case 'o': /* Operation */ case 'o': /* Operation */
@ -171,6 +172,7 @@ main(int argc, char **argv)
usage(argv0); usage(argv0);
if (opx == OPX_ERROR) if (opx == OPX_ERROR)
usage(argv0); usage(argv0);
clicon_debug_init(dbg, NULL);
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;
if (yang_spec_parse_file(h, yangfile, yspec) < 0) if (yang_spec_parse_file(h, yangfile, yspec) < 0)
@ -189,7 +191,7 @@ main(int argc, char **argv)
clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath); clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath);
goto done; goto done;
} }
if (debug){ if (clicon_debug_get()){
clicon_debug(1, "xb:"); clicon_debug(1, "xb:");
xml_print(stderr, xb); xml_print(stderr, xb);
} }
@ -256,7 +258,7 @@ main(int argc, char **argv)
default: default:
usage(argv0); usage(argv0);
} }
if (debug){ if (clicon_debug_get()){
clicon_debug(1, "x0:"); clicon_debug(1, "x0:");
xml_print(stderr, x0); xml_print(stderr, x0);
} }

View file

@ -140,6 +140,7 @@ main(int argc,
cxobj *xcfg = NULL; cxobj *xcfg = NULL;
cbuf *cbret = NULL; cbuf *cbret = NULL;
cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xerr = NULL; /* malloced must be freed */
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR); clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR);
@ -160,7 +161,7 @@ main(int argc,
usage(argv0); usage(argv0);
break; break;
case 'D': case 'D':
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0); usage(argv0);
break; break;
case 'f': /* XML file */ case 'f': /* XML file */
@ -210,6 +211,8 @@ main(int argc,
usage(argv[0]); usage(argv[0]);
break; break;
} }
clicon_debug_init(dbg, NULL);
/* Parse yang */ /* Parse yang */
if (yang_file_dir){ if (yang_file_dir){
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)

View file

@ -81,6 +81,7 @@ main(int argc, char **argv)
yang_stmt *yspec = NULL; yang_stmt *yspec = NULL;
int c; int c;
int logdst = CLICON_LOG_STDERR; int logdst = CLICON_LOG_STDERR;
int dbg = 0;
optind = 1; optind = 1;
opterr = 0; opterr = 0;
@ -90,7 +91,7 @@ main(int argc, char **argv)
usage(argv[0]); usage(argv[0]);
break; break;
case 'D': case 'D':
if (sscanf(optarg, "%d", &debug) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]); usage(argv[0]);
break; break;
case 'l': /* Log destination: s|e|o|f */ case 'l': /* Log destination: s|e|o|f */
@ -101,7 +102,8 @@ main(int argc, char **argv)
usage(argv[0]); usage(argv[0]);
break; break;
} }
clicon_log_init("clixon_util_yang", debug?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init("clixon_util_yang", dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(dbg, NULL);
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;
if (yang_parse_file(0, "yang test", yspec) == NULL){ if (yang_parse_file(0, "yang test", yspec) == NULL){

View file

@ -148,16 +148,19 @@ module clixon-config {
typedef cli_genmodel_type{ typedef cli_genmodel_type{
description description
"How to generate CLI from YANG model, "How to generate CLI from YANG model,
eg list a{ key x; leaf x; leaf y;}"; eg {container c {list a{ key x; leaf x; leaf y;}}";
type enumeration{ type enumeration{
enum NONE{ enum NONE{
description "No extra keywords: a <x> <y>"; description "No extra keywords: c a <x> <y>";
} }
enum VARS{ enum VARS{
description "Keywords on non-key variables: a <x> y <y>"; description "Keywords on non-key variables: c a <x> y <y>";
} }
enum ALL{ enum ALL{
description "Keywords on all variables: a x <x> y <y>"; description "Keywords on all variables: c a x <x> y <y>";
}
enum HIDE{
description "Keywords on non-key variables and hide container around lists: a <x> y <y>";
} }
} }
} }
@ -399,11 +402,12 @@ module clixon-config {
type int32; type int32;
default 1; default 1;
description description
"If set, generate CLI specification for CLI completion of "0: Do not generate CLISPEC syntax for the auto-cli.
loaded Yang modules. This CLI tree can be accessed in CLI 1: Generate a CLI specification for CLI completion of all loaded Yang modules.
spec files using the tree reference syntax (eg @datamodel). This CLI tree can be accessed in CLI-spec files using the tree reference syntax (eg
See also CLICON_CLI_MODEL_TREENAME. @datamodel).
(consider boolean)"; 2: Same including state syntax in a tree called @datamodelstate.
See also CLICON_CLI_MODEL_TREENAME.";
} }
leaf CLICON_CLI_MODEL_TREENAME { leaf CLICON_CLI_MODEL_TREENAME {
type string; type string;
@ -411,7 +415,9 @@ module clixon-config {
description description
"If set, CLI specs can reference the "If set, CLI specs can reference the
model syntax using this reference. model syntax using this reference.
Example: set @datamodel, cli_set();"; Example: set @datamodel, cli_set();
A second tree called eg @datamodelstate is created that
also contains state together with config.";
} }
leaf CLICON_CLI_GENMODEL_COMPLETION { leaf CLICON_CLI_GENMODEL_COMPLETION {
type int32; type int32;
@ -595,7 +601,8 @@ module clixon-config {
description description
"If set, tag datastores with RFC 7895 YANG Module Library "If set, tag datastores with RFC 7895 YANG Module Library
info. When loaded at startup, a check is made if the system info. When loaded at startup, a check is made if the system
yang modules match"; yang modules match.
See also CLICON_MODULE_LIBRARY_RFC7895";
} }
leaf CLICON_XML_CHANGELOG { leaf CLICON_XML_CHANGELOG {
type boolean; type boolean;
@ -669,9 +676,11 @@ module clixon-config {
leaf CLICON_MODULE_LIBRARY_RFC7895 { leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean; type boolean;
default true; default true;
description "Enable RFC 7895 YANG Module library support as state description
data. If enabled, module info will appear when doing "Enable RFC 7895 YANG Module library support as state data. If
netconf get or restconf GET"; enabled, module info will appear when doing netconf get or
restconf GET.
See also CLICON_XMLDB_MODSTATE";
} }
leaf CLICON_MODULE_SET_ID { leaf CLICON_MODULE_SET_ID {
type string; type string;